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';
9 /* UIEvent constructor polyfill for IE */
11 if (typeof window
.UIEvent
=== "function") return;
13 function UIEvent ( event
, params
) {
14 params
= params
|| { bubbles
: false, cancelable
: false, view
: window
, detail
: undefined };
15 const evt
= document
.createEvent( 'UIEvent' );
16 evt
.initUIEvent( event
, params
.bubbles
, params
.cancelable
, params
.view
, params
.detail
);
20 UIEvent
.prototype = window
.UIEvent
.prototype;
22 window
.UIEvent
= UIEvent
;
25 function push8(arr
, num
) {
30 function push16(arr
, num
) {
32 arr
.push((num
>> 8) & 0xFF,
36 function push32(arr
, num
) {
38 arr
.push((num
>> 24) & 0xFF,
44 describe('Remote Frame Buffer Protocol Client', function() {
48 before(FakeWebSocket
.replace
);
49 after(FakeWebSocket
.restore
);
52 this.clock
= clock
= sinon
.useFakeTimers();
53 // sinon doesn't support this yet
54 raf
= window
.requestAnimationFrame
;
55 window
.requestAnimationFrame
= setTimeout
;
56 // Use a single set of buffers instead of reallocating to
58 const sock
= new Websock();
59 const _sQ
= new Uint8Array(sock
._sQbufferSize
);
60 const rQ
= new Uint8Array(sock
._rQbufferSize
);
62 Websock
.prototype._old_allocate_buffers
= Websock
.prototype._allocate_buffers
;
63 Websock
.prototype._allocate_buffers = function () {
71 Websock
.prototype._allocate_buffers
= Websock
.prototype._old_allocate_buffers
;
73 window
.requestAnimationFrame
= raf
;
79 beforeEach(function () {
80 // Create a container element for all RFB objects to attach to
81 container
= document
.createElement('div');
82 container
.style
.width
= "100%";
83 container
.style
.height
= "100%";
84 document
.body
.appendChild(container
);
86 // And track all created RFB objects
89 afterEach(function () {
90 // Make sure every created RFB object is properly cleaned up
91 // or they might affect subsequent tests
92 rfbs
.forEach(function (rfb
) {
94 expect(rfb
._disconnect
).to
.have
.been
.called
;
98 document
.body
.removeChild(container
);
102 function make_rfb (url
, options
) {
103 url
= url
|| 'wss://host:8675';
104 const rfb
= new RFB(container
, url
, options
);
106 rfb
._sock
._websocket
._open();
107 rfb
._rfb_connection_state
= 'connected';
108 sinon
.spy(rfb
, "_disconnect");
113 describe('Connecting/Disconnecting', function () {
114 describe('#RFB', function () {
115 it('should set the current state to "connecting"', function () {
116 const client
= new RFB(document
.createElement('div'), 'wss://host:8675');
117 client
._rfb_connection_state
= '';
119 expect(client
._rfb_connection_state
).to
.equal('connecting');
122 it('should actually connect to the websocket', function () {
123 const client
= new RFB(document
.createElement('div'), 'ws://HOST:8675/PATH');
124 sinon
.spy(client
._sock
, 'open');
126 expect(client
._sock
.open
).to
.have
.been
.calledOnce
;
127 expect(client
._sock
.open
).to
.have
.been
.calledWith('ws://HOST:8675/PATH');
131 describe('#disconnect', function () {
133 beforeEach(function () {
137 it('should go to state "disconnecting" before "disconnected"', function () {
138 sinon
.spy(client
, '_updateConnectionState');
140 expect(client
._updateConnectionState
).to
.have
.been
.calledTwice
;
141 expect(client
._updateConnectionState
.getCall(0).args
[0])
142 .to
.equal('disconnecting');
143 expect(client
._updateConnectionState
.getCall(1).args
[0])
144 .to
.equal('disconnected');
145 expect(client
._rfb_connection_state
).to
.equal('disconnected');
148 it('should unregister error event handler', function () {
149 sinon
.spy(client
._sock
, 'off');
151 expect(client
._sock
.off
).to
.have
.been
.calledWith('error');
154 it('should unregister message event handler', function () {
155 sinon
.spy(client
._sock
, 'off');
157 expect(client
._sock
.off
).to
.have
.been
.calledWith('message');
160 it('should unregister open event handler', function () {
161 sinon
.spy(client
._sock
, 'off');
163 expect(client
._sock
.off
).to
.have
.been
.calledWith('open');
167 describe('#sendCredentials', function () {
169 beforeEach(function () {
171 client
._rfb_connection_state
= 'connecting';
174 it('should set the rfb credentials properly"', function () {
175 client
.sendCredentials({ password
: 'pass' });
176 expect(client
._rfb_credentials
).to
.deep
.equal({ password
: 'pass' });
179 it('should call init_msg "soon"', function () {
180 client
._init_msg
= sinon
.spy();
181 client
.sendCredentials({ password
: 'pass' });
183 expect(client
._init_msg
).to
.have
.been
.calledOnce
;
188 describe('Public API Basic Behavior', function () {
190 beforeEach(function () {
194 describe('#sendCtrlAlDel', function () {
195 it('should sent ctrl[down]-alt[down]-del[down] then del[up]-alt[up]-ctrl[up]', function () {
196 const expected
= {_sQ
: new Uint8Array(48), _sQlen
: 0, flush
: () => {}};
197 RFB
.messages
.keyEvent(expected
, 0xFFE3, 1);
198 RFB
.messages
.keyEvent(expected
, 0xFFE9, 1);
199 RFB
.messages
.keyEvent(expected
, 0xFFFF, 1);
200 RFB
.messages
.keyEvent(expected
, 0xFFFF, 0);
201 RFB
.messages
.keyEvent(expected
, 0xFFE9, 0);
202 RFB
.messages
.keyEvent(expected
, 0xFFE3, 0);
204 client
.sendCtrlAltDel();
205 expect(client
._sock
).to
.have
.sent(expected
._sQ
);
208 it('should not send the keys if we are not in a normal state', function () {
209 sinon
.spy(client
._sock
, 'flush');
210 client
._rfb_connection_state
= "connecting";
211 client
.sendCtrlAltDel();
212 expect(client
._sock
.flush
).to
.not
.have
.been
.called
;
215 it('should not send the keys if we are set as view_only', function () {
216 sinon
.spy(client
._sock
, 'flush');
217 client
._viewOnly
= true;
218 client
.sendCtrlAltDel();
219 expect(client
._sock
.flush
).to
.not
.have
.been
.called
;
223 describe('#sendKey', function () {
224 it('should send a single key with the given code and state (down = true)', function () {
225 const expected
= {_sQ
: new Uint8Array(8), _sQlen
: 0, flush
: () => {}};
226 RFB
.messages
.keyEvent(expected
, 123, 1);
227 client
.sendKey(123, 'Key123', true);
228 expect(client
._sock
).to
.have
.sent(expected
._sQ
);
231 it('should send both a down and up event if the state is not specified', function () {
232 const expected
= {_sQ
: new Uint8Array(16), _sQlen
: 0, flush
: () => {}};
233 RFB
.messages
.keyEvent(expected
, 123, 1);
234 RFB
.messages
.keyEvent(expected
, 123, 0);
235 client
.sendKey(123, 'Key123');
236 expect(client
._sock
).to
.have
.sent(expected
._sQ
);
239 it('should not send the key if we are not in a normal state', function () {
240 sinon
.spy(client
._sock
, 'flush');
241 client
._rfb_connection_state
= "connecting";
242 client
.sendKey(123, 'Key123');
243 expect(client
._sock
.flush
).to
.not
.have
.been
.called
;
246 it('should not send the key if we are set as view_only', function () {
247 sinon
.spy(client
._sock
, 'flush');
248 client
._viewOnly
= true;
249 client
.sendKey(123, 'Key123');
250 expect(client
._sock
.flush
).to
.not
.have
.been
.called
;
253 it('should send QEMU extended events if supported', function () {
254 client
._qemuExtKeyEventSupported
= true;
255 const expected
= {_sQ
: new Uint8Array(12), _sQlen
: 0, flush
: () => {}};
256 RFB
.messages
.QEMUExtendedKeyEvent(expected
, 0x20, true, 0x0039);
257 client
.sendKey(0x20, 'Space', true);
258 expect(client
._sock
).to
.have
.sent(expected
._sQ
);
261 it('should not send QEMU extended events if unknown key code', function () {
262 client
._qemuExtKeyEventSupported
= true;
263 const expected
= {_sQ
: new Uint8Array(8), _sQlen
: 0, flush
: () => {}};
264 RFB
.messages
.keyEvent(expected
, 123, 1);
265 client
.sendKey(123, 'FooBar', true);
266 expect(client
._sock
).to
.have
.sent(expected
._sQ
);
270 describe('#focus', function () {
271 it('should move focus to canvas object', function () {
272 client
._canvas
.focus
= sinon
.spy();
274 expect(client
._canvas
.focus
).to
.have
.been
.called
.once
;
278 describe('#blur', function () {
279 it('should remove focus from canvas object', function () {
280 client
._canvas
.blur
= sinon
.spy();
282 expect(client
._canvas
.blur
).to
.have
.been
.called
.once
;
286 describe('#clipboardPasteFrom', function () {
287 it('should send the given text in a paste event', function () {
288 const expected
= {_sQ
: new Uint8Array(11), _sQlen
: 0,
289 _sQbufferSize
: 11, flush
: () => {}};
290 RFB
.messages
.clientCutText(expected
, 'abc');
291 client
.clipboardPasteFrom('abc');
292 expect(client
._sock
).to
.have
.sent(expected
._sQ
);
295 it('should flush multiple times for large clipboards', function () {
296 sinon
.spy(client
._sock
, 'flush');
298 for (let i
= 0; i
< client
._sock
._sQbufferSize
+ 100; i
++) {
301 client
.clipboardPasteFrom(long_text
);
302 expect(client
._sock
.flush
).to
.have
.been
.calledTwice
;
305 it('should not send the text if we are not in a normal state', function () {
306 sinon
.spy(client
._sock
, 'flush');
307 client
._rfb_connection_state
= "connecting";
308 client
.clipboardPasteFrom('abc');
309 expect(client
._sock
.flush
).to
.not
.have
.been
.called
;
313 describe("XVP operations", function () {
314 beforeEach(function () {
315 client
._rfb_xvp_ver
= 1;
318 it('should send the shutdown signal on #machineShutdown', function () {
319 client
.machineShutdown();
320 expect(client
._sock
).to
.have
.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x02]));
323 it('should send the reboot signal on #machineReboot', function () {
324 client
.machineReboot();
325 expect(client
._sock
).to
.have
.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x03]));
328 it('should send the reset signal on #machineReset', function () {
329 client
.machineReset();
330 expect(client
._sock
).to
.have
.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x04]));
333 it('should not send XVP operations with higher versions than we support', function () {
334 sinon
.spy(client
._sock
, 'flush');
336 expect(client
._sock
.flush
).to
.not
.have
.been
.called
;
341 describe('Clipping', function () {
343 beforeEach(function () {
345 container
.style
.width
= '70px';
346 container
.style
.height
= '80px';
347 client
.clipViewport
= true;
350 it('should update display clip state when changing the property', function () {
351 const spy
= sinon
.spy(client
._display
, "clipViewport", ["set"]);
353 client
.clipViewport
= false;
354 expect(spy
.set).to
.have
.been
.calledOnce
;
355 expect(spy
.set).to
.have
.been
.calledWith(false);
358 client
.clipViewport
= true;
359 expect(spy
.set).to
.have
.been
.calledOnce
;
360 expect(spy
.set).to
.have
.been
.calledWith(true);
363 it('should update the viewport when the container size changes', function () {
364 sinon
.spy(client
._display
, "viewportChangeSize");
366 container
.style
.width
= '40px';
367 container
.style
.height
= '50px';
368 const event
= new UIEvent('resize');
369 window
.dispatchEvent(event
);
372 expect(client
._display
.viewportChangeSize
).to
.have
.been
.calledOnce
;
373 expect(client
._display
.viewportChangeSize
).to
.have
.been
.calledWith(40, 50);
376 it('should update the viewport when the remote session resizes', function () {
377 // Simple ExtendedDesktopSize FBU message
378 const incoming
= [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
379 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0xfe, 0xcc,
380 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
381 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff,
382 0x00, 0x00, 0x00, 0x00 ];
384 sinon
.spy(client
._display
, "viewportChangeSize");
386 client
._sock
._websocket
._receive_data(new Uint8Array(incoming
));
388 // FIXME: Display implicitly calls viewportChangeSize() when
389 // resizing the framebuffer, hence calledTwice.
390 expect(client
._display
.viewportChangeSize
).to
.have
.been
.calledTwice
;
391 expect(client
._display
.viewportChangeSize
).to
.have
.been
.calledWith(70, 80);
394 it('should not update the viewport if not clipping', function () {
395 client
.clipViewport
= false;
396 sinon
.spy(client
._display
, "viewportChangeSize");
398 container
.style
.width
= '40px';
399 container
.style
.height
= '50px';
400 const event
= new UIEvent('resize');
401 window
.dispatchEvent(event
);
404 expect(client
._display
.viewportChangeSize
).to
.not
.have
.been
.called
;
407 it('should not update the viewport if scaling', function () {
408 client
.scaleViewport
= true;
409 sinon
.spy(client
._display
, "viewportChangeSize");
411 container
.style
.width
= '40px';
412 container
.style
.height
= '50px';
413 const event
= new UIEvent('resize');
414 window
.dispatchEvent(event
);
417 expect(client
._display
.viewportChangeSize
).to
.not
.have
.been
.called
;
420 describe('Dragging', function () {
421 beforeEach(function () {
422 client
.dragViewport
= true;
423 sinon
.spy(RFB
.messages
, "pointerEvent");
426 afterEach(function () {
427 RFB
.messages
.pointerEvent
.restore();
430 it('should not send button messages when initiating viewport dragging', function () {
431 client
._handleMouseButton(13, 9, 0x001);
432 expect(RFB
.messages
.pointerEvent
).to
.not
.have
.been
.called
;
435 it('should send button messages when release without movement', function () {
437 client
._handleMouseButton(13, 9, 0x001);
438 client
._handleMouseButton(13, 9, 0x000);
439 expect(RFB
.messages
.pointerEvent
).to
.have
.been
.calledTwice
;
441 RFB
.messages
.pointerEvent
.reset();
444 client
._handleMouseButton(13, 9, 0x001);
445 client
._handleMouseMove(15, 14);
446 client
._handleMouseButton(15, 14, 0x000);
447 expect(RFB
.messages
.pointerEvent
).to
.have
.been
.calledTwice
;
450 it('should send button message directly when drag is disabled', function () {
451 client
.dragViewport
= false;
452 client
._handleMouseButton(13, 9, 0x001);
453 expect(RFB
.messages
.pointerEvent
).to
.have
.been
.calledOnce
;
456 it('should be initiate viewport dragging on sufficient movement', function () {
457 sinon
.spy(client
._display
, "viewportChangePos");
459 // Too small movement
461 client
._handleMouseButton(13, 9, 0x001);
462 client
._handleMouseMove(18, 9);
464 expect(RFB
.messages
.pointerEvent
).to
.not
.have
.been
.called
;
465 expect(client
._display
.viewportChangePos
).to
.not
.have
.been
.called
;
467 // Sufficient movement
469 client
._handleMouseMove(43, 9);
471 expect(RFB
.messages
.pointerEvent
).to
.not
.have
.been
.called
;
472 expect(client
._display
.viewportChangePos
).to
.have
.been
.calledOnce
;
473 expect(client
._display
.viewportChangePos
).to
.have
.been
.calledWith(-30, 0);
475 client
._display
.viewportChangePos
.reset();
477 // Now a small movement should move right away
479 client
._handleMouseMove(43, 14);
481 expect(RFB
.messages
.pointerEvent
).to
.not
.have
.been
.called
;
482 expect(client
._display
.viewportChangePos
).to
.have
.been
.calledOnce
;
483 expect(client
._display
.viewportChangePos
).to
.have
.been
.calledWith(0, -5);
486 it('should not send button messages when dragging ends', function () {
487 // First the movement
489 client
._handleMouseButton(13, 9, 0x001);
490 client
._handleMouseMove(43, 9);
491 client
._handleMouseButton(43, 9, 0x000);
493 expect(RFB
.messages
.pointerEvent
).to
.not
.have
.been
.called
;
496 it('should terminate viewport dragging on a button up event', function () {
497 // First the dragging movement
499 client
._handleMouseButton(13, 9, 0x001);
500 client
._handleMouseMove(43, 9);
501 client
._handleMouseButton(43, 9, 0x000);
503 // Another movement now should not move the viewport
505 sinon
.spy(client
._display
, "viewportChangePos");
507 client
._handleMouseMove(43, 59);
509 expect(client
._display
.viewportChangePos
).to
.not
.have
.been
.called
;
514 describe('Scaling', function () {
516 beforeEach(function () {
518 container
.style
.width
= '70px';
519 container
.style
.height
= '80px';
520 client
.scaleViewport
= true;
523 it('should update display scale factor when changing the property', function () {
524 const spy
= sinon
.spy(client
._display
, "scale", ["set"]);
525 sinon
.spy(client
._display
, "autoscale");
527 client
.scaleViewport
= false;
528 expect(spy
.set).to
.have
.been
.calledOnce
;
529 expect(spy
.set).to
.have
.been
.calledWith(1.0);
530 expect(client
._display
.autoscale
).to
.not
.have
.been
.called
;
532 client
.scaleViewport
= true;
533 expect(client
._display
.autoscale
).to
.have
.been
.calledOnce
;
534 expect(client
._display
.autoscale
).to
.have
.been
.calledWith(70, 80);
537 it('should update the clipping setting when changing the property', function () {
538 client
.clipViewport
= true;
540 const spy
= sinon
.spy(client
._display
, "clipViewport", ["set"]);
542 client
.scaleViewport
= false;
543 expect(spy
.set).to
.have
.been
.calledOnce
;
544 expect(spy
.set).to
.have
.been
.calledWith(true);
548 client
.scaleViewport
= true;
549 expect(spy
.set).to
.have
.been
.calledOnce
;
550 expect(spy
.set).to
.have
.been
.calledWith(false);
553 it('should update the scaling when the container size changes', function () {
554 sinon
.spy(client
._display
, "autoscale");
556 container
.style
.width
= '40px';
557 container
.style
.height
= '50px';
558 const event
= new UIEvent('resize');
559 window
.dispatchEvent(event
);
562 expect(client
._display
.autoscale
).to
.have
.been
.calledOnce
;
563 expect(client
._display
.autoscale
).to
.have
.been
.calledWith(40, 50);
566 it('should update the scaling when the remote session resizes', function () {
567 // Simple ExtendedDesktopSize FBU message
568 const incoming
= [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
569 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0xfe, 0xcc,
570 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
571 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff,
572 0x00, 0x00, 0x00, 0x00 ];
574 sinon
.spy(client
._display
, "autoscale");
576 client
._sock
._websocket
._receive_data(new Uint8Array(incoming
));
578 expect(client
._display
.autoscale
).to
.have
.been
.calledOnce
;
579 expect(client
._display
.autoscale
).to
.have
.been
.calledWith(70, 80);
582 it('should not update the display scale factor if not scaling', function () {
583 client
.scaleViewport
= false;
585 sinon
.spy(client
._display
, "autoscale");
587 container
.style
.width
= '40px';
588 container
.style
.height
= '50px';
589 const event
= new UIEvent('resize');
590 window
.dispatchEvent(event
);
593 expect(client
._display
.autoscale
).to
.not
.have
.been
.called
;
597 describe('Remote resize', function () {
599 beforeEach(function () {
601 client
._supportsSetDesktopSize
= true;
602 client
.resizeSession
= true;
603 container
.style
.width
= '70px';
604 container
.style
.height
= '80px';
605 sinon
.spy(RFB
.messages
, "setDesktopSize");
608 afterEach(function () {
609 RFB
.messages
.setDesktopSize
.restore();
612 it('should only request a resize when turned on', function () {
613 client
.resizeSession
= false;
614 expect(RFB
.messages
.setDesktopSize
).to
.not
.have
.been
.called
;
615 client
.resizeSession
= true;
616 expect(RFB
.messages
.setDesktopSize
).to
.have
.been
.calledOnce
;
619 it('should request a resize when initially connecting', function () {
620 // Simple ExtendedDesktopSize FBU message
621 const incoming
= [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
622 0x00, 0x04, 0x00, 0x04, 0xff, 0xff, 0xfe, 0xcc,
623 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
624 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04,
625 0x00, 0x00, 0x00, 0x00 ];
627 // First message should trigger a resize
629 client
._supportsSetDesktopSize
= false;
631 client
._sock
._websocket
._receive_data(new Uint8Array(incoming
));
633 expect(RFB
.messages
.setDesktopSize
).to
.have
.been
.calledOnce
;
634 expect(RFB
.messages
.setDesktopSize
).to
.have
.been
.calledWith(sinon
.match
.object
, 70, 80, 0, 0);
636 RFB
.messages
.setDesktopSize
.reset();
638 // Second message should not trigger a resize
640 client
._sock
._websocket
._receive_data(new Uint8Array(incoming
));
642 expect(RFB
.messages
.setDesktopSize
).to
.not
.have
.been
.called
;
645 it('should request a resize when the container resizes', function () {
646 container
.style
.width
= '40px';
647 container
.style
.height
= '50px';
648 const event
= new UIEvent('resize');
649 window
.dispatchEvent(event
);
652 expect(RFB
.messages
.setDesktopSize
).to
.have
.been
.calledOnce
;
653 expect(RFB
.messages
.setDesktopSize
).to
.have
.been
.calledWith(sinon
.match
.object
, 40, 50, 0, 0);
656 it('should not resize until the container size is stable', function () {
657 container
.style
.width
= '20px';
658 container
.style
.height
= '30px';
659 const event1
= new UIEvent('resize');
660 window
.dispatchEvent(event1
);
663 expect(RFB
.messages
.setDesktopSize
).to
.not
.have
.been
.called
;
665 container
.style
.width
= '40px';
666 container
.style
.height
= '50px';
667 const event2
= new UIEvent('resize');
668 window
.dispatchEvent(event2
);
671 expect(RFB
.messages
.setDesktopSize
).to
.not
.have
.been
.called
;
675 expect(RFB
.messages
.setDesktopSize
).to
.have
.been
.calledOnce
;
676 expect(RFB
.messages
.setDesktopSize
).to
.have
.been
.calledWith(sinon
.match
.object
, 40, 50, 0, 0);
679 it('should not resize when resize is disabled', function () {
680 client
._resizeSession
= false;
682 container
.style
.width
= '40px';
683 container
.style
.height
= '50px';
684 const event
= new UIEvent('resize');
685 window
.dispatchEvent(event
);
688 expect(RFB
.messages
.setDesktopSize
).to
.not
.have
.been
.called
;
691 it('should not resize when resize is not supported', function () {
692 client
._supportsSetDesktopSize
= false;
694 container
.style
.width
= '40px';
695 container
.style
.height
= '50px';
696 const event
= new UIEvent('resize');
697 window
.dispatchEvent(event
);
700 expect(RFB
.messages
.setDesktopSize
).to
.not
.have
.been
.called
;
703 it('should not resize when in view only mode', function () {
704 client
._viewOnly
= true;
706 container
.style
.width
= '40px';
707 container
.style
.height
= '50px';
708 const event
= new UIEvent('resize');
709 window
.dispatchEvent(event
);
712 expect(RFB
.messages
.setDesktopSize
).to
.not
.have
.been
.called
;
715 it('should not try to override a server resize', function () {
716 // Simple ExtendedDesktopSize FBU message
717 const incoming
= [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
718 0x00, 0x04, 0x00, 0x04, 0xff, 0xff, 0xfe, 0xcc,
719 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
720 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04,
721 0x00, 0x00, 0x00, 0x00 ];
723 client
._sock
._websocket
._receive_data(new Uint8Array(incoming
));
725 expect(RFB
.messages
.setDesktopSize
).to
.not
.have
.been
.called
;
729 describe('Misc Internals', function () {
730 describe('#_updateConnectionState', function () {
732 beforeEach(function () {
736 it('should clear the disconnect timer if the state is not "disconnecting"', function () {
737 const spy
= sinon
.spy();
738 client
._disconnTimer
= setTimeout(spy
, 50);
739 client
._rfb_connection_state
= 'connecting';
740 client
._updateConnectionState('connected');
742 expect(spy
).to
.not
.have
.been
.called
;
743 expect(client
._disconnTimer
).to
.be
.null;
746 it('should set the rfb_connection_state', function () {
747 client
._rfb_connection_state
= 'connecting';
748 client
._updateConnectionState('connected');
749 expect(client
._rfb_connection_state
).to
.equal('connected');
752 it('should not change the state when we are disconnected', function () {
754 expect(client
._rfb_connection_state
).to
.equal('disconnected');
755 client
._updateConnectionState('connecting');
756 expect(client
._rfb_connection_state
).to
.not
.equal('connecting');
759 it('should ignore state changes to the same state', function () {
760 const connectSpy
= sinon
.spy();
761 client
.addEventListener("connect", connectSpy
);
763 expect(client
._rfb_connection_state
).to
.equal('connected');
764 client
._updateConnectionState('connected');
765 expect(connectSpy
).to
.not
.have
.been
.called
;
769 const disconnectSpy
= sinon
.spy();
770 client
.addEventListener("disconnect", disconnectSpy
);
772 expect(client
._rfb_connection_state
).to
.equal('disconnected');
773 client
._updateConnectionState('disconnected');
774 expect(disconnectSpy
).to
.not
.have
.been
.called
;
777 it('should ignore illegal state changes', function () {
778 const spy
= sinon
.spy();
779 client
.addEventListener("disconnect", spy
);
780 client
._updateConnectionState('disconnected');
781 expect(client
._rfb_connection_state
).to
.not
.equal('disconnected');
782 expect(spy
).to
.not
.have
.been
.called
;
786 describe('#_fail', function () {
788 beforeEach(function () {
792 it('should close the WebSocket connection', function () {
793 sinon
.spy(client
._sock
, 'close');
795 expect(client
._sock
.close
).to
.have
.been
.calledOnce
;
798 it('should transition to disconnected', function () {
799 sinon
.spy(client
, '_updateConnectionState');
801 this.clock
.tick(2000);
802 expect(client
._updateConnectionState
).to
.have
.been
.called
;
803 expect(client
._rfb_connection_state
).to
.equal('disconnected');
806 it('should set clean_disconnect variable', function () {
807 client
._rfb_clean_disconnect
= true;
808 client
._rfb_connection_state
= 'connected';
810 expect(client
._rfb_clean_disconnect
).to
.be
.false;
813 it('should result in disconnect event with clean set to false', function () {
814 client
._rfb_connection_state
= 'connected';
815 const spy
= sinon
.spy();
816 client
.addEventListener("disconnect", spy
);
818 this.clock
.tick(2000);
819 expect(spy
).to
.have
.been
.calledOnce
;
820 expect(spy
.args
[0][0].detail
.clean
).to
.be
.false;
826 describe('Connection States', function () {
827 describe('connecting', function () {
828 it('should open the websocket connection', function () {
829 const client
= new RFB(document
.createElement('div'),
830 'ws://HOST:8675/PATH');
831 sinon
.spy(client
._sock
, 'open');
833 expect(client
._sock
.open
).to
.have
.been
.calledOnce
;
837 describe('connected', function () {
839 beforeEach(function () {
843 it('should result in a connect event if state becomes connected', function () {
844 const spy
= sinon
.spy();
845 client
.addEventListener("connect", spy
);
846 client
._rfb_connection_state
= 'connecting';
847 client
._updateConnectionState('connected');
848 expect(spy
).to
.have
.been
.calledOnce
;
851 it('should not result in a connect event if the state is not "connected"', function () {
852 const spy
= sinon
.spy();
853 client
.addEventListener("connect", spy
);
854 client
._sock
._websocket
.open
= () => {}; // explicitly don't call onopen
855 client
._updateConnectionState('connecting');
856 expect(spy
).to
.not
.have
.been
.called
;
860 describe('disconnecting', function () {
862 beforeEach(function () {
866 it('should force disconnect if we do not call Websock.onclose within the disconnection timeout', function () {
867 sinon
.spy(client
, '_updateConnectionState');
868 client
._sock
._websocket
.close
= () => {}; // explicitly don't call onclose
869 client
._updateConnectionState('disconnecting');
870 this.clock
.tick(3 * 1000);
871 expect(client
._updateConnectionState
).to
.have
.been
.calledTwice
;
872 expect(client
._rfb_disconnect_reason
).to
.not
.equal("");
873 expect(client
._rfb_connection_state
).to
.equal("disconnected");
876 it('should not fail if Websock.onclose gets called within the disconnection timeout', function () {
877 client
._updateConnectionState('disconnecting');
878 this.clock
.tick(3 * 1000 / 2);
879 client
._sock
._websocket
.close();
880 this.clock
.tick(3 * 1000 / 2 + 1);
881 expect(client
._rfb_connection_state
).to
.equal('disconnected');
884 it('should close the WebSocket connection', function () {
885 sinon
.spy(client
._sock
, 'close');
886 client
._updateConnectionState('disconnecting');
887 expect(client
._sock
.close
).to
.have
.been
.calledOnce
;
890 it('should not result in a disconnect event', function () {
891 const spy
= sinon
.spy();
892 client
.addEventListener("disconnect", spy
);
893 client
._sock
._websocket
.close
= () => {}; // explicitly don't call onclose
894 client
._updateConnectionState('disconnecting');
895 expect(spy
).to
.not
.have
.been
.called
;
899 describe('disconnected', function () {
901 beforeEach(function () {
902 client
= new RFB(document
.createElement('div'), 'ws://HOST:8675/PATH');
905 it('should result in a disconnect event if state becomes "disconnected"', function () {
906 const spy
= sinon
.spy();
907 client
.addEventListener("disconnect", spy
);
908 client
._rfb_connection_state
= 'disconnecting';
909 client
._updateConnectionState('disconnected');
910 expect(spy
).to
.have
.been
.calledOnce
;
911 expect(spy
.args
[0][0].detail
.clean
).to
.be
.true;
914 it('should result in a disconnect event without msg when no reason given', function () {
915 const spy
= sinon
.spy();
916 client
.addEventListener("disconnect", spy
);
917 client
._rfb_connection_state
= 'disconnecting';
918 client
._rfb_disconnect_reason
= "";
919 client
._updateConnectionState('disconnected');
920 expect(spy
).to
.have
.been
.calledOnce
;
921 expect(spy
.args
[0].length
).to
.equal(1);
926 describe('Protocol Initialization States', function () {
928 beforeEach(function () {
930 client
._rfb_connection_state
= 'connecting';
933 describe('ProtocolVersion', function () {
934 function send_ver (ver
, client
) {
935 const arr
= new Uint8Array(12);
936 for (let i
= 0; i
< ver
.length
; i
++) {
937 arr
[i
+4] = ver
.charCodeAt(i
);
939 arr
[0] = 'R'; arr
[1] = 'F'; arr
[2] = 'B'; arr
[3] = ' ';
941 client
._sock
._websocket
._receive_data(arr
);
944 describe('version parsing', function () {
945 it('should interpret version 003.003 as version 3.3', function () {
946 send_ver('003.003', client
);
947 expect(client
._rfb_version
).to
.equal(3.3);
950 it('should interpret version 003.006 as version 3.3', function () {
951 send_ver('003.006', client
);
952 expect(client
._rfb_version
).to
.equal(3.3);
955 it('should interpret version 003.889 as version 3.3', function () {
956 send_ver('003.889', client
);
957 expect(client
._rfb_version
).to
.equal(3.3);
960 it('should interpret version 003.007 as version 3.7', function () {
961 send_ver('003.007', client
);
962 expect(client
._rfb_version
).to
.equal(3.7);
965 it('should interpret version 003.008 as version 3.8', function () {
966 send_ver('003.008', client
);
967 expect(client
._rfb_version
).to
.equal(3.8);
970 it('should interpret version 004.000 as version 3.8', function () {
971 send_ver('004.000', client
);
972 expect(client
._rfb_version
).to
.equal(3.8);
975 it('should interpret version 004.001 as version 3.8', function () {
976 send_ver('004.001', client
);
977 expect(client
._rfb_version
).to
.equal(3.8);
980 it('should interpret version 005.000 as version 3.8', function () {
981 send_ver('005.000', client
);
982 expect(client
._rfb_version
).to
.equal(3.8);
985 it('should fail on an invalid version', function () {
986 sinon
.spy(client
, "_fail");
987 send_ver('002.000', client
);
988 expect(client
._fail
).to
.have
.been
.calledOnce
;
992 it('should send back the interpreted version', function () {
993 send_ver('004.000', client
);
995 const expected_str
= 'RFB 003.008\n';
997 for (let i
= 0; i
< expected_str
.length
; i
++) {
998 expected
[i
] = expected_str
.charCodeAt(i
);
1001 expect(client
._sock
).to
.have
.sent(new Uint8Array(expected
));
1004 it('should transition to the Security state on successful negotiation', function () {
1005 send_ver('003.008', client
);
1006 expect(client
._rfb_init_state
).to
.equal('Security');
1009 describe('Repeater', function () {
1010 beforeEach(function () {
1011 client
= make_rfb('wss://host:8675', { repeaterID
: "12345" });
1012 client
._rfb_connection_state
= 'connecting';
1015 it('should interpret version 000.000 as a repeater', function () {
1016 send_ver('000.000', client
);
1017 expect(client
._rfb_version
).to
.equal(0);
1019 const sent_data
= client
._sock
._websocket
._get_sent_data();
1020 expect(new Uint8Array(sent_data
.buffer
, 0, 9)).to
.array
.equal(new Uint8Array([73, 68, 58, 49, 50, 51, 52, 53, 0]));
1021 expect(sent_data
).to
.have
.length(250);
1024 it('should handle two step repeater negotiation', function () {
1025 send_ver('000.000', client
);
1026 send_ver('003.008', client
);
1027 expect(client
._rfb_version
).to
.equal(3.8);
1032 describe('Security', function () {
1033 beforeEach(function () {
1034 client
._rfb_init_state
= 'Security';
1037 it('should simply receive the auth scheme when for versions < 3.7', function () {
1038 client
._rfb_version
= 3.6;
1039 const auth_scheme_raw
= [1, 2, 3, 4];
1040 const auth_scheme
= (auth_scheme_raw
[0] << 24) + (auth_scheme_raw
[1] << 16) +
1041 (auth_scheme_raw
[2] << 8) + auth_scheme_raw
[3];
1042 client
._sock
._websocket
._receive_data(auth_scheme_raw
);
1043 expect(client
._rfb_auth_scheme
).to
.equal(auth_scheme
);
1046 it('should prefer no authentication is possible', function () {
1047 client
._rfb_version
= 3.7;
1048 const auth_schemes
= [2, 1, 3];
1049 client
._sock
._websocket
._receive_data(auth_schemes
);
1050 expect(client
._rfb_auth_scheme
).to
.equal(1);
1051 expect(client
._sock
).to
.have
.sent(new Uint8Array([1, 1]));
1054 it('should choose for the most prefered scheme possible for versions >= 3.7', function () {
1055 client
._rfb_version
= 3.7;
1056 const auth_schemes
= [2, 22, 16];
1057 client
._sock
._websocket
._receive_data(auth_schemes
);
1058 expect(client
._rfb_auth_scheme
).to
.equal(22);
1059 expect(client
._sock
).to
.have
.sent(new Uint8Array([22]));
1062 it('should fail if there are no supported schemes for versions >= 3.7', function () {
1063 sinon
.spy(client
, "_fail");
1064 client
._rfb_version
= 3.7;
1065 const auth_schemes
= [1, 32];
1066 client
._sock
._websocket
._receive_data(auth_schemes
);
1067 expect(client
._fail
).to
.have
.been
.calledOnce
;
1070 it('should fail with the appropriate message if no types are sent for versions >= 3.7', function () {
1071 client
._rfb_version
= 3.7;
1072 const failure_data
= [0, 0, 0, 0, 6, 119, 104, 111, 111, 112, 115];
1073 sinon
.spy(client
, '_fail');
1074 client
._sock
._websocket
._receive_data(failure_data
);
1076 expect(client
._fail
).to
.have
.been
.calledOnce
;
1077 expect(client
._fail
).to
.have
.been
.calledWith(
1078 'Security negotiation failed on no security types (reason: whoops)');
1081 it('should transition to the Authentication state and continue on successful negotiation', function () {
1082 client
._rfb_version
= 3.7;
1083 const auth_schemes
= [1, 1];
1084 client
._negotiate_authentication
= sinon
.spy();
1085 client
._sock
._websocket
._receive_data(auth_schemes
);
1086 expect(client
._rfb_init_state
).to
.equal('Authentication');
1087 expect(client
._negotiate_authentication
).to
.have
.been
.calledOnce
;
1091 describe('Authentication', function () {
1092 beforeEach(function () {
1093 client
._rfb_init_state
= 'Security';
1096 function send_security(type
, cl
) {
1097 cl
._sock
._websocket
._receive_data(new Uint8Array([1, type
]));
1100 it('should fail on auth scheme 0 (pre 3.7) with the given message', function () {
1101 client
._rfb_version
= 3.6;
1102 const err_msg
= "Whoopsies";
1103 const data
= [0, 0, 0, 0];
1104 const err_len
= err_msg
.length
;
1105 push32(data
, err_len
);
1106 for (let i
= 0; i
< err_len
; i
++) {
1107 data
.push(err_msg
.charCodeAt(i
));
1110 sinon
.spy(client
, '_fail');
1111 client
._sock
._websocket
._receive_data(new Uint8Array(data
));
1112 expect(client
._fail
).to
.have
.been
.calledWith(
1113 'Security negotiation failed on authentication scheme (reason: Whoopsies)');
1116 it('should transition straight to SecurityResult on "no auth" (1) for versions >= 3.8', function () {
1117 client
._rfb_version
= 3.8;
1118 send_security(1, client
);
1119 expect(client
._rfb_init_state
).to
.equal('SecurityResult');
1122 it('should transition straight to ServerInitialisation on "no auth" for versions < 3.8', function () {
1123 client
._rfb_version
= 3.7;
1124 send_security(1, client
);
1125 expect(client
._rfb_init_state
).to
.equal('ServerInitialisation');
1128 it('should fail on an unknown auth scheme', function () {
1129 sinon
.spy(client
, "_fail");
1130 client
._rfb_version
= 3.8;
1131 send_security(57, client
);
1132 expect(client
._fail
).to
.have
.been
.calledOnce
;
1135 describe('VNC Authentication (type 2) Handler', function () {
1136 beforeEach(function () {
1137 client
._rfb_init_state
= 'Security';
1138 client
._rfb_version
= 3.8;
1141 it('should fire the credentialsrequired event if missing a password', function () {
1142 const spy
= sinon
.spy();
1143 client
.addEventListener("credentialsrequired", spy
);
1144 send_security(2, client
);
1146 const challenge
= [];
1147 for (let i
= 0; i
< 16; i
++) { challenge
[i
] = i
; }
1148 client
._sock
._websocket
._receive_data(new Uint8Array(challenge
));
1150 expect(client
._rfb_credentials
).to
.be
.empty
;
1151 expect(spy
).to
.have
.been
.calledOnce
;
1152 expect(spy
.args
[0][0].detail
.types
).to
.have
.members(["password"]);
1155 it('should encrypt the password with DES and then send it back', function () {
1156 client
._rfb_credentials
= { password
: 'passwd' };
1157 send_security(2, client
);
1158 client
._sock
._websocket
._get_sent_data(); // skip the choice of auth reply
1160 const challenge
= [];
1161 for (let i
= 0; i
< 16; i
++) { challenge
[i
] = i
; }
1162 client
._sock
._websocket
._receive_data(new Uint8Array(challenge
));
1164 const des_pass
= RFB
.genDES('passwd', challenge
);
1165 expect(client
._sock
).to
.have
.sent(new Uint8Array(des_pass
));
1168 it('should transition to SecurityResult immediately after sending the password', function () {
1169 client
._rfb_credentials
= { password
: 'passwd' };
1170 send_security(2, client
);
1172 const challenge
= [];
1173 for (let i
= 0; i
< 16; i
++) { challenge
[i
] = i
; }
1174 client
._sock
._websocket
._receive_data(new Uint8Array(challenge
));
1176 expect(client
._rfb_init_state
).to
.equal('SecurityResult');
1180 describe('XVP Authentication (type 22) Handler', function () {
1181 beforeEach(function () {
1182 client
._rfb_init_state
= 'Security';
1183 client
._rfb_version
= 3.8;
1186 it('should fall through to standard VNC authentication upon completion', function () {
1187 client
._rfb_credentials
= { username
: 'user',
1189 password
: 'password' };
1190 client
._negotiate_std_vnc_auth
= sinon
.spy();
1191 send_security(22, client
);
1192 expect(client
._negotiate_std_vnc_auth
).to
.have
.been
.calledOnce
;
1195 it('should fire the credentialsrequired event if all credentials are missing', function() {
1196 const spy
= sinon
.spy();
1197 client
.addEventListener("credentialsrequired", spy
);
1198 client
._rfb_credentials
= {};
1199 send_security(22, client
);
1201 expect(client
._rfb_credentials
).to
.be
.empty
;
1202 expect(spy
).to
.have
.been
.calledOnce
;
1203 expect(spy
.args
[0][0].detail
.types
).to
.have
.members(["username", "password", "target"]);
1206 it('should fire the credentialsrequired event if some credentials are missing', function() {
1207 const spy
= sinon
.spy();
1208 client
.addEventListener("credentialsrequired", spy
);
1209 client
._rfb_credentials
= { username
: 'user',
1211 send_security(22, client
);
1213 expect(spy
).to
.have
.been
.calledOnce
;
1214 expect(spy
.args
[0][0].detail
.types
).to
.have
.members(["username", "password", "target"]);
1217 it('should send user and target separately', function () {
1218 client
._rfb_credentials
= { username
: 'user',
1220 password
: 'password' };
1221 client
._negotiate_std_vnc_auth
= sinon
.spy();
1223 send_security(22, client
);
1225 const expected
= [22, 4, 6]; // auth selection, len user, len target
1226 for (let i
= 0; i
< 10; i
++) { expected
[i
+3] = 'usertarget'.charCodeAt(i
); }
1228 expect(client
._sock
).to
.have
.sent(new Uint8Array(expected
));
1232 describe('TightVNC Authentication (type 16) Handler', function () {
1233 beforeEach(function () {
1234 client
._rfb_init_state
= 'Security';
1235 client
._rfb_version
= 3.8;
1236 send_security(16, client
);
1237 client
._sock
._websocket
._get_sent_data(); // skip the security reply
1240 function send_num_str_pairs(pairs
, client
) {
1242 push32(data
, pairs
.length
);
1244 for (let i
= 0; i
< pairs
.length
; i
++) {
1245 push32(data
, pairs
[i
][0]);
1246 for (let j
= 0; j
< 4; j
++) {
1247 data
.push(pairs
[i
][1].charCodeAt(j
));
1249 for (let j
= 0; j
< 8; j
++) {
1250 data
.push(pairs
[i
][2].charCodeAt(j
));
1254 client
._sock
._websocket
._receive_data(new Uint8Array(data
));
1257 it('should skip tunnel negotiation if no tunnels are requested', function () {
1258 client
._sock
._websocket
._receive_data(new Uint8Array([0, 0, 0, 0]));
1259 expect(client
._rfb_tightvnc
).to
.be
.true;
1262 it('should fail if no supported tunnels are listed', function () {
1263 sinon
.spy(client
, "_fail");
1264 send_num_str_pairs([[123, 'OTHR', 'SOMETHNG']], client
);
1265 expect(client
._fail
).to
.have
.been
.calledOnce
;
1268 it('should choose the notunnel tunnel type', function () {
1269 send_num_str_pairs([[0, 'TGHT', 'NOTUNNEL'], [123, 'OTHR', 'SOMETHNG']], client
);
1270 expect(client
._sock
).to
.have
.sent(new Uint8Array([0, 0, 0, 0]));
1273 it('should choose the notunnel tunnel type for Siemens devices', function () {
1274 send_num_str_pairs([[1, 'SICR', 'SCHANNEL'], [2, 'SICR', 'SCHANLPW']], client
);
1275 expect(client
._sock
).to
.have
.sent(new Uint8Array([0, 0, 0, 0]));
1278 it('should continue to sub-auth negotiation after tunnel negotiation', function () {
1279 send_num_str_pairs([[0, 'TGHT', 'NOTUNNEL']], client
);
1280 client
._sock
._websocket
._get_sent_data(); // skip the tunnel choice here
1281 send_num_str_pairs([[1, 'STDV', 'NOAUTH__']], client
);
1282 expect(client
._sock
).to
.have
.sent(new Uint8Array([0, 0, 0, 1]));
1283 expect(client
._rfb_init_state
).to
.equal('SecurityResult');
1286 /*it('should attempt to use VNC auth over no auth when possible', function () {
1287 client._rfb_tightvnc = true;
1288 client._negotiate_std_vnc_auth = sinon.spy();
1289 send_num_str_pairs([[1, 'STDV', 'NOAUTH__'], [2, 'STDV', 'VNCAUTH_']], client);
1290 expect(client._sock).to.have.sent([0, 0, 0, 1]);
1291 expect(client._negotiate_std_vnc_auth).to.have.been.calledOnce;
1292 expect(client._rfb_auth_scheme).to.equal(2);
1293 });*/ // while this would make sense, the original code doesn't actually do this
1295 it('should accept the "no auth" auth type and transition to SecurityResult', function () {
1296 client
._rfb_tightvnc
= true;
1297 send_num_str_pairs([[1, 'STDV', 'NOAUTH__']], client
);
1298 expect(client
._sock
).to
.have
.sent(new Uint8Array([0, 0, 0, 1]));
1299 expect(client
._rfb_init_state
).to
.equal('SecurityResult');
1302 it('should accept VNC authentication and transition to that', function () {
1303 client
._rfb_tightvnc
= true;
1304 client
._negotiate_std_vnc_auth
= sinon
.spy();
1305 send_num_str_pairs([[2, 'STDV', 'VNCAUTH__']], client
);
1306 expect(client
._sock
).to
.have
.sent(new Uint8Array([0, 0, 0, 2]));
1307 expect(client
._negotiate_std_vnc_auth
).to
.have
.been
.calledOnce
;
1308 expect(client
._rfb_auth_scheme
).to
.equal(2);
1311 it('should fail if there are no supported auth types', function () {
1312 sinon
.spy(client
, "_fail");
1313 client
._rfb_tightvnc
= true;
1314 send_num_str_pairs([[23, 'stdv', 'badval__']], client
);
1315 expect(client
._fail
).to
.have
.been
.calledOnce
;
1320 describe('SecurityResult', function () {
1321 beforeEach(function () {
1322 client
._rfb_init_state
= 'SecurityResult';
1325 it('should fall through to ServerInitialisation on a response code of 0', function () {
1326 client
._sock
._websocket
._receive_data(new Uint8Array([0, 0, 0, 0]));
1327 expect(client
._rfb_init_state
).to
.equal('ServerInitialisation');
1330 it('should fail on an error code of 1 with the given message for versions >= 3.8', function () {
1331 client
._rfb_version
= 3.8;
1332 sinon
.spy(client
, '_fail');
1333 const failure_data
= [0, 0, 0, 1, 0, 0, 0, 6, 119, 104, 111, 111, 112, 115];
1334 client
._sock
._websocket
._receive_data(new Uint8Array(failure_data
));
1335 expect(client
._fail
).to
.have
.been
.calledWith(
1336 'Security negotiation failed on security result (reason: whoops)');
1339 it('should fail on an error code of 1 with a standard message for version < 3.8', function () {
1340 sinon
.spy(client
, '_fail');
1341 client
._rfb_version
= 3.7;
1342 client
._sock
._websocket
._receive_data(new Uint8Array([0, 0, 0, 1]));
1343 expect(client
._fail
).to
.have
.been
.calledWith(
1344 'Security handshake failed');
1347 it('should result in securityfailure event when receiving a non zero status', function () {
1348 const spy
= sinon
.spy();
1349 client
.addEventListener("securityfailure", spy
);
1350 client
._sock
._websocket
._receive_data(new Uint8Array([0, 0, 0, 2]));
1351 expect(spy
).to
.have
.been
.calledOnce
;
1352 expect(spy
.args
[0][0].detail
.status
).to
.equal(2);
1355 it('should include reason when provided in securityfailure event', function () {
1356 client
._rfb_version
= 3.8;
1357 const spy
= sinon
.spy();
1358 client
.addEventListener("securityfailure", spy
);
1359 const failure_data
= [0, 0, 0, 1, 0, 0, 0, 12, 115, 117, 99, 104,
1360 32, 102, 97, 105, 108, 117, 114, 101];
1361 client
._sock
._websocket
._receive_data(new Uint8Array(failure_data
));
1362 expect(spy
.args
[0][0].detail
.status
).to
.equal(1);
1363 expect(spy
.args
[0][0].detail
.reason
).to
.equal('such failure');
1366 it('should not include reason when length is zero in securityfailure event', function () {
1367 client
._rfb_version
= 3.9;
1368 const spy
= sinon
.spy();
1369 client
.addEventListener("securityfailure", spy
);
1370 const failure_data
= [0, 0, 0, 1, 0, 0, 0, 0];
1371 client
._sock
._websocket
._receive_data(new Uint8Array(failure_data
));
1372 expect(spy
.args
[0][0].detail
.status
).to
.equal(1);
1373 expect('reason' in spy
.args
[0][0].detail
).to
.be
.false;
1376 it('should not include reason in securityfailure event for version < 3.8', function () {
1377 client
._rfb_version
= 3.6;
1378 const spy
= sinon
.spy();
1379 client
.addEventListener("securityfailure", spy
);
1380 client
._sock
._websocket
._receive_data(new Uint8Array([0, 0, 0, 2]));
1381 expect(spy
.args
[0][0].detail
.status
).to
.equal(2);
1382 expect('reason' in spy
.args
[0][0].detail
).to
.be
.false;
1386 describe('ClientInitialisation', function () {
1387 it('should transition to the ServerInitialisation state', function () {
1388 const client
= make_rfb();
1389 client
._rfb_connection_state
= 'connecting';
1390 client
._rfb_init_state
= 'SecurityResult';
1391 client
._sock
._websocket
._receive_data(new Uint8Array([0, 0, 0, 0]));
1392 expect(client
._rfb_init_state
).to
.equal('ServerInitialisation');
1395 it('should send 1 if we are in shared mode', function () {
1396 const client
= make_rfb('wss://host:8675', { shared
: true });
1397 client
._rfb_connection_state
= 'connecting';
1398 client
._rfb_init_state
= 'SecurityResult';
1399 client
._sock
._websocket
._receive_data(new Uint8Array([0, 0, 0, 0]));
1400 expect(client
._sock
).to
.have
.sent(new Uint8Array([1]));
1403 it('should send 0 if we are not in shared mode', function () {
1404 const client
= make_rfb('wss://host:8675', { shared
: false });
1405 client
._rfb_connection_state
= 'connecting';
1406 client
._rfb_init_state
= 'SecurityResult';
1407 client
._sock
._websocket
._receive_data(new Uint8Array([0, 0, 0, 0]));
1408 expect(client
._sock
).to
.have
.sent(new Uint8Array([0]));
1412 describe('ServerInitialisation', function () {
1413 beforeEach(function () {
1414 client
._rfb_init_state
= 'ServerInitialisation';
1417 function send_server_init(opts
, client
) {
1418 const full_opts
= { width
: 10, height
: 12, bpp
: 24, depth
: 24, big_endian
: 0,
1419 true_color
: 1, red_max
: 255, green_max
: 255, blue_max
: 255,
1420 red_shift
: 16, green_shift
: 8, blue_shift
: 0, name
: 'a name' };
1421 for (let opt
in opts
) {
1422 full_opts
[opt
] = opts
[opt
];
1426 push16(data
, full_opts
.width
);
1427 push16(data
, full_opts
.height
);
1429 data
.push(full_opts
.bpp
);
1430 data
.push(full_opts
.depth
);
1431 data
.push(full_opts
.big_endian
);
1432 data
.push(full_opts
.true_color
);
1434 push16(data
, full_opts
.red_max
);
1435 push16(data
, full_opts
.green_max
);
1436 push16(data
, full_opts
.blue_max
);
1437 push8(data
, full_opts
.red_shift
);
1438 push8(data
, full_opts
.green_shift
);
1439 push8(data
, full_opts
.blue_shift
);
1446 client
._sock
._websocket
._receive_data(new Uint8Array(data
));
1448 const name_data
= [];
1449 push32(name_data
, full_opts
.name
.length
);
1450 for (let i
= 0; i
< full_opts
.name
.length
; i
++) {
1451 name_data
.push(full_opts
.name
.charCodeAt(i
));
1453 client
._sock
._websocket
._receive_data(new Uint8Array(name_data
));
1456 it('should set the framebuffer width and height', function () {
1457 send_server_init({ width
: 32, height
: 84 }, client
);
1458 expect(client
._fb_width
).to
.equal(32);
1459 expect(client
._fb_height
).to
.equal(84);
1462 // NB(sross): we just warn, not fail, for endian-ness and shifts, so we don't test them
1464 it('should set the framebuffer name and call the callback', function () {
1465 const spy
= sinon
.spy();
1466 client
.addEventListener("desktopname", spy
);
1467 send_server_init({ name
: 'some name' }, client
);
1469 expect(client
._fb_name
).to
.equal('some name');
1470 expect(spy
).to
.have
.been
.calledOnce
;
1471 expect(spy
.args
[0][0].detail
.name
).to
.equal('some name');
1474 it('should handle the extended init message of the tight encoding', function () {
1475 // NB(sross): we don't actually do anything with it, so just test that we can
1476 // read it w/o throwing an error
1477 client
._rfb_tightvnc
= true;
1478 send_server_init({}, client
);
1480 const tight_data
= [];
1481 push16(tight_data
, 1);
1482 push16(tight_data
, 2);
1483 push16(tight_data
, 3);
1484 push16(tight_data
, 0);
1485 for (let i
= 0; i
< 16 + 32 + 48; i
++) {
1488 client
._sock
._websocket
._receive_data(tight_data
);
1490 expect(client
._rfb_connection_state
).to
.equal('connected');
1493 it('should resize the display', function () {
1494 sinon
.spy(client
._display
, 'resize');
1495 send_server_init({ width
: 27, height
: 32 }, client
);
1497 expect(client
._display
.resize
).to
.have
.been
.calledOnce
;
1498 expect(client
._display
.resize
).to
.have
.been
.calledWith(27, 32);
1501 it('should grab the mouse and keyboard', function () {
1502 sinon
.spy(client
._keyboard
, 'grab');
1503 sinon
.spy(client
._mouse
, 'grab');
1504 send_server_init({}, client
);
1505 expect(client
._keyboard
.grab
).to
.have
.been
.calledOnce
;
1506 expect(client
._mouse
.grab
).to
.have
.been
.calledOnce
;
1509 describe('Initial Update Request', function () {
1510 beforeEach(function () {
1511 sinon
.spy(RFB
.messages
, "pixelFormat");
1512 sinon
.spy(RFB
.messages
, "clientEncodings");
1513 sinon
.spy(RFB
.messages
, "fbUpdateRequest");
1516 afterEach(function () {
1517 RFB
.messages
.pixelFormat
.restore();
1518 RFB
.messages
.clientEncodings
.restore();
1519 RFB
.messages
.fbUpdateRequest
.restore();
1522 // TODO(directxman12): test the various options in this configuration matrix
1523 it('should reply with the pixel format, client encodings, and initial update request', function () {
1524 send_server_init({ width
: 27, height
: 32 }, client
);
1526 expect(RFB
.messages
.pixelFormat
).to
.have
.been
.calledOnce
;
1527 expect(RFB
.messages
.pixelFormat
).to
.have
.been
.calledWith(client
._sock
, 24, true);
1528 expect(RFB
.messages
.pixelFormat
).to
.have
.been
.calledBefore(RFB
.messages
.clientEncodings
);
1529 expect(RFB
.messages
.clientEncodings
).to
.have
.been
.calledOnce
;
1530 expect(RFB
.messages
.clientEncodings
.getCall(0).args
[1]).to
.include(encodings
.encodingTight
);
1531 expect(RFB
.messages
.clientEncodings
).to
.have
.been
.calledBefore(RFB
.messages
.fbUpdateRequest
);
1532 expect(RFB
.messages
.fbUpdateRequest
).to
.have
.been
.calledOnce
;
1533 expect(RFB
.messages
.fbUpdateRequest
).to
.have
.been
.calledWith(client
._sock
, false, 0, 0, 27, 32);
1536 it('should reply with restricted settings for Intel AMT servers', function () {
1537 send_server_init({ width
: 27, height
: 32, name
: "Intel(r) AMT KVM"}, client
);
1539 expect(RFB
.messages
.pixelFormat
).to
.have
.been
.calledOnce
;
1540 expect(RFB
.messages
.pixelFormat
).to
.have
.been
.calledWith(client
._sock
, 8, true);
1541 expect(RFB
.messages
.pixelFormat
).to
.have
.been
.calledBefore(RFB
.messages
.clientEncodings
);
1542 expect(RFB
.messages
.clientEncodings
).to
.have
.been
.calledOnce
;
1543 expect(RFB
.messages
.clientEncodings
.getCall(0).args
[1]).to
.not
.include(encodings
.encodingTight
);
1544 expect(RFB
.messages
.clientEncodings
.getCall(0).args
[1]).to
.not
.include(encodings
.encodingHextile
);
1545 expect(RFB
.messages
.clientEncodings
).to
.have
.been
.calledBefore(RFB
.messages
.fbUpdateRequest
);
1546 expect(RFB
.messages
.fbUpdateRequest
).to
.have
.been
.calledOnce
;
1547 expect(RFB
.messages
.fbUpdateRequest
).to
.have
.been
.calledWith(client
._sock
, false, 0, 0, 27, 32);
1551 it('should transition to the "connected" state', function () {
1552 send_server_init({}, client
);
1553 expect(client
._rfb_connection_state
).to
.equal('connected');
1558 describe('Protocol Message Processing After Completing Initialization', function () {
1561 beforeEach(function () {
1562 client
= make_rfb();
1563 client
._fb_name
= 'some device';
1564 client
._fb_width
= 640;
1565 client
._fb_height
= 20;
1568 describe('Framebuffer Update Handling', function () {
1569 const target_data_arr
= [
1570 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
1571 0x00, 0xff, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
1572 0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255,
1573 0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255
1577 const target_data_check_arr
= [
1578 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
1579 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
1580 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
1581 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
1583 let target_data_check
;
1585 before(function () {
1586 // NB(directxman12): PhantomJS 1.x doesn't implement Uint8ClampedArray
1587 target_data
= new Uint8Array(target_data_arr
);
1588 target_data_check
= new Uint8Array(target_data_check_arr
);
1591 function send_fbu_msg (rect_info
, rect_data
, client
, rect_cnt
) {
1594 if (!rect_cnt
|| rect_cnt
> -1) {
1596 data
.push(0); // msg type
1597 data
.push(0); // padding
1598 push16(data
, rect_cnt
|| rect_data
.length
);
1601 for (let i
= 0; i
< rect_data
.length
; i
++) {
1603 push16(data
, rect_info
[i
].x
);
1604 push16(data
, rect_info
[i
].y
);
1605 push16(data
, rect_info
[i
].width
);
1606 push16(data
, rect_info
[i
].height
);
1607 push32(data
, rect_info
[i
].encoding
);
1609 data
= data
.concat(rect_data
[i
]);
1612 client
._sock
._websocket
._receive_data(new Uint8Array(data
));
1615 it('should send an update request if there is sufficient data', function () {
1616 const expected_msg
= {_sQ
: new Uint8Array(10), _sQlen
: 0, flush
: () => {}};
1617 RFB
.messages
.fbUpdateRequest(expected_msg
, true, 0, 0, 640, 20);
1619 client
._framebufferUpdate
= () => true;
1620 client
._sock
._websocket
._receive_data(new Uint8Array([0]));
1622 expect(client
._sock
).to
.have
.sent(expected_msg
._sQ
);
1625 it('should not send an update request if we need more data', function () {
1626 client
._sock
._websocket
._receive_data(new Uint8Array([0]));
1627 expect(client
._sock
._websocket
._get_sent_data()).to
.have
.length(0);
1630 it('should resume receiving an update if we previously did not have enough data', function () {
1631 const expected_msg
= {_sQ
: new Uint8Array(10), _sQlen
: 0, flush
: () => {}};
1632 RFB
.messages
.fbUpdateRequest(expected_msg
, true, 0, 0, 640, 20);
1634 // just enough to set FBU.rects
1635 client
._sock
._websocket
._receive_data(new Uint8Array([0, 0, 0, 3]));
1636 expect(client
._sock
._websocket
._get_sent_data()).to
.have
.length(0);
1638 client
._framebufferUpdate = function () { this._sock
.rQskip8(); return true; }; // we magically have enough data
1639 // 247 should *not* be used as the message type here
1640 client
._sock
._websocket
._receive_data(new Uint8Array([247]));
1641 expect(client
._sock
).to
.have
.sent(expected_msg
._sQ
);
1644 it('should not send a request in continuous updates mode', function () {
1645 client
._enabledContinuousUpdates
= true;
1646 client
._framebufferUpdate
= () => true;
1647 client
._sock
._websocket
._receive_data(new Uint8Array([0]));
1649 expect(client
._sock
._websocket
._get_sent_data()).to
.have
.length(0);
1652 it('should fail on an unsupported encoding', function () {
1653 sinon
.spy(client
, "_fail");
1654 const rect_info
= { x
: 8, y
: 11, width
: 27, height
: 32, encoding
: 234 };
1655 send_fbu_msg([rect_info
], [[]], client
);
1656 expect(client
._fail
).to
.have
.been
.calledOnce
;
1659 it('should be able to pause and resume receiving rects if not enought data', function () {
1660 // seed some initial data to copy
1661 client
._fb_width
= 4;
1662 client
._fb_height
= 4;
1663 client
._display
.resize(4, 4);
1664 client
._display
.blitRgbxImage(0, 0, 4, 2, new Uint8Array(target_data_check_arr
.slice(0, 32)), 0);
1666 const info
= [{ x
: 0, y
: 2, width
: 2, height
: 2, encoding
: 0x01},
1667 { x
: 2, y
: 2, width
: 2, height
: 2, encoding
: 0x01}];
1668 // data says [{ old_x: 2, old_y: 0 }, { old_x: 0, old_y: 0 }]
1669 const rects
= [[0, 2, 0, 0], [0, 0, 0, 0]];
1670 send_fbu_msg([info
[0]], [rects
[0]], client
, 2);
1671 send_fbu_msg([info
[1]], [rects
[1]], client
, -1);
1672 expect(client
._display
).to
.have
.displayed(target_data_check
);
1675 describe('Message Encoding Handlers', function () {
1676 beforeEach(function () {
1677 // a really small frame
1678 client
._fb_width
= 4;
1679 client
._fb_height
= 4;
1680 client
._fb_depth
= 24;
1681 client
._display
.resize(4, 4);
1684 it('should handle the RAW encoding', function () {
1685 const info
= [{ x
: 0, y
: 0, width
: 2, height
: 2, encoding
: 0x00 },
1686 { x
: 2, y
: 0, width
: 2, height
: 2, encoding
: 0x00 },
1687 { x
: 0, y
: 2, width
: 4, height
: 1, encoding
: 0x00 },
1688 { x
: 0, y
: 3, width
: 4, height
: 1, encoding
: 0x00 }];
1691 [0x00, 0x00, 0xff, 0, 0x00, 0xff, 0x00, 0, 0x00, 0xff, 0x00, 0, 0x00, 0x00, 0xff, 0],
1692 [0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0],
1693 [0xff, 0x00, 0xee, 0, 0xff, 0xee, 0x00, 0, 0xff, 0xee, 0xaa, 0, 0xff, 0xee, 0xab, 0],
1694 [0xff, 0x00, 0xee, 0, 0xff, 0xee, 0x00, 0, 0xff, 0xee, 0xaa, 0, 0xff, 0xee, 0xab, 0]];
1695 send_fbu_msg(info
, rects
, client
);
1696 expect(client
._display
).to
.have
.displayed(target_data
);
1699 it('should handle the RAW encoding in low colour mode', function () {
1700 const info
= [{ x
: 0, y
: 0, width
: 2, height
: 2, encoding
: 0x00 },
1701 { x
: 2, y
: 0, width
: 2, height
: 2, encoding
: 0x00 },
1702 { x
: 0, y
: 2, width
: 4, height
: 1, encoding
: 0x00 },
1703 { x
: 0, y
: 3, width
: 4, height
: 1, encoding
: 0x00 }];
1705 [0x03, 0x03, 0x03, 0x03],
1706 [0x0c, 0x0c, 0x0c, 0x0c],
1707 [0x0c, 0x0c, 0x03, 0x03],
1708 [0x0c, 0x0c, 0x03, 0x03]];
1709 client
._fb_depth
= 8;
1710 send_fbu_msg(info
, rects
, client
);
1711 expect(client
._display
).to
.have
.displayed(target_data_check
);
1714 it('should handle the COPYRECT encoding', function () {
1715 // seed some initial data to copy
1716 client
._display
.blitRgbxImage(0, 0, 4, 2, new Uint8Array(target_data_check_arr
.slice(0, 32)), 0);
1718 const info
= [{ x
: 0, y
: 2, width
: 2, height
: 2, encoding
: 0x01},
1719 { x
: 2, y
: 2, width
: 2, height
: 2, encoding
: 0x01}];
1720 // data says [{ old_x: 0, old_y: 0 }, { old_x: 0, old_y: 0 }]
1721 const rects
= [[0, 2, 0, 0], [0, 0, 0, 0]];
1722 send_fbu_msg(info
, rects
, client
);
1723 expect(client
._display
).to
.have
.displayed(target_data_check
);
1726 // TODO(directxman12): for encodings with subrects, test resuming on partial send?
1727 // TODO(directxman12): test rre_chunk_sz (related to above about subrects)?
1729 it('should handle the RRE encoding', function () {
1730 const info
= [{ x
: 0, y
: 0, width
: 4, height
: 4, encoding
: 0x02 }];
1732 push32(rect
, 2); // 2 subrects
1733 push32(rect
, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1734 rect
.push(0xff); // becomes ff0000ff --> #0000FF color
1738 push16(rect
, 0); // x: 0
1739 push16(rect
, 0); // y: 0
1740 push16(rect
, 2); // width: 2
1741 push16(rect
, 2); // height: 2
1742 rect
.push(0xff); // becomes ff0000ff --> #0000FF color
1746 push16(rect
, 2); // x: 2
1747 push16(rect
, 2); // y: 2
1748 push16(rect
, 2); // width: 2
1749 push16(rect
, 2); // height: 2
1751 send_fbu_msg(info
, [rect
], client
);
1752 expect(client
._display
).to
.have
.displayed(target_data_check
);
1755 describe('the HEXTILE encoding handler', function () {
1756 it('should handle a tile with fg, bg specified, normal subrects', function () {
1757 const info
= [{ x
: 0, y
: 0, width
: 4, height
: 4, encoding
: 0x05 }];
1759 rect
.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects
1760 push32(rect
, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1761 rect
.push(0xff); // becomes ff0000ff --> #0000FF fg color
1765 rect
.push(2); // 2 subrects
1766 rect
.push(0); // x: 0, y: 0
1767 rect
.push(1 | (1 << 4)); // width: 2, height: 2
1768 rect
.push(2 | (2 << 4)); // x: 2, y: 2
1769 rect
.push(1 | (1 << 4)); // width: 2, height: 2
1770 send_fbu_msg(info
, [rect
], client
);
1771 expect(client
._display
).to
.have
.displayed(target_data_check
);
1774 it('should handle a raw tile', function () {
1775 const info
= [{ x
: 0, y
: 0, width
: 4, height
: 4, encoding
: 0x05 }];
1777 rect
.push(0x01); // raw
1778 for (let i
= 0; i
< target_data
.length
; i
+= 4) {
1779 rect
.push(target_data
[i
+ 2]);
1780 rect
.push(target_data
[i
+ 1]);
1781 rect
.push(target_data
[i
]);
1782 rect
.push(target_data
[i
+ 3]);
1784 send_fbu_msg(info
, [rect
], client
);
1785 expect(client
._display
).to
.have
.displayed(target_data
);
1788 it('should handle a tile with only bg specified (solid bg)', function () {
1789 const info
= [{ x
: 0, y
: 0, width
: 4, height
: 4, encoding
: 0x05 }];
1792 push32(rect
, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1793 send_fbu_msg(info
, [rect
], client
);
1795 const expected
= [];
1796 for (let i
= 0; i
< 16; i
++) { push32(expected
, 0xff00ff); }
1797 expect(client
._display
).to
.have
.displayed(new Uint8Array(expected
));
1800 it('should handle a tile with only bg specified and an empty frame afterwards', function () {
1801 // set the width so we can have two tiles
1802 client
._fb_width
= 8;
1803 client
._display
.resize(8, 4);
1805 const info
= [{ x
: 0, y
: 0, width
: 32, height
: 4, encoding
: 0x05 }];
1811 push32(rect
, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1813 // send an empty frame
1816 send_fbu_msg(info
, [rect
], client
);
1818 const expected
= [];
1819 for (let i
= 0; i
< 16; i
++) { push32(expected
, 0xff00ff); } // rect 1: solid
1820 for (let i
= 0; i
< 16; i
++) { push32(expected
, 0xff00ff); } // rect 2: same bkground color
1821 expect(client
._display
).to
.have
.displayed(new Uint8Array(expected
));
1824 it('should handle a tile with bg and coloured subrects', function () {
1825 const info
= [{ x
: 0, y
: 0, width
: 4, height
: 4, encoding
: 0x05 }];
1827 rect
.push(0x02 | 0x08 | 0x10); // bg spec, anysubrects, colouredsubrects
1828 push32(rect
, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1829 rect
.push(2); // 2 subrects
1830 rect
.push(0xff); // becomes ff0000ff --> #0000FF fg color
1834 rect
.push(0); // x: 0, y: 0
1835 rect
.push(1 | (1 << 4)); // width: 2, height: 2
1836 rect
.push(0xff); // becomes ff0000ff --> #0000FF fg color
1840 rect
.push(2 | (2 << 4)); // x: 2, y: 2
1841 rect
.push(1 | (1 << 4)); // width: 2, height: 2
1842 send_fbu_msg(info
, [rect
], client
);
1843 expect(client
._display
).to
.have
.displayed(target_data_check
);
1846 it('should carry over fg and bg colors from the previous tile if not specified', function () {
1847 client
._fb_width
= 4;
1848 client
._fb_height
= 17;
1849 client
._display
.resize(4, 17);
1851 const info
= [{ x
: 0, y
: 0, width
: 4, height
: 17, encoding
: 0x05}];
1853 rect
.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects
1854 push32(rect
, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1855 rect
.push(0xff); // becomes ff0000ff --> #0000FF fg color
1859 rect
.push(8); // 8 subrects
1860 for (let i
= 0; i
< 4; i
++) {
1861 rect
.push((0 << 4) | (i
* 4)); // x: 0, y: i*4
1862 rect
.push(1 | (1 << 4)); // width: 2, height: 2
1863 rect
.push((2 << 4) | (i
* 4 + 2)); // x: 2, y: i * 4 + 2
1864 rect
.push(1 | (1 << 4)); // width: 2, height: 2
1866 rect
.push(0x08); // anysubrects
1867 rect
.push(1); // 1 subrect
1868 rect
.push(0); // x: 0, y: 0
1869 rect
.push(1 | (1 << 4)); // width: 2, height: 2
1870 send_fbu_msg(info
, [rect
], client
);
1873 for (let i
= 0; i
< 4; i
++) { expected
= expected
.concat(target_data_check_arr
); }
1874 expected
= expected
.concat(target_data_check_arr
.slice(0, 16));
1875 expect(client
._display
).to
.have
.displayed(new Uint8Array(expected
));
1878 it('should fail on an invalid subencoding', function () {
1879 sinon
.spy(client
, "_fail");
1880 const info
= [{ x
: 0, y
: 0, width
: 4, height
: 4, encoding
: 0x05 }];
1881 const rects
= [[45]]; // an invalid subencoding
1882 send_fbu_msg(info
, rects
, client
);
1883 expect(client
._fail
).to
.have
.been
.calledOnce
;
1887 it
.skip('should handle the TIGHT encoding', function () {
1888 // TODO(directxman12): test this
1891 it
.skip('should handle the TIGHT_PNG encoding', function () {
1892 // TODO(directxman12): test this
1895 it('should handle the DesktopSize pseduo-encoding', function () {
1896 sinon
.spy(client
._display
, 'resize');
1897 send_fbu_msg([{ x
: 0, y
: 0, width
: 20, height
: 50, encoding
: -223 }], [[]], client
);
1899 expect(client
._fb_width
).to
.equal(20);
1900 expect(client
._fb_height
).to
.equal(50);
1902 expect(client
._display
.resize
).to
.have
.been
.calledOnce
;
1903 expect(client
._display
.resize
).to
.have
.been
.calledWith(20, 50);
1906 describe('the ExtendedDesktopSize pseudo-encoding handler', function () {
1907 beforeEach(function () {
1908 // a really small frame
1909 client
._fb_width
= 4;
1910 client
._fb_height
= 4;
1911 client
._display
.resize(4, 4);
1912 sinon
.spy(client
._display
, 'resize');
1915 function make_screen_data (nr_of_screens
) {
1917 push8(data
, nr_of_screens
); // number-of-screens
1918 push8(data
, 0); // padding
1919 push16(data
, 0); // padding
1920 for (let i
=0; i
<nr_of_screens
; i
+= 1) {
1921 push32(data
, 0); // id
1922 push16(data
, 0); // x-position
1923 push16(data
, 0); // y-position
1924 push16(data
, 20); // width
1925 push16(data
, 50); // height
1926 push32(data
, 0); // flags
1931 it('should handle a resize requested by this client', function () {
1932 const reason_for_change
= 1; // requested by this client
1933 const status_code
= 0; // No error
1935 send_fbu_msg([{ x
: reason_for_change
, y
: status_code
,
1936 width
: 20, height
: 50, encoding
: -308 }],
1937 make_screen_data(1), client
);
1939 expect(client
._fb_width
).to
.equal(20);
1940 expect(client
._fb_height
).to
.equal(50);
1942 expect(client
._display
.resize
).to
.have
.been
.calledOnce
;
1943 expect(client
._display
.resize
).to
.have
.been
.calledWith(20, 50);
1946 it('should handle a resize requested by another client', function () {
1947 const reason_for_change
= 2; // requested by another client
1948 const status_code
= 0; // No error
1950 send_fbu_msg([{ x
: reason_for_change
, y
: status_code
,
1951 width
: 20, height
: 50, encoding
: -308 }],
1952 make_screen_data(1), client
);
1954 expect(client
._fb_width
).to
.equal(20);
1955 expect(client
._fb_height
).to
.equal(50);
1957 expect(client
._display
.resize
).to
.have
.been
.calledOnce
;
1958 expect(client
._display
.resize
).to
.have
.been
.calledWith(20, 50);
1961 it('should be able to recieve requests which contain data for multiple screens', function () {
1962 const reason_for_change
= 2; // requested by another client
1963 const status_code
= 0; // No error
1965 send_fbu_msg([{ x
: reason_for_change
, y
: status_code
,
1966 width
: 60, height
: 50, encoding
: -308 }],
1967 make_screen_data(3), client
);
1969 expect(client
._fb_width
).to
.equal(60);
1970 expect(client
._fb_height
).to
.equal(50);
1972 expect(client
._display
.resize
).to
.have
.been
.calledOnce
;
1973 expect(client
._display
.resize
).to
.have
.been
.calledWith(60, 50);
1976 it('should not handle a failed request', function () {
1977 const reason_for_change
= 1; // requested by this client
1978 const status_code
= 1; // Resize is administratively prohibited
1980 send_fbu_msg([{ x
: reason_for_change
, y
: status_code
,
1981 width
: 20, height
: 50, encoding
: -308 }],
1982 make_screen_data(1), client
);
1984 expect(client
._fb_width
).to
.equal(4);
1985 expect(client
._fb_height
).to
.equal(4);
1987 expect(client
._display
.resize
).to
.not
.have
.been
.called
;
1991 it
.skip('should handle the Cursor pseudo-encoding', function () {
1992 // TODO(directxman12): test
1995 it('should handle the last_rect pseudo-encoding', function () {
1996 send_fbu_msg([{ x
: 0, y
: 0, width
: 0, height
: 0, encoding
: -224}], [[]], client
, 100);
1997 expect(client
._FBU
.rects
).to
.equal(0);
2002 describe('XVP Message Handling', function () {
2003 it('should set the XVP version and fire the callback with the version on XVP_INIT', function () {
2004 const spy
= sinon
.spy();
2005 client
.addEventListener("capabilities", spy
);
2006 client
._sock
._websocket
._receive_data(new Uint8Array([250, 0, 10, 1]));
2007 expect(client
._rfb_xvp_ver
).to
.equal(10);
2008 expect(spy
).to
.have
.been
.calledOnce
;
2009 expect(spy
.args
[0][0].detail
.capabilities
.power
).to
.be
.true;
2010 expect(client
.capabilities
.power
).to
.be
.true;
2013 it('should fail on unknown XVP message types', function () {
2014 sinon
.spy(client
, "_fail");
2015 client
._sock
._websocket
._receive_data(new Uint8Array([250, 0, 10, 237]));
2016 expect(client
._fail
).to
.have
.been
.calledOnce
;
2020 it('should fire the clipboard callback with the retrieved text on ServerCutText', function () {
2021 const expected_str
= 'cheese!';
2022 const data
= [3, 0, 0, 0];
2023 push32(data
, expected_str
.length
);
2024 for (let i
= 0; i
< expected_str
.length
; i
++) { data
.push(expected_str
.charCodeAt(i
)); }
2025 const spy
= sinon
.spy();
2026 client
.addEventListener("clipboard", spy
);
2028 client
._sock
._websocket
._receive_data(new Uint8Array(data
));
2029 expect(spy
).to
.have
.been
.calledOnce
;
2030 expect(spy
.args
[0][0].detail
.text
).to
.equal(expected_str
);
2033 it('should fire the bell callback on Bell', function () {
2034 const spy
= sinon
.spy();
2035 client
.addEventListener("bell", spy
);
2036 client
._sock
._websocket
._receive_data(new Uint8Array([2]));
2037 expect(spy
).to
.have
.been
.calledOnce
;
2040 it('should respond correctly to ServerFence', function () {
2041 const expected_msg
= {_sQ
: new Uint8Array(16), _sQlen
: 0, flush
: () => {}};
2042 const incoming_msg
= {_sQ
: new Uint8Array(16), _sQlen
: 0, flush
: () => {}};
2044 const payload
= "foo\x00ab9";
2046 // ClientFence and ServerFence are identical in structure
2047 RFB
.messages
.clientFence(expected_msg
, (1<<0) | (1<<1), payload
);
2048 RFB
.messages
.clientFence(incoming_msg
, 0xffffffff, payload
);
2050 client
._sock
._websocket
._receive_data(incoming_msg
._sQ
);
2052 expect(client
._sock
).to
.have
.sent(expected_msg
._sQ
);
2054 expected_msg
._sQlen
= 0;
2055 incoming_msg
._sQlen
= 0;
2057 RFB
.messages
.clientFence(expected_msg
, (1<<0), payload
);
2058 RFB
.messages
.clientFence(incoming_msg
, (1<<0) | (1<<31), payload
);
2060 client
._sock
._websocket
._receive_data(incoming_msg
._sQ
);
2062 expect(client
._sock
).to
.have
.sent(expected_msg
._sQ
);
2065 it('should enable continuous updates on first EndOfContinousUpdates', function () {
2066 const expected_msg
= {_sQ
: new Uint8Array(10), _sQlen
: 0, flush
: () => {}};
2068 RFB
.messages
.enableContinuousUpdates(expected_msg
, true, 0, 0, 640, 20);
2070 expect(client
._enabledContinuousUpdates
).to
.be
.false;
2072 client
._sock
._websocket
._receive_data(new Uint8Array([150]));
2074 expect(client
._enabledContinuousUpdates
).to
.be
.true;
2075 expect(client
._sock
).to
.have
.sent(expected_msg
._sQ
);
2078 it('should disable continuous updates on subsequent EndOfContinousUpdates', function () {
2079 client
._enabledContinuousUpdates
= true;
2080 client
._supportsContinuousUpdates
= true;
2082 client
._sock
._websocket
._receive_data(new Uint8Array([150]));
2084 expect(client
._enabledContinuousUpdates
).to
.be
.false;
2087 it('should update continuous updates on resize', function () {
2088 const expected_msg
= {_sQ
: new Uint8Array(10), _sQlen
: 0, flush
: () => {}};
2089 RFB
.messages
.enableContinuousUpdates(expected_msg
, true, 0, 0, 90, 700);
2091 client
._resize(450, 160);
2093 expect(client
._sock
._websocket
._get_sent_data()).to
.have
.length(0);
2095 client
._enabledContinuousUpdates
= true;
2097 client
._resize(90, 700);
2099 expect(client
._sock
).to
.have
.sent(expected_msg
._sQ
);
2102 it('should fail on an unknown message type', function () {
2103 sinon
.spy(client
, "_fail");
2104 client
._sock
._websocket
._receive_data(new Uint8Array([87]));
2105 expect(client
._fail
).to
.have
.been
.calledOnce
;
2109 describe('Asynchronous Events', function () {
2111 beforeEach(function () {
2112 client
= make_rfb();
2115 describe('Mouse event handlers', function () {
2116 it('should not send button messages in view-only mode', function () {
2117 client
._viewOnly
= true;
2118 sinon
.spy(client
._sock
, 'flush');
2119 client
._handleMouseButton(0, 0, 1, 0x001);
2120 expect(client
._sock
.flush
).to
.not
.have
.been
.called
;
2123 it('should not send movement messages in view-only mode', function () {
2124 client
._viewOnly
= true;
2125 sinon
.spy(client
._sock
, 'flush');
2126 client
._handleMouseMove(0, 0);
2127 expect(client
._sock
.flush
).to
.not
.have
.been
.called
;
2130 it('should send a pointer event on mouse button presses', function () {
2131 client
._handleMouseButton(10, 12, 1, 0x001);
2132 const pointer_msg
= {_sQ
: new Uint8Array(6), _sQlen
: 0, flush
: () => {}};
2133 RFB
.messages
.pointerEvent(pointer_msg
, 10, 12, 0x001);
2134 expect(client
._sock
).to
.have
.sent(pointer_msg
._sQ
);
2137 it('should send a mask of 1 on mousedown', function () {
2138 client
._handleMouseButton(10, 12, 1, 0x001);
2139 const pointer_msg
= {_sQ
: new Uint8Array(6), _sQlen
: 0, flush
: () => {}};
2140 RFB
.messages
.pointerEvent(pointer_msg
, 10, 12, 0x001);
2141 expect(client
._sock
).to
.have
.sent(pointer_msg
._sQ
);
2144 it('should send a mask of 0 on mouseup', function () {
2145 client
._mouse_buttonMask
= 0x001;
2146 client
._handleMouseButton(10, 12, 0, 0x001);
2147 const pointer_msg
= {_sQ
: new Uint8Array(6), _sQlen
: 0, flush
: () => {}};
2148 RFB
.messages
.pointerEvent(pointer_msg
, 10, 12, 0x000);
2149 expect(client
._sock
).to
.have
.sent(pointer_msg
._sQ
);
2152 it('should send a pointer event on mouse movement', function () {
2153 client
._handleMouseMove(10, 12);
2154 const pointer_msg
= {_sQ
: new Uint8Array(6), _sQlen
: 0, flush
: () => {}};
2155 RFB
.messages
.pointerEvent(pointer_msg
, 10, 12, 0x000);
2156 expect(client
._sock
).to
.have
.sent(pointer_msg
._sQ
);
2159 it('should set the button mask so that future mouse movements use it', function () {
2160 client
._handleMouseButton(10, 12, 1, 0x010);
2161 client
._handleMouseMove(13, 9);
2162 const pointer_msg
= {_sQ
: new Uint8Array(12), _sQlen
: 0, flush
: () => {}};
2163 RFB
.messages
.pointerEvent(pointer_msg
, 10, 12, 0x010);
2164 RFB
.messages
.pointerEvent(pointer_msg
, 13, 9, 0x010);
2165 expect(client
._sock
).to
.have
.sent(pointer_msg
._sQ
);
2169 describe('Keyboard Event Handlers', function () {
2170 it('should send a key message on a key press', function () {
2171 client
._handleKeyEvent(0x41, 'KeyA', true);
2172 const key_msg
= {_sQ
: new Uint8Array(8), _sQlen
: 0, flush
: () => {}};
2173 RFB
.messages
.keyEvent(key_msg
, 0x41, 1);
2174 expect(client
._sock
).to
.have
.sent(key_msg
._sQ
);
2177 it('should not send messages in view-only mode', function () {
2178 client
._viewOnly
= true;
2179 sinon
.spy(client
._sock
, 'flush');
2180 client
._handleKeyEvent('a', 'KeyA', true);
2181 expect(client
._sock
.flush
).to
.not
.have
.been
.called
;
2185 describe('WebSocket event handlers', function () {
2187 it('should do nothing if we receive an empty message and have nothing in the queue', function () {
2188 client
._normal_msg
= sinon
.spy();
2189 client
._sock
._websocket
._receive_data(new Uint8Array([]));
2190 expect(client
._normal_msg
).to
.not
.have
.been
.called
;
2193 it('should handle a message in the connected state as a normal message', function () {
2194 client
._normal_msg
= sinon
.spy();
2195 client
._sock
._websocket
._receive_data(new Uint8Array([1, 2, 3]));
2196 expect(client
._normal_msg
).to
.have
.been
.calledOnce
;
2199 it('should handle a message in any non-disconnected/failed state like an init message', function () {
2200 client
._rfb_connection_state
= 'connecting';
2201 client
._rfb_init_state
= 'ProtocolVersion';
2202 client
._init_msg
= sinon
.spy();
2203 client
._sock
._websocket
._receive_data(new Uint8Array([1, 2, 3]));
2204 expect(client
._init_msg
).to
.have
.been
.calledOnce
;
2207 it('should process all normal messages directly', function () {
2208 const spy
= sinon
.spy();
2209 client
.addEventListener("bell", spy
);
2210 client
._sock
._websocket
._receive_data(new Uint8Array([0x02, 0x02]));
2211 expect(spy
).to
.have
.been
.calledTwice
;
2215 it('should update the state to ProtocolVersion on open (if the state is "connecting")', function () {
2216 client
= new RFB(document
.createElement('div'), 'wss://host:8675');
2218 client
._sock
._websocket
._open();
2219 expect(client
._rfb_init_state
).to
.equal('ProtocolVersion');
2222 it('should fail if we are not currently ready to connect and we get an "open" event', function () {
2223 sinon
.spy(client
, "_fail");
2224 client
._rfb_connection_state
= 'connected';
2225 client
._sock
._websocket
._open();
2226 expect(client
._fail
).to
.have
.been
.calledOnce
;
2230 it('should transition to "disconnected" from "disconnecting" on a close event', function () {
2231 const real
= client
._sock
._websocket
.close
;
2232 client
._sock
._websocket
.close
= () => {};
2233 client
.disconnect();
2234 expect(client
._rfb_connection_state
).to
.equal('disconnecting');
2235 client
._sock
._websocket
.close
= real
;
2236 client
._sock
._websocket
.close();
2237 expect(client
._rfb_connection_state
).to
.equal('disconnected');
2240 it('should fail if we get a close event while connecting', function () {
2241 sinon
.spy(client
, "_fail");
2242 client
._rfb_connection_state
= 'connecting';
2243 client
._sock
._websocket
.close();
2244 expect(client
._fail
).to
.have
.been
.calledOnce
;
2247 it('should unregister close event handler', function () {
2248 sinon
.spy(client
._sock
, 'off');
2249 client
.disconnect();
2250 client
._sock
._websocket
.close();
2251 expect(client
._sock
.off
).to
.have
.been
.calledWith('close');
2254 // error events do nothing