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