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