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