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