]> git.proxmox.com Git - mirror_novnc.git/blob - tests/test.rfb.js
Merge branch 'camelcase' of https://github.com/samhed/noVNC
[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 ZStream from "../vendor/pako/lib/zlib/zstream.js";
6 import { deflateInit, deflate } from "../vendor/pako/lib/zlib/deflate.js";
7 import { encodings } from '../core/encodings.js';
8 import { toUnsigned32bit } from '../core/util/int.js';
9 import { encodeUTF8 } from '../core/util/strings.js';
10
11 import FakeWebSocket from './fake.websocket.js';
12
13 /* UIEvent constructor polyfill for IE */
14 (() => {
15 if (typeof window.UIEvent === "function") return;
16
17 function UIEvent( event, params ) {
18 params = params || { bubbles: false, cancelable: false, view: window, detail: undefined };
19 const evt = document.createEvent( 'UIEvent' );
20 evt.initUIEvent( event, params.bubbles, params.cancelable, params.view, params.detail );
21 return evt;
22 }
23
24 UIEvent.prototype = window.UIEvent.prototype;
25
26 window.UIEvent = UIEvent;
27 })();
28
29 function push8(arr, num) {
30 "use strict";
31 arr.push(num & 0xFF);
32 }
33
34 function push16(arr, num) {
35 "use strict";
36 arr.push((num >> 8) & 0xFF,
37 num & 0xFF);
38 }
39
40 function push32(arr, num) {
41 "use strict";
42 arr.push((num >> 24) & 0xFF,
43 (num >> 16) & 0xFF,
44 (num >> 8) & 0xFF,
45 num & 0xFF);
46 }
47
48 function pushString(arr, string) {
49 let utf8 = unescape(encodeURIComponent(string));
50 for (let i = 0; i < utf8.length; i++) {
51 arr.push(utf8.charCodeAt(i));
52 }
53 }
54
55 function deflateWithSize(data) {
56 // Adds the size of the string in front before deflating
57
58 let unCompData = [];
59 unCompData.push((data.length >> 24) & 0xFF,
60 (data.length >> 16) & 0xFF,
61 (data.length >> 8) & 0xFF,
62 (data.length & 0xFF));
63
64 for (let i = 0; i < data.length; i++) {
65 unCompData.push(data.charCodeAt(i));
66 }
67
68 let strm = new ZStream();
69 let chunkSize = 1024 * 10 * 10;
70 strm.output = new Uint8Array(chunkSize);
71 deflateInit(strm, 5);
72
73 /* eslint-disable camelcase */
74 strm.input = unCompData;
75 strm.avail_in = strm.input.length;
76 strm.next_in = 0;
77 strm.next_out = 0;
78 strm.avail_out = chunkSize;
79 /* eslint-enable camelcase */
80
81 deflate(strm, 3);
82
83 return new Uint8Array(strm.output.buffer, 0, strm.next_out);
84 }
85
86 describe('Remote Frame Buffer Protocol Client', function () {
87 let clock;
88 let raf;
89
90 before(FakeWebSocket.replace);
91 after(FakeWebSocket.restore);
92
93 before(function () {
94 this.clock = clock = sinon.useFakeTimers();
95 // sinon doesn't support this yet
96 raf = window.requestAnimationFrame;
97 window.requestAnimationFrame = setTimeout;
98 // Use a single set of buffers instead of reallocating to
99 // speed up tests
100 const sock = new Websock();
101 const _sQ = new Uint8Array(sock._sQbufferSize);
102 const rQ = new Uint8Array(sock._rQbufferSize);
103
104 Websock.prototype._oldAllocateBuffers = Websock.prototype._allocateBuffers;
105 Websock.prototype._allocateBuffers = function () {
106 this._sQ = _sQ;
107 this._rQ = rQ;
108 };
109
110 });
111
112 after(function () {
113 Websock.prototype._allocateBuffers = Websock.prototype._oldAllocateBuffers;
114 this.clock.restore();
115 window.requestAnimationFrame = raf;
116 });
117
118 let container;
119 let rfbs;
120
121 beforeEach(function () {
122 // Create a container element for all RFB objects to attach to
123 container = document.createElement('div');
124 container.style.width = "100%";
125 container.style.height = "100%";
126 document.body.appendChild(container);
127
128 // And track all created RFB objects
129 rfbs = [];
130 });
131 afterEach(function () {
132 // Make sure every created RFB object is properly cleaned up
133 // or they might affect subsequent tests
134 rfbs.forEach(function (rfb) {
135 rfb.disconnect();
136 expect(rfb._disconnect).to.have.been.called;
137 });
138 rfbs = [];
139
140 document.body.removeChild(container);
141 container = null;
142 });
143
144 function makeRFB(url, options) {
145 url = url || 'wss://host:8675';
146 const rfb = new RFB(container, url, options);
147 clock.tick();
148 rfb._sock._websocket._open();
149 rfb._rfbConnectionState = 'connected';
150 sinon.spy(rfb, "_disconnect");
151 rfbs.push(rfb);
152 return rfb;
153 }
154
155 describe('Connecting/Disconnecting', function () {
156 describe('#RFB', function () {
157 it('should set the current state to "connecting"', function () {
158 const client = new RFB(document.createElement('div'), 'wss://host:8675');
159 client._rfbConnectionState = '';
160 this.clock.tick();
161 expect(client._rfbConnectionState).to.equal('connecting');
162 });
163
164 it('should actually connect to the websocket', function () {
165 const client = new RFB(document.createElement('div'), 'ws://HOST:8675/PATH');
166 sinon.spy(client._sock, 'open');
167 this.clock.tick();
168 expect(client._sock.open).to.have.been.calledOnce;
169 expect(client._sock.open).to.have.been.calledWith('ws://HOST:8675/PATH');
170 });
171 });
172
173 describe('#disconnect', function () {
174 let client;
175 beforeEach(function () {
176 client = makeRFB();
177 });
178
179 it('should go to state "disconnecting" before "disconnected"', function () {
180 sinon.spy(client, '_updateConnectionState');
181 client.disconnect();
182 expect(client._updateConnectionState).to.have.been.calledTwice;
183 expect(client._updateConnectionState.getCall(0).args[0])
184 .to.equal('disconnecting');
185 expect(client._updateConnectionState.getCall(1).args[0])
186 .to.equal('disconnected');
187 expect(client._rfbConnectionState).to.equal('disconnected');
188 });
189
190 it('should unregister error event handler', function () {
191 sinon.spy(client._sock, 'off');
192 client.disconnect();
193 expect(client._sock.off).to.have.been.calledWith('error');
194 });
195
196 it('should unregister message event handler', function () {
197 sinon.spy(client._sock, 'off');
198 client.disconnect();
199 expect(client._sock.off).to.have.been.calledWith('message');
200 });
201
202 it('should unregister open event handler', function () {
203 sinon.spy(client._sock, 'off');
204 client.disconnect();
205 expect(client._sock.off).to.have.been.calledWith('open');
206 });
207 });
208
209 describe('#sendCredentials', function () {
210 let client;
211 beforeEach(function () {
212 client = makeRFB();
213 client._rfbConnectionState = 'connecting';
214 });
215
216 it('should set the rfb credentials properly"', function () {
217 client.sendCredentials({ password: 'pass' });
218 expect(client._rfbCredentials).to.deep.equal({ password: 'pass' });
219 });
220
221 it('should call initMsg "soon"', function () {
222 client._initMsg = sinon.spy();
223 client.sendCredentials({ password: 'pass' });
224 this.clock.tick(5);
225 expect(client._initMsg).to.have.been.calledOnce;
226 });
227 });
228 });
229
230 describe('Public API Basic Behavior', function () {
231 let client;
232 beforeEach(function () {
233 client = makeRFB();
234 });
235
236 describe('#sendCtrlAlDel', function () {
237 it('should sent ctrl[down]-alt[down]-del[down] then del[up]-alt[up]-ctrl[up]', function () {
238 const expected = {_sQ: new Uint8Array(48), _sQlen: 0, flush: () => {}};
239 RFB.messages.keyEvent(expected, 0xFFE3, 1);
240 RFB.messages.keyEvent(expected, 0xFFE9, 1);
241 RFB.messages.keyEvent(expected, 0xFFFF, 1);
242 RFB.messages.keyEvent(expected, 0xFFFF, 0);
243 RFB.messages.keyEvent(expected, 0xFFE9, 0);
244 RFB.messages.keyEvent(expected, 0xFFE3, 0);
245
246 client.sendCtrlAltDel();
247 expect(client._sock).to.have.sent(expected._sQ);
248 });
249
250 it('should not send the keys if we are not in a normal state', function () {
251 sinon.spy(client._sock, 'flush');
252 client._rfbConnectionState = "connecting";
253 client.sendCtrlAltDel();
254 expect(client._sock.flush).to.not.have.been.called;
255 });
256
257 it('should not send the keys if we are set as view_only', function () {
258 sinon.spy(client._sock, 'flush');
259 client._viewOnly = true;
260 client.sendCtrlAltDel();
261 expect(client._sock.flush).to.not.have.been.called;
262 });
263 });
264
265 describe('#sendKey', function () {
266 it('should send a single key with the given code and state (down = true)', function () {
267 const expected = {_sQ: new Uint8Array(8), _sQlen: 0, flush: () => {}};
268 RFB.messages.keyEvent(expected, 123, 1);
269 client.sendKey(123, 'Key123', true);
270 expect(client._sock).to.have.sent(expected._sQ);
271 });
272
273 it('should send both a down and up event if the state is not specified', function () {
274 const expected = {_sQ: new Uint8Array(16), _sQlen: 0, flush: () => {}};
275 RFB.messages.keyEvent(expected, 123, 1);
276 RFB.messages.keyEvent(expected, 123, 0);
277 client.sendKey(123, 'Key123');
278 expect(client._sock).to.have.sent(expected._sQ);
279 });
280
281 it('should not send the key if we are not in a normal state', function () {
282 sinon.spy(client._sock, 'flush');
283 client._rfbConnectionState = "connecting";
284 client.sendKey(123, 'Key123');
285 expect(client._sock.flush).to.not.have.been.called;
286 });
287
288 it('should not send the key if we are set as view_only', function () {
289 sinon.spy(client._sock, 'flush');
290 client._viewOnly = true;
291 client.sendKey(123, 'Key123');
292 expect(client._sock.flush).to.not.have.been.called;
293 });
294
295 it('should send QEMU extended events if supported', function () {
296 client._qemuExtKeyEventSupported = true;
297 const expected = {_sQ: new Uint8Array(12), _sQlen: 0, flush: () => {}};
298 RFB.messages.QEMUExtendedKeyEvent(expected, 0x20, true, 0x0039);
299 client.sendKey(0x20, 'Space', true);
300 expect(client._sock).to.have.sent(expected._sQ);
301 });
302
303 it('should not send QEMU extended events if unknown key code', function () {
304 client._qemuExtKeyEventSupported = true;
305 const expected = {_sQ: new Uint8Array(8), _sQlen: 0, flush: () => {}};
306 RFB.messages.keyEvent(expected, 123, 1);
307 client.sendKey(123, 'FooBar', true);
308 expect(client._sock).to.have.sent(expected._sQ);
309 });
310 });
311
312 describe('#focus', function () {
313 it('should move focus to canvas object', function () {
314 client._canvas.focus = sinon.spy();
315 client.focus();
316 expect(client._canvas.focus).to.have.been.calledOnce;
317 });
318 });
319
320 describe('#blur', function () {
321 it('should remove focus from canvas object', function () {
322 client._canvas.blur = sinon.spy();
323 client.blur();
324 expect(client._canvas.blur).to.have.been.calledOnce;
325 });
326 });
327
328 describe('#clipboardPasteFrom', function () {
329 describe('Clipboard update handling', function () {
330 beforeEach(function () {
331 sinon.spy(RFB.messages, 'clientCutText');
332 sinon.spy(RFB.messages, 'extendedClipboardNotify');
333 });
334
335 afterEach(function () {
336 RFB.messages.clientCutText.restore();
337 RFB.messages.extendedClipboardNotify.restore();
338 });
339
340 it('should send the given text in an clipboard update', function () {
341 client.clipboardPasteFrom('abc');
342
343 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
344 expect(RFB.messages.clientCutText).to.have.been.calledWith(client._sock,
345 new Uint8Array([97, 98, 99]));
346 });
347
348 it('should send an notify if extended clipboard is supported by server', function () {
349 // Send our capabilities
350 let data = [3, 0, 0, 0];
351 const flags = [0x1F, 0x00, 0x00, 0x01];
352 let fileSizes = [0x00, 0x00, 0x00, 0x1E];
353
354 push32(data, toUnsigned32bit(-8));
355 data = data.concat(flags);
356 data = data.concat(fileSizes);
357 client._sock._websocket._receiveData(new Uint8Array(data));
358
359 client.clipboardPasteFrom('extended test');
360 expect(RFB.messages.extendedClipboardNotify).to.have.been.calledOnce;
361 });
362 });
363
364 it('should flush multiple times for large clipboards', function () {
365 sinon.spy(client._sock, 'flush');
366 let longText = "";
367 for (let i = 0; i < client._sock._sQbufferSize + 100; i++) {
368 longText += 'a';
369 }
370 client.clipboardPasteFrom(longText);
371 expect(client._sock.flush).to.have.been.calledTwice;
372 });
373
374 it('should not send the text if we are not in a normal state', function () {
375 sinon.spy(client._sock, 'flush');
376 client._rfbConnectionState = "connecting";
377 client.clipboardPasteFrom('abc');
378 expect(client._sock.flush).to.not.have.been.called;
379 });
380 });
381
382 describe("XVP operations", function () {
383 beforeEach(function () {
384 client._rfbXvpVer = 1;
385 });
386
387 it('should send the shutdown signal on #machineShutdown', function () {
388 client.machineShutdown();
389 expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x02]));
390 });
391
392 it('should send the reboot signal on #machineReboot', function () {
393 client.machineReboot();
394 expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x03]));
395 });
396
397 it('should send the reset signal on #machineReset', function () {
398 client.machineReset();
399 expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x04]));
400 });
401
402 it('should not send XVP operations with higher versions than we support', function () {
403 sinon.spy(client._sock, 'flush');
404 client._xvpOp(2, 7);
405 expect(client._sock.flush).to.not.have.been.called;
406 });
407 });
408 });
409
410 describe('Clipping', function () {
411 let client;
412 beforeEach(function () {
413 client = makeRFB();
414 container.style.width = '70px';
415 container.style.height = '80px';
416 client.clipViewport = true;
417 });
418
419 it('should update display clip state when changing the property', function () {
420 const spy = sinon.spy(client._display, "clipViewport", ["set"]);
421
422 client.clipViewport = false;
423 expect(spy.set).to.have.been.calledOnce;
424 expect(spy.set).to.have.been.calledWith(false);
425 spy.set.resetHistory();
426
427 client.clipViewport = true;
428 expect(spy.set).to.have.been.calledOnce;
429 expect(spy.set).to.have.been.calledWith(true);
430 });
431
432 it('should update the viewport when the container size changes', function () {
433 sinon.spy(client._display, "viewportChangeSize");
434
435 container.style.width = '40px';
436 container.style.height = '50px';
437 const event = new UIEvent('resize');
438 window.dispatchEvent(event);
439 clock.tick();
440
441 expect(client._display.viewportChangeSize).to.have.been.calledOnce;
442 expect(client._display.viewportChangeSize).to.have.been.calledWith(40, 50);
443 });
444
445 it('should update the viewport when the remote session resizes', function () {
446 // Simple ExtendedDesktopSize FBU message
447 const incoming = [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
448 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0xfe, 0xcc,
449 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
450 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff,
451 0x00, 0x00, 0x00, 0x00 ];
452
453 sinon.spy(client._display, "viewportChangeSize");
454
455 client._sock._websocket._receiveData(new Uint8Array(incoming));
456
457 // FIXME: Display implicitly calls viewportChangeSize() when
458 // resizing the framebuffer, hence calledTwice.
459 expect(client._display.viewportChangeSize).to.have.been.calledTwice;
460 expect(client._display.viewportChangeSize).to.have.been.calledWith(70, 80);
461 });
462
463 it('should not update the viewport if not clipping', function () {
464 client.clipViewport = false;
465 sinon.spy(client._display, "viewportChangeSize");
466
467 container.style.width = '40px';
468 container.style.height = '50px';
469 const event = new UIEvent('resize');
470 window.dispatchEvent(event);
471 clock.tick();
472
473 expect(client._display.viewportChangeSize).to.not.have.been.called;
474 });
475
476 it('should not update the viewport if scaling', function () {
477 client.scaleViewport = true;
478 sinon.spy(client._display, "viewportChangeSize");
479
480 container.style.width = '40px';
481 container.style.height = '50px';
482 const event = new UIEvent('resize');
483 window.dispatchEvent(event);
484 clock.tick();
485
486 expect(client._display.viewportChangeSize).to.not.have.been.called;
487 });
488
489 describe('Dragging', function () {
490 beforeEach(function () {
491 client.dragViewport = true;
492 sinon.spy(RFB.messages, "pointerEvent");
493 });
494
495 afterEach(function () {
496 RFB.messages.pointerEvent.restore();
497 });
498
499 it('should not send button messages when initiating viewport dragging', function () {
500 client._handleMouseButton(13, 9, 0x001);
501 expect(RFB.messages.pointerEvent).to.not.have.been.called;
502 });
503
504 it('should send button messages when release without movement', function () {
505 // Just up and down
506 client._handleMouseButton(13, 9, 0x001);
507 client._handleMouseButton(13, 9, 0x000);
508 expect(RFB.messages.pointerEvent).to.have.been.calledTwice;
509
510 RFB.messages.pointerEvent.resetHistory();
511
512 // Small movement
513 client._handleMouseButton(13, 9, 0x001);
514 client._handleMouseMove(15, 14);
515 client._handleMouseButton(15, 14, 0x000);
516 expect(RFB.messages.pointerEvent).to.have.been.calledTwice;
517 });
518
519 it('should not send button messages when in view only', function () {
520 client._viewOnly = true;
521 client._handleMouseButton(13, 9, 0x001);
522 client._handleMouseButton(13, 9, 0x000);
523 expect(RFB.messages.pointerEvent).to.not.have.been.called;
524 });
525
526 it('should send button message directly when drag is disabled', function () {
527 client.dragViewport = false;
528 client._handleMouseButton(13, 9, 0x001);
529 expect(RFB.messages.pointerEvent).to.have.been.calledOnce;
530 });
531
532 it('should be initiate viewport dragging on sufficient movement', function () {
533 sinon.spy(client._display, "viewportChangePos");
534
535 // Too small movement
536
537 client._handleMouseButton(13, 9, 0x001);
538 client._handleMouseMove(18, 9);
539
540 expect(RFB.messages.pointerEvent).to.not.have.been.called;
541 expect(client._display.viewportChangePos).to.not.have.been.called;
542
543 // Sufficient movement
544
545 client._handleMouseMove(43, 9);
546
547 expect(RFB.messages.pointerEvent).to.not.have.been.called;
548 expect(client._display.viewportChangePos).to.have.been.calledOnce;
549 expect(client._display.viewportChangePos).to.have.been.calledWith(-30, 0);
550
551 client._display.viewportChangePos.resetHistory();
552
553 // Now a small movement should move right away
554
555 client._handleMouseMove(43, 14);
556
557 expect(RFB.messages.pointerEvent).to.not.have.been.called;
558 expect(client._display.viewportChangePos).to.have.been.calledOnce;
559 expect(client._display.viewportChangePos).to.have.been.calledWith(0, -5);
560 });
561
562 it('should not send button messages when dragging ends', function () {
563 // First the movement
564
565 client._handleMouseButton(13, 9, 0x001);
566 client._handleMouseMove(43, 9);
567 client._handleMouseButton(43, 9, 0x000);
568
569 expect(RFB.messages.pointerEvent).to.not.have.been.called;
570 });
571
572 it('should terminate viewport dragging on a button up event', function () {
573 // First the dragging movement
574
575 client._handleMouseButton(13, 9, 0x001);
576 client._handleMouseMove(43, 9);
577 client._handleMouseButton(43, 9, 0x000);
578
579 // Another movement now should not move the viewport
580
581 sinon.spy(client._display, "viewportChangePos");
582
583 client._handleMouseMove(43, 59);
584
585 expect(client._display.viewportChangePos).to.not.have.been.called;
586 });
587 });
588 });
589
590 describe('Scaling', function () {
591 let client;
592 beforeEach(function () {
593 client = makeRFB();
594 container.style.width = '70px';
595 container.style.height = '80px';
596 client.scaleViewport = true;
597 });
598
599 it('should update display scale factor when changing the property', function () {
600 const spy = sinon.spy(client._display, "scale", ["set"]);
601 sinon.spy(client._display, "autoscale");
602
603 client.scaleViewport = false;
604 expect(spy.set).to.have.been.calledOnce;
605 expect(spy.set).to.have.been.calledWith(1.0);
606 expect(client._display.autoscale).to.not.have.been.called;
607
608 client.scaleViewport = true;
609 expect(client._display.autoscale).to.have.been.calledOnce;
610 expect(client._display.autoscale).to.have.been.calledWith(70, 80);
611 });
612
613 it('should update the clipping setting when changing the property', function () {
614 client.clipViewport = true;
615
616 const spy = sinon.spy(client._display, "clipViewport", ["set"]);
617
618 client.scaleViewport = false;
619 expect(spy.set).to.have.been.calledOnce;
620 expect(spy.set).to.have.been.calledWith(true);
621
622 spy.set.resetHistory();
623
624 client.scaleViewport = true;
625 expect(spy.set).to.have.been.calledOnce;
626 expect(spy.set).to.have.been.calledWith(false);
627 });
628
629 it('should update the scaling when the container size changes', function () {
630 sinon.spy(client._display, "autoscale");
631
632 container.style.width = '40px';
633 container.style.height = '50px';
634 const event = new UIEvent('resize');
635 window.dispatchEvent(event);
636 clock.tick();
637
638 expect(client._display.autoscale).to.have.been.calledOnce;
639 expect(client._display.autoscale).to.have.been.calledWith(40, 50);
640 });
641
642 it('should update the scaling when the remote session resizes', function () {
643 // Simple ExtendedDesktopSize FBU message
644 const incoming = [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
645 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0xfe, 0xcc,
646 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
647 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff,
648 0x00, 0x00, 0x00, 0x00 ];
649
650 sinon.spy(client._display, "autoscale");
651
652 client._sock._websocket._receiveData(new Uint8Array(incoming));
653
654 expect(client._display.autoscale).to.have.been.calledOnce;
655 expect(client._display.autoscale).to.have.been.calledWith(70, 80);
656 });
657
658 it('should not update the display scale factor if not scaling', function () {
659 client.scaleViewport = false;
660
661 sinon.spy(client._display, "autoscale");
662
663 container.style.width = '40px';
664 container.style.height = '50px';
665 const event = new UIEvent('resize');
666 window.dispatchEvent(event);
667 clock.tick();
668
669 expect(client._display.autoscale).to.not.have.been.called;
670 });
671 });
672
673 describe('Remote resize', function () {
674 let client;
675 beforeEach(function () {
676 client = makeRFB();
677 client._supportsSetDesktopSize = true;
678 client.resizeSession = true;
679 container.style.width = '70px';
680 container.style.height = '80px';
681 sinon.spy(RFB.messages, "setDesktopSize");
682 });
683
684 afterEach(function () {
685 RFB.messages.setDesktopSize.restore();
686 });
687
688 it('should only request a resize when turned on', function () {
689 client.resizeSession = false;
690 expect(RFB.messages.setDesktopSize).to.not.have.been.called;
691 client.resizeSession = true;
692 expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;
693 });
694
695 it('should request a resize when initially connecting', function () {
696 // Simple ExtendedDesktopSize FBU message
697 const incoming = [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
698 0x00, 0x04, 0x00, 0x04, 0xff, 0xff, 0xfe, 0xcc,
699 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
700 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04,
701 0x00, 0x00, 0x00, 0x00 ];
702
703 // First message should trigger a resize
704
705 client._supportsSetDesktopSize = false;
706
707 client._sock._websocket._receiveData(new Uint8Array(incoming));
708
709 expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;
710 expect(RFB.messages.setDesktopSize).to.have.been.calledWith(sinon.match.object, 70, 80, 0, 0);
711
712 RFB.messages.setDesktopSize.resetHistory();
713
714 // Second message should not trigger a resize
715
716 client._sock._websocket._receiveData(new Uint8Array(incoming));
717
718 expect(RFB.messages.setDesktopSize).to.not.have.been.called;
719 });
720
721 it('should request a resize when the container resizes', function () {
722 container.style.width = '40px';
723 container.style.height = '50px';
724 const event = new UIEvent('resize');
725 window.dispatchEvent(event);
726 clock.tick(1000);
727
728 expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;
729 expect(RFB.messages.setDesktopSize).to.have.been.calledWith(sinon.match.object, 40, 50, 0, 0);
730 });
731
732 it('should not resize until the container size is stable', function () {
733 container.style.width = '20px';
734 container.style.height = '30px';
735 const event1 = new UIEvent('resize');
736 window.dispatchEvent(event1);
737 clock.tick(400);
738
739 expect(RFB.messages.setDesktopSize).to.not.have.been.called;
740
741 container.style.width = '40px';
742 container.style.height = '50px';
743 const event2 = new UIEvent('resize');
744 window.dispatchEvent(event2);
745 clock.tick(400);
746
747 expect(RFB.messages.setDesktopSize).to.not.have.been.called;
748
749 clock.tick(200);
750
751 expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;
752 expect(RFB.messages.setDesktopSize).to.have.been.calledWith(sinon.match.object, 40, 50, 0, 0);
753 });
754
755 it('should not resize when resize is disabled', function () {
756 client._resizeSession = false;
757
758 container.style.width = '40px';
759 container.style.height = '50px';
760 const event = new UIEvent('resize');
761 window.dispatchEvent(event);
762 clock.tick(1000);
763
764 expect(RFB.messages.setDesktopSize).to.not.have.been.called;
765 });
766
767 it('should not resize when resize is not supported', function () {
768 client._supportsSetDesktopSize = false;
769
770 container.style.width = '40px';
771 container.style.height = '50px';
772 const event = new UIEvent('resize');
773 window.dispatchEvent(event);
774 clock.tick(1000);
775
776 expect(RFB.messages.setDesktopSize).to.not.have.been.called;
777 });
778
779 it('should not resize when in view only mode', function () {
780 client._viewOnly = true;
781
782 container.style.width = '40px';
783 container.style.height = '50px';
784 const event = new UIEvent('resize');
785 window.dispatchEvent(event);
786 clock.tick(1000);
787
788 expect(RFB.messages.setDesktopSize).to.not.have.been.called;
789 });
790
791 it('should not try to override a server resize', function () {
792 // Simple ExtendedDesktopSize FBU message
793 const incoming = [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
794 0x00, 0x04, 0x00, 0x04, 0xff, 0xff, 0xfe, 0xcc,
795 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
796 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04,
797 0x00, 0x00, 0x00, 0x00 ];
798
799 client._sock._websocket._receiveData(new Uint8Array(incoming));
800
801 expect(RFB.messages.setDesktopSize).to.not.have.been.called;
802 });
803 });
804
805 describe('Misc Internals', function () {
806 describe('#_updateConnectionState', function () {
807 let client;
808 beforeEach(function () {
809 client = makeRFB();
810 });
811
812 it('should clear the disconnect timer if the state is not "disconnecting"', function () {
813 const spy = sinon.spy();
814 client._disconnTimer = setTimeout(spy, 50);
815 client._rfbConnectionState = 'connecting';
816 client._updateConnectionState('connected');
817 this.clock.tick(51);
818 expect(spy).to.not.have.been.called;
819 expect(client._disconnTimer).to.be.null;
820 });
821
822 it('should set the rfbConnectionState', function () {
823 client._rfbConnectionState = 'connecting';
824 client._updateConnectionState('connected');
825 expect(client._rfbConnectionState).to.equal('connected');
826 });
827
828 it('should not change the state when we are disconnected', function () {
829 client.disconnect();
830 expect(client._rfbConnectionState).to.equal('disconnected');
831 client._updateConnectionState('connecting');
832 expect(client._rfbConnectionState).to.not.equal('connecting');
833 });
834
835 it('should ignore state changes to the same state', function () {
836 const connectSpy = sinon.spy();
837 client.addEventListener("connect", connectSpy);
838
839 expect(client._rfbConnectionState).to.equal('connected');
840 client._updateConnectionState('connected');
841 expect(connectSpy).to.not.have.been.called;
842
843 client.disconnect();
844
845 const disconnectSpy = sinon.spy();
846 client.addEventListener("disconnect", disconnectSpy);
847
848 expect(client._rfbConnectionState).to.equal('disconnected');
849 client._updateConnectionState('disconnected');
850 expect(disconnectSpy).to.not.have.been.called;
851 });
852
853 it('should ignore illegal state changes', function () {
854 const spy = sinon.spy();
855 client.addEventListener("disconnect", spy);
856 client._updateConnectionState('disconnected');
857 expect(client._rfbConnectionState).to.not.equal('disconnected');
858 expect(spy).to.not.have.been.called;
859 });
860 });
861
862 describe('#_fail', function () {
863 let client;
864 beforeEach(function () {
865 client = makeRFB();
866 });
867
868 it('should close the WebSocket connection', function () {
869 sinon.spy(client._sock, 'close');
870 client._fail();
871 expect(client._sock.close).to.have.been.calledOnce;
872 });
873
874 it('should transition to disconnected', function () {
875 sinon.spy(client, '_updateConnectionState');
876 client._fail();
877 this.clock.tick(2000);
878 expect(client._updateConnectionState).to.have.been.called;
879 expect(client._rfbConnectionState).to.equal('disconnected');
880 });
881
882 it('should set clean_disconnect variable', function () {
883 client._rfbCleanDisconnect = true;
884 client._rfbConnectionState = 'connected';
885 client._fail();
886 expect(client._rfbCleanDisconnect).to.be.false;
887 });
888
889 it('should result in disconnect event with clean set to false', function () {
890 client._rfbConnectionState = 'connected';
891 const spy = sinon.spy();
892 client.addEventListener("disconnect", spy);
893 client._fail();
894 this.clock.tick(2000);
895 expect(spy).to.have.been.calledOnce;
896 expect(spy.args[0][0].detail.clean).to.be.false;
897 });
898
899 });
900 });
901
902 describe('Connection States', function () {
903 describe('connecting', function () {
904 it('should open the websocket connection', function () {
905 const client = new RFB(document.createElement('div'),
906 'ws://HOST:8675/PATH');
907 sinon.spy(client._sock, 'open');
908 this.clock.tick();
909 expect(client._sock.open).to.have.been.calledOnce;
910 });
911 });
912
913 describe('connected', function () {
914 let client;
915 beforeEach(function () {
916 client = makeRFB();
917 });
918
919 it('should result in a connect event if state becomes connected', function () {
920 const spy = sinon.spy();
921 client.addEventListener("connect", spy);
922 client._rfbConnectionState = 'connecting';
923 client._updateConnectionState('connected');
924 expect(spy).to.have.been.calledOnce;
925 });
926
927 it('should not result in a connect event if the state is not "connected"', function () {
928 const spy = sinon.spy();
929 client.addEventListener("connect", spy);
930 client._sock._websocket.open = () => {}; // explicitly don't call onopen
931 client._updateConnectionState('connecting');
932 expect(spy).to.not.have.been.called;
933 });
934 });
935
936 describe('disconnecting', function () {
937 let client;
938 beforeEach(function () {
939 client = makeRFB();
940 });
941
942 it('should force disconnect if we do not call Websock.onclose within the disconnection timeout', function () {
943 sinon.spy(client, '_updateConnectionState');
944 client._sock._websocket.close = () => {}; // explicitly don't call onclose
945 client._updateConnectionState('disconnecting');
946 this.clock.tick(3 * 1000);
947 expect(client._updateConnectionState).to.have.been.calledTwice;
948 expect(client._rfbDisconnectReason).to.not.equal("");
949 expect(client._rfbConnectionState).to.equal("disconnected");
950 });
951
952 it('should not fail if Websock.onclose gets called within the disconnection timeout', function () {
953 client._updateConnectionState('disconnecting');
954 this.clock.tick(3 * 1000 / 2);
955 client._sock._websocket.close();
956 this.clock.tick(3 * 1000 / 2 + 1);
957 expect(client._rfbConnectionState).to.equal('disconnected');
958 });
959
960 it('should close the WebSocket connection', function () {
961 sinon.spy(client._sock, 'close');
962 client._updateConnectionState('disconnecting');
963 expect(client._sock.close).to.have.been.calledOnce;
964 });
965
966 it('should not result in a disconnect event', function () {
967 const spy = sinon.spy();
968 client.addEventListener("disconnect", spy);
969 client._sock._websocket.close = () => {}; // explicitly don't call onclose
970 client._updateConnectionState('disconnecting');
971 expect(spy).to.not.have.been.called;
972 });
973 });
974
975 describe('disconnected', function () {
976 let client;
977 beforeEach(function () {
978 client = new RFB(document.createElement('div'), 'ws://HOST:8675/PATH');
979 });
980
981 it('should result in a disconnect event if state becomes "disconnected"', function () {
982 const spy = sinon.spy();
983 client.addEventListener("disconnect", spy);
984 client._rfbConnectionState = 'disconnecting';
985 client._updateConnectionState('disconnected');
986 expect(spy).to.have.been.calledOnce;
987 expect(spy.args[0][0].detail.clean).to.be.true;
988 });
989
990 it('should result in a disconnect event without msg when no reason given', function () {
991 const spy = sinon.spy();
992 client.addEventListener("disconnect", spy);
993 client._rfbConnectionState = 'disconnecting';
994 client._rfbDisconnectReason = "";
995 client._updateConnectionState('disconnected');
996 expect(spy).to.have.been.calledOnce;
997 expect(spy.args[0].length).to.equal(1);
998 });
999 });
1000 });
1001
1002 describe('Protocol Initialization States', function () {
1003 let client;
1004 beforeEach(function () {
1005 client = makeRFB();
1006 client._rfbConnectionState = 'connecting';
1007 });
1008
1009 describe('ProtocolVersion', function () {
1010 function sendVer(ver, client) {
1011 const arr = new Uint8Array(12);
1012 for (let i = 0; i < ver.length; i++) {
1013 arr[i+4] = ver.charCodeAt(i);
1014 }
1015 arr[0] = 'R'; arr[1] = 'F'; arr[2] = 'B'; arr[3] = ' ';
1016 arr[11] = '\n';
1017 client._sock._websocket._receiveData(arr);
1018 }
1019
1020 describe('version parsing', function () {
1021 it('should interpret version 003.003 as version 3.3', function () {
1022 sendVer('003.003', client);
1023 expect(client._rfbVersion).to.equal(3.3);
1024 });
1025
1026 it('should interpret version 003.006 as version 3.3', function () {
1027 sendVer('003.006', client);
1028 expect(client._rfbVersion).to.equal(3.3);
1029 });
1030
1031 it('should interpret version 003.889 as version 3.3', function () {
1032 sendVer('003.889', client);
1033 expect(client._rfbVersion).to.equal(3.3);
1034 });
1035
1036 it('should interpret version 003.007 as version 3.7', function () {
1037 sendVer('003.007', client);
1038 expect(client._rfbVersion).to.equal(3.7);
1039 });
1040
1041 it('should interpret version 003.008 as version 3.8', function () {
1042 sendVer('003.008', client);
1043 expect(client._rfbVersion).to.equal(3.8);
1044 });
1045
1046 it('should interpret version 004.000 as version 3.8', function () {
1047 sendVer('004.000', client);
1048 expect(client._rfbVersion).to.equal(3.8);
1049 });
1050
1051 it('should interpret version 004.001 as version 3.8', function () {
1052 sendVer('004.001', client);
1053 expect(client._rfbVersion).to.equal(3.8);
1054 });
1055
1056 it('should interpret version 005.000 as version 3.8', function () {
1057 sendVer('005.000', client);
1058 expect(client._rfbVersion).to.equal(3.8);
1059 });
1060
1061 it('should fail on an invalid version', function () {
1062 sinon.spy(client, "_fail");
1063 sendVer('002.000', client);
1064 expect(client._fail).to.have.been.calledOnce;
1065 });
1066 });
1067
1068 it('should send back the interpreted version', function () {
1069 sendVer('004.000', client);
1070
1071 const expectedStr = 'RFB 003.008\n';
1072 const expected = [];
1073 for (let i = 0; i < expectedStr.length; i++) {
1074 expected[i] = expectedStr.charCodeAt(i);
1075 }
1076
1077 expect(client._sock).to.have.sent(new Uint8Array(expected));
1078 });
1079
1080 it('should transition to the Security state on successful negotiation', function () {
1081 sendVer('003.008', client);
1082 expect(client._rfbInitState).to.equal('Security');
1083 });
1084
1085 describe('Repeater', function () {
1086 beforeEach(function () {
1087 client = makeRFB('wss://host:8675', { repeaterID: "12345" });
1088 client._rfbConnectionState = 'connecting';
1089 });
1090
1091 it('should interpret version 000.000 as a repeater', function () {
1092 sendVer('000.000', client);
1093 expect(client._rfbVersion).to.equal(0);
1094
1095 const sentData = client._sock._websocket._getSentData();
1096 expect(new Uint8Array(sentData.buffer, 0, 9)).to.array.equal(new Uint8Array([73, 68, 58, 49, 50, 51, 52, 53, 0]));
1097 expect(sentData).to.have.length(250);
1098 });
1099
1100 it('should handle two step repeater negotiation', function () {
1101 sendVer('000.000', client);
1102 sendVer('003.008', client);
1103 expect(client._rfbVersion).to.equal(3.8);
1104 });
1105 });
1106 });
1107
1108 describe('Security', function () {
1109 beforeEach(function () {
1110 client._rfbInitState = 'Security';
1111 });
1112
1113 it('should simply receive the auth scheme when for versions < 3.7', function () {
1114 client._rfbVersion = 3.6;
1115 const authSchemeRaw = [1, 2, 3, 4];
1116 const authScheme = (authSchemeRaw[0] << 24) + (authSchemeRaw[1] << 16) +
1117 (authSchemeRaw[2] << 8) + authSchemeRaw[3];
1118 client._sock._websocket._receiveData(new Uint8Array(authSchemeRaw));
1119 expect(client._rfbAuthScheme).to.equal(authScheme);
1120 });
1121
1122 it('should prefer no authentication is possible', function () {
1123 client._rfbVersion = 3.7;
1124 const authSchemes = [2, 1, 3];
1125 client._sock._websocket._receiveData(new Uint8Array(authSchemes));
1126 expect(client._rfbAuthScheme).to.equal(1);
1127 expect(client._sock).to.have.sent(new Uint8Array([1, 1]));
1128 });
1129
1130 it('should choose for the most prefered scheme possible for versions >= 3.7', function () {
1131 client._rfbVersion = 3.7;
1132 const authSchemes = [2, 22, 16];
1133 client._sock._websocket._receiveData(new Uint8Array(authSchemes));
1134 expect(client._rfbAuthScheme).to.equal(22);
1135 expect(client._sock).to.have.sent(new Uint8Array([22]));
1136 });
1137
1138 it('should fail if there are no supported schemes for versions >= 3.7', function () {
1139 sinon.spy(client, "_fail");
1140 client._rfbVersion = 3.7;
1141 const authSchemes = [1, 32];
1142 client._sock._websocket._receiveData(new Uint8Array(authSchemes));
1143 expect(client._fail).to.have.been.calledOnce;
1144 });
1145
1146 it('should fail with the appropriate message if no types are sent for versions >= 3.7', function () {
1147 client._rfbVersion = 3.7;
1148 const failureData = [0, 0, 0, 0, 6, 119, 104, 111, 111, 112, 115];
1149 sinon.spy(client, '_fail');
1150 client._sock._websocket._receiveData(new Uint8Array(failureData));
1151
1152 expect(client._fail).to.have.been.calledOnce;
1153 expect(client._fail).to.have.been.calledWith(
1154 'Security negotiation failed on no security types (reason: whoops)');
1155 });
1156
1157 it('should transition to the Authentication state and continue on successful negotiation', function () {
1158 client._rfbVersion = 3.7;
1159 const authSchemes = [1, 1];
1160 client._negotiateAuthentication = sinon.spy();
1161 client._sock._websocket._receiveData(new Uint8Array(authSchemes));
1162 expect(client._rfbInitState).to.equal('Authentication');
1163 expect(client._negotiateAuthentication).to.have.been.calledOnce;
1164 });
1165 });
1166
1167 describe('Authentication', function () {
1168 beforeEach(function () {
1169 client._rfbInitState = 'Security';
1170 });
1171
1172 function sendSecurity(type, cl) {
1173 cl._sock._websocket._receiveData(new Uint8Array([1, type]));
1174 }
1175
1176 it('should fail on auth scheme 0 (pre 3.7) with the given message', function () {
1177 client._rfbVersion = 3.6;
1178 const errMsg = "Whoopsies";
1179 const data = [0, 0, 0, 0];
1180 const errLen = errMsg.length;
1181 push32(data, errLen);
1182 for (let i = 0; i < errLen; i++) {
1183 data.push(errMsg.charCodeAt(i));
1184 }
1185
1186 sinon.spy(client, '_fail');
1187 client._sock._websocket._receiveData(new Uint8Array(data));
1188 expect(client._fail).to.have.been.calledWith(
1189 'Security negotiation failed on authentication scheme (reason: Whoopsies)');
1190 });
1191
1192 it('should transition straight to SecurityResult on "no auth" (1) for versions >= 3.8', function () {
1193 client._rfbVersion = 3.8;
1194 sendSecurity(1, client);
1195 expect(client._rfbInitState).to.equal('SecurityResult');
1196 });
1197
1198 it('should transition straight to ServerInitialisation on "no auth" for versions < 3.8', function () {
1199 client._rfbVersion = 3.7;
1200 sendSecurity(1, client);
1201 expect(client._rfbInitState).to.equal('ServerInitialisation');
1202 });
1203
1204 it('should fail on an unknown auth scheme', function () {
1205 sinon.spy(client, "_fail");
1206 client._rfbVersion = 3.8;
1207 sendSecurity(57, client);
1208 expect(client._fail).to.have.been.calledOnce;
1209 });
1210
1211 describe('VNC Authentication (type 2) Handler', function () {
1212 beforeEach(function () {
1213 client._rfbInitState = 'Security';
1214 client._rfbVersion = 3.8;
1215 });
1216
1217 it('should fire the credentialsrequired event if missing a password', function () {
1218 const spy = sinon.spy();
1219 client.addEventListener("credentialsrequired", spy);
1220 sendSecurity(2, client);
1221
1222 const challenge = [];
1223 for (let i = 0; i < 16; i++) { challenge[i] = i; }
1224 client._sock._websocket._receiveData(new Uint8Array(challenge));
1225
1226 expect(client._rfbCredentials).to.be.empty;
1227 expect(spy).to.have.been.calledOnce;
1228 expect(spy.args[0][0].detail.types).to.have.members(["password"]);
1229 });
1230
1231 it('should encrypt the password with DES and then send it back', function () {
1232 client._rfbCredentials = { password: 'passwd' };
1233 sendSecurity(2, client);
1234 client._sock._websocket._getSentData(); // skip the choice of auth reply
1235
1236 const challenge = [];
1237 for (let i = 0; i < 16; i++) { challenge[i] = i; }
1238 client._sock._websocket._receiveData(new Uint8Array(challenge));
1239
1240 const desPass = RFB.genDES('passwd', challenge);
1241 expect(client._sock).to.have.sent(new Uint8Array(desPass));
1242 });
1243
1244 it('should transition to SecurityResult immediately after sending the password', function () {
1245 client._rfbCredentials = { password: 'passwd' };
1246 sendSecurity(2, client);
1247
1248 const challenge = [];
1249 for (let i = 0; i < 16; i++) { challenge[i] = i; }
1250 client._sock._websocket._receiveData(new Uint8Array(challenge));
1251
1252 expect(client._rfbInitState).to.equal('SecurityResult');
1253 });
1254 });
1255
1256 describe('XVP Authentication (type 22) Handler', function () {
1257 beforeEach(function () {
1258 client._rfbInitState = 'Security';
1259 client._rfbVersion = 3.8;
1260 });
1261
1262 it('should fall through to standard VNC authentication upon completion', function () {
1263 client._rfbCredentials = { username: 'user',
1264 target: 'target',
1265 password: 'password' };
1266 client._negotiateStdVNCAuth = sinon.spy();
1267 sendSecurity(22, client);
1268 expect(client._negotiateStdVNCAuth).to.have.been.calledOnce;
1269 });
1270
1271 it('should fire the credentialsrequired event if all credentials are missing', function () {
1272 const spy = sinon.spy();
1273 client.addEventListener("credentialsrequired", spy);
1274 client._rfbCredentials = {};
1275 sendSecurity(22, client);
1276
1277 expect(client._rfbCredentials).to.be.empty;
1278 expect(spy).to.have.been.calledOnce;
1279 expect(spy.args[0][0].detail.types).to.have.members(["username", "password", "target"]);
1280 });
1281
1282 it('should fire the credentialsrequired event if some credentials are missing', function () {
1283 const spy = sinon.spy();
1284 client.addEventListener("credentialsrequired", spy);
1285 client._rfbCredentials = { username: 'user',
1286 target: 'target' };
1287 sendSecurity(22, client);
1288
1289 expect(spy).to.have.been.calledOnce;
1290 expect(spy.args[0][0].detail.types).to.have.members(["username", "password", "target"]);
1291 });
1292
1293 it('should send user and target separately', function () {
1294 client._rfbCredentials = { username: 'user',
1295 target: 'target',
1296 password: 'password' };
1297 client._negotiateStdVNCAuth = sinon.spy();
1298
1299 sendSecurity(22, client);
1300
1301 const expected = [22, 4, 6]; // auth selection, len user, len target
1302 for (let i = 0; i < 10; i++) { expected[i+3] = 'usertarget'.charCodeAt(i); }
1303
1304 expect(client._sock).to.have.sent(new Uint8Array(expected));
1305 });
1306 });
1307
1308 describe('TightVNC Authentication (type 16) Handler', function () {
1309 beforeEach(function () {
1310 client._rfbInitState = 'Security';
1311 client._rfbVersion = 3.8;
1312 sendSecurity(16, client);
1313 client._sock._websocket._getSentData(); // skip the security reply
1314 });
1315
1316 function sendNumStrPairs(pairs, client) {
1317 const data = [];
1318 push32(data, pairs.length);
1319
1320 for (let i = 0; i < pairs.length; i++) {
1321 push32(data, pairs[i][0]);
1322 for (let j = 0; j < 4; j++) {
1323 data.push(pairs[i][1].charCodeAt(j));
1324 }
1325 for (let j = 0; j < 8; j++) {
1326 data.push(pairs[i][2].charCodeAt(j));
1327 }
1328 }
1329
1330 client._sock._websocket._receiveData(new Uint8Array(data));
1331 }
1332
1333 it('should skip tunnel negotiation if no tunnels are requested', function () {
1334 client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 0]));
1335 expect(client._rfbTightVNC).to.be.true;
1336 });
1337
1338 it('should fail if no supported tunnels are listed', function () {
1339 sinon.spy(client, "_fail");
1340 sendNumStrPairs([[123, 'OTHR', 'SOMETHNG']], client);
1341 expect(client._fail).to.have.been.calledOnce;
1342 });
1343
1344 it('should choose the notunnel tunnel type', function () {
1345 sendNumStrPairs([[0, 'TGHT', 'NOTUNNEL'], [123, 'OTHR', 'SOMETHNG']], client);
1346 expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 0]));
1347 });
1348
1349 it('should choose the notunnel tunnel type for Siemens devices', function () {
1350 sendNumStrPairs([[1, 'SICR', 'SCHANNEL'], [2, 'SICR', 'SCHANLPW']], client);
1351 expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 0]));
1352 });
1353
1354 it('should continue to sub-auth negotiation after tunnel negotiation', function () {
1355 sendNumStrPairs([[0, 'TGHT', 'NOTUNNEL']], client);
1356 client._sock._websocket._getSentData(); // skip the tunnel choice here
1357 sendNumStrPairs([[1, 'STDV', 'NOAUTH__']], client);
1358 expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 1]));
1359 expect(client._rfbInitState).to.equal('SecurityResult');
1360 });
1361
1362 /*it('should attempt to use VNC auth over no auth when possible', function () {
1363 client._rfbTightVNC = true;
1364 client._negotiateStdVNCAuth = sinon.spy();
1365 sendNumStrPairs([[1, 'STDV', 'NOAUTH__'], [2, 'STDV', 'VNCAUTH_']], client);
1366 expect(client._sock).to.have.sent([0, 0, 0, 1]);
1367 expect(client._negotiateStdVNCAuth).to.have.been.calledOnce;
1368 expect(client._rfbAuthScheme).to.equal(2);
1369 });*/ // while this would make sense, the original code doesn't actually do this
1370
1371 it('should accept the "no auth" auth type and transition to SecurityResult', function () {
1372 client._rfbTightVNC = true;
1373 sendNumStrPairs([[1, 'STDV', 'NOAUTH__']], client);
1374 expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 1]));
1375 expect(client._rfbInitState).to.equal('SecurityResult');
1376 });
1377
1378 it('should accept VNC authentication and transition to that', function () {
1379 client._rfbTightVNC = true;
1380 client._negotiateStdVNCAuth = sinon.spy();
1381 sendNumStrPairs([[2, 'STDV', 'VNCAUTH__']], client);
1382 expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 2]));
1383 expect(client._negotiateStdVNCAuth).to.have.been.calledOnce;
1384 expect(client._rfbAuthScheme).to.equal(2);
1385 });
1386
1387 it('should fail if there are no supported auth types', function () {
1388 sinon.spy(client, "_fail");
1389 client._rfbTightVNC = true;
1390 sendNumStrPairs([[23, 'stdv', 'badval__']], client);
1391 expect(client._fail).to.have.been.calledOnce;
1392 });
1393 });
1394 });
1395
1396 describe('SecurityResult', function () {
1397 beforeEach(function () {
1398 client._rfbInitState = 'SecurityResult';
1399 });
1400
1401 it('should fall through to ServerInitialisation on a response code of 0', function () {
1402 client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 0]));
1403 expect(client._rfbInitState).to.equal('ServerInitialisation');
1404 });
1405
1406 it('should fail on an error code of 1 with the given message for versions >= 3.8', function () {
1407 client._rfbVersion = 3.8;
1408 sinon.spy(client, '_fail');
1409 const failureData = [0, 0, 0, 1, 0, 0, 0, 6, 119, 104, 111, 111, 112, 115];
1410 client._sock._websocket._receiveData(new Uint8Array(failureData));
1411 expect(client._fail).to.have.been.calledWith(
1412 'Security negotiation failed on security result (reason: whoops)');
1413 });
1414
1415 it('should fail on an error code of 1 with a standard message for version < 3.8', function () {
1416 sinon.spy(client, '_fail');
1417 client._rfbVersion = 3.7;
1418 client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 1]));
1419 expect(client._fail).to.have.been.calledWith(
1420 'Security handshake failed');
1421 });
1422
1423 it('should result in securityfailure event when receiving a non zero status', function () {
1424 const spy = sinon.spy();
1425 client.addEventListener("securityfailure", spy);
1426 client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 2]));
1427 expect(spy).to.have.been.calledOnce;
1428 expect(spy.args[0][0].detail.status).to.equal(2);
1429 });
1430
1431 it('should include reason when provided in securityfailure event', function () {
1432 client._rfbVersion = 3.8;
1433 const spy = sinon.spy();
1434 client.addEventListener("securityfailure", spy);
1435 const failureData = [0, 0, 0, 1, 0, 0, 0, 12, 115, 117, 99, 104,
1436 32, 102, 97, 105, 108, 117, 114, 101];
1437 client._sock._websocket._receiveData(new Uint8Array(failureData));
1438 expect(spy.args[0][0].detail.status).to.equal(1);
1439 expect(spy.args[0][0].detail.reason).to.equal('such failure');
1440 });
1441
1442 it('should not include reason when length is zero in securityfailure event', function () {
1443 client._rfbVersion = 3.9;
1444 const spy = sinon.spy();
1445 client.addEventListener("securityfailure", spy);
1446 const failureData = [0, 0, 0, 1, 0, 0, 0, 0];
1447 client._sock._websocket._receiveData(new Uint8Array(failureData));
1448 expect(spy.args[0][0].detail.status).to.equal(1);
1449 expect('reason' in spy.args[0][0].detail).to.be.false;
1450 });
1451
1452 it('should not include reason in securityfailure event for version < 3.8', function () {
1453 client._rfbVersion = 3.6;
1454 const spy = sinon.spy();
1455 client.addEventListener("securityfailure", spy);
1456 client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 2]));
1457 expect(spy.args[0][0].detail.status).to.equal(2);
1458 expect('reason' in spy.args[0][0].detail).to.be.false;
1459 });
1460 });
1461
1462 describe('ClientInitialisation', function () {
1463 it('should transition to the ServerInitialisation state', function () {
1464 const client = makeRFB();
1465 client._rfbConnectionState = 'connecting';
1466 client._rfbInitState = 'SecurityResult';
1467 client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 0]));
1468 expect(client._rfbInitState).to.equal('ServerInitialisation');
1469 });
1470
1471 it('should send 1 if we are in shared mode', function () {
1472 const client = makeRFB('wss://host:8675', { shared: true });
1473 client._rfbConnectionState = 'connecting';
1474 client._rfbInitState = 'SecurityResult';
1475 client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 0]));
1476 expect(client._sock).to.have.sent(new Uint8Array([1]));
1477 });
1478
1479 it('should send 0 if we are not in shared mode', function () {
1480 const client = makeRFB('wss://host:8675', { shared: false });
1481 client._rfbConnectionState = 'connecting';
1482 client._rfbInitState = 'SecurityResult';
1483 client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 0]));
1484 expect(client._sock).to.have.sent(new Uint8Array([0]));
1485 });
1486 });
1487
1488 describe('ServerInitialisation', function () {
1489 beforeEach(function () {
1490 client._rfbInitState = 'ServerInitialisation';
1491 });
1492
1493 function sendServerInit(opts, client) {
1494 const fullOpts = { width: 10, height: 12, bpp: 24, depth: 24, bigEndian: 0,
1495 trueColor: 1, redMax: 255, greenMax: 255, blueMax: 255,
1496 redShift: 16, greenShift: 8, blueShift: 0, name: 'a name' };
1497 for (let opt in opts) {
1498 fullOpts[opt] = opts[opt];
1499 }
1500 const data = [];
1501
1502 push16(data, fullOpts.width);
1503 push16(data, fullOpts.height);
1504
1505 data.push(fullOpts.bpp);
1506 data.push(fullOpts.depth);
1507 data.push(fullOpts.bigEndian);
1508 data.push(fullOpts.trueColor);
1509
1510 push16(data, fullOpts.redMax);
1511 push16(data, fullOpts.greenMax);
1512 push16(data, fullOpts.blueMax);
1513 push8(data, fullOpts.redShift);
1514 push8(data, fullOpts.greenShift);
1515 push8(data, fullOpts.blueShift);
1516
1517 // padding
1518 push8(data, 0);
1519 push8(data, 0);
1520 push8(data, 0);
1521
1522 client._sock._websocket._receiveData(new Uint8Array(data));
1523
1524 const nameData = [];
1525 let nameLen = [];
1526 pushString(nameData, fullOpts.name);
1527 push32(nameLen, nameData.length);
1528
1529 client._sock._websocket._receiveData(new Uint8Array(nameLen));
1530 client._sock._websocket._receiveData(new Uint8Array(nameData));
1531 }
1532
1533 it('should set the framebuffer width and height', function () {
1534 sendServerInit({ width: 32, height: 84 }, client);
1535 expect(client._fbWidth).to.equal(32);
1536 expect(client._fbHeight).to.equal(84);
1537 });
1538
1539 // NB(sross): we just warn, not fail, for endian-ness and shifts, so we don't test them
1540
1541 it('should set the framebuffer name and call the callback', function () {
1542 const spy = sinon.spy();
1543 client.addEventListener("desktopname", spy);
1544 sendServerInit({ name: 'som€ nam€' }, client);
1545
1546 expect(client._fbName).to.equal('som€ nam€');
1547 expect(spy).to.have.been.calledOnce;
1548 expect(spy.args[0][0].detail.name).to.equal('som€ nam€');
1549 });
1550
1551 it('should handle the extended init message of the tight encoding', function () {
1552 // NB(sross): we don't actually do anything with it, so just test that we can
1553 // read it w/o throwing an error
1554 client._rfbTightVNC = true;
1555 sendServerInit({}, client);
1556
1557 const tightData = [];
1558 push16(tightData, 1);
1559 push16(tightData, 2);
1560 push16(tightData, 3);
1561 push16(tightData, 0);
1562 for (let i = 0; i < 16 + 32 + 48; i++) {
1563 tightData.push(i);
1564 }
1565 client._sock._websocket._receiveData(new Uint8Array(tightData));
1566
1567 expect(client._rfbConnectionState).to.equal('connected');
1568 });
1569
1570 it('should resize the display', function () {
1571 sinon.spy(client._display, 'resize');
1572 sendServerInit({ width: 27, height: 32 }, client);
1573
1574 expect(client._display.resize).to.have.been.calledOnce;
1575 expect(client._display.resize).to.have.been.calledWith(27, 32);
1576 });
1577
1578 it('should grab the mouse and keyboard', function () {
1579 sinon.spy(client._keyboard, 'grab');
1580 sinon.spy(client._mouse, 'grab');
1581 sendServerInit({}, client);
1582 expect(client._keyboard.grab).to.have.been.calledOnce;
1583 expect(client._mouse.grab).to.have.been.calledOnce;
1584 });
1585
1586 describe('Initial Update Request', function () {
1587 beforeEach(function () {
1588 sinon.spy(RFB.messages, "pixelFormat");
1589 sinon.spy(RFB.messages, "clientEncodings");
1590 sinon.spy(RFB.messages, "fbUpdateRequest");
1591 });
1592
1593 afterEach(function () {
1594 RFB.messages.pixelFormat.restore();
1595 RFB.messages.clientEncodings.restore();
1596 RFB.messages.fbUpdateRequest.restore();
1597 });
1598
1599 // TODO(directxman12): test the various options in this configuration matrix
1600 it('should reply with the pixel format, client encodings, and initial update request', function () {
1601 sendServerInit({ width: 27, height: 32 }, client);
1602
1603 expect(RFB.messages.pixelFormat).to.have.been.calledOnce;
1604 expect(RFB.messages.pixelFormat).to.have.been.calledWith(client._sock, 24, true);
1605 expect(RFB.messages.pixelFormat).to.have.been.calledBefore(RFB.messages.clientEncodings);
1606 expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
1607 expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.encodingTight);
1608 expect(RFB.messages.clientEncodings).to.have.been.calledBefore(RFB.messages.fbUpdateRequest);
1609 expect(RFB.messages.fbUpdateRequest).to.have.been.calledOnce;
1610 expect(RFB.messages.fbUpdateRequest).to.have.been.calledWith(client._sock, false, 0, 0, 27, 32);
1611 });
1612
1613 it('should reply with restricted settings for Intel AMT servers', function () {
1614 sendServerInit({ width: 27, height: 32, name: "Intel(r) AMT KVM"}, client);
1615
1616 expect(RFB.messages.pixelFormat).to.have.been.calledOnce;
1617 expect(RFB.messages.pixelFormat).to.have.been.calledWith(client._sock, 8, true);
1618 expect(RFB.messages.pixelFormat).to.have.been.calledBefore(RFB.messages.clientEncodings);
1619 expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
1620 expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.not.include(encodings.encodingTight);
1621 expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.not.include(encodings.encodingHextile);
1622 expect(RFB.messages.clientEncodings).to.have.been.calledBefore(RFB.messages.fbUpdateRequest);
1623 expect(RFB.messages.fbUpdateRequest).to.have.been.calledOnce;
1624 expect(RFB.messages.fbUpdateRequest).to.have.been.calledWith(client._sock, false, 0, 0, 27, 32);
1625 });
1626 });
1627
1628 it('should transition to the "connected" state', function () {
1629 sendServerInit({}, client);
1630 expect(client._rfbConnectionState).to.equal('connected');
1631 });
1632 });
1633 });
1634
1635 describe('Protocol Message Processing After Completing Initialization', function () {
1636 let client;
1637
1638 beforeEach(function () {
1639 client = makeRFB();
1640 client._fbName = 'some device';
1641 client._fbWidth = 640;
1642 client._fbHeight = 20;
1643 });
1644
1645 describe('Framebuffer Update Handling', function () {
1646 const targetDataArr = [
1647 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
1648 0x00, 0xff, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
1649 0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255,
1650 0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255
1651 ];
1652 let targetData;
1653
1654 const targetDataCheckArr = [
1655 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
1656 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
1657 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
1658 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
1659 ];
1660 let targetDataCheck;
1661
1662 before(function () {
1663 // NB(directxman12): PhantomJS 1.x doesn't implement Uint8ClampedArray
1664 targetData = new Uint8Array(targetDataArr);
1665 targetDataCheck = new Uint8Array(targetDataCheckArr);
1666 });
1667
1668 function sendFbuMsg(rectInfo, rectData, client, rectCnt) {
1669 let data = [];
1670
1671 if (!rectCnt || rectCnt > -1) {
1672 // header
1673 data.push(0); // msg type
1674 data.push(0); // padding
1675 push16(data, rectCnt || rectData.length);
1676 }
1677
1678 for (let i = 0; i < rectData.length; i++) {
1679 if (rectInfo[i]) {
1680 push16(data, rectInfo[i].x);
1681 push16(data, rectInfo[i].y);
1682 push16(data, rectInfo[i].width);
1683 push16(data, rectInfo[i].height);
1684 push32(data, rectInfo[i].encoding);
1685 }
1686 data = data.concat(rectData[i]);
1687 }
1688
1689 client._sock._websocket._receiveData(new Uint8Array(data));
1690 }
1691
1692 it('should send an update request if there is sufficient data', function () {
1693 const expectedMsg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: () => {}};
1694 RFB.messages.fbUpdateRequest(expectedMsg, true, 0, 0, 640, 20);
1695
1696 client._framebufferUpdate = () => true;
1697 client._sock._websocket._receiveData(new Uint8Array([0]));
1698
1699 expect(client._sock).to.have.sent(expectedMsg._sQ);
1700 });
1701
1702 it('should not send an update request if we need more data', function () {
1703 client._sock._websocket._receiveData(new Uint8Array([0]));
1704 expect(client._sock._websocket._getSentData()).to.have.length(0);
1705 });
1706
1707 it('should resume receiving an update if we previously did not have enough data', function () {
1708 const expectedMsg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: () => {}};
1709 RFB.messages.fbUpdateRequest(expectedMsg, true, 0, 0, 640, 20);
1710
1711 // just enough to set FBU.rects
1712 client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 3]));
1713 expect(client._sock._websocket._getSentData()).to.have.length(0);
1714
1715 client._framebufferUpdate = function () { this._sock.rQskipBytes(1); return true; }; // we magically have enough data
1716 // 247 should *not* be used as the message type here
1717 client._sock._websocket._receiveData(new Uint8Array([247]));
1718 expect(client._sock).to.have.sent(expectedMsg._sQ);
1719 });
1720
1721 it('should not send a request in continuous updates mode', function () {
1722 client._enabledContinuousUpdates = true;
1723 client._framebufferUpdate = () => true;
1724 client._sock._websocket._receiveData(new Uint8Array([0]));
1725
1726 expect(client._sock._websocket._getSentData()).to.have.length(0);
1727 });
1728
1729 it('should fail on an unsupported encoding', function () {
1730 sinon.spy(client, "_fail");
1731 const rectInfo = { x: 8, y: 11, width: 27, height: 32, encoding: 234 };
1732 sendFbuMsg([rectInfo], [[]], client);
1733 expect(client._fail).to.have.been.calledOnce;
1734 });
1735
1736 it('should be able to pause and resume receiving rects if not enought data', function () {
1737 // seed some initial data to copy
1738 client._fbWidth = 4;
1739 client._fbHeight = 4;
1740 client._display.resize(4, 4);
1741 client._display.blitRgbxImage(0, 0, 4, 2, new Uint8Array(targetDataCheckArr.slice(0, 32)), 0);
1742
1743 const info = [{ x: 0, y: 2, width: 2, height: 2, encoding: 0x01},
1744 { x: 2, y: 2, width: 2, height: 2, encoding: 0x01}];
1745 // data says [{ old_x: 2, old_y: 0 }, { old_x: 0, old_y: 0 }]
1746 const rects = [[0, 2, 0, 0], [0, 0, 0, 0]];
1747 sendFbuMsg([info[0]], [rects[0]], client, 2);
1748 sendFbuMsg([info[1]], [rects[1]], client, -1);
1749 expect(client._display).to.have.displayed(targetDataCheck);
1750 });
1751
1752 describe('Message Encoding Handlers', function () {
1753 beforeEach(function () {
1754 // a really small frame
1755 client._fbWidth = 4;
1756 client._fbHeight = 4;
1757 client._fbDepth = 24;
1758 client._display.resize(4, 4);
1759 });
1760
1761 it('should handle the RAW encoding', function () {
1762 const info = [{ x: 0, y: 0, width: 2, height: 2, encoding: 0x00 },
1763 { x: 2, y: 0, width: 2, height: 2, encoding: 0x00 },
1764 { x: 0, y: 2, width: 4, height: 1, encoding: 0x00 },
1765 { x: 0, y: 3, width: 4, height: 1, encoding: 0x00 }];
1766 // data is in bgrx
1767 const rects = [
1768 [0x00, 0x00, 0xff, 0, 0x00, 0xff, 0x00, 0, 0x00, 0xff, 0x00, 0, 0x00, 0x00, 0xff, 0],
1769 [0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0],
1770 [0xff, 0x00, 0xee, 0, 0xff, 0xee, 0x00, 0, 0xff, 0xee, 0xaa, 0, 0xff, 0xee, 0xab, 0],
1771 [0xff, 0x00, 0xee, 0, 0xff, 0xee, 0x00, 0, 0xff, 0xee, 0xaa, 0, 0xff, 0xee, 0xab, 0]];
1772 sendFbuMsg(info, rects, client);
1773 expect(client._display).to.have.displayed(targetData);
1774 });
1775
1776 it('should handle the RAW encoding in low colour mode', function () {
1777 const info = [{ x: 0, y: 0, width: 2, height: 2, encoding: 0x00 },
1778 { x: 2, y: 0, width: 2, height: 2, encoding: 0x00 },
1779 { x: 0, y: 2, width: 4, height: 1, encoding: 0x00 },
1780 { x: 0, y: 3, width: 4, height: 1, encoding: 0x00 }];
1781 const rects = [
1782 [0x03, 0x03, 0x03, 0x03],
1783 [0x0c, 0x0c, 0x0c, 0x0c],
1784 [0x0c, 0x0c, 0x03, 0x03],
1785 [0x0c, 0x0c, 0x03, 0x03]];
1786 client._fbDepth = 8;
1787 sendFbuMsg(info, rects, client);
1788 expect(client._display).to.have.displayed(targetDataCheck);
1789 });
1790
1791 it('should handle the COPYRECT encoding', function () {
1792 // seed some initial data to copy
1793 client._display.blitRgbxImage(0, 0, 4, 2, new Uint8Array(targetDataCheckArr.slice(0, 32)), 0);
1794
1795 const info = [{ x: 0, y: 2, width: 2, height: 2, encoding: 0x01},
1796 { x: 2, y: 2, width: 2, height: 2, encoding: 0x01}];
1797 // data says [{ old_x: 0, old_y: 0 }, { old_x: 0, old_y: 0 }]
1798 const rects = [[0, 2, 0, 0], [0, 0, 0, 0]];
1799 sendFbuMsg(info, rects, client);
1800 expect(client._display).to.have.displayed(targetDataCheck);
1801 });
1802
1803 // TODO(directxman12): for encodings with subrects, test resuming on partial send?
1804 // TODO(directxman12): test rre_chunk_sz (related to above about subrects)?
1805
1806 it('should handle the RRE encoding', function () {
1807 const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x02 }];
1808 const rect = [];
1809 push32(rect, 2); // 2 subrects
1810 push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1811 rect.push(0xff); // becomes ff0000ff --> #0000FF color
1812 rect.push(0x00);
1813 rect.push(0x00);
1814 rect.push(0xff);
1815 push16(rect, 0); // x: 0
1816 push16(rect, 0); // y: 0
1817 push16(rect, 2); // width: 2
1818 push16(rect, 2); // height: 2
1819 rect.push(0xff); // becomes ff0000ff --> #0000FF color
1820 rect.push(0x00);
1821 rect.push(0x00);
1822 rect.push(0xff);
1823 push16(rect, 2); // x: 2
1824 push16(rect, 2); // y: 2
1825 push16(rect, 2); // width: 2
1826 push16(rect, 2); // height: 2
1827 sendFbuMsg(info, [rect], client);
1828 expect(client._display).to.have.displayed(targetDataCheck);
1829 });
1830
1831 describe('the HEXTILE encoding handler', function () {
1832 it('should handle a tile with fg, bg specified, normal subrects', function () {
1833 const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
1834 const rect = [];
1835 rect.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects
1836 push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1837 rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
1838 rect.push(0x00);
1839 rect.push(0x00);
1840 rect.push(0xff);
1841 rect.push(2); // 2 subrects
1842 rect.push(0); // x: 0, y: 0
1843 rect.push(1 | (1 << 4)); // width: 2, height: 2
1844 rect.push(2 | (2 << 4)); // x: 2, y: 2
1845 rect.push(1 | (1 << 4)); // width: 2, height: 2
1846 sendFbuMsg(info, [rect], client);
1847 expect(client._display).to.have.displayed(targetDataCheck);
1848 });
1849
1850 it('should handle a raw tile', function () {
1851 const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
1852 const rect = [];
1853 rect.push(0x01); // raw
1854 for (let i = 0; i < targetData.length; i += 4) {
1855 rect.push(targetData[i + 2]);
1856 rect.push(targetData[i + 1]);
1857 rect.push(targetData[i]);
1858 rect.push(targetData[i + 3]);
1859 }
1860 sendFbuMsg(info, [rect], client);
1861 expect(client._display).to.have.displayed(targetData);
1862 });
1863
1864 it('should handle a tile with only bg specified (solid bg)', function () {
1865 const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
1866 const rect = [];
1867 rect.push(0x02);
1868 push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1869 sendFbuMsg(info, [rect], client);
1870
1871 const expected = [];
1872 for (let i = 0; i < 16; i++) { push32(expected, 0xff00ff); }
1873 expect(client._display).to.have.displayed(new Uint8Array(expected));
1874 });
1875
1876 it('should handle a tile with only bg specified and an empty frame afterwards', function () {
1877 // set the width so we can have two tiles
1878 client._fbWidth = 8;
1879 client._display.resize(8, 4);
1880
1881 const info = [{ x: 0, y: 0, width: 32, height: 4, encoding: 0x05 }];
1882
1883 const rect = [];
1884
1885 // send a bg frame
1886 rect.push(0x02);
1887 push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1888
1889 // send an empty frame
1890 rect.push(0x00);
1891
1892 sendFbuMsg(info, [rect], client);
1893
1894 const expected = [];
1895 for (let i = 0; i < 16; i++) { push32(expected, 0xff00ff); } // rect 1: solid
1896 for (let i = 0; i < 16; i++) { push32(expected, 0xff00ff); } // rect 2: same bkground color
1897 expect(client._display).to.have.displayed(new Uint8Array(expected));
1898 });
1899
1900 it('should handle a tile with bg and coloured subrects', function () {
1901 const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
1902 const rect = [];
1903 rect.push(0x02 | 0x08 | 0x10); // bg spec, anysubrects, colouredsubrects
1904 push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1905 rect.push(2); // 2 subrects
1906 rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
1907 rect.push(0x00);
1908 rect.push(0x00);
1909 rect.push(0xff);
1910 rect.push(0); // x: 0, y: 0
1911 rect.push(1 | (1 << 4)); // width: 2, height: 2
1912 rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
1913 rect.push(0x00);
1914 rect.push(0x00);
1915 rect.push(0xff);
1916 rect.push(2 | (2 << 4)); // x: 2, y: 2
1917 rect.push(1 | (1 << 4)); // width: 2, height: 2
1918 sendFbuMsg(info, [rect], client);
1919 expect(client._display).to.have.displayed(targetDataCheck);
1920 });
1921
1922 it('should carry over fg and bg colors from the previous tile if not specified', function () {
1923 client._fbWidth = 4;
1924 client._fbHeight = 17;
1925 client._display.resize(4, 17);
1926
1927 const info = [{ x: 0, y: 0, width: 4, height: 17, encoding: 0x05}];
1928 const rect = [];
1929 rect.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects
1930 push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1931 rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
1932 rect.push(0x00);
1933 rect.push(0x00);
1934 rect.push(0xff);
1935 rect.push(8); // 8 subrects
1936 for (let i = 0; i < 4; i++) {
1937 rect.push((0 << 4) | (i * 4)); // x: 0, y: i*4
1938 rect.push(1 | (1 << 4)); // width: 2, height: 2
1939 rect.push((2 << 4) | (i * 4 + 2)); // x: 2, y: i * 4 + 2
1940 rect.push(1 | (1 << 4)); // width: 2, height: 2
1941 }
1942 rect.push(0x08); // anysubrects
1943 rect.push(1); // 1 subrect
1944 rect.push(0); // x: 0, y: 0
1945 rect.push(1 | (1 << 4)); // width: 2, height: 2
1946 sendFbuMsg(info, [rect], client);
1947
1948 let expected = [];
1949 for (let i = 0; i < 4; i++) { expected = expected.concat(targetDataCheckArr); }
1950 expected = expected.concat(targetDataCheckArr.slice(0, 16));
1951 expect(client._display).to.have.displayed(new Uint8Array(expected));
1952 });
1953
1954 it('should fail on an invalid subencoding', function () {
1955 sinon.spy(client, "_fail");
1956 const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
1957 const rects = [[45]]; // an invalid subencoding
1958 sendFbuMsg(info, rects, client);
1959 expect(client._fail).to.have.been.calledOnce;
1960 });
1961 });
1962
1963 it.skip('should handle the TIGHT encoding', function () {
1964 // TODO(directxman12): test this
1965 });
1966
1967 it.skip('should handle the TIGHT_PNG encoding', function () {
1968 // TODO(directxman12): test this
1969 });
1970
1971 it('should handle the DesktopSize pseduo-encoding', function () {
1972 sinon.spy(client._display, 'resize');
1973 sendFbuMsg([{ x: 0, y: 0, width: 20, height: 50, encoding: -223 }], [[]], client);
1974
1975 expect(client._fbWidth).to.equal(20);
1976 expect(client._fbHeight).to.equal(50);
1977
1978 expect(client._display.resize).to.have.been.calledOnce;
1979 expect(client._display.resize).to.have.been.calledWith(20, 50);
1980 });
1981
1982 describe('the ExtendedDesktopSize pseudo-encoding handler', function () {
1983 beforeEach(function () {
1984 // a really small frame
1985 client._fbWidth = 4;
1986 client._fbHeight = 4;
1987 client._display.resize(4, 4);
1988 sinon.spy(client._display, 'resize');
1989 });
1990
1991 function makeScreenData(nrOfScreens) {
1992 const data = [];
1993 push8(data, nrOfScreens); // number-of-screens
1994 push8(data, 0); // padding
1995 push16(data, 0); // padding
1996 for (let i=0; i<nrOfScreens; i += 1) {
1997 push32(data, 0); // id
1998 push16(data, 0); // x-position
1999 push16(data, 0); // y-position
2000 push16(data, 20); // width
2001 push16(data, 50); // height
2002 push32(data, 0); // flags
2003 }
2004 return data;
2005 }
2006
2007 it('should handle a resize requested by this client', function () {
2008 const reasonForChange = 1; // requested by this client
2009 const statusCode = 0; // No error
2010
2011 sendFbuMsg([{ x: reasonForChange, y: statusCode,
2012 width: 20, height: 50, encoding: -308 }],
2013 makeScreenData(1), client);
2014
2015 expect(client._fbWidth).to.equal(20);
2016 expect(client._fbHeight).to.equal(50);
2017
2018 expect(client._display.resize).to.have.been.calledOnce;
2019 expect(client._display.resize).to.have.been.calledWith(20, 50);
2020 });
2021
2022 it('should handle a resize requested by another client', function () {
2023 const reasonForChange = 2; // requested by another client
2024 const statusCode = 0; // No error
2025
2026 sendFbuMsg([{ x: reasonForChange, y: statusCode,
2027 width: 20, height: 50, encoding: -308 }],
2028 makeScreenData(1), client);
2029
2030 expect(client._fbWidth).to.equal(20);
2031 expect(client._fbHeight).to.equal(50);
2032
2033 expect(client._display.resize).to.have.been.calledOnce;
2034 expect(client._display.resize).to.have.been.calledWith(20, 50);
2035 });
2036
2037 it('should be able to recieve requests which contain data for multiple screens', function () {
2038 const reasonForChange = 2; // requested by another client
2039 const statusCode = 0; // No error
2040
2041 sendFbuMsg([{ x: reasonForChange, y: statusCode,
2042 width: 60, height: 50, encoding: -308 }],
2043 makeScreenData(3), client);
2044
2045 expect(client._fbWidth).to.equal(60);
2046 expect(client._fbHeight).to.equal(50);
2047
2048 expect(client._display.resize).to.have.been.calledOnce;
2049 expect(client._display.resize).to.have.been.calledWith(60, 50);
2050 });
2051
2052 it('should not handle a failed request', function () {
2053 const reasonForChange = 1; // requested by this client
2054 const statusCode = 1; // Resize is administratively prohibited
2055
2056 sendFbuMsg([{ x: reasonForChange, y: statusCode,
2057 width: 20, height: 50, encoding: -308 }],
2058 makeScreenData(1), client);
2059
2060 expect(client._fbWidth).to.equal(4);
2061 expect(client._fbHeight).to.equal(4);
2062
2063 expect(client._display.resize).to.not.have.been.called;
2064 });
2065 });
2066
2067 describe('the Cursor pseudo-encoding handler', function () {
2068 beforeEach(function () {
2069 sinon.spy(client._cursor, 'change');
2070 });
2071
2072 it('should handle a standard cursor', function () {
2073 const info = { x: 5, y: 7,
2074 width: 4, height: 4,
2075 encoding: -239};
2076 let rect = [];
2077 let expected = [];
2078
2079 for (let i = 0;i < info.width*info.height;i++) {
2080 push32(rect, 0x11223300);
2081 }
2082 push32(rect, 0xa0a0a0a0);
2083
2084 for (let i = 0;i < info.width*info.height/2;i++) {
2085 push32(expected, 0x332211ff);
2086 push32(expected, 0x33221100);
2087 }
2088 expected = new Uint8Array(expected);
2089
2090 sendFbuMsg([info], [rect], client);
2091
2092 expect(client._cursor.change).to.have.been.calledOnce;
2093 expect(client._cursor.change).to.have.been.calledWith(expected, 5, 7, 4, 4);
2094 });
2095
2096 it('should handle an empty cursor', function () {
2097 const info = { x: 0, y: 0,
2098 width: 0, height: 0,
2099 encoding: -239};
2100 const rect = [];
2101
2102 sendFbuMsg([info], [rect], client);
2103
2104 expect(client._cursor.change).to.have.been.calledOnce;
2105 expect(client._cursor.change).to.have.been.calledWith(new Uint8Array, 0, 0, 0, 0);
2106 });
2107
2108 it('should handle a transparent cursor', function () {
2109 const info = { x: 5, y: 7,
2110 width: 4, height: 4,
2111 encoding: -239};
2112 let rect = [];
2113 let expected = [];
2114
2115 for (let i = 0;i < info.width*info.height;i++) {
2116 push32(rect, 0x11223300);
2117 }
2118 push32(rect, 0x00000000);
2119
2120 for (let i = 0;i < info.width*info.height;i++) {
2121 push32(expected, 0x33221100);
2122 }
2123 expected = new Uint8Array(expected);
2124
2125 sendFbuMsg([info], [rect], client);
2126
2127 expect(client._cursor.change).to.have.been.calledOnce;
2128 expect(client._cursor.change).to.have.been.calledWith(expected, 5, 7, 4, 4);
2129 });
2130
2131 describe('dot for empty cursor', function () {
2132 beforeEach(function () {
2133 client.showDotCursor = true;
2134 // Was called when we enabled dot cursor
2135 client._cursor.change.resetHistory();
2136 });
2137
2138 it('should show a standard cursor', function () {
2139 const info = { x: 5, y: 7,
2140 width: 4, height: 4,
2141 encoding: -239};
2142 let rect = [];
2143 let expected = [];
2144
2145 for (let i = 0;i < info.width*info.height;i++) {
2146 push32(rect, 0x11223300);
2147 }
2148 push32(rect, 0xa0a0a0a0);
2149
2150 for (let i = 0;i < info.width*info.height/2;i++) {
2151 push32(expected, 0x332211ff);
2152 push32(expected, 0x33221100);
2153 }
2154 expected = new Uint8Array(expected);
2155
2156 sendFbuMsg([info], [rect], client);
2157
2158 expect(client._cursor.change).to.have.been.calledOnce;
2159 expect(client._cursor.change).to.have.been.calledWith(expected, 5, 7, 4, 4);
2160 });
2161
2162 it('should handle an empty cursor', function () {
2163 const info = { x: 0, y: 0,
2164 width: 0, height: 0,
2165 encoding: -239};
2166 const rect = [];
2167 const dot = RFB.cursors.dot;
2168
2169 sendFbuMsg([info], [rect], client);
2170
2171 expect(client._cursor.change).to.have.been.calledOnce;
2172 expect(client._cursor.change).to.have.been.calledWith(dot.rgbaPixels,
2173 dot.hotx,
2174 dot.hoty,
2175 dot.w,
2176 dot.h);
2177 });
2178
2179 it('should handle a transparent cursor', function () {
2180 const info = { x: 5, y: 7,
2181 width: 4, height: 4,
2182 encoding: -239};
2183 let rect = [];
2184 const dot = RFB.cursors.dot;
2185
2186 for (let i = 0;i < info.width*info.height;i++) {
2187 push32(rect, 0x11223300);
2188 }
2189 push32(rect, 0x00000000);
2190
2191 sendFbuMsg([info], [rect], client);
2192
2193 expect(client._cursor.change).to.have.been.calledOnce;
2194 expect(client._cursor.change).to.have.been.calledWith(dot.rgbaPixels,
2195 dot.hotx,
2196 dot.hoty,
2197 dot.w,
2198 dot.h);
2199 });
2200 });
2201 });
2202
2203 describe('the VMware Cursor pseudo-encoding handler', function () {
2204 beforeEach(function () {
2205 sinon.spy(client._cursor, 'change');
2206 });
2207 afterEach(function () {
2208 client._cursor.change.resetHistory();
2209 });
2210
2211 it('should handle the VMware cursor pseudo-encoding', function () {
2212 let data = [0x00, 0x00, 0xff, 0,
2213 0x00, 0xff, 0x00, 0,
2214 0x00, 0xff, 0x00, 0,
2215 0x00, 0x00, 0xff, 0];
2216 let rect = [];
2217 push8(rect, 0);
2218 push8(rect, 0);
2219
2220 //AND-mask
2221 for (let i = 0; i < data.length; i++) {
2222 push8(rect, data[i]);
2223 }
2224 //XOR-mask
2225 for (let i = 0; i < data.length; i++) {
2226 push8(rect, data[i]);
2227 }
2228
2229 sendFbuMsg([{ x: 0, y: 0, width: 2, height: 2,
2230 encoding: 0x574d5664}],
2231 [rect], client);
2232 expect(client._FBU.rects).to.equal(0);
2233 });
2234
2235 it('should handle insufficient cursor pixel data', function () {
2236
2237 // Specified 14x23 pixels for the cursor,
2238 // but only send 2x2 pixels worth of data
2239 let w = 14;
2240 let h = 23;
2241 let data = [0x00, 0x00, 0xff, 0,
2242 0x00, 0xff, 0x00, 0];
2243 let rect = [];
2244
2245 push8(rect, 0);
2246 push8(rect, 0);
2247
2248 //AND-mask
2249 for (let i = 0; i < data.length; i++) {
2250 push8(rect, data[i]);
2251 }
2252 //XOR-mask
2253 for (let i = 0; i < data.length; i++) {
2254 push8(rect, data[i]);
2255 }
2256
2257 sendFbuMsg([{ x: 0, y: 0, width: w, height: h,
2258 encoding: 0x574d5664}],
2259 [rect], client);
2260
2261 // expect one FBU to remain unhandled
2262 expect(client._FBU.rects).to.equal(1);
2263 });
2264
2265 it('should update the cursor when type is classic', function () {
2266 let andMask =
2267 [0xff, 0xff, 0xff, 0xff, //Transparent
2268 0xff, 0xff, 0xff, 0xff, //Transparent
2269 0x00, 0x00, 0x00, 0x00, //Opaque
2270 0xff, 0xff, 0xff, 0xff]; //Inverted
2271
2272 let xorMask =
2273 [0x00, 0x00, 0x00, 0x00, //Transparent
2274 0x00, 0x00, 0x00, 0x00, //Transparent
2275 0x11, 0x22, 0x33, 0x44, //Opaque
2276 0xff, 0xff, 0xff, 0x44]; //Inverted
2277
2278 let rect = [];
2279 push8(rect, 0); //cursor_type
2280 push8(rect, 0); //padding
2281 let hotx = 0;
2282 let hoty = 0;
2283 let w = 2;
2284 let h = 2;
2285
2286 //AND-mask
2287 for (let i = 0; i < andMask.length; i++) {
2288 push8(rect, andMask[i]);
2289 }
2290 //XOR-mask
2291 for (let i = 0; i < xorMask.length; i++) {
2292 push8(rect, xorMask[i]);
2293 }
2294
2295 let expectedRgba = [0x00, 0x00, 0x00, 0x00,
2296 0x00, 0x00, 0x00, 0x00,
2297 0x33, 0x22, 0x11, 0xff,
2298 0x00, 0x00, 0x00, 0xff];
2299
2300 sendFbuMsg([{ x: hotx, y: hoty,
2301 width: w, height: h,
2302 encoding: 0x574d5664}],
2303 [rect], client);
2304
2305 expect(client._cursor.change)
2306 .to.have.been.calledOnce;
2307 expect(client._cursor.change)
2308 .to.have.been.calledWith(expectedRgba,
2309 hotx, hoty,
2310 w, h);
2311 });
2312
2313 it('should update the cursor when type is alpha', function () {
2314 let data = [0xee, 0x55, 0xff, 0x00, // rgba
2315 0x00, 0xff, 0x00, 0xff,
2316 0x00, 0xff, 0x00, 0x22,
2317 0x00, 0xff, 0x00, 0x22,
2318 0x00, 0xff, 0x00, 0x22,
2319 0x00, 0x00, 0xff, 0xee];
2320 let rect = [];
2321 push8(rect, 1); //cursor_type
2322 push8(rect, 0); //padding
2323 let hotx = 0;
2324 let hoty = 0;
2325 let w = 3;
2326 let h = 2;
2327
2328 for (let i = 0; i < data.length; i++) {
2329 push8(rect, data[i]);
2330 }
2331
2332 let expectedRgba = [0xee, 0x55, 0xff, 0x00,
2333 0x00, 0xff, 0x00, 0xff,
2334 0x00, 0xff, 0x00, 0x22,
2335 0x00, 0xff, 0x00, 0x22,
2336 0x00, 0xff, 0x00, 0x22,
2337 0x00, 0x00, 0xff, 0xee];
2338
2339 sendFbuMsg([{ x: hotx, y: hoty,
2340 width: w, height: h,
2341 encoding: 0x574d5664}],
2342 [rect], client);
2343
2344 expect(client._cursor.change)
2345 .to.have.been.calledOnce;
2346 expect(client._cursor.change)
2347 .to.have.been.calledWith(expectedRgba,
2348 hotx, hoty,
2349 w, h);
2350 });
2351
2352 it('should not update cursor when incorrect cursor type given', function () {
2353 let rect = [];
2354 push8(rect, 3); // invalid cursor type
2355 push8(rect, 0); // padding
2356
2357 client._cursor.change.resetHistory();
2358 sendFbuMsg([{ x: 0, y: 0, width: 2, height: 2,
2359 encoding: 0x574d5664}],
2360 [rect], client);
2361
2362 expect(client._cursor.change)
2363 .to.not.have.been.called;
2364 });
2365 });
2366
2367 it('should handle the last_rect pseudo-encoding', function () {
2368 sendFbuMsg([{ x: 0, y: 0, width: 0, height: 0, encoding: -224}], [[]], client, 100);
2369 expect(client._FBU.rects).to.equal(0);
2370 });
2371
2372 it('should handle the DesktopName pseudo-encoding', function () {
2373 let data = [];
2374 push32(data, 13);
2375 pushString(data, "som€ nam€");
2376
2377 const spy = sinon.spy();
2378 client.addEventListener("desktopname", spy);
2379
2380 sendFbuMsg([{ x: 0, y: 0, width: 0, height: 0, encoding: -307 }], [data], client);
2381
2382 expect(client._fbName).to.equal('som€ nam€');
2383 expect(spy).to.have.been.calledOnce;
2384 expect(spy.args[0][0].detail.name).to.equal('som€ nam€');
2385 });
2386 });
2387 });
2388
2389 describe('XVP Message Handling', function () {
2390 it('should set the XVP version and fire the callback with the version on XVP_INIT', function () {
2391 const spy = sinon.spy();
2392 client.addEventListener("capabilities", spy);
2393 client._sock._websocket._receiveData(new Uint8Array([250, 0, 10, 1]));
2394 expect(client._rfbXvpVer).to.equal(10);
2395 expect(spy).to.have.been.calledOnce;
2396 expect(spy.args[0][0].detail.capabilities.power).to.be.true;
2397 expect(client.capabilities.power).to.be.true;
2398 });
2399
2400 it('should fail on unknown XVP message types', function () {
2401 sinon.spy(client, "_fail");
2402 client._sock._websocket._receiveData(new Uint8Array([250, 0, 10, 237]));
2403 expect(client._fail).to.have.been.calledOnce;
2404 });
2405 });
2406
2407 describe('Normal Clipboard Handling Receive', function () {
2408 it('should fire the clipboard callback with the retrieved text on ServerCutText', function () {
2409 const expectedStr = 'cheese!';
2410 const data = [3, 0, 0, 0];
2411 push32(data, expectedStr.length);
2412 for (let i = 0; i < expectedStr.length; i++) { data.push(expectedStr.charCodeAt(i)); }
2413 const spy = sinon.spy();
2414 client.addEventListener("clipboard", spy);
2415
2416 client._sock._websocket._receiveData(new Uint8Array(data));
2417 expect(spy).to.have.been.calledOnce;
2418 expect(spy.args[0][0].detail.text).to.equal(expectedStr);
2419 });
2420 });
2421
2422 describe('Extended clipboard Handling', function () {
2423
2424 describe('Extended clipboard initialization', function () {
2425 beforeEach(function () {
2426 sinon.spy(RFB.messages, 'extendedClipboardCaps');
2427 });
2428
2429 afterEach(function () {
2430 RFB.messages.extendedClipboardCaps.restore();
2431 });
2432
2433 it('should update capabilities when receiving a Caps message', function () {
2434 let data = [3, 0, 0, 0];
2435 const flags = [0x1F, 0x00, 0x00, 0x03];
2436 let fileSizes = [0x00, 0x00, 0x00, 0x1E,
2437 0x00, 0x00, 0x00, 0x3C];
2438
2439 push32(data, toUnsigned32bit(-12));
2440 data = data.concat(flags);
2441 data = data.concat(fileSizes);
2442 client._sock._websocket._receiveData(new Uint8Array(data));
2443
2444 // Check that we give an response caps when we receive one
2445 expect(RFB.messages.extendedClipboardCaps).to.have.been.calledOnce;
2446
2447 // FIXME: Can we avoid checking internal variables?
2448 expect(client._clipboardServerCapabilitiesFormats[0]).to.not.equal(true);
2449 expect(client._clipboardServerCapabilitiesFormats[1]).to.equal(true);
2450 expect(client._clipboardServerCapabilitiesFormats[2]).to.equal(true);
2451 expect(client._clipboardServerCapabilitiesActions[(1 << 24)]).to.equal(true);
2452 });
2453
2454
2455 });
2456
2457 describe('Extended Clipboard Handling Receive', function () {
2458
2459 beforeEach(function () {
2460 // Send our capabilities
2461 let data = [3, 0, 0, 0];
2462 const flags = [0x1F, 0x00, 0x00, 0x01];
2463 let fileSizes = [0x00, 0x00, 0x00, 0x1E];
2464
2465 push32(data, toUnsigned32bit(-8));
2466 data = data.concat(flags);
2467 data = data.concat(fileSizes);
2468 client._sock._websocket._receiveData(new Uint8Array(data));
2469 });
2470
2471 describe('Handle Provide', function () {
2472 it('should update clipboard with correct Unicode data from a Provide message', function () {
2473 let expectedData = "Aå漢字!";
2474 let data = [3, 0, 0, 0];
2475 const flags = [0x10, 0x00, 0x00, 0x01];
2476
2477 /* The size 10 (utf8 encoded string size) and the
2478 string "Aå漢字!" utf8 encoded and deflated. */
2479 let deflatedData = [120, 94, 99, 96, 96, 224, 114, 60,
2480 188, 244, 217, 158, 69, 79, 215,
2481 78, 87, 4, 0, 35, 207, 6, 66];
2482
2483 // How much data we are sending.
2484 push32(data, toUnsigned32bit(-(4 + deflatedData.length)));
2485
2486 data = data.concat(flags);
2487 data = data.concat(deflatedData);
2488
2489 const spy = sinon.spy();
2490 client.addEventListener("clipboard", spy);
2491
2492 client._sock._websocket._receiveData(new Uint8Array(data));
2493 expect(spy).to.have.been.calledOnce;
2494 expect(spy.args[0][0].detail.text).to.equal(expectedData);
2495 client.removeEventListener("clipboard", spy);
2496 });
2497
2498 it('should update clipboard with correct escape characters from a Provide message ', function () {
2499 let expectedData = "Oh\nmy!";
2500 let data = [3, 0, 0, 0];
2501 const flags = [0x10, 0x00, 0x00, 0x01];
2502
2503 let text = encodeUTF8("Oh\r\nmy!\0");
2504
2505 let deflatedText = deflateWithSize(text);
2506
2507 // How much data we are sending.
2508 push32(data, toUnsigned32bit(-(4 + deflatedText.length)));
2509
2510 data = data.concat(flags);
2511
2512 let sendData = new Uint8Array(data.length + deflatedText.length);
2513 sendData.set(data);
2514 sendData.set(deflatedText, data.length);
2515
2516 const spy = sinon.spy();
2517 client.addEventListener("clipboard", spy);
2518
2519 client._sock._websocket._receiveData(sendData);
2520 expect(spy).to.have.been.calledOnce;
2521 expect(spy.args[0][0].detail.text).to.equal(expectedData);
2522 client.removeEventListener("clipboard", spy);
2523 });
2524
2525 it('should be able to handle large Provide messages', function () {
2526 // repeat() is not supported in IE so a loop is needed instead
2527 let expectedData = "hello";
2528 for (let i = 1; i <= 100000; i++) {
2529 expectedData += "hello";
2530 }
2531
2532 let data = [3, 0, 0, 0];
2533 const flags = [0x10, 0x00, 0x00, 0x01];
2534
2535 let text = encodeUTF8(expectedData + "\0");
2536
2537 let deflatedText = deflateWithSize(text);
2538
2539 // How much data we are sending.
2540 push32(data, toUnsigned32bit(-(4 + deflatedText.length)));
2541
2542 data = data.concat(flags);
2543
2544 let sendData = new Uint8Array(data.length + deflatedText.length);
2545 sendData.set(data);
2546 sendData.set(deflatedText, data.length);
2547
2548 const spy = sinon.spy();
2549 client.addEventListener("clipboard", spy);
2550
2551 client._sock._websocket._receiveData(sendData);
2552 expect(spy).to.have.been.calledOnce;
2553 expect(spy.args[0][0].detail.text).to.equal(expectedData);
2554 client.removeEventListener("clipboard", spy);
2555 });
2556
2557 });
2558
2559 describe('Handle Notify', function () {
2560 beforeEach(function () {
2561 sinon.spy(RFB.messages, 'extendedClipboardRequest');
2562 });
2563
2564 afterEach(function () {
2565 RFB.messages.extendedClipboardRequest.restore();
2566 });
2567
2568 it('should make a request with supported formats when receiving a notify message', function () {
2569 let data = [3, 0, 0, 0];
2570 const flags = [0x08, 0x00, 0x00, 0x07];
2571 push32(data, toUnsigned32bit(-4));
2572 data = data.concat(flags);
2573 let expectedData = [0x01];
2574
2575 client._sock._websocket._receiveData(new Uint8Array(data));
2576
2577 expect(RFB.messages.extendedClipboardRequest).to.have.been.calledOnce;
2578 expect(RFB.messages.extendedClipboardRequest).to.have.been.calledWith(client._sock, expectedData);
2579 });
2580 });
2581
2582 describe('Handle Peek', function () {
2583 beforeEach(function () {
2584 sinon.spy(RFB.messages, 'extendedClipboardNotify');
2585 });
2586
2587 afterEach(function () {
2588 RFB.messages.extendedClipboardNotify.restore();
2589 });
2590
2591 it('should send an empty Notify when receiving a Peek and no excisting clipboard data', function () {
2592 let data = [3, 0, 0, 0];
2593 const flags = [0x04, 0x00, 0x00, 0x00];
2594 push32(data, toUnsigned32bit(-4));
2595 data = data.concat(flags);
2596 let expectedData = [];
2597
2598 client._sock._websocket._receiveData(new Uint8Array(data));
2599
2600 expect(RFB.messages.extendedClipboardNotify).to.have.been.calledOnce;
2601 expect(RFB.messages.extendedClipboardNotify).to.have.been.calledWith(client._sock, expectedData);
2602 });
2603
2604 it('should send a Notify message with supported formats when receiving a Peek', function () {
2605 let data = [3, 0, 0, 0];
2606 const flags = [0x04, 0x00, 0x00, 0x00];
2607 push32(data, toUnsigned32bit(-4));
2608 data = data.concat(flags);
2609 let expectedData = [0x01];
2610
2611 // Needed to have clipboard data to read.
2612 // This will trigger a call to Notify, reset history
2613 client.clipboardPasteFrom("HejHej");
2614 RFB.messages.extendedClipboardNotify.resetHistory();
2615
2616 client._sock._websocket._receiveData(new Uint8Array(data));
2617
2618 expect(RFB.messages.extendedClipboardNotify).to.have.been.calledOnce;
2619 expect(RFB.messages.extendedClipboardNotify).to.have.been.calledWith(client._sock, expectedData);
2620 });
2621 });
2622
2623 describe('Handle Request', function () {
2624 beforeEach(function () {
2625 sinon.spy(RFB.messages, 'extendedClipboardProvide');
2626 });
2627
2628 afterEach(function () {
2629 RFB.messages.extendedClipboardProvide.restore();
2630 });
2631
2632 it('should send a Provide message with supported formats when receiving a Request', function () {
2633 let data = [3, 0, 0, 0];
2634 const flags = [0x02, 0x00, 0x00, 0x01];
2635 push32(data, toUnsigned32bit(-4));
2636 data = data.concat(flags);
2637 let expectedData = [0x01];
2638
2639 client.clipboardPasteFrom("HejHej");
2640 expect(RFB.messages.extendedClipboardProvide).to.not.have.been.called;
2641
2642 client._sock._websocket._receiveData(new Uint8Array(data));
2643
2644 expect(RFB.messages.extendedClipboardProvide).to.have.been.calledOnce;
2645 expect(RFB.messages.extendedClipboardProvide).to.have.been.calledWith(client._sock, expectedData, ["HejHej"]);
2646 });
2647 });
2648 });
2649
2650 });
2651
2652 it('should fire the bell callback on Bell', function () {
2653 const spy = sinon.spy();
2654 client.addEventListener("bell", spy);
2655 client._sock._websocket._receiveData(new Uint8Array([2]));
2656 expect(spy).to.have.been.calledOnce;
2657 });
2658
2659 it('should respond correctly to ServerFence', function () {
2660 const expectedMsg = {_sQ: new Uint8Array(16), _sQlen: 0, flush: () => {}};
2661 const incomingMsg = {_sQ: new Uint8Array(16), _sQlen: 0, flush: () => {}};
2662
2663 const payload = "foo\x00ab9";
2664
2665 // ClientFence and ServerFence are identical in structure
2666 RFB.messages.clientFence(expectedMsg, (1<<0) | (1<<1), payload);
2667 RFB.messages.clientFence(incomingMsg, 0xffffffff, payload);
2668
2669 client._sock._websocket._receiveData(incomingMsg._sQ);
2670
2671 expect(client._sock).to.have.sent(expectedMsg._sQ);
2672
2673 expectedMsg._sQlen = 0;
2674 incomingMsg._sQlen = 0;
2675
2676 RFB.messages.clientFence(expectedMsg, (1<<0), payload);
2677 RFB.messages.clientFence(incomingMsg, (1<<0) | (1<<31), payload);
2678
2679 client._sock._websocket._receiveData(incomingMsg._sQ);
2680
2681 expect(client._sock).to.have.sent(expectedMsg._sQ);
2682 });
2683
2684 it('should enable continuous updates on first EndOfContinousUpdates', function () {
2685 const expectedMsg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: () => {}};
2686
2687 RFB.messages.enableContinuousUpdates(expectedMsg, true, 0, 0, 640, 20);
2688
2689 expect(client._enabledContinuousUpdates).to.be.false;
2690
2691 client._sock._websocket._receiveData(new Uint8Array([150]));
2692
2693 expect(client._enabledContinuousUpdates).to.be.true;
2694 expect(client._sock).to.have.sent(expectedMsg._sQ);
2695 });
2696
2697 it('should disable continuous updates on subsequent EndOfContinousUpdates', function () {
2698 client._enabledContinuousUpdates = true;
2699 client._supportsContinuousUpdates = true;
2700
2701 client._sock._websocket._receiveData(new Uint8Array([150]));
2702
2703 expect(client._enabledContinuousUpdates).to.be.false;
2704 });
2705
2706 it('should update continuous updates on resize', function () {
2707 const expectedMsg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: () => {}};
2708 RFB.messages.enableContinuousUpdates(expectedMsg, true, 0, 0, 90, 700);
2709
2710 client._resize(450, 160);
2711
2712 expect(client._sock._websocket._getSentData()).to.have.length(0);
2713
2714 client._enabledContinuousUpdates = true;
2715
2716 client._resize(90, 700);
2717
2718 expect(client._sock).to.have.sent(expectedMsg._sQ);
2719 });
2720
2721 it('should fail on an unknown message type', function () {
2722 sinon.spy(client, "_fail");
2723 client._sock._websocket._receiveData(new Uint8Array([87]));
2724 expect(client._fail).to.have.been.calledOnce;
2725 });
2726 });
2727
2728 describe('Asynchronous Events', function () {
2729 let client;
2730 beforeEach(function () {
2731 client = makeRFB();
2732 });
2733
2734 describe('Mouse event handlers', function () {
2735 beforeEach(function () {
2736 this.clock = sinon.useFakeTimers(Date.now());
2737 sinon.spy(RFB.messages, 'pointerEvent');
2738 });
2739 afterEach(function () {
2740 this.clock.restore();
2741 RFB.messages.pointerEvent.restore();
2742 });
2743
2744 it('should not send button messages in view-only mode', function () {
2745 client._viewOnly = true;
2746 client._handleMouseButton(0, 0, 1, 0x001);
2747 expect(RFB.messages.pointerEvent).to.not.have.been.called;
2748 });
2749
2750 it('should not send movement messages in view-only mode', function () {
2751 client._viewOnly = true;
2752 client._handleMouseMove(0, 0);
2753 expect(RFB.messages.pointerEvent).to.not.have.been.called;
2754 });
2755
2756 it('should send a pointer event on mouse button presses', function () {
2757 client._handleMouseButton(10, 12, 1, 0x001);
2758 expect(RFB.messages.pointerEvent).to.have.been.calledOnce;
2759 });
2760
2761 it('should send a mask of 1 on mousedown', function () {
2762 client._handleMouseButton(11, 13, 1, 0x001);
2763 expect(RFB.messages.pointerEvent).to.have.been.calledWith(
2764 client._sock, 11, 13, 0x001);
2765 });
2766
2767 it('should send a mask of 0 on mouseup', function () {
2768 client._mouseButtonMask = 0x001;
2769 client._handleMouseButton(105, 120, 0, 0x001);
2770 expect(RFB.messages.pointerEvent).to.have.been.calledWith(
2771 client._sock, 105, 120, 0x000);
2772 });
2773
2774 it('should send a mask of 0 on mousemove', function () {
2775 client._handleMouseMove(100, 200);
2776 expect(RFB.messages.pointerEvent).to.have.been.calledWith(
2777 client._sock, 100, 200, 0x000);
2778 });
2779
2780 it('should set the button mask so that future mouse movements use it', function () {
2781 client._handleMouseButton(10, 12, 1, 0x010);
2782 client._handleMouseMove(13, 9);
2783 expect(RFB.messages.pointerEvent).to.have.been.calledTwice;
2784 expect(RFB.messages.pointerEvent).to.have.been.calledWith(
2785 client._sock, 13, 9, 0x010);
2786 });
2787
2788 it('should send a single pointer event on mouse movement', function () {
2789 client._handleMouseMove(100, 200);
2790 this.clock.tick(100);
2791 expect(RFB.messages.pointerEvent).to.have.been.calledOnce;
2792 });
2793
2794 it('should delay one move if two events are too close', function () {
2795 client._handleMouseMove(18, 30);
2796 client._handleMouseMove(20, 50);
2797 expect(RFB.messages.pointerEvent).to.have.been.calledOnce;
2798 this.clock.tick(100);
2799 expect(RFB.messages.pointerEvent).to.have.been.calledTwice;
2800 });
2801
2802 it('should only send first and last move of many close events', function () {
2803 client._handleMouseMove(18, 40);
2804 client._handleMouseMove(20, 50);
2805 client._handleMouseMove(21, 55);
2806
2807 expect(RFB.messages.pointerEvent).to.have.been.calledOnce;
2808 this.clock.tick(60);
2809
2810 expect(RFB.messages.pointerEvent).to.have.been.calledTwice;
2811 expect(RFB.messages.pointerEvent.firstCall).to.have.been.calledWith(
2812 client._sock, 18, 40, 0x000);
2813 expect(RFB.messages.pointerEvent.secondCall).to.have.been.calledWith(
2814 client._sock, 21, 55, 0x000);
2815 });
2816
2817 // We selected the 17ms since that is ~60 FPS
2818 it('should send move events every 17 ms', function () {
2819 client._handleMouseMove(1, 10); // instant send
2820 this.clock.tick(10);
2821 client._handleMouseMove(2, 20); // delayed
2822 this.clock.tick(10); // timeout send
2823 client._handleMouseMove(3, 30); // delayed
2824 this.clock.tick(10);
2825 client._handleMouseMove(4, 40); // delayed
2826 this.clock.tick(10); // timeout send
2827 client._handleMouseMove(5, 50); // delayed
2828
2829 expect(RFB.messages.pointerEvent).to.have.callCount(3);
2830 expect(RFB.messages.pointerEvent.firstCall).to.have.been.calledWith(
2831 client._sock, 1, 10, 0x000);
2832 expect(RFB.messages.pointerEvent.secondCall).to.have.been.calledWith(
2833 client._sock, 2, 20, 0x000);
2834 expect(RFB.messages.pointerEvent.thirdCall).to.have.been.calledWith(
2835 client._sock, 4, 40, 0x000);
2836 });
2837
2838 it('should send waiting move events before a button press', function () {
2839 client._handleMouseMove(13, 9);
2840 client._handleMouseMove(20, 70);
2841 client._handleMouseButton(10, 12, 1, 0x100);
2842 expect(RFB.messages.pointerEvent).to.have.been.calledThrice;
2843 expect(RFB.messages.pointerEvent.firstCall).to.have.been.calledWith(
2844 client._sock, 13, 9, 0x000);
2845 expect(RFB.messages.pointerEvent.secondCall).to.have.been.calledWith(
2846 client._sock, 10, 12, 0x000);
2847 expect(RFB.messages.pointerEvent.thirdCall).to.have.been.calledWith(
2848 client._sock, 10, 12, 0x100);
2849 });
2850
2851 it('should not delay events when button mask changes', function () {
2852 client._handleMouseMove(13, 9); // instant
2853 client._handleMouseMove(11, 10); // delayed
2854 client._handleMouseButton(10, 12, 1, 0x010); // flush delayed
2855 expect(RFB.messages.pointerEvent).to.have.been.calledThrice;
2856 });
2857
2858 it('should send move events with enough time apart normally', function () {
2859 client._handleMouseMove(58, 60);
2860 expect(RFB.messages.pointerEvent).to.have.been.calledOnce;
2861
2862 this.clock.tick(20);
2863
2864 client._handleMouseMove(25, 60);
2865 expect(RFB.messages.pointerEvent).to.have.been.calledTwice;
2866 });
2867
2868 it('should not send waiting move events if disconnected', function () {
2869 client._handleMouseMove(88, 99);
2870 client._handleMouseMove(66, 77);
2871 client.disconnect();
2872 this.clock.tick(20);
2873 expect(RFB.messages.pointerEvent).to.have.been.calledOnce;
2874 });
2875 });
2876
2877 describe('Keyboard Event Handlers', function () {
2878 it('should send a key message on a key press', function () {
2879 client._handleKeyEvent(0x41, 'KeyA', true);
2880 const keyMsg = {_sQ: new Uint8Array(8), _sQlen: 0, flush: () => {}};
2881 RFB.messages.keyEvent(keyMsg, 0x41, 1);
2882 expect(client._sock).to.have.sent(keyMsg._sQ);
2883 });
2884
2885 it('should not send messages in view-only mode', function () {
2886 client._viewOnly = true;
2887 sinon.spy(client._sock, 'flush');
2888 client._handleKeyEvent('a', 'KeyA', true);
2889 expect(client._sock.flush).to.not.have.been.called;
2890 });
2891 });
2892
2893 describe('WebSocket event handlers', function () {
2894 // message events
2895 it('should do nothing if we receive an empty message and have nothing in the queue', function () {
2896 client._normalMsg = sinon.spy();
2897 client._sock._websocket._receiveData(new Uint8Array([]));
2898 expect(client._normalMsg).to.not.have.been.called;
2899 });
2900
2901 it('should handle a message in the connected state as a normal message', function () {
2902 client._normalMsg = sinon.spy();
2903 client._sock._websocket._receiveData(new Uint8Array([1, 2, 3]));
2904 expect(client._normalMsg).to.have.been.called;
2905 });
2906
2907 it('should handle a message in any non-disconnected/failed state like an init message', function () {
2908 client._rfbConnectionState = 'connecting';
2909 client._rfbInitState = 'ProtocolVersion';
2910 client._initMsg = sinon.spy();
2911 client._sock._websocket._receiveData(new Uint8Array([1, 2, 3]));
2912 expect(client._initMsg).to.have.been.called;
2913 });
2914
2915 it('should process all normal messages directly', function () {
2916 const spy = sinon.spy();
2917 client.addEventListener("bell", spy);
2918 client._sock._websocket._receiveData(new Uint8Array([0x02, 0x02]));
2919 expect(spy).to.have.been.calledTwice;
2920 });
2921
2922 // open events
2923 it('should update the state to ProtocolVersion on open (if the state is "connecting")', function () {
2924 client = new RFB(document.createElement('div'), 'wss://host:8675');
2925 this.clock.tick();
2926 client._sock._websocket._open();
2927 expect(client._rfbInitState).to.equal('ProtocolVersion');
2928 });
2929
2930 it('should fail if we are not currently ready to connect and we get an "open" event', function () {
2931 sinon.spy(client, "_fail");
2932 client._rfbConnectionState = 'connected';
2933 client._sock._websocket._open();
2934 expect(client._fail).to.have.been.calledOnce;
2935 });
2936
2937 // close events
2938 it('should transition to "disconnected" from "disconnecting" on a close event', function () {
2939 const real = client._sock._websocket.close;
2940 client._sock._websocket.close = () => {};
2941 client.disconnect();
2942 expect(client._rfbConnectionState).to.equal('disconnecting');
2943 client._sock._websocket.close = real;
2944 client._sock._websocket.close();
2945 expect(client._rfbConnectionState).to.equal('disconnected');
2946 });
2947
2948 it('should fail if we get a close event while connecting', function () {
2949 sinon.spy(client, "_fail");
2950 client._rfbConnectionState = 'connecting';
2951 client._sock._websocket.close();
2952 expect(client._fail).to.have.been.calledOnce;
2953 });
2954
2955 it('should unregister close event handler', function () {
2956 sinon.spy(client._sock, 'off');
2957 client.disconnect();
2958 client._sock._websocket.close();
2959 expect(client._sock.off).to.have.been.calledWith('close');
2960 });
2961
2962 // error events do nothing
2963 });
2964 });
2965
2966 describe('Quality level setting', function () {
2967 const defaultQuality = 6;
2968
2969 let client;
2970
2971 beforeEach(function () {
2972 client = makeRFB();
2973 sinon.spy(RFB.messages, "clientEncodings");
2974 });
2975
2976 afterEach(function () {
2977 RFB.messages.clientEncodings.restore();
2978 });
2979
2980 it(`should equal ${defaultQuality} by default`, function () {
2981 expect(client._qualityLevel).to.equal(defaultQuality);
2982 });
2983
2984 it('should ignore non-integers when set', function () {
2985 client.qualityLevel = '1';
2986 expect(RFB.messages.clientEncodings).to.not.have.been.called;
2987
2988 RFB.messages.clientEncodings.resetHistory();
2989
2990 client.qualityLevel = 1.5;
2991 expect(RFB.messages.clientEncodings).to.not.have.been.called;
2992
2993 RFB.messages.clientEncodings.resetHistory();
2994
2995 client.qualityLevel = null;
2996 expect(RFB.messages.clientEncodings).to.not.have.been.called;
2997
2998 RFB.messages.clientEncodings.resetHistory();
2999
3000 client.qualityLevel = undefined;
3001 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3002
3003 RFB.messages.clientEncodings.resetHistory();
3004
3005 client.qualityLevel = {};
3006 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3007 });
3008
3009 it('should ignore integers out of range [0, 9]', function () {
3010 client.qualityLevel = -1;
3011 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3012
3013 RFB.messages.clientEncodings.resetHistory();
3014
3015 client.qualityLevel = 10;
3016 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3017 });
3018
3019 it('should send clientEncodings with new quality value', function () {
3020 let newQuality;
3021
3022 newQuality = 8;
3023 client.qualityLevel = newQuality;
3024 expect(client.qualityLevel).to.equal(newQuality);
3025 expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
3026 expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.pseudoEncodingQualityLevel0 + newQuality);
3027 });
3028
3029 it('should not send clientEncodings if quality is the same', function () {
3030 let newQuality;
3031
3032 newQuality = 2;
3033 client.qualityLevel = newQuality;
3034 expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
3035 expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.pseudoEncodingQualityLevel0 + newQuality);
3036
3037 RFB.messages.clientEncodings.resetHistory();
3038
3039 client.qualityLevel = newQuality;
3040 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3041 });
3042
3043 it('should not send clientEncodings if not in connected state', function () {
3044 let newQuality;
3045
3046 client._rfbConnectionState = '';
3047 newQuality = 2;
3048 client.qualityLevel = newQuality;
3049 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3050
3051 RFB.messages.clientEncodings.resetHistory();
3052
3053 client._rfbConnectionState = 'connnecting';
3054 newQuality = 6;
3055 client.qualityLevel = newQuality;
3056 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3057
3058 RFB.messages.clientEncodings.resetHistory();
3059
3060 client._rfbConnectionState = 'connected';
3061 newQuality = 5;
3062 client.qualityLevel = newQuality;
3063 expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
3064 expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.pseudoEncodingQualityLevel0 + newQuality);
3065 });
3066 });
3067
3068 describe('Compression level setting', function () {
3069 const defaultCompression = 2;
3070
3071 let client;
3072
3073 beforeEach(function () {
3074 client = makeRFB();
3075 sinon.spy(RFB.messages, "clientEncodings");
3076 });
3077
3078 afterEach(function () {
3079 RFB.messages.clientEncodings.restore();
3080 });
3081
3082 it(`should equal ${defaultCompression} by default`, function () {
3083 expect(client._compressionLevel).to.equal(defaultCompression);
3084 });
3085
3086 it('should ignore non-integers when set', function () {
3087 client.compressionLevel = '1';
3088 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3089
3090 RFB.messages.clientEncodings.resetHistory();
3091
3092 client.compressionLevel = 1.5;
3093 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3094
3095 RFB.messages.clientEncodings.resetHistory();
3096
3097 client.compressionLevel = null;
3098 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3099
3100 RFB.messages.clientEncodings.resetHistory();
3101
3102 client.compressionLevel = undefined;
3103 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3104
3105 RFB.messages.clientEncodings.resetHistory();
3106
3107 client.compressionLevel = {};
3108 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3109 });
3110
3111 it('should ignore integers out of range [0, 9]', function () {
3112 client.compressionLevel = -1;
3113 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3114
3115 RFB.messages.clientEncodings.resetHistory();
3116
3117 client.compressionLevel = 10;
3118 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3119 });
3120
3121 it('should send clientEncodings with new compression value', function () {
3122 let newCompression;
3123
3124 newCompression = 5;
3125 client.compressionLevel = newCompression;
3126 expect(client.compressionLevel).to.equal(newCompression);
3127 expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
3128 expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.pseudoEncodingCompressLevel0 + newCompression);
3129 });
3130
3131 it('should not send clientEncodings if compression is the same', function () {
3132 let newCompression;
3133
3134 newCompression = 9;
3135 client.compressionLevel = newCompression;
3136 expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
3137 expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.pseudoEncodingCompressLevel0 + newCompression);
3138
3139 RFB.messages.clientEncodings.resetHistory();
3140
3141 client.compressionLevel = newCompression;
3142 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3143 });
3144
3145 it('should not send clientEncodings if not in connected state', function () {
3146 let newCompression;
3147
3148 client._rfbConnectionState = '';
3149 newCompression = 7;
3150 client.compressionLevel = newCompression;
3151 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3152
3153 RFB.messages.clientEncodings.resetHistory();
3154
3155 client._rfbConnectionState = 'connnecting';
3156 newCompression = 6;
3157 client.compressionLevel = newCompression;
3158 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3159
3160 RFB.messages.clientEncodings.resetHistory();
3161
3162 client._rfbConnectionState = 'connected';
3163 newCompression = 5;
3164 client.compressionLevel = newCompression;
3165 expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
3166 expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.pseudoEncodingCompressLevel0 + newCompression);
3167 });
3168 });
3169 });
3170
3171 describe('RFB messages', function () {
3172 let sock;
3173
3174 before(function () {
3175 FakeWebSocket.replace();
3176 sock = new Websock();
3177 sock.open();
3178 });
3179
3180 after(function () {
3181 FakeWebSocket.restore();
3182 });
3183
3184 describe('Extended Clipboard Handling Send', function () {
3185 beforeEach(function () {
3186 sinon.spy(RFB.messages, 'clientCutText');
3187 });
3188
3189 afterEach(function () {
3190 RFB.messages.clientCutText.restore();
3191 });
3192
3193 it('should call clientCutText with correct Caps data', function () {
3194 let formats = {
3195 0: 2,
3196 2: 4121
3197 };
3198 let expectedData = new Uint8Array([0x1F, 0x00, 0x00, 0x05,
3199 0x00, 0x00, 0x00, 0x02,
3200 0x00, 0x00, 0x10, 0x19]);
3201 let actions = [
3202 1 << 24, // Caps
3203 1 << 25, // Request
3204 1 << 26, // Peek
3205 1 << 27, // Notify
3206 1 << 28 // Provide
3207 ];
3208
3209 RFB.messages.extendedClipboardCaps(sock, actions, formats);
3210 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
3211 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData);
3212 });
3213
3214 it('should call clientCutText with correct Request data', function () {
3215 let formats = new Uint8Array([0x01]);
3216 let expectedData = new Uint8Array([0x02, 0x00, 0x00, 0x01]);
3217
3218 RFB.messages.extendedClipboardRequest(sock, formats);
3219 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
3220 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData);
3221 });
3222
3223 it('should call clientCutText with correct Notify data', function () {
3224 let formats = new Uint8Array([0x01]);
3225 let expectedData = new Uint8Array([0x08, 0x00, 0x00, 0x01]);
3226
3227 RFB.messages.extendedClipboardNotify(sock, formats);
3228 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
3229 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData);
3230 });
3231
3232 it('should call clientCutText with correct Provide data', function () {
3233 let testText = "Test string";
3234 let expectedText = encodeUTF8(testText + "\0");
3235
3236 let deflatedData = deflateWithSize(expectedText);
3237
3238 // Build Expected with flags and deflated data
3239 let expectedData = new Uint8Array(4 + deflatedData.length);
3240 expectedData[0] = 0x10; // The client capabilities
3241 expectedData[1] = 0x00; // Reserved flags
3242 expectedData[2] = 0x00; // Reserved flags
3243 expectedData[3] = 0x01; // The formats client supports
3244 expectedData.set(deflatedData, 4);
3245
3246 RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);
3247 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
3248 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData, true);
3249
3250 });
3251
3252 describe('End of line characters', function () {
3253 it('Carriage return', function () {
3254
3255 let testText = "Hello\rworld\r\r!";
3256 let expectedText = encodeUTF8("Hello\r\nworld\r\n\r\n!\0");
3257
3258 let deflatedData = deflateWithSize(expectedText);
3259
3260 // Build Expected with flags and deflated data
3261 let expectedData = new Uint8Array(4 + deflatedData.length);
3262 expectedData[0] = 0x10; // The client capabilities
3263 expectedData[1] = 0x00; // Reserved flags
3264 expectedData[2] = 0x00; // Reserved flags
3265 expectedData[3] = 0x01; // The formats client supports
3266 expectedData.set(deflatedData, 4);
3267
3268 RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);
3269 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
3270 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData, true);
3271 });
3272
3273 it('Carriage return Line feed', function () {
3274
3275 let testText = "Hello\r\n\r\nworld\r\n!";
3276 let expectedText = encodeUTF8(testText + "\0");
3277
3278 let deflatedData = deflateWithSize(expectedText);
3279
3280 // Build Expected with flags and deflated data
3281 let expectedData = new Uint8Array(4 + deflatedData.length);
3282 expectedData[0] = 0x10; // The client capabilities
3283 expectedData[1] = 0x00; // Reserved flags
3284 expectedData[2] = 0x00; // Reserved flags
3285 expectedData[3] = 0x01; // The formats client supports
3286 expectedData.set(deflatedData, 4);
3287
3288 RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);
3289 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
3290 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData, true);
3291 });
3292
3293 it('Line feed', function () {
3294 let testText = "Hello\n\n\nworld\n!";
3295 let expectedText = encodeUTF8("Hello\r\n\r\n\r\nworld\r\n!\0");
3296
3297 let deflatedData = deflateWithSize(expectedText);
3298
3299 // Build Expected with flags and deflated data
3300 let expectedData = new Uint8Array(4 + deflatedData.length);
3301 expectedData[0] = 0x10; // The client capabilities
3302 expectedData[1] = 0x00; // Reserved flags
3303 expectedData[2] = 0x00; // Reserved flags
3304 expectedData[3] = 0x01; // The formats client supports
3305 expectedData.set(deflatedData, 4);
3306
3307 RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);
3308 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
3309 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData, true);
3310 });
3311
3312 it('Carriage return and Line feed mixed', function () {
3313 let testText = "\rHello\r\n\rworld\n\n!";
3314 let expectedText = encodeUTF8("\r\nHello\r\n\r\nworld\r\n\r\n!\0");
3315
3316 let deflatedData = deflateWithSize(expectedText);
3317
3318 // Build Expected with flags and deflated data
3319 let expectedData = new Uint8Array(4 + deflatedData.length);
3320 expectedData[0] = 0x10; // The client capabilities
3321 expectedData[1] = 0x00; // Reserved flags
3322 expectedData[2] = 0x00; // Reserved flags
3323 expectedData[3] = 0x01; // The formats client supports
3324 expectedData.set(deflatedData, 4);
3325
3326 RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);
3327 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
3328 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData, true);
3329 });
3330 });
3331 });
3332 });