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