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