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