1 const expect
= chai
.expect
;
3 import RFB
from '../core/rfb.js';
4 import Websock
from '../core/websock.js';
5 import { encodings
} from '../core/encodings.js';
7 import FakeWebSocket
from './fake.websocket.js';
8 import sinon
from '../vendor/sinon.js';
10 /* UIEvent constructor polyfill for IE */
12 if (typeof window
.UIEvent
=== "function") return;
14 function UIEvent ( event
, params
) {
15 params
= params
|| { bubbles
: false, cancelable
: false, view
: window
, detail
: undefined };
16 const evt
= document
.createEvent( 'UIEvent' );
17 evt
.initUIEvent( event
, params
.bubbles
, params
.cancelable
, params
.view
, params
.detail
);
21 UIEvent
.prototype = window
.UIEvent
.prototype;
23 window
.UIEvent
= UIEvent
;
26 function push8(arr
, num
) {
31 function push16(arr
, num
) {
33 arr
.push((num
>> 8) & 0xFF,
37 function push32(arr
, num
) {
39 arr
.push((num
>> 24) & 0xFF,
45 describe('Remote Frame Buffer Protocol Client', function() {
49 before(FakeWebSocket
.replace
);
50 after(FakeWebSocket
.restore
);
53 this.clock
= clock
= sinon
.useFakeTimers();
54 // sinon doesn't support this yet
55 raf
= window
.requestAnimationFrame
;
56 window
.requestAnimationFrame
= setTimeout
;
57 // Use a single set of buffers instead of reallocating to
59 const sock
= new Websock();
60 const _sQ
= new Uint8Array(sock
._sQbufferSize
);
61 const rQ
= new Uint8Array(sock
._rQbufferSize
);
63 Websock
.prototype._old_allocate_buffers
= Websock
.prototype._allocate_buffers
;
64 Websock
.prototype._allocate_buffers = function () {
72 Websock
.prototype._allocate_buffers
= Websock
.prototype._old_allocate_buffers
;
74 window
.requestAnimationFrame
= raf
;
80 beforeEach(function () {
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
);
87 // And track all created RFB objects
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
) {
95 expect(rfb
._disconnect
).to
.have
.been
.called
;
99 document
.body
.removeChild(container
);
103 function make_rfb (url
, options
) {
104 url
= url
|| 'wss://host:8675';
105 const rfb
= new RFB(container
, url
, options
);
107 rfb
._sock
._websocket
._open();
108 rfb
._rfb_connection_state
= 'connected';
109 sinon
.spy(rfb
, "_disconnect");
114 describe('Connecting/Disconnecting', function () {
115 describe('#RFB', function () {
116 it('should set the current state to "connecting"', function () {
117 const client
= new RFB(document
.createElement('div'), 'wss://host:8675');
118 client
._rfb_connection_state
= '';
120 expect(client
._rfb_connection_state
).to
.equal('connecting');
123 it('should actually connect to the websocket', function () {
124 const client
= new RFB(document
.createElement('div'), 'ws://HOST:8675/PATH');
125 sinon
.spy(client
._sock
, 'open');
127 expect(client
._sock
.open
).to
.have
.been
.calledOnce
;
128 expect(client
._sock
.open
).to
.have
.been
.calledWith('ws://HOST:8675/PATH');
132 describe('#disconnect', function () {
134 beforeEach(function () {
138 it('should go to state "disconnecting" before "disconnected"', function () {
139 sinon
.spy(client
, '_updateConnectionState');
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');
149 it('should unregister error event handler', function () {
150 sinon
.spy(client
._sock
, 'off');
152 expect(client
._sock
.off
).to
.have
.been
.calledWith('error');
155 it('should unregister message event handler', function () {
156 sinon
.spy(client
._sock
, 'off');
158 expect(client
._sock
.off
).to
.have
.been
.calledWith('message');
161 it('should unregister open event handler', function () {
162 sinon
.spy(client
._sock
, 'off');
164 expect(client
._sock
.off
).to
.have
.been
.calledWith('open');
168 describe('#sendCredentials', function () {
170 beforeEach(function () {
172 client
._rfb_connection_state
= 'connecting';
175 it('should set the rfb credentials properly"', function () {
176 client
.sendCredentials({ password
: 'pass' });
177 expect(client
._rfb_credentials
).to
.deep
.equal({ password
: 'pass' });
180 it('should call init_msg "soon"', function () {
181 client
._init_msg
= sinon
.spy();
182 client
.sendCredentials({ password
: 'pass' });
184 expect(client
._init_msg
).to
.have
.been
.calledOnce
;
189 describe('Public API Basic Behavior', function () {
191 beforeEach(function () {
195 describe('#sendCtrlAlDel', function () {
196 it('should sent ctrl[down]-alt[down]-del[down] then del[up]-alt[up]-ctrl[up]', function () {
197 const expected
= {_sQ
: new Uint8Array(48), _sQlen
: 0, flush
: () => {}};
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);
205 client
.sendCtrlAltDel();
206 expect(client
._sock
).to
.have
.sent(expected
._sQ
);
209 it('should not send the keys if we are not in a normal state', function () {
210 sinon
.spy(client
._sock
, 'flush');
211 client
._rfb_connection_state
= "connecting";
212 client
.sendCtrlAltDel();
213 expect(client
._sock
.flush
).to
.not
.have
.been
.called
;
216 it('should not send the keys if we are set as view_only', function () {
217 sinon
.spy(client
._sock
, 'flush');
218 client
._viewOnly
= true;
219 client
.sendCtrlAltDel();
220 expect(client
._sock
.flush
).to
.not
.have
.been
.called
;
224 describe('#sendKey', function () {
225 it('should send a single key with the given code and state (down = true)', function () {
226 const expected
= {_sQ
: new Uint8Array(8), _sQlen
: 0, flush
: () => {}};
227 RFB
.messages
.keyEvent(expected
, 123, 1);
228 client
.sendKey(123, 'Key123', true);
229 expect(client
._sock
).to
.have
.sent(expected
._sQ
);
232 it('should send both a down and up event if the state is not specified', function () {
233 const expected
= {_sQ
: new Uint8Array(16), _sQlen
: 0, flush
: () => {}};
234 RFB
.messages
.keyEvent(expected
, 123, 1);
235 RFB
.messages
.keyEvent(expected
, 123, 0);
236 client
.sendKey(123, 'Key123');
237 expect(client
._sock
).to
.have
.sent(expected
._sQ
);
240 it('should not send the key if we are not in a normal state', function () {
241 sinon
.spy(client
._sock
, 'flush');
242 client
._rfb_connection_state
= "connecting";
243 client
.sendKey(123, 'Key123');
244 expect(client
._sock
.flush
).to
.not
.have
.been
.called
;
247 it('should not send the key if we are set as view_only', function () {
248 sinon
.spy(client
._sock
, 'flush');
249 client
._viewOnly
= true;
250 client
.sendKey(123, 'Key123');
251 expect(client
._sock
.flush
).to
.not
.have
.been
.called
;
254 it('should send QEMU extended events if supported', function () {
255 client
._qemuExtKeyEventSupported
= true;
256 const expected
= {_sQ
: new Uint8Array(12), _sQlen
: 0, flush
: () => {}};
257 RFB
.messages
.QEMUExtendedKeyEvent(expected
, 0x20, true, 0x0039);
258 client
.sendKey(0x20, 'Space', true);
259 expect(client
._sock
).to
.have
.sent(expected
._sQ
);
262 it('should not send QEMU extended events if unknown key code', function () {
263 client
._qemuExtKeyEventSupported
= true;
264 const expected
= {_sQ
: new Uint8Array(8), _sQlen
: 0, flush
: () => {}};
265 RFB
.messages
.keyEvent(expected
, 123, 1);
266 client
.sendKey(123, 'FooBar', true);
267 expect(client
._sock
).to
.have
.sent(expected
._sQ
);
271 describe('#focus', function () {
272 it('should move focus to canvas object', function () {
273 client
._canvas
.focus
= sinon
.spy();
275 expect(client
._canvas
.focus
).to
.have
.been
.called
.once
;
279 describe('#blur', function () {
280 it('should remove focus from canvas object', function () {
281 client
._canvas
.blur
= sinon
.spy();
283 expect(client
._canvas
.blur
).to
.have
.been
.called
.once
;
287 describe('#clipboardPasteFrom', function () {
288 it('should send the given text in a paste event', function () {
289 const expected
= {_sQ
: new Uint8Array(11), _sQlen
: 0,
290 _sQbufferSize
: 11, flush
: () => {}};
291 RFB
.messages
.clientCutText(expected
, 'abc');
292 client
.clipboardPasteFrom('abc');
293 expect(client
._sock
).to
.have
.sent(expected
._sQ
);
296 it('should flush multiple times for large clipboards', function () {
297 sinon
.spy(client
._sock
, 'flush');
299 for (let i
= 0; i
< client
._sock
._sQbufferSize
+ 100; i
++) {
302 client
.clipboardPasteFrom(long_text
);
303 expect(client
._sock
.flush
).to
.have
.been
.calledTwice
;
306 it('should not send the text if we are not in a normal state', function () {
307 sinon
.spy(client
._sock
, 'flush');
308 client
._rfb_connection_state
= "connecting";
309 client
.clipboardPasteFrom('abc');
310 expect(client
._sock
.flush
).to
.not
.have
.been
.called
;
314 describe("XVP operations", function () {
315 beforeEach(function () {
316 client
._rfb_xvp_ver
= 1;
319 it('should send the shutdown signal on #machineShutdown', function () {
320 client
.machineShutdown();
321 expect(client
._sock
).to
.have
.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x02]));
324 it('should send the reboot signal on #machineReboot', function () {
325 client
.machineReboot();
326 expect(client
._sock
).to
.have
.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x03]));
329 it('should send the reset signal on #machineReset', function () {
330 client
.machineReset();
331 expect(client
._sock
).to
.have
.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x04]));
334 it('should not send XVP operations with higher versions than we support', function () {
335 sinon
.spy(client
._sock
, 'flush');
337 expect(client
._sock
.flush
).to
.not
.have
.been
.called
;
342 describe('Clipping', function () {
344 beforeEach(function () {
346 container
.style
.width
= '70px';
347 container
.style
.height
= '80px';
348 client
.clipViewport
= true;
351 it('should update display clip state when changing the property', function () {
352 const spy
= sinon
.spy(client
._display
, "clipViewport", ["set"]);
354 client
.clipViewport
= false;
355 expect(spy
.set).to
.have
.been
.calledOnce
;
356 expect(spy
.set).to
.have
.been
.calledWith(false);
359 client
.clipViewport
= true;
360 expect(spy
.set).to
.have
.been
.calledOnce
;
361 expect(spy
.set).to
.have
.been
.calledWith(true);
364 it('should update the viewport when the container size changes', function () {
365 sinon
.spy(client
._display
, "viewportChangeSize");
367 container
.style
.width
= '40px';
368 container
.style
.height
= '50px';
369 const event
= new UIEvent('resize');
370 window
.dispatchEvent(event
);
373 expect(client
._display
.viewportChangeSize
).to
.have
.been
.calledOnce
;
374 expect(client
._display
.viewportChangeSize
).to
.have
.been
.calledWith(40, 50);
377 it('should update the viewport when the remote session resizes', function () {
378 // Simple ExtendedDesktopSize FBU message
379 const incoming
= [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
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 ];
385 sinon
.spy(client
._display
, "viewportChangeSize");
387 client
._sock
._websocket
._receive_data(new Uint8Array(incoming
));
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);
395 it('should not update the viewport if not clipping', function () {
396 client
.clipViewport
= false;
397 sinon
.spy(client
._display
, "viewportChangeSize");
399 container
.style
.width
= '40px';
400 container
.style
.height
= '50px';
401 const event
= new UIEvent('resize');
402 window
.dispatchEvent(event
);
405 expect(client
._display
.viewportChangeSize
).to
.not
.have
.been
.called
;
408 it('should not update the viewport if scaling', function () {
409 client
.scaleViewport
= true;
410 sinon
.spy(client
._display
, "viewportChangeSize");
412 container
.style
.width
= '40px';
413 container
.style
.height
= '50px';
414 const event
= new UIEvent('resize');
415 window
.dispatchEvent(event
);
418 expect(client
._display
.viewportChangeSize
).to
.not
.have
.been
.called
;
421 describe('Dragging', function () {
422 beforeEach(function () {
423 client
.dragViewport
= true;
424 sinon
.spy(RFB
.messages
, "pointerEvent");
427 afterEach(function () {
428 RFB
.messages
.pointerEvent
.restore();
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
;
436 it('should send button messages when release without movement', function () {
438 client
._handleMouseButton(13, 9, 0x001);
439 client
._handleMouseButton(13, 9, 0x000);
440 expect(RFB
.messages
.pointerEvent
).to
.have
.been
.calledTwice
;
442 RFB
.messages
.pointerEvent
.reset();
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
;
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
;
457 it('should be initiate viewport dragging on sufficient movement', function () {
458 sinon
.spy(client
._display
, "viewportChangePos");
460 // Too small movement
462 client
._handleMouseButton(13, 9, 0x001);
463 client
._handleMouseMove(18, 9);
465 expect(RFB
.messages
.pointerEvent
).to
.not
.have
.been
.called
;
466 expect(client
._display
.viewportChangePos
).to
.not
.have
.been
.called
;
468 // Sufficient movement
470 client
._handleMouseMove(43, 9);
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);
476 client
._display
.viewportChangePos
.reset();
478 // Now a small movement should move right away
480 client
._handleMouseMove(43, 14);
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);
487 it('should not send button messages when dragging ends', function () {
488 // First the movement
490 client
._handleMouseButton(13, 9, 0x001);
491 client
._handleMouseMove(43, 9);
492 client
._handleMouseButton(43, 9, 0x000);
494 expect(RFB
.messages
.pointerEvent
).to
.not
.have
.been
.called
;
497 it('should terminate viewport dragging on a button up event', function () {
498 // First the dragging movement
500 client
._handleMouseButton(13, 9, 0x001);
501 client
._handleMouseMove(43, 9);
502 client
._handleMouseButton(43, 9, 0x000);
504 // Another movement now should not move the viewport
506 sinon
.spy(client
._display
, "viewportChangePos");
508 client
._handleMouseMove(43, 59);
510 expect(client
._display
.viewportChangePos
).to
.not
.have
.been
.called
;
515 describe('Scaling', function () {
517 beforeEach(function () {
519 container
.style
.width
= '70px';
520 container
.style
.height
= '80px';
521 client
.scaleViewport
= true;
524 it('should update display scale factor when changing the property', function () {
525 const spy
= sinon
.spy(client
._display
, "scale", ["set"]);
526 sinon
.spy(client
._display
, "autoscale");
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
;
533 client
.scaleViewport
= true;
534 expect(client
._display
.autoscale
).to
.have
.been
.calledOnce
;
535 expect(client
._display
.autoscale
).to
.have
.been
.calledWith(70, 80);
538 it('should update the clipping setting when changing the property', function () {
539 client
.clipViewport
= true;
541 const spy
= sinon
.spy(client
._display
, "clipViewport", ["set"]);
543 client
.scaleViewport
= false;
544 expect(spy
.set).to
.have
.been
.calledOnce
;
545 expect(spy
.set).to
.have
.been
.calledWith(true);
549 client
.scaleViewport
= true;
550 expect(spy
.set).to
.have
.been
.calledOnce
;
551 expect(spy
.set).to
.have
.been
.calledWith(false);
554 it('should update the scaling when the container size changes', function () {
555 sinon
.spy(client
._display
, "autoscale");
557 container
.style
.width
= '40px';
558 container
.style
.height
= '50px';
559 const event
= new UIEvent('resize');
560 window
.dispatchEvent(event
);
563 expect(client
._display
.autoscale
).to
.have
.been
.calledOnce
;
564 expect(client
._display
.autoscale
).to
.have
.been
.calledWith(40, 50);
567 it('should update the scaling when the remote session resizes', function () {
568 // Simple ExtendedDesktopSize FBU message
569 const incoming
= [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
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 ];
575 sinon
.spy(client
._display
, "autoscale");
577 client
._sock
._websocket
._receive_data(new Uint8Array(incoming
));
579 expect(client
._display
.autoscale
).to
.have
.been
.calledOnce
;
580 expect(client
._display
.autoscale
).to
.have
.been
.calledWith(70, 80);
583 it('should not update the display scale factor if not scaling', function () {
584 client
.scaleViewport
= false;
586 sinon
.spy(client
._display
, "autoscale");
588 container
.style
.width
= '40px';
589 container
.style
.height
= '50px';
590 const event
= new UIEvent('resize');
591 window
.dispatchEvent(event
);
594 expect(client
._display
.autoscale
).to
.not
.have
.been
.called
;
598 describe('Remote resize', function () {
600 beforeEach(function () {
602 client
._supportsSetDesktopSize
= true;
603 client
.resizeSession
= true;
604 container
.style
.width
= '70px';
605 container
.style
.height
= '80px';
606 sinon
.spy(RFB
.messages
, "setDesktopSize");
609 afterEach(function () {
610 RFB
.messages
.setDesktopSize
.restore();
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
;
620 it('should request a resize when initially connecting', function () {
621 // Simple ExtendedDesktopSize FBU message
622 const incoming
= [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
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 ];
628 // First message should trigger a resize
630 client
._supportsSetDesktopSize
= false;
632 client
._sock
._websocket
._receive_data(new Uint8Array(incoming
));
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);
637 RFB
.messages
.setDesktopSize
.reset();
639 // Second message should not trigger a resize
641 client
._sock
._websocket
._receive_data(new Uint8Array(incoming
));
643 expect(RFB
.messages
.setDesktopSize
).to
.not
.have
.been
.called
;
646 it('should request a resize when the container resizes', function () {
647 container
.style
.width
= '40px';
648 container
.style
.height
= '50px';
649 const event
= new UIEvent('resize');
650 window
.dispatchEvent(event
);
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);
657 it('should not resize until the container size is stable', function () {
658 container
.style
.width
= '20px';
659 container
.style
.height
= '30px';
660 const event1
= new UIEvent('resize');
661 window
.dispatchEvent(event1
);
664 expect(RFB
.messages
.setDesktopSize
).to
.not
.have
.been
.called
;
666 container
.style
.width
= '40px';
667 container
.style
.height
= '50px';
668 const event2
= new UIEvent('resize');
669 window
.dispatchEvent(event2
);
672 expect(RFB
.messages
.setDesktopSize
).to
.not
.have
.been
.called
;
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);
680 it('should not resize when resize is disabled', function () {
681 client
._resizeSession
= false;
683 container
.style
.width
= '40px';
684 container
.style
.height
= '50px';
685 const event
= new UIEvent('resize');
686 window
.dispatchEvent(event
);
689 expect(RFB
.messages
.setDesktopSize
).to
.not
.have
.been
.called
;
692 it('should not resize when resize is not supported', function () {
693 client
._supportsSetDesktopSize
= false;
695 container
.style
.width
= '40px';
696 container
.style
.height
= '50px';
697 const event
= new UIEvent('resize');
698 window
.dispatchEvent(event
);
701 expect(RFB
.messages
.setDesktopSize
).to
.not
.have
.been
.called
;
704 it('should not resize when in view only mode', function () {
705 client
._viewOnly
= true;
707 container
.style
.width
= '40px';
708 container
.style
.height
= '50px';
709 const event
= new UIEvent('resize');
710 window
.dispatchEvent(event
);
713 expect(RFB
.messages
.setDesktopSize
).to
.not
.have
.been
.called
;
716 it('should not try to override a server resize', function () {
717 // Simple ExtendedDesktopSize FBU message
718 const incoming
= [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
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 ];
724 client
._sock
._websocket
._receive_data(new Uint8Array(incoming
));
726 expect(RFB
.messages
.setDesktopSize
).to
.not
.have
.been
.called
;
730 describe('Misc Internals', function () {
731 describe('#_updateConnectionState', function () {
733 beforeEach(function () {
737 it('should clear the disconnect timer if the state is not "disconnecting"', function () {
738 const spy
= sinon
.spy();
739 client
._disconnTimer
= setTimeout(spy
, 50);
740 client
._rfb_connection_state
= 'connecting';
741 client
._updateConnectionState('connected');
743 expect(spy
).to
.not
.have
.been
.called
;
744 expect(client
._disconnTimer
).to
.be
.null;
747 it('should set the rfb_connection_state', function () {
748 client
._rfb_connection_state
= 'connecting';
749 client
._updateConnectionState('connected');
750 expect(client
._rfb_connection_state
).to
.equal('connected');
753 it('should not change the state when we are disconnected', function () {
755 expect(client
._rfb_connection_state
).to
.equal('disconnected');
756 client
._updateConnectionState('connecting');
757 expect(client
._rfb_connection_state
).to
.not
.equal('connecting');
760 it('should ignore state changes to the same state', function () {
761 const connectSpy
= sinon
.spy();
762 client
.addEventListener("connect", connectSpy
);
764 expect(client
._rfb_connection_state
).to
.equal('connected');
765 client
._updateConnectionState('connected');
766 expect(connectSpy
).to
.not
.have
.been
.called
;
770 const disconnectSpy
= sinon
.spy();
771 client
.addEventListener("disconnect", disconnectSpy
);
773 expect(client
._rfb_connection_state
).to
.equal('disconnected');
774 client
._updateConnectionState('disconnected');
775 expect(disconnectSpy
).to
.not
.have
.been
.called
;
778 it('should ignore illegal state changes', function () {
779 const spy
= sinon
.spy();
780 client
.addEventListener("disconnect", spy
);
781 client
._updateConnectionState('disconnected');
782 expect(client
._rfb_connection_state
).to
.not
.equal('disconnected');
783 expect(spy
).to
.not
.have
.been
.called
;
787 describe('#_fail', function () {
789 beforeEach(function () {
793 it('should close the WebSocket connection', function () {
794 sinon
.spy(client
._sock
, 'close');
796 expect(client
._sock
.close
).to
.have
.been
.calledOnce
;
799 it('should transition to disconnected', function () {
800 sinon
.spy(client
, '_updateConnectionState');
802 this.clock
.tick(2000);
803 expect(client
._updateConnectionState
).to
.have
.been
.called
;
804 expect(client
._rfb_connection_state
).to
.equal('disconnected');
807 it('should set clean_disconnect variable', function () {
808 client
._rfb_clean_disconnect
= true;
809 client
._rfb_connection_state
= 'connected';
811 expect(client
._rfb_clean_disconnect
).to
.be
.false;
814 it('should result in disconnect event with clean set to false', function () {
815 client
._rfb_connection_state
= 'connected';
816 const spy
= sinon
.spy();
817 client
.addEventListener("disconnect", spy
);
819 this.clock
.tick(2000);
820 expect(spy
).to
.have
.been
.calledOnce
;
821 expect(spy
.args
[0][0].detail
.clean
).to
.be
.false;
827 describe('Connection States', function () {
828 describe('connecting', function () {
829 it('should open the websocket connection', function () {
830 const client
= new RFB(document
.createElement('div'),
831 'ws://HOST:8675/PATH');
832 sinon
.spy(client
._sock
, 'open');
834 expect(client
._sock
.open
).to
.have
.been
.calledOnce
;
838 describe('connected', function () {
840 beforeEach(function () {
844 it('should result in a connect event if state becomes connected', function () {
845 const spy
= sinon
.spy();
846 client
.addEventListener("connect", spy
);
847 client
._rfb_connection_state
= 'connecting';
848 client
._updateConnectionState('connected');
849 expect(spy
).to
.have
.been
.calledOnce
;
852 it('should not result in a connect event if the state is not "connected"', function () {
853 const spy
= sinon
.spy();
854 client
.addEventListener("connect", spy
);
855 client
._sock
._websocket
.open
= () => {}; // explicitly don't call onopen
856 client
._updateConnectionState('connecting');
857 expect(spy
).to
.not
.have
.been
.called
;
861 describe('disconnecting', function () {
863 beforeEach(function () {
867 it('should force disconnect if we do not call Websock.onclose within the disconnection timeout', function () {
868 sinon
.spy(client
, '_updateConnectionState');
869 client
._sock
._websocket
.close
= () => {}; // explicitly don't call onclose
870 client
._updateConnectionState('disconnecting');
871 this.clock
.tick(3 * 1000);
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");
877 it('should not fail if Websock.onclose gets called within the disconnection timeout', function () {
878 client
._updateConnectionState('disconnecting');
879 this.clock
.tick(3 * 1000 / 2);
880 client
._sock
._websocket
.close();
881 this.clock
.tick(3 * 1000 / 2 + 1);
882 expect(client
._rfb_connection_state
).to
.equal('disconnected');
885 it('should close the WebSocket connection', function () {
886 sinon
.spy(client
._sock
, 'close');
887 client
._updateConnectionState('disconnecting');
888 expect(client
._sock
.close
).to
.have
.been
.calledOnce
;
891 it('should not result in a disconnect event', function () {
892 const spy
= sinon
.spy();
893 client
.addEventListener("disconnect", spy
);
894 client
._sock
._websocket
.close
= () => {}; // explicitly don't call onclose
895 client
._updateConnectionState('disconnecting');
896 expect(spy
).to
.not
.have
.been
.called
;
900 describe('disconnected', function () {
902 beforeEach(function () {
903 client
= new RFB(document
.createElement('div'), 'ws://HOST:8675/PATH');
906 it('should result in a disconnect event if state becomes "disconnected"', function () {
907 const spy
= sinon
.spy();
908 client
.addEventListener("disconnect", spy
);
909 client
._rfb_connection_state
= 'disconnecting';
910 client
._updateConnectionState('disconnected');
911 expect(spy
).to
.have
.been
.calledOnce
;
912 expect(spy
.args
[0][0].detail
.clean
).to
.be
.true;
915 it('should result in a disconnect event without msg when no reason given', function () {
916 const spy
= sinon
.spy();
917 client
.addEventListener("disconnect", spy
);
918 client
._rfb_connection_state
= 'disconnecting';
919 client
._rfb_disconnect_reason
= "";
920 client
._updateConnectionState('disconnected');
921 expect(spy
).to
.have
.been
.calledOnce
;
922 expect(spy
.args
[0].length
).to
.equal(1);
927 describe('Protocol Initialization States', function () {
929 beforeEach(function () {
931 client
._rfb_connection_state
= 'connecting';
934 describe('ProtocolVersion', function () {
935 function send_ver (ver
, client
) {
936 const arr
= new Uint8Array(12);
937 for (let i
= 0; i
< ver
.length
; i
++) {
938 arr
[i
+4] = ver
.charCodeAt(i
);
940 arr
[0] = 'R'; arr
[1] = 'F'; arr
[2] = 'B'; arr
[3] = ' ';
942 client
._sock
._websocket
._receive_data(arr
);
945 describe('version parsing', function () {
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);
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);
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);
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);
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);
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);
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);
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);
986 it('should fail on an invalid version', function () {
987 sinon
.spy(client
, "_fail");
988 send_ver('002.000', client
);
989 expect(client
._fail
).to
.have
.been
.calledOnce
;
993 it('should send back the interpreted version', function () {
994 send_ver('004.000', client
);
996 const expected_str
= 'RFB 003.008\n';
998 for (let i
= 0; i
< expected_str
.length
; i
++) {
999 expected
[i
] = expected_str
.charCodeAt(i
);
1002 expect(client
._sock
).to
.have
.sent(new Uint8Array(expected
));
1005 it('should transition to the Security state on successful negotiation', function () {
1006 send_ver('003.008', client
);
1007 expect(client
._rfb_init_state
).to
.equal('Security');
1010 describe('Repeater', function () {
1011 beforeEach(function () {
1012 client
= make_rfb('wss://host:8675', { repeaterID
: "12345" });
1013 client
._rfb_connection_state
= 'connecting';
1016 it('should interpret version 000.000 as a repeater', function () {
1017 send_ver('000.000', client
);
1018 expect(client
._rfb_version
).to
.equal(0);
1020 const sent_data
= client
._sock
._websocket
._get_sent_data();
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);
1025 it('should handle two step repeater negotiation', function () {
1026 send_ver('000.000', client
);
1027 send_ver('003.008', client
);
1028 expect(client
._rfb_version
).to
.equal(3.8);
1033 describe('Security', function () {
1034 beforeEach(function () {
1035 client
._rfb_init_state
= 'Security';
1038 it('should simply receive the auth scheme when for versions < 3.7', function () {
1039 client
._rfb_version
= 3.6;
1040 const auth_scheme_raw
= [1, 2, 3, 4];
1041 const auth_scheme
= (auth_scheme_raw
[0] << 24) + (auth_scheme_raw
[1] << 16) +
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
);
1047 it('should prefer no authentication is possible', function () {
1048 client
._rfb_version
= 3.7;
1049 const auth_schemes
= [2, 1, 3];
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]));
1055 it('should choose for the most prefered scheme possible for versions >= 3.7', function () {
1056 client
._rfb_version
= 3.7;
1057 const auth_schemes
= [2, 22, 16];
1058 client
._sock
._websocket
._receive_data(auth_schemes
);
1059 expect(client
._rfb_auth_scheme
).to
.equal(22);
1060 expect(client
._sock
).to
.have
.sent(new Uint8Array([22]));
1063 it('should fail if there are no supported schemes for versions >= 3.7', function () {
1064 sinon
.spy(client
, "_fail");
1065 client
._rfb_version
= 3.7;
1066 const auth_schemes
= [1, 32];
1067 client
._sock
._websocket
._receive_data(auth_schemes
);
1068 expect(client
._fail
).to
.have
.been
.calledOnce
;
1071 it('should fail with the appropriate message if no types are sent for versions >= 3.7', function () {
1072 client
._rfb_version
= 3.7;
1073 const failure_data
= [0, 0, 0, 0, 6, 119, 104, 111, 111, 112, 115];
1074 sinon
.spy(client
, '_fail');
1075 client
._sock
._websocket
._receive_data(failure_data
);
1077 expect(client
._fail
).to
.have
.been
.calledOnce
;
1078 expect(client
._fail
).to
.have
.been
.calledWith(
1079 'Security negotiation failed on no security types (reason: whoops)');
1082 it('should transition to the Authentication state and continue on successful negotiation', function () {
1083 client
._rfb_version
= 3.7;
1084 const auth_schemes
= [1, 1];
1085 client
._negotiate_authentication
= sinon
.spy();
1086 client
._sock
._websocket
._receive_data(auth_schemes
);
1087 expect(client
._rfb_init_state
).to
.equal('Authentication');
1088 expect(client
._negotiate_authentication
).to
.have
.been
.calledOnce
;
1092 describe('Authentication', function () {
1093 beforeEach(function () {
1094 client
._rfb_init_state
= 'Security';
1097 function send_security(type
, cl
) {
1098 cl
._sock
._websocket
._receive_data(new Uint8Array([1, type
]));
1101 it('should fail on auth scheme 0 (pre 3.7) with the given message', function () {
1102 client
._rfb_version
= 3.6;
1103 const err_msg
= "Whoopsies";
1104 const data
= [0, 0, 0, 0];
1105 const err_len
= err_msg
.length
;
1106 push32(data
, err_len
);
1107 for (let i
= 0; i
< err_len
; i
++) {
1108 data
.push(err_msg
.charCodeAt(i
));
1111 sinon
.spy(client
, '_fail');
1112 client
._sock
._websocket
._receive_data(new Uint8Array(data
));
1113 expect(client
._fail
).to
.have
.been
.calledWith(
1114 'Security negotiation failed on authentication scheme (reason: Whoopsies)');
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
);
1120 expect(client
._rfb_init_state
).to
.equal('SecurityResult');
1123 it('should transition straight to ServerInitialisation on "no auth" for versions < 3.8', function () {
1124 client
._rfb_version
= 3.7;
1125 send_security(1, client
);
1126 expect(client
._rfb_init_state
).to
.equal('ServerInitialisation');
1129 it('should fail on an unknown auth scheme', function () {
1130 sinon
.spy(client
, "_fail");
1131 client
._rfb_version
= 3.8;
1132 send_security(57, client
);
1133 expect(client
._fail
).to
.have
.been
.calledOnce
;
1136 describe('VNC Authentication (type 2) Handler', function () {
1137 beforeEach(function () {
1138 client
._rfb_init_state
= 'Security';
1139 client
._rfb_version
= 3.8;
1142 it('should fire the credentialsrequired event if missing a password', function () {
1143 const spy
= sinon
.spy();
1144 client
.addEventListener("credentialsrequired", spy
);
1145 send_security(2, client
);
1147 const challenge
= [];
1148 for (let i
= 0; i
< 16; i
++) { challenge
[i
] = i
; }
1149 client
._sock
._websocket
._receive_data(new Uint8Array(challenge
));
1151 expect(client
._rfb_credentials
).to
.be
.empty
;
1152 expect(spy
).to
.have
.been
.calledOnce
;
1153 expect(spy
.args
[0][0].detail
.types
).to
.have
.members(["password"]);
1156 it('should encrypt the password with DES and then send it back', function () {
1157 client
._rfb_credentials
= { password
: 'passwd' };
1158 send_security(2, client
);
1159 client
._sock
._websocket
._get_sent_data(); // skip the choice of auth reply
1161 const challenge
= [];
1162 for (let i
= 0; i
< 16; i
++) { challenge
[i
] = i
; }
1163 client
._sock
._websocket
._receive_data(new Uint8Array(challenge
));
1165 const des_pass
= RFB
.genDES('passwd', challenge
);
1166 expect(client
._sock
).to
.have
.sent(new Uint8Array(des_pass
));
1169 it('should transition to SecurityResult immediately after sending the password', function () {
1170 client
._rfb_credentials
= { password
: 'passwd' };
1171 send_security(2, client
);
1173 const challenge
= [];
1174 for (let i
= 0; i
< 16; i
++) { challenge
[i
] = i
; }
1175 client
._sock
._websocket
._receive_data(new Uint8Array(challenge
));
1177 expect(client
._rfb_init_state
).to
.equal('SecurityResult');
1181 describe('XVP Authentication (type 22) Handler', function () {
1182 beforeEach(function () {
1183 client
._rfb_init_state
= 'Security';
1184 client
._rfb_version
= 3.8;
1187 it('should fall through to standard VNC authentication upon completion', function () {
1188 client
._rfb_credentials
= { username
: 'user',
1190 password
: 'password' };
1191 client
._negotiate_std_vnc_auth
= sinon
.spy();
1192 send_security(22, client
);
1193 expect(client
._negotiate_std_vnc_auth
).to
.have
.been
.calledOnce
;
1196 it('should fire the credentialsrequired event if all credentials are missing', function() {
1197 const spy
= sinon
.spy();
1198 client
.addEventListener("credentialsrequired", spy
);
1199 client
._rfb_credentials
= {};
1200 send_security(22, client
);
1202 expect(client
._rfb_credentials
).to
.be
.empty
;
1203 expect(spy
).to
.have
.been
.calledOnce
;
1204 expect(spy
.args
[0][0].detail
.types
).to
.have
.members(["username", "password", "target"]);
1207 it('should fire the credentialsrequired event if some credentials are missing', function() {
1208 const spy
= sinon
.spy();
1209 client
.addEventListener("credentialsrequired", spy
);
1210 client
._rfb_credentials
= { username
: 'user',
1212 send_security(22, client
);
1214 expect(spy
).to
.have
.been
.calledOnce
;
1215 expect(spy
.args
[0][0].detail
.types
).to
.have
.members(["username", "password", "target"]);
1218 it('should send user and target separately', function () {
1219 client
._rfb_credentials
= { username
: 'user',
1221 password
: 'password' };
1222 client
._negotiate_std_vnc_auth
= sinon
.spy();
1224 send_security(22, client
);
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
); }
1229 expect(client
._sock
).to
.have
.sent(new Uint8Array(expected
));
1233 describe('TightVNC Authentication (type 16) Handler', function () {
1234 beforeEach(function () {
1235 client
._rfb_init_state
= 'Security';
1236 client
._rfb_version
= 3.8;
1237 send_security(16, client
);
1238 client
._sock
._websocket
._get_sent_data(); // skip the security reply
1241 function send_num_str_pairs(pairs
, client
) {
1243 push32(data
, pairs
.length
);
1245 for (let i
= 0; i
< pairs
.length
; i
++) {
1246 push32(data
, pairs
[i
][0]);
1247 for (let j
= 0; j
< 4; j
++) {
1248 data
.push(pairs
[i
][1].charCodeAt(j
));
1250 for (let j
= 0; j
< 8; j
++) {
1251 data
.push(pairs
[i
][2].charCodeAt(j
));
1255 client
._sock
._websocket
._receive_data(new Uint8Array(data
));
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;
1263 it('should fail if no supported tunnels are listed', function () {
1264 sinon
.spy(client
, "_fail");
1265 send_num_str_pairs([[123, 'OTHR', 'SOMETHNG']], client
);
1266 expect(client
._fail
).to
.have
.been
.calledOnce
;
1269 it('should choose the notunnel tunnel type', function () {
1270 send_num_str_pairs([[0, 'TGHT', 'NOTUNNEL'], [123, 'OTHR', 'SOMETHNG']], client
);
1271 expect(client
._sock
).to
.have
.sent(new Uint8Array([0, 0, 0, 0]));
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]));
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
);
1283 expect(client
._sock
).to
.have
.sent(new Uint8Array([0, 0, 0, 1]));
1284 expect(client
._rfb_init_state
).to
.equal('SecurityResult');
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
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
);
1299 expect(client
._sock
).to
.have
.sent(new Uint8Array([0, 0, 0, 1]));
1300 expect(client
._rfb_init_state
).to
.equal('SecurityResult');
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
);
1307 expect(client
._sock
).to
.have
.sent(new Uint8Array([0, 0, 0, 2]));
1308 expect(client
._negotiate_std_vnc_auth
).to
.have
.been
.calledOnce
;
1309 expect(client
._rfb_auth_scheme
).to
.equal(2);
1312 it('should fail if there are no supported auth types', function () {
1313 sinon
.spy(client
, "_fail");
1314 client
._rfb_tightvnc
= true;
1315 send_num_str_pairs([[23, 'stdv', 'badval__']], client
);
1316 expect(client
._fail
).to
.have
.been
.calledOnce
;
1321 describe('SecurityResult', function () {
1322 beforeEach(function () {
1323 client
._rfb_init_state
= 'SecurityResult';
1326 it('should fall through to ServerInitialisation on a response code of 0', function () {
1327 client
._sock
._websocket
._receive_data(new Uint8Array([0, 0, 0, 0]));
1328 expect(client
._rfb_init_state
).to
.equal('ServerInitialisation');
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');
1334 const failure_data
= [0, 0, 0, 1, 0, 0, 0, 6, 119, 104, 111, 111, 112, 115];
1335 client
._sock
._websocket
._receive_data(new Uint8Array(failure_data
));
1336 expect(client
._fail
).to
.have
.been
.calledWith(
1337 'Security negotiation failed on security result (reason: whoops)');
1340 it('should fail on an error code of 1 with a standard message for version < 3.8', function () {
1341 sinon
.spy(client
, '_fail');
1342 client
._rfb_version
= 3.7;
1343 client
._sock
._websocket
._receive_data(new Uint8Array([0, 0, 0, 1]));
1344 expect(client
._fail
).to
.have
.been
.calledWith(
1345 'Security handshake failed');
1348 it('should result in securityfailure event when receiving a non zero status', function () {
1349 const spy
= sinon
.spy();
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);
1356 it('should include reason when provided in securityfailure event', function () {
1357 client
._rfb_version
= 3.8;
1358 const spy
= sinon
.spy();
1359 client
.addEventListener("securityfailure", spy
);
1360 const failure_data
= [0, 0, 0, 1, 0, 0, 0, 12, 115, 117, 99, 104,
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');
1367 it('should not include reason when length is zero in securityfailure event', function () {
1368 client
._rfb_version
= 3.9;
1369 const spy
= sinon
.spy();
1370 client
.addEventListener("securityfailure", spy
);
1371 const failure_data
= [0, 0, 0, 1, 0, 0, 0, 0];
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;
1377 it('should not include reason in securityfailure event for version < 3.8', function () {
1378 client
._rfb_version
= 3.6;
1379 const spy
= sinon
.spy();
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;
1387 describe('ClientInitialisation', function () {
1388 it('should transition to the ServerInitialisation state', function () {
1389 const client
= make_rfb();
1390 client
._rfb_connection_state
= 'connecting';
1391 client
._rfb_init_state
= 'SecurityResult';
1392 client
._sock
._websocket
._receive_data(new Uint8Array([0, 0, 0, 0]));
1393 expect(client
._rfb_init_state
).to
.equal('ServerInitialisation');
1396 it('should send 1 if we are in shared mode', function () {
1397 const client
= make_rfb('wss://host:8675', { shared
: true });
1398 client
._rfb_connection_state
= 'connecting';
1399 client
._rfb_init_state
= 'SecurityResult';
1400 client
._sock
._websocket
._receive_data(new Uint8Array([0, 0, 0, 0]));
1401 expect(client
._sock
).to
.have
.sent(new Uint8Array([1]));
1404 it('should send 0 if we are not in shared mode', function () {
1405 const client
= make_rfb('wss://host:8675', { shared
: false });
1406 client
._rfb_connection_state
= 'connecting';
1407 client
._rfb_init_state
= 'SecurityResult';
1408 client
._sock
._websocket
._receive_data(new Uint8Array([0, 0, 0, 0]));
1409 expect(client
._sock
).to
.have
.sent(new Uint8Array([0]));
1413 describe('ServerInitialisation', function () {
1414 beforeEach(function () {
1415 client
._rfb_init_state
= 'ServerInitialisation';
1418 function send_server_init(opts
, client
) {
1419 const full_opts
= { width
: 10, height
: 12, bpp
: 24, depth
: 24, big_endian
: 0,
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' };
1422 for (let opt
in opts
) {
1423 full_opts
[opt
] = opts
[opt
];
1427 push16(data
, full_opts
.width
);
1428 push16(data
, full_opts
.height
);
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
);
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
);
1447 client
._sock
._websocket
._receive_data(new Uint8Array(data
));
1449 const name_data
= [];
1450 push32(name_data
, full_opts
.name
.length
);
1451 for (let i
= 0; i
< full_opts
.name
.length
; i
++) {
1452 name_data
.push(full_opts
.name
.charCodeAt(i
));
1454 client
._sock
._websocket
._receive_data(new Uint8Array(name_data
));
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);
1463 // NB(sross): we just warn, not fail, for endian-ness and shifts, so we don't test them
1465 it('should set the framebuffer name and call the callback', function () {
1466 const spy
= sinon
.spy();
1467 client
.addEventListener("desktopname", spy
);
1468 send_server_init({ name
: 'some name' }, client
);
1470 expect(client
._fb_name
).to
.equal('some name');
1471 expect(spy
).to
.have
.been
.calledOnce
;
1472 expect(spy
.args
[0][0].detail
.name
).to
.equal('some name');
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
);
1481 const tight_data
= [];
1482 push16(tight_data
, 1);
1483 push16(tight_data
, 2);
1484 push16(tight_data
, 3);
1485 push16(tight_data
, 0);
1486 for (let i
= 0; i
< 16 + 32 + 48; i
++) {
1489 client
._sock
._websocket
._receive_data(tight_data
);
1491 expect(client
._rfb_connection_state
).to
.equal('connected');
1494 it('should resize the display', function () {
1495 sinon
.spy(client
._display
, 'resize');
1496 send_server_init({ width
: 27, height
: 32 }, client
);
1498 expect(client
._display
.resize
).to
.have
.been
.calledOnce
;
1499 expect(client
._display
.resize
).to
.have
.been
.calledWith(27, 32);
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
;
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");
1517 afterEach(function () {
1518 RFB
.messages
.pixelFormat
.restore();
1519 RFB
.messages
.clientEncodings
.restore();
1520 RFB
.messages
.fbUpdateRequest
.restore();
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
);
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);
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
);
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);
1552 it('should transition to the "connected" state', function () {
1553 send_server_init({}, client
);
1554 expect(client
._rfb_connection_state
).to
.equal('connected');
1559 describe('Protocol Message Processing After Completing Initialization', function () {
1562 beforeEach(function () {
1563 client
= make_rfb();
1564 client
._fb_name
= 'some device';
1565 client
._fb_width
= 640;
1566 client
._fb_height
= 20;
1569 describe('Framebuffer Update Handling', function () {
1570 const target_data_arr
= [
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
1578 const target_data_check_arr
= [
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
1584 let target_data_check
;
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
);
1592 function send_fbu_msg (rect_info
, rect_data
, client
, rect_cnt
) {
1595 if (!rect_cnt
|| rect_cnt
> -1) {
1597 data
.push(0); // msg type
1598 data
.push(0); // padding
1599 push16(data
, rect_cnt
|| rect_data
.length
);
1602 for (let i
= 0; i
< rect_data
.length
; i
++) {
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
);
1610 data
= data
.concat(rect_data
[i
]);
1613 client
._sock
._websocket
._receive_data(new Uint8Array(data
));
1616 it('should send an update request if there is sufficient data', function () {
1617 const expected_msg
= {_sQ
: new Uint8Array(10), _sQlen
: 0, flush
: () => {}};
1618 RFB
.messages
.fbUpdateRequest(expected_msg
, true, 0, 0, 640, 20);
1620 client
._framebufferUpdate
= () => true;
1621 client
._sock
._websocket
._receive_data(new Uint8Array([0]));
1623 expect(client
._sock
).to
.have
.sent(expected_msg
._sQ
);
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);
1631 it('should resume receiving an update if we previously did not have enough data', function () {
1632 const expected_msg
= {_sQ
: new Uint8Array(10), _sQlen
: 0, flush
: () => {}};
1633 RFB
.messages
.fbUpdateRequest(expected_msg
, true, 0, 0, 640, 20);
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);
1639 client
._framebufferUpdate = function () { this._sock
.rQskip8(); return true; }; // we magically have enough data
1640 // 247 should *not* be used as the message type here
1641 client
._sock
._websocket
._receive_data(new Uint8Array([247]));
1642 expect(client
._sock
).to
.have
.sent(expected_msg
._sQ
);
1645 it('should not send a request in continuous updates mode', function () {
1646 client
._enabledContinuousUpdates
= true;
1647 client
._framebufferUpdate
= () => true;
1648 client
._sock
._websocket
._receive_data(new Uint8Array([0]));
1650 expect(client
._sock
._websocket
._get_sent_data()).to
.have
.length(0);
1653 it('should fail on an unsupported encoding', function () {
1654 sinon
.spy(client
, "_fail");
1655 const rect_info
= { x
: 8, y
: 11, width
: 27, height
: 32, encoding
: 234 };
1656 send_fbu_msg([rect_info
], [[]], client
);
1657 expect(client
._fail
).to
.have
.been
.calledOnce
;
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);
1665 client
._display
.blitRgbxImage(0, 0, 4, 2, new Uint8Array(target_data_check_arr
.slice(0, 32)), 0);
1667 const info
= [{ x
: 0, y
: 2, width
: 2, height
: 2, encoding
: 0x01},
1668 { x
: 2, y
: 2, width
: 2, height
: 2, encoding
: 0x01}];
1669 // data says [{ old_x: 2, old_y: 0 }, { old_x: 0, old_y: 0 }]
1670 const rects
= [[0, 2, 0, 0], [0, 0, 0, 0]];
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
);
1676 describe('Message Encoding Handlers', function () {
1677 beforeEach(function () {
1678 // a really small frame
1679 client
._fb_width
= 4;
1680 client
._fb_height
= 4;
1681 client
._fb_depth
= 24;
1682 client
._display
.resize(4, 4);
1685 it('should handle the RAW encoding', function () {
1686 const info
= [{ x
: 0, y
: 0, width
: 2, height
: 2, encoding
: 0x00 },
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 }];
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
);
1700 it('should handle the RAW encoding in low colour mode', function () {
1701 const info
= [{ x
: 0, y
: 0, width
: 2, height
: 2, encoding
: 0x00 },
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 }];
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
);
1715 it('should handle the COPYRECT encoding', function () {
1716 // seed some initial data to copy
1717 client
._display
.blitRgbxImage(0, 0, 4, 2, new Uint8Array(target_data_check_arr
.slice(0, 32)), 0);
1719 const info
= [{ x
: 0, y
: 2, width
: 2, height
: 2, encoding
: 0x01},
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 }]
1722 const rects
= [[0, 2, 0, 0], [0, 0, 0, 0]];
1723 send_fbu_msg(info
, rects
, client
);
1724 expect(client
._display
).to
.have
.displayed(target_data_check
);
1727 // TODO(directxman12): for encodings with subrects, test resuming on partial send?
1728 // TODO(directxman12): test rre_chunk_sz (related to above about subrects)?
1730 it('should handle the RRE encoding', function () {
1731 const info
= [{ x
: 0, y
: 0, width
: 4, height
: 4, encoding
: 0x02 }];
1733 push32(rect
, 2); // 2 subrects
1734 push32(rect
, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1735 rect
.push(0xff); // becomes ff0000ff --> #0000FF color
1739 push16(rect
, 0); // x: 0
1740 push16(rect
, 0); // y: 0
1741 push16(rect
, 2); // width: 2
1742 push16(rect
, 2); // height: 2
1743 rect
.push(0xff); // becomes ff0000ff --> #0000FF color
1747 push16(rect
, 2); // x: 2
1748 push16(rect
, 2); // y: 2
1749 push16(rect
, 2); // width: 2
1750 push16(rect
, 2); // height: 2
1752 send_fbu_msg(info
, [rect
], client
);
1753 expect(client
._display
).to
.have
.displayed(target_data_check
);
1756 describe('the HEXTILE encoding handler', function () {
1757 it('should handle a tile with fg, bg specified, normal subrects', function () {
1758 const info
= [{ x
: 0, y
: 0, width
: 4, height
: 4, encoding
: 0x05 }];
1760 rect
.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects
1761 push32(rect
, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1762 rect
.push(0xff); // becomes ff0000ff --> #0000FF fg color
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
);
1775 it('should handle a raw tile', function () {
1776 const info
= [{ x
: 0, y
: 0, width
: 4, height
: 4, encoding
: 0x05 }];
1778 rect
.push(0x01); // raw
1779 for (let i
= 0; i
< target_data
.length
; i
+= 4) {
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]);
1785 send_fbu_msg(info
, [rect
], client
);
1786 expect(client
._display
).to
.have
.displayed(target_data
);
1789 it('should handle a tile with only bg specified (solid bg)', function () {
1790 const info
= [{ x
: 0, y
: 0, width
: 4, height
: 4, encoding
: 0x05 }];
1793 push32(rect
, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1794 send_fbu_msg(info
, [rect
], client
);
1796 const expected
= [];
1797 for (let i
= 0; i
< 16; i
++) { push32(expected
, 0xff00ff); }
1798 expect(client
._display
).to
.have
.displayed(new Uint8Array(expected
));
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;
1804 client
._display
.resize(8, 4);
1806 const info
= [{ x
: 0, y
: 0, width
: 32, height
: 4, encoding
: 0x05 }];
1812 push32(rect
, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1814 // send an empty frame
1817 send_fbu_msg(info
, [rect
], client
);
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
1822 expect(client
._display
).to
.have
.displayed(new Uint8Array(expected
));
1825 it('should handle a tile with bg and coloured subrects', function () {
1826 const info
= [{ x
: 0, y
: 0, width
: 4, height
: 4, encoding
: 0x05 }];
1828 rect
.push(0x02 | 0x08 | 0x10); // bg spec, anysubrects, colouredsubrects
1829 push32(rect
, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1830 rect
.push(2); // 2 subrects
1831 rect
.push(0xff); // becomes ff0000ff --> #0000FF fg color
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
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
);
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);
1852 const info
= [{ x
: 0, y
: 0, width
: 4, height
: 17, encoding
: 0x05}];
1854 rect
.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects
1855 push32(rect
, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1856 rect
.push(0xff); // becomes ff0000ff --> #0000FF fg color
1860 rect
.push(8); // 8 subrects
1861 for (let i
= 0; i
< 4; i
++) {
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
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
);
1874 for (let i
= 0; i
< 4; i
++) { expected
= expected
.concat(target_data_check_arr
); }
1875 expected
= expected
.concat(target_data_check_arr
.slice(0, 16));
1876 expect(client
._display
).to
.have
.displayed(new Uint8Array(expected
));
1879 it('should fail on an invalid subencoding', function () {
1880 sinon
.spy(client
,"_fail");
1881 const info
= [{ x
: 0, y
: 0, width
: 4, height
: 4, encoding
: 0x05 }];
1882 const rects
= [[45]]; // an invalid subencoding
1883 send_fbu_msg(info
, rects
, client
);
1884 expect(client
._fail
).to
.have
.been
.calledOnce
;
1888 it
.skip('should handle the TIGHT encoding', function () {
1889 // TODO(directxman12): test this
1892 it
.skip('should handle the TIGHT_PNG encoding', function () {
1893 // TODO(directxman12): test this
1896 it('should handle the DesktopSize pseduo-encoding', function () {
1897 sinon
.spy(client
._display
, 'resize');
1898 send_fbu_msg([{ x
: 0, y
: 0, width
: 20, height
: 50, encoding
: -223 }], [[]], client
);
1900 expect(client
._fb_width
).to
.equal(20);
1901 expect(client
._fb_height
).to
.equal(50);
1903 expect(client
._display
.resize
).to
.have
.been
.calledOnce
;
1904 expect(client
._display
.resize
).to
.have
.been
.calledWith(20, 50);
1907 describe('the ExtendedDesktopSize pseudo-encoding handler', function () {
1908 beforeEach(function () {
1909 // a really small frame
1910 client
._fb_width
= 4;
1911 client
._fb_height
= 4;
1912 client
._display
.resize(4, 4);
1913 sinon
.spy(client
._display
, 'resize');
1916 function make_screen_data (nr_of_screens
) {
1918 push8(data
, nr_of_screens
); // number-of-screens
1919 push8(data
, 0); // padding
1920 push16(data
, 0); // padding
1921 for (let i
=0; i
<nr_of_screens
; i
+= 1) {
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
1932 it('should handle a resize requested by this client', function () {
1933 const reason_for_change
= 1; // requested by this client
1934 const status_code
= 0; // No error
1936 send_fbu_msg([{ x
: reason_for_change
, y
: status_code
,
1937 width
: 20, height
: 50, encoding
: -308 }],
1938 make_screen_data(1), client
);
1940 expect(client
._fb_width
).to
.equal(20);
1941 expect(client
._fb_height
).to
.equal(50);
1943 expect(client
._display
.resize
).to
.have
.been
.calledOnce
;
1944 expect(client
._display
.resize
).to
.have
.been
.calledWith(20, 50);
1947 it('should handle a resize requested by another client', function () {
1948 const reason_for_change
= 2; // requested by another client
1949 const status_code
= 0; // No error
1951 send_fbu_msg([{ x
: reason_for_change
, y
: status_code
,
1952 width
: 20, height
: 50, encoding
: -308 }],
1953 make_screen_data(1), client
);
1955 expect(client
._fb_width
).to
.equal(20);
1956 expect(client
._fb_height
).to
.equal(50);
1958 expect(client
._display
.resize
).to
.have
.been
.calledOnce
;
1959 expect(client
._display
.resize
).to
.have
.been
.calledWith(20, 50);
1962 it('should be able to recieve requests which contain data for multiple screens', function () {
1963 const reason_for_change
= 2; // requested by another client
1964 const status_code
= 0; // No error
1966 send_fbu_msg([{ x
: reason_for_change
, y
: status_code
,
1967 width
: 60, height
: 50, encoding
: -308 }],
1968 make_screen_data(3), client
);
1970 expect(client
._fb_width
).to
.equal(60);
1971 expect(client
._fb_height
).to
.equal(50);
1973 expect(client
._display
.resize
).to
.have
.been
.calledOnce
;
1974 expect(client
._display
.resize
).to
.have
.been
.calledWith(60, 50);
1977 it('should not handle a failed request', function () {
1978 const reason_for_change
= 1; // requested by this client
1979 const status_code
= 1; // Resize is administratively prohibited
1981 send_fbu_msg([{ x
: reason_for_change
, y
: status_code
,
1982 width
: 20, height
: 50, encoding
: -308 }],
1983 make_screen_data(1), client
);
1985 expect(client
._fb_width
).to
.equal(4);
1986 expect(client
._fb_height
).to
.equal(4);
1988 expect(client
._display
.resize
).to
.not
.have
.been
.called
;
1992 it
.skip('should handle the Cursor pseudo-encoding', function () {
1993 // TODO(directxman12): test
1996 it('should handle the last_rect pseudo-encoding', function () {
1997 send_fbu_msg([{ x
: 0, y
: 0, width
: 0, height
: 0, encoding
: -224}], [[]], client
, 100);
1998 expect(client
._FBU
.rects
).to
.equal(0);
2003 describe('XVP Message Handling', function () {
2004 it('should set the XVP version and fire the callback with the version on XVP_INIT', function () {
2005 const spy
= sinon
.spy();
2006 client
.addEventListener("capabilities", spy
);
2007 client
._sock
._websocket
._receive_data(new Uint8Array([250, 0, 10, 1]));
2008 expect(client
._rfb_xvp_ver
).to
.equal(10);
2009 expect(spy
).to
.have
.been
.calledOnce
;
2010 expect(spy
.args
[0][0].detail
.capabilities
.power
).to
.be
.true;
2011 expect(client
.capabilities
.power
).to
.be
.true;
2014 it('should fail on unknown XVP message types', function () {
2015 sinon
.spy(client
, "_fail");
2016 client
._sock
._websocket
._receive_data(new Uint8Array([250, 0, 10, 237]));
2017 expect(client
._fail
).to
.have
.been
.calledOnce
;
2021 it('should fire the clipboard callback with the retrieved text on ServerCutText', function () {
2022 const expected_str
= 'cheese!';
2023 const data
= [3, 0, 0, 0];
2024 push32(data
, expected_str
.length
);
2025 for (let i
= 0; i
< expected_str
.length
; i
++) { data
.push(expected_str
.charCodeAt(i
)); }
2026 const spy
= sinon
.spy();
2027 client
.addEventListener("clipboard", spy
);
2029 client
._sock
._websocket
._receive_data(new Uint8Array(data
));
2030 expect(spy
).to
.have
.been
.calledOnce
;
2031 expect(spy
.args
[0][0].detail
.text
).to
.equal(expected_str
);
2034 it('should fire the bell callback on Bell', function () {
2035 const spy
= sinon
.spy();
2036 client
.addEventListener("bell", spy
);
2037 client
._sock
._websocket
._receive_data(new Uint8Array([2]));
2038 expect(spy
).to
.have
.been
.calledOnce
;
2041 it('should respond correctly to ServerFence', function () {
2042 const expected_msg
= {_sQ
: new Uint8Array(16), _sQlen
: 0, flush
: () => {}};
2043 const incoming_msg
= {_sQ
: new Uint8Array(16), _sQlen
: 0, flush
: () => {}};
2045 const payload
= "foo\x00ab9";
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
);
2051 client
._sock
._websocket
._receive_data(incoming_msg
._sQ
);
2053 expect(client
._sock
).to
.have
.sent(expected_msg
._sQ
);
2055 expected_msg
._sQlen
= 0;
2056 incoming_msg
._sQlen
= 0;
2058 RFB
.messages
.clientFence(expected_msg
, (1<<0), payload
);
2059 RFB
.messages
.clientFence(incoming_msg
, (1<<0) | (1<<31), payload
);
2061 client
._sock
._websocket
._receive_data(incoming_msg
._sQ
);
2063 expect(client
._sock
).to
.have
.sent(expected_msg
._sQ
);
2066 it('should enable continuous updates on first EndOfContinousUpdates', function () {
2067 const expected_msg
= {_sQ
: new Uint8Array(10), _sQlen
: 0, flush
: () => {}};
2069 RFB
.messages
.enableContinuousUpdates(expected_msg
, true, 0, 0, 640, 20);
2071 expect(client
._enabledContinuousUpdates
).to
.be
.false;
2073 client
._sock
._websocket
._receive_data(new Uint8Array([150]));
2075 expect(client
._enabledContinuousUpdates
).to
.be
.true;
2076 expect(client
._sock
).to
.have
.sent(expected_msg
._sQ
);
2079 it('should disable continuous updates on subsequent EndOfContinousUpdates', function () {
2080 client
._enabledContinuousUpdates
= true;
2081 client
._supportsContinuousUpdates
= true;
2083 client
._sock
._websocket
._receive_data(new Uint8Array([150]));
2085 expect(client
._enabledContinuousUpdates
).to
.be
.false;
2088 it('should update continuous updates on resize', function () {
2089 const expected_msg
= {_sQ
: new Uint8Array(10), _sQlen
: 0, flush
: () => {}};
2090 RFB
.messages
.enableContinuousUpdates(expected_msg
, true, 0, 0, 90, 700);
2092 client
._resize(450, 160);
2094 expect(client
._sock
._websocket
._get_sent_data()).to
.have
.length(0);
2096 client
._enabledContinuousUpdates
= true;
2098 client
._resize(90, 700);
2100 expect(client
._sock
).to
.have
.sent(expected_msg
._sQ
);
2103 it('should fail on an unknown message type', function () {
2104 sinon
.spy(client
, "_fail");
2105 client
._sock
._websocket
._receive_data(new Uint8Array([87]));
2106 expect(client
._fail
).to
.have
.been
.calledOnce
;
2110 describe('Asynchronous Events', function () {
2112 beforeEach(function () {
2113 client
= make_rfb();
2116 describe('Mouse event handlers', function () {
2117 it('should not send button messages in view-only mode', function () {
2118 client
._viewOnly
= true;
2119 sinon
.spy(client
._sock
, 'flush');
2120 client
._handleMouseButton(0, 0, 1, 0x001);
2121 expect(client
._sock
.flush
).to
.not
.have
.been
.called
;
2124 it('should not send movement messages in view-only mode', function () {
2125 client
._viewOnly
= true;
2126 sinon
.spy(client
._sock
, 'flush');
2127 client
._handleMouseMove(0, 0);
2128 expect(client
._sock
.flush
).to
.not
.have
.been
.called
;
2131 it('should send a pointer event on mouse button presses', function () {
2132 client
._handleMouseButton(10, 12, 1, 0x001);
2133 const pointer_msg
= {_sQ
: new Uint8Array(6), _sQlen
: 0, flush
: () => {}};
2134 RFB
.messages
.pointerEvent(pointer_msg
, 10, 12, 0x001);
2135 expect(client
._sock
).to
.have
.sent(pointer_msg
._sQ
);
2138 it('should send a mask of 1 on mousedown', function () {
2139 client
._handleMouseButton(10, 12, 1, 0x001);
2140 const pointer_msg
= {_sQ
: new Uint8Array(6), _sQlen
: 0, flush
: () => {}};
2141 RFB
.messages
.pointerEvent(pointer_msg
, 10, 12, 0x001);
2142 expect(client
._sock
).to
.have
.sent(pointer_msg
._sQ
);
2145 it('should send a mask of 0 on mouseup', function () {
2146 client
._mouse_buttonMask
= 0x001;
2147 client
._handleMouseButton(10, 12, 0, 0x001);
2148 const pointer_msg
= {_sQ
: new Uint8Array(6), _sQlen
: 0, flush
: () => {}};
2149 RFB
.messages
.pointerEvent(pointer_msg
, 10, 12, 0x000);
2150 expect(client
._sock
).to
.have
.sent(pointer_msg
._sQ
);
2153 it('should send a pointer event on mouse movement', function () {
2154 client
._handleMouseMove(10, 12);
2155 const pointer_msg
= {_sQ
: new Uint8Array(6), _sQlen
: 0, flush
: () => {}};
2156 RFB
.messages
.pointerEvent(pointer_msg
, 10, 12, 0x000);
2157 expect(client
._sock
).to
.have
.sent(pointer_msg
._sQ
);
2160 it('should set the button mask so that future mouse movements use it', function () {
2161 client
._handleMouseButton(10, 12, 1, 0x010);
2162 client
._handleMouseMove(13, 9);
2163 const pointer_msg
= {_sQ
: new Uint8Array(12), _sQlen
: 0, flush
: () => {}};
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
);
2170 describe('Keyboard Event Handlers', function () {
2171 it('should send a key message on a key press', function () {
2172 client
._handleKeyEvent(0x41, 'KeyA', true);
2173 const key_msg
= {_sQ
: new Uint8Array(8), _sQlen
: 0, flush
: () => {}};
2174 RFB
.messages
.keyEvent(key_msg
, 0x41, 1);
2175 expect(client
._sock
).to
.have
.sent(key_msg
._sQ
);
2178 it('should not send messages in view-only mode', function () {
2179 client
._viewOnly
= true;
2180 sinon
.spy(client
._sock
, 'flush');
2181 client
._handleKeyEvent('a', 'KeyA', true);
2182 expect(client
._sock
.flush
).to
.not
.have
.been
.called
;
2186 describe('WebSocket event handlers', function () {
2188 it ('should do nothing if we receive an empty message and have nothing in the queue', function () {
2189 client
._normal_msg
= sinon
.spy();
2190 client
._sock
._websocket
._receive_data(new Uint8Array([]));
2191 expect(client
._normal_msg
).to
.not
.have
.been
.called
;
2194 it('should handle a message in the connected state as a normal message', function () {
2195 client
._normal_msg
= sinon
.spy();
2196 client
._sock
._websocket
._receive_data(new Uint8Array([1, 2, 3]));
2197 expect(client
._normal_msg
).to
.have
.been
.calledOnce
;
2200 it('should handle a message in any non-disconnected/failed state like an init message', function () {
2201 client
._rfb_connection_state
= 'connecting';
2202 client
._rfb_init_state
= 'ProtocolVersion';
2203 client
._init_msg
= sinon
.spy();
2204 client
._sock
._websocket
._receive_data(new Uint8Array([1, 2, 3]));
2205 expect(client
._init_msg
).to
.have
.been
.calledOnce
;
2208 it('should process all normal messages directly', function () {
2209 const spy
= sinon
.spy();
2210 client
.addEventListener("bell", spy
);
2211 client
._sock
._websocket
._receive_data(new Uint8Array([0x02, 0x02]));
2212 expect(spy
).to
.have
.been
.calledTwice
;
2216 it('should update the state to ProtocolVersion on open (if the state is "connecting")', function () {
2217 client
= new RFB(document
.createElement('div'), 'wss://host:8675');
2219 client
._sock
._websocket
._open();
2220 expect(client
._rfb_init_state
).to
.equal('ProtocolVersion');
2223 it('should fail if we are not currently ready to connect and we get an "open" event', function () {
2224 sinon
.spy(client
, "_fail");
2225 client
._rfb_connection_state
= 'connected';
2226 client
._sock
._websocket
._open();
2227 expect(client
._fail
).to
.have
.been
.calledOnce
;
2231 it('should transition to "disconnected" from "disconnecting" on a close event', function () {
2232 const real
= client
._sock
._websocket
.close
;
2233 client
._sock
._websocket
.close
= () => {};
2234 client
.disconnect();
2235 expect(client
._rfb_connection_state
).to
.equal('disconnecting');
2236 client
._sock
._websocket
.close
= real
;
2237 client
._sock
._websocket
.close();
2238 expect(client
._rfb_connection_state
).to
.equal('disconnected');
2241 it('should fail if we get a close event while connecting', function () {
2242 sinon
.spy(client
, "_fail");
2243 client
._rfb_connection_state
= 'connecting';
2244 client
._sock
._websocket
.close();
2245 expect(client
._fail
).to
.have
.been
.calledOnce
;
2248 it('should unregister close event handler', function () {
2249 sinon
.spy(client
._sock
, 'off');
2250 client
.disconnect();
2251 client
._sock
._websocket
.close();
2252 expect(client
._sock
.off
).to
.have
.been
.calledWith('close');
2255 // error events do nothing