]> git.proxmox.com Git - mirror_novnc.git/blob - tests/test.rfb.js
Cleanup: RFB Client
[mirror_novnc.git] / tests / test.rfb.js
1 // requires local modules: util, base64, websock, rfb, keyboard, keysym, keysymdef, input, jsunzip, des, display
2 // requires test modules: fake.websocket
3 /* jshint expr: true */
4 var assert = chai.assert;
5 var expect = chai.expect;
6
7 function make_rfb (extra_opts) {
8 if (!extra_opts) {
9 extra_opts = {};
10 }
11
12 extra_opts.target = extra_opts.target || document.createElement('canvas');
13 return new RFB(extra_opts);
14 }
15
16 // some useful assertions for noVNC
17 chai.use(function (_chai, utils) {
18 _chai.Assertion.addMethod('displayed', function (target_data) {
19 var obj = this._obj;
20 var data_cl = obj._drawCtx.getImageData(0, 0, obj._fb_width, obj._fb_height).data;
21 // NB(directxman12): PhantomJS 1.x doesn't implement Uint8ClampedArray, so work around that
22 var data = new Uint8Array(data_cl);
23 this.assert(utils.eql(data, target_data),
24 "expected #{this} to have displayed the image #{exp}, but instead it displayed #{act}",
25 "expected #{this} not to have displayed the image #{act}",
26 target_data,
27 data);
28 });
29
30 _chai.Assertion.addMethod('sent', function (target_data) {
31 var obj = this._obj;
32 var data = obj._websocket._get_sent_data();
33 this.assert(utils.eql(data, target_data),
34 "expected #{this} to have sent the data #{exp}, but it actually sent #{act}",
35 "expected #{this} not to have sent the data #{act}",
36 target_data,
37 data);
38 });
39 });
40
41 describe('Remote Frame Buffer Protocol Client', function() {
42 "use strict";
43 before(FakeWebSocket.replace);
44 after(FakeWebSocket.restore);
45
46 describe('Public API Basic Behavior', function () {
47 var client;
48 beforeEach(function () {
49 client = make_rfb();
50 });
51
52 describe('#connect', function () {
53 beforeEach(function () { client._updateState = sinon.spy(); });
54
55 it('should set the current state to "connect"', function () {
56 client.connect('host', 8675);
57 expect(client._updateState).to.have.been.calledOnce;
58 expect(client._updateState).to.have.been.calledWith('connect');
59 });
60
61 it('should fail if we are missing a host', function () {
62 sinon.spy(client, '_fail');
63 client.connect(undefined, 8675);
64 expect(client._fail).to.have.been.calledOnce;
65 });
66
67 it('should fail if we are missing a port', function () {
68 sinon.spy(client, '_fail');
69 client.connect('abc');
70 expect(client._fail).to.have.been.calledOnce;
71 });
72
73 it('should not update the state if we are missing a host or port', function () {
74 sinon.spy(client, '_fail');
75 client.connect('abc');
76 expect(client._fail).to.have.been.calledOnce;
77 expect(client._updateState).to.have.been.calledOnce;
78 expect(client._updateState).to.have.been.calledWith('failed');
79 });
80 });
81
82 describe('#disconnect', function () {
83 beforeEach(function () { client._updateState = sinon.spy(); });
84
85 it('should set the current state to "disconnect"', function () {
86 client.disconnect();
87 expect(client._updateState).to.have.been.calledOnce;
88 expect(client._updateState).to.have.been.calledWith('disconnect');
89 });
90 });
91
92 describe('#sendPassword', function () {
93 beforeEach(function () { this.clock = sinon.useFakeTimers(); });
94 afterEach(function () { this.clock.restore(); });
95
96 it('should set the state to "Authentication"', function () {
97 client._rfb_state = "blah";
98 client.sendPassword('pass');
99 expect(client._rfb_state).to.equal('Authentication');
100 });
101
102 it('should call init_msg "soon"', function () {
103 client._init_msg = sinon.spy();
104 client.sendPassword('pass');
105 this.clock.tick(5);
106 expect(client._init_msg).to.have.been.calledOnce;
107 });
108 });
109
110 describe('#sendCtrlAlDel', function () {
111 beforeEach(function () {
112 client._sock = new Websock();
113 client._sock.open('ws://', 'binary');
114 client._sock._websocket._open();
115 sinon.spy(client._sock, 'send');
116 client._rfb_state = "normal";
117 client._view_only = false;
118 });
119
120 it('should sent ctrl[down]-alt[down]-del[down] then del[up]-alt[up]-ctrl[up]', function () {
121 var expected = [];
122 expected = expected.concat(RFB.messages.keyEvent(0xFFE3, 1));
123 expected = expected.concat(RFB.messages.keyEvent(0xFFE9, 1));
124 expected = expected.concat(RFB.messages.keyEvent(0xFFFF, 1));
125 expected = expected.concat(RFB.messages.keyEvent(0xFFFF, 0));
126 expected = expected.concat(RFB.messages.keyEvent(0xFFE9, 0));
127 expected = expected.concat(RFB.messages.keyEvent(0xFFE3, 0));
128
129 client.sendCtrlAltDel();
130 expect(client._sock).to.have.sent(expected);
131 });
132
133 it('should not send the keys if we are not in a normal state', function () {
134 client._rfb_state = "broken";
135 client.sendCtrlAltDel();
136 expect(client._sock.send).to.not.have.been.called;
137 });
138
139 it('should not send the keys if we are set as view_only', function () {
140 client._view_only = true;
141 client.sendCtrlAltDel();
142 expect(client._sock.send).to.not.have.been.called;
143 });
144 });
145
146 describe('#sendKey', function () {
147 beforeEach(function () {
148 client._sock = new Websock();
149 client._sock.open('ws://', 'binary');
150 client._sock._websocket._open();
151 sinon.spy(client._sock, 'send');
152 client._rfb_state = "normal";
153 client._view_only = false;
154 });
155
156 it('should send a single key with the given code and state (down = true)', function () {
157 var expected = RFB.messages.keyEvent(123, 1);
158 client.sendKey(123, true);
159 expect(client._sock).to.have.sent(expected);
160 });
161
162 it('should send both a down and up event if the state is not specified', function () {
163 var expected = RFB.messages.keyEvent(123, 1);
164 expected = expected.concat(RFB.messages.keyEvent(123, 0));
165 client.sendKey(123);
166 expect(client._sock).to.have.sent(expected);
167 });
168
169 it('should not send the key if we are not in a normal state', function () {
170 client._rfb_state = "broken";
171 client.sendKey(123);
172 expect(client._sock.send).to.not.have.been.called;
173 });
174
175 it('should not send the key if we are set as view_only', function () {
176 client._view_only = true;
177 client.sendKey(123);
178 expect(client._sock.send).to.not.have.been.called;
179 });
180 });
181
182 describe('#clipboardPasteFrom', function () {
183 beforeEach(function () {
184 client._sock = new Websock();
185 client._sock.open('ws://', 'binary');
186 client._sock._websocket._open();
187 sinon.spy(client._sock, 'send');
188 client._rfb_state = "normal";
189 client._view_only = false;
190 });
191
192 it('should send the given text in a paste event', function () {
193 var expected = RFB.messages.clientCutText('abc');
194 client.clipboardPasteFrom('abc');
195 expect(client._sock).to.have.sent(expected);
196 });
197
198 it('should not send the text if we are not in a normal state', function () {
199 client._rfb_state = "broken";
200 client.clipboardPasteFrom('abc');
201 expect(client._sock.send).to.not.have.been.called;
202 });
203 });
204
205 describe("XVP operations", function () {
206 beforeEach(function () {
207 client._sock = new Websock();
208 client._sock.open('ws://', 'binary');
209 client._sock._websocket._open();
210 sinon.spy(client._sock, 'send');
211 client._rfb_state = "normal";
212 client._view_only = false;
213 client._rfb_xvp_ver = 1;
214 });
215
216 it('should send the shutdown signal on #xvpShutdown', function () {
217 client.xvpShutdown();
218 expect(client._sock).to.have.sent([0xFA, 0x00, 0x01, 0x02]);
219 });
220
221 it('should send the reboot signal on #xvpReboot', function () {
222 client.xvpReboot();
223 expect(client._sock).to.have.sent([0xFA, 0x00, 0x01, 0x03]);
224 });
225
226 it('should send the reset signal on #xvpReset', function () {
227 client.xvpReset();
228 expect(client._sock).to.have.sent([0xFA, 0x00, 0x01, 0x04]);
229 });
230
231 it('should support sending arbitrary XVP operations via #xvpOp', function () {
232 client.xvpOp(1, 7);
233 expect(client._sock).to.have.sent([0xFA, 0x00, 0x01, 0x07]);
234 });
235
236 it('should not send XVP operations with higher versions than we support', function () {
237 expect(client.xvpOp(2, 7)).to.be.false;
238 expect(client._sock.send).to.not.have.been.called;
239 });
240 });
241 });
242
243 describe('Misc Internals', function () {
244 describe('#_updateState', function () {
245 var client;
246 beforeEach(function () {
247 this.clock = sinon.useFakeTimers();
248 client = make_rfb();
249 });
250
251 afterEach(function () {
252 this.clock.restore();
253 });
254
255 it('should clear the disconnect timer if the state is not disconnect', function () {
256 var spy = sinon.spy();
257 client._disconnTimer = setTimeout(spy, 50);
258 client._updateState('normal');
259 this.clock.tick(51);
260 expect(spy).to.not.have.been.called;
261 expect(client._disconnTimer).to.be.null;
262 });
263 });
264 });
265
266 describe('Page States', function () {
267 describe('loaded', function () {
268 var client;
269 beforeEach(function () { client = make_rfb(); });
270
271 it('should close any open WebSocket connection', function () {
272 sinon.spy(client._sock, 'close');
273 client._updateState('loaded');
274 expect(client._sock.close).to.have.been.calledOnce;
275 });
276 });
277
278 describe('disconnected', function () {
279 var client;
280 beforeEach(function () { client = make_rfb(); });
281
282 it('should close any open WebSocket connection', function () {
283 sinon.spy(client._sock, 'close');
284 client._updateState('disconnected');
285 expect(client._sock.close).to.have.been.calledOnce;
286 });
287 });
288
289 describe('connect', function () {
290 var client;
291 beforeEach(function () { client = make_rfb(); });
292
293 it('should reset the variable states', function () {
294 sinon.spy(client, '_init_vars');
295 client._updateState('connect');
296 expect(client._init_vars).to.have.been.calledOnce;
297 });
298
299 it('should actually connect to the websocket', function () {
300 sinon.spy(client._sock, 'open');
301 client._updateState('connect');
302 expect(client._sock.open).to.have.been.calledOnce;
303 });
304
305 it('should use wss:// to connect if encryption is enabled', function () {
306 sinon.spy(client._sock, 'open');
307 client.set_encrypt(true);
308 client._updateState('connect');
309 expect(client._sock.open.args[0][0]).to.contain('wss://');
310 });
311
312 it('should use ws:// to connect if encryption is not enabled', function () {
313 sinon.spy(client._sock, 'open');
314 client.set_encrypt(true);
315 client._updateState('connect');
316 expect(client._sock.open.args[0][0]).to.contain('wss://');
317 });
318
319 it('should use a uri with the host, port, and path specified to connect', function () {
320 sinon.spy(client._sock, 'open');
321 client.set_encrypt(false);
322 client._rfb_host = 'HOST';
323 client._rfb_port = 8675;
324 client._rfb_path = 'PATH';
325 client._updateState('connect');
326 expect(client._sock.open).to.have.been.calledWith('ws://HOST:8675/PATH');
327 });
328
329 it('should attempt to close the websocket before we open an new one', function () {
330 sinon.spy(client._sock, 'close');
331 client._updateState('connect');
332 expect(client._sock.close).to.have.been.calledOnce;
333 });
334 });
335
336 describe('disconnect', function () {
337 var client;
338 beforeEach(function () {
339 this.clock = sinon.useFakeTimers();
340 client = make_rfb();
341 client.connect('host', 8675);
342 });
343
344 afterEach(function () {
345 this.clock.restore();
346 });
347
348 it('should fail if we do not call Websock.onclose within the disconnection timeout', function () {
349 client._sock._websocket.close = function () {}; // explicitly don't call onclose
350 client._updateState('disconnect');
351 this.clock.tick(client.get_disconnectTimeout() * 1000);
352 expect(client._rfb_state).to.equal('failed');
353 });
354
355 it('should not fail if Websock.onclose gets called within the disconnection timeout', function () {
356 client._updateState('disconnect');
357 this.clock.tick(client.get_disconnectTimeout() * 500);
358 client._sock._websocket.close();
359 this.clock.tick(client.get_disconnectTimeout() * 500 + 1);
360 expect(client._rfb_state).to.equal('disconnected');
361 });
362
363 it('should close the WebSocket connection', function () {
364 sinon.spy(client._sock, 'close');
365 client._updateState('disconnect');
366 expect(client._sock.close).to.have.been.calledTwice; // once on loaded, once on disconnect
367 });
368 });
369
370 describe('failed', function () {
371 var client;
372 beforeEach(function () {
373 this.clock = sinon.useFakeTimers();
374 client = make_rfb();
375 client.connect('host', 8675);
376 });
377
378 afterEach(function () {
379 this.clock.restore();
380 });
381
382 it('should close the WebSocket connection', function () {
383 sinon.spy(client._sock, 'close');
384 client._updateState('failed');
385 expect(client._sock.close).to.have.been.called;
386 });
387
388 it('should transition to disconnected but stay in failed state', function () {
389 client.set_onUpdateState(sinon.spy());
390 client._updateState('failed');
391 this.clock.tick(50);
392 expect(client._rfb_state).to.equal('failed');
393
394 var onUpdateState = client.get_onUpdateState();
395 expect(onUpdateState).to.have.been.called;
396 // it should be specifically the last call
397 expect(onUpdateState.args[onUpdateState.args.length - 1][1]).to.equal('disconnected');
398 expect(onUpdateState.args[onUpdateState.args.length - 1][2]).to.equal('failed');
399 });
400
401 });
402
403 describe('fatal', function () {
404 var client;
405 beforeEach(function () { client = make_rfb(); });
406
407 it('should close any open WebSocket connection', function () {
408 sinon.spy(client._sock, 'close');
409 client._updateState('fatal');
410 expect(client._sock.close).to.have.been.calledOnce;
411 });
412 });
413
414 // NB(directxman12): Normal does *nothing* in updateState
415 });
416
417 describe('Protocol Initialization States', function () {
418 describe('ProtocolVersion', function () {
419 beforeEach(function () {
420 this.clock = sinon.useFakeTimers();
421 });
422
423 afterEach(function () {
424 this.clock.restore();
425 });
426
427 function send_ver (ver, client) {
428 var arr = new Uint8Array(12);
429 for (var i = 0; i < ver.length; i++) {
430 arr[i+4] = ver.charCodeAt(i);
431 }
432 arr[0] = 'R'; arr[1] = 'F'; arr[2] = 'B'; arr[3] = ' ';
433 arr[11] = '\n';
434 client._sock._websocket._receive_data(arr);
435 }
436
437 describe('version parsing', function () {
438 var client;
439 beforeEach(function () {
440 client = make_rfb();
441 client.connect('host', 8675);
442 client._sock._websocket._open();
443 });
444
445 it('should interpret version 000.000 as a repeater', function () {
446 client._repeaterID = '\x01\x02\x03\x04\x05';
447 send_ver('000.000', client);
448 expect(client._rfb_version).to.equal(0);
449
450 var sent_data = client._sock._websocket._get_sent_data();
451 expect(sent_data.slice(0, 5)).to.deep.equal([1, 2, 3, 4, 5]);
452 });
453
454 it('should interpret version 003.003 as version 3.3', function () {
455 send_ver('003.003', client);
456 expect(client._rfb_version).to.equal(3.3);
457 });
458
459 it('should interpret version 003.006 as version 3.3', function () {
460 send_ver('003.006', client);
461 expect(client._rfb_version).to.equal(3.3);
462 });
463
464 it('should interpret version 003.889 as version 3.3', function () {
465 send_ver('003.889', client);
466 expect(client._rfb_version).to.equal(3.3);
467 });
468
469 it('should interpret version 003.007 as version 3.7', function () {
470 send_ver('003.007', client);
471 expect(client._rfb_version).to.equal(3.7);
472 });
473
474 it('should interpret version 003.008 as version 3.8', function () {
475 send_ver('003.008', client);
476 expect(client._rfb_version).to.equal(3.8);
477 });
478
479 it('should interpret version 004.000 as version 3.8', function () {
480 send_ver('004.000', client);
481 expect(client._rfb_version).to.equal(3.8);
482 });
483
484 it('should interpret version 004.001 as version 3.8', function () {
485 send_ver('004.001', client);
486 expect(client._rfb_version).to.equal(3.8);
487 });
488
489 it('should fail on an invalid version', function () {
490 send_ver('002.000', client);
491 expect(client._rfb_state).to.equal('failed');
492 });
493 });
494
495 var client;
496 beforeEach(function () {
497 client = make_rfb();
498 client.connect('host', 8675);
499 client._sock._websocket._open();
500 });
501
502 it('should handle two step repeater negotiation', function () {
503 client._repeaterID = '\x01\x02\x03\x04\x05';
504
505 send_ver('000.000', client);
506 expect(client._rfb_version).to.equal(0);
507 var sent_data = client._sock._websocket._get_sent_data();
508 expect(sent_data.slice(0, 5)).to.deep.equal([1, 2, 3, 4, 5]);
509 expect(sent_data).to.have.length(250);
510
511 send_ver('003.008', client);
512 expect(client._rfb_version).to.equal(3.8);
513 });
514
515 it('should initialize the flush interval', function () {
516 client._sock.flush = sinon.spy();
517 send_ver('003.008', client);
518 this.clock.tick(100);
519 expect(client._sock.flush).to.have.been.calledThrice;
520 });
521
522 it('should send back the interpreted version', function () {
523 send_ver('004.000', client);
524
525 var expected_str = 'RFB 003.008\n';
526 var expected = [];
527 for (var i = 0; i < expected_str.length; i++) {
528 expected[i] = expected_str.charCodeAt(i);
529 }
530
531 expect(client._sock).to.have.sent(expected);
532 });
533
534 it('should transition to the Security state on successful negotiation', function () {
535 send_ver('003.008', client);
536 expect(client._rfb_state).to.equal('Security');
537 });
538 });
539
540 describe('Security', function () {
541 var client;
542
543 beforeEach(function () {
544 client = make_rfb();
545 client.connect('host', 8675);
546 client._sock._websocket._open();
547 client._rfb_state = 'Security';
548 });
549
550 it('should simply receive the auth scheme when for versions < 3.7', function () {
551 client._rfb_version = 3.6;
552 var auth_scheme_raw = [1, 2, 3, 4];
553 var auth_scheme = (auth_scheme_raw[0] << 24) + (auth_scheme_raw[1] << 16) +
554 (auth_scheme_raw[2] << 8) + auth_scheme_raw[3];
555 client._sock._websocket._receive_data(auth_scheme_raw);
556 expect(client._rfb_auth_scheme).to.equal(auth_scheme);
557 });
558
559 it('should choose for the most prefered scheme possible for versions >= 3.7', function () {
560 client._rfb_version = 3.7;
561 var auth_schemes = [2, 1, 2];
562 client._sock._websocket._receive_data(auth_schemes);
563 expect(client._rfb_auth_scheme).to.equal(2);
564 expect(client._sock).to.have.sent([2]);
565 });
566
567 it('should fail if there are no supported schemes for versions >= 3.7', function () {
568 client._rfb_version = 3.7;
569 var auth_schemes = [1, 32];
570 client._sock._websocket._receive_data(auth_schemes);
571 expect(client._rfb_state).to.equal('failed');
572 });
573
574 it('should fail with the appropriate message if no types are sent for versions >= 3.7', function () {
575 client._rfb_version = 3.7;
576 var failure_data = [0, 0, 0, 0, 6, 119, 104, 111, 111, 112, 115];
577 sinon.spy(client, '_fail');
578 client._sock._websocket._receive_data(failure_data);
579
580 expect(client._fail).to.have.been.calledTwice;
581 expect(client._fail).to.have.been.calledWith('Security failure: whoops');
582 });
583
584 it('should transition to the Authentication state and continue on successful negotiation', function () {
585 client._rfb_version = 3.7;
586 var auth_schemes = [1, 1];
587 client._negotiate_authentication = sinon.spy();
588 client._sock._websocket._receive_data(auth_schemes);
589 expect(client._rfb_state).to.equal('Authentication');
590 expect(client._negotiate_authentication).to.have.been.calledOnce;
591 });
592 });
593
594 describe('Authentication', function () {
595 var client;
596
597 beforeEach(function () {
598 client = make_rfb();
599 client.connect('host', 8675);
600 client._sock._websocket._open();
601 client._rfb_state = 'Security';
602 });
603
604 function send_security(type, cl) {
605 cl._sock._websocket._receive_data(new Uint8Array([1, type]));
606 }
607
608 it('should fail on auth scheme 0 (pre 3.7) with the given message', function () {
609 client._rfb_version = 3.6;
610 var err_msg = "Whoopsies";
611 var data = [0, 0, 0, 0];
612 var err_len = err_msg.length;
613 data.push32(err_len);
614 for (var i = 0; i < err_len; i++) {
615 data.push(err_msg.charCodeAt(i));
616 }
617
618 sinon.spy(client, '_fail');
619 client._sock._websocket._receive_data(new Uint8Array(data));
620 expect(client._rfb_state).to.equal('failed');
621 expect(client._fail).to.have.been.calledWith('Auth failure: Whoopsies');
622 });
623
624 it('should transition straight to SecurityResult on "no auth" (1) for versions >= 3.8', function () {
625 client._rfb_version = 3.8;
626 send_security(1, client);
627 expect(client._rfb_state).to.equal('SecurityResult');
628 });
629
630 it('should transition straight to ClientInitialisation on "no auth" for versions < 3.8', function () {
631 client._rfb_version = 3.7;
632 sinon.spy(client, '_updateState');
633 send_security(1, client);
634 expect(client._updateState).to.have.been.calledWith('ClientInitialisation');
635 expect(client._rfb_state).to.equal('ServerInitialisation');
636 });
637
638 it('should fail on an unknown auth scheme', function () {
639 client._rfb_version = 3.8;
640 send_security(57, client);
641 expect(client._rfb_state).to.equal('failed');
642 });
643
644 describe('VNC Authentication (type 2) Handler', function () {
645 var client;
646
647 beforeEach(function () {
648 client = make_rfb();
649 client.connect('host', 8675);
650 client._sock._websocket._open();
651 client._rfb_state = 'Security';
652 client._rfb_version = 3.8;
653 });
654
655 it('should transition to the "password" state if missing a password', function () {
656 send_security(2, client);
657 expect(client._rfb_state).to.equal('password');
658 });
659
660 it('should encrypt the password with DES and then send it back', function () {
661 client._rfb_password = 'passwd';
662 send_security(2, client);
663 client._sock._websocket._get_sent_data(); // skip the choice of auth reply
664
665 var challenge = [];
666 for (var i = 0; i < 16; i++) { challenge[i] = i; }
667 client._sock._websocket._receive_data(new Uint8Array(challenge));
668
669 var des_pass = RFB.genDES('passwd', challenge);
670 expect(client._sock).to.have.sent(des_pass);
671 });
672
673 it('should transition to SecurityResult immediately after sending the password', function () {
674 client._rfb_password = 'passwd';
675 send_security(2, client);
676
677 var challenge = [];
678 for (var i = 0; i < 16; i++) { challenge[i] = i; }
679 client._sock._websocket._receive_data(new Uint8Array(challenge));
680
681 expect(client._rfb_state).to.equal('SecurityResult');
682 });
683 });
684
685 describe('XVP Authentication (type 22) Handler', function () {
686 var client;
687
688 beforeEach(function () {
689 client = make_rfb();
690 client.connect('host', 8675);
691 client._sock._websocket._open();
692 client._rfb_state = 'Security';
693 client._rfb_version = 3.8;
694 });
695
696 it('should fall through to standard VNC authentication upon completion', function () {
697 client.set_xvp_password_sep('#');
698 client._rfb_password = 'user#target#password';
699 client._negotiate_std_vnc_auth = sinon.spy();
700 send_security(22, client);
701 expect(client._negotiate_std_vnc_auth).to.have.been.calledOnce;
702 });
703
704 it('should transition to the "password" state if the passwords is missing', function() {
705 send_security(22, client);
706 expect(client._rfb_state).to.equal('password');
707 });
708
709 it('should transition to the "password" state if the passwords is improperly formatted', function() {
710 client._rfb_password = 'user@target';
711 send_security(22, client);
712 expect(client._rfb_state).to.equal('password');
713 });
714
715 it('should split the password, send the first two parts, and pass on the last part', function () {
716 client.set_xvp_password_sep('#');
717 client._rfb_password = 'user#target#password';
718 client._negotiate_std_vnc_auth = sinon.spy();
719
720 send_security(22, client);
721
722 expect(client._rfb_password).to.equal('password');
723
724 var expected = [22, 4, 6]; // auth selection, len user, len target
725 for (var i = 0; i < 10; i++) { expected[i+3] = 'usertarget'.charCodeAt(i); }
726
727 expect(client._sock).to.have.sent(expected);
728 });
729 });
730
731 describe('TightVNC Authentication (type 16) Handler', function () {
732 var client;
733
734 beforeEach(function () {
735 client = make_rfb();
736 client.connect('host', 8675);
737 client._sock._websocket._open();
738 client._rfb_state = 'Security';
739 client._rfb_version = 3.8;
740 send_security(16, client);
741 client._sock._websocket._get_sent_data(); // skip the security reply
742 });
743
744 function send_num_str_pairs(pairs, client) {
745 var pairs_len = pairs.length;
746 var data = [];
747 data.push32(pairs_len);
748
749 for (var i = 0; i < pairs_len; i++) {
750 data.push32(pairs[i][0]);
751 var j;
752 for (j = 0; j < 4; j++) {
753 data.push(pairs[i][1].charCodeAt(j));
754 }
755 for (j = 0; j < 8; j++) {
756 data.push(pairs[i][2].charCodeAt(j));
757 }
758 }
759
760 client._sock._websocket._receive_data(new Uint8Array(data));
761 }
762
763 it('should skip tunnel negotiation if no tunnels are requested', function () {
764 client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0]));
765 expect(client._rfb_tightvnc).to.be.true;
766 });
767
768 it('should fail if no supported tunnels are listed', function () {
769 send_num_str_pairs([[123, 'OTHR', 'SOMETHNG']], client);
770 expect(client._rfb_state).to.equal('failed');
771 });
772
773 it('should choose the notunnel tunnel type', function () {
774 send_num_str_pairs([[0, 'TGHT', 'NOTUNNEL'], [123, 'OTHR', 'SOMETHNG']], client);
775 expect(client._sock).to.have.sent([0, 0, 0, 0]);
776 });
777
778 it('should continue to sub-auth negotiation after tunnel negotiation', function () {
779 send_num_str_pairs([[0, 'TGHT', 'NOTUNNEL']], client);
780 client._sock._websocket._get_sent_data(); // skip the tunnel choice here
781 send_num_str_pairs([[1, 'STDV', 'NOAUTH__']], client);
782 expect(client._sock).to.have.sent([0, 0, 0, 1]);
783 expect(client._rfb_state).to.equal('SecurityResult');
784 });
785
786 /*it('should attempt to use VNC auth over no auth when possible', function () {
787 client._rfb_tightvnc = true;
788 client._negotiate_std_vnc_auth = sinon.spy();
789 send_num_str_pairs([[1, 'STDV', 'NOAUTH__'], [2, 'STDV', 'VNCAUTH_']], client);
790 expect(client._sock).to.have.sent([0, 0, 0, 1]);
791 expect(client._negotiate_std_vnc_auth).to.have.been.calledOnce;
792 expect(client._rfb_auth_scheme).to.equal(2);
793 });*/ // while this would make sense, the original code doesn't actually do this
794
795 it('should accept the "no auth" auth type and transition to SecurityResult', function () {
796 client._rfb_tightvnc = true;
797 send_num_str_pairs([[1, 'STDV', 'NOAUTH__']], client);
798 expect(client._sock).to.have.sent([0, 0, 0, 1]);
799 expect(client._rfb_state).to.equal('SecurityResult');
800 });
801
802 it('should accept VNC authentication and transition to that', function () {
803 client._rfb_tightvnc = true;
804 client._negotiate_std_vnc_auth = sinon.spy();
805 send_num_str_pairs([[2, 'STDV', 'VNCAUTH__']], client);
806 expect(client._sock).to.have.sent([0, 0, 0, 2]);
807 expect(client._negotiate_std_vnc_auth).to.have.been.calledOnce;
808 expect(client._rfb_auth_scheme).to.equal(2);
809 });
810
811 it('should fail if there are no supported auth types', function () {
812 client._rfb_tightvnc = true;
813 send_num_str_pairs([[23, 'stdv', 'badval__']], client);
814 expect(client._rfb_state).to.equal('failed');
815 });
816 });
817 });
818
819 describe('SecurityResult', function () {
820 var client;
821
822 beforeEach(function () {
823 client = make_rfb();
824 client.connect('host', 8675);
825 client._sock._websocket._open();
826 client._rfb_state = 'SecurityResult';
827 });
828
829 it('should fall through to ClientInitialisation on a response code of 0', function () {
830 client._updateState = sinon.spy();
831 client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0]));
832 expect(client._updateState).to.have.been.calledOnce;
833 expect(client._updateState).to.have.been.calledWith('ClientInitialisation');
834 });
835
836 it('should fail on an error code of 1 with the given message for versions >= 3.8', function () {
837 client._rfb_version = 3.8;
838 sinon.spy(client, '_fail');
839 var failure_data = [0, 0, 0, 1, 0, 0, 0, 6, 119, 104, 111, 111, 112, 115];
840 client._sock._websocket._receive_data(new Uint8Array(failure_data));
841 expect(client._rfb_state).to.equal('failed');
842 expect(client._fail).to.have.been.calledWith('whoops');
843 });
844
845 it('should fail on an error code of 1 with a standard message for version < 3.8', function () {
846 client._rfb_version = 3.7;
847 client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 1]));
848 expect(client._rfb_state).to.equal('failed');
849 });
850 });
851
852 describe('ClientInitialisation', function () {
853 var client;
854
855 beforeEach(function () {
856 client = make_rfb();
857 client.connect('host', 8675);
858 client._sock._websocket._open();
859 client._rfb_state = 'SecurityResult';
860 });
861
862 it('should transition to the ServerInitialisation state', function () {
863 client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0]));
864 expect(client._rfb_state).to.equal('ServerInitialisation');
865 });
866
867 it('should send 1 if we are in shared mode', function () {
868 client.set_shared(true);
869 client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0]));
870 expect(client._sock).to.have.sent([1]);
871 });
872
873 it('should send 0 if we are not in shared mode', function () {
874 client.set_shared(false);
875 client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0]));
876 expect(client._sock).to.have.sent([0]);
877 });
878 });
879
880 describe('ServerInitialisation', function () {
881 var client;
882
883 beforeEach(function () {
884 client = make_rfb();
885 client.connect('host', 8675);
886 client._sock._websocket._open();
887 client._rfb_state = 'ServerInitialisation';
888 });
889
890 function send_server_init(opts, client) {
891 var full_opts = { width: 10, height: 12, bpp: 24, depth: 24, big_endian: 0,
892 true_color: 1, red_max: 255, green_max: 255, blue_max: 255,
893 red_shift: 16, green_shift: 8, blue_shift: 0, name: 'a name' };
894 for (var opt in opts) {
895 full_opts[opt] = opts[opt];
896 }
897 var data = [];
898
899 data.push16(full_opts.width);
900 data.push16(full_opts.height);
901
902 data.push(full_opts.bpp);
903 data.push(full_opts.depth);
904 data.push(full_opts.big_endian);
905 data.push(full_opts.true_color);
906
907 data.push16(full_opts.red_max);
908 data.push16(full_opts.green_max);
909 data.push16(full_opts.blue_max);
910 data.push8(full_opts.red_shift);
911 data.push8(full_opts.green_shift);
912 data.push8(full_opts.blue_shift);
913
914 // padding
915 data.push8(0);
916 data.push8(0);
917 data.push8(0);
918
919 client._sock._websocket._receive_data(new Uint8Array(data));
920
921 var name_data = [];
922 name_data.push32(full_opts.name.length);
923 for (var i = 0; i < full_opts.name.length; i++) {
924 name_data.push(full_opts.name.charCodeAt(i));
925 }
926 client._sock._websocket._receive_data(new Uint8Array(name_data));
927 }
928
929 it('should set the framebuffer width and height', function () {
930 send_server_init({ width: 32, height: 84 }, client);
931 expect(client._fb_width).to.equal(32);
932 expect(client._fb_height).to.equal(84);
933 });
934
935 // NB(sross): we just warn, not fail, for endian-ness and shifts, so we don't test them
936
937 it('should set the framebuffer name and call the callback', function () {
938 client.set_onDesktopName(sinon.spy());
939 send_server_init({ name: 'some name' }, client);
940
941 var spy = client.get_onDesktopName();
942 expect(client._fb_name).to.equal('some name');
943 expect(spy).to.have.been.calledOnce;
944 expect(spy.args[0][1]).to.equal('some name');
945 });
946
947 it('should handle the extended init message of the tight encoding', function () {
948 // NB(sross): we don't actually do anything with it, so just test that we can
949 // read it w/o throwing an error
950 client._rfb_tightvnc = true;
951 send_server_init({}, client);
952
953 var tight_data = [];
954 tight_data.push16(1);
955 tight_data.push16(2);
956 tight_data.push16(3);
957 tight_data.push16(0);
958 for (var i = 0; i < 16 + 32 + 48; i++) {
959 tight_data.push(i);
960 }
961 client._sock._websocket._receive_data(tight_data);
962
963 expect(client._rfb_state).to.equal('normal');
964 });
965
966 it('should set the true color mode on the display to the configuration variable', function () {
967 client.set_true_color(false);
968 sinon.spy(client._display, 'set_true_color');
969 send_server_init({ true_color: 1 }, client);
970 expect(client._display.set_true_color).to.have.been.calledOnce;
971 expect(client._display.set_true_color).to.have.been.calledWith(false);
972 });
973
974 it('should call the resize callback and resize the display', function () {
975 client.set_onFBResize(sinon.spy());
976 sinon.spy(client._display, 'resize');
977 send_server_init({ width: 27, height: 32 }, client);
978
979 var spy = client.get_onFBResize();
980 expect(client._display.resize).to.have.been.calledOnce;
981 expect(client._display.resize).to.have.been.calledWith(27, 32);
982 expect(spy).to.have.been.calledOnce;
983 expect(spy.args[0][1]).to.equal(27);
984 expect(spy.args[0][2]).to.equal(32);
985 });
986
987 it('should grab the mouse and keyboard', function () {
988 sinon.spy(client._keyboard, 'grab');
989 sinon.spy(client._mouse, 'grab');
990 send_server_init({}, client);
991 expect(client._keyboard.grab).to.have.been.calledOnce;
992 expect(client._mouse.grab).to.have.been.calledOnce;
993 });
994
995 it('should set the BPP and depth to 4 and 3 respectively if in true color mode', function () {
996 client.set_true_color(true);
997 send_server_init({}, client);
998 expect(client._fb_Bpp).to.equal(4);
999 expect(client._fb_depth).to.equal(3);
1000 });
1001
1002 it('should set the BPP and depth to 1 and 1 respectively if not in true color mode', function () {
1003 client.set_true_color(false);
1004 send_server_init({}, client);
1005 expect(client._fb_Bpp).to.equal(1);
1006 expect(client._fb_depth).to.equal(1);
1007 });
1008
1009 // TODO(directxman12): test the various options in this configuration matrix
1010 it('should reply with the pixel format, client encodings, and initial update request', function () {
1011 client.set_true_color(true);
1012 client.set_local_cursor(false);
1013 var expected = RFB.messages.pixelFormat(4, 3, true);
1014 expected = expected.concat(RFB.messages.clientEncodings(client._encodings, false, true));
1015 var expected_cdr = { cleanBox: { x: 0, y: 0, w: 0, h: 0 },
1016 dirtyBoxes: [ { x: 0, y: 0, w: 27, h: 32 } ] };
1017 expected = expected.concat(RFB.messages.fbUpdateRequests(expected_cdr, 27, 32));
1018
1019 send_server_init({ width: 27, height: 32 }, client);
1020 expect(client._sock).to.have.sent(expected);
1021 });
1022
1023 it('should check for sending mouse events', function () {
1024 // be lazy with our checking so we don't have to check through the whole sent buffer
1025 sinon.spy(client, '_checkEvents');
1026 send_server_init({}, client);
1027 expect(client._checkEvents).to.have.been.calledOnce;
1028 });
1029
1030 it('should transition to the "normal" state', function () {
1031 send_server_init({}, client);
1032 expect(client._rfb_state).to.equal('normal');
1033 });
1034 });
1035 });
1036
1037 describe('Protocol Message Processing After Completing Initialization', function () {
1038 var client;
1039
1040 beforeEach(function () {
1041 client = make_rfb();
1042 client.connect('host', 8675);
1043 client._sock._websocket._open();
1044 client._rfb_state = 'normal';
1045 client._fb_name = 'some device';
1046 client._fb_width = 640;
1047 client._fb_height = 20;
1048 });
1049
1050 describe('Framebuffer Update Handling', function () {
1051 var client;
1052
1053 beforeEach(function () {
1054 client = make_rfb();
1055 client.connect('host', 8675);
1056 client._sock._websocket._open();
1057 client._rfb_state = 'normal';
1058 client._fb_name = 'some device';
1059 client._fb_width = 640;
1060 client._fb_height = 20;
1061 });
1062
1063 var target_data_arr = [
1064 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
1065 0x00, 0xff, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
1066 0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255,
1067 0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255
1068 ];
1069 var target_data;
1070
1071 var target_data_check_arr = [
1072 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
1073 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
1074 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
1075 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
1076 ];
1077 var target_data_check;
1078
1079 before(function () {
1080 // NB(directxman12): PhantomJS 1.x doesn't implement Uint8ClampedArray
1081 target_data = new Uint8Array(target_data_arr);
1082 target_data_check = new Uint8Array(target_data_check_arr);
1083 });
1084
1085 function send_fbu_msg (rect_info, rect_data, client, rect_cnt) {
1086 var data = [];
1087
1088 if (!rect_cnt || rect_cnt > -1) {
1089 // header
1090 data.push(0); // msg type
1091 data.push(0); // padding
1092 data.push16(rect_cnt || rect_data.length);
1093 }
1094
1095 for (var i = 0; i < rect_data.length; i++) {
1096 if (rect_info[i]) {
1097 data.push16(rect_info[i].x);
1098 data.push16(rect_info[i].y);
1099 data.push16(rect_info[i].width);
1100 data.push16(rect_info[i].height);
1101 data.push32(rect_info[i].encoding);
1102 }
1103 data = data.concat(rect_data[i]);
1104 }
1105
1106 client._sock._websocket._receive_data(new Uint8Array(data));
1107 }
1108
1109 it('should send an update request if there is sufficient data', function () {
1110 var expected_cdr = { cleanBox: { x: 0, y: 0, w: 0, h: 0 },
1111 dirtyBoxes: [ { x: 0, y: 0, w: 640, h: 20 } ] };
1112 var expected_msg = RFB.messages.fbUpdateRequests(expected_cdr, 640, 20);
1113
1114 client._framebufferUpdate = function () { return true; };
1115 client._sock._websocket._receive_data(new Uint8Array([0]));
1116
1117 expect(client._sock).to.have.sent(expected_msg);
1118 });
1119
1120 it('should not send an update request if we need more data', function () {
1121 client._sock._websocket._receive_data(new Uint8Array([0]));
1122 expect(client._sock._websocket._get_sent_data()).to.have.length(0);
1123 });
1124
1125 it('should resume receiving an update if we previously did not have enough data', function () {
1126 var expected_cdr = { cleanBox: { x: 0, y: 0, w: 0, h: 0 },
1127 dirtyBoxes: [ { x: 0, y: 0, w: 640, h: 20 } ] };
1128 var expected_msg = RFB.messages.fbUpdateRequests(expected_cdr, 640, 20);
1129
1130 // just enough to set FBU.rects
1131 client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 3]));
1132 expect(client._sock._websocket._get_sent_data()).to.have.length(0);
1133
1134 client._framebufferUpdate = function () { return true; }; // we magically have enough data
1135 // 247 should *not* be used as the message type here
1136 client._sock._websocket._receive_data(new Uint8Array([247]));
1137 expect(client._sock).to.have.sent(expected_msg);
1138 });
1139
1140 it('should parse out information from a header before any actual data comes in', function () {
1141 client.set_onFBUReceive(sinon.spy());
1142 var rect_info = { x: 8, y: 11, width: 27, height: 32, encoding: 0x02, encodingName: 'RRE' };
1143 send_fbu_msg([rect_info], [[]], client);
1144
1145 var spy = client.get_onFBUReceive();
1146 expect(spy).to.have.been.calledOnce;
1147 expect(spy).to.have.been.calledWith(sinon.match.any, rect_info);
1148 });
1149
1150 it('should fire onFBUComplete when the update is complete', function () {
1151 client.set_onFBUComplete(sinon.spy());
1152 var rect_info = { x: 8, y: 11, width: 27, height: 32, encoding: -224, encodingName: 'last_rect' };
1153 send_fbu_msg([rect_info], [[]], client); // last_rect
1154
1155 var spy = client.get_onFBUComplete();
1156 expect(spy).to.have.been.calledOnce;
1157 expect(spy).to.have.been.calledWith(sinon.match.any, rect_info);
1158 });
1159
1160 it('should not fire onFBUComplete if we have not finished processing the update', function () {
1161 client.set_onFBUComplete(sinon.spy());
1162 var rect_info = { x: 8, y: 11, width: 27, height: 32, encoding: 0x00, encodingName: 'RAW' };
1163 send_fbu_msg([rect_info], [[]], client);
1164 expect(client.get_onFBUComplete()).to.not.have.been.called;
1165 });
1166
1167 it('should call the appropriate encoding handler', function () {
1168 client._encHandlers[0x02] = sinon.spy();
1169 var rect_info = { x: 8, y: 11, width: 27, height: 32, encoding: 0x02 };
1170 send_fbu_msg([rect_info], [[]], client);
1171 expect(client._encHandlers[0x02]).to.have.been.calledOnce;
1172 });
1173
1174 it('should fail on an unsupported encoding', function () {
1175 client.set_onFBUReceive(sinon.spy());
1176 var rect_info = { x: 8, y: 11, width: 27, height: 32, encoding: 234 };
1177 send_fbu_msg([rect_info], [[]], client);
1178 expect(client._rfb_state).to.equal('failed');
1179 });
1180
1181 it('should be able to pause and resume receiving rects if not enought data', function () {
1182 // seed some initial data to copy
1183 client._fb_width = 4;
1184 client._fb_height = 4;
1185 client._display.resize(4, 4);
1186 var initial_data = client._display._drawCtx.createImageData(4, 2);
1187 var initial_data_arr = target_data_check_arr.slice(0, 32);
1188 for (var i = 0; i < 32; i++) { initial_data.data[i] = initial_data_arr[i]; }
1189 client._display._drawCtx.putImageData(initial_data, 0, 0);
1190
1191 var info = [{ x: 0, y: 2, width: 2, height: 2, encoding: 0x01},
1192 { x: 2, y: 2, width: 2, height: 2, encoding: 0x01}];
1193 // data says [{ old_x: 0, old_y: 0 }, { old_x: 0, old_y: 0 }]
1194 var rects = [[0, 2, 0, 0], [0, 0, 0, 0]];
1195 send_fbu_msg([info[0]], [rects[0]], client, 2);
1196 send_fbu_msg([info[1]], [rects[1]], client, -1);
1197 expect(client._display).to.have.displayed(target_data_check);
1198 });
1199
1200 describe('Message Encoding Handlers', function () {
1201 var client;
1202
1203 beforeEach(function () {
1204 client = make_rfb();
1205 client.connect('host', 8675);
1206 client._sock._websocket._open();
1207 client._rfb_state = 'normal';
1208 client._fb_name = 'some device';
1209 // a really small frame
1210 client._fb_width = 4;
1211 client._fb_height = 4;
1212 client._display._fb_width = 4;
1213 client._display._fb_height = 4;
1214 client._fb_Bpp = 4;
1215 });
1216
1217 it('should handle the RAW encoding', function () {
1218 var info = [{ x: 0, y: 0, width: 2, height: 2, encoding: 0x00 },
1219 { x: 2, y: 0, width: 2, height: 2, encoding: 0x00 },
1220 { x: 0, y: 2, width: 4, height: 1, encoding: 0x00 },
1221 { x: 0, y: 3, width: 4, height: 1, encoding: 0x00 }];
1222 // data is in bgrx
1223 var rects = [
1224 [0x00, 0x00, 0xff, 0, 0x00, 0xff, 0x00, 0, 0x00, 0xff, 0x00, 0, 0x00, 0x00, 0xff, 0],
1225 [0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0],
1226 [0xff, 0x00, 0xee, 0, 0xff, 0xee, 0x00, 0, 0xff, 0xee, 0xaa, 0, 0xff, 0xee, 0xab, 0],
1227 [0xff, 0x00, 0xee, 0, 0xff, 0xee, 0x00, 0, 0xff, 0xee, 0xaa, 0, 0xff, 0xee, 0xab, 0]];
1228 send_fbu_msg(info, rects, client);
1229 expect(client._display).to.have.displayed(target_data);
1230 });
1231
1232 it('should handle the COPYRECT encoding', function () {
1233 // seed some initial data to copy
1234 var initial_data = client._display._drawCtx.createImageData(4, 2);
1235 var initial_data_arr = target_data_check_arr.slice(0, 32);
1236 for (var i = 0; i < 32; i++) { initial_data.data[i] = initial_data_arr[i]; }
1237 client._display._drawCtx.putImageData(initial_data, 0, 0);
1238
1239 var info = [{ x: 0, y: 2, width: 2, height: 2, encoding: 0x01},
1240 { x: 2, y: 2, width: 2, height: 2, encoding: 0x01}];
1241 // data says [{ old_x: 0, old_y: 0 }, { old_x: 0, old_y: 0 }]
1242 var rects = [[0, 2, 0, 0], [0, 0, 0, 0]];
1243 send_fbu_msg(info, rects, client);
1244 expect(client._display).to.have.displayed(target_data_check);
1245 });
1246
1247 // TODO(directxman12): for encodings with subrects, test resuming on partial send?
1248 // TODO(directxman12): test rre_chunk_sz (related to above about subrects)?
1249
1250 it('should handle the RRE encoding', function () {
1251 var info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x02 }];
1252 var rect = [];
1253 rect.push32(2); // 2 subrects
1254 rect.push32(0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1255 rect.push(0xff); // becomes ff0000ff --> #0000FF color
1256 rect.push(0x00);
1257 rect.push(0x00);
1258 rect.push(0xff);
1259 rect.push16(0); // x: 0
1260 rect.push16(0); // y: 0
1261 rect.push16(2); // width: 2
1262 rect.push16(2); // height: 2
1263 rect.push(0xff); // becomes ff0000ff --> #0000FF color
1264 rect.push(0x00);
1265 rect.push(0x00);
1266 rect.push(0xff);
1267 rect.push16(2); // x: 2
1268 rect.push16(2); // y: 2
1269 rect.push16(2); // width: 2
1270 rect.push16(2); // height: 2
1271
1272 send_fbu_msg(info, [rect], client);
1273 expect(client._display).to.have.displayed(target_data_check);
1274 });
1275
1276 describe('the HEXTILE encoding handler', function () {
1277 var client;
1278 beforeEach(function () {
1279 client = make_rfb();
1280 client.connect('host', 8675);
1281 client._sock._websocket._open();
1282 client._rfb_state = 'normal';
1283 client._fb_name = 'some device';
1284 // a really small frame
1285 client._fb_width = 4;
1286 client._fb_height = 4;
1287 client._display._fb_width = 4;
1288 client._display._fb_height = 4;
1289 client._fb_Bpp = 4;
1290 });
1291
1292 it('should handle a tile with fg, bg specified, normal subrects', function () {
1293 var info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
1294 var rect = [];
1295 rect.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects
1296 rect.push32(0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1297 rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
1298 rect.push(0x00);
1299 rect.push(0x00);
1300 rect.push(0xff);
1301 rect.push(2); // 2 subrects
1302 rect.push(0); // x: 0, y: 0
1303 rect.push(1 | (1 << 4)); // width: 2, height: 2
1304 rect.push(2 | (2 << 4)); // x: 2, y: 2
1305 rect.push(1 | (1 << 4)); // width: 2, height: 2
1306 send_fbu_msg(info, [rect], client);
1307 expect(client._display).to.have.displayed(target_data_check);
1308 });
1309
1310 it('should handle a raw tile', function () {
1311 var info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
1312 var rect = [];
1313 rect.push(0x01); // raw
1314 for (var i = 0; i < target_data.length; i += 4) {
1315 rect.push(target_data[i + 2]);
1316 rect.push(target_data[i + 1]);
1317 rect.push(target_data[i]);
1318 rect.push(target_data[i + 3]);
1319 }
1320 send_fbu_msg(info, [rect], client);
1321 expect(client._display).to.have.displayed(target_data);
1322 });
1323
1324 it('should handle a tile with only bg specified (solid bg)', function () {
1325 var info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
1326 var rect = [];
1327 rect.push(0x02);
1328 rect.push32(0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1329 send_fbu_msg(info, [rect], client);
1330
1331 var expected = [];
1332 for (var i = 0; i < 16; i++) { expected.push32(0xff00ff); }
1333 expect(client._display).to.have.displayed(new Uint8Array(expected));
1334 });
1335
1336 it('should handle a tile with bg and coloured subrects', function () {
1337 var info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
1338 var rect = [];
1339 rect.push(0x02 | 0x08 | 0x10); // bg spec, anysubrects, colouredsubrects
1340 rect.push32(0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1341 rect.push(2); // 2 subrects
1342 rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
1343 rect.push(0x00);
1344 rect.push(0x00);
1345 rect.push(0xff);
1346 rect.push(0); // x: 0, y: 0
1347 rect.push(1 | (1 << 4)); // width: 2, height: 2
1348 rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
1349 rect.push(0x00);
1350 rect.push(0x00);
1351 rect.push(0xff);
1352 rect.push(2 | (2 << 4)); // x: 2, y: 2
1353 rect.push(1 | (1 << 4)); // width: 2, height: 2
1354 send_fbu_msg(info, [rect], client);
1355 expect(client._display).to.have.displayed(target_data_check);
1356 });
1357
1358 it('should carry over fg and bg colors from the previous tile if not specified', function () {
1359 client._fb_width = 4;
1360 client._fb_height = 17;
1361 client._display.resize(4, 17);
1362
1363 var info = [{ x: 0, y: 0, width: 4, height: 17, encoding: 0x05}];
1364 var rect = [];
1365 rect.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects
1366 rect.push32(0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1367 rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
1368 rect.push(0x00);
1369 rect.push(0x00);
1370 rect.push(0xff);
1371 rect.push(8); // 8 subrects
1372 var i;
1373 for (i = 0; i < 4; i++) {
1374 rect.push((0 << 4) | (i * 4)); // x: 0, y: i*4
1375 rect.push(1 | (1 << 4)); // width: 2, height: 2
1376 rect.push((2 << 4) | (i * 4 + 2)); // x: 2, y: i * 4 + 2
1377 rect.push(1 | (1 << 4)); // width: 2, height: 2
1378 }
1379 rect.push(0x08); // anysubrects
1380 rect.push(1); // 1 subrect
1381 rect.push(0); // x: 0, y: 0
1382 rect.push(1 | (1 << 4)); // width: 2, height: 2
1383 send_fbu_msg(info, [rect], client);
1384
1385 var expected = [];
1386 for (i = 0; i < 4; i++) { expected = expected.concat(target_data_check_arr); }
1387 expected = expected.concat(target_data_check_arr.slice(0, 16));
1388 expect(client._display).to.have.displayed(new Uint8Array(expected));
1389 });
1390
1391 it('should fail on an invalid subencoding', function () {
1392 var info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
1393 var rects = [[45]]; // an invalid subencoding
1394 send_fbu_msg(info, rects, client);
1395 expect(client._rfb_state).to.equal('failed');
1396 });
1397 });
1398
1399 it.skip('should handle the TIGHT encoding', function () {
1400 // TODO(directxman12): test this
1401 });
1402
1403 it.skip('should handle the TIGHT_PNG encoding', function () {
1404 // TODO(directxman12): test this
1405 });
1406
1407 it('should handle the DesktopSize pseduo-encoding', function () {
1408 client.set_onFBResize(sinon.spy());
1409 sinon.spy(client._display, 'resize');
1410 send_fbu_msg([{ x: 0, y: 0, width: 20, height: 50, encoding: -223 }], [[]], client);
1411
1412 var spy = client.get_onFBResize();
1413 expect(spy).to.have.been.calledOnce;
1414 expect(spy).to.have.been.calledWith(sinon.match.any, 20, 50);
1415
1416 expect(client._fb_width).to.equal(20);
1417 expect(client._fb_height).to.equal(50);
1418
1419 expect(client._display.resize).to.have.been.calledOnce;
1420 expect(client._display.resize).to.have.been.calledWith(20, 50);
1421 });
1422
1423 it.skip('should handle the Cursor pseudo-encoding', function () {
1424 // TODO(directxman12): test
1425 });
1426
1427 it('should handle the last_rect pseudo-encoding', function () {
1428 client.set_onFBUReceive(sinon.spy());
1429 send_fbu_msg([{ x: 0, y: 0, width: 0, height: 0, encoding: -224}], [[]], client, 100);
1430 expect(client._FBU.rects).to.equal(0);
1431 expect(client.get_onFBUReceive()).to.have.been.calledOnce;
1432 });
1433 });
1434 });
1435
1436 it('should set the colour map on the display on SetColourMapEntries', function () {
1437 var expected_cm = [];
1438 var data = [1, 0, 0, 1, 0, 4];
1439 var i;
1440 for (i = 0; i < 4; i++) {
1441 expected_cm[i + 1] = [i * 10, i * 10 + 1, i * 10 + 2];
1442 data.push16(expected_cm[i + 1][2] << 8);
1443 data.push16(expected_cm[i + 1][1] << 8);
1444 data.push16(expected_cm[i + 1][0] << 8);
1445 }
1446
1447 client._sock._websocket._receive_data(new Uint8Array(data));
1448 expect(client._display.get_colourMap()).to.deep.equal(expected_cm);
1449 });
1450
1451 describe('XVP Message Handling', function () {
1452 beforeEach(function () {
1453 client = make_rfb();
1454 client.connect('host', 8675);
1455 client._sock._websocket._open();
1456 client._rfb_state = 'normal';
1457 client._fb_name = 'some device';
1458 client._fb_width = 27;
1459 client._fb_height = 32;
1460 });
1461
1462 it('should call updateState with a message on XVP_FAIL, but keep the same state', function () {
1463 client._updateState = sinon.spy();
1464 client._sock._websocket._receive_data(new Uint8Array([250, 0, 10, 0]));
1465 expect(client._updateState).to.have.been.calledOnce;
1466 expect(client._updateState).to.have.been.calledWith('normal', 'Operation Failed');
1467 });
1468
1469 it('should set the XVP version and fire the callback with the version on XVP_INIT', function () {
1470 client.set_onXvpInit(sinon.spy());
1471 client._sock._websocket._receive_data(new Uint8Array([250, 0, 10, 1]));
1472 expect(client._rfb_xvp_ver).to.equal(10);
1473 expect(client.get_onXvpInit()).to.have.been.calledOnce;
1474 expect(client.get_onXvpInit()).to.have.been.calledWith(10);
1475 });
1476
1477 it('should fail on unknown XVP message types', function () {
1478 client._sock._websocket._receive_data(new Uint8Array([250, 0, 10, 237]));
1479 expect(client._rfb_state).to.equal('failed');
1480 });
1481 });
1482
1483 it('should fire the clipboard callback with the retrieved text on ServerCutText', function () {
1484 var expected_str = 'cheese!';
1485 var data = [3, 0, 0, 0];
1486 data.push32(expected_str.length);
1487 for (var i = 0; i < expected_str.length; i++) { data.push(expected_str.charCodeAt(i)); }
1488 client.set_onClipboard(sinon.spy());
1489
1490 client._sock._websocket._receive_data(new Uint8Array(data));
1491 var spy = client.get_onClipboard();
1492 expect(spy).to.have.been.calledOnce;
1493 expect(spy.args[0][1]).to.equal(expected_str);
1494 });
1495
1496 it('should fire the bell callback on Bell', function () {
1497 client.set_onBell(sinon.spy());
1498 client._sock._websocket._receive_data(new Uint8Array([2]));
1499 expect(client.get_onBell()).to.have.been.calledOnce;
1500 });
1501
1502 it('should fail on an unknown message type', function () {
1503 client._sock._websocket._receive_data(new Uint8Array([87]));
1504 expect(client._rfb_state).to.equal('failed');
1505 });
1506 });
1507
1508 describe('Asynchronous Events', function () {
1509 describe('Mouse event handlers', function () {
1510 var client;
1511 beforeEach(function () {
1512 client = make_rfb();
1513 client._sock.send = sinon.spy();
1514 client._rfb_state = 'normal';
1515 });
1516
1517 it('should not send button messages in view-only mode', function () {
1518 client._view_only = true;
1519 client._mouse._onMouseButton(0, 0, 1, 0x001);
1520 expect(client._sock.send).to.not.have.been.called;
1521 });
1522
1523 it('should not send movement messages in view-only mode', function () {
1524 client._view_only = true;
1525 client._mouse._onMouseMove(0, 0);
1526 expect(client._sock.send).to.not.have.been.called;
1527 });
1528
1529 it('should send a pointer event on mouse button presses', function () {
1530 client._mouse._onMouseButton(10, 12, 1, 0x001);
1531 expect(client._sock.send).to.have.been.calledOnce;
1532 var pointer_msg = RFB.messages.pointerEvent(10, 12, 0x001);
1533 expect(client._sock.send).to.have.been.calledWith(pointer_msg);
1534 });
1535
1536 it('should send a pointer event on mouse movement', function () {
1537 client._mouse._onMouseMove(10, 12);
1538 expect(client._sock.send).to.have.been.calledOnce;
1539 var pointer_msg = RFB.messages.pointerEvent(10, 12, 0);
1540 expect(client._sock.send).to.have.been.calledWith(pointer_msg);
1541 });
1542
1543 it('should set the button mask so that future mouse movements use it', function () {
1544 client._mouse._onMouseButton(10, 12, 1, 0x010);
1545 client._sock.send = sinon.spy();
1546 client._mouse._onMouseMove(13, 9);
1547 expect(client._sock.send).to.have.been.calledOnce;
1548 var pointer_msg = RFB.messages.pointerEvent(13, 9, 0x010);
1549 expect(client._sock.send).to.have.been.calledWith(pointer_msg);
1550 });
1551
1552 // NB(directxman12): we don't need to test not sending messages in
1553 // non-normal modes, since we haven't grabbed input
1554 // yet (grabbing input should be checked in the lifecycle tests).
1555
1556 it('should not send movement messages when viewport dragging', function () {
1557 client._viewportDragging = true;
1558 client._display.viewportChange = sinon.spy();
1559 client._mouse._onMouseMove(13, 9);
1560 expect(client._sock.send).to.not.have.been.called;
1561 });
1562
1563 it('should not send button messages when initiating viewport dragging', function () {
1564 client._viewportDrag = true;
1565 client._mouse._onMouseButton(13, 9, 0x001);
1566 expect(client._sock.send).to.not.have.been.called;
1567 });
1568
1569 it('should be initiate viewport dragging on a button down event, if enabled', function () {
1570 client._viewportDrag = true;
1571 client._mouse._onMouseButton(13, 9, 0x001);
1572 expect(client._viewportDragging).to.be.true;
1573 expect(client._viewportDragPos).to.deep.equal({ x: 13, y: 9 });
1574 });
1575
1576 it('should terminate viewport dragging on a button up event, if enabled', function () {
1577 client._viewportDrag = true;
1578 client._viewportDragging = true;
1579 client._mouse._onMouseButton(13, 9, 0x000);
1580 expect(client._viewportDragging).to.be.false;
1581 });
1582
1583 it('if enabled, viewportDragging should occur on mouse movement while a button is down', function () {
1584 client._viewportDrag = true;
1585 client._viewportDragging = true;
1586 client._viewportDragPos = { x: 13, y: 9 };
1587 client._display.viewportChange = sinon.spy();
1588
1589 client._mouse._onMouseMove(10, 4);
1590
1591 expect(client._viewportDragging).to.be.true;
1592 expect(client._viewportDragPos).to.deep.equal({ x: 10, y: 4 });
1593 expect(client._display.viewportChange).to.have.been.calledOnce;
1594 expect(client._display.viewportChange).to.have.been.calledWith(3, 5);
1595 });
1596 });
1597
1598 describe('Keyboard Event Handlers', function () {
1599 var client;
1600 beforeEach(function () {
1601 client = make_rfb();
1602 client._sock.send = sinon.spy();
1603 });
1604
1605 it('should send a key message on a key press', function () {
1606 client._keyboard._onKeyPress(1234, 1);
1607 expect(client._sock.send).to.have.been.calledOnce;
1608 var key_msg = RFB.messages.keyEvent(1234, 1);
1609 expect(client._sock.send).to.have.been.calledWith(key_msg);
1610 });
1611
1612 it('should not send messages in view-only mode', function () {
1613 client._view_only = true;
1614 client._keyboard._onKeyPress(1234, 1);
1615 expect(client._sock.send).to.not.have.been.called;
1616 });
1617 });
1618
1619 describe('WebSocket event handlers', function () {
1620 var client;
1621 beforeEach(function () {
1622 client = make_rfb();
1623 this.clock = sinon.useFakeTimers();
1624 });
1625
1626 afterEach(function () { this.clock.restore(); });
1627
1628 // message events
1629 it ('should do nothing if we receive an empty message and have nothing in the queue', function () {
1630 client.connect('host', 8675);
1631 client._rfb_state = 'normal';
1632 client._normal_msg = sinon.spy();
1633 client._sock._websocket._receive_data(Base64.encode([]));
1634 expect(client._normal_msg).to.not.have.been.called;
1635 });
1636
1637 it('should handle a message in the normal state as a normal message', function () {
1638 client.connect('host', 8675);
1639 client._rfb_state = 'normal';
1640 client._normal_msg = sinon.spy();
1641 client._sock._websocket._receive_data(Base64.encode([1, 2, 3]));
1642 expect(client._normal_msg).to.have.been.calledOnce;
1643 });
1644
1645 it('should handle a message in any non-disconnected/failed state like an init message', function () {
1646 client.connect('host', 8675);
1647 client._rfb_state = 'ProtocolVersion';
1648 client._init_msg = sinon.spy();
1649 client._sock._websocket._receive_data(Base64.encode([1, 2, 3]));
1650 expect(client._init_msg).to.have.been.calledOnce;
1651 });
1652
1653 it('should split up the handling of muplitle normal messages across 10ms intervals', function () {
1654 client.connect('host', 8675);
1655 client._sock._websocket._open();
1656 client._rfb_state = 'normal';
1657 client.set_onBell(sinon.spy());
1658 client._sock._websocket._receive_data(new Uint8Array([0x02, 0x02]));
1659 expect(client.get_onBell()).to.have.been.calledOnce;
1660 this.clock.tick(20);
1661 expect(client.get_onBell()).to.have.been.calledTwice;
1662 });
1663
1664 // open events
1665 it('should update the state to ProtocolVersion on open (if the state is "connect")', function () {
1666 client.connect('host', 8675);
1667 client._sock._websocket._open();
1668 expect(client._rfb_state).to.equal('ProtocolVersion');
1669 });
1670
1671 it('should fail if we are not currently ready to connect and we get an "open" event', function () {
1672 client.connect('host', 8675);
1673 client._rfb_state = 'some_other_state';
1674 client._sock._websocket._open();
1675 expect(client._rfb_state).to.equal('failed');
1676 });
1677
1678 // close events
1679 it('should transition to "disconnected" from "disconnect" on a close event', function () {
1680 client.connect('host', 8675);
1681 client._rfb_state = 'disconnect';
1682 client._sock._websocket.close();
1683 expect(client._rfb_state).to.equal('disconnected');
1684 });
1685
1686 it('should transition to failed if we get a close event from any non-"disconnection" state', function () {
1687 client.connect('host', 8675);
1688 client._rfb_state = 'normal';
1689 client._sock._websocket.close();
1690 expect(client._rfb_state).to.equal('failed');
1691 });
1692
1693 // error events do nothing
1694 });
1695 });
1696 });