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