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