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