1 const expect
= chai
.expect
;
3 import RFB
from '../core/rfb.js';
4 import Websock
from '../core/websock.js';
5 import ZStream
from "../vendor/pako/lib/zlib/zstream.js";
6 import { deflateInit
, deflate
} from "../vendor/pako/lib/zlib/deflate.js";
7 import { encodings
} from '../core/encodings.js';
8 import { toUnsigned32bit
} from '../core/util/int.js';
9 import { encodeUTF8
} from '../core/util/strings.js';
11 import FakeWebSocket
from './fake.websocket.js';
13 /* UIEvent constructor polyfill for IE */
15 if (typeof window
.UIEvent
=== "function") return;
17 function UIEvent( event
, params
) {
18 params
= params
|| { bubbles
: false, cancelable
: false, view
: window
, detail
: undefined };
19 const evt
= document
.createEvent( 'UIEvent' );
20 evt
.initUIEvent( event
, params
.bubbles
, params
.cancelable
, params
.view
, params
.detail
);
24 UIEvent
.prototype = window
.UIEvent
.prototype;
26 window
.UIEvent
= UIEvent
;
29 function push8(arr
, num
) {
34 function push16(arr
, num
) {
36 arr
.push((num
>> 8) & 0xFF,
40 function push32(arr
, num
) {
42 arr
.push((num
>> 24) & 0xFF,
48 function pushString(arr
, string
) {
49 let utf8
= unescape(encodeURIComponent(string
));
50 for (let i
= 0; i
< utf8
.length
; i
++) {
51 arr
.push(utf8
.charCodeAt(i
));
55 function deflateWithSize(data
) {
56 // Adds the size of the string in front before deflating
59 unCompData
.push((data
.length
>> 24) & 0xFF,
60 (data
.length
>> 16) & 0xFF,
61 (data
.length
>> 8) & 0xFF,
62 (data
.length
& 0xFF));
64 for (let i
= 0; i
< data
.length
; i
++) {
65 unCompData
.push(data
.charCodeAt(i
));
68 let strm
= new ZStream();
69 let chunkSize
= 1024 * 10 * 10;
70 strm
.output
= new Uint8Array(chunkSize
);
73 strm
.input
= unCompData
;
74 strm
.avail_in
= strm
.input
.length
;
77 strm
.avail_out
= chunkSize
;
81 return new Uint8Array(strm
.output
.buffer
, 0, strm
.next_out
);
84 describe('Remote Frame Buffer Protocol Client', function () {
88 before(FakeWebSocket
.replace
);
89 after(FakeWebSocket
.restore
);
92 this.clock
= clock
= sinon
.useFakeTimers();
93 // sinon doesn't support this yet
94 raf
= window
.requestAnimationFrame
;
95 window
.requestAnimationFrame
= setTimeout
;
96 // Use a single set of buffers instead of reallocating to
98 const sock
= new Websock();
99 const _sQ
= new Uint8Array(sock
._sQbufferSize
);
100 const rQ
= new Uint8Array(sock
._rQbufferSize
);
102 Websock
.prototype._old_allocate_buffers
= Websock
.prototype._allocate_buffers
;
103 Websock
.prototype._allocate_buffers = function () {
111 Websock
.prototype._allocate_buffers
= Websock
.prototype._old_allocate_buffers
;
112 this.clock
.restore();
113 window
.requestAnimationFrame
= raf
;
119 beforeEach(function () {
120 // Create a container element for all RFB objects to attach to
121 container
= document
.createElement('div');
122 container
.style
.width
= "100%";
123 container
.style
.height
= "100%";
124 document
.body
.appendChild(container
);
126 // And track all created RFB objects
129 afterEach(function () {
130 // Make sure every created RFB object is properly cleaned up
131 // or they might affect subsequent tests
132 rfbs
.forEach(function (rfb
) {
134 expect(rfb
._disconnect
).to
.have
.been
.called
;
138 document
.body
.removeChild(container
);
142 function make_rfb(url
, options
) {
143 url
= url
|| 'wss://host:8675';
144 const rfb
= new RFB(container
, url
, options
);
146 rfb
._sock
._websocket
._open();
147 rfb
._rfb_connection_state
= 'connected';
148 sinon
.spy(rfb
, "_disconnect");
153 describe('Connecting/Disconnecting', function () {
154 describe('#RFB', function () {
155 it('should set the current state to "connecting"', function () {
156 const client
= new RFB(document
.createElement('div'), 'wss://host:8675');
157 client
._rfb_connection_state
= '';
159 expect(client
._rfb_connection_state
).to
.equal('connecting');
162 it('should actually connect to the websocket', function () {
163 const client
= new RFB(document
.createElement('div'), 'ws://HOST:8675/PATH');
164 sinon
.spy(client
._sock
, 'open');
166 expect(client
._sock
.open
).to
.have
.been
.calledOnce
;
167 expect(client
._sock
.open
).to
.have
.been
.calledWith('ws://HOST:8675/PATH');
171 describe('#disconnect', function () {
173 beforeEach(function () {
177 it('should go to state "disconnecting" before "disconnected"', function () {
178 sinon
.spy(client
, '_updateConnectionState');
180 expect(client
._updateConnectionState
).to
.have
.been
.calledTwice
;
181 expect(client
._updateConnectionState
.getCall(0).args
[0])
182 .to
.equal('disconnecting');
183 expect(client
._updateConnectionState
.getCall(1).args
[0])
184 .to
.equal('disconnected');
185 expect(client
._rfb_connection_state
).to
.equal('disconnected');
188 it('should unregister error event handler', function () {
189 sinon
.spy(client
._sock
, 'off');
191 expect(client
._sock
.off
).to
.have
.been
.calledWith('error');
194 it('should unregister message event handler', function () {
195 sinon
.spy(client
._sock
, 'off');
197 expect(client
._sock
.off
).to
.have
.been
.calledWith('message');
200 it('should unregister open event handler', function () {
201 sinon
.spy(client
._sock
, 'off');
203 expect(client
._sock
.off
).to
.have
.been
.calledWith('open');
207 describe('#sendCredentials', function () {
209 beforeEach(function () {
211 client
._rfb_connection_state
= 'connecting';
214 it('should set the rfb credentials properly"', function () {
215 client
.sendCredentials({ password
: 'pass' });
216 expect(client
._rfb_credentials
).to
.deep
.equal({ password
: 'pass' });
219 it('should call init_msg "soon"', function () {
220 client
._init_msg
= sinon
.spy();
221 client
.sendCredentials({ password
: 'pass' });
223 expect(client
._init_msg
).to
.have
.been
.calledOnce
;
228 describe('Public API Basic Behavior', function () {
230 beforeEach(function () {
234 describe('#sendCtrlAlDel', function () {
235 it('should sent ctrl[down]-alt[down]-del[down] then del[up]-alt[up]-ctrl[up]', function () {
236 const expected
= {_sQ
: new Uint8Array(48), _sQlen
: 0, flush
: () => {}};
237 RFB
.messages
.keyEvent(expected
, 0xFFE3, 1);
238 RFB
.messages
.keyEvent(expected
, 0xFFE9, 1);
239 RFB
.messages
.keyEvent(expected
, 0xFFFF, 1);
240 RFB
.messages
.keyEvent(expected
, 0xFFFF, 0);
241 RFB
.messages
.keyEvent(expected
, 0xFFE9, 0);
242 RFB
.messages
.keyEvent(expected
, 0xFFE3, 0);
244 client
.sendCtrlAltDel();
245 expect(client
._sock
).to
.have
.sent(expected
._sQ
);
248 it('should not send the keys if we are not in a normal state', function () {
249 sinon
.spy(client
._sock
, 'flush');
250 client
._rfb_connection_state
= "connecting";
251 client
.sendCtrlAltDel();
252 expect(client
._sock
.flush
).to
.not
.have
.been
.called
;
255 it('should not send the keys if we are set as view_only', function () {
256 sinon
.spy(client
._sock
, 'flush');
257 client
._viewOnly
= true;
258 client
.sendCtrlAltDel();
259 expect(client
._sock
.flush
).to
.not
.have
.been
.called
;
263 describe('#sendKey', function () {
264 it('should send a single key with the given code and state (down = true)', function () {
265 const expected
= {_sQ
: new Uint8Array(8), _sQlen
: 0, flush
: () => {}};
266 RFB
.messages
.keyEvent(expected
, 123, 1);
267 client
.sendKey(123, 'Key123', true);
268 expect(client
._sock
).to
.have
.sent(expected
._sQ
);
271 it('should send both a down and up event if the state is not specified', function () {
272 const expected
= {_sQ
: new Uint8Array(16), _sQlen
: 0, flush
: () => {}};
273 RFB
.messages
.keyEvent(expected
, 123, 1);
274 RFB
.messages
.keyEvent(expected
, 123, 0);
275 client
.sendKey(123, 'Key123');
276 expect(client
._sock
).to
.have
.sent(expected
._sQ
);
279 it('should not send the key if we are not in a normal state', function () {
280 sinon
.spy(client
._sock
, 'flush');
281 client
._rfb_connection_state
= "connecting";
282 client
.sendKey(123, 'Key123');
283 expect(client
._sock
.flush
).to
.not
.have
.been
.called
;
286 it('should not send the key if we are set as view_only', function () {
287 sinon
.spy(client
._sock
, 'flush');
288 client
._viewOnly
= true;
289 client
.sendKey(123, 'Key123');
290 expect(client
._sock
.flush
).to
.not
.have
.been
.called
;
293 it('should send QEMU extended events if supported', function () {
294 client
._qemuExtKeyEventSupported
= true;
295 const expected
= {_sQ
: new Uint8Array(12), _sQlen
: 0, flush
: () => {}};
296 RFB
.messages
.QEMUExtendedKeyEvent(expected
, 0x20, true, 0x0039);
297 client
.sendKey(0x20, 'Space', true);
298 expect(client
._sock
).to
.have
.sent(expected
._sQ
);
301 it('should not send QEMU extended events if unknown key code', function () {
302 client
._qemuExtKeyEventSupported
= true;
303 const expected
= {_sQ
: new Uint8Array(8), _sQlen
: 0, flush
: () => {}};
304 RFB
.messages
.keyEvent(expected
, 123, 1);
305 client
.sendKey(123, 'FooBar', true);
306 expect(client
._sock
).to
.have
.sent(expected
._sQ
);
310 describe('#focus', function () {
311 it('should move focus to canvas object', function () {
312 client
._canvas
.focus
= sinon
.spy();
314 expect(client
._canvas
.focus
).to
.have
.been
.calledOnce
;
318 describe('#blur', function () {
319 it('should remove focus from canvas object', function () {
320 client
._canvas
.blur
= sinon
.spy();
322 expect(client
._canvas
.blur
).to
.have
.been
.calledOnce
;
326 describe('#clipboardPasteFrom', function () {
327 describe('Clipboard update handling', function () {
328 beforeEach(function () {
329 sinon
.spy(RFB
.messages
, 'clientCutText');
330 sinon
.spy(RFB
.messages
, 'extendedClipboardNotify');
333 afterEach(function () {
334 RFB
.messages
.clientCutText
.restore();
335 RFB
.messages
.extendedClipboardNotify
.restore();
338 it('should send the given text in an clipboard update', function () {
339 client
.clipboardPasteFrom('abc');
341 expect(RFB
.messages
.clientCutText
).to
.have
.been
.calledOnce
;
342 expect(RFB
.messages
.clientCutText
).to
.have
.been
.calledWith(client
._sock
,
343 new Uint8Array([97, 98, 99]));
346 it('should send an notify if extended clipboard is supported by server', function () {
347 // Send our capabilities
348 let data
= [3, 0, 0, 0];
349 const flags
= [0x1F, 0x00, 0x00, 0x01];
350 let fileSizes
= [0x00, 0x00, 0x00, 0x1E];
352 push32(data
, toUnsigned32bit(-8));
353 data
= data
.concat(flags
);
354 data
= data
.concat(fileSizes
);
355 client
._sock
._websocket
._receive_data(new Uint8Array(data
));
357 client
.clipboardPasteFrom('extended test');
358 expect(RFB
.messages
.extendedClipboardNotify
).to
.have
.been
.calledOnce
;
362 it('should flush multiple times for large clipboards', function () {
363 sinon
.spy(client
._sock
, 'flush');
365 for (let i
= 0; i
< client
._sock
._sQbufferSize
+ 100; i
++) {
368 client
.clipboardPasteFrom(long_text
);
369 expect(client
._sock
.flush
).to
.have
.been
.calledTwice
;
372 it('should not send the text if we are not in a normal state', function () {
373 sinon
.spy(client
._sock
, 'flush');
374 client
._rfb_connection_state
= "connecting";
375 client
.clipboardPasteFrom('abc');
376 expect(client
._sock
.flush
).to
.not
.have
.been
.called
;
380 describe("XVP operations", function () {
381 beforeEach(function () {
382 client
._rfb_xvp_ver
= 1;
385 it('should send the shutdown signal on #machineShutdown', function () {
386 client
.machineShutdown();
387 expect(client
._sock
).to
.have
.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x02]));
390 it('should send the reboot signal on #machineReboot', function () {
391 client
.machineReboot();
392 expect(client
._sock
).to
.have
.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x03]));
395 it('should send the reset signal on #machineReset', function () {
396 client
.machineReset();
397 expect(client
._sock
).to
.have
.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x04]));
400 it('should not send XVP operations with higher versions than we support', function () {
401 sinon
.spy(client
._sock
, 'flush');
403 expect(client
._sock
.flush
).to
.not
.have
.been
.called
;
408 describe('Clipping', function () {
410 beforeEach(function () {
412 container
.style
.width
= '70px';
413 container
.style
.height
= '80px';
414 client
.clipViewport
= true;
417 it('should update display clip state when changing the property', function () {
418 const spy
= sinon
.spy(client
._display
, "clipViewport", ["set"]);
420 client
.clipViewport
= false;
421 expect(spy
.set).to
.have
.been
.calledOnce
;
422 expect(spy
.set).to
.have
.been
.calledWith(false);
423 spy
.set.resetHistory();
425 client
.clipViewport
= true;
426 expect(spy
.set).to
.have
.been
.calledOnce
;
427 expect(spy
.set).to
.have
.been
.calledWith(true);
430 it('should update the viewport when the container size changes', function () {
431 sinon
.spy(client
._display
, "viewportChangeSize");
433 container
.style
.width
= '40px';
434 container
.style
.height
= '50px';
435 const event
= new UIEvent('resize');
436 window
.dispatchEvent(event
);
439 expect(client
._display
.viewportChangeSize
).to
.have
.been
.calledOnce
;
440 expect(client
._display
.viewportChangeSize
).to
.have
.been
.calledWith(40, 50);
443 it('should update the viewport when the remote session resizes', function () {
444 // Simple ExtendedDesktopSize FBU message
445 const incoming
= [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
446 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0xfe, 0xcc,
447 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
448 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff,
449 0x00, 0x00, 0x00, 0x00 ];
451 sinon
.spy(client
._display
, "viewportChangeSize");
453 client
._sock
._websocket
._receive_data(new Uint8Array(incoming
));
455 // FIXME: Display implicitly calls viewportChangeSize() when
456 // resizing the framebuffer, hence calledTwice.
457 expect(client
._display
.viewportChangeSize
).to
.have
.been
.calledTwice
;
458 expect(client
._display
.viewportChangeSize
).to
.have
.been
.calledWith(70, 80);
461 it('should not update the viewport if not clipping', function () {
462 client
.clipViewport
= false;
463 sinon
.spy(client
._display
, "viewportChangeSize");
465 container
.style
.width
= '40px';
466 container
.style
.height
= '50px';
467 const event
= new UIEvent('resize');
468 window
.dispatchEvent(event
);
471 expect(client
._display
.viewportChangeSize
).to
.not
.have
.been
.called
;
474 it('should not update the viewport if scaling', function () {
475 client
.scaleViewport
= true;
476 sinon
.spy(client
._display
, "viewportChangeSize");
478 container
.style
.width
= '40px';
479 container
.style
.height
= '50px';
480 const event
= new UIEvent('resize');
481 window
.dispatchEvent(event
);
484 expect(client
._display
.viewportChangeSize
).to
.not
.have
.been
.called
;
487 describe('Dragging', function () {
488 beforeEach(function () {
489 client
.dragViewport
= true;
490 sinon
.spy(RFB
.messages
, "pointerEvent");
493 afterEach(function () {
494 RFB
.messages
.pointerEvent
.restore();
497 it('should not send button messages when initiating viewport dragging', function () {
498 client
._handleMouseButton(13, 9, 0x001);
499 expect(RFB
.messages
.pointerEvent
).to
.not
.have
.been
.called
;
502 it('should send button messages when release without movement', function () {
504 client
._handleMouseButton(13, 9, 0x001);
505 client
._handleMouseButton(13, 9, 0x000);
506 expect(RFB
.messages
.pointerEvent
).to
.have
.been
.calledTwice
;
508 RFB
.messages
.pointerEvent
.resetHistory();
511 client
._handleMouseButton(13, 9, 0x001);
512 client
._handleMouseMove(15, 14);
513 client
._handleMouseButton(15, 14, 0x000);
514 expect(RFB
.messages
.pointerEvent
).to
.have
.been
.calledTwice
;
517 it('should send button message directly when drag is disabled', function () {
518 client
.dragViewport
= false;
519 client
._handleMouseButton(13, 9, 0x001);
520 expect(RFB
.messages
.pointerEvent
).to
.have
.been
.calledOnce
;
523 it('should be initiate viewport dragging on sufficient movement', function () {
524 sinon
.spy(client
._display
, "viewportChangePos");
526 // Too small movement
528 client
._handleMouseButton(13, 9, 0x001);
529 client
._handleMouseMove(18, 9);
531 expect(RFB
.messages
.pointerEvent
).to
.not
.have
.been
.called
;
532 expect(client
._display
.viewportChangePos
).to
.not
.have
.been
.called
;
534 // Sufficient movement
536 client
._handleMouseMove(43, 9);
538 expect(RFB
.messages
.pointerEvent
).to
.not
.have
.been
.called
;
539 expect(client
._display
.viewportChangePos
).to
.have
.been
.calledOnce
;
540 expect(client
._display
.viewportChangePos
).to
.have
.been
.calledWith(-30, 0);
542 client
._display
.viewportChangePos
.resetHistory();
544 // Now a small movement should move right away
546 client
._handleMouseMove(43, 14);
548 expect(RFB
.messages
.pointerEvent
).to
.not
.have
.been
.called
;
549 expect(client
._display
.viewportChangePos
).to
.have
.been
.calledOnce
;
550 expect(client
._display
.viewportChangePos
).to
.have
.been
.calledWith(0, -5);
553 it('should not send button messages when dragging ends', function () {
554 // First the movement
556 client
._handleMouseButton(13, 9, 0x001);
557 client
._handleMouseMove(43, 9);
558 client
._handleMouseButton(43, 9, 0x000);
560 expect(RFB
.messages
.pointerEvent
).to
.not
.have
.been
.called
;
563 it('should terminate viewport dragging on a button up event', function () {
564 // First the dragging movement
566 client
._handleMouseButton(13, 9, 0x001);
567 client
._handleMouseMove(43, 9);
568 client
._handleMouseButton(43, 9, 0x000);
570 // Another movement now should not move the viewport
572 sinon
.spy(client
._display
, "viewportChangePos");
574 client
._handleMouseMove(43, 59);
576 expect(client
._display
.viewportChangePos
).to
.not
.have
.been
.called
;
581 describe('Scaling', function () {
583 beforeEach(function () {
585 container
.style
.width
= '70px';
586 container
.style
.height
= '80px';
587 client
.scaleViewport
= true;
590 it('should update display scale factor when changing the property', function () {
591 const spy
= sinon
.spy(client
._display
, "scale", ["set"]);
592 sinon
.spy(client
._display
, "autoscale");
594 client
.scaleViewport
= false;
595 expect(spy
.set).to
.have
.been
.calledOnce
;
596 expect(spy
.set).to
.have
.been
.calledWith(1.0);
597 expect(client
._display
.autoscale
).to
.not
.have
.been
.called
;
599 client
.scaleViewport
= true;
600 expect(client
._display
.autoscale
).to
.have
.been
.calledOnce
;
601 expect(client
._display
.autoscale
).to
.have
.been
.calledWith(70, 80);
604 it('should update the clipping setting when changing the property', function () {
605 client
.clipViewport
= true;
607 const spy
= sinon
.spy(client
._display
, "clipViewport", ["set"]);
609 client
.scaleViewport
= false;
610 expect(spy
.set).to
.have
.been
.calledOnce
;
611 expect(spy
.set).to
.have
.been
.calledWith(true);
613 spy
.set.resetHistory();
615 client
.scaleViewport
= true;
616 expect(spy
.set).to
.have
.been
.calledOnce
;
617 expect(spy
.set).to
.have
.been
.calledWith(false);
620 it('should update the scaling when the container size changes', function () {
621 sinon
.spy(client
._display
, "autoscale");
623 container
.style
.width
= '40px';
624 container
.style
.height
= '50px';
625 const event
= new UIEvent('resize');
626 window
.dispatchEvent(event
);
629 expect(client
._display
.autoscale
).to
.have
.been
.calledOnce
;
630 expect(client
._display
.autoscale
).to
.have
.been
.calledWith(40, 50);
633 it('should update the scaling when the remote session resizes', function () {
634 // Simple ExtendedDesktopSize FBU message
635 const incoming
= [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
636 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0xfe, 0xcc,
637 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
638 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff,
639 0x00, 0x00, 0x00, 0x00 ];
641 sinon
.spy(client
._display
, "autoscale");
643 client
._sock
._websocket
._receive_data(new Uint8Array(incoming
));
645 expect(client
._display
.autoscale
).to
.have
.been
.calledOnce
;
646 expect(client
._display
.autoscale
).to
.have
.been
.calledWith(70, 80);
649 it('should not update the display scale factor if not scaling', function () {
650 client
.scaleViewport
= false;
652 sinon
.spy(client
._display
, "autoscale");
654 container
.style
.width
= '40px';
655 container
.style
.height
= '50px';
656 const event
= new UIEvent('resize');
657 window
.dispatchEvent(event
);
660 expect(client
._display
.autoscale
).to
.not
.have
.been
.called
;
664 describe('Remote resize', function () {
666 beforeEach(function () {
668 client
._supportsSetDesktopSize
= true;
669 client
.resizeSession
= true;
670 container
.style
.width
= '70px';
671 container
.style
.height
= '80px';
672 sinon
.spy(RFB
.messages
, "setDesktopSize");
675 afterEach(function () {
676 RFB
.messages
.setDesktopSize
.restore();
679 it('should only request a resize when turned on', function () {
680 client
.resizeSession
= false;
681 expect(RFB
.messages
.setDesktopSize
).to
.not
.have
.been
.called
;
682 client
.resizeSession
= true;
683 expect(RFB
.messages
.setDesktopSize
).to
.have
.been
.calledOnce
;
686 it('should request a resize when initially connecting', function () {
687 // Simple ExtendedDesktopSize FBU message
688 const incoming
= [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
689 0x00, 0x04, 0x00, 0x04, 0xff, 0xff, 0xfe, 0xcc,
690 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
691 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04,
692 0x00, 0x00, 0x00, 0x00 ];
694 // First message should trigger a resize
696 client
._supportsSetDesktopSize
= false;
698 client
._sock
._websocket
._receive_data(new Uint8Array(incoming
));
700 expect(RFB
.messages
.setDesktopSize
).to
.have
.been
.calledOnce
;
701 expect(RFB
.messages
.setDesktopSize
).to
.have
.been
.calledWith(sinon
.match
.object
, 70, 80, 0, 0);
703 RFB
.messages
.setDesktopSize
.resetHistory();
705 // Second message should not trigger a resize
707 client
._sock
._websocket
._receive_data(new Uint8Array(incoming
));
709 expect(RFB
.messages
.setDesktopSize
).to
.not
.have
.been
.called
;
712 it('should request a resize when the container resizes', function () {
713 container
.style
.width
= '40px';
714 container
.style
.height
= '50px';
715 const event
= new UIEvent('resize');
716 window
.dispatchEvent(event
);
719 expect(RFB
.messages
.setDesktopSize
).to
.have
.been
.calledOnce
;
720 expect(RFB
.messages
.setDesktopSize
).to
.have
.been
.calledWith(sinon
.match
.object
, 40, 50, 0, 0);
723 it('should not resize until the container size is stable', function () {
724 container
.style
.width
= '20px';
725 container
.style
.height
= '30px';
726 const event1
= new UIEvent('resize');
727 window
.dispatchEvent(event1
);
730 expect(RFB
.messages
.setDesktopSize
).to
.not
.have
.been
.called
;
732 container
.style
.width
= '40px';
733 container
.style
.height
= '50px';
734 const event2
= new UIEvent('resize');
735 window
.dispatchEvent(event2
);
738 expect(RFB
.messages
.setDesktopSize
).to
.not
.have
.been
.called
;
742 expect(RFB
.messages
.setDesktopSize
).to
.have
.been
.calledOnce
;
743 expect(RFB
.messages
.setDesktopSize
).to
.have
.been
.calledWith(sinon
.match
.object
, 40, 50, 0, 0);
746 it('should not resize when resize is disabled', function () {
747 client
._resizeSession
= false;
749 container
.style
.width
= '40px';
750 container
.style
.height
= '50px';
751 const event
= new UIEvent('resize');
752 window
.dispatchEvent(event
);
755 expect(RFB
.messages
.setDesktopSize
).to
.not
.have
.been
.called
;
758 it('should not resize when resize is not supported', function () {
759 client
._supportsSetDesktopSize
= false;
761 container
.style
.width
= '40px';
762 container
.style
.height
= '50px';
763 const event
= new UIEvent('resize');
764 window
.dispatchEvent(event
);
767 expect(RFB
.messages
.setDesktopSize
).to
.not
.have
.been
.called
;
770 it('should not resize when in view only mode', function () {
771 client
._viewOnly
= true;
773 container
.style
.width
= '40px';
774 container
.style
.height
= '50px';
775 const event
= new UIEvent('resize');
776 window
.dispatchEvent(event
);
779 expect(RFB
.messages
.setDesktopSize
).to
.not
.have
.been
.called
;
782 it('should not try to override a server resize', function () {
783 // Simple ExtendedDesktopSize FBU message
784 const incoming
= [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
785 0x00, 0x04, 0x00, 0x04, 0xff, 0xff, 0xfe, 0xcc,
786 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
787 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04,
788 0x00, 0x00, 0x00, 0x00 ];
790 client
._sock
._websocket
._receive_data(new Uint8Array(incoming
));
792 expect(RFB
.messages
.setDesktopSize
).to
.not
.have
.been
.called
;
796 describe('Misc Internals', function () {
797 describe('#_updateConnectionState', function () {
799 beforeEach(function () {
803 it('should clear the disconnect timer if the state is not "disconnecting"', function () {
804 const spy
= sinon
.spy();
805 client
._disconnTimer
= setTimeout(spy
, 50);
806 client
._rfb_connection_state
= 'connecting';
807 client
._updateConnectionState('connected');
809 expect(spy
).to
.not
.have
.been
.called
;
810 expect(client
._disconnTimer
).to
.be
.null;
813 it('should set the rfb_connection_state', function () {
814 client
._rfb_connection_state
= 'connecting';
815 client
._updateConnectionState('connected');
816 expect(client
._rfb_connection_state
).to
.equal('connected');
819 it('should not change the state when we are disconnected', function () {
821 expect(client
._rfb_connection_state
).to
.equal('disconnected');
822 client
._updateConnectionState('connecting');
823 expect(client
._rfb_connection_state
).to
.not
.equal('connecting');
826 it('should ignore state changes to the same state', function () {
827 const connectSpy
= sinon
.spy();
828 client
.addEventListener("connect", connectSpy
);
830 expect(client
._rfb_connection_state
).to
.equal('connected');
831 client
._updateConnectionState('connected');
832 expect(connectSpy
).to
.not
.have
.been
.called
;
836 const disconnectSpy
= sinon
.spy();
837 client
.addEventListener("disconnect", disconnectSpy
);
839 expect(client
._rfb_connection_state
).to
.equal('disconnected');
840 client
._updateConnectionState('disconnected');
841 expect(disconnectSpy
).to
.not
.have
.been
.called
;
844 it('should ignore illegal state changes', function () {
845 const spy
= sinon
.spy();
846 client
.addEventListener("disconnect", spy
);
847 client
._updateConnectionState('disconnected');
848 expect(client
._rfb_connection_state
).to
.not
.equal('disconnected');
849 expect(spy
).to
.not
.have
.been
.called
;
853 describe('#_fail', function () {
855 beforeEach(function () {
859 it('should close the WebSocket connection', function () {
860 sinon
.spy(client
._sock
, 'close');
862 expect(client
._sock
.close
).to
.have
.been
.calledOnce
;
865 it('should transition to disconnected', function () {
866 sinon
.spy(client
, '_updateConnectionState');
868 this.clock
.tick(2000);
869 expect(client
._updateConnectionState
).to
.have
.been
.called
;
870 expect(client
._rfb_connection_state
).to
.equal('disconnected');
873 it('should set clean_disconnect variable', function () {
874 client
._rfb_clean_disconnect
= true;
875 client
._rfb_connection_state
= 'connected';
877 expect(client
._rfb_clean_disconnect
).to
.be
.false;
880 it('should result in disconnect event with clean set to false', function () {
881 client
._rfb_connection_state
= 'connected';
882 const spy
= sinon
.spy();
883 client
.addEventListener("disconnect", spy
);
885 this.clock
.tick(2000);
886 expect(spy
).to
.have
.been
.calledOnce
;
887 expect(spy
.args
[0][0].detail
.clean
).to
.be
.false;
893 describe('Connection States', function () {
894 describe('connecting', function () {
895 it('should open the websocket connection', function () {
896 const client
= new RFB(document
.createElement('div'),
897 'ws://HOST:8675/PATH');
898 sinon
.spy(client
._sock
, 'open');
900 expect(client
._sock
.open
).to
.have
.been
.calledOnce
;
904 describe('connected', function () {
906 beforeEach(function () {
910 it('should result in a connect event if state becomes connected', function () {
911 const spy
= sinon
.spy();
912 client
.addEventListener("connect", spy
);
913 client
._rfb_connection_state
= 'connecting';
914 client
._updateConnectionState('connected');
915 expect(spy
).to
.have
.been
.calledOnce
;
918 it('should not result in a connect event if the state is not "connected"', function () {
919 const spy
= sinon
.spy();
920 client
.addEventListener("connect", spy
);
921 client
._sock
._websocket
.open
= () => {}; // explicitly don't call onopen
922 client
._updateConnectionState('connecting');
923 expect(spy
).to
.not
.have
.been
.called
;
927 describe('disconnecting', function () {
929 beforeEach(function () {
933 it('should force disconnect if we do not call Websock.onclose within the disconnection timeout', function () {
934 sinon
.spy(client
, '_updateConnectionState');
935 client
._sock
._websocket
.close
= () => {}; // explicitly don't call onclose
936 client
._updateConnectionState('disconnecting');
937 this.clock
.tick(3 * 1000);
938 expect(client
._updateConnectionState
).to
.have
.been
.calledTwice
;
939 expect(client
._rfb_disconnect_reason
).to
.not
.equal("");
940 expect(client
._rfb_connection_state
).to
.equal("disconnected");
943 it('should not fail if Websock.onclose gets called within the disconnection timeout', function () {
944 client
._updateConnectionState('disconnecting');
945 this.clock
.tick(3 * 1000 / 2);
946 client
._sock
._websocket
.close();
947 this.clock
.tick(3 * 1000 / 2 + 1);
948 expect(client
._rfb_connection_state
).to
.equal('disconnected');
951 it('should close the WebSocket connection', function () {
952 sinon
.spy(client
._sock
, 'close');
953 client
._updateConnectionState('disconnecting');
954 expect(client
._sock
.close
).to
.have
.been
.calledOnce
;
957 it('should not result in a disconnect event', function () {
958 const spy
= sinon
.spy();
959 client
.addEventListener("disconnect", spy
);
960 client
._sock
._websocket
.close
= () => {}; // explicitly don't call onclose
961 client
._updateConnectionState('disconnecting');
962 expect(spy
).to
.not
.have
.been
.called
;
966 describe('disconnected', function () {
968 beforeEach(function () {
969 client
= new RFB(document
.createElement('div'), 'ws://HOST:8675/PATH');
972 it('should result in a disconnect event if state becomes "disconnected"', function () {
973 const spy
= sinon
.spy();
974 client
.addEventListener("disconnect", spy
);
975 client
._rfb_connection_state
= 'disconnecting';
976 client
._updateConnectionState('disconnected');
977 expect(spy
).to
.have
.been
.calledOnce
;
978 expect(spy
.args
[0][0].detail
.clean
).to
.be
.true;
981 it('should result in a disconnect event without msg when no reason given', function () {
982 const spy
= sinon
.spy();
983 client
.addEventListener("disconnect", spy
);
984 client
._rfb_connection_state
= 'disconnecting';
985 client
._rfb_disconnect_reason
= "";
986 client
._updateConnectionState('disconnected');
987 expect(spy
).to
.have
.been
.calledOnce
;
988 expect(spy
.args
[0].length
).to
.equal(1);
993 describe('Protocol Initialization States', function () {
995 beforeEach(function () {
997 client
._rfb_connection_state
= 'connecting';
1000 describe('ProtocolVersion', function () {
1001 function send_ver(ver
, client
) {
1002 const arr
= new Uint8Array(12);
1003 for (let i
= 0; i
< ver
.length
; i
++) {
1004 arr
[i
+4] = ver
.charCodeAt(i
);
1006 arr
[0] = 'R'; arr
[1] = 'F'; arr
[2] = 'B'; arr
[3] = ' ';
1008 client
._sock
._websocket
._receive_data(arr
);
1011 describe('version parsing', function () {
1012 it('should interpret version 003.003 as version 3.3', function () {
1013 send_ver('003.003', client
);
1014 expect(client
._rfb_version
).to
.equal(3.3);
1017 it('should interpret version 003.006 as version 3.3', function () {
1018 send_ver('003.006', client
);
1019 expect(client
._rfb_version
).to
.equal(3.3);
1022 it('should interpret version 003.889 as version 3.3', function () {
1023 send_ver('003.889', client
);
1024 expect(client
._rfb_version
).to
.equal(3.3);
1027 it('should interpret version 003.007 as version 3.7', function () {
1028 send_ver('003.007', client
);
1029 expect(client
._rfb_version
).to
.equal(3.7);
1032 it('should interpret version 003.008 as version 3.8', function () {
1033 send_ver('003.008', client
);
1034 expect(client
._rfb_version
).to
.equal(3.8);
1037 it('should interpret version 004.000 as version 3.8', function () {
1038 send_ver('004.000', client
);
1039 expect(client
._rfb_version
).to
.equal(3.8);
1042 it('should interpret version 004.001 as version 3.8', function () {
1043 send_ver('004.001', client
);
1044 expect(client
._rfb_version
).to
.equal(3.8);
1047 it('should interpret version 005.000 as version 3.8', function () {
1048 send_ver('005.000', client
);
1049 expect(client
._rfb_version
).to
.equal(3.8);
1052 it('should fail on an invalid version', function () {
1053 sinon
.spy(client
, "_fail");
1054 send_ver('002.000', client
);
1055 expect(client
._fail
).to
.have
.been
.calledOnce
;
1059 it('should send back the interpreted version', function () {
1060 send_ver('004.000', client
);
1062 const expected_str
= 'RFB 003.008\n';
1063 const expected
= [];
1064 for (let i
= 0; i
< expected_str
.length
; i
++) {
1065 expected
[i
] = expected_str
.charCodeAt(i
);
1068 expect(client
._sock
).to
.have
.sent(new Uint8Array(expected
));
1071 it('should transition to the Security state on successful negotiation', function () {
1072 send_ver('003.008', client
);
1073 expect(client
._rfb_init_state
).to
.equal('Security');
1076 describe('Repeater', function () {
1077 beforeEach(function () {
1078 client
= make_rfb('wss://host:8675', { repeaterID
: "12345" });
1079 client
._rfb_connection_state
= 'connecting';
1082 it('should interpret version 000.000 as a repeater', function () {
1083 send_ver('000.000', client
);
1084 expect(client
._rfb_version
).to
.equal(0);
1086 const sent_data
= client
._sock
._websocket
._get_sent_data();
1087 expect(new Uint8Array(sent_data
.buffer
, 0, 9)).to
.array
.equal(new Uint8Array([73, 68, 58, 49, 50, 51, 52, 53, 0]));
1088 expect(sent_data
).to
.have
.length(250);
1091 it('should handle two step repeater negotiation', function () {
1092 send_ver('000.000', client
);
1093 send_ver('003.008', client
);
1094 expect(client
._rfb_version
).to
.equal(3.8);
1099 describe('Security', function () {
1100 beforeEach(function () {
1101 client
._rfb_init_state
= 'Security';
1104 it('should simply receive the auth scheme when for versions < 3.7', function () {
1105 client
._rfb_version
= 3.6;
1106 const auth_scheme_raw
= [1, 2, 3, 4];
1107 const auth_scheme
= (auth_scheme_raw
[0] << 24) + (auth_scheme_raw
[1] << 16) +
1108 (auth_scheme_raw
[2] << 8) + auth_scheme_raw
[3];
1109 client
._sock
._websocket
._receive_data(new Uint8Array(auth_scheme_raw
));
1110 expect(client
._rfb_auth_scheme
).to
.equal(auth_scheme
);
1113 it('should prefer no authentication is possible', function () {
1114 client
._rfb_version
= 3.7;
1115 const auth_schemes
= [2, 1, 3];
1116 client
._sock
._websocket
._receive_data(new Uint8Array(auth_schemes
));
1117 expect(client
._rfb_auth_scheme
).to
.equal(1);
1118 expect(client
._sock
).to
.have
.sent(new Uint8Array([1, 1]));
1121 it('should choose for the most prefered scheme possible for versions >= 3.7', function () {
1122 client
._rfb_version
= 3.7;
1123 const auth_schemes
= [2, 22, 16];
1124 client
._sock
._websocket
._receive_data(new Uint8Array(auth_schemes
));
1125 expect(client
._rfb_auth_scheme
).to
.equal(22);
1126 expect(client
._sock
).to
.have
.sent(new Uint8Array([22]));
1129 it('should fail if there are no supported schemes for versions >= 3.7', function () {
1130 sinon
.spy(client
, "_fail");
1131 client
._rfb_version
= 3.7;
1132 const auth_schemes
= [1, 32];
1133 client
._sock
._websocket
._receive_data(new Uint8Array(auth_schemes
));
1134 expect(client
._fail
).to
.have
.been
.calledOnce
;
1137 it('should fail with the appropriate message if no types are sent for versions >= 3.7', function () {
1138 client
._rfb_version
= 3.7;
1139 const failure_data
= [0, 0, 0, 0, 6, 119, 104, 111, 111, 112, 115];
1140 sinon
.spy(client
, '_fail');
1141 client
._sock
._websocket
._receive_data(new Uint8Array(failure_data
));
1143 expect(client
._fail
).to
.have
.been
.calledOnce
;
1144 expect(client
._fail
).to
.have
.been
.calledWith(
1145 'Security negotiation failed on no security types (reason: whoops)');
1148 it('should transition to the Authentication state and continue on successful negotiation', function () {
1149 client
._rfb_version
= 3.7;
1150 const auth_schemes
= [1, 1];
1151 client
._negotiate_authentication
= sinon
.spy();
1152 client
._sock
._websocket
._receive_data(new Uint8Array(auth_schemes
));
1153 expect(client
._rfb_init_state
).to
.equal('Authentication');
1154 expect(client
._negotiate_authentication
).to
.have
.been
.calledOnce
;
1158 describe('Authentication', function () {
1159 beforeEach(function () {
1160 client
._rfb_init_state
= 'Security';
1163 function send_security(type
, cl
) {
1164 cl
._sock
._websocket
._receive_data(new Uint8Array([1, type
]));
1167 it('should fail on auth scheme 0 (pre 3.7) with the given message', function () {
1168 client
._rfb_version
= 3.6;
1169 const err_msg
= "Whoopsies";
1170 const data
= [0, 0, 0, 0];
1171 const err_len
= err_msg
.length
;
1172 push32(data
, err_len
);
1173 for (let i
= 0; i
< err_len
; i
++) {
1174 data
.push(err_msg
.charCodeAt(i
));
1177 sinon
.spy(client
, '_fail');
1178 client
._sock
._websocket
._receive_data(new Uint8Array(data
));
1179 expect(client
._fail
).to
.have
.been
.calledWith(
1180 'Security negotiation failed on authentication scheme (reason: Whoopsies)');
1183 it('should transition straight to SecurityResult on "no auth" (1) for versions >= 3.8', function () {
1184 client
._rfb_version
= 3.8;
1185 send_security(1, client
);
1186 expect(client
._rfb_init_state
).to
.equal('SecurityResult');
1189 it('should transition straight to ServerInitialisation on "no auth" for versions < 3.8', function () {
1190 client
._rfb_version
= 3.7;
1191 send_security(1, client
);
1192 expect(client
._rfb_init_state
).to
.equal('ServerInitialisation');
1195 it('should fail on an unknown auth scheme', function () {
1196 sinon
.spy(client
, "_fail");
1197 client
._rfb_version
= 3.8;
1198 send_security(57, client
);
1199 expect(client
._fail
).to
.have
.been
.calledOnce
;
1202 describe('VNC Authentication (type 2) Handler', function () {
1203 beforeEach(function () {
1204 client
._rfb_init_state
= 'Security';
1205 client
._rfb_version
= 3.8;
1208 it('should fire the credentialsrequired event if missing a password', function () {
1209 const spy
= sinon
.spy();
1210 client
.addEventListener("credentialsrequired", spy
);
1211 send_security(2, client
);
1213 const challenge
= [];
1214 for (let i
= 0; i
< 16; i
++) { challenge
[i
] = i
; }
1215 client
._sock
._websocket
._receive_data(new Uint8Array(challenge
));
1217 expect(client
._rfb_credentials
).to
.be
.empty
;
1218 expect(spy
).to
.have
.been
.calledOnce
;
1219 expect(spy
.args
[0][0].detail
.types
).to
.have
.members(["password"]);
1222 it('should encrypt the password with DES and then send it back', function () {
1223 client
._rfb_credentials
= { password
: 'passwd' };
1224 send_security(2, client
);
1225 client
._sock
._websocket
._get_sent_data(); // skip the choice of auth reply
1227 const challenge
= [];
1228 for (let i
= 0; i
< 16; i
++) { challenge
[i
] = i
; }
1229 client
._sock
._websocket
._receive_data(new Uint8Array(challenge
));
1231 const des_pass
= RFB
.genDES('passwd', challenge
);
1232 expect(client
._sock
).to
.have
.sent(new Uint8Array(des_pass
));
1235 it('should transition to SecurityResult immediately after sending the password', function () {
1236 client
._rfb_credentials
= { password
: 'passwd' };
1237 send_security(2, client
);
1239 const challenge
= [];
1240 for (let i
= 0; i
< 16; i
++) { challenge
[i
] = i
; }
1241 client
._sock
._websocket
._receive_data(new Uint8Array(challenge
));
1243 expect(client
._rfb_init_state
).to
.equal('SecurityResult');
1247 describe('XVP Authentication (type 22) Handler', function () {
1248 beforeEach(function () {
1249 client
._rfb_init_state
= 'Security';
1250 client
._rfb_version
= 3.8;
1253 it('should fall through to standard VNC authentication upon completion', function () {
1254 client
._rfb_credentials
= { username
: 'user',
1256 password
: 'password' };
1257 client
._negotiate_std_vnc_auth
= sinon
.spy();
1258 send_security(22, client
);
1259 expect(client
._negotiate_std_vnc_auth
).to
.have
.been
.calledOnce
;
1262 it('should fire the credentialsrequired event if all credentials are missing', function () {
1263 const spy
= sinon
.spy();
1264 client
.addEventListener("credentialsrequired", spy
);
1265 client
._rfb_credentials
= {};
1266 send_security(22, client
);
1268 expect(client
._rfb_credentials
).to
.be
.empty
;
1269 expect(spy
).to
.have
.been
.calledOnce
;
1270 expect(spy
.args
[0][0].detail
.types
).to
.have
.members(["username", "password", "target"]);
1273 it('should fire the credentialsrequired event if some credentials are missing', function () {
1274 const spy
= sinon
.spy();
1275 client
.addEventListener("credentialsrequired", spy
);
1276 client
._rfb_credentials
= { username
: 'user',
1278 send_security(22, client
);
1280 expect(spy
).to
.have
.been
.calledOnce
;
1281 expect(spy
.args
[0][0].detail
.types
).to
.have
.members(["username", "password", "target"]);
1284 it('should send user and target separately', function () {
1285 client
._rfb_credentials
= { username
: 'user',
1287 password
: 'password' };
1288 client
._negotiate_std_vnc_auth
= sinon
.spy();
1290 send_security(22, client
);
1292 const expected
= [22, 4, 6]; // auth selection, len user, len target
1293 for (let i
= 0; i
< 10; i
++) { expected
[i
+3] = 'usertarget'.charCodeAt(i
); }
1295 expect(client
._sock
).to
.have
.sent(new Uint8Array(expected
));
1299 describe('TightVNC Authentication (type 16) Handler', function () {
1300 beforeEach(function () {
1301 client
._rfb_init_state
= 'Security';
1302 client
._rfb_version
= 3.8;
1303 send_security(16, client
);
1304 client
._sock
._websocket
._get_sent_data(); // skip the security reply
1307 function send_num_str_pairs(pairs
, client
) {
1309 push32(data
, pairs
.length
);
1311 for (let i
= 0; i
< pairs
.length
; i
++) {
1312 push32(data
, pairs
[i
][0]);
1313 for (let j
= 0; j
< 4; j
++) {
1314 data
.push(pairs
[i
][1].charCodeAt(j
));
1316 for (let j
= 0; j
< 8; j
++) {
1317 data
.push(pairs
[i
][2].charCodeAt(j
));
1321 client
._sock
._websocket
._receive_data(new Uint8Array(data
));
1324 it('should skip tunnel negotiation if no tunnels are requested', function () {
1325 client
._sock
._websocket
._receive_data(new Uint8Array([0, 0, 0, 0]));
1326 expect(client
._rfb_tightvnc
).to
.be
.true;
1329 it('should fail if no supported tunnels are listed', function () {
1330 sinon
.spy(client
, "_fail");
1331 send_num_str_pairs([[123, 'OTHR', 'SOMETHNG']], client
);
1332 expect(client
._fail
).to
.have
.been
.calledOnce
;
1335 it('should choose the notunnel tunnel type', function () {
1336 send_num_str_pairs([[0, 'TGHT', 'NOTUNNEL'], [123, 'OTHR', 'SOMETHNG']], client
);
1337 expect(client
._sock
).to
.have
.sent(new Uint8Array([0, 0, 0, 0]));
1340 it('should choose the notunnel tunnel type for Siemens devices', function () {
1341 send_num_str_pairs([[1, 'SICR', 'SCHANNEL'], [2, 'SICR', 'SCHANLPW']], client
);
1342 expect(client
._sock
).to
.have
.sent(new Uint8Array([0, 0, 0, 0]));
1345 it('should continue to sub-auth negotiation after tunnel negotiation', function () {
1346 send_num_str_pairs([[0, 'TGHT', 'NOTUNNEL']], client
);
1347 client
._sock
._websocket
._get_sent_data(); // skip the tunnel choice here
1348 send_num_str_pairs([[1, 'STDV', 'NOAUTH__']], client
);
1349 expect(client
._sock
).to
.have
.sent(new Uint8Array([0, 0, 0, 1]));
1350 expect(client
._rfb_init_state
).to
.equal('SecurityResult');
1353 /*it('should attempt to use VNC auth over no auth when possible', function () {
1354 client._rfb_tightvnc = true;
1355 client._negotiate_std_vnc_auth = sinon.spy();
1356 send_num_str_pairs([[1, 'STDV', 'NOAUTH__'], [2, 'STDV', 'VNCAUTH_']], client);
1357 expect(client._sock).to.have.sent([0, 0, 0, 1]);
1358 expect(client._negotiate_std_vnc_auth).to.have.been.calledOnce;
1359 expect(client._rfb_auth_scheme).to.equal(2);
1360 });*/ // while this would make sense, the original code doesn't actually do this
1362 it('should accept the "no auth" auth type and transition to SecurityResult', function () {
1363 client
._rfb_tightvnc
= true;
1364 send_num_str_pairs([[1, 'STDV', 'NOAUTH__']], client
);
1365 expect(client
._sock
).to
.have
.sent(new Uint8Array([0, 0, 0, 1]));
1366 expect(client
._rfb_init_state
).to
.equal('SecurityResult');
1369 it('should accept VNC authentication and transition to that', function () {
1370 client
._rfb_tightvnc
= true;
1371 client
._negotiate_std_vnc_auth
= sinon
.spy();
1372 send_num_str_pairs([[2, 'STDV', 'VNCAUTH__']], client
);
1373 expect(client
._sock
).to
.have
.sent(new Uint8Array([0, 0, 0, 2]));
1374 expect(client
._negotiate_std_vnc_auth
).to
.have
.been
.calledOnce
;
1375 expect(client
._rfb_auth_scheme
).to
.equal(2);
1378 it('should fail if there are no supported auth types', function () {
1379 sinon
.spy(client
, "_fail");
1380 client
._rfb_tightvnc
= true;
1381 send_num_str_pairs([[23, 'stdv', 'badval__']], client
);
1382 expect(client
._fail
).to
.have
.been
.calledOnce
;
1387 describe('SecurityResult', function () {
1388 beforeEach(function () {
1389 client
._rfb_init_state
= 'SecurityResult';
1392 it('should fall through to ServerInitialisation on a response code of 0', function () {
1393 client
._sock
._websocket
._receive_data(new Uint8Array([0, 0, 0, 0]));
1394 expect(client
._rfb_init_state
).to
.equal('ServerInitialisation');
1397 it('should fail on an error code of 1 with the given message for versions >= 3.8', function () {
1398 client
._rfb_version
= 3.8;
1399 sinon
.spy(client
, '_fail');
1400 const failure_data
= [0, 0, 0, 1, 0, 0, 0, 6, 119, 104, 111, 111, 112, 115];
1401 client
._sock
._websocket
._receive_data(new Uint8Array(failure_data
));
1402 expect(client
._fail
).to
.have
.been
.calledWith(
1403 'Security negotiation failed on security result (reason: whoops)');
1406 it('should fail on an error code of 1 with a standard message for version < 3.8', function () {
1407 sinon
.spy(client
, '_fail');
1408 client
._rfb_version
= 3.7;
1409 client
._sock
._websocket
._receive_data(new Uint8Array([0, 0, 0, 1]));
1410 expect(client
._fail
).to
.have
.been
.calledWith(
1411 'Security handshake failed');
1414 it('should result in securityfailure event when receiving a non zero status', function () {
1415 const spy
= sinon
.spy();
1416 client
.addEventListener("securityfailure", spy
);
1417 client
._sock
._websocket
._receive_data(new Uint8Array([0, 0, 0, 2]));
1418 expect(spy
).to
.have
.been
.calledOnce
;
1419 expect(spy
.args
[0][0].detail
.status
).to
.equal(2);
1422 it('should include reason when provided in securityfailure event', function () {
1423 client
._rfb_version
= 3.8;
1424 const spy
= sinon
.spy();
1425 client
.addEventListener("securityfailure", spy
);
1426 const failure_data
= [0, 0, 0, 1, 0, 0, 0, 12, 115, 117, 99, 104,
1427 32, 102, 97, 105, 108, 117, 114, 101];
1428 client
._sock
._websocket
._receive_data(new Uint8Array(failure_data
));
1429 expect(spy
.args
[0][0].detail
.status
).to
.equal(1);
1430 expect(spy
.args
[0][0].detail
.reason
).to
.equal('such failure');
1433 it('should not include reason when length is zero in securityfailure event', function () {
1434 client
._rfb_version
= 3.9;
1435 const spy
= sinon
.spy();
1436 client
.addEventListener("securityfailure", spy
);
1437 const failure_data
= [0, 0, 0, 1, 0, 0, 0, 0];
1438 client
._sock
._websocket
._receive_data(new Uint8Array(failure_data
));
1439 expect(spy
.args
[0][0].detail
.status
).to
.equal(1);
1440 expect('reason' in spy
.args
[0][0].detail
).to
.be
.false;
1443 it('should not include reason in securityfailure event for version < 3.8', function () {
1444 client
._rfb_version
= 3.6;
1445 const spy
= sinon
.spy();
1446 client
.addEventListener("securityfailure", spy
);
1447 client
._sock
._websocket
._receive_data(new Uint8Array([0, 0, 0, 2]));
1448 expect(spy
.args
[0][0].detail
.status
).to
.equal(2);
1449 expect('reason' in spy
.args
[0][0].detail
).to
.be
.false;
1453 describe('ClientInitialisation', function () {
1454 it('should transition to the ServerInitialisation state', function () {
1455 const client
= make_rfb();
1456 client
._rfb_connection_state
= 'connecting';
1457 client
._rfb_init_state
= 'SecurityResult';
1458 client
._sock
._websocket
._receive_data(new Uint8Array([0, 0, 0, 0]));
1459 expect(client
._rfb_init_state
).to
.equal('ServerInitialisation');
1462 it('should send 1 if we are in shared mode', function () {
1463 const client
= make_rfb('wss://host:8675', { shared
: true });
1464 client
._rfb_connection_state
= 'connecting';
1465 client
._rfb_init_state
= 'SecurityResult';
1466 client
._sock
._websocket
._receive_data(new Uint8Array([0, 0, 0, 0]));
1467 expect(client
._sock
).to
.have
.sent(new Uint8Array([1]));
1470 it('should send 0 if we are not in shared mode', function () {
1471 const client
= make_rfb('wss://host:8675', { shared
: false });
1472 client
._rfb_connection_state
= 'connecting';
1473 client
._rfb_init_state
= 'SecurityResult';
1474 client
._sock
._websocket
._receive_data(new Uint8Array([0, 0, 0, 0]));
1475 expect(client
._sock
).to
.have
.sent(new Uint8Array([0]));
1479 describe('ServerInitialisation', function () {
1480 beforeEach(function () {
1481 client
._rfb_init_state
= 'ServerInitialisation';
1484 function send_server_init(opts
, client
) {
1485 const full_opts
= { width
: 10, height
: 12, bpp
: 24, depth
: 24, big_endian
: 0,
1486 true_color
: 1, red_max
: 255, green_max
: 255, blue_max
: 255,
1487 red_shift
: 16, green_shift
: 8, blue_shift
: 0, name
: 'a name' };
1488 for (let opt
in opts
) {
1489 full_opts
[opt
] = opts
[opt
];
1493 push16(data
, full_opts
.width
);
1494 push16(data
, full_opts
.height
);
1496 data
.push(full_opts
.bpp
);
1497 data
.push(full_opts
.depth
);
1498 data
.push(full_opts
.big_endian
);
1499 data
.push(full_opts
.true_color
);
1501 push16(data
, full_opts
.red_max
);
1502 push16(data
, full_opts
.green_max
);
1503 push16(data
, full_opts
.blue_max
);
1504 push8(data
, full_opts
.red_shift
);
1505 push8(data
, full_opts
.green_shift
);
1506 push8(data
, full_opts
.blue_shift
);
1513 client
._sock
._websocket
._receive_data(new Uint8Array(data
));
1515 const name_data
= [];
1517 pushString(name_data
, full_opts
.name
);
1518 push32(name_len
, name_data
.length
);
1520 client
._sock
._websocket
._receive_data(new Uint8Array(name_len
));
1521 client
._sock
._websocket
._receive_data(new Uint8Array(name_data
));
1524 it('should set the framebuffer width and height', function () {
1525 send_server_init({ width
: 32, height
: 84 }, client
);
1526 expect(client
._fb_width
).to
.equal(32);
1527 expect(client
._fb_height
).to
.equal(84);
1530 // NB(sross): we just warn, not fail, for endian-ness and shifts, so we don't test them
1532 it('should set the framebuffer name and call the callback', function () {
1533 const spy
= sinon
.spy();
1534 client
.addEventListener("desktopname", spy
);
1535 send_server_init({ name
: 'som€ nam€' }, client
);
1537 expect(client
._fb_name
).to
.equal('som€ nam€');
1538 expect(spy
).to
.have
.been
.calledOnce
;
1539 expect(spy
.args
[0][0].detail
.name
).to
.equal('som€ nam€');
1542 it('should handle the extended init message of the tight encoding', function () {
1543 // NB(sross): we don't actually do anything with it, so just test that we can
1544 // read it w/o throwing an error
1545 client
._rfb_tightvnc
= true;
1546 send_server_init({}, client
);
1548 const tight_data
= [];
1549 push16(tight_data
, 1);
1550 push16(tight_data
, 2);
1551 push16(tight_data
, 3);
1552 push16(tight_data
, 0);
1553 for (let i
= 0; i
< 16 + 32 + 48; i
++) {
1556 client
._sock
._websocket
._receive_data(new Uint8Array(tight_data
));
1558 expect(client
._rfb_connection_state
).to
.equal('connected');
1561 it('should resize the display', function () {
1562 sinon
.spy(client
._display
, 'resize');
1563 send_server_init({ width
: 27, height
: 32 }, client
);
1565 expect(client
._display
.resize
).to
.have
.been
.calledOnce
;
1566 expect(client
._display
.resize
).to
.have
.been
.calledWith(27, 32);
1569 it('should grab the mouse and keyboard', function () {
1570 sinon
.spy(client
._keyboard
, 'grab');
1571 sinon
.spy(client
._mouse
, 'grab');
1572 send_server_init({}, client
);
1573 expect(client
._keyboard
.grab
).to
.have
.been
.calledOnce
;
1574 expect(client
._mouse
.grab
).to
.have
.been
.calledOnce
;
1577 describe('Initial Update Request', function () {
1578 beforeEach(function () {
1579 sinon
.spy(RFB
.messages
, "pixelFormat");
1580 sinon
.spy(RFB
.messages
, "clientEncodings");
1581 sinon
.spy(RFB
.messages
, "fbUpdateRequest");
1584 afterEach(function () {
1585 RFB
.messages
.pixelFormat
.restore();
1586 RFB
.messages
.clientEncodings
.restore();
1587 RFB
.messages
.fbUpdateRequest
.restore();
1590 // TODO(directxman12): test the various options in this configuration matrix
1591 it('should reply with the pixel format, client encodings, and initial update request', function () {
1592 send_server_init({ width
: 27, height
: 32 }, client
);
1594 expect(RFB
.messages
.pixelFormat
).to
.have
.been
.calledOnce
;
1595 expect(RFB
.messages
.pixelFormat
).to
.have
.been
.calledWith(client
._sock
, 24, true);
1596 expect(RFB
.messages
.pixelFormat
).to
.have
.been
.calledBefore(RFB
.messages
.clientEncodings
);
1597 expect(RFB
.messages
.clientEncodings
).to
.have
.been
.calledOnce
;
1598 expect(RFB
.messages
.clientEncodings
.getCall(0).args
[1]).to
.include(encodings
.encodingTight
);
1599 expect(RFB
.messages
.clientEncodings
).to
.have
.been
.calledBefore(RFB
.messages
.fbUpdateRequest
);
1600 expect(RFB
.messages
.fbUpdateRequest
).to
.have
.been
.calledOnce
;
1601 expect(RFB
.messages
.fbUpdateRequest
).to
.have
.been
.calledWith(client
._sock
, false, 0, 0, 27, 32);
1604 it('should reply with restricted settings for Intel AMT servers', function () {
1605 send_server_init({ width
: 27, height
: 32, name
: "Intel(r) AMT KVM"}, client
);
1607 expect(RFB
.messages
.pixelFormat
).to
.have
.been
.calledOnce
;
1608 expect(RFB
.messages
.pixelFormat
).to
.have
.been
.calledWith(client
._sock
, 8, true);
1609 expect(RFB
.messages
.pixelFormat
).to
.have
.been
.calledBefore(RFB
.messages
.clientEncodings
);
1610 expect(RFB
.messages
.clientEncodings
).to
.have
.been
.calledOnce
;
1611 expect(RFB
.messages
.clientEncodings
.getCall(0).args
[1]).to
.not
.include(encodings
.encodingTight
);
1612 expect(RFB
.messages
.clientEncodings
.getCall(0).args
[1]).to
.not
.include(encodings
.encodingHextile
);
1613 expect(RFB
.messages
.clientEncodings
).to
.have
.been
.calledBefore(RFB
.messages
.fbUpdateRequest
);
1614 expect(RFB
.messages
.fbUpdateRequest
).to
.have
.been
.calledOnce
;
1615 expect(RFB
.messages
.fbUpdateRequest
).to
.have
.been
.calledWith(client
._sock
, false, 0, 0, 27, 32);
1619 it('should transition to the "connected" state', function () {
1620 send_server_init({}, client
);
1621 expect(client
._rfb_connection_state
).to
.equal('connected');
1626 describe('Protocol Message Processing After Completing Initialization', function () {
1629 beforeEach(function () {
1630 client
= make_rfb();
1631 client
._fb_name
= 'some device';
1632 client
._fb_width
= 640;
1633 client
._fb_height
= 20;
1636 describe('Framebuffer Update Handling', function () {
1637 const target_data_arr
= [
1638 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
1639 0x00, 0xff, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
1640 0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255,
1641 0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255
1645 const target_data_check_arr
= [
1646 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
1647 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
1648 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
1649 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
1651 let target_data_check
;
1653 before(function () {
1654 // NB(directxman12): PhantomJS 1.x doesn't implement Uint8ClampedArray
1655 target_data
= new Uint8Array(target_data_arr
);
1656 target_data_check
= new Uint8Array(target_data_check_arr
);
1659 function send_fbu_msg(rect_info
, rect_data
, client
, rect_cnt
) {
1662 if (!rect_cnt
|| rect_cnt
> -1) {
1664 data
.push(0); // msg type
1665 data
.push(0); // padding
1666 push16(data
, rect_cnt
|| rect_data
.length
);
1669 for (let i
= 0; i
< rect_data
.length
; i
++) {
1671 push16(data
, rect_info
[i
].x
);
1672 push16(data
, rect_info
[i
].y
);
1673 push16(data
, rect_info
[i
].width
);
1674 push16(data
, rect_info
[i
].height
);
1675 push32(data
, rect_info
[i
].encoding
);
1677 data
= data
.concat(rect_data
[i
]);
1680 client
._sock
._websocket
._receive_data(new Uint8Array(data
));
1683 it('should send an update request if there is sufficient data', function () {
1684 const expected_msg
= {_sQ
: new Uint8Array(10), _sQlen
: 0, flush
: () => {}};
1685 RFB
.messages
.fbUpdateRequest(expected_msg
, true, 0, 0, 640, 20);
1687 client
._framebufferUpdate
= () => true;
1688 client
._sock
._websocket
._receive_data(new Uint8Array([0]));
1690 expect(client
._sock
).to
.have
.sent(expected_msg
._sQ
);
1693 it('should not send an update request if we need more data', function () {
1694 client
._sock
._websocket
._receive_data(new Uint8Array([0]));
1695 expect(client
._sock
._websocket
._get_sent_data()).to
.have
.length(0);
1698 it('should resume receiving an update if we previously did not have enough data', function () {
1699 const expected_msg
= {_sQ
: new Uint8Array(10), _sQlen
: 0, flush
: () => {}};
1700 RFB
.messages
.fbUpdateRequest(expected_msg
, true, 0, 0, 640, 20);
1702 // just enough to set FBU.rects
1703 client
._sock
._websocket
._receive_data(new Uint8Array([0, 0, 0, 3]));
1704 expect(client
._sock
._websocket
._get_sent_data()).to
.have
.length(0);
1706 client
._framebufferUpdate = function () { this._sock
.rQskipBytes(1); return true; }; // we magically have enough data
1707 // 247 should *not* be used as the message type here
1708 client
._sock
._websocket
._receive_data(new Uint8Array([247]));
1709 expect(client
._sock
).to
.have
.sent(expected_msg
._sQ
);
1712 it('should not send a request in continuous updates mode', function () {
1713 client
._enabledContinuousUpdates
= true;
1714 client
._framebufferUpdate
= () => true;
1715 client
._sock
._websocket
._receive_data(new Uint8Array([0]));
1717 expect(client
._sock
._websocket
._get_sent_data()).to
.have
.length(0);
1720 it('should fail on an unsupported encoding', function () {
1721 sinon
.spy(client
, "_fail");
1722 const rect_info
= { x
: 8, y
: 11, width
: 27, height
: 32, encoding
: 234 };
1723 send_fbu_msg([rect_info
], [[]], client
);
1724 expect(client
._fail
).to
.have
.been
.calledOnce
;
1727 it('should be able to pause and resume receiving rects if not enought data', function () {
1728 // seed some initial data to copy
1729 client
._fb_width
= 4;
1730 client
._fb_height
= 4;
1731 client
._display
.resize(4, 4);
1732 client
._display
.blitRgbxImage(0, 0, 4, 2, new Uint8Array(target_data_check_arr
.slice(0, 32)), 0);
1734 const info
= [{ x
: 0, y
: 2, width
: 2, height
: 2, encoding
: 0x01},
1735 { x
: 2, y
: 2, width
: 2, height
: 2, encoding
: 0x01}];
1736 // data says [{ old_x: 2, old_y: 0 }, { old_x: 0, old_y: 0 }]
1737 const rects
= [[0, 2, 0, 0], [0, 0, 0, 0]];
1738 send_fbu_msg([info
[0]], [rects
[0]], client
, 2);
1739 send_fbu_msg([info
[1]], [rects
[1]], client
, -1);
1740 expect(client
._display
).to
.have
.displayed(target_data_check
);
1743 describe('Message Encoding Handlers', function () {
1744 beforeEach(function () {
1745 // a really small frame
1746 client
._fb_width
= 4;
1747 client
._fb_height
= 4;
1748 client
._fb_depth
= 24;
1749 client
._display
.resize(4, 4);
1752 it('should handle the RAW encoding', function () {
1753 const info
= [{ x
: 0, y
: 0, width
: 2, height
: 2, encoding
: 0x00 },
1754 { x
: 2, y
: 0, width
: 2, height
: 2, encoding
: 0x00 },
1755 { x
: 0, y
: 2, width
: 4, height
: 1, encoding
: 0x00 },
1756 { x
: 0, y
: 3, width
: 4, height
: 1, encoding
: 0x00 }];
1759 [0x00, 0x00, 0xff, 0, 0x00, 0xff, 0x00, 0, 0x00, 0xff, 0x00, 0, 0x00, 0x00, 0xff, 0],
1760 [0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0],
1761 [0xff, 0x00, 0xee, 0, 0xff, 0xee, 0x00, 0, 0xff, 0xee, 0xaa, 0, 0xff, 0xee, 0xab, 0],
1762 [0xff, 0x00, 0xee, 0, 0xff, 0xee, 0x00, 0, 0xff, 0xee, 0xaa, 0, 0xff, 0xee, 0xab, 0]];
1763 send_fbu_msg(info
, rects
, client
);
1764 expect(client
._display
).to
.have
.displayed(target_data
);
1767 it('should handle the RAW encoding in low colour mode', function () {
1768 const info
= [{ x
: 0, y
: 0, width
: 2, height
: 2, encoding
: 0x00 },
1769 { x
: 2, y
: 0, width
: 2, height
: 2, encoding
: 0x00 },
1770 { x
: 0, y
: 2, width
: 4, height
: 1, encoding
: 0x00 },
1771 { x
: 0, y
: 3, width
: 4, height
: 1, encoding
: 0x00 }];
1773 [0x03, 0x03, 0x03, 0x03],
1774 [0x0c, 0x0c, 0x0c, 0x0c],
1775 [0x0c, 0x0c, 0x03, 0x03],
1776 [0x0c, 0x0c, 0x03, 0x03]];
1777 client
._fb_depth
= 8;
1778 send_fbu_msg(info
, rects
, client
);
1779 expect(client
._display
).to
.have
.displayed(target_data_check
);
1782 it('should handle the COPYRECT encoding', function () {
1783 // seed some initial data to copy
1784 client
._display
.blitRgbxImage(0, 0, 4, 2, new Uint8Array(target_data_check_arr
.slice(0, 32)), 0);
1786 const info
= [{ x
: 0, y
: 2, width
: 2, height
: 2, encoding
: 0x01},
1787 { x
: 2, y
: 2, width
: 2, height
: 2, encoding
: 0x01}];
1788 // data says [{ old_x: 0, old_y: 0 }, { old_x: 0, old_y: 0 }]
1789 const rects
= [[0, 2, 0, 0], [0, 0, 0, 0]];
1790 send_fbu_msg(info
, rects
, client
);
1791 expect(client
._display
).to
.have
.displayed(target_data_check
);
1794 // TODO(directxman12): for encodings with subrects, test resuming on partial send?
1795 // TODO(directxman12): test rre_chunk_sz (related to above about subrects)?
1797 it('should handle the RRE encoding', function () {
1798 const info
= [{ x
: 0, y
: 0, width
: 4, height
: 4, encoding
: 0x02 }];
1800 push32(rect
, 2); // 2 subrects
1801 push32(rect
, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1802 rect
.push(0xff); // becomes ff0000ff --> #0000FF color
1806 push16(rect
, 0); // x: 0
1807 push16(rect
, 0); // y: 0
1808 push16(rect
, 2); // width: 2
1809 push16(rect
, 2); // height: 2
1810 rect
.push(0xff); // becomes ff0000ff --> #0000FF color
1814 push16(rect
, 2); // x: 2
1815 push16(rect
, 2); // y: 2
1816 push16(rect
, 2); // width: 2
1817 push16(rect
, 2); // height: 2
1819 send_fbu_msg(info
, [rect
], client
);
1820 expect(client
._display
).to
.have
.displayed(target_data_check
);
1823 describe('the HEXTILE encoding handler', function () {
1824 it('should handle a tile with fg, bg specified, normal subrects', function () {
1825 const info
= [{ x
: 0, y
: 0, width
: 4, height
: 4, encoding
: 0x05 }];
1827 rect
.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects
1828 push32(rect
, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1829 rect
.push(0xff); // becomes ff0000ff --> #0000FF fg color
1833 rect
.push(2); // 2 subrects
1834 rect
.push(0); // x: 0, y: 0
1835 rect
.push(1 | (1 << 4)); // width: 2, height: 2
1836 rect
.push(2 | (2 << 4)); // x: 2, y: 2
1837 rect
.push(1 | (1 << 4)); // width: 2, height: 2
1838 send_fbu_msg(info
, [rect
], client
);
1839 expect(client
._display
).to
.have
.displayed(target_data_check
);
1842 it('should handle a raw tile', function () {
1843 const info
= [{ x
: 0, y
: 0, width
: 4, height
: 4, encoding
: 0x05 }];
1845 rect
.push(0x01); // raw
1846 for (let i
= 0; i
< target_data
.length
; i
+= 4) {
1847 rect
.push(target_data
[i
+ 2]);
1848 rect
.push(target_data
[i
+ 1]);
1849 rect
.push(target_data
[i
]);
1850 rect
.push(target_data
[i
+ 3]);
1852 send_fbu_msg(info
, [rect
], client
);
1853 expect(client
._display
).to
.have
.displayed(target_data
);
1856 it('should handle a tile with only bg specified (solid bg)', function () {
1857 const info
= [{ x
: 0, y
: 0, width
: 4, height
: 4, encoding
: 0x05 }];
1860 push32(rect
, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1861 send_fbu_msg(info
, [rect
], client
);
1863 const expected
= [];
1864 for (let i
= 0; i
< 16; i
++) { push32(expected
, 0xff00ff); }
1865 expect(client
._display
).to
.have
.displayed(new Uint8Array(expected
));
1868 it('should handle a tile with only bg specified and an empty frame afterwards', function () {
1869 // set the width so we can have two tiles
1870 client
._fb_width
= 8;
1871 client
._display
.resize(8, 4);
1873 const info
= [{ x
: 0, y
: 0, width
: 32, height
: 4, encoding
: 0x05 }];
1879 push32(rect
, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1881 // send an empty frame
1884 send_fbu_msg(info
, [rect
], client
);
1886 const expected
= [];
1887 for (let i
= 0; i
< 16; i
++) { push32(expected
, 0xff00ff); } // rect 1: solid
1888 for (let i
= 0; i
< 16; i
++) { push32(expected
, 0xff00ff); } // rect 2: same bkground color
1889 expect(client
._display
).to
.have
.displayed(new Uint8Array(expected
));
1892 it('should handle a tile with bg and coloured subrects', function () {
1893 const info
= [{ x
: 0, y
: 0, width
: 4, height
: 4, encoding
: 0x05 }];
1895 rect
.push(0x02 | 0x08 | 0x10); // bg spec, anysubrects, colouredsubrects
1896 push32(rect
, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1897 rect
.push(2); // 2 subrects
1898 rect
.push(0xff); // becomes ff0000ff --> #0000FF fg color
1902 rect
.push(0); // x: 0, y: 0
1903 rect
.push(1 | (1 << 4)); // width: 2, height: 2
1904 rect
.push(0xff); // becomes ff0000ff --> #0000FF fg color
1908 rect
.push(2 | (2 << 4)); // x: 2, y: 2
1909 rect
.push(1 | (1 << 4)); // width: 2, height: 2
1910 send_fbu_msg(info
, [rect
], client
);
1911 expect(client
._display
).to
.have
.displayed(target_data_check
);
1914 it('should carry over fg and bg colors from the previous tile if not specified', function () {
1915 client
._fb_width
= 4;
1916 client
._fb_height
= 17;
1917 client
._display
.resize(4, 17);
1919 const info
= [{ x
: 0, y
: 0, width
: 4, height
: 17, encoding
: 0x05}];
1921 rect
.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects
1922 push32(rect
, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1923 rect
.push(0xff); // becomes ff0000ff --> #0000FF fg color
1927 rect
.push(8); // 8 subrects
1928 for (let i
= 0; i
< 4; i
++) {
1929 rect
.push((0 << 4) | (i
* 4)); // x: 0, y: i*4
1930 rect
.push(1 | (1 << 4)); // width: 2, height: 2
1931 rect
.push((2 << 4) | (i
* 4 + 2)); // x: 2, y: i * 4 + 2
1932 rect
.push(1 | (1 << 4)); // width: 2, height: 2
1934 rect
.push(0x08); // anysubrects
1935 rect
.push(1); // 1 subrect
1936 rect
.push(0); // x: 0, y: 0
1937 rect
.push(1 | (1 << 4)); // width: 2, height: 2
1938 send_fbu_msg(info
, [rect
], client
);
1941 for (let i
= 0; i
< 4; i
++) { expected
= expected
.concat(target_data_check_arr
); }
1942 expected
= expected
.concat(target_data_check_arr
.slice(0, 16));
1943 expect(client
._display
).to
.have
.displayed(new Uint8Array(expected
));
1946 it('should fail on an invalid subencoding', function () {
1947 sinon
.spy(client
, "_fail");
1948 const info
= [{ x
: 0, y
: 0, width
: 4, height
: 4, encoding
: 0x05 }];
1949 const rects
= [[45]]; // an invalid subencoding
1950 send_fbu_msg(info
, rects
, client
);
1951 expect(client
._fail
).to
.have
.been
.calledOnce
;
1955 it
.skip('should handle the TIGHT encoding', function () {
1956 // TODO(directxman12): test this
1959 it
.skip('should handle the TIGHT_PNG encoding', function () {
1960 // TODO(directxman12): test this
1963 it('should handle the DesktopSize pseduo-encoding', function () {
1964 sinon
.spy(client
._display
, 'resize');
1965 send_fbu_msg([{ x
: 0, y
: 0, width
: 20, height
: 50, encoding
: -223 }], [[]], client
);
1967 expect(client
._fb_width
).to
.equal(20);
1968 expect(client
._fb_height
).to
.equal(50);
1970 expect(client
._display
.resize
).to
.have
.been
.calledOnce
;
1971 expect(client
._display
.resize
).to
.have
.been
.calledWith(20, 50);
1974 describe('the ExtendedDesktopSize pseudo-encoding handler', function () {
1975 beforeEach(function () {
1976 // a really small frame
1977 client
._fb_width
= 4;
1978 client
._fb_height
= 4;
1979 client
._display
.resize(4, 4);
1980 sinon
.spy(client
._display
, 'resize');
1983 function make_screen_data(nr_of_screens
) {
1985 push8(data
, nr_of_screens
); // number-of-screens
1986 push8(data
, 0); // padding
1987 push16(data
, 0); // padding
1988 for (let i
=0; i
<nr_of_screens
; i
+= 1) {
1989 push32(data
, 0); // id
1990 push16(data
, 0); // x-position
1991 push16(data
, 0); // y-position
1992 push16(data
, 20); // width
1993 push16(data
, 50); // height
1994 push32(data
, 0); // flags
1999 it('should handle a resize requested by this client', function () {
2000 const reason_for_change
= 1; // requested by this client
2001 const status_code
= 0; // No error
2003 send_fbu_msg([{ x
: reason_for_change
, y
: status_code
,
2004 width
: 20, height
: 50, encoding
: -308 }],
2005 make_screen_data(1), client
);
2007 expect(client
._fb_width
).to
.equal(20);
2008 expect(client
._fb_height
).to
.equal(50);
2010 expect(client
._display
.resize
).to
.have
.been
.calledOnce
;
2011 expect(client
._display
.resize
).to
.have
.been
.calledWith(20, 50);
2014 it('should handle a resize requested by another client', function () {
2015 const reason_for_change
= 2; // requested by another client
2016 const status_code
= 0; // No error
2018 send_fbu_msg([{ x
: reason_for_change
, y
: status_code
,
2019 width
: 20, height
: 50, encoding
: -308 }],
2020 make_screen_data(1), client
);
2022 expect(client
._fb_width
).to
.equal(20);
2023 expect(client
._fb_height
).to
.equal(50);
2025 expect(client
._display
.resize
).to
.have
.been
.calledOnce
;
2026 expect(client
._display
.resize
).to
.have
.been
.calledWith(20, 50);
2029 it('should be able to recieve requests which contain data for multiple screens', function () {
2030 const reason_for_change
= 2; // requested by another client
2031 const status_code
= 0; // No error
2033 send_fbu_msg([{ x
: reason_for_change
, y
: status_code
,
2034 width
: 60, height
: 50, encoding
: -308 }],
2035 make_screen_data(3), client
);
2037 expect(client
._fb_width
).to
.equal(60);
2038 expect(client
._fb_height
).to
.equal(50);
2040 expect(client
._display
.resize
).to
.have
.been
.calledOnce
;
2041 expect(client
._display
.resize
).to
.have
.been
.calledWith(60, 50);
2044 it('should not handle a failed request', function () {
2045 const reason_for_change
= 1; // requested by this client
2046 const status_code
= 1; // Resize is administratively prohibited
2048 send_fbu_msg([{ x
: reason_for_change
, y
: status_code
,
2049 width
: 20, height
: 50, encoding
: -308 }],
2050 make_screen_data(1), client
);
2052 expect(client
._fb_width
).to
.equal(4);
2053 expect(client
._fb_height
).to
.equal(4);
2055 expect(client
._display
.resize
).to
.not
.have
.been
.called
;
2059 describe('the Cursor pseudo-encoding handler', function () {
2060 beforeEach(function () {
2061 sinon
.spy(client
._cursor
, 'change');
2064 it('should handle a standard cursor', function () {
2065 const info
= { x
: 5, y
: 7,
2066 width
: 4, height
: 4,
2071 for (let i
= 0;i
< info
.width
*info
.height
;i
++) {
2072 push32(rect
, 0x11223300);
2074 push32(rect
, 0xa0a0a0a0);
2076 for (let i
= 0;i
< info
.width
*info
.height
/2;i
++) {
2077 push32(expected
, 0x332211ff);
2078 push32(expected
, 0x33221100);
2080 expected
= new Uint8Array(expected
);
2082 send_fbu_msg([info
], [rect
], client
);
2084 expect(client
._cursor
.change
).to
.have
.been
.calledOnce
;
2085 expect(client
._cursor
.change
).to
.have
.been
.calledWith(expected
, 5, 7, 4, 4);
2088 it('should handle an empty cursor', function () {
2089 const info
= { x
: 0, y
: 0,
2090 width
: 0, height
: 0,
2094 send_fbu_msg([info
], [rect
], client
);
2096 expect(client
._cursor
.change
).to
.have
.been
.calledOnce
;
2097 expect(client
._cursor
.change
).to
.have
.been
.calledWith(new Uint8Array
, 0, 0, 0, 0);
2100 it('should handle a transparent cursor', function () {
2101 const info
= { x
: 5, y
: 7,
2102 width
: 4, height
: 4,
2107 for (let i
= 0;i
< info
.width
*info
.height
;i
++) {
2108 push32(rect
, 0x11223300);
2110 push32(rect
, 0x00000000);
2112 for (let i
= 0;i
< info
.width
*info
.height
;i
++) {
2113 push32(expected
, 0x33221100);
2115 expected
= new Uint8Array(expected
);
2117 send_fbu_msg([info
], [rect
], client
);
2119 expect(client
._cursor
.change
).to
.have
.been
.calledOnce
;
2120 expect(client
._cursor
.change
).to
.have
.been
.calledWith(expected
, 5, 7, 4, 4);
2123 describe('dot for empty cursor', function () {
2124 beforeEach(function () {
2125 client
.showDotCursor
= true;
2126 // Was called when we enabled dot cursor
2127 client
._cursor
.change
.resetHistory();
2130 it('should show a standard cursor', function () {
2131 const info
= { x
: 5, y
: 7,
2132 width
: 4, height
: 4,
2137 for (let i
= 0;i
< info
.width
*info
.height
;i
++) {
2138 push32(rect
, 0x11223300);
2140 push32(rect
, 0xa0a0a0a0);
2142 for (let i
= 0;i
< info
.width
*info
.height
/2;i
++) {
2143 push32(expected
, 0x332211ff);
2144 push32(expected
, 0x33221100);
2146 expected
= new Uint8Array(expected
);
2148 send_fbu_msg([info
], [rect
], client
);
2150 expect(client
._cursor
.change
).to
.have
.been
.calledOnce
;
2151 expect(client
._cursor
.change
).to
.have
.been
.calledWith(expected
, 5, 7, 4, 4);
2154 it('should handle an empty cursor', function () {
2155 const info
= { x
: 0, y
: 0,
2156 width
: 0, height
: 0,
2159 const dot
= RFB
.cursors
.dot
;
2161 send_fbu_msg([info
], [rect
], client
);
2163 expect(client
._cursor
.change
).to
.have
.been
.calledOnce
;
2164 expect(client
._cursor
.change
).to
.have
.been
.calledWith(dot
.rgbaPixels
,
2171 it('should handle a transparent cursor', function () {
2172 const info
= { x
: 5, y
: 7,
2173 width
: 4, height
: 4,
2176 const dot
= RFB
.cursors
.dot
;
2178 for (let i
= 0;i
< info
.width
*info
.height
;i
++) {
2179 push32(rect
, 0x11223300);
2181 push32(rect
, 0x00000000);
2183 send_fbu_msg([info
], [rect
], client
);
2185 expect(client
._cursor
.change
).to
.have
.been
.calledOnce
;
2186 expect(client
._cursor
.change
).to
.have
.been
.calledWith(dot
.rgbaPixels
,
2195 describe('the VMware Cursor pseudo-encoding handler', function () {
2196 beforeEach(function () {
2197 sinon
.spy(client
._cursor
, 'change');
2199 afterEach(function () {
2200 client
._cursor
.change
.resetHistory();
2203 it('should handle the VMware cursor pseudo-encoding', function () {
2204 let data
= [0x00, 0x00, 0xff, 0,
2205 0x00, 0xff, 0x00, 0,
2206 0x00, 0xff, 0x00, 0,
2207 0x00, 0x00, 0xff, 0];
2213 for (let i
= 0; i
< data
.length
; i
++) {
2214 push8(rect
, data
[i
]);
2217 for (let i
= 0; i
< data
.length
; i
++) {
2218 push8(rect
, data
[i
]);
2221 send_fbu_msg([{ x
: 0, y
: 0, width
: 2, height
: 2,
2222 encoding
: 0x574d5664}],
2224 expect(client
._FBU
.rects
).to
.equal(0);
2227 it('should handle insufficient cursor pixel data', function () {
2229 // Specified 14x23 pixels for the cursor,
2230 // but only send 2x2 pixels worth of data
2233 let data
= [0x00, 0x00, 0xff, 0,
2234 0x00, 0xff, 0x00, 0];
2241 for (let i
= 0; i
< data
.length
; i
++) {
2242 push8(rect
, data
[i
]);
2245 for (let i
= 0; i
< data
.length
; i
++) {
2246 push8(rect
, data
[i
]);
2249 send_fbu_msg([{ x
: 0, y
: 0, width
: w
, height
: h
,
2250 encoding
: 0x574d5664}],
2253 // expect one FBU to remain unhandled
2254 expect(client
._FBU
.rects
).to
.equal(1);
2257 it('should update the cursor when type is classic', function () {
2259 [0xff, 0xff, 0xff, 0xff, //Transparent
2260 0xff, 0xff, 0xff, 0xff, //Transparent
2261 0x00, 0x00, 0x00, 0x00, //Opaque
2262 0xff, 0xff, 0xff, 0xff]; //Inverted
2265 [0x00, 0x00, 0x00, 0x00, //Transparent
2266 0x00, 0x00, 0x00, 0x00, //Transparent
2267 0x11, 0x22, 0x33, 0x44, //Opaque
2268 0xff, 0xff, 0xff, 0x44]; //Inverted
2271 push8(rect
, 0); //cursor_type
2272 push8(rect
, 0); //padding
2279 for (let i
= 0; i
< and_mask
.length
; i
++) {
2280 push8(rect
, and_mask
[i
]);
2283 for (let i
= 0; i
< xor_mask
.length
; i
++) {
2284 push8(rect
, xor_mask
[i
]);
2287 let expected_rgba
= [0x00, 0x00, 0x00, 0x00,
2288 0x00, 0x00, 0x00, 0x00,
2289 0x33, 0x22, 0x11, 0xff,
2290 0x00, 0x00, 0x00, 0xff];
2292 send_fbu_msg([{ x
: hotx
, y
: hoty
,
2293 width
: w
, height
: h
,
2294 encoding
: 0x574d5664}],
2297 expect(client
._cursor
.change
)
2298 .to
.have
.been
.calledOnce
;
2299 expect(client
._cursor
.change
)
2300 .to
.have
.been
.calledWith(expected_rgba
,
2305 it('should update the cursor when type is alpha', function () {
2306 let data
= [0xee, 0x55, 0xff, 0x00, // rgba
2307 0x00, 0xff, 0x00, 0xff,
2308 0x00, 0xff, 0x00, 0x22,
2309 0x00, 0xff, 0x00, 0x22,
2310 0x00, 0xff, 0x00, 0x22,
2311 0x00, 0x00, 0xff, 0xee];
2313 push8(rect
, 1); //cursor_type
2314 push8(rect
, 0); //padding
2320 for (let i
= 0; i
< data
.length
; i
++) {
2321 push8(rect
, data
[i
]);
2324 let expected_rgba
= [0xee, 0x55, 0xff, 0x00,
2325 0x00, 0xff, 0x00, 0xff,
2326 0x00, 0xff, 0x00, 0x22,
2327 0x00, 0xff, 0x00, 0x22,
2328 0x00, 0xff, 0x00, 0x22,
2329 0x00, 0x00, 0xff, 0xee];
2331 send_fbu_msg([{ x
: hotx
, y
: hoty
,
2332 width
: w
, height
: h
,
2333 encoding
: 0x574d5664}],
2336 expect(client
._cursor
.change
)
2337 .to
.have
.been
.calledOnce
;
2338 expect(client
._cursor
.change
)
2339 .to
.have
.been
.calledWith(expected_rgba
,
2344 it('should not update cursor when incorrect cursor type given', function () {
2346 push8(rect
, 3); // invalid cursor type
2347 push8(rect
, 0); // padding
2349 client
._cursor
.change
.resetHistory();
2350 send_fbu_msg([{ x
: 0, y
: 0, width
: 2, height
: 2,
2351 encoding
: 0x574d5664}],
2354 expect(client
._cursor
.change
)
2355 .to
.not
.have
.been
.called
;
2359 it('should handle the last_rect pseudo-encoding', function () {
2360 send_fbu_msg([{ x
: 0, y
: 0, width
: 0, height
: 0, encoding
: -224}], [[]], client
, 100);
2361 expect(client
._FBU
.rects
).to
.equal(0);
2364 it('should handle the DesktopName pseudo-encoding', function () {
2367 pushString(data
, "som€ nam€");
2369 const spy
= sinon
.spy();
2370 client
.addEventListener("desktopname", spy
);
2372 send_fbu_msg([{ x
: 0, y
: 0, width
: 0, height
: 0, encoding
: -307 }], [data
], client
);
2374 expect(client
._fb_name
).to
.equal('som€ nam€');
2375 expect(spy
).to
.have
.been
.calledOnce
;
2376 expect(spy
.args
[0][0].detail
.name
).to
.equal('som€ nam€');
2381 describe('XVP Message Handling', function () {
2382 it('should set the XVP version and fire the callback with the version on XVP_INIT', function () {
2383 const spy
= sinon
.spy();
2384 client
.addEventListener("capabilities", spy
);
2385 client
._sock
._websocket
._receive_data(new Uint8Array([250, 0, 10, 1]));
2386 expect(client
._rfb_xvp_ver
).to
.equal(10);
2387 expect(spy
).to
.have
.been
.calledOnce
;
2388 expect(spy
.args
[0][0].detail
.capabilities
.power
).to
.be
.true;
2389 expect(client
.capabilities
.power
).to
.be
.true;
2392 it('should fail on unknown XVP message types', function () {
2393 sinon
.spy(client
, "_fail");
2394 client
._sock
._websocket
._receive_data(new Uint8Array([250, 0, 10, 237]));
2395 expect(client
._fail
).to
.have
.been
.calledOnce
;
2399 describe('Normal Clipboard Handling Receive', function () {
2400 it('should fire the clipboard callback with the retrieved text on ServerCutText', function () {
2401 const expected_str
= 'cheese!';
2402 const data
= [3, 0, 0, 0];
2403 push32(data
, expected_str
.length
);
2404 for (let i
= 0; i
< expected_str
.length
; i
++) { data
.push(expected_str
.charCodeAt(i
)); }
2405 const spy
= sinon
.spy();
2406 client
.addEventListener("clipboard", spy
);
2408 client
._sock
._websocket
._receive_data(new Uint8Array(data
));
2409 expect(spy
).to
.have
.been
.calledOnce
;
2410 expect(spy
.args
[0][0].detail
.text
).to
.equal(expected_str
);
2414 describe('Extended clipboard Handling', function () {
2416 describe('Extended clipboard initialization', function () {
2417 beforeEach(function () {
2418 sinon
.spy(RFB
.messages
, 'extendedClipboardCaps');
2421 afterEach(function () {
2422 RFB
.messages
.extendedClipboardCaps
.restore();
2425 it('should update capabilities when receiving a Caps message', function () {
2426 let data
= [3, 0, 0, 0];
2427 const flags
= [0x1F, 0x00, 0x00, 0x03];
2428 let fileSizes
= [0x00, 0x00, 0x00, 0x1E,
2429 0x00, 0x00, 0x00, 0x3C];
2431 push32(data
, toUnsigned32bit(-12));
2432 data
= data
.concat(flags
);
2433 data
= data
.concat(fileSizes
);
2434 client
._sock
._websocket
._receive_data(new Uint8Array(data
));
2436 // Check that we give an response caps when we receive one
2437 expect(RFB
.messages
.extendedClipboardCaps
).to
.have
.been
.calledOnce
;
2439 // FIXME: Can we avoid checking internal variables?
2440 expect(client
._clipboardServerCapabilitiesFormats
[0]).to
.not
.equal(true);
2441 expect(client
._clipboardServerCapabilitiesFormats
[1]).to
.equal(true);
2442 expect(client
._clipboardServerCapabilitiesFormats
[2]).to
.equal(true);
2443 expect(client
._clipboardServerCapabilitiesActions
[(1 << 24)]).to
.equal(true);
2449 describe('Extended Clipboard Handling Receive', function () {
2451 beforeEach(function () {
2452 // Send our capabilities
2453 let data
= [3, 0, 0, 0];
2454 const flags
= [0x1F, 0x00, 0x00, 0x01];
2455 let fileSizes
= [0x00, 0x00, 0x00, 0x1E];
2457 push32(data
, toUnsigned32bit(-8));
2458 data
= data
.concat(flags
);
2459 data
= data
.concat(fileSizes
);
2460 client
._sock
._websocket
._receive_data(new Uint8Array(data
));
2463 describe('Handle Provide', function () {
2464 it('should update clipboard with correct Unicode data from a Provide message', function () {
2465 let expectedData
= "Aå漢å—!";
2466 let data
= [3, 0, 0, 0];
2467 const flags
= [0x10, 0x00, 0x00, 0x01];
2469 /* The size 10 (utf8 encoded string size) and the
2470 string "Aå漢å—!" utf8 encoded and deflated. */
2471 let deflatedData
= [120, 94, 99, 96, 96, 224, 114, 60,
2472 188, 244, 217, 158, 69, 79, 215,
2473 78, 87, 4, 0, 35, 207, 6, 66];
2475 // How much data we are sending.
2476 push32(data
, toUnsigned32bit(-(4 + deflatedData
.length
)));
2478 data
= data
.concat(flags
);
2479 data
= data
.concat(deflatedData
);
2481 const spy
= sinon
.spy();
2482 client
.addEventListener("clipboard", spy
);
2484 client
._sock
._websocket
._receive_data(new Uint8Array(data
));
2485 expect(spy
).to
.have
.been
.calledOnce
;
2486 expect(spy
.args
[0][0].detail
.text
).to
.equal(expectedData
);
2487 client
.removeEventListener("clipboard", spy
);
2490 it('should update clipboard with correct escape characters from a Provide message ', function () {
2491 let expectedData
= "Oh\nmy!";
2492 let data
= [3, 0, 0, 0];
2493 const flags
= [0x10, 0x00, 0x00, 0x01];
2495 let text
= encodeUTF8("Oh\r\nmy!\0");
2497 let deflatedText
= deflateWithSize(text
);
2499 // How much data we are sending.
2500 push32(data
, toUnsigned32bit(-(4 + deflatedText
.length
)));
2502 data
= data
.concat(flags
);
2504 let sendData
= new Uint8Array(data
.length
+ deflatedText
.length
);
2506 sendData
.set(deflatedText
, data
.length
);
2508 const spy
= sinon
.spy();
2509 client
.addEventListener("clipboard", spy
);
2511 client
._sock
._websocket
._receive_data(sendData
);
2512 expect(spy
).to
.have
.been
.calledOnce
;
2513 expect(spy
.args
[0][0].detail
.text
).to
.equal(expectedData
);
2514 client
.removeEventListener("clipboard", spy
);
2519 describe('Handle Notify', function () {
2520 beforeEach(function () {
2521 sinon
.spy(RFB
.messages
, 'extendedClipboardRequest');
2524 afterEach(function () {
2525 RFB
.messages
.extendedClipboardRequest
.restore();
2528 it('should make a request with supported formats when receiving a notify message', function () {
2529 let data
= [3, 0, 0, 0];
2530 const flags
= [0x08, 0x00, 0x00, 0x07];
2531 push32(data
, toUnsigned32bit(-4));
2532 data
= data
.concat(flags
);
2533 let expectedData
= [0x01];
2535 client
._sock
._websocket
._receive_data(new Uint8Array(data
));
2537 expect(RFB
.messages
.extendedClipboardRequest
).to
.have
.been
.calledOnce
;
2538 expect(RFB
.messages
.extendedClipboardRequest
).to
.have
.been
.calledWith(client
._sock
, expectedData
);
2542 describe('Handle Peek', function () {
2543 beforeEach(function () {
2544 sinon
.spy(RFB
.messages
, 'extendedClipboardNotify');
2547 afterEach(function () {
2548 RFB
.messages
.extendedClipboardNotify
.restore();
2551 it('should send an empty Notify when receiving a Peek and no excisting clipboard data', function () {
2552 let data
= [3, 0, 0, 0];
2553 const flags
= [0x04, 0x00, 0x00, 0x00];
2554 push32(data
, toUnsigned32bit(-4));
2555 data
= data
.concat(flags
);
2556 let expectedData
= [];
2558 client
._sock
._websocket
._receive_data(new Uint8Array(data
));
2560 expect(RFB
.messages
.extendedClipboardNotify
).to
.have
.been
.calledOnce
;
2561 expect(RFB
.messages
.extendedClipboardNotify
).to
.have
.been
.calledWith(client
._sock
, expectedData
);
2564 it('should send a Notify message with supported formats when receiving a Peek', function () {
2565 let data
= [3, 0, 0, 0];
2566 const flags
= [0x04, 0x00, 0x00, 0x00];
2567 push32(data
, toUnsigned32bit(-4));
2568 data
= data
.concat(flags
);
2569 let expectedData
= [0x01];
2571 // Needed to have clipboard data to read.
2572 // This will trigger a call to Notify, reset history
2573 client
.clipboardPasteFrom("HejHej");
2574 RFB
.messages
.extendedClipboardNotify
.resetHistory();
2576 client
._sock
._websocket
._receive_data(new Uint8Array(data
));
2578 expect(RFB
.messages
.extendedClipboardNotify
).to
.have
.been
.calledOnce
;
2579 expect(RFB
.messages
.extendedClipboardNotify
).to
.have
.been
.calledWith(client
._sock
, expectedData
);
2583 describe('Handle Request', function () {
2584 beforeEach(function () {
2585 sinon
.spy(RFB
.messages
, 'extendedClipboardProvide');
2588 afterEach(function () {
2589 RFB
.messages
.extendedClipboardProvide
.restore();
2592 it('should send a Provide message with supported formats when receiving a Request', function () {
2593 let data
= [3, 0, 0, 0];
2594 const flags
= [0x02, 0x00, 0x00, 0x01];
2595 push32(data
, toUnsigned32bit(-4));
2596 data
= data
.concat(flags
);
2597 let expectedData
= [0x01];
2599 client
.clipboardPasteFrom("HejHej");
2600 expect(RFB
.messages
.extendedClipboardProvide
).to
.not
.have
.been
.called
;
2602 client
._sock
._websocket
._receive_data(new Uint8Array(data
));
2604 expect(RFB
.messages
.extendedClipboardProvide
).to
.have
.been
.calledOnce
;
2605 expect(RFB
.messages
.extendedClipboardProvide
).to
.have
.been
.calledWith(client
._sock
, expectedData
, ["HejHej"]);
2612 it('should fire the bell callback on Bell', function () {
2613 const spy
= sinon
.spy();
2614 client
.addEventListener("bell", spy
);
2615 client
._sock
._websocket
._receive_data(new Uint8Array([2]));
2616 expect(spy
).to
.have
.been
.calledOnce
;
2619 it('should respond correctly to ServerFence', function () {
2620 const expected_msg
= {_sQ
: new Uint8Array(16), _sQlen
: 0, flush
: () => {}};
2621 const incoming_msg
= {_sQ
: new Uint8Array(16), _sQlen
: 0, flush
: () => {}};
2623 const payload
= "foo\x00ab9";
2625 // ClientFence and ServerFence are identical in structure
2626 RFB
.messages
.clientFence(expected_msg
, (1<<0) | (1<<1), payload
);
2627 RFB
.messages
.clientFence(incoming_msg
, 0xffffffff, payload
);
2629 client
._sock
._websocket
._receive_data(incoming_msg
._sQ
);
2631 expect(client
._sock
).to
.have
.sent(expected_msg
._sQ
);
2633 expected_msg
._sQlen
= 0;
2634 incoming_msg
._sQlen
= 0;
2636 RFB
.messages
.clientFence(expected_msg
, (1<<0), payload
);
2637 RFB
.messages
.clientFence(incoming_msg
, (1<<0) | (1<<31), payload
);
2639 client
._sock
._websocket
._receive_data(incoming_msg
._sQ
);
2641 expect(client
._sock
).to
.have
.sent(expected_msg
._sQ
);
2644 it('should enable continuous updates on first EndOfContinousUpdates', function () {
2645 const expected_msg
= {_sQ
: new Uint8Array(10), _sQlen
: 0, flush
: () => {}};
2647 RFB
.messages
.enableContinuousUpdates(expected_msg
, true, 0, 0, 640, 20);
2649 expect(client
._enabledContinuousUpdates
).to
.be
.false;
2651 client
._sock
._websocket
._receive_data(new Uint8Array([150]));
2653 expect(client
._enabledContinuousUpdates
).to
.be
.true;
2654 expect(client
._sock
).to
.have
.sent(expected_msg
._sQ
);
2657 it('should disable continuous updates on subsequent EndOfContinousUpdates', function () {
2658 client
._enabledContinuousUpdates
= true;
2659 client
._supportsContinuousUpdates
= true;
2661 client
._sock
._websocket
._receive_data(new Uint8Array([150]));
2663 expect(client
._enabledContinuousUpdates
).to
.be
.false;
2666 it('should update continuous updates on resize', function () {
2667 const expected_msg
= {_sQ
: new Uint8Array(10), _sQlen
: 0, flush
: () => {}};
2668 RFB
.messages
.enableContinuousUpdates(expected_msg
, true, 0, 0, 90, 700);
2670 client
._resize(450, 160);
2672 expect(client
._sock
._websocket
._get_sent_data()).to
.have
.length(0);
2674 client
._enabledContinuousUpdates
= true;
2676 client
._resize(90, 700);
2678 expect(client
._sock
).to
.have
.sent(expected_msg
._sQ
);
2681 it('should fail on an unknown message type', function () {
2682 sinon
.spy(client
, "_fail");
2683 client
._sock
._websocket
._receive_data(new Uint8Array([87]));
2684 expect(client
._fail
).to
.have
.been
.calledOnce
;
2688 describe('Asynchronous Events', function () {
2690 beforeEach(function () {
2691 client
= make_rfb();
2694 describe('Mouse event handlers', function () {
2695 it('should not send button messages in view-only mode', function () {
2696 client
._viewOnly
= true;
2697 sinon
.spy(client
._sock
, 'flush');
2698 client
._handleMouseButton(0, 0, 1, 0x001);
2699 expect(client
._sock
.flush
).to
.not
.have
.been
.called
;
2702 it('should not send movement messages in view-only mode', function () {
2703 client
._viewOnly
= true;
2704 sinon
.spy(client
._sock
, 'flush');
2705 client
._handleMouseMove(0, 0);
2706 expect(client
._sock
.flush
).to
.not
.have
.been
.called
;
2709 it('should send a pointer event on mouse button presses', function () {
2710 client
._handleMouseButton(10, 12, 1, 0x001);
2711 const pointer_msg
= {_sQ
: new Uint8Array(6), _sQlen
: 0, flush
: () => {}};
2712 RFB
.messages
.pointerEvent(pointer_msg
, 10, 12, 0x001);
2713 expect(client
._sock
).to
.have
.sent(pointer_msg
._sQ
);
2716 it('should send a mask of 1 on mousedown', function () {
2717 client
._handleMouseButton(10, 12, 1, 0x001);
2718 const pointer_msg
= {_sQ
: new Uint8Array(6), _sQlen
: 0, flush
: () => {}};
2719 RFB
.messages
.pointerEvent(pointer_msg
, 10, 12, 0x001);
2720 expect(client
._sock
).to
.have
.sent(pointer_msg
._sQ
);
2723 it('should send a mask of 0 on mouseup', function () {
2724 client
._mouse_buttonMask
= 0x001;
2725 client
._handleMouseButton(10, 12, 0, 0x001);
2726 const pointer_msg
= {_sQ
: new Uint8Array(6), _sQlen
: 0, flush
: () => {}};
2727 RFB
.messages
.pointerEvent(pointer_msg
, 10, 12, 0x000);
2728 expect(client
._sock
).to
.have
.sent(pointer_msg
._sQ
);
2731 it('should send a pointer event on mouse movement', function () {
2732 client
._handleMouseMove(10, 12);
2733 const pointer_msg
= {_sQ
: new Uint8Array(6), _sQlen
: 0, flush
: () => {}};
2734 RFB
.messages
.pointerEvent(pointer_msg
, 10, 12, 0x000);
2735 expect(client
._sock
).to
.have
.sent(pointer_msg
._sQ
);
2738 it('should set the button mask so that future mouse movements use it', function () {
2739 client
._handleMouseButton(10, 12, 1, 0x010);
2740 client
._handleMouseMove(13, 9);
2741 const pointer_msg
= {_sQ
: new Uint8Array(12), _sQlen
: 0, flush
: () => {}};
2742 RFB
.messages
.pointerEvent(pointer_msg
, 10, 12, 0x010);
2743 RFB
.messages
.pointerEvent(pointer_msg
, 13, 9, 0x010);
2744 expect(client
._sock
).to
.have
.sent(pointer_msg
._sQ
);
2748 describe('Keyboard Event Handlers', function () {
2749 it('should send a key message on a key press', function () {
2750 client
._handleKeyEvent(0x41, 'KeyA', true);
2751 const key_msg
= {_sQ
: new Uint8Array(8), _sQlen
: 0, flush
: () => {}};
2752 RFB
.messages
.keyEvent(key_msg
, 0x41, 1);
2753 expect(client
._sock
).to
.have
.sent(key_msg
._sQ
);
2756 it('should not send messages in view-only mode', function () {
2757 client
._viewOnly
= true;
2758 sinon
.spy(client
._sock
, 'flush');
2759 client
._handleKeyEvent('a', 'KeyA', true);
2760 expect(client
._sock
.flush
).to
.not
.have
.been
.called
;
2764 describe('WebSocket event handlers', function () {
2766 it('should do nothing if we receive an empty message and have nothing in the queue', function () {
2767 client
._normal_msg
= sinon
.spy();
2768 client
._sock
._websocket
._receive_data(new Uint8Array([]));
2769 expect(client
._normal_msg
).to
.not
.have
.been
.called
;
2772 it('should handle a message in the connected state as a normal message', function () {
2773 client
._normal_msg
= sinon
.spy();
2774 client
._sock
._websocket
._receive_data(new Uint8Array([1, 2, 3]));
2775 expect(client
._normal_msg
).to
.have
.been
.called
;
2778 it('should handle a message in any non-disconnected/failed state like an init message', function () {
2779 client
._rfb_connection_state
= 'connecting';
2780 client
._rfb_init_state
= 'ProtocolVersion';
2781 client
._init_msg
= sinon
.spy();
2782 client
._sock
._websocket
._receive_data(new Uint8Array([1, 2, 3]));
2783 expect(client
._init_msg
).to
.have
.been
.called
;
2786 it('should process all normal messages directly', function () {
2787 const spy
= sinon
.spy();
2788 client
.addEventListener("bell", spy
);
2789 client
._sock
._websocket
._receive_data(new Uint8Array([0x02, 0x02]));
2790 expect(spy
).to
.have
.been
.calledTwice
;
2794 it('should update the state to ProtocolVersion on open (if the state is "connecting")', function () {
2795 client
= new RFB(document
.createElement('div'), 'wss://host:8675');
2797 client
._sock
._websocket
._open();
2798 expect(client
._rfb_init_state
).to
.equal('ProtocolVersion');
2801 it('should fail if we are not currently ready to connect and we get an "open" event', function () {
2802 sinon
.spy(client
, "_fail");
2803 client
._rfb_connection_state
= 'connected';
2804 client
._sock
._websocket
._open();
2805 expect(client
._fail
).to
.have
.been
.calledOnce
;
2809 it('should transition to "disconnected" from "disconnecting" on a close event', function () {
2810 const real
= client
._sock
._websocket
.close
;
2811 client
._sock
._websocket
.close
= () => {};
2812 client
.disconnect();
2813 expect(client
._rfb_connection_state
).to
.equal('disconnecting');
2814 client
._sock
._websocket
.close
= real
;
2815 client
._sock
._websocket
.close();
2816 expect(client
._rfb_connection_state
).to
.equal('disconnected');
2819 it('should fail if we get a close event while connecting', function () {
2820 sinon
.spy(client
, "_fail");
2821 client
._rfb_connection_state
= 'connecting';
2822 client
._sock
._websocket
.close();
2823 expect(client
._fail
).to
.have
.been
.calledOnce
;
2826 it('should unregister close event handler', function () {
2827 sinon
.spy(client
._sock
, 'off');
2828 client
.disconnect();
2829 client
._sock
._websocket
.close();
2830 expect(client
._sock
.off
).to
.have
.been
.calledWith('close');
2833 // error events do nothing
2838 describe('RFB messages', function () {
2841 before(function () {
2842 FakeWebSocket
.replace();
2843 sock
= new Websock();
2848 FakeWebSocket
.restore();
2851 describe('Extended Clipboard Handling Send', function () {
2852 beforeEach(function () {
2853 sinon
.spy(RFB
.messages
, 'clientCutText');
2856 afterEach(function () {
2857 RFB
.messages
.clientCutText
.restore();
2860 it('should call clientCutText with correct Caps data', function () {
2865 let expectedData
= new Uint8Array([0x1F, 0x00, 0x00, 0x05,
2866 0x00, 0x00, 0x00, 0x02,
2867 0x00, 0x00, 0x10, 0x19]);
2876 RFB
.messages
.extendedClipboardCaps(sock
, actions
, formats
);
2877 expect(RFB
.messages
.clientCutText
).to
.have
.been
.calledOnce
;
2878 expect(RFB
.messages
.clientCutText
).to
.have
.been
.calledWith(sock
, expectedData
);
2881 it('should call clientCutText with correct Request data', function () {
2882 let formats
= new Uint8Array([0x01]);
2883 let expectedData
= new Uint8Array([0x02, 0x00, 0x00, 0x01]);
2885 RFB
.messages
.extendedClipboardRequest(sock
, formats
);
2886 expect(RFB
.messages
.clientCutText
).to
.have
.been
.calledOnce
;
2887 expect(RFB
.messages
.clientCutText
).to
.have
.been
.calledWith(sock
, expectedData
);
2890 it('should call clientCutText with correct Notify data', function () {
2891 let formats
= new Uint8Array([0x01]);
2892 let expectedData
= new Uint8Array([0x08, 0x00, 0x00, 0x01]);
2894 RFB
.messages
.extendedClipboardNotify(sock
, formats
);
2895 expect(RFB
.messages
.clientCutText
).to
.have
.been
.calledOnce
;
2896 expect(RFB
.messages
.clientCutText
).to
.have
.been
.calledWith(sock
, expectedData
);
2899 it('should call clientCutText with correct Provide data', function () {
2900 let testText
= "Test string";
2901 let expectedText
= encodeUTF8(testText
+ "\0");
2903 let deflatedData
= deflateWithSize(expectedText
);
2905 // Build Expected with flags and deflated data
2906 let expectedData
= new Uint8Array(4 + deflatedData
.length
);
2907 expectedData
[0] = 0x10; // The client capabilities
2908 expectedData
[1] = 0x00; // Reserved flags
2909 expectedData
[2] = 0x00; // Reserved flags
2910 expectedData
[3] = 0x01; // The formats client supports
2911 expectedData
.set(deflatedData
, 4);
2913 RFB
.messages
.extendedClipboardProvide(sock
, [0x01], [testText
]);
2914 expect(RFB
.messages
.clientCutText
).to
.have
.been
.calledOnce
;
2915 expect(RFB
.messages
.clientCutText
).to
.have
.been
.calledWith(sock
, expectedData
, true);
2919 describe('End of line characters', function () {
2920 it('Carriage return', function () {
2922 let testText
= "Hello\rworld\r\r!";
2923 let expectedText
= encodeUTF8("Hello\r\nworld\r\n\r\n!\0");
2925 let deflatedData
= deflateWithSize(expectedText
);
2927 // Build Expected with flags and deflated data
2928 let expectedData
= new Uint8Array(4 + deflatedData
.length
);
2929 expectedData
[0] = 0x10; // The client capabilities
2930 expectedData
[1] = 0x00; // Reserved flags
2931 expectedData
[2] = 0x00; // Reserved flags
2932 expectedData
[3] = 0x01; // The formats client supports
2933 expectedData
.set(deflatedData
, 4);
2935 RFB
.messages
.extendedClipboardProvide(sock
, [0x01], [testText
]);
2936 expect(RFB
.messages
.clientCutText
).to
.have
.been
.calledOnce
;
2937 expect(RFB
.messages
.clientCutText
).to
.have
.been
.calledWith(sock
, expectedData
, true);
2940 it('Carriage return Line feed', function () {
2942 let testText
= "Hello\r\n\r\nworld\r\n!";
2943 let expectedText
= encodeUTF8(testText
+ "\0");
2945 let deflatedData
= deflateWithSize(expectedText
);
2947 // Build Expected with flags and deflated data
2948 let expectedData
= new Uint8Array(4 + deflatedData
.length
);
2949 expectedData
[0] = 0x10; // The client capabilities
2950 expectedData
[1] = 0x00; // Reserved flags
2951 expectedData
[2] = 0x00; // Reserved flags
2952 expectedData
[3] = 0x01; // The formats client supports
2953 expectedData
.set(deflatedData
, 4);
2955 RFB
.messages
.extendedClipboardProvide(sock
, [0x01], [testText
]);
2956 expect(RFB
.messages
.clientCutText
).to
.have
.been
.calledOnce
;
2957 expect(RFB
.messages
.clientCutText
).to
.have
.been
.calledWith(sock
, expectedData
, true);
2960 it('Line feed', function () {
2961 let testText
= "Hello\n\n\nworld\n!";
2962 let expectedText
= encodeUTF8("Hello\r\n\r\n\r\nworld\r\n!\0");
2964 let deflatedData
= deflateWithSize(expectedText
);
2966 // Build Expected with flags and deflated data
2967 let expectedData
= new Uint8Array(4 + deflatedData
.length
);
2968 expectedData
[0] = 0x10; // The client capabilities
2969 expectedData
[1] = 0x00; // Reserved flags
2970 expectedData
[2] = 0x00; // Reserved flags
2971 expectedData
[3] = 0x01; // The formats client supports
2972 expectedData
.set(deflatedData
, 4);
2974 RFB
.messages
.extendedClipboardProvide(sock
, [0x01], [testText
]);
2975 expect(RFB
.messages
.clientCutText
).to
.have
.been
.calledOnce
;
2976 expect(RFB
.messages
.clientCutText
).to
.have
.been
.calledWith(sock
, expectedData
, true);
2979 it('Carriage return and Line feed mixed', function () {
2980 let testText
= "\rHello\r\n\rworld\n\n!";
2981 let expectedText
= encodeUTF8("\r\nHello\r\n\r\nworld\r\n\r\n!\0");
2983 let deflatedData
= deflateWithSize(expectedText
);
2985 // Build Expected with flags and deflated data
2986 let expectedData
= new Uint8Array(4 + deflatedData
.length
);
2987 expectedData
[0] = 0x10; // The client capabilities
2988 expectedData
[1] = 0x00; // Reserved flags
2989 expectedData
[2] = 0x00; // Reserved flags
2990 expectedData
[3] = 0x01; // The formats client supports
2991 expectedData
.set(deflatedData
, 4);
2993 RFB
.messages
.extendedClipboardProvide(sock
, [0x01], [testText
]);
2994 expect(RFB
.messages
.clientCutText
).to
.have
.been
.calledOnce
;
2995 expect(RFB
.messages
.clientCutText
).to
.have
.been
.calledWith(sock
, expectedData
, true);