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