]> git.proxmox.com Git - mirror_novnc.git/blame - tests/test.rfb.js
Remove unused properties and variables
[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
2c5491e1 142 function make_rfb(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();
147 rfb._rfb_connection_state = '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');
ee5cae9f 157 client._rfb_connection_state = '';
2f4516f2 158 this.clock.tick();
ee5cae9f 159 expect(client._rfb_connection_state).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
PO
173 beforeEach(function () {
174 client = make_rfb();
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');
185 expect(client._rfb_connection_state).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
PO
209 beforeEach(function () {
210 client = make_rfb();
211 client._rfb_connection_state = 'connecting';
212 });
213
430f00d6
PO
214 it('should set the rfb credentials properly"', function () {
215 client.sendCredentials({ password: 'pass' });
216 expect(client._rfb_credentials).to.deep.equal({ password: 'pass' });
b1dee947
SR
217 });
218
219 it('should call init_msg "soon"', function () {
220 client._init_msg = sinon.spy();
430f00d6 221 client.sendCredentials({ password: 'pass' });
b1dee947
SR
222 this.clock.tick(5);
223 expect(client._init_msg).to.have.been.calledOnce;
224 });
225 });
057b8fec 226 });
b1dee947 227
057b8fec 228 describe('Public API Basic Behavior', function () {
2b5f94fa 229 let client;
057b8fec
PO
230 beforeEach(function () {
231 client = make_rfb();
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');
bb25d3d6 250 client._rfb_connection_state = "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');
bb25d3d6 281 client._rfb_connection_state = "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);
355 client._sock._websocket._receive_data(new Uint8Array(data));
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');
364 let long_text = "";
365 for (let i = 0; i < client._sock._sQbufferSize + 100; i++) {
366 long_text += 'a';
367 }
368 client.clipboardPasteFrom(long_text);
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');
bb25d3d6 374 client._rfb_connection_state = "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 () {
b1dee947
SR
382 client._rfb_xvp_ver = 1;
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
PO
410 beforeEach(function () {
411 client = make_rfb();
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
453 client._sock._websocket._receive_data(new Uint8Array(incoming));
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
PO
583 beforeEach(function () {
584 client = make_rfb();
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
643 client._sock._websocket._receive_data(new Uint8Array(incoming));
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
PO
666 beforeEach(function () {
667 client = make_rfb();
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
698 client._sock._websocket._receive_data(new Uint8Array(incoming));
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
707 client._sock._websocket._receive_data(new Uint8Array(incoming));
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
PO
789
790 client._sock._websocket._receive_data(new Uint8Array(incoming));
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 () {
b1dee947
SR
800 client = make_rfb();
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);
2f4516f2
PO
806 client._rfb_connection_state = 'connecting';
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
3bb12056 813 it('should set the rfb_connection_state', function () {
bb25d3d6
PO
814 client._rfb_connection_state = 'connecting';
815 client._updateConnectionState('connected');
816 expect(client._rfb_connection_state).to.equal('connected');
3bb12056
SM
817 });
818
819 it('should not change the state when we are disconnected', function () {
bb25d3d6
PO
820 client.disconnect();
821 expect(client._rfb_connection_state).to.equal('disconnected');
b2e961d4
SM
822 client._updateConnectionState('connecting');
823 expect(client._rfb_connection_state).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
bb25d3d6 830 expect(client._rfb_connection_state).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
839 expect(client._rfb_connection_state).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
SM
847 client._updateConnectionState('disconnected');
848 expect(client._rfb_connection_state).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 () {
3bb12056 856 client = make_rfb();
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;
870 expect(client._rfb_connection_state).to.equal('disconnected');
871 });
872
d472f3f1
SM
873 it('should set clean_disconnect variable', function () {
874 client._rfb_clean_disconnect = true;
b2e961d4 875 client._rfb_connection_state = 'connected';
d472f3f1
SM
876 client._fail();
877 expect(client._rfb_clean_disconnect).to.be.false;
67cd2072
SM
878 });
879
d472f3f1 880 it('should result in disconnect event with clean set to false', function () {
b2e961d4 881 client._rfb_connection_state = '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
SM
906 beforeEach(function () {
907 client = make_rfb();
908 });
909
910 it('should result in a connect event if state becomes connected', function () {
2b5f94fa 911 const spy = sinon.spy();
ee5cae9f
SM
912 client.addEventListener("connect", spy);
913 client._rfb_connection_state = 'connecting';
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 () {
b1dee947 930 client = make_rfb();
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
SM
938 expect(client._updateConnectionState).to.have.been.calledTwice;
939 expect(client._rfb_disconnect_reason).to.not.equal("");
940 expect(client._rfb_connection_state).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);
c00ee156 948 expect(client._rfb_connection_state).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);
3bb12056 975 client._rfb_connection_state = '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);
3bb12056
SM
984 client._rfb_connection_state = 'disconnecting';
985 client._rfb_disconnect_reason = "";
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
PO
995 beforeEach(function () {
996 client = make_rfb();
2f4516f2 997 client._rfb_connection_state = 'connecting';
057b8fec 998 });
b1dee947 999
057b8fec 1000 describe('ProtocolVersion', function () {
2c5491e1 1001 function send_ver(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';
1008 client._sock._websocket._receive_data(arr);
1009 }
1010
1011 describe('version parsing', function () {
b1dee947
SR
1012 it('should interpret version 003.003 as version 3.3', function () {
1013 send_ver('003.003', client);
1014 expect(client._rfb_version).to.equal(3.3);
1015 });
1016
1017 it('should interpret version 003.006 as version 3.3', function () {
1018 send_ver('003.006', client);
1019 expect(client._rfb_version).to.equal(3.3);
1020 });
1021
1022 it('should interpret version 003.889 as version 3.3', function () {
1023 send_ver('003.889', client);
1024 expect(client._rfb_version).to.equal(3.3);
1025 });
1026
1027 it('should interpret version 003.007 as version 3.7', function () {
1028 send_ver('003.007', client);
1029 expect(client._rfb_version).to.equal(3.7);
1030 });
1031
1032 it('should interpret version 003.008 as version 3.8', function () {
1033 send_ver('003.008', client);
1034 expect(client._rfb_version).to.equal(3.8);
1035 });
1036
1037 it('should interpret version 004.000 as version 3.8', function () {
1038 send_ver('004.000', client);
1039 expect(client._rfb_version).to.equal(3.8);
1040 });
1041
1042 it('should interpret version 004.001 as version 3.8', function () {
1043 send_ver('004.001', client);
1044 expect(client._rfb_version).to.equal(3.8);
1045 });
1046
49aa5b81
LOH
1047 it('should interpret version 005.000 as version 3.8', function () {
1048 send_ver('005.000', client);
1049 expect(client._rfb_version).to.equal(3.8);
1050 });
1051
b1dee947 1052 it('should fail on an invalid version', function () {
3bb12056 1053 sinon.spy(client, "_fail");
b1dee947 1054 send_ver('002.000', client);
3bb12056 1055 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
1056 });
1057 });
1058
b1dee947
SR
1059 it('should send back the interpreted version', function () {
1060 send_ver('004.000', client);
1061
2b5f94fa
JD
1062 const expected_str = 'RFB 003.008\n';
1063 const expected = [];
1064 for (let i = 0; i < expected_str.length; i++) {
b1dee947
SR
1065 expected[i] = expected_str.charCodeAt(i);
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 () {
1072 send_ver('003.008', client);
c00ee156 1073 expect(client._rfb_init_state).to.equal('Security');
b1dee947 1074 });
3d7bb020
PO
1075
1076 describe('Repeater', function () {
057b8fec 1077 beforeEach(function () {
2f4516f2
PO
1078 client = make_rfb('wss://host:8675', { repeaterID: "12345" });
1079 client._rfb_connection_state = 'connecting';
057b8fec
PO
1080 });
1081
1082 it('should interpret version 000.000 as a repeater', function () {
3d7bb020
PO
1083 send_ver('000.000', client);
1084 expect(client._rfb_version).to.equal(0);
1085
2b5f94fa 1086 const sent_data = client._sock._websocket._get_sent_data();
3d7bb020
PO
1087 expect(new Uint8Array(sent_data.buffer, 0, 9)).to.array.equal(new Uint8Array([73, 68, 58, 49, 50, 51, 52, 53, 0]));
1088 expect(sent_data).to.have.length(250);
1089 });
1090
1091 it('should handle two step repeater negotiation', function () {
3d7bb020
PO
1092 send_ver('000.000', client);
1093 send_ver('003.008', client);
1094 expect(client._rfb_version).to.equal(3.8);
1095 });
1096 });
b1dee947
SR
1097 });
1098
1099 describe('Security', function () {
b1dee947 1100 beforeEach(function () {
c00ee156 1101 client._rfb_init_state = 'Security';
b1dee947
SR
1102 });
1103
1104 it('should simply receive the auth scheme when for versions < 3.7', function () {
1105 client._rfb_version = 3.6;
2b5f94fa
JD
1106 const auth_scheme_raw = [1, 2, 3, 4];
1107 const auth_scheme = (auth_scheme_raw[0] << 24) + (auth_scheme_raw[1] << 16) +
b1dee947 1108 (auth_scheme_raw[2] << 8) + auth_scheme_raw[3];
70e67958 1109 client._sock._websocket._receive_data(new Uint8Array(auth_scheme_raw));
b1dee947
SR
1110 expect(client._rfb_auth_scheme).to.equal(auth_scheme);
1111 });
1112
0ee5ca6e
PO
1113 it('should prefer no authentication is possible', function () {
1114 client._rfb_version = 3.7;
2b5f94fa 1115 const auth_schemes = [2, 1, 3];
70e67958 1116 client._sock._websocket._receive_data(new Uint8Array(auth_schemes));
0ee5ca6e
PO
1117 expect(client._rfb_auth_scheme).to.equal(1);
1118 expect(client._sock).to.have.sent(new Uint8Array([1, 1]));
1119 });
1120
b1dee947
SR
1121 it('should choose for the most prefered scheme possible for versions >= 3.7', function () {
1122 client._rfb_version = 3.7;
2b5f94fa 1123 const auth_schemes = [2, 22, 16];
70e67958 1124 client._sock._websocket._receive_data(new Uint8Array(auth_schemes));
0ee5ca6e
PO
1125 expect(client._rfb_auth_scheme).to.equal(22);
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");
b1dee947 1131 client._rfb_version = 3.7;
2b5f94fa 1132 const auth_schemes = [1, 32];
70e67958 1133 client._sock._websocket._receive_data(new Uint8Array(auth_schemes));
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 () {
1138 client._rfb_version = 3.7;
2b5f94fa 1139 const failure_data = [0, 0, 0, 0, 6, 119, 104, 111, 111, 112, 115];
b1dee947 1140 sinon.spy(client, '_fail');
70e67958 1141 client._sock._websocket._receive_data(new Uint8Array(failure_data));
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 () {
1149 client._rfb_version = 3.7;
2b5f94fa 1150 const auth_schemes = [1, 1];
b1dee947 1151 client._negotiate_authentication = sinon.spy();
70e67958 1152 client._sock._websocket._receive_data(new Uint8Array(auth_schemes));
c00ee156 1153 expect(client._rfb_init_state).to.equal('Authentication');
b1dee947
SR
1154 expect(client._negotiate_authentication).to.have.been.calledOnce;
1155 });
1156 });
1157
1158 describe('Authentication', function () {
b1dee947 1159 beforeEach(function () {
c00ee156 1160 client._rfb_init_state = 'Security';
b1dee947
SR
1161 });
1162
1163 function send_security(type, cl) {
1164 cl._sock._websocket._receive_data(new Uint8Array([1, type]));
1165 }
1166
1167 it('should fail on auth scheme 0 (pre 3.7) with the given message', function () {
1168 client._rfb_version = 3.6;
2b5f94fa
JD
1169 const err_msg = "Whoopsies";
1170 const data = [0, 0, 0, 0];
1171 const err_len = err_msg.length;
3949a095 1172 push32(data, err_len);
2b5f94fa 1173 for (let i = 0; i < err_len; i++) {
b1dee947
SR
1174 data.push(err_msg.charCodeAt(i));
1175 }
1176
1177 sinon.spy(client, '_fail');
1178 client._sock._websocket._receive_data(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 () {
1184 client._rfb_version = 3.8;
1185 send_security(1, client);
c00ee156 1186 expect(client._rfb_init_state).to.equal('SecurityResult');
b1dee947
SR
1187 });
1188
c00ee156 1189 it('should transition straight to ServerInitialisation on "no auth" for versions < 3.8', function () {
b1dee947 1190 client._rfb_version = 3.7;
b1dee947 1191 send_security(1, client);
c00ee156 1192 expect(client._rfb_init_state).to.equal('ServerInitialisation');
b1dee947
SR
1193 });
1194
1195 it('should fail on an unknown auth scheme', function () {
3bb12056 1196 sinon.spy(client, "_fail");
b1dee947
SR
1197 client._rfb_version = 3.8;
1198 send_security(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 () {
c00ee156 1204 client._rfb_init_state = 'Security';
b1dee947
SR
1205 client._rfb_version = 3.8;
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);
b1dee947 1211 send_security(2, client);
7d714b15 1212
2b5f94fa
JD
1213 const challenge = [];
1214 for (let i = 0; i < 16; i++) { challenge[i] = i; }
aa5b3a35
SM
1215 client._sock._websocket._receive_data(new Uint8Array(challenge));
1216
430f00d6 1217 expect(client._rfb_credentials).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 () {
430f00d6 1223 client._rfb_credentials = { password: 'passwd' };
b1dee947
SR
1224 send_security(2, client);
1225 client._sock._websocket._get_sent_data(); // skip the choice of auth reply
1226
2b5f94fa
JD
1227 const challenge = [];
1228 for (let i = 0; i < 16; i++) { challenge[i] = i; }
b1dee947
SR
1229 client._sock._websocket._receive_data(new Uint8Array(challenge));
1230
2b5f94fa 1231 const des_pass = RFB.genDES('passwd', challenge);
9ff86fb7 1232 expect(client._sock).to.have.sent(new Uint8Array(des_pass));
b1dee947
SR
1233 });
1234
1235 it('should transition to SecurityResult immediately after sending the password', function () {
430f00d6 1236 client._rfb_credentials = { password: 'passwd' };
b1dee947
SR
1237 send_security(2, client);
1238
2b5f94fa
JD
1239 const challenge = [];
1240 for (let i = 0; i < 16; i++) { challenge[i] = i; }
b1dee947
SR
1241 client._sock._websocket._receive_data(new Uint8Array(challenge));
1242
c00ee156 1243 expect(client._rfb_init_state).to.equal('SecurityResult');
b1dee947
SR
1244 });
1245 });
1246
1247 describe('XVP Authentication (type 22) Handler', function () {
b1dee947 1248 beforeEach(function () {
c00ee156 1249 client._rfb_init_state = 'Security';
b1dee947
SR
1250 client._rfb_version = 3.8;
1251 });
1252
1253 it('should fall through to standard VNC authentication upon completion', function () {
430f00d6
PO
1254 client._rfb_credentials = { username: 'user',
1255 target: 'target',
1256 password: 'password' };
b1dee947
SR
1257 client._negotiate_std_vnc_auth = sinon.spy();
1258 send_security(22, client);
1259 expect(client._negotiate_std_vnc_auth).to.have.been.calledOnce;
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);
430f00d6 1265 client._rfb_credentials = {};
b1dee947 1266 send_security(22, client);
7d714b15 1267
430f00d6 1268 expect(client._rfb_credentials).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);
430f00d6
PO
1276 client._rfb_credentials = { username: 'user',
1277 target: 'target' };
b1dee947 1278 send_security(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
PO
1284 it('should send user and target separately', function () {
1285 client._rfb_credentials = { username: 'user',
1286 target: 'target',
1287 password: 'password' };
b1dee947
SR
1288 client._negotiate_std_vnc_auth = sinon.spy();
1289
1290 send_security(22, client);
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 () {
c00ee156 1301 client._rfb_init_state = 'Security';
b1dee947
SR
1302 client._rfb_version = 3.8;
1303 send_security(16, client);
1304 client._sock._websocket._get_sent_data(); // skip the security reply
1305 });
1306
1307 function send_num_str_pairs(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
1321 client._sock._websocket._receive_data(new Uint8Array(data));
1322 }
1323
1324 it('should skip tunnel negotiation if no tunnels are requested', function () {
1325 client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0]));
1326 expect(client._rfb_tightvnc).to.be.true;
1327 });
1328
1329 it('should fail if no supported tunnels are listed', function () {
3bb12056 1330 sinon.spy(client, "_fail");
b1dee947 1331 send_num_str_pairs([[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 () {
1336 send_num_str_pairs([[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
PO
1340 it('should choose the notunnel tunnel type for Siemens devices', function () {
1341 send_num_str_pairs([[1, 'SICR', 'SCHANNEL'], [2, 'SICR', 'SCHANLPW']], client);
1342 expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 0]));
1343 });
1344
b1dee947
SR
1345 it('should continue to sub-auth negotiation after tunnel negotiation', function () {
1346 send_num_str_pairs([[0, 'TGHT', 'NOTUNNEL']], client);
1347 client._sock._websocket._get_sent_data(); // skip the tunnel choice here
1348 send_num_str_pairs([[1, 'STDV', 'NOAUTH__']], client);
9ff86fb7 1349 expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 1]));
c00ee156 1350 expect(client._rfb_init_state).to.equal('SecurityResult');
b1dee947
SR
1351 });
1352
1353 /*it('should attempt to use VNC auth over no auth when possible', function () {
1354 client._rfb_tightvnc = true;
1355 client._negotiate_std_vnc_auth = sinon.spy();
1356 send_num_str_pairs([[1, 'STDV', 'NOAUTH__'], [2, 'STDV', 'VNCAUTH_']], client);
1357 expect(client._sock).to.have.sent([0, 0, 0, 1]);
1358 expect(client._negotiate_std_vnc_auth).to.have.been.calledOnce;
1359 expect(client._rfb_auth_scheme).to.equal(2);
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 () {
1363 client._rfb_tightvnc = true;
1364 send_num_str_pairs([[1, 'STDV', 'NOAUTH__']], client);
9ff86fb7 1365 expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 1]));
c00ee156 1366 expect(client._rfb_init_state).to.equal('SecurityResult');
b1dee947
SR
1367 });
1368
1369 it('should accept VNC authentication and transition to that', function () {
1370 client._rfb_tightvnc = true;
1371 client._negotiate_std_vnc_auth = sinon.spy();
1372 send_num_str_pairs([[2, 'STDV', 'VNCAUTH__']], client);
9ff86fb7 1373 expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 2]));
b1dee947
SR
1374 expect(client._negotiate_std_vnc_auth).to.have.been.calledOnce;
1375 expect(client._rfb_auth_scheme).to.equal(2);
1376 });
1377
1378 it('should fail if there are no supported auth types', function () {
3bb12056 1379 sinon.spy(client, "_fail");
b1dee947
SR
1380 client._rfb_tightvnc = true;
1381 send_num_str_pairs([[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 () {
c00ee156 1389 client._rfb_init_state = 'SecurityResult';
b1dee947
SR
1390 });
1391
c00ee156 1392 it('should fall through to ServerInitialisation on a response code of 0', function () {
b1dee947 1393 client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0]));
c00ee156 1394 expect(client._rfb_init_state).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 () {
1398 client._rfb_version = 3.8;
1399 sinon.spy(client, '_fail');
2b5f94fa 1400 const failure_data = [0, 0, 0, 1, 0, 0, 0, 6, 119, 104, 111, 111, 112, 115];
b1dee947 1401 client._sock._websocket._receive_data(new Uint8Array(failure_data));
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');
b1dee947
SR
1408 client._rfb_version = 3.7;
1409 client._sock._websocket._receive_data(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
SM
1416 client.addEventListener("securityfailure", spy);
1417 client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 2]));
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 () {
1423 client._rfb_version = 3.8;
2b5f94fa 1424 const spy = sinon.spy();
d472f3f1 1425 client.addEventListener("securityfailure", spy);
2b5f94fa 1426 const failure_data = [0, 0, 0, 1, 0, 0, 0, 12, 115, 117, 99, 104,
7b536961 1427 32, 102, 97, 105, 108, 117, 114, 101];
d472f3f1
SM
1428 client._sock._websocket._receive_data(new Uint8Array(failure_data));
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 () {
1434 client._rfb_version = 3.9;
2b5f94fa 1435 const spy = sinon.spy();
d472f3f1 1436 client.addEventListener("securityfailure", spy);
2b5f94fa 1437 const failure_data = [0, 0, 0, 1, 0, 0, 0, 0];
d472f3f1
SM
1438 client._sock._websocket._receive_data(new Uint8Array(failure_data));
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 () {
1444 client._rfb_version = 3.6;
2b5f94fa 1445 const spy = sinon.spy();
d472f3f1
SM
1446 client.addEventListener("securityfailure", spy);
1447 client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 2]));
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 () {
2b5f94fa 1455 const client = make_rfb();
2f4516f2 1456 client._rfb_connection_state = 'connecting';
3d7bb020 1457 client._rfb_init_state = 'SecurityResult';
b1dee947 1458 client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0]));
c00ee156 1459 expect(client._rfb_init_state).to.equal('ServerInitialisation');
b1dee947
SR
1460 });
1461
1462 it('should send 1 if we are in shared mode', function () {
2b5f94fa 1463 const client = make_rfb('wss://host:8675', { shared: true });
2f4516f2 1464 client._rfb_connection_state = 'connecting';
3d7bb020 1465 client._rfb_init_state = 'SecurityResult';
b1dee947 1466 client._sock._websocket._receive_data(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 () {
2b5f94fa 1471 const client = make_rfb('wss://host:8675', { shared: false });
2f4516f2 1472 client._rfb_connection_state = 'connecting';
3d7bb020 1473 client._rfb_init_state = 'SecurityResult';
b1dee947 1474 client._sock._websocket._receive_data(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 () {
c00ee156 1481 client._rfb_init_state = 'ServerInitialisation';
b1dee947
SR
1482 });
1483
1484 function send_server_init(opts, client) {
2b5f94fa 1485 const full_opts = { width: 10, height: 12, bpp: 24, depth: 24, big_endian: 0,
7b536961
PO
1486 true_color: 1, red_max: 255, green_max: 255, blue_max: 255,
1487 red_shift: 16, green_shift: 8, blue_shift: 0, name: 'a name' };
2b5f94fa 1488 for (let opt in opts) {
b1dee947
SR
1489 full_opts[opt] = opts[opt];
1490 }
2b5f94fa 1491 const data = [];
b1dee947 1492
3949a095
SR
1493 push16(data, full_opts.width);
1494 push16(data, full_opts.height);
b1dee947
SR
1495
1496 data.push(full_opts.bpp);
1497 data.push(full_opts.depth);
1498 data.push(full_opts.big_endian);
1499 data.push(full_opts.true_color);
1500
3949a095
SR
1501 push16(data, full_opts.red_max);
1502 push16(data, full_opts.green_max);
1503 push16(data, full_opts.blue_max);
1504 push8(data, full_opts.red_shift);
1505 push8(data, full_opts.green_shift);
1506 push8(data, full_opts.blue_shift);
b1dee947
SR
1507
1508 // padding
3949a095
SR
1509 push8(data, 0);
1510 push8(data, 0);
1511 push8(data, 0);
b1dee947
SR
1512
1513 client._sock._websocket._receive_data(new Uint8Array(data));
1514
2b5f94fa 1515 const name_data = [];
8d6f686b
NL
1516 let name_len = [];
1517 pushString(name_data, full_opts.name);
1518 push32(name_len, name_data.length);
1519
1520 client._sock._websocket._receive_data(new Uint8Array(name_len));
b1dee947
SR
1521 client._sock._websocket._receive_data(new Uint8Array(name_data));
1522 }
1523
1524 it('should set the framebuffer width and height', function () {
1525 send_server_init({ width: 32, height: 84 }, client);
1526 expect(client._fb_width).to.equal(32);
1527 expect(client._fb_height).to.equal(84);
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);
8d6f686b 1535 send_server_init({ name: 'som€ nam€' }, client);
b1dee947 1536
8d6f686b 1537 expect(client._fb_name).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
1545 client._rfb_tightvnc = true;
1546 send_server_init({}, client);
1547
2b5f94fa 1548 const tight_data = [];
3949a095
SR
1549 push16(tight_data, 1);
1550 push16(tight_data, 2);
1551 push16(tight_data, 3);
1552 push16(tight_data, 0);
2b5f94fa 1553 for (let i = 0; i < 16 + 32 + 48; i++) {
b1dee947
SR
1554 tight_data.push(i);
1555 }
70e67958 1556 client._sock._websocket._receive_data(new Uint8Array(tight_data));
b1dee947 1557
c2a4d3ef 1558 expect(client._rfb_connection_state).to.equal('connected');
b1dee947
SR
1559 });
1560
9b84f516 1561 it('should resize the display', function () {
b1dee947
SR
1562 sinon.spy(client._display, 'resize');
1563 send_server_init({ width: 27, height: 32 }, client);
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');
1572 send_server_init({}, client);
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 () {
1592 send_server_init({ width: 27, height: 32 }, client);
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 () {
1605 send_server_init({ width: 27, height: 32, name: "Intel(r) AMT KVM"}, client);
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 () {
b1dee947 1620 send_server_init({}, client);
c2a4d3ef 1621 expect(client._rfb_connection_state).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 () {
1630 client = make_rfb();
b1dee947
SR
1631 client._fb_name = 'some device';
1632 client._fb_width = 640;
1633 client._fb_height = 20;
1634 });
1635
1636 describe('Framebuffer Update Handling', function () {
2b5f94fa 1637 const target_data_arr = [
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 ];
2b5f94fa 1643 let target_data;
b1dee947 1644
2b5f94fa 1645 const target_data_check_arr = [
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 ];
2b5f94fa 1651 let target_data_check;
b1dee947
SR
1652
1653 before(function () {
1654 // NB(directxman12): PhantomJS 1.x doesn't implement Uint8ClampedArray
1655 target_data = new Uint8Array(target_data_arr);
1656 target_data_check = new Uint8Array(target_data_check_arr);
1657 });
1658
2c5491e1 1659 function send_fbu_msg(rect_info, rect_data, client, rect_cnt) {
2b5f94fa 1660 let data = [];
b1dee947
SR
1661
1662 if (!rect_cnt || rect_cnt > -1) {
1663 // header
1664 data.push(0); // msg type
1665 data.push(0); // padding
3949a095 1666 push16(data, rect_cnt || rect_data.length);
b1dee947
SR
1667 }
1668
2b5f94fa 1669 for (let i = 0; i < rect_data.length; i++) {
b1dee947 1670 if (rect_info[i]) {
3949a095
SR
1671 push16(data, rect_info[i].x);
1672 push16(data, rect_info[i].y);
1673 push16(data, rect_info[i].width);
1674 push16(data, rect_info[i].height);
1675 push32(data, rect_info[i].encoding);
b1dee947
SR
1676 }
1677 data = data.concat(rect_data[i]);
1678 }
1679
1680 client._sock._websocket._receive_data(new Uint8Array(data));
1681 }
1682
1683 it('should send an update request if there is sufficient data', function () {
0e4808bf 1684 const expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: () => {}};
2ba767a7 1685 RFB.messages.fbUpdateRequest(expected_msg, true, 0, 0, 640, 20);
b1dee947 1686
651c23ec 1687 client._framebufferUpdate = () => true;
b1dee947
SR
1688 client._sock._websocket._receive_data(new Uint8Array([0]));
1689
9ff86fb7 1690 expect(client._sock).to.have.sent(expected_msg._sQ);
b1dee947
SR
1691 });
1692
1693 it('should not send an update request if we need more data', function () {
1694 client._sock._websocket._receive_data(new Uint8Array([0]));
1695 expect(client._sock._websocket._get_sent_data()).to.have.length(0);
1696 });
1697
1698 it('should resume receiving an update if we previously did not have enough data', function () {
0e4808bf 1699 const expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: () => {}};
2ba767a7 1700 RFB.messages.fbUpdateRequest(expected_msg, true, 0, 0, 640, 20);
b1dee947
SR
1701
1702 // just enough to set FBU.rects
1703 client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 3]));
1704 expect(client._sock._websocket._get_sent_data()).to.have.length(0);
1705
8a189a62 1706 client._framebufferUpdate = function () { this._sock.rQskipBytes(1); return true; }; // we magically have enough data
b1dee947
SR
1707 // 247 should *not* be used as the message type here
1708 client._sock._websocket._receive_data(new Uint8Array([247]));
9ff86fb7 1709 expect(client._sock).to.have.sent(expected_msg._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;
76a86ff5 1715 client._sock._websocket._receive_data(new Uint8Array([0]));
1716
1717 expect(client._sock._websocket._get_sent_data()).to.have.length(0);
1718 });
1719
b1dee947 1720 it('should fail on an unsupported encoding', function () {
3bb12056 1721 sinon.spy(client, "_fail");
2b5f94fa 1722 const rect_info = { x: 8, y: 11, width: 27, height: 32, encoding: 234 };
b1dee947 1723 send_fbu_msg([rect_info], [[]], 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
1729 client._fb_width = 4;
1730 client._fb_height = 4;
1731 client._display.resize(4, 4);
02329ab1 1732 client._display.blitRgbxImage(0, 0, 4, 2, new Uint8Array(target_data_check_arr.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]];
b1dee947
SR
1738 send_fbu_msg([info[0]], [rects[0]], client, 2);
1739 send_fbu_msg([info[1]], [rects[1]], client, -1);
1740 expect(client._display).to.have.displayed(target_data_check);
1741 });
1742
1743 describe('Message Encoding Handlers', function () {
b1dee947 1744 beforeEach(function () {
b1dee947
SR
1745 // a really small frame
1746 client._fb_width = 4;
1747 client._fb_height = 4;
69411b9e 1748 client._fb_depth = 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]];
1763 send_fbu_msg(info, rects, client);
1764 expect(client._display).to.have.displayed(target_data);
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]];
1777 client._fb_depth = 8;
1778 send_fbu_msg(info, rects, client);
1779 expect(client._display).to.have.displayed(target_data_check);
1780 });
1781
b1dee947
SR
1782 it('should handle the COPYRECT encoding', function () {
1783 // seed some initial data to copy
02329ab1 1784 client._display.blitRgbxImage(0, 0, 4, 2, new Uint8Array(target_data_check_arr.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]];
b1dee947
SR
1790 send_fbu_msg(info, rects, client);
1791 expect(client._display).to.have.displayed(target_data_check);
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
b1dee947
SR
1818
1819 send_fbu_msg(info, [rect], client);
1820 expect(client._display).to.have.displayed(target_data_check);
1821 });
1822
1823 describe('the HEXTILE encoding handler', function () {
b1dee947 1824 it('should handle a tile with fg, bg specified, normal subrects', function () {
2b5f94fa
JD
1825 const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
1826 const rect = [];
b1dee947 1827 rect.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects
3949a095 1828 push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
b1dee947
SR
1829 rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
1830 rect.push(0x00);
1831 rect.push(0x00);
1832 rect.push(0xff);
1833 rect.push(2); // 2 subrects
1834 rect.push(0); // x: 0, y: 0
1835 rect.push(1 | (1 << 4)); // width: 2, height: 2
1836 rect.push(2 | (2 << 4)); // x: 2, y: 2
1837 rect.push(1 | (1 << 4)); // width: 2, height: 2
1838 send_fbu_msg(info, [rect], client);
1839 expect(client._display).to.have.displayed(target_data_check);
1840 });
1841
1842 it('should handle a raw tile', function () {
2b5f94fa
JD
1843 const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
1844 const rect = [];
b1dee947 1845 rect.push(0x01); // raw
2b5f94fa 1846 for (let i = 0; i < target_data.length; i += 4) {
b1dee947
SR
1847 rect.push(target_data[i + 2]);
1848 rect.push(target_data[i + 1]);
1849 rect.push(target_data[i]);
1850 rect.push(target_data[i + 3]);
1851 }
1852 send_fbu_msg(info, [rect], client);
1853 expect(client._display).to.have.displayed(target_data);
1854 });
1855
1856 it('should handle a tile with only bg specified (solid bg)', function () {
2b5f94fa
JD
1857 const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
1858 const rect = [];
b1dee947 1859 rect.push(0x02);
3949a095 1860 push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
b1dee947
SR
1861 send_fbu_msg(info, [rect], client);
1862
2b5f94fa
JD
1863 const expected = [];
1864 for (let i = 0; i < 16; i++) { push32(expected, 0xff00ff); }
b1dee947
SR
1865 expect(client._display).to.have.displayed(new Uint8Array(expected));
1866 });
1867
40ac6f0a
RK
1868 it('should handle a tile with only bg specified and an empty frame afterwards', function () {
1869 // set the width so we can have two tiles
1870 client._fb_width = 8;
02329ab1 1871 client._display.resize(8, 4);
40ac6f0a 1872
2b5f94fa 1873 const info = [{ x: 0, y: 0, width: 32, height: 4, encoding: 0x05 }];
40ac6f0a 1874
2b5f94fa 1875 const rect = [];
40ac6f0a
RK
1876
1877 // send a bg frame
1878 rect.push(0x02);
3949a095 1879 push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
40ac6f0a
RK
1880
1881 // send an empty frame
1882 rect.push(0x00);
1883
1884 send_fbu_msg(info, [rect], client);
1885
2b5f94fa
JD
1886 const expected = [];
1887 for (let i = 0; i < 16; i++) { push32(expected, 0xff00ff); } // rect 1: solid
1888 for (let i = 0; i < 16; i++) { push32(expected, 0xff00ff); } // rect 2: same bkground color
40ac6f0a
RK
1889 expect(client._display).to.have.displayed(new Uint8Array(expected));
1890 });
1891
b1dee947 1892 it('should handle a tile with bg and coloured subrects', function () {
2b5f94fa
JD
1893 const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
1894 const rect = [];
b1dee947 1895 rect.push(0x02 | 0x08 | 0x10); // bg spec, anysubrects, colouredsubrects
3949a095 1896 push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
b1dee947
SR
1897 rect.push(2); // 2 subrects
1898 rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
1899 rect.push(0x00);
1900 rect.push(0x00);
1901 rect.push(0xff);
1902 rect.push(0); // x: 0, y: 0
1903 rect.push(1 | (1 << 4)); // width: 2, height: 2
1904 rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
1905 rect.push(0x00);
1906 rect.push(0x00);
1907 rect.push(0xff);
1908 rect.push(2 | (2 << 4)); // x: 2, y: 2
1909 rect.push(1 | (1 << 4)); // width: 2, height: 2
1910 send_fbu_msg(info, [rect], client);
1911 expect(client._display).to.have.displayed(target_data_check);
1912 });
1913
1914 it('should carry over fg and bg colors from the previous tile if not specified', function () {
1915 client._fb_width = 4;
1916 client._fb_height = 17;
1917 client._display.resize(4, 17);
1918
2b5f94fa
JD
1919 const info = [{ x: 0, y: 0, width: 4, height: 17, encoding: 0x05}];
1920 const rect = [];
b1dee947 1921 rect.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects
3949a095 1922 push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
b1dee947
SR
1923 rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
1924 rect.push(0x00);
1925 rect.push(0x00);
1926 rect.push(0xff);
1927 rect.push(8); // 8 subrects
2b5f94fa 1928 for (let i = 0; i < 4; i++) {
b1dee947
SR
1929 rect.push((0 << 4) | (i * 4)); // x: 0, y: i*4
1930 rect.push(1 | (1 << 4)); // width: 2, height: 2
1931 rect.push((2 << 4) | (i * 4 + 2)); // x: 2, y: i * 4 + 2
1932 rect.push(1 | (1 << 4)); // width: 2, height: 2
1933 }
1934 rect.push(0x08); // anysubrects
1935 rect.push(1); // 1 subrect
1936 rect.push(0); // x: 0, y: 0
1937 rect.push(1 | (1 << 4)); // width: 2, height: 2
1938 send_fbu_msg(info, [rect], client);
1939
2b5f94fa
JD
1940 let expected = [];
1941 for (let i = 0; i < 4; i++) { expected = expected.concat(target_data_check_arr); }
b1dee947
SR
1942 expected = expected.concat(target_data_check_arr.slice(0, 16));
1943 expect(client._display).to.have.displayed(new Uint8Array(expected));
1944 });
1945
1946 it('should fail on an invalid subencoding', function () {
6786fd87 1947 sinon.spy(client, "_fail");
2b5f94fa
JD
1948 const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
1949 const rects = [[45]]; // an invalid subencoding
b1dee947 1950 send_fbu_msg(info, rects, client);
3bb12056 1951 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
1952 });
1953 });
1954
1955 it.skip('should handle the TIGHT encoding', function () {
1956 // TODO(directxman12): test this
1957 });
1958
1959 it.skip('should handle the TIGHT_PNG encoding', function () {
1960 // TODO(directxman12): test this
1961 });
1962
1963 it('should handle the DesktopSize pseduo-encoding', function () {
b1dee947
SR
1964 sinon.spy(client._display, 'resize');
1965 send_fbu_msg([{ x: 0, y: 0, width: 20, height: 50, encoding: -223 }], [[]], client);
1966
b1dee947
SR
1967 expect(client._fb_width).to.equal(20);
1968 expect(client._fb_height).to.equal(50);
1969
1970 expect(client._display.resize).to.have.been.calledOnce;
1971 expect(client._display.resize).to.have.been.calledWith(20, 50);
1972 });
1973
4dec490a 1974 describe('the ExtendedDesktopSize pseudo-encoding handler', function () {
4dec490a 1975 beforeEach(function () {
4dec490a 1976 // a really small frame
1977 client._fb_width = 4;
1978 client._fb_height = 4;
02329ab1 1979 client._display.resize(4, 4);
4dec490a 1980 sinon.spy(client._display, 'resize');
4dec490a 1981 });
1982
2c5491e1 1983 function make_screen_data(nr_of_screens) {
2b5f94fa 1984 const data = [];
3949a095
SR
1985 push8(data, nr_of_screens); // number-of-screens
1986 push8(data, 0); // padding
1987 push16(data, 0); // padding
2b5f94fa 1988 for (let i=0; i<nr_of_screens; i += 1) {
3949a095
SR
1989 push32(data, 0); // id
1990 push16(data, 0); // x-position
1991 push16(data, 0); // y-position
1992 push16(data, 20); // width
1993 push16(data, 50); // height
1994 push32(data, 0); // flags
4dec490a 1995 }
1996 return data;
1997 }
1998
1999 it('should handle a resize requested by this client', function () {
2b5f94fa
JD
2000 const reason_for_change = 1; // requested by this client
2001 const status_code = 0; // No error
4dec490a 2002
2003 send_fbu_msg([{ x: reason_for_change, y: status_code,
2004 width: 20, height: 50, encoding: -308 }],
2005 make_screen_data(1), client);
2006
4dec490a 2007 expect(client._fb_width).to.equal(20);
2008 expect(client._fb_height).to.equal(50);
2009
2010 expect(client._display.resize).to.have.been.calledOnce;
2011 expect(client._display.resize).to.have.been.calledWith(20, 50);
4dec490a 2012 });
2013
2014 it('should handle a resize requested by another client', function () {
2b5f94fa
JD
2015 const reason_for_change = 2; // requested by another client
2016 const status_code = 0; // No error
4dec490a 2017
2018 send_fbu_msg([{ x: reason_for_change, y: status_code,
2019 width: 20, height: 50, encoding: -308 }],
2020 make_screen_data(1), client);
2021
4dec490a 2022 expect(client._fb_width).to.equal(20);
2023 expect(client._fb_height).to.equal(50);
2024
2025 expect(client._display.resize).to.have.been.calledOnce;
2026 expect(client._display.resize).to.have.been.calledWith(20, 50);
4dec490a 2027 });
2028
2029 it('should be able to recieve requests which contain data for multiple screens', function () {
2b5f94fa
JD
2030 const reason_for_change = 2; // requested by another client
2031 const status_code = 0; // No error
4dec490a 2032
2033 send_fbu_msg([{ x: reason_for_change, y: status_code,
2034 width: 60, height: 50, encoding: -308 }],
2035 make_screen_data(3), client);
2036
4dec490a 2037 expect(client._fb_width).to.equal(60);
2038 expect(client._fb_height).to.equal(50);
2039
2040 expect(client._display.resize).to.have.been.calledOnce;
2041 expect(client._display.resize).to.have.been.calledWith(60, 50);
4dec490a 2042 });
2043
2044 it('should not handle a failed request', function () {
2b5f94fa
JD
2045 const reason_for_change = 1; // requested by this client
2046 const status_code = 1; // Resize is administratively prohibited
4dec490a 2047
2048 send_fbu_msg([{ x: reason_for_change, y: status_code,
2049 width: 20, height: 50, encoding: -308 }],
2050 make_screen_data(1), client);
2051
2052 expect(client._fb_width).to.equal(4);
2053 expect(client._fb_height).to.equal(4);
2054
2055 expect(client._display.resize).to.not.have.been.called;
4dec490a 2056 });
2057 });
2058
d1050405
PO
2059 describe('the Cursor pseudo-encoding handler', function () {
2060 beforeEach(function () {
2061 sinon.spy(client._cursor, 'change');
2062 });
2063
2064 it('should handle a standard cursor', function () {
2065 const info = { x: 5, y: 7,
2066 width: 4, height: 4,
2067 encoding: -239};
2068 let rect = [];
2069 let expected = [];
2070
2071 for (let i = 0;i < info.width*info.height;i++) {
2072 push32(rect, 0x11223300);
2073 }
2074 push32(rect, 0xa0a0a0a0);
2075
2076 for (let i = 0;i < info.width*info.height/2;i++) {
2077 push32(expected, 0x332211ff);
2078 push32(expected, 0x33221100);
2079 }
2080 expected = new Uint8Array(expected);
2081
2082 send_fbu_msg([info], [rect], client);
2083
2084 expect(client._cursor.change).to.have.been.calledOnce;
2085 expect(client._cursor.change).to.have.been.calledWith(expected, 5, 7, 4, 4);
2086 });
2087
2088 it('should handle an empty cursor', function () {
2089 const info = { x: 0, y: 0,
2090 width: 0, height: 0,
2091 encoding: -239};
2092 const rect = [];
2093
2094 send_fbu_msg([info], [rect], client);
2095
2096 expect(client._cursor.change).to.have.been.calledOnce;
2097 expect(client._cursor.change).to.have.been.calledWith(new Uint8Array, 0, 0, 0, 0);
2098 });
2099
2100 it('should handle a transparent cursor', function () {
2101 const info = { x: 5, y: 7,
2102 width: 4, height: 4,
2103 encoding: -239};
2104 let rect = [];
2105 let expected = [];
2106
2107 for (let i = 0;i < info.width*info.height;i++) {
2108 push32(rect, 0x11223300);
2109 }
2110 push32(rect, 0x00000000);
2111
2112 for (let i = 0;i < info.width*info.height;i++) {
2113 push32(expected, 0x33221100);
2114 }
2115 expected = new Uint8Array(expected);
2116
2117 send_fbu_msg([info], [rect], client);
2118
2119 expect(client._cursor.change).to.have.been.calledOnce;
2120 expect(client._cursor.change).to.have.been.calledWith(expected, 5, 7, 4, 4);
2121 });
2122
2123 describe('dot for empty cursor', function () {
2124 beforeEach(function () {
2125 client.showDotCursor = true;
2126 // Was called when we enabled dot cursor
c9765e50 2127 client._cursor.change.resetHistory();
d1050405
PO
2128 });
2129
2130 it('should show a standard cursor', function () {
2131 const info = { x: 5, y: 7,
2132 width: 4, height: 4,
2133 encoding: -239};
2134 let rect = [];
2135 let expected = [];
2136
2137 for (let i = 0;i < info.width*info.height;i++) {
2138 push32(rect, 0x11223300);
2139 }
2140 push32(rect, 0xa0a0a0a0);
2141
2142 for (let i = 0;i < info.width*info.height/2;i++) {
2143 push32(expected, 0x332211ff);
2144 push32(expected, 0x33221100);
2145 }
2146 expected = new Uint8Array(expected);
2147
2148 send_fbu_msg([info], [rect], client);
2149
2150 expect(client._cursor.change).to.have.been.calledOnce;
2151 expect(client._cursor.change).to.have.been.calledWith(expected, 5, 7, 4, 4);
2152 });
2153
2154 it('should handle an empty cursor', function () {
2155 const info = { x: 0, y: 0,
2156 width: 0, height: 0,
2157 encoding: -239};
2158 const rect = [];
2159 const dot = RFB.cursors.dot;
2160
2161 send_fbu_msg([info], [rect], client);
2162
2163 expect(client._cursor.change).to.have.been.calledOnce;
2164 expect(client._cursor.change).to.have.been.calledWith(dot.rgbaPixels,
2165 dot.hotx,
2166 dot.hoty,
2167 dot.w,
2168 dot.h);
2169 });
2170
2171 it('should handle a transparent cursor', function () {
2172 const info = { x: 5, y: 7,
2173 width: 4, height: 4,
2174 encoding: -239};
2175 let rect = [];
2176 const dot = RFB.cursors.dot;
2177
2178 for (let i = 0;i < info.width*info.height;i++) {
2179 push32(rect, 0x11223300);
2180 }
2181 push32(rect, 0x00000000);
2182
2183 send_fbu_msg([info], [rect], client);
2184
2185 expect(client._cursor.change).to.have.been.calledOnce;
2186 expect(client._cursor.change).to.have.been.calledWith(dot.rgbaPixels,
2187 dot.hotx,
2188 dot.hoty,
2189 dot.w,
2190 dot.h);
2191 });
2192 });
b1dee947
SR
2193 });
2194
296ba51f
NL
2195 describe('the VMware Cursor pseudo-encoding handler', function () {
2196 beforeEach(function () {
2197 sinon.spy(client._cursor, 'change');
2198 });
2199 afterEach(function () {
2200 client._cursor.change.resetHistory();
2201 });
2202
2203 it('should handle the VMware cursor pseudo-encoding', function () {
2204 let data = [0x00, 0x00, 0xff, 0,
2205 0x00, 0xff, 0x00, 0,
2206 0x00, 0xff, 0x00, 0,
2207 0x00, 0x00, 0xff, 0];
2208 let rect = [];
2209 push8(rect, 0);
2210 push8(rect, 0);
2211
2212 //AND-mask
2213 for (let i = 0; i < data.length; i++) {
2214 push8(rect, data[i]);
2215 }
2216 //XOR-mask
2217 for (let i = 0; i < data.length; i++) {
2218 push8(rect, data[i]);
2219 }
2220
2221 send_fbu_msg([{ x: 0, y: 0, width: 2, height: 2,
2222 encoding: 0x574d5664}],
2223 [rect], client);
2224 expect(client._FBU.rects).to.equal(0);
2225 });
2226
2227 it('should handle insufficient cursor pixel data', function () {
2228
2229 // Specified 14x23 pixels for the cursor,
2230 // but only send 2x2 pixels worth of data
2231 let w = 14;
2232 let h = 23;
2233 let data = [0x00, 0x00, 0xff, 0,
2234 0x00, 0xff, 0x00, 0];
2235 let rect = [];
2236
2237 push8(rect, 0);
2238 push8(rect, 0);
2239
2240 //AND-mask
2241 for (let i = 0; i < data.length; i++) {
2242 push8(rect, data[i]);
2243 }
2244 //XOR-mask
2245 for (let i = 0; i < data.length; i++) {
2246 push8(rect, data[i]);
2247 }
2248
2249 send_fbu_msg([{ x: 0, y: 0, width: w, height: h,
2250 encoding: 0x574d5664}],
2251 [rect], client);
2252
2253 // expect one FBU to remain unhandled
2254 expect(client._FBU.rects).to.equal(1);
2255 });
2256
2257 it('should update the cursor when type is classic', function () {
2258 let and_mask =
2259 [0xff, 0xff, 0xff, 0xff, //Transparent
2260 0xff, 0xff, 0xff, 0xff, //Transparent
2261 0x00, 0x00, 0x00, 0x00, //Opaque
2262 0xff, 0xff, 0xff, 0xff]; //Inverted
2263
2264 let xor_mask =
2265 [0x00, 0x00, 0x00, 0x00, //Transparent
2266 0x00, 0x00, 0x00, 0x00, //Transparent
2267 0x11, 0x22, 0x33, 0x44, //Opaque
2268 0xff, 0xff, 0xff, 0x44]; //Inverted
2269
2270 let rect = [];
2271 push8(rect, 0); //cursor_type
2272 push8(rect, 0); //padding
2273 let hotx = 0;
2274 let hoty = 0;
2275 let w = 2;
2276 let h = 2;
2277
2278 //AND-mask
2279 for (let i = 0; i < and_mask.length; i++) {
2280 push8(rect, and_mask[i]);
2281 }
2282 //XOR-mask
2283 for (let i = 0; i < xor_mask.length; i++) {
2284 push8(rect, xor_mask[i]);
2285 }
2286
2287 let expected_rgba = [0x00, 0x00, 0x00, 0x00,
2288 0x00, 0x00, 0x00, 0x00,
2289 0x33, 0x22, 0x11, 0xff,
2290 0x00, 0x00, 0x00, 0xff];
2291
2292 send_fbu_msg([{ x: hotx, y: hoty,
2293 width: w, height: h,
2294 encoding: 0x574d5664}],
2295 [rect], client);
2296
2297 expect(client._cursor.change)
2298 .to.have.been.calledOnce;
2299 expect(client._cursor.change)
2300 .to.have.been.calledWith(expected_rgba,
2301 hotx, hoty,
2302 w, h);
2303 });
2304
2305 it('should update the cursor when type is alpha', function () {
71bb3fdf 2306 let data = [0xee, 0x55, 0xff, 0x00, // rgba
296ba51f
NL
2307 0x00, 0xff, 0x00, 0xff,
2308 0x00, 0xff, 0x00, 0x22,
2309 0x00, 0xff, 0x00, 0x22,
2310 0x00, 0xff, 0x00, 0x22,
2311 0x00, 0x00, 0xff, 0xee];
2312 let rect = [];
2313 push8(rect, 1); //cursor_type
2314 push8(rect, 0); //padding
2315 let hotx = 0;
2316 let hoty = 0;
2317 let w = 3;
2318 let h = 2;
2319
2320 for (let i = 0; i < data.length; i++) {
2321 push8(rect, data[i]);
2322 }
2323
71bb3fdf 2324 let expected_rgba = [0xee, 0x55, 0xff, 0x00,
296ba51f
NL
2325 0x00, 0xff, 0x00, 0xff,
2326 0x00, 0xff, 0x00, 0x22,
2327 0x00, 0xff, 0x00, 0x22,
2328 0x00, 0xff, 0x00, 0x22,
71bb3fdf 2329 0x00, 0x00, 0xff, 0xee];
296ba51f
NL
2330
2331 send_fbu_msg([{ x: hotx, y: hoty,
2332 width: w, height: h,
2333 encoding: 0x574d5664}],
2334 [rect], client);
2335
2336 expect(client._cursor.change)
2337 .to.have.been.calledOnce;
2338 expect(client._cursor.change)
2339 .to.have.been.calledWith(expected_rgba,
2340 hotx, hoty,
2341 w, h);
2342 });
2343
2344 it('should not update cursor when incorrect cursor type given', function () {
2345 let rect = [];
2346 push8(rect, 3); // invalid cursor type
2347 push8(rect, 0); // padding
2348
2349 client._cursor.change.resetHistory();
2350 send_fbu_msg([{ x: 0, y: 0, width: 2, height: 2,
2351 encoding: 0x574d5664}],
2352 [rect], client);
2353
2354 expect(client._cursor.change)
2355 .to.not.have.been.called;
2356 });
2357 });
2358
b1dee947 2359 it('should handle the last_rect pseudo-encoding', function () {
b1dee947
SR
2360 send_fbu_msg([{ x: 0, y: 0, width: 0, height: 0, encoding: -224}], [[]], client, 100);
2361 expect(client._FBU.rects).to.equal(0);
b1dee947 2362 });
ce66b469
NL
2363
2364 it('should handle the DesktopName pseudo-encoding', function () {
2365 let data = [];
8d6f686b
NL
2366 push32(data, 13);
2367 pushString(data, "som€ nam€");
ce66b469
NL
2368
2369 const spy = sinon.spy();
2370 client.addEventListener("desktopname", spy);
2371
2372 send_fbu_msg([{ x: 0, y: 0, width: 0, height: 0, encoding: -307 }], [data], client);
2373
8d6f686b 2374 expect(client._fb_name).to.equal('som€ nam€');
ce66b469 2375 expect(spy).to.have.been.calledOnce;
8d6f686b 2376 expect(spy.args[0][0].detail.name).to.equal('som€ nam€');
ce66b469 2377 });
b1dee947
SR
2378 });
2379 });
2380
b1dee947 2381 describe('XVP Message Handling', function () {
b1dee947 2382 it('should set the XVP version and fire the callback with the version on XVP_INIT', function () {
2b5f94fa 2383 const spy = sinon.spy();
e89eef94 2384 client.addEventListener("capabilities", spy);
b1dee947
SR
2385 client._sock._websocket._receive_data(new Uint8Array([250, 0, 10, 1]));
2386 expect(client._rfb_xvp_ver).to.equal(10);
e89eef94
PO
2387 expect(spy).to.have.been.calledOnce;
2388 expect(spy.args[0][0].detail.capabilities.power).to.be.true;
747b4623 2389 expect(client.capabilities.power).to.be.true;
b1dee947
SR
2390 });
2391
2392 it('should fail on unknown XVP message types', function () {
3bb12056 2393 sinon.spy(client, "_fail");
b1dee947 2394 client._sock._websocket._receive_data(new Uint8Array([250, 0, 10, 237]));
3bb12056 2395 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
2396 });
2397 });
2398
f73fdc3e
NL
2399 describe('Normal Clipboard Handling Receive', function () {
2400 it('should fire the clipboard callback with the retrieved text on ServerCutText', function () {
2401 const expected_str = 'cheese!';
2402 const data = [3, 0, 0, 0];
2403 push32(data, expected_str.length);
2404 for (let i = 0; i < expected_str.length; i++) { data.push(expected_str.charCodeAt(i)); }
2405 const spy = sinon.spy();
2406 client.addEventListener("clipboard", spy);
2407
2408 client._sock._websocket._receive_data(new Uint8Array(data));
2409 expect(spy).to.have.been.calledOnce;
2410 expect(spy.args[0][0].detail.text).to.equal(expected_str);
2411 });
2412 });
2413
2414 describe('Extended clipboard Handling', function () {
2415
2416 describe('Extended clipboard initialization', function () {
2417 beforeEach(function () {
2418 sinon.spy(RFB.messages, 'extendedClipboardCaps');
2419 });
2420
2421 afterEach(function () {
2422 RFB.messages.extendedClipboardCaps.restore();
2423 });
2424
2425 it('should update capabilities when receiving a Caps message', function () {
2426 let data = [3, 0, 0, 0];
2427 const flags = [0x1F, 0x00, 0x00, 0x03];
2428 let fileSizes = [0x00, 0x00, 0x00, 0x1E,
2429 0x00, 0x00, 0x00, 0x3C];
2430
2431 push32(data, toUnsigned32bit(-12));
2432 data = data.concat(flags);
2433 data = data.concat(fileSizes);
2434 client._sock._websocket._receive_data(new Uint8Array(data));
2435
2436 // Check that we give an response caps when we receive one
2437 expect(RFB.messages.extendedClipboardCaps).to.have.been.calledOnce;
2438
2439 // FIXME: Can we avoid checking internal variables?
2440 expect(client._clipboardServerCapabilitiesFormats[0]).to.not.equal(true);
2441 expect(client._clipboardServerCapabilitiesFormats[1]).to.equal(true);
2442 expect(client._clipboardServerCapabilitiesFormats[2]).to.equal(true);
2443 expect(client._clipboardServerCapabilitiesActions[(1 << 24)]).to.equal(true);
2444 });
2445
2446
2447 });
2448
2449 describe('Extended Clipboard Handling Receive', function () {
2450
2451 beforeEach(function () {
2452 // Send our capabilities
2453 let data = [3, 0, 0, 0];
2454 const flags = [0x1F, 0x00, 0x00, 0x01];
2455 let fileSizes = [0x00, 0x00, 0x00, 0x1E];
2456
2457 push32(data, toUnsigned32bit(-8));
2458 data = data.concat(flags);
2459 data = data.concat(fileSizes);
2460 client._sock._websocket._receive_data(new Uint8Array(data));
2461 });
2462
2463 describe('Handle Provide', function () {
2464 it('should update clipboard with correct Unicode data from a Provide message', function () {
2465 let expectedData = "Aå漢字!";
2466 let data = [3, 0, 0, 0];
2467 const flags = [0x10, 0x00, 0x00, 0x01];
2468
2469 /* The size 10 (utf8 encoded string size) and the
2470 string "Aå漢字!" utf8 encoded and deflated. */
2471 let deflatedData = [120, 94, 99, 96, 96, 224, 114, 60,
2472 188, 244, 217, 158, 69, 79, 215,
2473 78, 87, 4, 0, 35, 207, 6, 66];
2474
2475 // How much data we are sending.
2476 push32(data, toUnsigned32bit(-(4 + deflatedData.length)));
2477
2478 data = data.concat(flags);
2479 data = data.concat(deflatedData);
2480
2481 const spy = sinon.spy();
2482 client.addEventListener("clipboard", spy);
2483
2484 client._sock._websocket._receive_data(new Uint8Array(data));
2485 expect(spy).to.have.been.calledOnce;
2486 expect(spy.args[0][0].detail.text).to.equal(expectedData);
2487 client.removeEventListener("clipboard", spy);
2488 });
2489
2490 it('should update clipboard with correct escape characters from a Provide message ', function () {
2491 let expectedData = "Oh\nmy!";
2492 let data = [3, 0, 0, 0];
2493 const flags = [0x10, 0x00, 0x00, 0x01];
2494
2495 let text = encodeUTF8("Oh\r\nmy!\0");
2496
2497 let deflatedText = deflateWithSize(text);
2498
2499 // How much data we are sending.
2500 push32(data, toUnsigned32bit(-(4 + deflatedText.length)));
ceb8ef4e
AT
2501
2502 data = data.concat(flags);
2503
2504 let sendData = new Uint8Array(data.length + deflatedText.length);
2505 sendData.set(data);
2506 sendData.set(deflatedText, data.length);
2507
2508 const spy = sinon.spy();
2509 client.addEventListener("clipboard", spy);
2510
2511 client._sock._websocket._receive_data(sendData);
2512 expect(spy).to.have.been.calledOnce;
2513 expect(spy.args[0][0].detail.text).to.equal(expectedData);
2514 client.removeEventListener("clipboard", spy);
2515 });
2516
2517 it('should be able to handle large Provide messages', function () {
2518 // repeat() is not supported in IE so a loop is needed instead
2519 let expectedData = "hello";
2520 for (let i = 1; i <= 100000; i++) {
2521 expectedData += "hello";
2522 }
2523
2524 let data = [3, 0, 0, 0];
2525 const flags = [0x10, 0x00, 0x00, 0x01];
2526
2527 let text = encodeUTF8(expectedData + "\0");
2528
2529 let deflatedText = deflateWithSize(text);
2530
2531 // How much data we are sending.
2532 push32(data, toUnsigned32bit(-(4 + deflatedText.length)));
f73fdc3e
NL
2533
2534 data = data.concat(flags);
2535
2536 let sendData = new Uint8Array(data.length + deflatedText.length);
2537 sendData.set(data);
2538 sendData.set(deflatedText, data.length);
2539
2540 const spy = sinon.spy();
2541 client.addEventListener("clipboard", spy);
2542
2543 client._sock._websocket._receive_data(sendData);
2544 expect(spy).to.have.been.calledOnce;
2545 expect(spy.args[0][0].detail.text).to.equal(expectedData);
2546 client.removeEventListener("clipboard", spy);
2547 });
2548
2549 });
2550
2551 describe('Handle Notify', function () {
2552 beforeEach(function () {
2553 sinon.spy(RFB.messages, 'extendedClipboardRequest');
2554 });
2555
2556 afterEach(function () {
2557 RFB.messages.extendedClipboardRequest.restore();
2558 });
2559
2560 it('should make a request with supported formats when receiving a notify message', function () {
2561 let data = [3, 0, 0, 0];
2562 const flags = [0x08, 0x00, 0x00, 0x07];
2563 push32(data, toUnsigned32bit(-4));
2564 data = data.concat(flags);
2565 let expectedData = [0x01];
2566
2567 client._sock._websocket._receive_data(new Uint8Array(data));
2568
2569 expect(RFB.messages.extendedClipboardRequest).to.have.been.calledOnce;
2570 expect(RFB.messages.extendedClipboardRequest).to.have.been.calledWith(client._sock, expectedData);
2571 });
2572 });
2573
2574 describe('Handle Peek', function () {
2575 beforeEach(function () {
2576 sinon.spy(RFB.messages, 'extendedClipboardNotify');
2577 });
2578
2579 afterEach(function () {
2580 RFB.messages.extendedClipboardNotify.restore();
2581 });
2582
2583 it('should send an empty Notify when receiving a Peek and no excisting clipboard data', function () {
2584 let data = [3, 0, 0, 0];
2585 const flags = [0x04, 0x00, 0x00, 0x00];
2586 push32(data, toUnsigned32bit(-4));
2587 data = data.concat(flags);
2588 let expectedData = [];
2589
2590 client._sock._websocket._receive_data(new Uint8Array(data));
2591
2592 expect(RFB.messages.extendedClipboardNotify).to.have.been.calledOnce;
2593 expect(RFB.messages.extendedClipboardNotify).to.have.been.calledWith(client._sock, expectedData);
2594 });
2595
2596 it('should send a Notify message with supported formats when receiving a Peek', function () {
2597 let data = [3, 0, 0, 0];
2598 const flags = [0x04, 0x00, 0x00, 0x00];
2599 push32(data, toUnsigned32bit(-4));
2600 data = data.concat(flags);
2601 let expectedData = [0x01];
2602
2603 // Needed to have clipboard data to read.
2604 // This will trigger a call to Notify, reset history
2605 client.clipboardPasteFrom("HejHej");
2606 RFB.messages.extendedClipboardNotify.resetHistory();
2607
2608 client._sock._websocket._receive_data(new Uint8Array(data));
2609
2610 expect(RFB.messages.extendedClipboardNotify).to.have.been.calledOnce;
2611 expect(RFB.messages.extendedClipboardNotify).to.have.been.calledWith(client._sock, expectedData);
2612 });
2613 });
2614
2615 describe('Handle Request', function () {
2616 beforeEach(function () {
2617 sinon.spy(RFB.messages, 'extendedClipboardProvide');
2618 });
2619
2620 afterEach(function () {
2621 RFB.messages.extendedClipboardProvide.restore();
2622 });
2623
2624 it('should send a Provide message with supported formats when receiving a Request', function () {
2625 let data = [3, 0, 0, 0];
2626 const flags = [0x02, 0x00, 0x00, 0x01];
2627 push32(data, toUnsigned32bit(-4));
2628 data = data.concat(flags);
2629 let expectedData = [0x01];
2630
2631 client.clipboardPasteFrom("HejHej");
2632 expect(RFB.messages.extendedClipboardProvide).to.not.have.been.called;
2633
2634 client._sock._websocket._receive_data(new Uint8Array(data));
2635
2636 expect(RFB.messages.extendedClipboardProvide).to.have.been.calledOnce;
2637 expect(RFB.messages.extendedClipboardProvide).to.have.been.calledWith(client._sock, expectedData, ["HejHej"]);
2638 });
2639 });
2640 });
b1dee947 2641
b1dee947
SR
2642 });
2643
2644 it('should fire the bell callback on Bell', function () {
2b5f94fa 2645 const spy = sinon.spy();
e89eef94 2646 client.addEventListener("bell", spy);
b1dee947 2647 client._sock._websocket._receive_data(new Uint8Array([2]));
e89eef94 2648 expect(spy).to.have.been.calledOnce;
b1dee947
SR
2649 });
2650
3df13262 2651 it('should respond correctly to ServerFence', function () {
0e4808bf
JD
2652 const expected_msg = {_sQ: new Uint8Array(16), _sQlen: 0, flush: () => {}};
2653 const incoming_msg = {_sQ: new Uint8Array(16), _sQlen: 0, flush: () => {}};
3df13262 2654
2b5f94fa 2655 const payload = "foo\x00ab9";
3df13262 2656
2657 // ClientFence and ServerFence are identical in structure
2658 RFB.messages.clientFence(expected_msg, (1<<0) | (1<<1), payload);
2659 RFB.messages.clientFence(incoming_msg, 0xffffffff, payload);
2660
2661 client._sock._websocket._receive_data(incoming_msg._sQ);
2662
2663 expect(client._sock).to.have.sent(expected_msg._sQ);
2664
2665 expected_msg._sQlen = 0;
2666 incoming_msg._sQlen = 0;
2667
2668 RFB.messages.clientFence(expected_msg, (1<<0), payload);
2669 RFB.messages.clientFence(incoming_msg, (1<<0) | (1<<31), payload);
2670
2671 client._sock._websocket._receive_data(incoming_msg._sQ);
2672
2673 expect(client._sock).to.have.sent(expected_msg._sQ);
2674 });
2675
76a86ff5 2676 it('should enable continuous updates on first EndOfContinousUpdates', function () {
0e4808bf 2677 const expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: () => {}};
76a86ff5 2678
2679 RFB.messages.enableContinuousUpdates(expected_msg, true, 0, 0, 640, 20);
2680
2681 expect(client._enabledContinuousUpdates).to.be.false;
2682
2683 client._sock._websocket._receive_data(new Uint8Array([150]));
2684
2685 expect(client._enabledContinuousUpdates).to.be.true;
2686 expect(client._sock).to.have.sent(expected_msg._sQ);
2687 });
2688
2689 it('should disable continuous updates on subsequent EndOfContinousUpdates', function () {
2690 client._enabledContinuousUpdates = true;
2691 client._supportsContinuousUpdates = true;
2692
2693 client._sock._websocket._receive_data(new Uint8Array([150]));
2694
2695 expect(client._enabledContinuousUpdates).to.be.false;
2696 });
2697
2698 it('should update continuous updates on resize', function () {
0e4808bf 2699 const expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: () => {}};
76a86ff5 2700 RFB.messages.enableContinuousUpdates(expected_msg, true, 0, 0, 90, 700);
2701
91d5c625 2702 client._resize(450, 160);
76a86ff5 2703
2704 expect(client._sock._websocket._get_sent_data()).to.have.length(0);
2705
2706 client._enabledContinuousUpdates = true;
2707
91d5c625 2708 client._resize(90, 700);
76a86ff5 2709
2710 expect(client._sock).to.have.sent(expected_msg._sQ);
2711 });
2712
b1dee947 2713 it('should fail on an unknown message type', function () {
3bb12056 2714 sinon.spy(client, "_fail");
b1dee947 2715 client._sock._websocket._receive_data(new Uint8Array([87]));
3bb12056 2716 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
2717 });
2718 });
2719
2720 describe('Asynchronous Events', function () {
2b5f94fa 2721 let client;
057b8fec
PO
2722 beforeEach(function () {
2723 client = make_rfb();
057b8fec 2724 });
b1dee947 2725
057b8fec 2726 describe('Mouse event handlers', function () {
b1dee947 2727 it('should not send button messages in view-only mode', function () {
747b4623 2728 client._viewOnly = true;
057b8fec 2729 sinon.spy(client._sock, 'flush');
747b4623 2730 client._handleMouseButton(0, 0, 1, 0x001);
9ff86fb7 2731 expect(client._sock.flush).to.not.have.been.called;
b1dee947
SR
2732 });
2733
2734 it('should not send movement messages in view-only mode', function () {
747b4623 2735 client._viewOnly = true;
057b8fec 2736 sinon.spy(client._sock, 'flush');
747b4623 2737 client._handleMouseMove(0, 0);
9ff86fb7 2738 expect(client._sock.flush).to.not.have.been.called;
b1dee947
SR
2739 });
2740
2741 it('should send a pointer event on mouse button presses', function () {
747b4623 2742 client._handleMouseButton(10, 12, 1, 0x001);
0e4808bf 2743 const pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: () => {}};
9ff86fb7
SR
2744 RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x001);
2745 expect(client._sock).to.have.sent(pointer_msg._sQ);
b1dee947
SR
2746 });
2747
d02a99f0 2748 it('should send a mask of 1 on mousedown', function () {
747b4623 2749 client._handleMouseButton(10, 12, 1, 0x001);
0e4808bf 2750 const pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: () => {}};
cf0236de 2751 RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x001);
9ff86fb7 2752 expect(client._sock).to.have.sent(pointer_msg._sQ);
d02a99f0
SR
2753 });
2754
2755 it('should send a mask of 0 on mouseup', function () {
3b4fd003 2756 client._mouse_buttonMask = 0x001;
747b4623 2757 client._handleMouseButton(10, 12, 0, 0x001);
0e4808bf 2758 const pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: () => {}};
9ff86fb7
SR
2759 RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x000);
2760 expect(client._sock).to.have.sent(pointer_msg._sQ);
d02a99f0
SR
2761 });
2762
b1dee947 2763 it('should send a pointer event on mouse movement', function () {
747b4623 2764 client._handleMouseMove(10, 12);
0e4808bf 2765 const pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: () => {}};
9ff86fb7
SR
2766 RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x000);
2767 expect(client._sock).to.have.sent(pointer_msg._sQ);
b1dee947
SR
2768 });
2769
2770 it('should set the button mask so that future mouse movements use it', function () {
747b4623
PO
2771 client._handleMouseButton(10, 12, 1, 0x010);
2772 client._handleMouseMove(13, 9);
0e4808bf 2773 const pointer_msg = {_sQ: new Uint8Array(12), _sQlen: 0, flush: () => {}};
9ff86fb7
SR
2774 RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x010);
2775 RFB.messages.pointerEvent(pointer_msg, 13, 9, 0x010);
2776 expect(client._sock).to.have.sent(pointer_msg._sQ);
b1dee947 2777 });
b1dee947
SR
2778 });
2779
2780 describe('Keyboard Event Handlers', function () {
b1dee947 2781 it('should send a key message on a key press', function () {
747b4623 2782 client._handleKeyEvent(0x41, 'KeyA', true);
0e4808bf 2783 const key_msg = {_sQ: new Uint8Array(8), _sQlen: 0, flush: () => {}};
d0703d1b 2784 RFB.messages.keyEvent(key_msg, 0x41, 1);
9ff86fb7 2785 expect(client._sock).to.have.sent(key_msg._sQ);
b1dee947
SR
2786 });
2787
2788 it('should not send messages in view-only mode', function () {
747b4623 2789 client._viewOnly = true;
057b8fec 2790 sinon.spy(client._sock, 'flush');
747b4623 2791 client._handleKeyEvent('a', 'KeyA', true);
9ff86fb7 2792 expect(client._sock.flush).to.not.have.been.called;
b1dee947
SR
2793 });
2794 });
2795
2796 describe('WebSocket event handlers', function () {
b1dee947 2797 // message events
d80d9d37 2798 it('should do nothing if we receive an empty message and have nothing in the queue', function () {
b1dee947 2799 client._normal_msg = sinon.spy();
38781d93 2800 client._sock._websocket._receive_data(new Uint8Array([]));
b1dee947
SR
2801 expect(client._normal_msg).to.not.have.been.called;
2802 });
2803
c2a4d3ef 2804 it('should handle a message in the connected state as a normal message', function () {
b1dee947 2805 client._normal_msg = sinon.spy();
38781d93 2806 client._sock._websocket._receive_data(new Uint8Array([1, 2, 3]));
70e67958 2807 expect(client._normal_msg).to.have.been.called;
b1dee947
SR
2808 });
2809
2810 it('should handle a message in any non-disconnected/failed state like an init message', function () {
2f4516f2 2811 client._rfb_connection_state = 'connecting';
c00ee156 2812 client._rfb_init_state = 'ProtocolVersion';
b1dee947 2813 client._init_msg = sinon.spy();
38781d93 2814 client._sock._websocket._receive_data(new Uint8Array([1, 2, 3]));
70e67958 2815 expect(client._init_msg).to.have.been.called;
b1dee947
SR
2816 });
2817
9535539b 2818 it('should process all normal messages directly', function () {
2b5f94fa 2819 const spy = sinon.spy();
e89eef94 2820 client.addEventListener("bell", spy);
b1dee947 2821 client._sock._websocket._receive_data(new Uint8Array([0x02, 0x02]));
e89eef94 2822 expect(spy).to.have.been.calledTwice;
b1dee947
SR
2823 });
2824
2825 // open events
c2a4d3ef 2826 it('should update the state to ProtocolVersion on open (if the state is "connecting")', function () {
9b84f516 2827 client = new RFB(document.createElement('div'), 'wss://host:8675');
2f4516f2 2828 this.clock.tick();
b1dee947 2829 client._sock._websocket._open();
c00ee156 2830 expect(client._rfb_init_state).to.equal('ProtocolVersion');
b1dee947
SR
2831 });
2832
2833 it('should fail if we are not currently ready to connect and we get an "open" event', function () {
3bb12056 2834 sinon.spy(client, "_fail");
bb25d3d6 2835 client._rfb_connection_state = 'connected';
b1dee947 2836 client._sock._websocket._open();
3bb12056 2837 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
2838 });
2839
2840 // close events
c2a4d3ef 2841 it('should transition to "disconnected" from "disconnecting" on a close event', function () {
2b5f94fa 2842 const real = client._sock._websocket.close;
651c23ec 2843 client._sock._websocket.close = () => {};
bb25d3d6
PO
2844 client.disconnect();
2845 expect(client._rfb_connection_state).to.equal('disconnecting');
2846 client._sock._websocket.close = real;
b1dee947 2847 client._sock._websocket.close();
c00ee156 2848 expect(client._rfb_connection_state).to.equal('disconnected');
b1dee947
SR
2849 });
2850
b45905ab 2851 it('should fail if we get a close event while connecting', function () {
3bb12056 2852 sinon.spy(client, "_fail");
b45905ab 2853 client._rfb_connection_state = 'connecting';
b1dee947 2854 client._sock._websocket.close();
3bb12056 2855 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
2856 });
2857
155d78b3
JS
2858 it('should unregister close event handler', function () {
2859 sinon.spy(client._sock, 'off');
bb25d3d6 2860 client.disconnect();
155d78b3
JS
2861 client._sock._websocket.close();
2862 expect(client._sock.off).to.have.been.calledWith('close');
2863 });
2864
b1dee947
SR
2865 // error events do nothing
2866 });
efd1f8a4
AT
2867 });
2868
2869 describe('Quality level setting', function () {
2870 const defaultQuality = 6;
2871
2872 let client;
2873
2874 beforeEach(function () {
2875 client = make_rfb();
2876 sinon.spy(RFB.messages, "clientEncodings");
2877 });
2878
2879 afterEach(function () {
2880 RFB.messages.clientEncodings.restore();
2881 });
2882
2883 it(`should equal ${defaultQuality} by default`, function () {
2884 expect(client._qualityLevel).to.equal(defaultQuality);
2885 });
2886
2887 it('should ignore non-integers when set', function () {
2888 client.qualityLevel = '1';
2889 expect(RFB.messages.clientEncodings).to.not.have.been.called;
2890
2891 RFB.messages.clientEncodings.resetHistory();
2892
2893 client.qualityLevel = 1.5;
2894 expect(RFB.messages.clientEncodings).to.not.have.been.called;
2895
2896 RFB.messages.clientEncodings.resetHistory();
2897
2898 client.qualityLevel = null;
2899 expect(RFB.messages.clientEncodings).to.not.have.been.called;
2900
2901 RFB.messages.clientEncodings.resetHistory();
2902
2903 client.qualityLevel = undefined;
2904 expect(RFB.messages.clientEncodings).to.not.have.been.called;
2905
2906 RFB.messages.clientEncodings.resetHistory();
2907
2908 client.qualityLevel = {};
2909 expect(RFB.messages.clientEncodings).to.not.have.been.called;
2910 });
2911
2912 it('should ignore integers out of range [0, 9]', function () {
2913 client.qualityLevel = -1;
2914 expect(RFB.messages.clientEncodings).to.not.have.been.called;
2915
2916 RFB.messages.clientEncodings.resetHistory();
2917
2918 client.qualityLevel = 10;
2919 expect(RFB.messages.clientEncodings).to.not.have.been.called;
2920 });
2921
2922 it('should send clientEncodings with new quality value', function () {
2923 let newQuality;
2924
2925 newQuality = 8;
2926 client.qualityLevel = newQuality;
2927 expect(client.qualityLevel).to.equal(newQuality);
2928 expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
2929 expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.pseudoEncodingQualityLevel0 + newQuality);
2930 });
2931
2932 it('should not send clientEncodings if quality is the same', function () {
2933 let newQuality;
2934
2935 newQuality = 2;
2936 client.qualityLevel = newQuality;
2937 expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
2938 expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.pseudoEncodingQualityLevel0 + newQuality);
2939
2940 RFB.messages.clientEncodings.resetHistory();
2941
2942 client.qualityLevel = newQuality;
2943 expect(RFB.messages.clientEncodings).to.not.have.been.called;
2944 });
2945
2946 it('should not send clientEncodings if not in connected state', function () {
2947 let newQuality;
2948
2949 client._rfb_connection_state = '';
2950 newQuality = 2;
2951 client.qualityLevel = newQuality;
2952 expect(RFB.messages.clientEncodings).to.not.have.been.called;
2953
2954 RFB.messages.clientEncodings.resetHistory();
2955
2956 client._rfb_connection_state = 'connnecting';
2957 newQuality = 6;
2958 client.qualityLevel = newQuality;
2959 expect(RFB.messages.clientEncodings).to.not.have.been.called;
2960
2961 RFB.messages.clientEncodings.resetHistory();
2962
2963 client._rfb_connection_state = 'connected';
2964 newQuality = 5;
2965 client.qualityLevel = newQuality;
2966 expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
2967 expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.pseudoEncodingQualityLevel0 + newQuality);
2968 });
479d8cef
SM
2969 });
2970
2971 describe('Compression level setting', function () {
2972 const defaultCompression = 2;
2973
2974 let client;
2975
2976 beforeEach(function () {
2977 client = make_rfb();
2978 sinon.spy(RFB.messages, "clientEncodings");
2979 });
2980
2981 afterEach(function () {
2982 RFB.messages.clientEncodings.restore();
2983 });
2984
2985 it(`should equal ${defaultCompression} by default`, function () {
2986 expect(client._compressionLevel).to.equal(defaultCompression);
2987 });
2988
2989 it('should ignore non-integers when set', function () {
2990 client.compressionLevel = '1';
2991 expect(RFB.messages.clientEncodings).to.not.have.been.called;
2992
2993 RFB.messages.clientEncodings.resetHistory();
2994
2995 client.compressionLevel = 1.5;
2996 expect(RFB.messages.clientEncodings).to.not.have.been.called;
2997
2998 RFB.messages.clientEncodings.resetHistory();
2999
3000 client.compressionLevel = null;
3001 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3002
3003 RFB.messages.clientEncodings.resetHistory();
3004
3005 client.compressionLevel = undefined;
3006 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3007
3008 RFB.messages.clientEncodings.resetHistory();
3009
3010 client.compressionLevel = {};
3011 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3012 });
3013
3014 it('should ignore integers out of range [0, 9]', function () {
3015 client.compressionLevel = -1;
3016 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3017
3018 RFB.messages.clientEncodings.resetHistory();
3019
3020 client.compressionLevel = 10;
3021 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3022 });
3023
3024 it('should send clientEncodings with new compression value', function () {
3025 let newCompression;
3026
3027 newCompression = 5;
3028 client.compressionLevel = newCompression;
3029 expect(client.compressionLevel).to.equal(newCompression);
3030 expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
3031 expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.pseudoEncodingCompressLevel0 + newCompression);
3032 });
3033
3034 it('should not send clientEncodings if compression is the same', function () {
3035 let newCompression;
3036
3037 newCompression = 9;
3038 client.compressionLevel = newCompression;
3039 expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
3040 expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.pseudoEncodingCompressLevel0 + newCompression);
3041
3042 RFB.messages.clientEncodings.resetHistory();
3043
3044 client.compressionLevel = newCompression;
3045 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3046 });
3047
3048 it('should not send clientEncodings if not in connected state', function () {
3049 let newCompression;
3050
3051 client._rfb_connection_state = '';
3052 newCompression = 7;
3053 client.compressionLevel = newCompression;
3054 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3055
3056 RFB.messages.clientEncodings.resetHistory();
3057
3058 client._rfb_connection_state = 'connnecting';
3059 newCompression = 6;
3060 client.compressionLevel = newCompression;
3061 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3062
3063 RFB.messages.clientEncodings.resetHistory();
3064
3065 client._rfb_connection_state = 'connected';
3066 newCompression = 5;
3067 client.compressionLevel = newCompression;
3068 expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
3069 expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.pseudoEncodingCompressLevel0 + newCompression);
3070 });
b1dee947
SR
3071 });
3072});
f73fdc3e
NL
3073
3074describe('RFB messages', function () {
3075 let sock;
3076
3077 before(function () {
3078 FakeWebSocket.replace();
3079 sock = new Websock();
3080 sock.open();
3081 });
3082
3083 after(function () {
3084 FakeWebSocket.restore();
3085 });
3086
3087 describe('Extended Clipboard Handling Send', function () {
3088 beforeEach(function () {
3089 sinon.spy(RFB.messages, 'clientCutText');
3090 });
3091
3092 afterEach(function () {
3093 RFB.messages.clientCutText.restore();
3094 });
3095
3096 it('should call clientCutText with correct Caps data', function () {
3097 let formats = {
3098 0: 2,
3099 2: 4121
3100 };
3101 let expectedData = new Uint8Array([0x1F, 0x00, 0x00, 0x05,
3102 0x00, 0x00, 0x00, 0x02,
3103 0x00, 0x00, 0x10, 0x19]);
3104 let actions = [
3105 1 << 24, // Caps
3106 1 << 25, // Request
3107 1 << 26, // Peek
3108 1 << 27, // Notify
3109 1 << 28 // Provide
3110 ];
3111
3112 RFB.messages.extendedClipboardCaps(sock, actions, formats);
3113 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
3114 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData);
3115 });
3116
3117 it('should call clientCutText with correct Request data', function () {
3118 let formats = new Uint8Array([0x01]);
3119 let expectedData = new Uint8Array([0x02, 0x00, 0x00, 0x01]);
3120
3121 RFB.messages.extendedClipboardRequest(sock, formats);
3122 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
3123 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData);
3124 });
3125
3126 it('should call clientCutText with correct Notify data', function () {
3127 let formats = new Uint8Array([0x01]);
3128 let expectedData = new Uint8Array([0x08, 0x00, 0x00, 0x01]);
3129
3130 RFB.messages.extendedClipboardNotify(sock, formats);
3131 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
3132 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData);
3133 });
3134
3135 it('should call clientCutText with correct Provide data', function () {
3136 let testText = "Test string";
3137 let expectedText = encodeUTF8(testText + "\0");
3138
3139 let deflatedData = deflateWithSize(expectedText);
3140
3141 // Build Expected with flags and deflated data
3142 let expectedData = new Uint8Array(4 + deflatedData.length);
3143 expectedData[0] = 0x10; // The client capabilities
3144 expectedData[1] = 0x00; // Reserved flags
3145 expectedData[2] = 0x00; // Reserved flags
3146 expectedData[3] = 0x01; // The formats client supports
3147 expectedData.set(deflatedData, 4);
3148
3149 RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);
3150 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
3151 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData, true);
3152
3153 });
3154
3155 describe('End of line characters', function () {
3156 it('Carriage return', function () {
3157
3158 let testText = "Hello\rworld\r\r!";
3159 let expectedText = encodeUTF8("Hello\r\nworld\r\n\r\n!\0");
3160
3161 let deflatedData = deflateWithSize(expectedText);
3162
3163 // Build Expected with flags and deflated data
3164 let expectedData = new Uint8Array(4 + deflatedData.length);
3165 expectedData[0] = 0x10; // The client capabilities
3166 expectedData[1] = 0x00; // Reserved flags
3167 expectedData[2] = 0x00; // Reserved flags
3168 expectedData[3] = 0x01; // The formats client supports
3169 expectedData.set(deflatedData, 4);
3170
3171 RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);
3172 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
3173 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData, true);
3174 });
3175
3176 it('Carriage return Line feed', function () {
3177
3178 let testText = "Hello\r\n\r\nworld\r\n!";
3179 let expectedText = encodeUTF8(testText + "\0");
3180
3181 let deflatedData = deflateWithSize(expectedText);
3182
3183 // Build Expected with flags and deflated data
3184 let expectedData = new Uint8Array(4 + deflatedData.length);
3185 expectedData[0] = 0x10; // The client capabilities
3186 expectedData[1] = 0x00; // Reserved flags
3187 expectedData[2] = 0x00; // Reserved flags
3188 expectedData[3] = 0x01; // The formats client supports
3189 expectedData.set(deflatedData, 4);
3190
3191 RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);
3192 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
3193 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData, true);
3194 });
3195
3196 it('Line feed', function () {
3197 let testText = "Hello\n\n\nworld\n!";
3198 let expectedText = encodeUTF8("Hello\r\n\r\n\r\nworld\r\n!\0");
3199
3200 let deflatedData = deflateWithSize(expectedText);
3201
3202 // Build Expected with flags and deflated data
3203 let expectedData = new Uint8Array(4 + deflatedData.length);
3204 expectedData[0] = 0x10; // The client capabilities
3205 expectedData[1] = 0x00; // Reserved flags
3206 expectedData[2] = 0x00; // Reserved flags
3207 expectedData[3] = 0x01; // The formats client supports
3208 expectedData.set(deflatedData, 4);
3209
3210 RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);
3211 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
3212 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData, true);
3213 });
3214
3215 it('Carriage return and Line feed mixed', function () {
3216 let testText = "\rHello\r\n\rworld\n\n!";
3217 let expectedText = encodeUTF8("\r\nHello\r\n\r\nworld\r\n\r\n!\0");
3218
3219 let deflatedData = deflateWithSize(expectedText);
3220
3221 // Build Expected with flags and deflated data
3222 let expectedData = new Uint8Array(4 + deflatedData.length);
3223 expectedData[0] = 0x10; // The client capabilities
3224 expectedData[1] = 0x00; // Reserved flags
3225 expectedData[2] = 0x00; // Reserved flags
3226 expectedData[3] = 0x01; // The formats client supports
3227 expectedData.set(deflatedData, 4);
3228
3229 RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);
3230 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
3231 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData, true);
3232 });
3233 });
3234 });
3235});