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