]> git.proxmox.com Git - mirror_novnc.git/blame - tests/test.rfb.js
Make afterEach() hooks work when skipping tests
[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';
69411b9e 5import { encodings } from '../core/encodings.js';
dfae3209
SR
6
7import FakeWebSocket from './fake.websocket.js';
dfae3209 8
9b84f516 9/* UIEvent constructor polyfill for IE */
651c23ec 10(() => {
9b84f516
PO
11 if (typeof window.UIEvent === "function") return;
12
2c5491e1 13 function UIEvent( event, params ) {
9b84f516 14 params = params || { bubbles: false, cancelable: false, view: window, detail: undefined };
2b5f94fa 15 const evt = document.createEvent( 'UIEvent' );
9b84f516
PO
16 evt.initUIEvent( event, params.bubbles, params.cancelable, params.view, params.detail );
17 return evt;
18 }
19
20 UIEvent.prototype = window.UIEvent.prototype;
21
22 window.UIEvent = UIEvent;
23})();
24
885363a3 25function push8(arr, num) {
3949a095
SR
26 "use strict";
27 arr.push(num & 0xFF);
885363a3 28}
3949a095 29
885363a3 30function push16(arr, num) {
3949a095
SR
31 "use strict";
32 arr.push((num >> 8) & 0xFF,
7b536961 33 num & 0xFF);
885363a3 34}
3949a095 35
885363a3 36function push32(arr, num) {
3949a095
SR
37 "use strict";
38 arr.push((num >> 24) & 0xFF,
7b536961
PO
39 (num >> 16) & 0xFF,
40 (num >> 8) & 0xFF,
41 num & 0xFF);
885363a3 42}
3949a095 43
ce66b469
NL
44function pushString(arr, string) {
45 let utf8 = unescape(encodeURIComponent(string));
46 for (let i = 0; i < utf8.length; i++) {
47 arr.push(utf8.charCodeAt(i));
48 }
49}
50
2c5491e1 51describe('Remote Frame Buffer Protocol Client', function () {
2b5f94fa
JD
52 let clock;
53 let raf;
2f4516f2 54
b1dee947
SR
55 before(FakeWebSocket.replace);
56 after(FakeWebSocket.restore);
57
38781d93 58 before(function () {
2f4516f2 59 this.clock = clock = sinon.useFakeTimers();
9b84f516
PO
60 // sinon doesn't support this yet
61 raf = window.requestAnimationFrame;
62 window.requestAnimationFrame = setTimeout;
38781d93
SR
63 // Use a single set of buffers instead of reallocating to
64 // speed up tests
2b5f94fa
JD
65 const sock = new Websock();
66 const _sQ = new Uint8Array(sock._sQbufferSize);
67 const rQ = new Uint8Array(sock._rQbufferSize);
38781d93
SR
68
69 Websock.prototype._old_allocate_buffers = Websock.prototype._allocate_buffers;
70 Websock.prototype._allocate_buffers = function () {
9ff86fb7 71 this._sQ = _sQ;
38781d93
SR
72 this._rQ = rQ;
73 };
74
75 });
76
77 after(function () {
78 Websock.prototype._allocate_buffers = Websock.prototype._old_allocate_buffers;
79 this.clock.restore();
9b84f516 80 window.requestAnimationFrame = raf;
38781d93
SR
81 });
82
2b5f94fa
JD
83 let container;
84 let rfbs;
bb25d3d6
PO
85
86 beforeEach(function () {
9b84f516
PO
87 // Create a container element for all RFB objects to attach to
88 container = document.createElement('div');
89 container.style.width = "100%";
90 container.style.height = "100%";
91 document.body.appendChild(container);
92
93 // And track all created RFB objects
bb25d3d6
PO
94 rfbs = [];
95 });
96 afterEach(function () {
97 // Make sure every created RFB object is properly cleaned up
98 // or they might affect subsequent tests
99 rfbs.forEach(function (rfb) {
100 rfb.disconnect();
101 expect(rfb._disconnect).to.have.been.called;
102 });
103 rfbs = [];
9b84f516
PO
104
105 document.body.removeChild(container);
106 container = null;
bb25d3d6
PO
107 });
108
2c5491e1 109 function make_rfb(url, options) {
2f4516f2 110 url = url || 'wss://host:8675';
2b5f94fa 111 const rfb = new RFB(container, url, options);
2f4516f2
PO
112 clock.tick();
113 rfb._sock._websocket._open();
114 rfb._rfb_connection_state = 'connected';
bb25d3d6
PO
115 sinon.spy(rfb, "_disconnect");
116 rfbs.push(rfb);
2f4516f2
PO
117 return rfb;
118 }
b1dee947 119
2f4516f2
PO
120 describe('Connecting/Disconnecting', function () {
121 describe('#RFB', function () {
c2a4d3ef 122 it('should set the current state to "connecting"', function () {
2b5f94fa 123 const client = new RFB(document.createElement('div'), 'wss://host:8675');
ee5cae9f 124 client._rfb_connection_state = '';
2f4516f2 125 this.clock.tick();
ee5cae9f 126 expect(client._rfb_connection_state).to.equal('connecting');
b1dee947
SR
127 });
128
2f4516f2 129 it('should actually connect to the websocket', function () {
2b5f94fa 130 const client = new RFB(document.createElement('div'), 'ws://HOST:8675/PATH');
2f4516f2
PO
131 sinon.spy(client._sock, 'open');
132 this.clock.tick();
133 expect(client._sock.open).to.have.been.calledOnce;
134 expect(client._sock.open).to.have.been.calledWith('ws://HOST:8675/PATH');
b1dee947 135 });
b1dee947
SR
136 });
137
138 describe('#disconnect', function () {
2b5f94fa 139 let client;
2f4516f2
PO
140 beforeEach(function () {
141 client = make_rfb();
142 });
b1dee947 143
ee5cae9f
SM
144 it('should go to state "disconnecting" before "disconnected"', function () {
145 sinon.spy(client, '_updateConnectionState');
b1dee947 146 client.disconnect();
ee5cae9f
SM
147 expect(client._updateConnectionState).to.have.been.calledTwice;
148 expect(client._updateConnectionState.getCall(0).args[0])
149 .to.equal('disconnecting');
150 expect(client._updateConnectionState.getCall(1).args[0])
151 .to.equal('disconnected');
152 expect(client._rfb_connection_state).to.equal('disconnected');
b1dee947 153 });
155d78b3
JS
154
155 it('should unregister error event handler', function () {
156 sinon.spy(client._sock, 'off');
157 client.disconnect();
158 expect(client._sock.off).to.have.been.calledWith('error');
159 });
160
161 it('should unregister message event handler', function () {
162 sinon.spy(client._sock, 'off');
163 client.disconnect();
164 expect(client._sock.off).to.have.been.calledWith('message');
165 });
166
167 it('should unregister open event handler', function () {
168 sinon.spy(client._sock, 'off');
169 client.disconnect();
170 expect(client._sock.off).to.have.been.calledWith('open');
171 });
b1dee947
SR
172 });
173
430f00d6 174 describe('#sendCredentials', function () {
2b5f94fa 175 let client;
2f4516f2
PO
176 beforeEach(function () {
177 client = make_rfb();
178 client._rfb_connection_state = 'connecting';
179 });
180
430f00d6
PO
181 it('should set the rfb credentials properly"', function () {
182 client.sendCredentials({ password: 'pass' });
183 expect(client._rfb_credentials).to.deep.equal({ password: 'pass' });
b1dee947
SR
184 });
185
186 it('should call init_msg "soon"', function () {
187 client._init_msg = sinon.spy();
430f00d6 188 client.sendCredentials({ password: 'pass' });
b1dee947
SR
189 this.clock.tick(5);
190 expect(client._init_msg).to.have.been.calledOnce;
191 });
192 });
057b8fec 193 });
b1dee947 194
057b8fec 195 describe('Public API Basic Behavior', function () {
2b5f94fa 196 let client;
057b8fec
PO
197 beforeEach(function () {
198 client = make_rfb();
057b8fec 199 });
b1dee947 200
057b8fec 201 describe('#sendCtrlAlDel', function () {
b1dee947 202 it('should sent ctrl[down]-alt[down]-del[down] then del[up]-alt[up]-ctrl[up]', function () {
0e4808bf 203 const expected = {_sQ: new Uint8Array(48), _sQlen: 0, flush: () => {}};
9ff86fb7
SR
204 RFB.messages.keyEvent(expected, 0xFFE3, 1);
205 RFB.messages.keyEvent(expected, 0xFFE9, 1);
206 RFB.messages.keyEvent(expected, 0xFFFF, 1);
207 RFB.messages.keyEvent(expected, 0xFFFF, 0);
208 RFB.messages.keyEvent(expected, 0xFFE9, 0);
209 RFB.messages.keyEvent(expected, 0xFFE3, 0);
b1dee947
SR
210
211 client.sendCtrlAltDel();
9ff86fb7 212 expect(client._sock).to.have.sent(expected._sQ);
b1dee947
SR
213 });
214
215 it('should not send the keys if we are not in a normal state', function () {
057b8fec 216 sinon.spy(client._sock, 'flush');
bb25d3d6 217 client._rfb_connection_state = "connecting";
b1dee947 218 client.sendCtrlAltDel();
9ff86fb7 219 expect(client._sock.flush).to.not.have.been.called;
b1dee947
SR
220 });
221
222 it('should not send the keys if we are set as view_only', function () {
057b8fec 223 sinon.spy(client._sock, 'flush');
747b4623 224 client._viewOnly = true;
b1dee947 225 client.sendCtrlAltDel();
9ff86fb7 226 expect(client._sock.flush).to.not.have.been.called;
b1dee947
SR
227 });
228 });
229
230 describe('#sendKey', function () {
b1dee947 231 it('should send a single key with the given code and state (down = true)', function () {
0e4808bf 232 const expected = {_sQ: new Uint8Array(8), _sQlen: 0, flush: () => {}};
9ff86fb7 233 RFB.messages.keyEvent(expected, 123, 1);
94f5cf05 234 client.sendKey(123, 'Key123', true);
9ff86fb7 235 expect(client._sock).to.have.sent(expected._sQ);
b1dee947
SR
236 });
237
238 it('should send both a down and up event if the state is not specified', function () {
0e4808bf 239 const expected = {_sQ: new Uint8Array(16), _sQlen: 0, flush: () => {}};
9ff86fb7
SR
240 RFB.messages.keyEvent(expected, 123, 1);
241 RFB.messages.keyEvent(expected, 123, 0);
94f5cf05 242 client.sendKey(123, 'Key123');
9ff86fb7 243 expect(client._sock).to.have.sent(expected._sQ);
b1dee947
SR
244 });
245
246 it('should not send the key if we are not in a normal state', function () {
057b8fec 247 sinon.spy(client._sock, 'flush');
bb25d3d6 248 client._rfb_connection_state = "connecting";
94f5cf05 249 client.sendKey(123, 'Key123');
9ff86fb7 250 expect(client._sock.flush).to.not.have.been.called;
b1dee947
SR
251 });
252
253 it('should not send the key if we are set as view_only', function () {
057b8fec 254 sinon.spy(client._sock, 'flush');
747b4623 255 client._viewOnly = true;
94f5cf05 256 client.sendKey(123, 'Key123');
9ff86fb7 257 expect(client._sock.flush).to.not.have.been.called;
b1dee947 258 });
94f5cf05
PO
259
260 it('should send QEMU extended events if supported', function () {
261 client._qemuExtKeyEventSupported = true;
0e4808bf 262 const expected = {_sQ: new Uint8Array(12), _sQlen: 0, flush: () => {}};
94f5cf05
PO
263 RFB.messages.QEMUExtendedKeyEvent(expected, 0x20, true, 0x0039);
264 client.sendKey(0x20, 'Space', true);
265 expect(client._sock).to.have.sent(expected._sQ);
266 });
be70fe0a
PO
267
268 it('should not send QEMU extended events if unknown key code', function () {
269 client._qemuExtKeyEventSupported = true;
0e4808bf 270 const expected = {_sQ: new Uint8Array(8), _sQlen: 0, flush: () => {}};
be70fe0a
PO
271 RFB.messages.keyEvent(expected, 123, 1);
272 client.sendKey(123, 'FooBar', true);
273 expect(client._sock).to.have.sent(expected._sQ);
274 });
b1dee947
SR
275 });
276
9b84f516
PO
277 describe('#focus', function () {
278 it('should move focus to canvas object', function () {
279 client._canvas.focus = sinon.spy();
280 client.focus();
b875486d 281 expect(client._canvas.focus).to.have.been.calledOnce;
9b84f516
PO
282 });
283 });
284
285 describe('#blur', function () {
286 it('should remove focus from canvas object', function () {
287 client._canvas.blur = sinon.spy();
288 client.blur();
b875486d 289 expect(client._canvas.blur).to.have.been.calledOnce;
9b84f516
PO
290 });
291 });
292
b1dee947 293 describe('#clipboardPasteFrom', function () {
b1dee947 294 it('should send the given text in a paste event', function () {
2b5f94fa 295 const expected = {_sQ: new Uint8Array(11), _sQlen: 0,
7b536961 296 _sQbufferSize: 11, flush: () => {}};
9ff86fb7 297 RFB.messages.clientCutText(expected, 'abc');
b1dee947 298 client.clipboardPasteFrom('abc');
9ff86fb7 299 expect(client._sock).to.have.sent(expected._sQ);
b1dee947
SR
300 });
301
2bb8b28d
SM
302 it('should flush multiple times for large clipboards', function () {
303 sinon.spy(client._sock, 'flush');
304 let long_text = "";
305 for (let i = 0; i < client._sock._sQbufferSize + 100; i++) {
306 long_text += 'a';
307 }
308 client.clipboardPasteFrom(long_text);
309 expect(client._sock.flush).to.have.been.calledTwice;
310 });
311
b1dee947 312 it('should not send the text if we are not in a normal state', function () {
057b8fec 313 sinon.spy(client._sock, 'flush');
bb25d3d6 314 client._rfb_connection_state = "connecting";
b1dee947 315 client.clipboardPasteFrom('abc');
9ff86fb7 316 expect(client._sock.flush).to.not.have.been.called;
b1dee947
SR
317 });
318 });
319
320 describe("XVP operations", function () {
321 beforeEach(function () {
b1dee947
SR
322 client._rfb_xvp_ver = 1;
323 });
324
cd523e8f
PO
325 it('should send the shutdown signal on #machineShutdown', function () {
326 client.machineShutdown();
9ff86fb7 327 expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x02]));
b1dee947
SR
328 });
329
cd523e8f
PO
330 it('should send the reboot signal on #machineReboot', function () {
331 client.machineReboot();
9ff86fb7 332 expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x03]));
b1dee947
SR
333 });
334
cd523e8f
PO
335 it('should send the reset signal on #machineReset', function () {
336 client.machineReset();
9ff86fb7 337 expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x04]));
b1dee947
SR
338 });
339
b1dee947 340 it('should not send XVP operations with higher versions than we support', function () {
057b8fec 341 sinon.spy(client._sock, 'flush');
cd523e8f 342 client._xvpOp(2, 7);
9ff86fb7 343 expect(client._sock.flush).to.not.have.been.called;
b1dee947
SR
344 });
345 });
346 });
347
9b84f516 348 describe('Clipping', function () {
2b5f94fa 349 let client;
9b84f516
PO
350 beforeEach(function () {
351 client = make_rfb();
352 container.style.width = '70px';
353 container.style.height = '80px';
354 client.clipViewport = true;
355 });
356
357 it('should update display clip state when changing the property', function () {
2b5f94fa 358 const spy = sinon.spy(client._display, "clipViewport", ["set"]);
9b84f516
PO
359
360 client.clipViewport = false;
361 expect(spy.set).to.have.been.calledOnce;
362 expect(spy.set).to.have.been.calledWith(false);
c9765e50 363 spy.set.resetHistory();
9b84f516
PO
364
365 client.clipViewport = true;
366 expect(spy.set).to.have.been.calledOnce;
367 expect(spy.set).to.have.been.calledWith(true);
368 });
369
370 it('should update the viewport when the container size changes', function () {
371 sinon.spy(client._display, "viewportChangeSize");
372
373 container.style.width = '40px';
374 container.style.height = '50px';
2b5f94fa 375 const event = new UIEvent('resize');
9b84f516
PO
376 window.dispatchEvent(event);
377 clock.tick();
378
379 expect(client._display.viewportChangeSize).to.have.been.calledOnce;
380 expect(client._display.viewportChangeSize).to.have.been.calledWith(40, 50);
381 });
382
383 it('should update the viewport when the remote session resizes', function () {
384 // Simple ExtendedDesktopSize FBU message
2b5f94fa 385 const incoming = [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
7b536961
PO
386 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0xfe, 0xcc,
387 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
388 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff,
389 0x00, 0x00, 0x00, 0x00 ];
9b84f516
PO
390
391 sinon.spy(client._display, "viewportChangeSize");
392
393 client._sock._websocket._receive_data(new Uint8Array(incoming));
394
395 // FIXME: Display implicitly calls viewportChangeSize() when
396 // resizing the framebuffer, hence calledTwice.
397 expect(client._display.viewportChangeSize).to.have.been.calledTwice;
398 expect(client._display.viewportChangeSize).to.have.been.calledWith(70, 80);
399 });
400
401 it('should not update the viewport if not clipping', function () {
402 client.clipViewport = false;
403 sinon.spy(client._display, "viewportChangeSize");
404
405 container.style.width = '40px';
406 container.style.height = '50px';
2b5f94fa 407 const event = new UIEvent('resize');
9b84f516
PO
408 window.dispatchEvent(event);
409 clock.tick();
410
411 expect(client._display.viewportChangeSize).to.not.have.been.called;
412 });
413
414 it('should not update the viewport if scaling', function () {
415 client.scaleViewport = true;
416 sinon.spy(client._display, "viewportChangeSize");
417
418 container.style.width = '40px';
419 container.style.height = '50px';
2b5f94fa 420 const event = new UIEvent('resize');
9b84f516
PO
421 window.dispatchEvent(event);
422 clock.tick();
423
424 expect(client._display.viewportChangeSize).to.not.have.been.called;
425 });
426
427 describe('Dragging', function () {
428 beforeEach(function () {
429 client.dragViewport = true;
430 sinon.spy(RFB.messages, "pointerEvent");
431 });
432
433 afterEach(function () {
434 RFB.messages.pointerEvent.restore();
435 });
436
437 it('should not send button messages when initiating viewport dragging', function () {
438 client._handleMouseButton(13, 9, 0x001);
439 expect(RFB.messages.pointerEvent).to.not.have.been.called;
440 });
441
442 it('should send button messages when release without movement', function () {
443 // Just up and down
444 client._handleMouseButton(13, 9, 0x001);
445 client._handleMouseButton(13, 9, 0x000);
446 expect(RFB.messages.pointerEvent).to.have.been.calledTwice;
447
c9765e50 448 RFB.messages.pointerEvent.resetHistory();
9b84f516
PO
449
450 // Small movement
451 client._handleMouseButton(13, 9, 0x001);
452 client._handleMouseMove(15, 14);
453 client._handleMouseButton(15, 14, 0x000);
454 expect(RFB.messages.pointerEvent).to.have.been.calledTwice;
455 });
456
457 it('should send button message directly when drag is disabled', function () {
458 client.dragViewport = false;
459 client._handleMouseButton(13, 9, 0x001);
460 expect(RFB.messages.pointerEvent).to.have.been.calledOnce;
461 });
462
463 it('should be initiate viewport dragging on sufficient movement', function () {
464 sinon.spy(client._display, "viewportChangePos");
465
466 // Too small movement
467
468 client._handleMouseButton(13, 9, 0x001);
469 client._handleMouseMove(18, 9);
470
471 expect(RFB.messages.pointerEvent).to.not.have.been.called;
472 expect(client._display.viewportChangePos).to.not.have.been.called;
473
474 // Sufficient movement
475
476 client._handleMouseMove(43, 9);
477
478 expect(RFB.messages.pointerEvent).to.not.have.been.called;
479 expect(client._display.viewportChangePos).to.have.been.calledOnce;
480 expect(client._display.viewportChangePos).to.have.been.calledWith(-30, 0);
481
c9765e50 482 client._display.viewportChangePos.resetHistory();
9b84f516
PO
483
484 // Now a small movement should move right away
485
486 client._handleMouseMove(43, 14);
487
488 expect(RFB.messages.pointerEvent).to.not.have.been.called;
489 expect(client._display.viewportChangePos).to.have.been.calledOnce;
490 expect(client._display.viewportChangePos).to.have.been.calledWith(0, -5);
491 });
492
493 it('should not send button messages when dragging ends', function () {
494 // First the movement
495
496 client._handleMouseButton(13, 9, 0x001);
497 client._handleMouseMove(43, 9);
498 client._handleMouseButton(43, 9, 0x000);
499
500 expect(RFB.messages.pointerEvent).to.not.have.been.called;
501 });
502
503 it('should terminate viewport dragging on a button up event', function () {
504 // First the dragging movement
505
506 client._handleMouseButton(13, 9, 0x001);
507 client._handleMouseMove(43, 9);
508 client._handleMouseButton(43, 9, 0x000);
509
510 // Another movement now should not move the viewport
511
512 sinon.spy(client._display, "viewportChangePos");
513
514 client._handleMouseMove(43, 59);
515
516 expect(client._display.viewportChangePos).to.not.have.been.called;
517 });
518 });
519 });
520
521 describe('Scaling', function () {
2b5f94fa 522 let client;
9b84f516
PO
523 beforeEach(function () {
524 client = make_rfb();
525 container.style.width = '70px';
526 container.style.height = '80px';
527 client.scaleViewport = true;
528 });
529
530 it('should update display scale factor when changing the property', function () {
2b5f94fa 531 const spy = sinon.spy(client._display, "scale", ["set"]);
9b84f516
PO
532 sinon.spy(client._display, "autoscale");
533
534 client.scaleViewport = false;
535 expect(spy.set).to.have.been.calledOnce;
536 expect(spy.set).to.have.been.calledWith(1.0);
537 expect(client._display.autoscale).to.not.have.been.called;
538
539 client.scaleViewport = true;
540 expect(client._display.autoscale).to.have.been.calledOnce;
541 expect(client._display.autoscale).to.have.been.calledWith(70, 80);
542 });
543
544 it('should update the clipping setting when changing the property', function () {
545 client.clipViewport = true;
546
2b5f94fa 547 const spy = sinon.spy(client._display, "clipViewport", ["set"]);
9b84f516
PO
548
549 client.scaleViewport = false;
550 expect(spy.set).to.have.been.calledOnce;
551 expect(spy.set).to.have.been.calledWith(true);
552
c9765e50 553 spy.set.resetHistory();
9b84f516
PO
554
555 client.scaleViewport = true;
556 expect(spy.set).to.have.been.calledOnce;
557 expect(spy.set).to.have.been.calledWith(false);
558 });
559
560 it('should update the scaling when the container size changes', function () {
561 sinon.spy(client._display, "autoscale");
562
563 container.style.width = '40px';
564 container.style.height = '50px';
2b5f94fa 565 const event = new UIEvent('resize');
9b84f516
PO
566 window.dispatchEvent(event);
567 clock.tick();
568
569 expect(client._display.autoscale).to.have.been.calledOnce;
570 expect(client._display.autoscale).to.have.been.calledWith(40, 50);
571 });
572
573 it('should update the scaling when the remote session resizes', function () {
574 // Simple ExtendedDesktopSize FBU message
2b5f94fa 575 const incoming = [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
7b536961
PO
576 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0xfe, 0xcc,
577 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
578 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff,
579 0x00, 0x00, 0x00, 0x00 ];
9b84f516
PO
580
581 sinon.spy(client._display, "autoscale");
582
583 client._sock._websocket._receive_data(new Uint8Array(incoming));
584
585 expect(client._display.autoscale).to.have.been.calledOnce;
586 expect(client._display.autoscale).to.have.been.calledWith(70, 80);
587 });
588
589 it('should not update the display scale factor if not scaling', function () {
590 client.scaleViewport = false;
591
592 sinon.spy(client._display, "autoscale");
593
594 container.style.width = '40px';
595 container.style.height = '50px';
2b5f94fa 596 const event = new UIEvent('resize');
9b84f516
PO
597 window.dispatchEvent(event);
598 clock.tick();
599
600 expect(client._display.autoscale).to.not.have.been.called;
601 });
602 });
603
604 describe('Remote resize', function () {
2b5f94fa 605 let client;
9b84f516
PO
606 beforeEach(function () {
607 client = make_rfb();
608 client._supportsSetDesktopSize = true;
609 client.resizeSession = true;
610 container.style.width = '70px';
611 container.style.height = '80px';
612 sinon.spy(RFB.messages, "setDesktopSize");
613 });
614
615 afterEach(function () {
616 RFB.messages.setDesktopSize.restore();
617 });
618
619 it('should only request a resize when turned on', function () {
620 client.resizeSession = false;
621 expect(RFB.messages.setDesktopSize).to.not.have.been.called;
622 client.resizeSession = true;
623 expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;
624 });
625
626 it('should request a resize when initially connecting', function () {
627 // Simple ExtendedDesktopSize FBU message
2b5f94fa 628 const incoming = [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
7b536961
PO
629 0x00, 0x04, 0x00, 0x04, 0xff, 0xff, 0xfe, 0xcc,
630 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
631 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04,
632 0x00, 0x00, 0x00, 0x00 ];
9b84f516
PO
633
634 // First message should trigger a resize
635
636 client._supportsSetDesktopSize = false;
637
638 client._sock._websocket._receive_data(new Uint8Array(incoming));
639
640 expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;
641 expect(RFB.messages.setDesktopSize).to.have.been.calledWith(sinon.match.object, 70, 80, 0, 0);
642
c9765e50 643 RFB.messages.setDesktopSize.resetHistory();
9b84f516
PO
644
645 // Second message should not trigger a resize
646
647 client._sock._websocket._receive_data(new Uint8Array(incoming));
648
649 expect(RFB.messages.setDesktopSize).to.not.have.been.called;
650 });
651
652 it('should request a resize when the container resizes', function () {
653 container.style.width = '40px';
654 container.style.height = '50px';
2b5f94fa 655 const event = new UIEvent('resize');
9b84f516
PO
656 window.dispatchEvent(event);
657 clock.tick(1000);
658
659 expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;
660 expect(RFB.messages.setDesktopSize).to.have.been.calledWith(sinon.match.object, 40, 50, 0, 0);
661 });
662
663 it('should not resize until the container size is stable', function () {
664 container.style.width = '20px';
665 container.style.height = '30px';
2b5f94fa 666 const event1 = new UIEvent('resize');
8727f598 667 window.dispatchEvent(event1);
9b84f516
PO
668 clock.tick(400);
669
670 expect(RFB.messages.setDesktopSize).to.not.have.been.called;
671
672 container.style.width = '40px';
673 container.style.height = '50px';
2b5f94fa 674 const event2 = new UIEvent('resize');
8727f598 675 window.dispatchEvent(event2);
9b84f516
PO
676 clock.tick(400);
677
678 expect(RFB.messages.setDesktopSize).to.not.have.been.called;
679
680 clock.tick(200);
681
682 expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;
683 expect(RFB.messages.setDesktopSize).to.have.been.calledWith(sinon.match.object, 40, 50, 0, 0);
684 });
685
686 it('should not resize when resize is disabled', function () {
687 client._resizeSession = false;
688
689 container.style.width = '40px';
690 container.style.height = '50px';
2b5f94fa 691 const event = new UIEvent('resize');
9b84f516
PO
692 window.dispatchEvent(event);
693 clock.tick(1000);
694
695 expect(RFB.messages.setDesktopSize).to.not.have.been.called;
696 });
697
698 it('should not resize when resize is not supported', function () {
699 client._supportsSetDesktopSize = false;
700
701 container.style.width = '40px';
702 container.style.height = '50px';
2b5f94fa 703 const event = new UIEvent('resize');
9b84f516
PO
704 window.dispatchEvent(event);
705 clock.tick(1000);
706
707 expect(RFB.messages.setDesktopSize).to.not.have.been.called;
708 });
709
710 it('should not resize when in view only mode', function () {
711 client._viewOnly = true;
712
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.not.have.been.called;
720 });
721
722 it('should not try to override a server resize', function () {
723 // Simple ExtendedDesktopSize FBU message
2b5f94fa 724 const incoming = [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
7b536961
PO
725 0x00, 0x04, 0x00, 0x04, 0xff, 0xff, 0xfe, 0xcc,
726 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
727 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04,
728 0x00, 0x00, 0x00, 0x00 ];
9b84f516
PO
729
730 client._sock._websocket._receive_data(new Uint8Array(incoming));
731
732 expect(RFB.messages.setDesktopSize).to.not.have.been.called;
733 });
734 });
735
b1dee947 736 describe('Misc Internals', function () {
c00ee156 737 describe('#_updateConnectionState', function () {
2b5f94fa 738 let client;
b1dee947 739 beforeEach(function () {
b1dee947
SR
740 client = make_rfb();
741 });
742
c2a4d3ef 743 it('should clear the disconnect timer if the state is not "disconnecting"', function () {
2b5f94fa 744 const spy = sinon.spy();
b1dee947 745 client._disconnTimer = setTimeout(spy, 50);
2f4516f2
PO
746 client._rfb_connection_state = 'connecting';
747 client._updateConnectionState('connected');
b1dee947
SR
748 this.clock.tick(51);
749 expect(spy).to.not.have.been.called;
750 expect(client._disconnTimer).to.be.null;
751 });
3bb12056 752
3bb12056 753 it('should set the rfb_connection_state', function () {
bb25d3d6
PO
754 client._rfb_connection_state = 'connecting';
755 client._updateConnectionState('connected');
756 expect(client._rfb_connection_state).to.equal('connected');
3bb12056
SM
757 });
758
759 it('should not change the state when we are disconnected', function () {
bb25d3d6
PO
760 client.disconnect();
761 expect(client._rfb_connection_state).to.equal('disconnected');
b2e961d4
SM
762 client._updateConnectionState('connecting');
763 expect(client._rfb_connection_state).to.not.equal('connecting');
3bb12056
SM
764 });
765
766 it('should ignore state changes to the same state', function () {
2b5f94fa 767 const connectSpy = sinon.spy();
ee5cae9f 768 client.addEventListener("connect", connectSpy);
ee5cae9f 769
bb25d3d6 770 expect(client._rfb_connection_state).to.equal('connected');
ee5cae9f
SM
771 client._updateConnectionState('connected');
772 expect(connectSpy).to.not.have.been.called;
773
bb25d3d6
PO
774 client.disconnect();
775
2b5f94fa 776 const disconnectSpy = sinon.spy();
bb25d3d6
PO
777 client.addEventListener("disconnect", disconnectSpy);
778
779 expect(client._rfb_connection_state).to.equal('disconnected');
ee5cae9f
SM
780 client._updateConnectionState('disconnected');
781 expect(disconnectSpy).to.not.have.been.called;
b2e961d4
SM
782 });
783
784 it('should ignore illegal state changes', function () {
2b5f94fa 785 const spy = sinon.spy();
ee5cae9f 786 client.addEventListener("disconnect", spy);
b2e961d4
SM
787 client._updateConnectionState('disconnected');
788 expect(client._rfb_connection_state).to.not.equal('disconnected');
3bb12056
SM
789 expect(spy).to.not.have.been.called;
790 });
791 });
792
793 describe('#_fail', function () {
2b5f94fa 794 let client;
3bb12056 795 beforeEach(function () {
3bb12056 796 client = make_rfb();
3bb12056
SM
797 });
798
3bb12056
SM
799 it('should close the WebSocket connection', function () {
800 sinon.spy(client._sock, 'close');
801 client._fail();
802 expect(client._sock.close).to.have.been.calledOnce;
803 });
804
805 it('should transition to disconnected', function () {
806 sinon.spy(client, '_updateConnectionState');
807 client._fail();
808 this.clock.tick(2000);
809 expect(client._updateConnectionState).to.have.been.called;
810 expect(client._rfb_connection_state).to.equal('disconnected');
811 });
812
d472f3f1
SM
813 it('should set clean_disconnect variable', function () {
814 client._rfb_clean_disconnect = true;
b2e961d4 815 client._rfb_connection_state = 'connected';
d472f3f1
SM
816 client._fail();
817 expect(client._rfb_clean_disconnect).to.be.false;
67cd2072
SM
818 });
819
d472f3f1 820 it('should result in disconnect event with clean set to false', function () {
b2e961d4 821 client._rfb_connection_state = 'connected';
2b5f94fa 822 const spy = sinon.spy();
e89eef94 823 client.addEventListener("disconnect", spy);
d472f3f1 824 client._fail();
3bb12056
SM
825 this.clock.tick(2000);
826 expect(spy).to.have.been.calledOnce;
d472f3f1 827 expect(spy.args[0][0].detail.clean).to.be.false;
3bb12056
SM
828 });
829
b1dee947
SR
830 });
831 });
832
c00ee156 833 describe('Connection States', function () {
ee5cae9f
SM
834 describe('connecting', function () {
835 it('should open the websocket connection', function () {
2b5f94fa 836 const client = new RFB(document.createElement('div'),
7b536961 837 'ws://HOST:8675/PATH');
ee5cae9f
SM
838 sinon.spy(client._sock, 'open');
839 this.clock.tick();
840 expect(client._sock.open).to.have.been.calledOnce;
841 });
842 });
843
844 describe('connected', function () {
2b5f94fa 845 let client;
ee5cae9f
SM
846 beforeEach(function () {
847 client = make_rfb();
848 });
849
850 it('should result in a connect event if state becomes connected', function () {
2b5f94fa 851 const spy = sinon.spy();
ee5cae9f
SM
852 client.addEventListener("connect", spy);
853 client._rfb_connection_state = 'connecting';
854 client._updateConnectionState('connected');
855 expect(spy).to.have.been.calledOnce;
856 });
857
858 it('should not result in a connect event if the state is not "connected"', function () {
2b5f94fa 859 const spy = sinon.spy();
ee5cae9f 860 client.addEventListener("connect", spy);
651c23ec 861 client._sock._websocket.open = () => {}; // explicitly don't call onopen
ee5cae9f
SM
862 client._updateConnectionState('connecting');
863 expect(spy).to.not.have.been.called;
864 });
865 });
866
c2a4d3ef 867 describe('disconnecting', function () {
2b5f94fa 868 let client;
b1dee947 869 beforeEach(function () {
b1dee947 870 client = make_rfb();
b1dee947
SR
871 });
872
3bb12056
SM
873 it('should force disconnect if we do not call Websock.onclose within the disconnection timeout', function () {
874 sinon.spy(client, '_updateConnectionState');
651c23ec 875 client._sock._websocket.close = () => {}; // explicitly don't call onclose
c2a4d3ef 876 client._updateConnectionState('disconnecting');
68e09edc 877 this.clock.tick(3 * 1000);
3bb12056
SM
878 expect(client._updateConnectionState).to.have.been.calledTwice;
879 expect(client._rfb_disconnect_reason).to.not.equal("");
880 expect(client._rfb_connection_state).to.equal("disconnected");
b1dee947
SR
881 });
882
883 it('should not fail if Websock.onclose gets called within the disconnection timeout', function () {
c2a4d3ef 884 client._updateConnectionState('disconnecting');
68e09edc 885 this.clock.tick(3 * 1000 / 2);
b1dee947 886 client._sock._websocket.close();
68e09edc 887 this.clock.tick(3 * 1000 / 2 + 1);
c00ee156 888 expect(client._rfb_connection_state).to.equal('disconnected');
b1dee947
SR
889 });
890
891 it('should close the WebSocket connection', function () {
892 sinon.spy(client._sock, 'close');
c2a4d3ef 893 client._updateConnectionState('disconnecting');
3bb12056 894 expect(client._sock.close).to.have.been.calledOnce;
b1dee947 895 });
bb25d3d6
PO
896
897 it('should not result in a disconnect event', function () {
2b5f94fa 898 const spy = sinon.spy();
bb25d3d6 899 client.addEventListener("disconnect", spy);
651c23ec 900 client._sock._websocket.close = () => {}; // explicitly don't call onclose
bb25d3d6
PO
901 client._updateConnectionState('disconnecting');
902 expect(spy).to.not.have.been.called;
903 });
b1dee947
SR
904 });
905
3bb12056 906 describe('disconnected', function () {
2b5f94fa 907 let client;
bb25d3d6 908 beforeEach(function () {
9b84f516 909 client = new RFB(document.createElement('div'), 'ws://HOST:8675/PATH');
bb25d3d6 910 });
b1dee947 911
d472f3f1 912 it('should result in a disconnect event if state becomes "disconnected"', function () {
2b5f94fa 913 const spy = sinon.spy();
e89eef94 914 client.addEventListener("disconnect", spy);
3bb12056 915 client._rfb_connection_state = 'disconnecting';
3bb12056 916 client._updateConnectionState('disconnected');
3bb12056 917 expect(spy).to.have.been.calledOnce;
d472f3f1 918 expect(spy.args[0][0].detail.clean).to.be.true;
b1dee947
SR
919 });
920
d472f3f1 921 it('should result in a disconnect event without msg when no reason given', function () {
2b5f94fa 922 const spy = sinon.spy();
e89eef94 923 client.addEventListener("disconnect", spy);
3bb12056
SM
924 client._rfb_connection_state = 'disconnecting';
925 client._rfb_disconnect_reason = "";
926 client._updateConnectionState('disconnected');
3bb12056
SM
927 expect(spy).to.have.been.calledOnce;
928 expect(spy.args[0].length).to.equal(1);
b1dee947 929 });
b1dee947 930 });
b1dee947
SR
931 });
932
933 describe('Protocol Initialization States', function () {
2b5f94fa 934 let client;
057b8fec
PO
935 beforeEach(function () {
936 client = make_rfb();
2f4516f2 937 client._rfb_connection_state = 'connecting';
057b8fec 938 });
b1dee947 939
057b8fec 940 describe('ProtocolVersion', function () {
2c5491e1 941 function send_ver(ver, client) {
2b5f94fa
JD
942 const arr = new Uint8Array(12);
943 for (let i = 0; i < ver.length; i++) {
b1dee947
SR
944 arr[i+4] = ver.charCodeAt(i);
945 }
946 arr[0] = 'R'; arr[1] = 'F'; arr[2] = 'B'; arr[3] = ' ';
947 arr[11] = '\n';
948 client._sock._websocket._receive_data(arr);
949 }
950
951 describe('version parsing', function () {
b1dee947
SR
952 it('should interpret version 003.003 as version 3.3', function () {
953 send_ver('003.003', client);
954 expect(client._rfb_version).to.equal(3.3);
955 });
956
957 it('should interpret version 003.006 as version 3.3', function () {
958 send_ver('003.006', client);
959 expect(client._rfb_version).to.equal(3.3);
960 });
961
962 it('should interpret version 003.889 as version 3.3', function () {
963 send_ver('003.889', client);
964 expect(client._rfb_version).to.equal(3.3);
965 });
966
967 it('should interpret version 003.007 as version 3.7', function () {
968 send_ver('003.007', client);
969 expect(client._rfb_version).to.equal(3.7);
970 });
971
972 it('should interpret version 003.008 as version 3.8', function () {
973 send_ver('003.008', client);
974 expect(client._rfb_version).to.equal(3.8);
975 });
976
977 it('should interpret version 004.000 as version 3.8', function () {
978 send_ver('004.000', client);
979 expect(client._rfb_version).to.equal(3.8);
980 });
981
982 it('should interpret version 004.001 as version 3.8', function () {
983 send_ver('004.001', client);
984 expect(client._rfb_version).to.equal(3.8);
985 });
986
49aa5b81
LOH
987 it('should interpret version 005.000 as version 3.8', function () {
988 send_ver('005.000', client);
989 expect(client._rfb_version).to.equal(3.8);
990 });
991
b1dee947 992 it('should fail on an invalid version', function () {
3bb12056 993 sinon.spy(client, "_fail");
b1dee947 994 send_ver('002.000', client);
3bb12056 995 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
996 });
997 });
998
b1dee947
SR
999 it('should send back the interpreted version', function () {
1000 send_ver('004.000', client);
1001
2b5f94fa
JD
1002 const expected_str = 'RFB 003.008\n';
1003 const expected = [];
1004 for (let i = 0; i < expected_str.length; i++) {
b1dee947
SR
1005 expected[i] = expected_str.charCodeAt(i);
1006 }
1007
9ff86fb7 1008 expect(client._sock).to.have.sent(new Uint8Array(expected));
b1dee947
SR
1009 });
1010
1011 it('should transition to the Security state on successful negotiation', function () {
1012 send_ver('003.008', client);
c00ee156 1013 expect(client._rfb_init_state).to.equal('Security');
b1dee947 1014 });
3d7bb020
PO
1015
1016 describe('Repeater', function () {
057b8fec 1017 beforeEach(function () {
2f4516f2
PO
1018 client = make_rfb('wss://host:8675', { repeaterID: "12345" });
1019 client._rfb_connection_state = 'connecting';
057b8fec
PO
1020 });
1021
1022 it('should interpret version 000.000 as a repeater', function () {
3d7bb020
PO
1023 send_ver('000.000', client);
1024 expect(client._rfb_version).to.equal(0);
1025
2b5f94fa 1026 const sent_data = client._sock._websocket._get_sent_data();
3d7bb020
PO
1027 expect(new Uint8Array(sent_data.buffer, 0, 9)).to.array.equal(new Uint8Array([73, 68, 58, 49, 50, 51, 52, 53, 0]));
1028 expect(sent_data).to.have.length(250);
1029 });
1030
1031 it('should handle two step repeater negotiation', function () {
3d7bb020
PO
1032 send_ver('000.000', client);
1033 send_ver('003.008', client);
1034 expect(client._rfb_version).to.equal(3.8);
1035 });
1036 });
b1dee947
SR
1037 });
1038
1039 describe('Security', function () {
b1dee947 1040 beforeEach(function () {
c00ee156 1041 client._rfb_init_state = 'Security';
b1dee947
SR
1042 });
1043
1044 it('should simply receive the auth scheme when for versions < 3.7', function () {
1045 client._rfb_version = 3.6;
2b5f94fa
JD
1046 const auth_scheme_raw = [1, 2, 3, 4];
1047 const auth_scheme = (auth_scheme_raw[0] << 24) + (auth_scheme_raw[1] << 16) +
b1dee947 1048 (auth_scheme_raw[2] << 8) + auth_scheme_raw[3];
70e67958 1049 client._sock._websocket._receive_data(new Uint8Array(auth_scheme_raw));
b1dee947
SR
1050 expect(client._rfb_auth_scheme).to.equal(auth_scheme);
1051 });
1052
0ee5ca6e
PO
1053 it('should prefer no authentication is possible', function () {
1054 client._rfb_version = 3.7;
2b5f94fa 1055 const auth_schemes = [2, 1, 3];
70e67958 1056 client._sock._websocket._receive_data(new Uint8Array(auth_schemes));
0ee5ca6e
PO
1057 expect(client._rfb_auth_scheme).to.equal(1);
1058 expect(client._sock).to.have.sent(new Uint8Array([1, 1]));
1059 });
1060
b1dee947
SR
1061 it('should choose for the most prefered scheme possible for versions >= 3.7', function () {
1062 client._rfb_version = 3.7;
2b5f94fa 1063 const auth_schemes = [2, 22, 16];
70e67958 1064 client._sock._websocket._receive_data(new Uint8Array(auth_schemes));
0ee5ca6e
PO
1065 expect(client._rfb_auth_scheme).to.equal(22);
1066 expect(client._sock).to.have.sent(new Uint8Array([22]));
b1dee947
SR
1067 });
1068
1069 it('should fail if there are no supported schemes for versions >= 3.7', function () {
3bb12056 1070 sinon.spy(client, "_fail");
b1dee947 1071 client._rfb_version = 3.7;
2b5f94fa 1072 const auth_schemes = [1, 32];
70e67958 1073 client._sock._websocket._receive_data(new Uint8Array(auth_schemes));
3bb12056 1074 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
1075 });
1076
1077 it('should fail with the appropriate message if no types are sent for versions >= 3.7', function () {
1078 client._rfb_version = 3.7;
2b5f94fa 1079 const failure_data = [0, 0, 0, 0, 6, 119, 104, 111, 111, 112, 115];
b1dee947 1080 sinon.spy(client, '_fail');
70e67958 1081 client._sock._websocket._receive_data(new Uint8Array(failure_data));
b1dee947 1082
159c50c0 1083 expect(client._fail).to.have.been.calledOnce;
67cd2072 1084 expect(client._fail).to.have.been.calledWith(
d472f3f1 1085 'Security negotiation failed on no security types (reason: whoops)');
b1dee947
SR
1086 });
1087
1088 it('should transition to the Authentication state and continue on successful negotiation', function () {
1089 client._rfb_version = 3.7;
2b5f94fa 1090 const auth_schemes = [1, 1];
b1dee947 1091 client._negotiate_authentication = sinon.spy();
70e67958 1092 client._sock._websocket._receive_data(new Uint8Array(auth_schemes));
c00ee156 1093 expect(client._rfb_init_state).to.equal('Authentication');
b1dee947
SR
1094 expect(client._negotiate_authentication).to.have.been.calledOnce;
1095 });
1096 });
1097
1098 describe('Authentication', function () {
b1dee947 1099 beforeEach(function () {
c00ee156 1100 client._rfb_init_state = 'Security';
b1dee947
SR
1101 });
1102
1103 function send_security(type, cl) {
1104 cl._sock._websocket._receive_data(new Uint8Array([1, type]));
1105 }
1106
1107 it('should fail on auth scheme 0 (pre 3.7) with the given message', function () {
1108 client._rfb_version = 3.6;
2b5f94fa
JD
1109 const err_msg = "Whoopsies";
1110 const data = [0, 0, 0, 0];
1111 const err_len = err_msg.length;
3949a095 1112 push32(data, err_len);
2b5f94fa 1113 for (let i = 0; i < err_len; i++) {
b1dee947
SR
1114 data.push(err_msg.charCodeAt(i));
1115 }
1116
1117 sinon.spy(client, '_fail');
1118 client._sock._websocket._receive_data(new Uint8Array(data));
67cd2072 1119 expect(client._fail).to.have.been.calledWith(
d472f3f1 1120 'Security negotiation failed on authentication scheme (reason: Whoopsies)');
b1dee947
SR
1121 });
1122
1123 it('should transition straight to SecurityResult on "no auth" (1) for versions >= 3.8', function () {
1124 client._rfb_version = 3.8;
1125 send_security(1, client);
c00ee156 1126 expect(client._rfb_init_state).to.equal('SecurityResult');
b1dee947
SR
1127 });
1128
c00ee156 1129 it('should transition straight to ServerInitialisation on "no auth" for versions < 3.8', function () {
b1dee947 1130 client._rfb_version = 3.7;
b1dee947 1131 send_security(1, client);
c00ee156 1132 expect(client._rfb_init_state).to.equal('ServerInitialisation');
b1dee947
SR
1133 });
1134
1135 it('should fail on an unknown auth scheme', function () {
3bb12056 1136 sinon.spy(client, "_fail");
b1dee947
SR
1137 client._rfb_version = 3.8;
1138 send_security(57, client);
3bb12056 1139 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
1140 });
1141
1142 describe('VNC Authentication (type 2) Handler', function () {
b1dee947 1143 beforeEach(function () {
c00ee156 1144 client._rfb_init_state = 'Security';
b1dee947
SR
1145 client._rfb_version = 3.8;
1146 });
1147
e89eef94 1148 it('should fire the credentialsrequired event if missing a password', function () {
2b5f94fa 1149 const spy = sinon.spy();
e89eef94 1150 client.addEventListener("credentialsrequired", spy);
b1dee947 1151 send_security(2, client);
7d714b15 1152
2b5f94fa
JD
1153 const challenge = [];
1154 for (let i = 0; i < 16; i++) { challenge[i] = i; }
aa5b3a35
SM
1155 client._sock._websocket._receive_data(new Uint8Array(challenge));
1156
430f00d6 1157 expect(client._rfb_credentials).to.be.empty;
7d714b15 1158 expect(spy).to.have.been.calledOnce;
e89eef94 1159 expect(spy.args[0][0].detail.types).to.have.members(["password"]);
b1dee947
SR
1160 });
1161
1162 it('should encrypt the password with DES and then send it back', function () {
430f00d6 1163 client._rfb_credentials = { password: 'passwd' };
b1dee947
SR
1164 send_security(2, client);
1165 client._sock._websocket._get_sent_data(); // skip the choice of auth reply
1166
2b5f94fa
JD
1167 const challenge = [];
1168 for (let i = 0; i < 16; i++) { challenge[i] = i; }
b1dee947
SR
1169 client._sock._websocket._receive_data(new Uint8Array(challenge));
1170
2b5f94fa 1171 const des_pass = RFB.genDES('passwd', challenge);
9ff86fb7 1172 expect(client._sock).to.have.sent(new Uint8Array(des_pass));
b1dee947
SR
1173 });
1174
1175 it('should transition to SecurityResult immediately after sending the password', function () {
430f00d6 1176 client._rfb_credentials = { password: 'passwd' };
b1dee947
SR
1177 send_security(2, client);
1178
2b5f94fa
JD
1179 const challenge = [];
1180 for (let i = 0; i < 16; i++) { challenge[i] = i; }
b1dee947
SR
1181 client._sock._websocket._receive_data(new Uint8Array(challenge));
1182
c00ee156 1183 expect(client._rfb_init_state).to.equal('SecurityResult');
b1dee947
SR
1184 });
1185 });
1186
1187 describe('XVP Authentication (type 22) Handler', function () {
b1dee947 1188 beforeEach(function () {
c00ee156 1189 client._rfb_init_state = 'Security';
b1dee947
SR
1190 client._rfb_version = 3.8;
1191 });
1192
1193 it('should fall through to standard VNC authentication upon completion', function () {
430f00d6
PO
1194 client._rfb_credentials = { username: 'user',
1195 target: 'target',
1196 password: 'password' };
b1dee947
SR
1197 client._negotiate_std_vnc_auth = sinon.spy();
1198 send_security(22, client);
1199 expect(client._negotiate_std_vnc_auth).to.have.been.calledOnce;
1200 });
1201
2c5491e1 1202 it('should fire the credentialsrequired event if all credentials are missing', function () {
2b5f94fa 1203 const spy = sinon.spy();
e89eef94 1204 client.addEventListener("credentialsrequired", spy);
430f00d6 1205 client._rfb_credentials = {};
b1dee947 1206 send_security(22, client);
7d714b15 1207
430f00d6 1208 expect(client._rfb_credentials).to.be.empty;
7d714b15 1209 expect(spy).to.have.been.calledOnce;
e89eef94 1210 expect(spy.args[0][0].detail.types).to.have.members(["username", "password", "target"]);
b1dee947
SR
1211 });
1212
2c5491e1 1213 it('should fire the credentialsrequired event if some credentials are missing', function () {
2b5f94fa 1214 const spy = sinon.spy();
e89eef94 1215 client.addEventListener("credentialsrequired", spy);
430f00d6
PO
1216 client._rfb_credentials = { username: 'user',
1217 target: 'target' };
b1dee947 1218 send_security(22, client);
7d714b15 1219
7d714b15 1220 expect(spy).to.have.been.calledOnce;
e89eef94 1221 expect(spy.args[0][0].detail.types).to.have.members(["username", "password", "target"]);
b1dee947
SR
1222 });
1223
430f00d6
PO
1224 it('should send user and target separately', function () {
1225 client._rfb_credentials = { username: 'user',
1226 target: 'target',
1227 password: 'password' };
b1dee947
SR
1228 client._negotiate_std_vnc_auth = sinon.spy();
1229
1230 send_security(22, client);
1231
2b5f94fa
JD
1232 const expected = [22, 4, 6]; // auth selection, len user, len target
1233 for (let i = 0; i < 10; i++) { expected[i+3] = 'usertarget'.charCodeAt(i); }
b1dee947 1234
9ff86fb7 1235 expect(client._sock).to.have.sent(new Uint8Array(expected));
b1dee947
SR
1236 });
1237 });
1238
1239 describe('TightVNC Authentication (type 16) Handler', function () {
b1dee947 1240 beforeEach(function () {
c00ee156 1241 client._rfb_init_state = 'Security';
b1dee947
SR
1242 client._rfb_version = 3.8;
1243 send_security(16, client);
1244 client._sock._websocket._get_sent_data(); // skip the security reply
1245 });
1246
1247 function send_num_str_pairs(pairs, client) {
2b5f94fa 1248 const data = [];
651c23ec 1249 push32(data, pairs.length);
b1dee947 1250
651c23ec 1251 for (let i = 0; i < pairs.length; i++) {
3949a095 1252 push32(data, pairs[i][0]);
2b5f94fa 1253 for (let j = 0; j < 4; j++) {
b1dee947
SR
1254 data.push(pairs[i][1].charCodeAt(j));
1255 }
2b5f94fa 1256 for (let j = 0; j < 8; j++) {
b1dee947
SR
1257 data.push(pairs[i][2].charCodeAt(j));
1258 }
1259 }
1260
1261 client._sock._websocket._receive_data(new Uint8Array(data));
1262 }
1263
1264 it('should skip tunnel negotiation if no tunnels are requested', function () {
1265 client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0]));
1266 expect(client._rfb_tightvnc).to.be.true;
1267 });
1268
1269 it('should fail if no supported tunnels are listed', function () {
3bb12056 1270 sinon.spy(client, "_fail");
b1dee947 1271 send_num_str_pairs([[123, 'OTHR', 'SOMETHNG']], client);
3bb12056 1272 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
1273 });
1274
1275 it('should choose the notunnel tunnel type', function () {
1276 send_num_str_pairs([[0, 'TGHT', 'NOTUNNEL'], [123, 'OTHR', 'SOMETHNG']], client);
9ff86fb7 1277 expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 0]));
b1dee947
SR
1278 });
1279
8f47bd29
PO
1280 it('should choose the notunnel tunnel type for Siemens devices', function () {
1281 send_num_str_pairs([[1, 'SICR', 'SCHANNEL'], [2, 'SICR', 'SCHANLPW']], client);
1282 expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 0]));
1283 });
1284
b1dee947
SR
1285 it('should continue to sub-auth negotiation after tunnel negotiation', function () {
1286 send_num_str_pairs([[0, 'TGHT', 'NOTUNNEL']], client);
1287 client._sock._websocket._get_sent_data(); // skip the tunnel choice here
1288 send_num_str_pairs([[1, 'STDV', 'NOAUTH__']], client);
9ff86fb7 1289 expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 1]));
c00ee156 1290 expect(client._rfb_init_state).to.equal('SecurityResult');
b1dee947
SR
1291 });
1292
1293 /*it('should attempt to use VNC auth over no auth when possible', function () {
1294 client._rfb_tightvnc = true;
1295 client._negotiate_std_vnc_auth = sinon.spy();
1296 send_num_str_pairs([[1, 'STDV', 'NOAUTH__'], [2, 'STDV', 'VNCAUTH_']], client);
1297 expect(client._sock).to.have.sent([0, 0, 0, 1]);
1298 expect(client._negotiate_std_vnc_auth).to.have.been.calledOnce;
1299 expect(client._rfb_auth_scheme).to.equal(2);
1300 });*/ // while this would make sense, the original code doesn't actually do this
1301
1302 it('should accept the "no auth" auth type and transition to SecurityResult', function () {
1303 client._rfb_tightvnc = true;
1304 send_num_str_pairs([[1, 'STDV', 'NOAUTH__']], client);
9ff86fb7 1305 expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 1]));
c00ee156 1306 expect(client._rfb_init_state).to.equal('SecurityResult');
b1dee947
SR
1307 });
1308
1309 it('should accept VNC authentication and transition to that', function () {
1310 client._rfb_tightvnc = true;
1311 client._negotiate_std_vnc_auth = sinon.spy();
1312 send_num_str_pairs([[2, 'STDV', 'VNCAUTH__']], client);
9ff86fb7 1313 expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 2]));
b1dee947
SR
1314 expect(client._negotiate_std_vnc_auth).to.have.been.calledOnce;
1315 expect(client._rfb_auth_scheme).to.equal(2);
1316 });
1317
1318 it('should fail if there are no supported auth types', function () {
3bb12056 1319 sinon.spy(client, "_fail");
b1dee947
SR
1320 client._rfb_tightvnc = true;
1321 send_num_str_pairs([[23, 'stdv', 'badval__']], client);
3bb12056 1322 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
1323 });
1324 });
1325 });
1326
1327 describe('SecurityResult', function () {
b1dee947 1328 beforeEach(function () {
c00ee156 1329 client._rfb_init_state = 'SecurityResult';
b1dee947
SR
1330 });
1331
c00ee156 1332 it('should fall through to ServerInitialisation on a response code of 0', function () {
b1dee947 1333 client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0]));
c00ee156 1334 expect(client._rfb_init_state).to.equal('ServerInitialisation');
b1dee947
SR
1335 });
1336
1337 it('should fail on an error code of 1 with the given message for versions >= 3.8', function () {
1338 client._rfb_version = 3.8;
1339 sinon.spy(client, '_fail');
2b5f94fa 1340 const failure_data = [0, 0, 0, 1, 0, 0, 0, 6, 119, 104, 111, 111, 112, 115];
b1dee947 1341 client._sock._websocket._receive_data(new Uint8Array(failure_data));
67cd2072 1342 expect(client._fail).to.have.been.calledWith(
d472f3f1 1343 'Security negotiation failed on security result (reason: whoops)');
b1dee947
SR
1344 });
1345
1346 it('should fail on an error code of 1 with a standard message for version < 3.8', function () {
3bb12056 1347 sinon.spy(client, '_fail');
b1dee947
SR
1348 client._rfb_version = 3.7;
1349 client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 1]));
d472f3f1
SM
1350 expect(client._fail).to.have.been.calledWith(
1351 'Security handshake failed');
1352 });
1353
1354 it('should result in securityfailure event when receiving a non zero status', function () {
2b5f94fa 1355 const spy = sinon.spy();
d472f3f1
SM
1356 client.addEventListener("securityfailure", spy);
1357 client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 2]));
1358 expect(spy).to.have.been.calledOnce;
1359 expect(spy.args[0][0].detail.status).to.equal(2);
1360 });
1361
1362 it('should include reason when provided in securityfailure event', function () {
1363 client._rfb_version = 3.8;
2b5f94fa 1364 const spy = sinon.spy();
d472f3f1 1365 client.addEventListener("securityfailure", spy);
2b5f94fa 1366 const failure_data = [0, 0, 0, 1, 0, 0, 0, 12, 115, 117, 99, 104,
7b536961 1367 32, 102, 97, 105, 108, 117, 114, 101];
d472f3f1
SM
1368 client._sock._websocket._receive_data(new Uint8Array(failure_data));
1369 expect(spy.args[0][0].detail.status).to.equal(1);
1370 expect(spy.args[0][0].detail.reason).to.equal('such failure');
1371 });
1372
1373 it('should not include reason when length is zero in securityfailure event', function () {
1374 client._rfb_version = 3.9;
2b5f94fa 1375 const spy = sinon.spy();
d472f3f1 1376 client.addEventListener("securityfailure", spy);
2b5f94fa 1377 const failure_data = [0, 0, 0, 1, 0, 0, 0, 0];
d472f3f1
SM
1378 client._sock._websocket._receive_data(new Uint8Array(failure_data));
1379 expect(spy.args[0][0].detail.status).to.equal(1);
1380 expect('reason' in spy.args[0][0].detail).to.be.false;
1381 });
1382
1383 it('should not include reason in securityfailure event for version < 3.8', function () {
1384 client._rfb_version = 3.6;
2b5f94fa 1385 const spy = sinon.spy();
d472f3f1
SM
1386 client.addEventListener("securityfailure", spy);
1387 client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 2]));
1388 expect(spy.args[0][0].detail.status).to.equal(2);
1389 expect('reason' in spy.args[0][0].detail).to.be.false;
b1dee947
SR
1390 });
1391 });
1392
1393 describe('ClientInitialisation', function () {
b1dee947 1394 it('should transition to the ServerInitialisation state', function () {
2b5f94fa 1395 const client = make_rfb();
2f4516f2 1396 client._rfb_connection_state = 'connecting';
3d7bb020 1397 client._rfb_init_state = 'SecurityResult';
b1dee947 1398 client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0]));
c00ee156 1399 expect(client._rfb_init_state).to.equal('ServerInitialisation');
b1dee947
SR
1400 });
1401
1402 it('should send 1 if we are in shared mode', function () {
2b5f94fa 1403 const client = make_rfb('wss://host:8675', { shared: true });
2f4516f2 1404 client._rfb_connection_state = 'connecting';
3d7bb020 1405 client._rfb_init_state = 'SecurityResult';
b1dee947 1406 client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0]));
9ff86fb7 1407 expect(client._sock).to.have.sent(new Uint8Array([1]));
b1dee947
SR
1408 });
1409
1410 it('should send 0 if we are not in shared mode', function () {
2b5f94fa 1411 const client = make_rfb('wss://host:8675', { shared: false });
2f4516f2 1412 client._rfb_connection_state = 'connecting';
3d7bb020 1413 client._rfb_init_state = 'SecurityResult';
b1dee947 1414 client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0]));
9ff86fb7 1415 expect(client._sock).to.have.sent(new Uint8Array([0]));
b1dee947
SR
1416 });
1417 });
1418
1419 describe('ServerInitialisation', function () {
b1dee947 1420 beforeEach(function () {
c00ee156 1421 client._rfb_init_state = 'ServerInitialisation';
b1dee947
SR
1422 });
1423
1424 function send_server_init(opts, client) {
2b5f94fa 1425 const full_opts = { width: 10, height: 12, bpp: 24, depth: 24, big_endian: 0,
7b536961
PO
1426 true_color: 1, red_max: 255, green_max: 255, blue_max: 255,
1427 red_shift: 16, green_shift: 8, blue_shift: 0, name: 'a name' };
2b5f94fa 1428 for (let opt in opts) {
b1dee947
SR
1429 full_opts[opt] = opts[opt];
1430 }
2b5f94fa 1431 const data = [];
b1dee947 1432
3949a095
SR
1433 push16(data, full_opts.width);
1434 push16(data, full_opts.height);
b1dee947
SR
1435
1436 data.push(full_opts.bpp);
1437 data.push(full_opts.depth);
1438 data.push(full_opts.big_endian);
1439 data.push(full_opts.true_color);
1440
3949a095
SR
1441 push16(data, full_opts.red_max);
1442 push16(data, full_opts.green_max);
1443 push16(data, full_opts.blue_max);
1444 push8(data, full_opts.red_shift);
1445 push8(data, full_opts.green_shift);
1446 push8(data, full_opts.blue_shift);
b1dee947
SR
1447
1448 // padding
3949a095
SR
1449 push8(data, 0);
1450 push8(data, 0);
1451 push8(data, 0);
b1dee947
SR
1452
1453 client._sock._websocket._receive_data(new Uint8Array(data));
1454
2b5f94fa 1455 const name_data = [];
8d6f686b
NL
1456 let name_len = [];
1457 pushString(name_data, full_opts.name);
1458 push32(name_len, name_data.length);
1459
1460 client._sock._websocket._receive_data(new Uint8Array(name_len));
b1dee947
SR
1461 client._sock._websocket._receive_data(new Uint8Array(name_data));
1462 }
1463
1464 it('should set the framebuffer width and height', function () {
1465 send_server_init({ width: 32, height: 84 }, client);
1466 expect(client._fb_width).to.equal(32);
1467 expect(client._fb_height).to.equal(84);
1468 });
1469
1470 // NB(sross): we just warn, not fail, for endian-ness and shifts, so we don't test them
1471
1472 it('should set the framebuffer name and call the callback', function () {
2b5f94fa 1473 const spy = sinon.spy();
e89eef94 1474 client.addEventListener("desktopname", spy);
8d6f686b 1475 send_server_init({ name: 'som€ nam€' }, client);
b1dee947 1476
8d6f686b 1477 expect(client._fb_name).to.equal('som€ nam€');
b1dee947 1478 expect(spy).to.have.been.calledOnce;
8d6f686b 1479 expect(spy.args[0][0].detail.name).to.equal('som€ nam€');
b1dee947
SR
1480 });
1481
1482 it('should handle the extended init message of the tight encoding', function () {
1483 // NB(sross): we don't actually do anything with it, so just test that we can
1484 // read it w/o throwing an error
1485 client._rfb_tightvnc = true;
1486 send_server_init({}, client);
1487
2b5f94fa 1488 const tight_data = [];
3949a095
SR
1489 push16(tight_data, 1);
1490 push16(tight_data, 2);
1491 push16(tight_data, 3);
1492 push16(tight_data, 0);
2b5f94fa 1493 for (let i = 0; i < 16 + 32 + 48; i++) {
b1dee947
SR
1494 tight_data.push(i);
1495 }
70e67958 1496 client._sock._websocket._receive_data(new Uint8Array(tight_data));
b1dee947 1497
c2a4d3ef 1498 expect(client._rfb_connection_state).to.equal('connected');
b1dee947
SR
1499 });
1500
9b84f516 1501 it('should resize the display', function () {
b1dee947
SR
1502 sinon.spy(client._display, 'resize');
1503 send_server_init({ width: 27, height: 32 }, client);
1504
b1dee947
SR
1505 expect(client._display.resize).to.have.been.calledOnce;
1506 expect(client._display.resize).to.have.been.calledWith(27, 32);
b1dee947
SR
1507 });
1508
1509 it('should grab the mouse and keyboard', function () {
1510 sinon.spy(client._keyboard, 'grab');
1511 sinon.spy(client._mouse, 'grab');
1512 send_server_init({}, client);
1513 expect(client._keyboard.grab).to.have.been.calledOnce;
1514 expect(client._mouse.grab).to.have.been.calledOnce;
1515 });
1516
69411b9e
PO
1517 describe('Initial Update Request', function () {
1518 beforeEach(function () {
1519 sinon.spy(RFB.messages, "pixelFormat");
1520 sinon.spy(RFB.messages, "clientEncodings");
1521 sinon.spy(RFB.messages, "fbUpdateRequest");
1522 });
49a81837 1523
69411b9e
PO
1524 afterEach(function () {
1525 RFB.messages.pixelFormat.restore();
1526 RFB.messages.clientEncodings.restore();
1527 RFB.messages.fbUpdateRequest.restore();
1528 });
b1dee947 1529
69411b9e
PO
1530 // TODO(directxman12): test the various options in this configuration matrix
1531 it('should reply with the pixel format, client encodings, and initial update request', function () {
1532 send_server_init({ width: 27, height: 32 }, client);
1533
1534 expect(RFB.messages.pixelFormat).to.have.been.calledOnce;
1535 expect(RFB.messages.pixelFormat).to.have.been.calledWith(client._sock, 24, true);
1536 expect(RFB.messages.pixelFormat).to.have.been.calledBefore(RFB.messages.clientEncodings);
1537 expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
1538 expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.encodingTight);
1539 expect(RFB.messages.clientEncodings).to.have.been.calledBefore(RFB.messages.fbUpdateRequest);
1540 expect(RFB.messages.fbUpdateRequest).to.have.been.calledOnce;
1541 expect(RFB.messages.fbUpdateRequest).to.have.been.calledWith(client._sock, false, 0, 0, 27, 32);
1542 });
1543
1544 it('should reply with restricted settings for Intel AMT servers', function () {
1545 send_server_init({ width: 27, height: 32, name: "Intel(r) AMT KVM"}, client);
1546
1547 expect(RFB.messages.pixelFormat).to.have.been.calledOnce;
1548 expect(RFB.messages.pixelFormat).to.have.been.calledWith(client._sock, 8, true);
1549 expect(RFB.messages.pixelFormat).to.have.been.calledBefore(RFB.messages.clientEncodings);
1550 expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
1551 expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.not.include(encodings.encodingTight);
1552 expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.not.include(encodings.encodingHextile);
1553 expect(RFB.messages.clientEncodings).to.have.been.calledBefore(RFB.messages.fbUpdateRequest);
1554 expect(RFB.messages.fbUpdateRequest).to.have.been.calledOnce;
1555 expect(RFB.messages.fbUpdateRequest).to.have.been.calledWith(client._sock, false, 0, 0, 27, 32);
1556 });
b1dee947
SR
1557 });
1558
c2a4d3ef 1559 it('should transition to the "connected" state', function () {
b1dee947 1560 send_server_init({}, client);
c2a4d3ef 1561 expect(client._rfb_connection_state).to.equal('connected');
b1dee947
SR
1562 });
1563 });
1564 });
1565
1566 describe('Protocol Message Processing After Completing Initialization', function () {
2b5f94fa 1567 let client;
b1dee947
SR
1568
1569 beforeEach(function () {
1570 client = make_rfb();
b1dee947
SR
1571 client._fb_name = 'some device';
1572 client._fb_width = 640;
1573 client._fb_height = 20;
1574 });
1575
1576 describe('Framebuffer Update Handling', function () {
2b5f94fa 1577 const target_data_arr = [
b1dee947
SR
1578 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
1579 0x00, 0xff, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
1580 0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255,
1581 0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255
1582 ];
2b5f94fa 1583 let target_data;
b1dee947 1584
2b5f94fa 1585 const target_data_check_arr = [
b1dee947
SR
1586 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
1587 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
1588 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
1589 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
1590 ];
2b5f94fa 1591 let target_data_check;
b1dee947
SR
1592
1593 before(function () {
1594 // NB(directxman12): PhantomJS 1.x doesn't implement Uint8ClampedArray
1595 target_data = new Uint8Array(target_data_arr);
1596 target_data_check = new Uint8Array(target_data_check_arr);
1597 });
1598
2c5491e1 1599 function send_fbu_msg(rect_info, rect_data, client, rect_cnt) {
2b5f94fa 1600 let data = [];
b1dee947
SR
1601
1602 if (!rect_cnt || rect_cnt > -1) {
1603 // header
1604 data.push(0); // msg type
1605 data.push(0); // padding
3949a095 1606 push16(data, rect_cnt || rect_data.length);
b1dee947
SR
1607 }
1608
2b5f94fa 1609 for (let i = 0; i < rect_data.length; i++) {
b1dee947 1610 if (rect_info[i]) {
3949a095
SR
1611 push16(data, rect_info[i].x);
1612 push16(data, rect_info[i].y);
1613 push16(data, rect_info[i].width);
1614 push16(data, rect_info[i].height);
1615 push32(data, rect_info[i].encoding);
b1dee947
SR
1616 }
1617 data = data.concat(rect_data[i]);
1618 }
1619
1620 client._sock._websocket._receive_data(new Uint8Array(data));
1621 }
1622
1623 it('should send an update request if there is sufficient data', function () {
0e4808bf 1624 const expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: () => {}};
2ba767a7 1625 RFB.messages.fbUpdateRequest(expected_msg, true, 0, 0, 640, 20);
b1dee947 1626
651c23ec 1627 client._framebufferUpdate = () => true;
b1dee947
SR
1628 client._sock._websocket._receive_data(new Uint8Array([0]));
1629
9ff86fb7 1630 expect(client._sock).to.have.sent(expected_msg._sQ);
b1dee947
SR
1631 });
1632
1633 it('should not send an update request if we need more data', function () {
1634 client._sock._websocket._receive_data(new Uint8Array([0]));
1635 expect(client._sock._websocket._get_sent_data()).to.have.length(0);
1636 });
1637
1638 it('should resume receiving an update if we previously did not have enough data', function () {
0e4808bf 1639 const expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: () => {}};
2ba767a7 1640 RFB.messages.fbUpdateRequest(expected_msg, true, 0, 0, 640, 20);
b1dee947
SR
1641
1642 // just enough to set FBU.rects
1643 client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 3]));
1644 expect(client._sock._websocket._get_sent_data()).to.have.length(0);
1645
8a189a62 1646 client._framebufferUpdate = function () { this._sock.rQskipBytes(1); return true; }; // we magically have enough data
b1dee947
SR
1647 // 247 should *not* be used as the message type here
1648 client._sock._websocket._receive_data(new Uint8Array([247]));
9ff86fb7 1649 expect(client._sock).to.have.sent(expected_msg._sQ);
b1dee947
SR
1650 });
1651
2ba767a7 1652 it('should not send a request in continuous updates mode', function () {
76a86ff5 1653 client._enabledContinuousUpdates = true;
651c23ec 1654 client._framebufferUpdate = () => true;
76a86ff5 1655 client._sock._websocket._receive_data(new Uint8Array([0]));
1656
1657 expect(client._sock._websocket._get_sent_data()).to.have.length(0);
1658 });
1659
b1dee947 1660 it('should fail on an unsupported encoding', function () {
3bb12056 1661 sinon.spy(client, "_fail");
2b5f94fa 1662 const rect_info = { x: 8, y: 11, width: 27, height: 32, encoding: 234 };
b1dee947 1663 send_fbu_msg([rect_info], [[]], client);
3bb12056 1664 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
1665 });
1666
1667 it('should be able to pause and resume receiving rects if not enought data', function () {
1668 // seed some initial data to copy
1669 client._fb_width = 4;
1670 client._fb_height = 4;
1671 client._display.resize(4, 4);
02329ab1 1672 client._display.blitRgbxImage(0, 0, 4, 2, new Uint8Array(target_data_check_arr.slice(0, 32)), 0);
b1dee947 1673
2b5f94fa 1674 const info = [{ x: 0, y: 2, width: 2, height: 2, encoding: 0x01},
7b536961 1675 { x: 2, y: 2, width: 2, height: 2, encoding: 0x01}];
02329ab1 1676 // data says [{ old_x: 2, old_y: 0 }, { old_x: 0, old_y: 0 }]
2b5f94fa 1677 const rects = [[0, 2, 0, 0], [0, 0, 0, 0]];
b1dee947
SR
1678 send_fbu_msg([info[0]], [rects[0]], client, 2);
1679 send_fbu_msg([info[1]], [rects[1]], client, -1);
1680 expect(client._display).to.have.displayed(target_data_check);
1681 });
1682
1683 describe('Message Encoding Handlers', function () {
b1dee947 1684 beforeEach(function () {
b1dee947
SR
1685 // a really small frame
1686 client._fb_width = 4;
1687 client._fb_height = 4;
69411b9e 1688 client._fb_depth = 24;
02329ab1 1689 client._display.resize(4, 4);
b1dee947
SR
1690 });
1691
1692 it('should handle the RAW encoding', function () {
2b5f94fa 1693 const info = [{ x: 0, y: 0, width: 2, height: 2, encoding: 0x00 },
7b536961
PO
1694 { x: 2, y: 0, width: 2, height: 2, encoding: 0x00 },
1695 { x: 0, y: 2, width: 4, height: 1, encoding: 0x00 },
1696 { x: 0, y: 3, width: 4, height: 1, encoding: 0x00 }];
b1dee947 1697 // data is in bgrx
2b5f94fa 1698 const rects = [
b1dee947
SR
1699 [0x00, 0x00, 0xff, 0, 0x00, 0xff, 0x00, 0, 0x00, 0xff, 0x00, 0, 0x00, 0x00, 0xff, 0],
1700 [0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0],
1701 [0xff, 0x00, 0xee, 0, 0xff, 0xee, 0x00, 0, 0xff, 0xee, 0xaa, 0, 0xff, 0xee, 0xab, 0],
1702 [0xff, 0x00, 0xee, 0, 0xff, 0xee, 0x00, 0, 0xff, 0xee, 0xaa, 0, 0xff, 0xee, 0xab, 0]];
1703 send_fbu_msg(info, rects, client);
1704 expect(client._display).to.have.displayed(target_data);
1705 });
1706
69411b9e 1707 it('should handle the RAW encoding in low colour mode', function () {
2b5f94fa 1708 const info = [{ x: 0, y: 0, width: 2, height: 2, encoding: 0x00 },
7b536961
PO
1709 { x: 2, y: 0, width: 2, height: 2, encoding: 0x00 },
1710 { x: 0, y: 2, width: 4, height: 1, encoding: 0x00 },
1711 { x: 0, y: 3, width: 4, height: 1, encoding: 0x00 }];
2b5f94fa 1712 const rects = [
69411b9e
PO
1713 [0x03, 0x03, 0x03, 0x03],
1714 [0x0c, 0x0c, 0x0c, 0x0c],
1715 [0x0c, 0x0c, 0x03, 0x03],
1716 [0x0c, 0x0c, 0x03, 0x03]];
1717 client._fb_depth = 8;
1718 send_fbu_msg(info, rects, client);
1719 expect(client._display).to.have.displayed(target_data_check);
1720 });
1721
b1dee947
SR
1722 it('should handle the COPYRECT encoding', function () {
1723 // seed some initial data to copy
02329ab1 1724 client._display.blitRgbxImage(0, 0, 4, 2, new Uint8Array(target_data_check_arr.slice(0, 32)), 0);
b1dee947 1725
2b5f94fa 1726 const info = [{ x: 0, y: 2, width: 2, height: 2, encoding: 0x01},
7b536961 1727 { x: 2, y: 2, width: 2, height: 2, encoding: 0x01}];
b1dee947 1728 // data says [{ old_x: 0, old_y: 0 }, { old_x: 0, old_y: 0 }]
2b5f94fa 1729 const rects = [[0, 2, 0, 0], [0, 0, 0, 0]];
b1dee947
SR
1730 send_fbu_msg(info, rects, client);
1731 expect(client._display).to.have.displayed(target_data_check);
1732 });
1733
1734 // TODO(directxman12): for encodings with subrects, test resuming on partial send?
1735 // TODO(directxman12): test rre_chunk_sz (related to above about subrects)?
1736
1737 it('should handle the RRE encoding', function () {
2b5f94fa
JD
1738 const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x02 }];
1739 const rect = [];
3949a095
SR
1740 push32(rect, 2); // 2 subrects
1741 push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
b1dee947
SR
1742 rect.push(0xff); // becomes ff0000ff --> #0000FF color
1743 rect.push(0x00);
1744 rect.push(0x00);
1745 rect.push(0xff);
3949a095
SR
1746 push16(rect, 0); // x: 0
1747 push16(rect, 0); // y: 0
1748 push16(rect, 2); // width: 2
1749 push16(rect, 2); // height: 2
b1dee947
SR
1750 rect.push(0xff); // becomes ff0000ff --> #0000FF color
1751 rect.push(0x00);
1752 rect.push(0x00);
1753 rect.push(0xff);
3949a095
SR
1754 push16(rect, 2); // x: 2
1755 push16(rect, 2); // y: 2
1756 push16(rect, 2); // width: 2
1757 push16(rect, 2); // height: 2
b1dee947
SR
1758
1759 send_fbu_msg(info, [rect], client);
1760 expect(client._display).to.have.displayed(target_data_check);
1761 });
1762
1763 describe('the HEXTILE encoding handler', function () {
b1dee947 1764 it('should handle a tile with fg, bg specified, normal subrects', function () {
2b5f94fa
JD
1765 const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
1766 const rect = [];
b1dee947 1767 rect.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects
3949a095 1768 push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
b1dee947
SR
1769 rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
1770 rect.push(0x00);
1771 rect.push(0x00);
1772 rect.push(0xff);
1773 rect.push(2); // 2 subrects
1774 rect.push(0); // x: 0, y: 0
1775 rect.push(1 | (1 << 4)); // width: 2, height: 2
1776 rect.push(2 | (2 << 4)); // x: 2, y: 2
1777 rect.push(1 | (1 << 4)); // width: 2, height: 2
1778 send_fbu_msg(info, [rect], client);
1779 expect(client._display).to.have.displayed(target_data_check);
1780 });
1781
1782 it('should handle a raw tile', function () {
2b5f94fa
JD
1783 const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
1784 const rect = [];
b1dee947 1785 rect.push(0x01); // raw
2b5f94fa 1786 for (let i = 0; i < target_data.length; i += 4) {
b1dee947
SR
1787 rect.push(target_data[i + 2]);
1788 rect.push(target_data[i + 1]);
1789 rect.push(target_data[i]);
1790 rect.push(target_data[i + 3]);
1791 }
1792 send_fbu_msg(info, [rect], client);
1793 expect(client._display).to.have.displayed(target_data);
1794 });
1795
1796 it('should handle a tile with only bg specified (solid bg)', function () {
2b5f94fa
JD
1797 const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
1798 const rect = [];
b1dee947 1799 rect.push(0x02);
3949a095 1800 push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
b1dee947
SR
1801 send_fbu_msg(info, [rect], client);
1802
2b5f94fa
JD
1803 const expected = [];
1804 for (let i = 0; i < 16; i++) { push32(expected, 0xff00ff); }
b1dee947
SR
1805 expect(client._display).to.have.displayed(new Uint8Array(expected));
1806 });
1807
40ac6f0a
RK
1808 it('should handle a tile with only bg specified and an empty frame afterwards', function () {
1809 // set the width so we can have two tiles
1810 client._fb_width = 8;
02329ab1 1811 client._display.resize(8, 4);
40ac6f0a 1812
2b5f94fa 1813 const info = [{ x: 0, y: 0, width: 32, height: 4, encoding: 0x05 }];
40ac6f0a 1814
2b5f94fa 1815 const rect = [];
40ac6f0a
RK
1816
1817 // send a bg frame
1818 rect.push(0x02);
3949a095 1819 push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
40ac6f0a
RK
1820
1821 // send an empty frame
1822 rect.push(0x00);
1823
1824 send_fbu_msg(info, [rect], client);
1825
2b5f94fa
JD
1826 const expected = [];
1827 for (let i = 0; i < 16; i++) { push32(expected, 0xff00ff); } // rect 1: solid
1828 for (let i = 0; i < 16; i++) { push32(expected, 0xff00ff); } // rect 2: same bkground color
40ac6f0a
RK
1829 expect(client._display).to.have.displayed(new Uint8Array(expected));
1830 });
1831
b1dee947 1832 it('should handle a tile with bg and coloured subrects', function () {
2b5f94fa
JD
1833 const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
1834 const rect = [];
b1dee947 1835 rect.push(0x02 | 0x08 | 0x10); // bg spec, anysubrects, colouredsubrects
3949a095 1836 push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
b1dee947
SR
1837 rect.push(2); // 2 subrects
1838 rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
1839 rect.push(0x00);
1840 rect.push(0x00);
1841 rect.push(0xff);
1842 rect.push(0); // x: 0, y: 0
1843 rect.push(1 | (1 << 4)); // width: 2, height: 2
1844 rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
1845 rect.push(0x00);
1846 rect.push(0x00);
1847 rect.push(0xff);
1848 rect.push(2 | (2 << 4)); // x: 2, y: 2
1849 rect.push(1 | (1 << 4)); // width: 2, height: 2
1850 send_fbu_msg(info, [rect], client);
1851 expect(client._display).to.have.displayed(target_data_check);
1852 });
1853
1854 it('should carry over fg and bg colors from the previous tile if not specified', function () {
1855 client._fb_width = 4;
1856 client._fb_height = 17;
1857 client._display.resize(4, 17);
1858
2b5f94fa
JD
1859 const info = [{ x: 0, y: 0, width: 4, height: 17, encoding: 0x05}];
1860 const rect = [];
b1dee947 1861 rect.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects
3949a095 1862 push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
b1dee947
SR
1863 rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
1864 rect.push(0x00);
1865 rect.push(0x00);
1866 rect.push(0xff);
1867 rect.push(8); // 8 subrects
2b5f94fa 1868 for (let i = 0; i < 4; i++) {
b1dee947
SR
1869 rect.push((0 << 4) | (i * 4)); // x: 0, y: i*4
1870 rect.push(1 | (1 << 4)); // width: 2, height: 2
1871 rect.push((2 << 4) | (i * 4 + 2)); // x: 2, y: i * 4 + 2
1872 rect.push(1 | (1 << 4)); // width: 2, height: 2
1873 }
1874 rect.push(0x08); // anysubrects
1875 rect.push(1); // 1 subrect
1876 rect.push(0); // x: 0, y: 0
1877 rect.push(1 | (1 << 4)); // width: 2, height: 2
1878 send_fbu_msg(info, [rect], client);
1879
2b5f94fa
JD
1880 let expected = [];
1881 for (let i = 0; i < 4; i++) { expected = expected.concat(target_data_check_arr); }
b1dee947
SR
1882 expected = expected.concat(target_data_check_arr.slice(0, 16));
1883 expect(client._display).to.have.displayed(new Uint8Array(expected));
1884 });
1885
1886 it('should fail on an invalid subencoding', function () {
6786fd87 1887 sinon.spy(client, "_fail");
2b5f94fa
JD
1888 const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
1889 const rects = [[45]]; // an invalid subencoding
b1dee947 1890 send_fbu_msg(info, rects, client);
3bb12056 1891 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
1892 });
1893 });
1894
1895 it.skip('should handle the TIGHT encoding', function () {
1896 // TODO(directxman12): test this
1897 });
1898
1899 it.skip('should handle the TIGHT_PNG encoding', function () {
1900 // TODO(directxman12): test this
1901 });
1902
1903 it('should handle the DesktopSize pseduo-encoding', function () {
b1dee947
SR
1904 sinon.spy(client._display, 'resize');
1905 send_fbu_msg([{ x: 0, y: 0, width: 20, height: 50, encoding: -223 }], [[]], client);
1906
b1dee947
SR
1907 expect(client._fb_width).to.equal(20);
1908 expect(client._fb_height).to.equal(50);
1909
1910 expect(client._display.resize).to.have.been.calledOnce;
1911 expect(client._display.resize).to.have.been.calledWith(20, 50);
1912 });
1913
4dec490a 1914 describe('the ExtendedDesktopSize pseudo-encoding handler', function () {
4dec490a 1915 beforeEach(function () {
4dec490a 1916 // a really small frame
1917 client._fb_width = 4;
1918 client._fb_height = 4;
02329ab1 1919 client._display.resize(4, 4);
4dec490a 1920 sinon.spy(client._display, 'resize');
4dec490a 1921 });
1922
2c5491e1 1923 function make_screen_data(nr_of_screens) {
2b5f94fa 1924 const data = [];
3949a095
SR
1925 push8(data, nr_of_screens); // number-of-screens
1926 push8(data, 0); // padding
1927 push16(data, 0); // padding
2b5f94fa 1928 for (let i=0; i<nr_of_screens; i += 1) {
3949a095
SR
1929 push32(data, 0); // id
1930 push16(data, 0); // x-position
1931 push16(data, 0); // y-position
1932 push16(data, 20); // width
1933 push16(data, 50); // height
1934 push32(data, 0); // flags
4dec490a 1935 }
1936 return data;
1937 }
1938
1939 it('should handle a resize requested by this client', function () {
2b5f94fa
JD
1940 const reason_for_change = 1; // requested by this client
1941 const status_code = 0; // No error
4dec490a 1942
1943 send_fbu_msg([{ x: reason_for_change, y: status_code,
1944 width: 20, height: 50, encoding: -308 }],
1945 make_screen_data(1), client);
1946
4dec490a 1947 expect(client._fb_width).to.equal(20);
1948 expect(client._fb_height).to.equal(50);
1949
1950 expect(client._display.resize).to.have.been.calledOnce;
1951 expect(client._display.resize).to.have.been.calledWith(20, 50);
4dec490a 1952 });
1953
1954 it('should handle a resize requested by another client', function () {
2b5f94fa
JD
1955 const reason_for_change = 2; // requested by another client
1956 const status_code = 0; // No error
4dec490a 1957
1958 send_fbu_msg([{ x: reason_for_change, y: status_code,
1959 width: 20, height: 50, encoding: -308 }],
1960 make_screen_data(1), client);
1961
4dec490a 1962 expect(client._fb_width).to.equal(20);
1963 expect(client._fb_height).to.equal(50);
1964
1965 expect(client._display.resize).to.have.been.calledOnce;
1966 expect(client._display.resize).to.have.been.calledWith(20, 50);
4dec490a 1967 });
1968
1969 it('should be able to recieve requests which contain data for multiple screens', function () {
2b5f94fa
JD
1970 const reason_for_change = 2; // requested by another client
1971 const status_code = 0; // No error
4dec490a 1972
1973 send_fbu_msg([{ x: reason_for_change, y: status_code,
1974 width: 60, height: 50, encoding: -308 }],
1975 make_screen_data(3), client);
1976
4dec490a 1977 expect(client._fb_width).to.equal(60);
1978 expect(client._fb_height).to.equal(50);
1979
1980 expect(client._display.resize).to.have.been.calledOnce;
1981 expect(client._display.resize).to.have.been.calledWith(60, 50);
4dec490a 1982 });
1983
1984 it('should not handle a failed request', function () {
2b5f94fa
JD
1985 const reason_for_change = 1; // requested by this client
1986 const status_code = 1; // Resize is administratively prohibited
4dec490a 1987
1988 send_fbu_msg([{ x: reason_for_change, y: status_code,
1989 width: 20, height: 50, encoding: -308 }],
1990 make_screen_data(1), client);
1991
1992 expect(client._fb_width).to.equal(4);
1993 expect(client._fb_height).to.equal(4);
1994
1995 expect(client._display.resize).to.not.have.been.called;
4dec490a 1996 });
1997 });
1998
d1050405
PO
1999 describe('the Cursor pseudo-encoding handler', function () {
2000 beforeEach(function () {
2001 sinon.spy(client._cursor, 'change');
2002 });
2003
2004 it('should handle a standard cursor', function () {
2005 const info = { x: 5, y: 7,
2006 width: 4, height: 4,
2007 encoding: -239};
2008 let rect = [];
2009 let expected = [];
2010
2011 for (let i = 0;i < info.width*info.height;i++) {
2012 push32(rect, 0x11223300);
2013 }
2014 push32(rect, 0xa0a0a0a0);
2015
2016 for (let i = 0;i < info.width*info.height/2;i++) {
2017 push32(expected, 0x332211ff);
2018 push32(expected, 0x33221100);
2019 }
2020 expected = new Uint8Array(expected);
2021
2022 send_fbu_msg([info], [rect], client);
2023
2024 expect(client._cursor.change).to.have.been.calledOnce;
2025 expect(client._cursor.change).to.have.been.calledWith(expected, 5, 7, 4, 4);
2026 });
2027
2028 it('should handle an empty cursor', function () {
2029 const info = { x: 0, y: 0,
2030 width: 0, height: 0,
2031 encoding: -239};
2032 const rect = [];
2033
2034 send_fbu_msg([info], [rect], client);
2035
2036 expect(client._cursor.change).to.have.been.calledOnce;
2037 expect(client._cursor.change).to.have.been.calledWith(new Uint8Array, 0, 0, 0, 0);
2038 });
2039
2040 it('should handle a transparent cursor', function () {
2041 const info = { x: 5, y: 7,
2042 width: 4, height: 4,
2043 encoding: -239};
2044 let rect = [];
2045 let expected = [];
2046
2047 for (let i = 0;i < info.width*info.height;i++) {
2048 push32(rect, 0x11223300);
2049 }
2050 push32(rect, 0x00000000);
2051
2052 for (let i = 0;i < info.width*info.height;i++) {
2053 push32(expected, 0x33221100);
2054 }
2055 expected = new Uint8Array(expected);
2056
2057 send_fbu_msg([info], [rect], client);
2058
2059 expect(client._cursor.change).to.have.been.calledOnce;
2060 expect(client._cursor.change).to.have.been.calledWith(expected, 5, 7, 4, 4);
2061 });
2062
2063 describe('dot for empty cursor', function () {
2064 beforeEach(function () {
2065 client.showDotCursor = true;
2066 // Was called when we enabled dot cursor
c9765e50 2067 client._cursor.change.resetHistory();
d1050405
PO
2068 });
2069
2070 it('should show a standard cursor', function () {
2071 const info = { x: 5, y: 7,
2072 width: 4, height: 4,
2073 encoding: -239};
2074 let rect = [];
2075 let expected = [];
2076
2077 for (let i = 0;i < info.width*info.height;i++) {
2078 push32(rect, 0x11223300);
2079 }
2080 push32(rect, 0xa0a0a0a0);
2081
2082 for (let i = 0;i < info.width*info.height/2;i++) {
2083 push32(expected, 0x332211ff);
2084 push32(expected, 0x33221100);
2085 }
2086 expected = new Uint8Array(expected);
2087
2088 send_fbu_msg([info], [rect], client);
2089
2090 expect(client._cursor.change).to.have.been.calledOnce;
2091 expect(client._cursor.change).to.have.been.calledWith(expected, 5, 7, 4, 4);
2092 });
2093
2094 it('should handle an empty cursor', function () {
2095 const info = { x: 0, y: 0,
2096 width: 0, height: 0,
2097 encoding: -239};
2098 const rect = [];
2099 const dot = RFB.cursors.dot;
2100
2101 send_fbu_msg([info], [rect], client);
2102
2103 expect(client._cursor.change).to.have.been.calledOnce;
2104 expect(client._cursor.change).to.have.been.calledWith(dot.rgbaPixels,
2105 dot.hotx,
2106 dot.hoty,
2107 dot.w,
2108 dot.h);
2109 });
2110
2111 it('should handle a transparent cursor', function () {
2112 const info = { x: 5, y: 7,
2113 width: 4, height: 4,
2114 encoding: -239};
2115 let rect = [];
2116 const dot = RFB.cursors.dot;
2117
2118 for (let i = 0;i < info.width*info.height;i++) {
2119 push32(rect, 0x11223300);
2120 }
2121 push32(rect, 0x00000000);
2122
2123 send_fbu_msg([info], [rect], client);
2124
2125 expect(client._cursor.change).to.have.been.calledOnce;
2126 expect(client._cursor.change).to.have.been.calledWith(dot.rgbaPixels,
2127 dot.hotx,
2128 dot.hoty,
2129 dot.w,
2130 dot.h);
2131 });
2132 });
b1dee947
SR
2133 });
2134
296ba51f
NL
2135 describe('the VMware Cursor pseudo-encoding handler', function () {
2136 beforeEach(function () {
2137 sinon.spy(client._cursor, 'change');
2138 });
2139 afterEach(function () {
2140 client._cursor.change.resetHistory();
2141 });
2142
2143 it('should handle the VMware cursor pseudo-encoding', function () {
2144 let data = [0x00, 0x00, 0xff, 0,
2145 0x00, 0xff, 0x00, 0,
2146 0x00, 0xff, 0x00, 0,
2147 0x00, 0x00, 0xff, 0];
2148 let rect = [];
2149 push8(rect, 0);
2150 push8(rect, 0);
2151
2152 //AND-mask
2153 for (let i = 0; i < data.length; i++) {
2154 push8(rect, data[i]);
2155 }
2156 //XOR-mask
2157 for (let i = 0; i < data.length; i++) {
2158 push8(rect, data[i]);
2159 }
2160
2161 send_fbu_msg([{ x: 0, y: 0, width: 2, height: 2,
2162 encoding: 0x574d5664}],
2163 [rect], client);
2164 expect(client._FBU.rects).to.equal(0);
2165 });
2166
2167 it('should handle insufficient cursor pixel data', function () {
2168
2169 // Specified 14x23 pixels for the cursor,
2170 // but only send 2x2 pixels worth of data
2171 let w = 14;
2172 let h = 23;
2173 let data = [0x00, 0x00, 0xff, 0,
2174 0x00, 0xff, 0x00, 0];
2175 let rect = [];
2176
2177 push8(rect, 0);
2178 push8(rect, 0);
2179
2180 //AND-mask
2181 for (let i = 0; i < data.length; i++) {
2182 push8(rect, data[i]);
2183 }
2184 //XOR-mask
2185 for (let i = 0; i < data.length; i++) {
2186 push8(rect, data[i]);
2187 }
2188
2189 send_fbu_msg([{ x: 0, y: 0, width: w, height: h,
2190 encoding: 0x574d5664}],
2191 [rect], client);
2192
2193 // expect one FBU to remain unhandled
2194 expect(client._FBU.rects).to.equal(1);
2195 });
2196
2197 it('should update the cursor when type is classic', function () {
2198 let and_mask =
2199 [0xff, 0xff, 0xff, 0xff, //Transparent
2200 0xff, 0xff, 0xff, 0xff, //Transparent
2201 0x00, 0x00, 0x00, 0x00, //Opaque
2202 0xff, 0xff, 0xff, 0xff]; //Inverted
2203
2204 let xor_mask =
2205 [0x00, 0x00, 0x00, 0x00, //Transparent
2206 0x00, 0x00, 0x00, 0x00, //Transparent
2207 0x11, 0x22, 0x33, 0x44, //Opaque
2208 0xff, 0xff, 0xff, 0x44]; //Inverted
2209
2210 let rect = [];
2211 push8(rect, 0); //cursor_type
2212 push8(rect, 0); //padding
2213 let hotx = 0;
2214 let hoty = 0;
2215 let w = 2;
2216 let h = 2;
2217
2218 //AND-mask
2219 for (let i = 0; i < and_mask.length; i++) {
2220 push8(rect, and_mask[i]);
2221 }
2222 //XOR-mask
2223 for (let i = 0; i < xor_mask.length; i++) {
2224 push8(rect, xor_mask[i]);
2225 }
2226
2227 let expected_rgba = [0x00, 0x00, 0x00, 0x00,
2228 0x00, 0x00, 0x00, 0x00,
2229 0x33, 0x22, 0x11, 0xff,
2230 0x00, 0x00, 0x00, 0xff];
2231
2232 send_fbu_msg([{ x: hotx, y: hoty,
2233 width: w, height: h,
2234 encoding: 0x574d5664}],
2235 [rect], client);
2236
2237 expect(client._cursor.change)
2238 .to.have.been.calledOnce;
2239 expect(client._cursor.change)
2240 .to.have.been.calledWith(expected_rgba,
2241 hotx, hoty,
2242 w, h);
2243 });
2244
2245 it('should update the cursor when type is alpha', function () {
2246 let data = [0xee, 0x55, 0xff, 0x00, // bgra
2247 0x00, 0xff, 0x00, 0xff,
2248 0x00, 0xff, 0x00, 0x22,
2249 0x00, 0xff, 0x00, 0x22,
2250 0x00, 0xff, 0x00, 0x22,
2251 0x00, 0x00, 0xff, 0xee];
2252 let rect = [];
2253 push8(rect, 1); //cursor_type
2254 push8(rect, 0); //padding
2255 let hotx = 0;
2256 let hoty = 0;
2257 let w = 3;
2258 let h = 2;
2259
2260 for (let i = 0; i < data.length; i++) {
2261 push8(rect, data[i]);
2262 }
2263
2264 let expected_rgba = [0xff, 0x55, 0xee, 0x00,
2265 0x00, 0xff, 0x00, 0xff,
2266 0x00, 0xff, 0x00, 0x22,
2267 0x00, 0xff, 0x00, 0x22,
2268 0x00, 0xff, 0x00, 0x22,
2269 0xff, 0x00, 0x00, 0xee];
2270
2271 send_fbu_msg([{ x: hotx, y: hoty,
2272 width: w, height: h,
2273 encoding: 0x574d5664}],
2274 [rect], client);
2275
2276 expect(client._cursor.change)
2277 .to.have.been.calledOnce;
2278 expect(client._cursor.change)
2279 .to.have.been.calledWith(expected_rgba,
2280 hotx, hoty,
2281 w, h);
2282 });
2283
2284 it('should not update cursor when incorrect cursor type given', function () {
2285 let rect = [];
2286 push8(rect, 3); // invalid cursor type
2287 push8(rect, 0); // padding
2288
2289 client._cursor.change.resetHistory();
2290 send_fbu_msg([{ x: 0, y: 0, width: 2, height: 2,
2291 encoding: 0x574d5664}],
2292 [rect], client);
2293
2294 expect(client._cursor.change)
2295 .to.not.have.been.called;
2296 });
2297 });
2298
b1dee947 2299 it('should handle the last_rect pseudo-encoding', function () {
b1dee947
SR
2300 send_fbu_msg([{ x: 0, y: 0, width: 0, height: 0, encoding: -224}], [[]], client, 100);
2301 expect(client._FBU.rects).to.equal(0);
b1dee947 2302 });
ce66b469
NL
2303
2304 it('should handle the DesktopName pseudo-encoding', function () {
2305 let data = [];
8d6f686b
NL
2306 push32(data, 13);
2307 pushString(data, "som€ nam€");
ce66b469
NL
2308
2309 const spy = sinon.spy();
2310 client.addEventListener("desktopname", spy);
2311
2312 send_fbu_msg([{ x: 0, y: 0, width: 0, height: 0, encoding: -307 }], [data], client);
2313
8d6f686b 2314 expect(client._fb_name).to.equal('som€ nam€');
ce66b469 2315 expect(spy).to.have.been.calledOnce;
8d6f686b 2316 expect(spy.args[0][0].detail.name).to.equal('som€ nam€');
ce66b469 2317 });
b1dee947
SR
2318 });
2319 });
2320
b1dee947 2321 describe('XVP Message Handling', function () {
b1dee947 2322 it('should set the XVP version and fire the callback with the version on XVP_INIT', function () {
2b5f94fa 2323 const spy = sinon.spy();
e89eef94 2324 client.addEventListener("capabilities", spy);
b1dee947
SR
2325 client._sock._websocket._receive_data(new Uint8Array([250, 0, 10, 1]));
2326 expect(client._rfb_xvp_ver).to.equal(10);
e89eef94
PO
2327 expect(spy).to.have.been.calledOnce;
2328 expect(spy.args[0][0].detail.capabilities.power).to.be.true;
747b4623 2329 expect(client.capabilities.power).to.be.true;
b1dee947
SR
2330 });
2331
2332 it('should fail on unknown XVP message types', function () {
3bb12056 2333 sinon.spy(client, "_fail");
b1dee947 2334 client._sock._websocket._receive_data(new Uint8Array([250, 0, 10, 237]));
3bb12056 2335 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
2336 });
2337 });
2338
2339 it('should fire the clipboard callback with the retrieved text on ServerCutText', function () {
2b5f94fa
JD
2340 const expected_str = 'cheese!';
2341 const data = [3, 0, 0, 0];
3949a095 2342 push32(data, expected_str.length);
2b5f94fa
JD
2343 for (let i = 0; i < expected_str.length; i++) { data.push(expected_str.charCodeAt(i)); }
2344 const spy = sinon.spy();
e89eef94 2345 client.addEventListener("clipboard", spy);
b1dee947
SR
2346
2347 client._sock._websocket._receive_data(new Uint8Array(data));
b1dee947 2348 expect(spy).to.have.been.calledOnce;
e89eef94 2349 expect(spy.args[0][0].detail.text).to.equal(expected_str);
b1dee947
SR
2350 });
2351
2352 it('should fire the bell callback on Bell', function () {
2b5f94fa 2353 const spy = sinon.spy();
e89eef94 2354 client.addEventListener("bell", spy);
b1dee947 2355 client._sock._websocket._receive_data(new Uint8Array([2]));
e89eef94 2356 expect(spy).to.have.been.calledOnce;
b1dee947
SR
2357 });
2358
3df13262 2359 it('should respond correctly to ServerFence', function () {
0e4808bf
JD
2360 const expected_msg = {_sQ: new Uint8Array(16), _sQlen: 0, flush: () => {}};
2361 const incoming_msg = {_sQ: new Uint8Array(16), _sQlen: 0, flush: () => {}};
3df13262 2362
2b5f94fa 2363 const payload = "foo\x00ab9";
3df13262 2364
2365 // ClientFence and ServerFence are identical in structure
2366 RFB.messages.clientFence(expected_msg, (1<<0) | (1<<1), payload);
2367 RFB.messages.clientFence(incoming_msg, 0xffffffff, payload);
2368
2369 client._sock._websocket._receive_data(incoming_msg._sQ);
2370
2371 expect(client._sock).to.have.sent(expected_msg._sQ);
2372
2373 expected_msg._sQlen = 0;
2374 incoming_msg._sQlen = 0;
2375
2376 RFB.messages.clientFence(expected_msg, (1<<0), payload);
2377 RFB.messages.clientFence(incoming_msg, (1<<0) | (1<<31), payload);
2378
2379 client._sock._websocket._receive_data(incoming_msg._sQ);
2380
2381 expect(client._sock).to.have.sent(expected_msg._sQ);
2382 });
2383
76a86ff5 2384 it('should enable continuous updates on first EndOfContinousUpdates', function () {
0e4808bf 2385 const expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: () => {}};
76a86ff5 2386
2387 RFB.messages.enableContinuousUpdates(expected_msg, true, 0, 0, 640, 20);
2388
2389 expect(client._enabledContinuousUpdates).to.be.false;
2390
2391 client._sock._websocket._receive_data(new Uint8Array([150]));
2392
2393 expect(client._enabledContinuousUpdates).to.be.true;
2394 expect(client._sock).to.have.sent(expected_msg._sQ);
2395 });
2396
2397 it('should disable continuous updates on subsequent EndOfContinousUpdates', function () {
2398 client._enabledContinuousUpdates = true;
2399 client._supportsContinuousUpdates = true;
2400
2401 client._sock._websocket._receive_data(new Uint8Array([150]));
2402
2403 expect(client._enabledContinuousUpdates).to.be.false;
2404 });
2405
2406 it('should update continuous updates on resize', function () {
0e4808bf 2407 const expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: () => {}};
76a86ff5 2408 RFB.messages.enableContinuousUpdates(expected_msg, true, 0, 0, 90, 700);
2409
91d5c625 2410 client._resize(450, 160);
76a86ff5 2411
2412 expect(client._sock._websocket._get_sent_data()).to.have.length(0);
2413
2414 client._enabledContinuousUpdates = true;
2415
91d5c625 2416 client._resize(90, 700);
76a86ff5 2417
2418 expect(client._sock).to.have.sent(expected_msg._sQ);
2419 });
2420
b1dee947 2421 it('should fail on an unknown message type', function () {
3bb12056 2422 sinon.spy(client, "_fail");
b1dee947 2423 client._sock._websocket._receive_data(new Uint8Array([87]));
3bb12056 2424 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
2425 });
2426 });
2427
2428 describe('Asynchronous Events', function () {
2b5f94fa 2429 let client;
057b8fec
PO
2430 beforeEach(function () {
2431 client = make_rfb();
057b8fec 2432 });
b1dee947 2433
057b8fec 2434 describe('Mouse event handlers', function () {
b1dee947 2435 it('should not send button messages in view-only mode', function () {
747b4623 2436 client._viewOnly = true;
057b8fec 2437 sinon.spy(client._sock, 'flush');
747b4623 2438 client._handleMouseButton(0, 0, 1, 0x001);
9ff86fb7 2439 expect(client._sock.flush).to.not.have.been.called;
b1dee947
SR
2440 });
2441
2442 it('should not send movement messages in view-only mode', function () {
747b4623 2443 client._viewOnly = true;
057b8fec 2444 sinon.spy(client._sock, 'flush');
747b4623 2445 client._handleMouseMove(0, 0);
9ff86fb7 2446 expect(client._sock.flush).to.not.have.been.called;
b1dee947
SR
2447 });
2448
2449 it('should send a pointer event on mouse button presses', function () {
747b4623 2450 client._handleMouseButton(10, 12, 1, 0x001);
0e4808bf 2451 const pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: () => {}};
9ff86fb7
SR
2452 RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x001);
2453 expect(client._sock).to.have.sent(pointer_msg._sQ);
b1dee947
SR
2454 });
2455
d02a99f0 2456 it('should send a mask of 1 on mousedown', function () {
747b4623 2457 client._handleMouseButton(10, 12, 1, 0x001);
0e4808bf 2458 const pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: () => {}};
cf0236de 2459 RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x001);
9ff86fb7 2460 expect(client._sock).to.have.sent(pointer_msg._sQ);
d02a99f0
SR
2461 });
2462
2463 it('should send a mask of 0 on mouseup', function () {
3b4fd003 2464 client._mouse_buttonMask = 0x001;
747b4623 2465 client._handleMouseButton(10, 12, 0, 0x001);
0e4808bf 2466 const pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: () => {}};
9ff86fb7
SR
2467 RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x000);
2468 expect(client._sock).to.have.sent(pointer_msg._sQ);
d02a99f0
SR
2469 });
2470
b1dee947 2471 it('should send a pointer event on mouse movement', function () {
747b4623 2472 client._handleMouseMove(10, 12);
0e4808bf 2473 const pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: () => {}};
9ff86fb7
SR
2474 RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x000);
2475 expect(client._sock).to.have.sent(pointer_msg._sQ);
b1dee947
SR
2476 });
2477
2478 it('should set the button mask so that future mouse movements use it', function () {
747b4623
PO
2479 client._handleMouseButton(10, 12, 1, 0x010);
2480 client._handleMouseMove(13, 9);
0e4808bf 2481 const pointer_msg = {_sQ: new Uint8Array(12), _sQlen: 0, flush: () => {}};
9ff86fb7
SR
2482 RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x010);
2483 RFB.messages.pointerEvent(pointer_msg, 13, 9, 0x010);
2484 expect(client._sock).to.have.sent(pointer_msg._sQ);
b1dee947 2485 });
b1dee947
SR
2486 });
2487
2488 describe('Keyboard Event Handlers', function () {
b1dee947 2489 it('should send a key message on a key press', function () {
747b4623 2490 client._handleKeyEvent(0x41, 'KeyA', true);
0e4808bf 2491 const key_msg = {_sQ: new Uint8Array(8), _sQlen: 0, flush: () => {}};
d0703d1b 2492 RFB.messages.keyEvent(key_msg, 0x41, 1);
9ff86fb7 2493 expect(client._sock).to.have.sent(key_msg._sQ);
b1dee947
SR
2494 });
2495
2496 it('should not send messages in view-only mode', function () {
747b4623 2497 client._viewOnly = true;
057b8fec 2498 sinon.spy(client._sock, 'flush');
747b4623 2499 client._handleKeyEvent('a', 'KeyA', true);
9ff86fb7 2500 expect(client._sock.flush).to.not.have.been.called;
b1dee947
SR
2501 });
2502 });
2503
2504 describe('WebSocket event handlers', function () {
b1dee947 2505 // message events
d80d9d37 2506 it('should do nothing if we receive an empty message and have nothing in the queue', function () {
b1dee947 2507 client._normal_msg = sinon.spy();
38781d93 2508 client._sock._websocket._receive_data(new Uint8Array([]));
b1dee947
SR
2509 expect(client._normal_msg).to.not.have.been.called;
2510 });
2511
c2a4d3ef 2512 it('should handle a message in the connected state as a normal message', function () {
b1dee947 2513 client._normal_msg = sinon.spy();
38781d93 2514 client._sock._websocket._receive_data(new Uint8Array([1, 2, 3]));
70e67958 2515 expect(client._normal_msg).to.have.been.called;
b1dee947
SR
2516 });
2517
2518 it('should handle a message in any non-disconnected/failed state like an init message', function () {
2f4516f2 2519 client._rfb_connection_state = 'connecting';
c00ee156 2520 client._rfb_init_state = 'ProtocolVersion';
b1dee947 2521 client._init_msg = sinon.spy();
38781d93 2522 client._sock._websocket._receive_data(new Uint8Array([1, 2, 3]));
70e67958 2523 expect(client._init_msg).to.have.been.called;
b1dee947
SR
2524 });
2525
9535539b 2526 it('should process all normal messages directly', function () {
2b5f94fa 2527 const spy = sinon.spy();
e89eef94 2528 client.addEventListener("bell", spy);
b1dee947 2529 client._sock._websocket._receive_data(new Uint8Array([0x02, 0x02]));
e89eef94 2530 expect(spy).to.have.been.calledTwice;
b1dee947
SR
2531 });
2532
2533 // open events
c2a4d3ef 2534 it('should update the state to ProtocolVersion on open (if the state is "connecting")', function () {
9b84f516 2535 client = new RFB(document.createElement('div'), 'wss://host:8675');
2f4516f2 2536 this.clock.tick();
b1dee947 2537 client._sock._websocket._open();
c00ee156 2538 expect(client._rfb_init_state).to.equal('ProtocolVersion');
b1dee947
SR
2539 });
2540
2541 it('should fail if we are not currently ready to connect and we get an "open" event', function () {
3bb12056 2542 sinon.spy(client, "_fail");
bb25d3d6 2543 client._rfb_connection_state = 'connected';
b1dee947 2544 client._sock._websocket._open();
3bb12056 2545 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
2546 });
2547
2548 // close events
c2a4d3ef 2549 it('should transition to "disconnected" from "disconnecting" on a close event', function () {
2b5f94fa 2550 const real = client._sock._websocket.close;
651c23ec 2551 client._sock._websocket.close = () => {};
bb25d3d6
PO
2552 client.disconnect();
2553 expect(client._rfb_connection_state).to.equal('disconnecting');
2554 client._sock._websocket.close = real;
b1dee947 2555 client._sock._websocket.close();
c00ee156 2556 expect(client._rfb_connection_state).to.equal('disconnected');
b1dee947
SR
2557 });
2558
b45905ab 2559 it('should fail if we get a close event while connecting', function () {
3bb12056 2560 sinon.spy(client, "_fail");
b45905ab 2561 client._rfb_connection_state = 'connecting';
b1dee947 2562 client._sock._websocket.close();
3bb12056 2563 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
2564 });
2565
155d78b3
JS
2566 it('should unregister close event handler', function () {
2567 sinon.spy(client._sock, 'off');
bb25d3d6 2568 client.disconnect();
155d78b3
JS
2569 client._sock._websocket.close();
2570 expect(client._sock.off).to.have.been.calledWith('close');
2571 });
2572
b1dee947
SR
2573 // error events do nothing
2574 });
2575 });
2576});