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