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