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