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