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