]> git.proxmox.com Git - mirror_novnc.git/blob - tests/test.rfb.js
Merge branch 'clipboard_unicode' of https://github.com/CendioNiko/noVNC
[mirror_novnc.git] / tests / test.rfb.js
1 const expect = chai.expect;
2
3 import RFB from '../core/rfb.js';
4 import Websock from '../core/websock.js';
5 import ZStream from "../vendor/pako/lib/zlib/zstream.js";
6 import { deflateInit, deflate } from "../vendor/pako/lib/zlib/deflate.js";
7 import { encodings } from '../core/encodings.js';
8 import { toUnsigned32bit } from '../core/util/int.js';
9 import { encodeUTF8 } from '../core/util/strings.js';
10
11 import FakeWebSocket from './fake.websocket.js';
12
13 /* UIEvent constructor polyfill for IE */
14 (() => {
15 if (typeof window.UIEvent === "function") return;
16
17 function UIEvent( event, params ) {
18 params = params || { bubbles: false, cancelable: false, view: window, detail: undefined };
19 const evt = document.createEvent( 'UIEvent' );
20 evt.initUIEvent( event, params.bubbles, params.cancelable, params.view, params.detail );
21 return evt;
22 }
23
24 UIEvent.prototype = window.UIEvent.prototype;
25
26 window.UIEvent = UIEvent;
27 })();
28
29 function push8(arr, num) {
30 "use strict";
31 arr.push(num & 0xFF);
32 }
33
34 function push16(arr, num) {
35 "use strict";
36 arr.push((num >> 8) & 0xFF,
37 num & 0xFF);
38 }
39
40 function push32(arr, num) {
41 "use strict";
42 arr.push((num >> 24) & 0xFF,
43 (num >> 16) & 0xFF,
44 (num >> 8) & 0xFF,
45 num & 0xFF);
46 }
47
48 function pushString(arr, string) {
49 let utf8 = unescape(encodeURIComponent(string));
50 for (let i = 0; i < utf8.length; i++) {
51 arr.push(utf8.charCodeAt(i));
52 }
53 }
54
55 function deflateWithSize(data) {
56 // Adds the size of the string in front before deflating
57
58 let unCompData = [];
59 unCompData.push((data.length >> 24) & 0xFF,
60 (data.length >> 16) & 0xFF,
61 (data.length >> 8) & 0xFF,
62 (data.length & 0xFF));
63
64 for (let i = 0; i < data.length; i++) {
65 unCompData.push(data.charCodeAt(i));
66 }
67
68 let strm = new ZStream();
69 let chunkSize = 1024 * 10 * 10;
70 strm.output = new Uint8Array(chunkSize);
71 deflateInit(strm, 5);
72
73 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 make_rfb(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._rfb_connection_state = '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._rfb_connection_state = '';
158 this.clock.tick();
159 expect(client._rfb_connection_state).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 = make_rfb();
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._rfb_connection_state).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 = make_rfb();
211 client._rfb_connection_state = 'connecting';
212 });
213
214 it('should set the rfb credentials properly"', function () {
215 client.sendCredentials({ password: 'pass' });
216 expect(client._rfb_credentials).to.deep.equal({ password: 'pass' });
217 });
218
219 it('should call init_msg "soon"', function () {
220 client._init_msg = sinon.spy();
221 client.sendCredentials({ password: 'pass' });
222 this.clock.tick(5);
223 expect(client._init_msg).to.have.been.calledOnce;
224 });
225 });
226 });
227
228 describe('Public API Basic Behavior', function () {
229 let client;
230 beforeEach(function () {
231 client = make_rfb();
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._rfb_connection_state = "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._rfb_connection_state = "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 long_text = "";
365 for (let i = 0; i < client._sock._sQbufferSize + 100; i++) {
366 long_text += 'a';
367 }
368 client.clipboardPasteFrom(long_text);
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._rfb_connection_state = "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._rfb_xvp_ver = 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 = make_rfb();
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 = make_rfb();
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 = make_rfb();
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 = make_rfb();
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._rfb_connection_state = '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 rfb_connection_state', function () {
814 client._rfb_connection_state = 'connecting';
815 client._updateConnectionState('connected');
816 expect(client._rfb_connection_state).to.equal('connected');
817 });
818
819 it('should not change the state when we are disconnected', function () {
820 client.disconnect();
821 expect(client._rfb_connection_state).to.equal('disconnected');
822 client._updateConnectionState('connecting');
823 expect(client._rfb_connection_state).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._rfb_connection_state).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._rfb_connection_state).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._rfb_connection_state).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 = make_rfb();
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._rfb_connection_state).to.equal('disconnected');
871 });
872
873 it('should set clean_disconnect variable', function () {
874 client._rfb_clean_disconnect = true;
875 client._rfb_connection_state = 'connected';
876 client._fail();
877 expect(client._rfb_clean_disconnect).to.be.false;
878 });
879
880 it('should result in disconnect event with clean set to false', function () {
881 client._rfb_connection_state = '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 = make_rfb();
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._rfb_connection_state = '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 = make_rfb();
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._rfb_disconnect_reason).to.not.equal("");
940 expect(client._rfb_connection_state).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._rfb_connection_state).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._rfb_connection_state = '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._rfb_connection_state = 'disconnecting';
985 client._rfb_disconnect_reason = "";
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 = make_rfb();
997 client._rfb_connection_state = 'connecting';
998 });
999
1000 describe('ProtocolVersion', function () {
1001 function send_ver(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 send_ver('003.003', client);
1014 expect(client._rfb_version).to.equal(3.3);
1015 });
1016
1017 it('should interpret version 003.006 as version 3.3', function () {
1018 send_ver('003.006', client);
1019 expect(client._rfb_version).to.equal(3.3);
1020 });
1021
1022 it('should interpret version 003.889 as version 3.3', function () {
1023 send_ver('003.889', client);
1024 expect(client._rfb_version).to.equal(3.3);
1025 });
1026
1027 it('should interpret version 003.007 as version 3.7', function () {
1028 send_ver('003.007', client);
1029 expect(client._rfb_version).to.equal(3.7);
1030 });
1031
1032 it('should interpret version 003.008 as version 3.8', function () {
1033 send_ver('003.008', client);
1034 expect(client._rfb_version).to.equal(3.8);
1035 });
1036
1037 it('should interpret version 004.000 as version 3.8', function () {
1038 send_ver('004.000', client);
1039 expect(client._rfb_version).to.equal(3.8);
1040 });
1041
1042 it('should interpret version 004.001 as version 3.8', function () {
1043 send_ver('004.001', client);
1044 expect(client._rfb_version).to.equal(3.8);
1045 });
1046
1047 it('should interpret version 005.000 as version 3.8', function () {
1048 send_ver('005.000', client);
1049 expect(client._rfb_version).to.equal(3.8);
1050 });
1051
1052 it('should fail on an invalid version', function () {
1053 sinon.spy(client, "_fail");
1054 send_ver('002.000', client);
1055 expect(client._fail).to.have.been.calledOnce;
1056 });
1057 });
1058
1059 it('should send back the interpreted version', function () {
1060 send_ver('004.000', client);
1061
1062 const expected_str = 'RFB 003.008\n';
1063 const expected = [];
1064 for (let i = 0; i < expected_str.length; i++) {
1065 expected[i] = expected_str.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 send_ver('003.008', client);
1073 expect(client._rfb_init_state).to.equal('Security');
1074 });
1075
1076 describe('Repeater', function () {
1077 beforeEach(function () {
1078 client = make_rfb('wss://host:8675', { repeaterID: "12345" });
1079 client._rfb_connection_state = 'connecting';
1080 });
1081
1082 it('should interpret version 000.000 as a repeater', function () {
1083 send_ver('000.000', client);
1084 expect(client._rfb_version).to.equal(0);
1085
1086 const sent_data = client._sock._websocket._get_sent_data();
1087 expect(new Uint8Array(sent_data.buffer, 0, 9)).to.array.equal(new Uint8Array([73, 68, 58, 49, 50, 51, 52, 53, 0]));
1088 expect(sent_data).to.have.length(250);
1089 });
1090
1091 it('should handle two step repeater negotiation', function () {
1092 send_ver('000.000', client);
1093 send_ver('003.008', client);
1094 expect(client._rfb_version).to.equal(3.8);
1095 });
1096 });
1097 });
1098
1099 describe('Security', function () {
1100 beforeEach(function () {
1101 client._rfb_init_state = 'Security';
1102 });
1103
1104 it('should simply receive the auth scheme when for versions < 3.7', function () {
1105 client._rfb_version = 3.6;
1106 const auth_scheme_raw = [1, 2, 3, 4];
1107 const auth_scheme = (auth_scheme_raw[0] << 24) + (auth_scheme_raw[1] << 16) +
1108 (auth_scheme_raw[2] << 8) + auth_scheme_raw[3];
1109 client._sock._websocket._receive_data(new Uint8Array(auth_scheme_raw));
1110 expect(client._rfb_auth_scheme).to.equal(auth_scheme);
1111 });
1112
1113 it('should prefer no authentication is possible', function () {
1114 client._rfb_version = 3.7;
1115 const auth_schemes = [2, 1, 3];
1116 client._sock._websocket._receive_data(new Uint8Array(auth_schemes));
1117 expect(client._rfb_auth_scheme).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._rfb_version = 3.7;
1123 const auth_schemes = [2, 22, 16];
1124 client._sock._websocket._receive_data(new Uint8Array(auth_schemes));
1125 expect(client._rfb_auth_scheme).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._rfb_version = 3.7;
1132 const auth_schemes = [1, 32];
1133 client._sock._websocket._receive_data(new Uint8Array(auth_schemes));
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._rfb_version = 3.7;
1139 const failure_data = [0, 0, 0, 0, 6, 119, 104, 111, 111, 112, 115];
1140 sinon.spy(client, '_fail');
1141 client._sock._websocket._receive_data(new Uint8Array(failure_data));
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._rfb_version = 3.7;
1150 const auth_schemes = [1, 1];
1151 client._negotiate_authentication = sinon.spy();
1152 client._sock._websocket._receive_data(new Uint8Array(auth_schemes));
1153 expect(client._rfb_init_state).to.equal('Authentication');
1154 expect(client._negotiate_authentication).to.have.been.calledOnce;
1155 });
1156 });
1157
1158 describe('Authentication', function () {
1159 beforeEach(function () {
1160 client._rfb_init_state = 'Security';
1161 });
1162
1163 function send_security(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._rfb_version = 3.6;
1169 const err_msg = "Whoopsies";
1170 const data = [0, 0, 0, 0];
1171 const err_len = err_msg.length;
1172 push32(data, err_len);
1173 for (let i = 0; i < err_len; i++) {
1174 data.push(err_msg.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._rfb_version = 3.8;
1185 send_security(1, client);
1186 expect(client._rfb_init_state).to.equal('SecurityResult');
1187 });
1188
1189 it('should transition straight to ServerInitialisation on "no auth" for versions < 3.8', function () {
1190 client._rfb_version = 3.7;
1191 send_security(1, client);
1192 expect(client._rfb_init_state).to.equal('ServerInitialisation');
1193 });
1194
1195 it('should fail on an unknown auth scheme', function () {
1196 sinon.spy(client, "_fail");
1197 client._rfb_version = 3.8;
1198 send_security(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._rfb_init_state = 'Security';
1205 client._rfb_version = 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 send_security(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._rfb_credentials).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._rfb_credentials = { password: 'passwd' };
1224 send_security(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 des_pass = RFB.genDES('passwd', challenge);
1232 expect(client._sock).to.have.sent(new Uint8Array(des_pass));
1233 });
1234
1235 it('should transition to SecurityResult immediately after sending the password', function () {
1236 client._rfb_credentials = { password: 'passwd' };
1237 send_security(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._rfb_init_state).to.equal('SecurityResult');
1244 });
1245 });
1246
1247 describe('XVP Authentication (type 22) Handler', function () {
1248 beforeEach(function () {
1249 client._rfb_init_state = 'Security';
1250 client._rfb_version = 3.8;
1251 });
1252
1253 it('should fall through to standard VNC authentication upon completion', function () {
1254 client._rfb_credentials = { username: 'user',
1255 target: 'target',
1256 password: 'password' };
1257 client._negotiate_std_vnc_auth = sinon.spy();
1258 send_security(22, client);
1259 expect(client._negotiate_std_vnc_auth).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._rfb_credentials = {};
1266 send_security(22, client);
1267
1268 expect(client._rfb_credentials).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._rfb_credentials = { username: 'user',
1277 target: 'target' };
1278 send_security(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._rfb_credentials = { username: 'user',
1286 target: 'target',
1287 password: 'password' };
1288 client._negotiate_std_vnc_auth = sinon.spy();
1289
1290 send_security(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._rfb_init_state = 'Security';
1302 client._rfb_version = 3.8;
1303 send_security(16, client);
1304 client._sock._websocket._get_sent_data(); // skip the security reply
1305 });
1306
1307 function send_num_str_pairs(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._rfb_tightvnc).to.be.true;
1327 });
1328
1329 it('should fail if no supported tunnels are listed', function () {
1330 sinon.spy(client, "_fail");
1331 send_num_str_pairs([[123, 'OTHR', 'SOMETHNG']], client);
1332 expect(client._fail).to.have.been.calledOnce;
1333 });
1334
1335 it('should choose the notunnel tunnel type', function () {
1336 send_num_str_pairs([[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 send_num_str_pairs([[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 send_num_str_pairs([[0, 'TGHT', 'NOTUNNEL']], client);
1347 client._sock._websocket._get_sent_data(); // skip the tunnel choice here
1348 send_num_str_pairs([[1, 'STDV', 'NOAUTH__']], client);
1349 expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 1]));
1350 expect(client._rfb_init_state).to.equal('SecurityResult');
1351 });
1352
1353 /*it('should attempt to use VNC auth over no auth when possible', function () {
1354 client._rfb_tightvnc = true;
1355 client._negotiate_std_vnc_auth = sinon.spy();
1356 send_num_str_pairs([[1, 'STDV', 'NOAUTH__'], [2, 'STDV', 'VNCAUTH_']], client);
1357 expect(client._sock).to.have.sent([0, 0, 0, 1]);
1358 expect(client._negotiate_std_vnc_auth).to.have.been.calledOnce;
1359 expect(client._rfb_auth_scheme).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._rfb_tightvnc = true;
1364 send_num_str_pairs([[1, 'STDV', 'NOAUTH__']], client);
1365 expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 1]));
1366 expect(client._rfb_init_state).to.equal('SecurityResult');
1367 });
1368
1369 it('should accept VNC authentication and transition to that', function () {
1370 client._rfb_tightvnc = true;
1371 client._negotiate_std_vnc_auth = sinon.spy();
1372 send_num_str_pairs([[2, 'STDV', 'VNCAUTH__']], client);
1373 expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 2]));
1374 expect(client._negotiate_std_vnc_auth).to.have.been.calledOnce;
1375 expect(client._rfb_auth_scheme).to.equal(2);
1376 });
1377
1378 it('should fail if there are no supported auth types', function () {
1379 sinon.spy(client, "_fail");
1380 client._rfb_tightvnc = true;
1381 send_num_str_pairs([[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._rfb_init_state = '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._rfb_init_state).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._rfb_version = 3.8;
1399 sinon.spy(client, '_fail');
1400 const failure_data = [0, 0, 0, 1, 0, 0, 0, 6, 119, 104, 111, 111, 112, 115];
1401 client._sock._websocket._receive_data(new Uint8Array(failure_data));
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._rfb_version = 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._rfb_version = 3.8;
1424 const spy = sinon.spy();
1425 client.addEventListener("securityfailure", spy);
1426 const failure_data = [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(failure_data));
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._rfb_version = 3.9;
1435 const spy = sinon.spy();
1436 client.addEventListener("securityfailure", spy);
1437 const failure_data = [0, 0, 0, 1, 0, 0, 0, 0];
1438 client._sock._websocket._receive_data(new Uint8Array(failure_data));
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._rfb_version = 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 = make_rfb();
1456 client._rfb_connection_state = 'connecting';
1457 client._rfb_init_state = 'SecurityResult';
1458 client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0]));
1459 expect(client._rfb_init_state).to.equal('ServerInitialisation');
1460 });
1461
1462 it('should send 1 if we are in shared mode', function () {
1463 const client = make_rfb('wss://host:8675', { shared: true });
1464 client._rfb_connection_state = 'connecting';
1465 client._rfb_init_state = '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 = make_rfb('wss://host:8675', { shared: false });
1472 client._rfb_connection_state = 'connecting';
1473 client._rfb_init_state = '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._rfb_init_state = 'ServerInitialisation';
1482 });
1483
1484 function send_server_init(opts, client) {
1485 const full_opts = { width: 10, height: 12, bpp: 24, depth: 24, big_endian: 0,
1486 true_color: 1, red_max: 255, green_max: 255, blue_max: 255,
1487 red_shift: 16, green_shift: 8, blue_shift: 0, name: 'a name' };
1488 for (let opt in opts) {
1489 full_opts[opt] = opts[opt];
1490 }
1491 const data = [];
1492
1493 push16(data, full_opts.width);
1494 push16(data, full_opts.height);
1495
1496 data.push(full_opts.bpp);
1497 data.push(full_opts.depth);
1498 data.push(full_opts.big_endian);
1499 data.push(full_opts.true_color);
1500
1501 push16(data, full_opts.red_max);
1502 push16(data, full_opts.green_max);
1503 push16(data, full_opts.blue_max);
1504 push8(data, full_opts.red_shift);
1505 push8(data, full_opts.green_shift);
1506 push8(data, full_opts.blue_shift);
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 name_data = [];
1516 let name_len = [];
1517 pushString(name_data, full_opts.name);
1518 push32(name_len, name_data.length);
1519
1520 client._sock._websocket._receive_data(new Uint8Array(name_len));
1521 client._sock._websocket._receive_data(new Uint8Array(name_data));
1522 }
1523
1524 it('should set the framebuffer width and height', function () {
1525 send_server_init({ width: 32, height: 84 }, client);
1526 expect(client._fb_width).to.equal(32);
1527 expect(client._fb_height).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 send_server_init({ name: 'som€ nam€' }, client);
1536
1537 expect(client._fb_name).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._rfb_tightvnc = true;
1546 send_server_init({}, client);
1547
1548 const tight_data = [];
1549 push16(tight_data, 1);
1550 push16(tight_data, 2);
1551 push16(tight_data, 3);
1552 push16(tight_data, 0);
1553 for (let i = 0; i < 16 + 32 + 48; i++) {
1554 tight_data.push(i);
1555 }
1556 client._sock._websocket._receive_data(new Uint8Array(tight_data));
1557
1558 expect(client._rfb_connection_state).to.equal('connected');
1559 });
1560
1561 it('should resize the display', function () {
1562 sinon.spy(client._display, 'resize');
1563 send_server_init({ 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 send_server_init({}, 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 send_server_init({ 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 send_server_init({ 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 send_server_init({}, client);
1621 expect(client._rfb_connection_state).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 = make_rfb();
1631 client._fb_name = 'some device';
1632 client._fb_width = 640;
1633 client._fb_height = 20;
1634 });
1635
1636 describe('Framebuffer Update Handling', function () {
1637 const target_data_arr = [
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 target_data;
1644
1645 const target_data_check_arr = [
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 target_data_check;
1652
1653 before(function () {
1654 // NB(directxman12): PhantomJS 1.x doesn't implement Uint8ClampedArray
1655 target_data = new Uint8Array(target_data_arr);
1656 target_data_check = new Uint8Array(target_data_check_arr);
1657 });
1658
1659 function send_fbu_msg(rect_info, rect_data, client, rect_cnt) {
1660 let data = [];
1661
1662 if (!rect_cnt || rect_cnt > -1) {
1663 // header
1664 data.push(0); // msg type
1665 data.push(0); // padding
1666 push16(data, rect_cnt || rect_data.length);
1667 }
1668
1669 for (let i = 0; i < rect_data.length; i++) {
1670 if (rect_info[i]) {
1671 push16(data, rect_info[i].x);
1672 push16(data, rect_info[i].y);
1673 push16(data, rect_info[i].width);
1674 push16(data, rect_info[i].height);
1675 push32(data, rect_info[i].encoding);
1676 }
1677 data = data.concat(rect_data[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 expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: () => {}};
1685 RFB.messages.fbUpdateRequest(expected_msg, 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(expected_msg._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 expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: () => {}};
1700 RFB.messages.fbUpdateRequest(expected_msg, 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(expected_msg._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 rect_info = { x: 8, y: 11, width: 27, height: 32, encoding: 234 };
1723 send_fbu_msg([rect_info], [[]], 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._fb_width = 4;
1730 client._fb_height = 4;
1731 client._display.resize(4, 4);
1732 client._display.blitRgbxImage(0, 0, 4, 2, new Uint8Array(target_data_check_arr.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 send_fbu_msg([info[0]], [rects[0]], client, 2);
1739 send_fbu_msg([info[1]], [rects[1]], client, -1);
1740 expect(client._display).to.have.displayed(target_data_check);
1741 });
1742
1743 describe('Message Encoding Handlers', function () {
1744 beforeEach(function () {
1745 // a really small frame
1746 client._fb_width = 4;
1747 client._fb_height = 4;
1748 client._fb_depth = 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 send_fbu_msg(info, rects, client);
1764 expect(client._display).to.have.displayed(target_data);
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._fb_depth = 8;
1778 send_fbu_msg(info, rects, client);
1779 expect(client._display).to.have.displayed(target_data_check);
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(target_data_check_arr.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 send_fbu_msg(info, rects, client);
1791 expect(client._display).to.have.displayed(target_data_check);
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
1819 send_fbu_msg(info, [rect], client);
1820 expect(client._display).to.have.displayed(target_data_check);
1821 });
1822
1823 describe('the HEXTILE encoding handler', function () {
1824 it('should handle a tile with fg, bg specified, normal subrects', function () {
1825 const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
1826 const rect = [];
1827 rect.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects
1828 push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1829 rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
1830 rect.push(0x00);
1831 rect.push(0x00);
1832 rect.push(0xff);
1833 rect.push(2); // 2 subrects
1834 rect.push(0); // x: 0, y: 0
1835 rect.push(1 | (1 << 4)); // width: 2, height: 2
1836 rect.push(2 | (2 << 4)); // x: 2, y: 2
1837 rect.push(1 | (1 << 4)); // width: 2, height: 2
1838 send_fbu_msg(info, [rect], client);
1839 expect(client._display).to.have.displayed(target_data_check);
1840 });
1841
1842 it('should handle a raw tile', function () {
1843 const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
1844 const rect = [];
1845 rect.push(0x01); // raw
1846 for (let i = 0; i < target_data.length; i += 4) {
1847 rect.push(target_data[i + 2]);
1848 rect.push(target_data[i + 1]);
1849 rect.push(target_data[i]);
1850 rect.push(target_data[i + 3]);
1851 }
1852 send_fbu_msg(info, [rect], client);
1853 expect(client._display).to.have.displayed(target_data);
1854 });
1855
1856 it('should handle a tile with only bg specified (solid bg)', function () {
1857 const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
1858 const rect = [];
1859 rect.push(0x02);
1860 push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1861 send_fbu_msg(info, [rect], client);
1862
1863 const expected = [];
1864 for (let i = 0; i < 16; i++) { push32(expected, 0xff00ff); }
1865 expect(client._display).to.have.displayed(new Uint8Array(expected));
1866 });
1867
1868 it('should handle a tile with only bg specified and an empty frame afterwards', function () {
1869 // set the width so we can have two tiles
1870 client._fb_width = 8;
1871 client._display.resize(8, 4);
1872
1873 const info = [{ x: 0, y: 0, width: 32, height: 4, encoding: 0x05 }];
1874
1875 const rect = [];
1876
1877 // send a bg frame
1878 rect.push(0x02);
1879 push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1880
1881 // send an empty frame
1882 rect.push(0x00);
1883
1884 send_fbu_msg(info, [rect], client);
1885
1886 const expected = [];
1887 for (let i = 0; i < 16; i++) { push32(expected, 0xff00ff); } // rect 1: solid
1888 for (let i = 0; i < 16; i++) { push32(expected, 0xff00ff); } // rect 2: same bkground color
1889 expect(client._display).to.have.displayed(new Uint8Array(expected));
1890 });
1891
1892 it('should handle a tile with bg and coloured subrects', function () {
1893 const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
1894 const rect = [];
1895 rect.push(0x02 | 0x08 | 0x10); // bg spec, anysubrects, colouredsubrects
1896 push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1897 rect.push(2); // 2 subrects
1898 rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
1899 rect.push(0x00);
1900 rect.push(0x00);
1901 rect.push(0xff);
1902 rect.push(0); // x: 0, y: 0
1903 rect.push(1 | (1 << 4)); // width: 2, height: 2
1904 rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
1905 rect.push(0x00);
1906 rect.push(0x00);
1907 rect.push(0xff);
1908 rect.push(2 | (2 << 4)); // x: 2, y: 2
1909 rect.push(1 | (1 << 4)); // width: 2, height: 2
1910 send_fbu_msg(info, [rect], client);
1911 expect(client._display).to.have.displayed(target_data_check);
1912 });
1913
1914 it('should carry over fg and bg colors from the previous tile if not specified', function () {
1915 client._fb_width = 4;
1916 client._fb_height = 17;
1917 client._display.resize(4, 17);
1918
1919 const info = [{ x: 0, y: 0, width: 4, height: 17, encoding: 0x05}];
1920 const rect = [];
1921 rect.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects
1922 push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1923 rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
1924 rect.push(0x00);
1925 rect.push(0x00);
1926 rect.push(0xff);
1927 rect.push(8); // 8 subrects
1928 for (let i = 0; i < 4; i++) {
1929 rect.push((0 << 4) | (i * 4)); // x: 0, y: i*4
1930 rect.push(1 | (1 << 4)); // width: 2, height: 2
1931 rect.push((2 << 4) | (i * 4 + 2)); // x: 2, y: i * 4 + 2
1932 rect.push(1 | (1 << 4)); // width: 2, height: 2
1933 }
1934 rect.push(0x08); // anysubrects
1935 rect.push(1); // 1 subrect
1936 rect.push(0); // x: 0, y: 0
1937 rect.push(1 | (1 << 4)); // width: 2, height: 2
1938 send_fbu_msg(info, [rect], client);
1939
1940 let expected = [];
1941 for (let i = 0; i < 4; i++) { expected = expected.concat(target_data_check_arr); }
1942 expected = expected.concat(target_data_check_arr.slice(0, 16));
1943 expect(client._display).to.have.displayed(new Uint8Array(expected));
1944 });
1945
1946 it('should fail on an invalid subencoding', function () {
1947 sinon.spy(client, "_fail");
1948 const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
1949 const rects = [[45]]; // an invalid subencoding
1950 send_fbu_msg(info, rects, client);
1951 expect(client._fail).to.have.been.calledOnce;
1952 });
1953 });
1954
1955 it.skip('should handle the TIGHT encoding', function () {
1956 // TODO(directxman12): test this
1957 });
1958
1959 it.skip('should handle the TIGHT_PNG encoding', function () {
1960 // TODO(directxman12): test this
1961 });
1962
1963 it('should handle the DesktopSize pseduo-encoding', function () {
1964 sinon.spy(client._display, 'resize');
1965 send_fbu_msg([{ x: 0, y: 0, width: 20, height: 50, encoding: -223 }], [[]], client);
1966
1967 expect(client._fb_width).to.equal(20);
1968 expect(client._fb_height).to.equal(50);
1969
1970 expect(client._display.resize).to.have.been.calledOnce;
1971 expect(client._display.resize).to.have.been.calledWith(20, 50);
1972 });
1973
1974 describe('the ExtendedDesktopSize pseudo-encoding handler', function () {
1975 beforeEach(function () {
1976 // a really small frame
1977 client._fb_width = 4;
1978 client._fb_height = 4;
1979 client._display.resize(4, 4);
1980 sinon.spy(client._display, 'resize');
1981 });
1982
1983 function make_screen_data(nr_of_screens) {
1984 const data = [];
1985 push8(data, nr_of_screens); // number-of-screens
1986 push8(data, 0); // padding
1987 push16(data, 0); // padding
1988 for (let i=0; i<nr_of_screens; i += 1) {
1989 push32(data, 0); // id
1990 push16(data, 0); // x-position
1991 push16(data, 0); // y-position
1992 push16(data, 20); // width
1993 push16(data, 50); // height
1994 push32(data, 0); // flags
1995 }
1996 return data;
1997 }
1998
1999 it('should handle a resize requested by this client', function () {
2000 const reason_for_change = 1; // requested by this client
2001 const status_code = 0; // No error
2002
2003 send_fbu_msg([{ x: reason_for_change, y: status_code,
2004 width: 20, height: 50, encoding: -308 }],
2005 make_screen_data(1), client);
2006
2007 expect(client._fb_width).to.equal(20);
2008 expect(client._fb_height).to.equal(50);
2009
2010 expect(client._display.resize).to.have.been.calledOnce;
2011 expect(client._display.resize).to.have.been.calledWith(20, 50);
2012 });
2013
2014 it('should handle a resize requested by another client', function () {
2015 const reason_for_change = 2; // requested by another client
2016 const status_code = 0; // No error
2017
2018 send_fbu_msg([{ x: reason_for_change, y: status_code,
2019 width: 20, height: 50, encoding: -308 }],
2020 make_screen_data(1), client);
2021
2022 expect(client._fb_width).to.equal(20);
2023 expect(client._fb_height).to.equal(50);
2024
2025 expect(client._display.resize).to.have.been.calledOnce;
2026 expect(client._display.resize).to.have.been.calledWith(20, 50);
2027 });
2028
2029 it('should be able to recieve requests which contain data for multiple screens', function () {
2030 const reason_for_change = 2; // requested by another client
2031 const status_code = 0; // No error
2032
2033 send_fbu_msg([{ x: reason_for_change, y: status_code,
2034 width: 60, height: 50, encoding: -308 }],
2035 make_screen_data(3), client);
2036
2037 expect(client._fb_width).to.equal(60);
2038 expect(client._fb_height).to.equal(50);
2039
2040 expect(client._display.resize).to.have.been.calledOnce;
2041 expect(client._display.resize).to.have.been.calledWith(60, 50);
2042 });
2043
2044 it('should not handle a failed request', function () {
2045 const reason_for_change = 1; // requested by this client
2046 const status_code = 1; // Resize is administratively prohibited
2047
2048 send_fbu_msg([{ x: reason_for_change, y: status_code,
2049 width: 20, height: 50, encoding: -308 }],
2050 make_screen_data(1), client);
2051
2052 expect(client._fb_width).to.equal(4);
2053 expect(client._fb_height).to.equal(4);
2054
2055 expect(client._display.resize).to.not.have.been.called;
2056 });
2057 });
2058
2059 describe('the Cursor pseudo-encoding handler', function () {
2060 beforeEach(function () {
2061 sinon.spy(client._cursor, 'change');
2062 });
2063
2064 it('should handle a standard cursor', function () {
2065 const info = { x: 5, y: 7,
2066 width: 4, height: 4,
2067 encoding: -239};
2068 let rect = [];
2069 let expected = [];
2070
2071 for (let i = 0;i < info.width*info.height;i++) {
2072 push32(rect, 0x11223300);
2073 }
2074 push32(rect, 0xa0a0a0a0);
2075
2076 for (let i = 0;i < info.width*info.height/2;i++) {
2077 push32(expected, 0x332211ff);
2078 push32(expected, 0x33221100);
2079 }
2080 expected = new Uint8Array(expected);
2081
2082 send_fbu_msg([info], [rect], client);
2083
2084 expect(client._cursor.change).to.have.been.calledOnce;
2085 expect(client._cursor.change).to.have.been.calledWith(expected, 5, 7, 4, 4);
2086 });
2087
2088 it('should handle an empty cursor', function () {
2089 const info = { x: 0, y: 0,
2090 width: 0, height: 0,
2091 encoding: -239};
2092 const rect = [];
2093
2094 send_fbu_msg([info], [rect], client);
2095
2096 expect(client._cursor.change).to.have.been.calledOnce;
2097 expect(client._cursor.change).to.have.been.calledWith(new Uint8Array, 0, 0, 0, 0);
2098 });
2099
2100 it('should handle a transparent cursor', function () {
2101 const info = { x: 5, y: 7,
2102 width: 4, height: 4,
2103 encoding: -239};
2104 let rect = [];
2105 let expected = [];
2106
2107 for (let i = 0;i < info.width*info.height;i++) {
2108 push32(rect, 0x11223300);
2109 }
2110 push32(rect, 0x00000000);
2111
2112 for (let i = 0;i < info.width*info.height;i++) {
2113 push32(expected, 0x33221100);
2114 }
2115 expected = new Uint8Array(expected);
2116
2117 send_fbu_msg([info], [rect], client);
2118
2119 expect(client._cursor.change).to.have.been.calledOnce;
2120 expect(client._cursor.change).to.have.been.calledWith(expected, 5, 7, 4, 4);
2121 });
2122
2123 describe('dot for empty cursor', function () {
2124 beforeEach(function () {
2125 client.showDotCursor = true;
2126 // Was called when we enabled dot cursor
2127 client._cursor.change.resetHistory();
2128 });
2129
2130 it('should show a standard cursor', function () {
2131 const info = { x: 5, y: 7,
2132 width: 4, height: 4,
2133 encoding: -239};
2134 let rect = [];
2135 let expected = [];
2136
2137 for (let i = 0;i < info.width*info.height;i++) {
2138 push32(rect, 0x11223300);
2139 }
2140 push32(rect, 0xa0a0a0a0);
2141
2142 for (let i = 0;i < info.width*info.height/2;i++) {
2143 push32(expected, 0x332211ff);
2144 push32(expected, 0x33221100);
2145 }
2146 expected = new Uint8Array(expected);
2147
2148 send_fbu_msg([info], [rect], client);
2149
2150 expect(client._cursor.change).to.have.been.calledOnce;
2151 expect(client._cursor.change).to.have.been.calledWith(expected, 5, 7, 4, 4);
2152 });
2153
2154 it('should handle an empty cursor', function () {
2155 const info = { x: 0, y: 0,
2156 width: 0, height: 0,
2157 encoding: -239};
2158 const rect = [];
2159 const dot = RFB.cursors.dot;
2160
2161 send_fbu_msg([info], [rect], client);
2162
2163 expect(client._cursor.change).to.have.been.calledOnce;
2164 expect(client._cursor.change).to.have.been.calledWith(dot.rgbaPixels,
2165 dot.hotx,
2166 dot.hoty,
2167 dot.w,
2168 dot.h);
2169 });
2170
2171 it('should handle a transparent cursor', function () {
2172 const info = { x: 5, y: 7,
2173 width: 4, height: 4,
2174 encoding: -239};
2175 let rect = [];
2176 const dot = RFB.cursors.dot;
2177
2178 for (let i = 0;i < info.width*info.height;i++) {
2179 push32(rect, 0x11223300);
2180 }
2181 push32(rect, 0x00000000);
2182
2183 send_fbu_msg([info], [rect], client);
2184
2185 expect(client._cursor.change).to.have.been.calledOnce;
2186 expect(client._cursor.change).to.have.been.calledWith(dot.rgbaPixels,
2187 dot.hotx,
2188 dot.hoty,
2189 dot.w,
2190 dot.h);
2191 });
2192 });
2193 });
2194
2195 describe('the VMware Cursor pseudo-encoding handler', function () {
2196 beforeEach(function () {
2197 sinon.spy(client._cursor, 'change');
2198 });
2199 afterEach(function () {
2200 client._cursor.change.resetHistory();
2201 });
2202
2203 it('should handle the VMware cursor pseudo-encoding', function () {
2204 let data = [0x00, 0x00, 0xff, 0,
2205 0x00, 0xff, 0x00, 0,
2206 0x00, 0xff, 0x00, 0,
2207 0x00, 0x00, 0xff, 0];
2208 let rect = [];
2209 push8(rect, 0);
2210 push8(rect, 0);
2211
2212 //AND-mask
2213 for (let i = 0; i < data.length; i++) {
2214 push8(rect, data[i]);
2215 }
2216 //XOR-mask
2217 for (let i = 0; i < data.length; i++) {
2218 push8(rect, data[i]);
2219 }
2220
2221 send_fbu_msg([{ x: 0, y: 0, width: 2, height: 2,
2222 encoding: 0x574d5664}],
2223 [rect], client);
2224 expect(client._FBU.rects).to.equal(0);
2225 });
2226
2227 it('should handle insufficient cursor pixel data', function () {
2228
2229 // Specified 14x23 pixels for the cursor,
2230 // but only send 2x2 pixels worth of data
2231 let w = 14;
2232 let h = 23;
2233 let data = [0x00, 0x00, 0xff, 0,
2234 0x00, 0xff, 0x00, 0];
2235 let rect = [];
2236
2237 push8(rect, 0);
2238 push8(rect, 0);
2239
2240 //AND-mask
2241 for (let i = 0; i < data.length; i++) {
2242 push8(rect, data[i]);
2243 }
2244 //XOR-mask
2245 for (let i = 0; i < data.length; i++) {
2246 push8(rect, data[i]);
2247 }
2248
2249 send_fbu_msg([{ x: 0, y: 0, width: w, height: h,
2250 encoding: 0x574d5664}],
2251 [rect], client);
2252
2253 // expect one FBU to remain unhandled
2254 expect(client._FBU.rects).to.equal(1);
2255 });
2256
2257 it('should update the cursor when type is classic', function () {
2258 let and_mask =
2259 [0xff, 0xff, 0xff, 0xff, //Transparent
2260 0xff, 0xff, 0xff, 0xff, //Transparent
2261 0x00, 0x00, 0x00, 0x00, //Opaque
2262 0xff, 0xff, 0xff, 0xff]; //Inverted
2263
2264 let xor_mask =
2265 [0x00, 0x00, 0x00, 0x00, //Transparent
2266 0x00, 0x00, 0x00, 0x00, //Transparent
2267 0x11, 0x22, 0x33, 0x44, //Opaque
2268 0xff, 0xff, 0xff, 0x44]; //Inverted
2269
2270 let rect = [];
2271 push8(rect, 0); //cursor_type
2272 push8(rect, 0); //padding
2273 let hotx = 0;
2274 let hoty = 0;
2275 let w = 2;
2276 let h = 2;
2277
2278 //AND-mask
2279 for (let i = 0; i < and_mask.length; i++) {
2280 push8(rect, and_mask[i]);
2281 }
2282 //XOR-mask
2283 for (let i = 0; i < xor_mask.length; i++) {
2284 push8(rect, xor_mask[i]);
2285 }
2286
2287 let expected_rgba = [0x00, 0x00, 0x00, 0x00,
2288 0x00, 0x00, 0x00, 0x00,
2289 0x33, 0x22, 0x11, 0xff,
2290 0x00, 0x00, 0x00, 0xff];
2291
2292 send_fbu_msg([{ x: hotx, y: hoty,
2293 width: w, height: h,
2294 encoding: 0x574d5664}],
2295 [rect], client);
2296
2297 expect(client._cursor.change)
2298 .to.have.been.calledOnce;
2299 expect(client._cursor.change)
2300 .to.have.been.calledWith(expected_rgba,
2301 hotx, hoty,
2302 w, h);
2303 });
2304
2305 it('should update the cursor when type is alpha', function () {
2306 let data = [0xee, 0x55, 0xff, 0x00, // rgba
2307 0x00, 0xff, 0x00, 0xff,
2308 0x00, 0xff, 0x00, 0x22,
2309 0x00, 0xff, 0x00, 0x22,
2310 0x00, 0xff, 0x00, 0x22,
2311 0x00, 0x00, 0xff, 0xee];
2312 let rect = [];
2313 push8(rect, 1); //cursor_type
2314 push8(rect, 0); //padding
2315 let hotx = 0;
2316 let hoty = 0;
2317 let w = 3;
2318 let h = 2;
2319
2320 for (let i = 0; i < data.length; i++) {
2321 push8(rect, data[i]);
2322 }
2323
2324 let expected_rgba = [0xee, 0x55, 0xff, 0x00,
2325 0x00, 0xff, 0x00, 0xff,
2326 0x00, 0xff, 0x00, 0x22,
2327 0x00, 0xff, 0x00, 0x22,
2328 0x00, 0xff, 0x00, 0x22,
2329 0x00, 0x00, 0xff, 0xee];
2330
2331 send_fbu_msg([{ x: hotx, y: hoty,
2332 width: w, height: h,
2333 encoding: 0x574d5664}],
2334 [rect], client);
2335
2336 expect(client._cursor.change)
2337 .to.have.been.calledOnce;
2338 expect(client._cursor.change)
2339 .to.have.been.calledWith(expected_rgba,
2340 hotx, hoty,
2341 w, h);
2342 });
2343
2344 it('should not update cursor when incorrect cursor type given', function () {
2345 let rect = [];
2346 push8(rect, 3); // invalid cursor type
2347 push8(rect, 0); // padding
2348
2349 client._cursor.change.resetHistory();
2350 send_fbu_msg([{ x: 0, y: 0, width: 2, height: 2,
2351 encoding: 0x574d5664}],
2352 [rect], client);
2353
2354 expect(client._cursor.change)
2355 .to.not.have.been.called;
2356 });
2357 });
2358
2359 it('should handle the last_rect pseudo-encoding', function () {
2360 send_fbu_msg([{ x: 0, y: 0, width: 0, height: 0, encoding: -224}], [[]], client, 100);
2361 expect(client._FBU.rects).to.equal(0);
2362 });
2363
2364 it('should handle the DesktopName pseudo-encoding', function () {
2365 let data = [];
2366 push32(data, 13);
2367 pushString(data, "som€ nam€");
2368
2369 const spy = sinon.spy();
2370 client.addEventListener("desktopname", spy);
2371
2372 send_fbu_msg([{ x: 0, y: 0, width: 0, height: 0, encoding: -307 }], [data], client);
2373
2374 expect(client._fb_name).to.equal('som€ nam€');
2375 expect(spy).to.have.been.calledOnce;
2376 expect(spy.args[0][0].detail.name).to.equal('som€ nam€');
2377 });
2378 });
2379 });
2380
2381 describe('XVP Message Handling', function () {
2382 it('should set the XVP version and fire the callback with the version on XVP_INIT', function () {
2383 const spy = sinon.spy();
2384 client.addEventListener("capabilities", spy);
2385 client._sock._websocket._receive_data(new Uint8Array([250, 0, 10, 1]));
2386 expect(client._rfb_xvp_ver).to.equal(10);
2387 expect(spy).to.have.been.calledOnce;
2388 expect(spy.args[0][0].detail.capabilities.power).to.be.true;
2389 expect(client.capabilities.power).to.be.true;
2390 });
2391
2392 it('should fail on unknown XVP message types', function () {
2393 sinon.spy(client, "_fail");
2394 client._sock._websocket._receive_data(new Uint8Array([250, 0, 10, 237]));
2395 expect(client._fail).to.have.been.calledOnce;
2396 });
2397 });
2398
2399 describe('Normal Clipboard Handling Receive', function () {
2400 it('should fire the clipboard callback with the retrieved text on ServerCutText', function () {
2401 const expected_str = 'cheese!';
2402 const data = [3, 0, 0, 0];
2403 push32(data, expected_str.length);
2404 for (let i = 0; i < expected_str.length; i++) { data.push(expected_str.charCodeAt(i)); }
2405 const spy = sinon.spy();
2406 client.addEventListener("clipboard", spy);
2407
2408 client._sock._websocket._receive_data(new Uint8Array(data));
2409 expect(spy).to.have.been.calledOnce;
2410 expect(spy.args[0][0].detail.text).to.equal(expected_str);
2411 });
2412 });
2413
2414 describe('Extended clipboard Handling', function () {
2415
2416 describe('Extended clipboard initialization', function () {
2417 beforeEach(function () {
2418 sinon.spy(RFB.messages, 'extendedClipboardCaps');
2419 });
2420
2421 afterEach(function () {
2422 RFB.messages.extendedClipboardCaps.restore();
2423 });
2424
2425 it('should update capabilities when receiving a Caps message', function () {
2426 let data = [3, 0, 0, 0];
2427 const flags = [0x1F, 0x00, 0x00, 0x03];
2428 let fileSizes = [0x00, 0x00, 0x00, 0x1E,
2429 0x00, 0x00, 0x00, 0x3C];
2430
2431 push32(data, toUnsigned32bit(-12));
2432 data = data.concat(flags);
2433 data = data.concat(fileSizes);
2434 client._sock._websocket._receive_data(new Uint8Array(data));
2435
2436 // Check that we give an response caps when we receive one
2437 expect(RFB.messages.extendedClipboardCaps).to.have.been.calledOnce;
2438
2439 // FIXME: Can we avoid checking internal variables?
2440 expect(client._clipboardServerCapabilitiesFormats[0]).to.not.equal(true);
2441 expect(client._clipboardServerCapabilitiesFormats[1]).to.equal(true);
2442 expect(client._clipboardServerCapabilitiesFormats[2]).to.equal(true);
2443 expect(client._clipboardServerCapabilitiesActions[(1 << 24)]).to.equal(true);
2444 });
2445
2446
2447 });
2448
2449 describe('Extended Clipboard Handling Receive', function () {
2450
2451 beforeEach(function () {
2452 // Send our capabilities
2453 let data = [3, 0, 0, 0];
2454 const flags = [0x1F, 0x00, 0x00, 0x01];
2455 let fileSizes = [0x00, 0x00, 0x00, 0x1E];
2456
2457 push32(data, toUnsigned32bit(-8));
2458 data = data.concat(flags);
2459 data = data.concat(fileSizes);
2460 client._sock._websocket._receive_data(new Uint8Array(data));
2461 });
2462
2463 describe('Handle Provide', function () {
2464 it('should update clipboard with correct Unicode data from a Provide message', function () {
2465 let expectedData = "Aå漢字!";
2466 let data = [3, 0, 0, 0];
2467 const flags = [0x10, 0x00, 0x00, 0x01];
2468
2469 /* The size 10 (utf8 encoded string size) and the
2470 string "Aå漢字!" utf8 encoded and deflated. */
2471 let deflatedData = [120, 94, 99, 96, 96, 224, 114, 60,
2472 188, 244, 217, 158, 69, 79, 215,
2473 78, 87, 4, 0, 35, 207, 6, 66];
2474
2475 // How much data we are sending.
2476 push32(data, toUnsigned32bit(-(4 + deflatedData.length)));
2477
2478 data = data.concat(flags);
2479 data = data.concat(deflatedData);
2480
2481 const spy = sinon.spy();
2482 client.addEventListener("clipboard", spy);
2483
2484 client._sock._websocket._receive_data(new Uint8Array(data));
2485 expect(spy).to.have.been.calledOnce;
2486 expect(spy.args[0][0].detail.text).to.equal(expectedData);
2487 client.removeEventListener("clipboard", spy);
2488 });
2489
2490 it('should update clipboard with correct escape characters from a Provide message ', function () {
2491 let expectedData = "Oh\nmy!";
2492 let data = [3, 0, 0, 0];
2493 const flags = [0x10, 0x00, 0x00, 0x01];
2494
2495 let text = encodeUTF8("Oh\r\nmy!\0");
2496
2497 let deflatedText = deflateWithSize(text);
2498
2499 // How much data we are sending.
2500 push32(data, toUnsigned32bit(-(4 + deflatedText.length)));
2501
2502 data = data.concat(flags);
2503
2504 let sendData = new Uint8Array(data.length + deflatedText.length);
2505 sendData.set(data);
2506 sendData.set(deflatedText, data.length);
2507
2508 const spy = sinon.spy();
2509 client.addEventListener("clipboard", spy);
2510
2511 client._sock._websocket._receive_data(sendData);
2512 expect(spy).to.have.been.calledOnce;
2513 expect(spy.args[0][0].detail.text).to.equal(expectedData);
2514 client.removeEventListener("clipboard", spy);
2515 });
2516
2517 });
2518
2519 describe('Handle Notify', function () {
2520 beforeEach(function () {
2521 sinon.spy(RFB.messages, 'extendedClipboardRequest');
2522 });
2523
2524 afterEach(function () {
2525 RFB.messages.extendedClipboardRequest.restore();
2526 });
2527
2528 it('should make a request with supported formats when receiving a notify message', function () {
2529 let data = [3, 0, 0, 0];
2530 const flags = [0x08, 0x00, 0x00, 0x07];
2531 push32(data, toUnsigned32bit(-4));
2532 data = data.concat(flags);
2533 let expectedData = [0x01];
2534
2535 client._sock._websocket._receive_data(new Uint8Array(data));
2536
2537 expect(RFB.messages.extendedClipboardRequest).to.have.been.calledOnce;
2538 expect(RFB.messages.extendedClipboardRequest).to.have.been.calledWith(client._sock, expectedData);
2539 });
2540 });
2541
2542 describe('Handle Peek', function () {
2543 beforeEach(function () {
2544 sinon.spy(RFB.messages, 'extendedClipboardNotify');
2545 });
2546
2547 afterEach(function () {
2548 RFB.messages.extendedClipboardNotify.restore();
2549 });
2550
2551 it('should send an empty Notify when receiving a Peek and no excisting clipboard data', function () {
2552 let data = [3, 0, 0, 0];
2553 const flags = [0x04, 0x00, 0x00, 0x00];
2554 push32(data, toUnsigned32bit(-4));
2555 data = data.concat(flags);
2556 let expectedData = [];
2557
2558 client._sock._websocket._receive_data(new Uint8Array(data));
2559
2560 expect(RFB.messages.extendedClipboardNotify).to.have.been.calledOnce;
2561 expect(RFB.messages.extendedClipboardNotify).to.have.been.calledWith(client._sock, expectedData);
2562 });
2563
2564 it('should send a Notify message with supported formats when receiving a Peek', function () {
2565 let data = [3, 0, 0, 0];
2566 const flags = [0x04, 0x00, 0x00, 0x00];
2567 push32(data, toUnsigned32bit(-4));
2568 data = data.concat(flags);
2569 let expectedData = [0x01];
2570
2571 // Needed to have clipboard data to read.
2572 // This will trigger a call to Notify, reset history
2573 client.clipboardPasteFrom("HejHej");
2574 RFB.messages.extendedClipboardNotify.resetHistory();
2575
2576 client._sock._websocket._receive_data(new Uint8Array(data));
2577
2578 expect(RFB.messages.extendedClipboardNotify).to.have.been.calledOnce;
2579 expect(RFB.messages.extendedClipboardNotify).to.have.been.calledWith(client._sock, expectedData);
2580 });
2581 });
2582
2583 describe('Handle Request', function () {
2584 beforeEach(function () {
2585 sinon.spy(RFB.messages, 'extendedClipboardProvide');
2586 });
2587
2588 afterEach(function () {
2589 RFB.messages.extendedClipboardProvide.restore();
2590 });
2591
2592 it('should send a Provide message with supported formats when receiving a Request', function () {
2593 let data = [3, 0, 0, 0];
2594 const flags = [0x02, 0x00, 0x00, 0x01];
2595 push32(data, toUnsigned32bit(-4));
2596 data = data.concat(flags);
2597 let expectedData = [0x01];
2598
2599 client.clipboardPasteFrom("HejHej");
2600 expect(RFB.messages.extendedClipboardProvide).to.not.have.been.called;
2601
2602 client._sock._websocket._receive_data(new Uint8Array(data));
2603
2604 expect(RFB.messages.extendedClipboardProvide).to.have.been.calledOnce;
2605 expect(RFB.messages.extendedClipboardProvide).to.have.been.calledWith(client._sock, expectedData, ["HejHej"]);
2606 });
2607 });
2608 });
2609
2610 });
2611
2612 it('should fire the bell callback on Bell', function () {
2613 const spy = sinon.spy();
2614 client.addEventListener("bell", spy);
2615 client._sock._websocket._receive_data(new Uint8Array([2]));
2616 expect(spy).to.have.been.calledOnce;
2617 });
2618
2619 it('should respond correctly to ServerFence', function () {
2620 const expected_msg = {_sQ: new Uint8Array(16), _sQlen: 0, flush: () => {}};
2621 const incoming_msg = {_sQ: new Uint8Array(16), _sQlen: 0, flush: () => {}};
2622
2623 const payload = "foo\x00ab9";
2624
2625 // ClientFence and ServerFence are identical in structure
2626 RFB.messages.clientFence(expected_msg, (1<<0) | (1<<1), payload);
2627 RFB.messages.clientFence(incoming_msg, 0xffffffff, payload);
2628
2629 client._sock._websocket._receive_data(incoming_msg._sQ);
2630
2631 expect(client._sock).to.have.sent(expected_msg._sQ);
2632
2633 expected_msg._sQlen = 0;
2634 incoming_msg._sQlen = 0;
2635
2636 RFB.messages.clientFence(expected_msg, (1<<0), payload);
2637 RFB.messages.clientFence(incoming_msg, (1<<0) | (1<<31), payload);
2638
2639 client._sock._websocket._receive_data(incoming_msg._sQ);
2640
2641 expect(client._sock).to.have.sent(expected_msg._sQ);
2642 });
2643
2644 it('should enable continuous updates on first EndOfContinousUpdates', function () {
2645 const expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: () => {}};
2646
2647 RFB.messages.enableContinuousUpdates(expected_msg, true, 0, 0, 640, 20);
2648
2649 expect(client._enabledContinuousUpdates).to.be.false;
2650
2651 client._sock._websocket._receive_data(new Uint8Array([150]));
2652
2653 expect(client._enabledContinuousUpdates).to.be.true;
2654 expect(client._sock).to.have.sent(expected_msg._sQ);
2655 });
2656
2657 it('should disable continuous updates on subsequent EndOfContinousUpdates', function () {
2658 client._enabledContinuousUpdates = true;
2659 client._supportsContinuousUpdates = true;
2660
2661 client._sock._websocket._receive_data(new Uint8Array([150]));
2662
2663 expect(client._enabledContinuousUpdates).to.be.false;
2664 });
2665
2666 it('should update continuous updates on resize', function () {
2667 const expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: () => {}};
2668 RFB.messages.enableContinuousUpdates(expected_msg, true, 0, 0, 90, 700);
2669
2670 client._resize(450, 160);
2671
2672 expect(client._sock._websocket._get_sent_data()).to.have.length(0);
2673
2674 client._enabledContinuousUpdates = true;
2675
2676 client._resize(90, 700);
2677
2678 expect(client._sock).to.have.sent(expected_msg._sQ);
2679 });
2680
2681 it('should fail on an unknown message type', function () {
2682 sinon.spy(client, "_fail");
2683 client._sock._websocket._receive_data(new Uint8Array([87]));
2684 expect(client._fail).to.have.been.calledOnce;
2685 });
2686 });
2687
2688 describe('Asynchronous Events', function () {
2689 let client;
2690 beforeEach(function () {
2691 client = make_rfb();
2692 });
2693
2694 describe('Mouse event handlers', function () {
2695 it('should not send button messages in view-only mode', function () {
2696 client._viewOnly = true;
2697 sinon.spy(client._sock, 'flush');
2698 client._handleMouseButton(0, 0, 1, 0x001);
2699 expect(client._sock.flush).to.not.have.been.called;
2700 });
2701
2702 it('should not send movement messages in view-only mode', function () {
2703 client._viewOnly = true;
2704 sinon.spy(client._sock, 'flush');
2705 client._handleMouseMove(0, 0);
2706 expect(client._sock.flush).to.not.have.been.called;
2707 });
2708
2709 it('should send a pointer event on mouse button presses', function () {
2710 client._handleMouseButton(10, 12, 1, 0x001);
2711 const pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: () => {}};
2712 RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x001);
2713 expect(client._sock).to.have.sent(pointer_msg._sQ);
2714 });
2715
2716 it('should send a mask of 1 on mousedown', function () {
2717 client._handleMouseButton(10, 12, 1, 0x001);
2718 const pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: () => {}};
2719 RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x001);
2720 expect(client._sock).to.have.sent(pointer_msg._sQ);
2721 });
2722
2723 it('should send a mask of 0 on mouseup', function () {
2724 client._mouse_buttonMask = 0x001;
2725 client._handleMouseButton(10, 12, 0, 0x001);
2726 const pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: () => {}};
2727 RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x000);
2728 expect(client._sock).to.have.sent(pointer_msg._sQ);
2729 });
2730
2731 it('should send a pointer event on mouse movement', function () {
2732 client._handleMouseMove(10, 12);
2733 const pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: () => {}};
2734 RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x000);
2735 expect(client._sock).to.have.sent(pointer_msg._sQ);
2736 });
2737
2738 it('should set the button mask so that future mouse movements use it', function () {
2739 client._handleMouseButton(10, 12, 1, 0x010);
2740 client._handleMouseMove(13, 9);
2741 const pointer_msg = {_sQ: new Uint8Array(12), _sQlen: 0, flush: () => {}};
2742 RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x010);
2743 RFB.messages.pointerEvent(pointer_msg, 13, 9, 0x010);
2744 expect(client._sock).to.have.sent(pointer_msg._sQ);
2745 });
2746 });
2747
2748 describe('Keyboard Event Handlers', function () {
2749 it('should send a key message on a key press', function () {
2750 client._handleKeyEvent(0x41, 'KeyA', true);
2751 const key_msg = {_sQ: new Uint8Array(8), _sQlen: 0, flush: () => {}};
2752 RFB.messages.keyEvent(key_msg, 0x41, 1);
2753 expect(client._sock).to.have.sent(key_msg._sQ);
2754 });
2755
2756 it('should not send messages in view-only mode', function () {
2757 client._viewOnly = true;
2758 sinon.spy(client._sock, 'flush');
2759 client._handleKeyEvent('a', 'KeyA', true);
2760 expect(client._sock.flush).to.not.have.been.called;
2761 });
2762 });
2763
2764 describe('WebSocket event handlers', function () {
2765 // message events
2766 it('should do nothing if we receive an empty message and have nothing in the queue', function () {
2767 client._normal_msg = sinon.spy();
2768 client._sock._websocket._receive_data(new Uint8Array([]));
2769 expect(client._normal_msg).to.not.have.been.called;
2770 });
2771
2772 it('should handle a message in the connected state as a normal message', function () {
2773 client._normal_msg = sinon.spy();
2774 client._sock._websocket._receive_data(new Uint8Array([1, 2, 3]));
2775 expect(client._normal_msg).to.have.been.called;
2776 });
2777
2778 it('should handle a message in any non-disconnected/failed state like an init message', function () {
2779 client._rfb_connection_state = 'connecting';
2780 client._rfb_init_state = 'ProtocolVersion';
2781 client._init_msg = sinon.spy();
2782 client._sock._websocket._receive_data(new Uint8Array([1, 2, 3]));
2783 expect(client._init_msg).to.have.been.called;
2784 });
2785
2786 it('should process all normal messages directly', function () {
2787 const spy = sinon.spy();
2788 client.addEventListener("bell", spy);
2789 client._sock._websocket._receive_data(new Uint8Array([0x02, 0x02]));
2790 expect(spy).to.have.been.calledTwice;
2791 });
2792
2793 // open events
2794 it('should update the state to ProtocolVersion on open (if the state is "connecting")', function () {
2795 client = new RFB(document.createElement('div'), 'wss://host:8675');
2796 this.clock.tick();
2797 client._sock._websocket._open();
2798 expect(client._rfb_init_state).to.equal('ProtocolVersion');
2799 });
2800
2801 it('should fail if we are not currently ready to connect and we get an "open" event', function () {
2802 sinon.spy(client, "_fail");
2803 client._rfb_connection_state = 'connected';
2804 client._sock._websocket._open();
2805 expect(client._fail).to.have.been.calledOnce;
2806 });
2807
2808 // close events
2809 it('should transition to "disconnected" from "disconnecting" on a close event', function () {
2810 const real = client._sock._websocket.close;
2811 client._sock._websocket.close = () => {};
2812 client.disconnect();
2813 expect(client._rfb_connection_state).to.equal('disconnecting');
2814 client._sock._websocket.close = real;
2815 client._sock._websocket.close();
2816 expect(client._rfb_connection_state).to.equal('disconnected');
2817 });
2818
2819 it('should fail if we get a close event while connecting', function () {
2820 sinon.spy(client, "_fail");
2821 client._rfb_connection_state = 'connecting';
2822 client._sock._websocket.close();
2823 expect(client._fail).to.have.been.calledOnce;
2824 });
2825
2826 it('should unregister close event handler', function () {
2827 sinon.spy(client._sock, 'off');
2828 client.disconnect();
2829 client._sock._websocket.close();
2830 expect(client._sock.off).to.have.been.calledWith('close');
2831 });
2832
2833 // error events do nothing
2834 });
2835 });
2836 });
2837
2838 describe('RFB messages', function () {
2839 let sock;
2840
2841 before(function () {
2842 FakeWebSocket.replace();
2843 sock = new Websock();
2844 sock.open();
2845 });
2846
2847 after(function () {
2848 FakeWebSocket.restore();
2849 });
2850
2851 describe('Extended Clipboard Handling Send', function () {
2852 beforeEach(function () {
2853 sinon.spy(RFB.messages, 'clientCutText');
2854 });
2855
2856 afterEach(function () {
2857 RFB.messages.clientCutText.restore();
2858 });
2859
2860 it('should call clientCutText with correct Caps data', function () {
2861 let formats = {
2862 0: 2,
2863 2: 4121
2864 };
2865 let expectedData = new Uint8Array([0x1F, 0x00, 0x00, 0x05,
2866 0x00, 0x00, 0x00, 0x02,
2867 0x00, 0x00, 0x10, 0x19]);
2868 let actions = [
2869 1 << 24, // Caps
2870 1 << 25, // Request
2871 1 << 26, // Peek
2872 1 << 27, // Notify
2873 1 << 28 // Provide
2874 ];
2875
2876 RFB.messages.extendedClipboardCaps(sock, actions, formats);
2877 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
2878 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData);
2879 });
2880
2881 it('should call clientCutText with correct Request data', function () {
2882 let formats = new Uint8Array([0x01]);
2883 let expectedData = new Uint8Array([0x02, 0x00, 0x00, 0x01]);
2884
2885 RFB.messages.extendedClipboardRequest(sock, formats);
2886 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
2887 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData);
2888 });
2889
2890 it('should call clientCutText with correct Notify data', function () {
2891 let formats = new Uint8Array([0x01]);
2892 let expectedData = new Uint8Array([0x08, 0x00, 0x00, 0x01]);
2893
2894 RFB.messages.extendedClipboardNotify(sock, formats);
2895 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
2896 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData);
2897 });
2898
2899 it('should call clientCutText with correct Provide data', function () {
2900 let testText = "Test string";
2901 let expectedText = encodeUTF8(testText + "\0");
2902
2903 let deflatedData = deflateWithSize(expectedText);
2904
2905 // Build Expected with flags and deflated data
2906 let expectedData = new Uint8Array(4 + deflatedData.length);
2907 expectedData[0] = 0x10; // The client capabilities
2908 expectedData[1] = 0x00; // Reserved flags
2909 expectedData[2] = 0x00; // Reserved flags
2910 expectedData[3] = 0x01; // The formats client supports
2911 expectedData.set(deflatedData, 4);
2912
2913 RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);
2914 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
2915 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData, true);
2916
2917 });
2918
2919 describe('End of line characters', function () {
2920 it('Carriage return', function () {
2921
2922 let testText = "Hello\rworld\r\r!";
2923 let expectedText = encodeUTF8("Hello\r\nworld\r\n\r\n!\0");
2924
2925 let deflatedData = deflateWithSize(expectedText);
2926
2927 // Build Expected with flags and deflated data
2928 let expectedData = new Uint8Array(4 + deflatedData.length);
2929 expectedData[0] = 0x10; // The client capabilities
2930 expectedData[1] = 0x00; // Reserved flags
2931 expectedData[2] = 0x00; // Reserved flags
2932 expectedData[3] = 0x01; // The formats client supports
2933 expectedData.set(deflatedData, 4);
2934
2935 RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);
2936 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
2937 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData, true);
2938 });
2939
2940 it('Carriage return Line feed', function () {
2941
2942 let testText = "Hello\r\n\r\nworld\r\n!";
2943 let expectedText = encodeUTF8(testText + "\0");
2944
2945 let deflatedData = deflateWithSize(expectedText);
2946
2947 // Build Expected with flags and deflated data
2948 let expectedData = new Uint8Array(4 + deflatedData.length);
2949 expectedData[0] = 0x10; // The client capabilities
2950 expectedData[1] = 0x00; // Reserved flags
2951 expectedData[2] = 0x00; // Reserved flags
2952 expectedData[3] = 0x01; // The formats client supports
2953 expectedData.set(deflatedData, 4);
2954
2955 RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);
2956 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
2957 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData, true);
2958 });
2959
2960 it('Line feed', function () {
2961 let testText = "Hello\n\n\nworld\n!";
2962 let expectedText = encodeUTF8("Hello\r\n\r\n\r\nworld\r\n!\0");
2963
2964 let deflatedData = deflateWithSize(expectedText);
2965
2966 // Build Expected with flags and deflated data
2967 let expectedData = new Uint8Array(4 + deflatedData.length);
2968 expectedData[0] = 0x10; // The client capabilities
2969 expectedData[1] = 0x00; // Reserved flags
2970 expectedData[2] = 0x00; // Reserved flags
2971 expectedData[3] = 0x01; // The formats client supports
2972 expectedData.set(deflatedData, 4);
2973
2974 RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);
2975 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
2976 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData, true);
2977 });
2978
2979 it('Carriage return and Line feed mixed', function () {
2980 let testText = "\rHello\r\n\rworld\n\n!";
2981 let expectedText = encodeUTF8("\r\nHello\r\n\r\nworld\r\n\r\n!\0");
2982
2983 let deflatedData = deflateWithSize(expectedText);
2984
2985 // Build Expected with flags and deflated data
2986 let expectedData = new Uint8Array(4 + deflatedData.length);
2987 expectedData[0] = 0x10; // The client capabilities
2988 expectedData[1] = 0x00; // Reserved flags
2989 expectedData[2] = 0x00; // Reserved flags
2990 expectedData[3] = 0x01; // The formats client supports
2991 expectedData.set(deflatedData, 4);
2992
2993 RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);
2994 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
2995 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData, true);
2996 });
2997 });
2998 });
2999 });