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