1 // requires local modules: util, base64, websock, rfb, keyboard, keysym, keysymdef, input, jsunzip, des, display
2 // requires test modules: fake.websocket
3 /* jshint expr: true */
4 var assert
= chai
.assert
;
5 var expect
= chai
.expect
;
7 function make_rfb (extra_opts
) {
12 extra_opts
.target
= extra_opts
.target
|| document
.createElement('canvas');
13 return new RFB(extra_opts
);
16 // some useful assertions for noVNC
17 chai
.use(function (_chai
, utils
) {
18 _chai
.Assertion
.addMethod('displayed', function (target_data
) {
20 var data_cl
= obj
._drawCtx
.getImageData(0, 0, obj
._fb_width
, obj
._fb_height
).data
;
21 // NB(directxman12): PhantomJS 1.x doesn't implement Uint8ClampedArray, so work around that
22 var data
= new Uint8Array(data_cl
);
23 this.assert(utils
.eql(data
, target_data
),
24 "expected #{this} to have displayed the image #{exp}, but instead it displayed #{act}",
25 "expected #{this} not to have displayed the image #{act}",
30 _chai
.Assertion
.addMethod('sent', function (target_data
) {
32 var data
= obj
._websocket
._get_sent_data();
33 this.assert(utils
.eql(data
, target_data
),
34 "expected #{this} to have sent the data #{exp}, but it actually sent #{act}",
35 "expected #{this} not to have sent the data #{act}",
41 describe('Remote Frame Buffer Protocol Client', function() {
43 before(FakeWebSocket
.replace
);
44 after(FakeWebSocket
.restore
);
46 describe('Public API Basic Behavior', function () {
48 beforeEach(function () {
52 describe('#connect', function () {
53 beforeEach(function () { client
._updateState
= sinon
.spy(); });
55 it('should set the current state to "connect"', function () {
56 client
.connect('host', 8675);
57 expect(client
._updateState
).to
.have
.been
.calledOnce
;
58 expect(client
._updateState
).to
.have
.been
.calledWith('connect');
61 it('should fail if we are missing a host', function () {
62 sinon
.spy(client
, '_fail');
63 client
.connect(undefined, 8675);
64 expect(client
._fail
).to
.have
.been
.calledOnce
;
67 it('should fail if we are missing a port', function () {
68 sinon
.spy(client
, '_fail');
69 client
.connect('abc');
70 expect(client
._fail
).to
.have
.been
.calledOnce
;
73 it('should not update the state if we are missing a host or port', function () {
74 sinon
.spy(client
, '_fail');
75 client
.connect('abc');
76 expect(client
._fail
).to
.have
.been
.calledOnce
;
77 expect(client
._updateState
).to
.have
.been
.calledOnce
;
78 expect(client
._updateState
).to
.have
.been
.calledWith('failed');
82 describe('#disconnect', function () {
83 beforeEach(function () { client
._updateState
= sinon
.spy(); });
85 it('should set the current state to "disconnect"', function () {
87 expect(client
._updateState
).to
.have
.been
.calledOnce
;
88 expect(client
._updateState
).to
.have
.been
.calledWith('disconnect');
92 describe('#sendPassword', function () {
93 beforeEach(function () { this.clock
= sinon
.useFakeTimers(); });
94 afterEach(function () { this.clock
.restore(); });
96 it('should set the state to "Authentication"', function () {
97 client
._rfb_state
= "blah";
98 client
.sendPassword('pass');
99 expect(client
._rfb_state
).to
.equal('Authentication');
102 it('should call init_msg "soon"', function () {
103 client
._init_msg
= sinon
.spy();
104 client
.sendPassword('pass');
106 expect(client
._init_msg
).to
.have
.been
.calledOnce
;
110 describe('#sendCtrlAlDel', function () {
111 beforeEach(function () {
112 client
._sock
= new Websock();
113 client
._sock
.open('ws://', 'binary');
114 client
._sock
._websocket
._open();
115 sinon
.spy(client
._sock
, 'send');
116 client
._rfb_state
= "normal";
117 client
._view_only
= false;
120 it('should sent ctrl[down]-alt[down]-del[down] then del[up]-alt[up]-ctrl[up]', function () {
122 expected
= expected
.concat(RFB
.messages
.keyEvent(0xFFE3, 1));
123 expected
= expected
.concat(RFB
.messages
.keyEvent(0xFFE9, 1));
124 expected
= expected
.concat(RFB
.messages
.keyEvent(0xFFFF, 1));
125 expected
= expected
.concat(RFB
.messages
.keyEvent(0xFFFF, 0));
126 expected
= expected
.concat(RFB
.messages
.keyEvent(0xFFE9, 0));
127 expected
= expected
.concat(RFB
.messages
.keyEvent(0xFFE3, 0));
129 client
.sendCtrlAltDel();
130 expect(client
._sock
).to
.have
.sent(expected
);
133 it('should not send the keys if we are not in a normal state', function () {
134 client
._rfb_state
= "broken";
135 client
.sendCtrlAltDel();
136 expect(client
._sock
.send
).to
.not
.have
.been
.called
;
139 it('should not send the keys if we are set as view_only', function () {
140 client
._view_only
= true;
141 client
.sendCtrlAltDel();
142 expect(client
._sock
.send
).to
.not
.have
.been
.called
;
146 describe('#sendKey', function () {
147 beforeEach(function () {
148 client
._sock
= new Websock();
149 client
._sock
.open('ws://', 'binary');
150 client
._sock
._websocket
._open();
151 sinon
.spy(client
._sock
, 'send');
152 client
._rfb_state
= "normal";
153 client
._view_only
= false;
156 it('should send a single key with the given code and state (down = true)', function () {
157 var expected
= RFB
.messages
.keyEvent(123, 1);
158 client
.sendKey(123, true);
159 expect(client
._sock
).to
.have
.sent(expected
);
162 it('should send both a down and up event if the state is not specified', function () {
163 var expected
= RFB
.messages
.keyEvent(123, 1);
164 expected
= expected
.concat(RFB
.messages
.keyEvent(123, 0));
166 expect(client
._sock
).to
.have
.sent(expected
);
169 it('should not send the key if we are not in a normal state', function () {
170 client
._rfb_state
= "broken";
172 expect(client
._sock
.send
).to
.not
.have
.been
.called
;
175 it('should not send the key if we are set as view_only', function () {
176 client
._view_only
= true;
178 expect(client
._sock
.send
).to
.not
.have
.been
.called
;
182 describe('#clipboardPasteFrom', function () {
183 beforeEach(function () {
184 client
._sock
= new Websock();
185 client
._sock
.open('ws://', 'binary');
186 client
._sock
._websocket
._open();
187 sinon
.spy(client
._sock
, 'send');
188 client
._rfb_state
= "normal";
189 client
._view_only
= false;
192 it('should send the given text in a paste event', function () {
193 var expected
= RFB
.messages
.clientCutText('abc');
194 client
.clipboardPasteFrom('abc');
195 expect(client
._sock
).to
.have
.sent(expected
);
198 it('should not send the text if we are not in a normal state', function () {
199 client
._rfb_state
= "broken";
200 client
.clipboardPasteFrom('abc');
201 expect(client
._sock
.send
).to
.not
.have
.been
.called
;
205 describe("XVP operations", function () {
206 beforeEach(function () {
207 client
._sock
= new Websock();
208 client
._sock
.open('ws://', 'binary');
209 client
._sock
._websocket
._open();
210 sinon
.spy(client
._sock
, 'send');
211 client
._rfb_state
= "normal";
212 client
._view_only
= false;
213 client
._rfb_xvp_ver
= 1;
216 it('should send the shutdown signal on #xvpShutdown', function () {
217 client
.xvpShutdown();
218 expect(client
._sock
).to
.have
.sent([0xFA, 0x00, 0x01, 0x02]);
221 it('should send the reboot signal on #xvpReboot', function () {
223 expect(client
._sock
).to
.have
.sent([0xFA, 0x00, 0x01, 0x03]);
226 it('should send the reset signal on #xvpReset', function () {
228 expect(client
._sock
).to
.have
.sent([0xFA, 0x00, 0x01, 0x04]);
231 it('should support sending arbitrary XVP operations via #xvpOp', function () {
233 expect(client
._sock
).to
.have
.sent([0xFA, 0x00, 0x01, 0x07]);
236 it('should not send XVP operations with higher versions than we support', function () {
237 expect(client
.xvpOp(2, 7)).to
.be
.false;
238 expect(client
._sock
.send
).to
.not
.have
.been
.called
;
243 describe('Misc Internals', function () {
244 describe('#_updateState', function () {
246 beforeEach(function () {
247 this.clock
= sinon
.useFakeTimers();
251 afterEach(function () {
252 this.clock
.restore();
255 it('should clear the disconnect timer if the state is not disconnect', function () {
256 var spy
= sinon
.spy();
257 client
._disconnTimer
= setTimeout(spy
, 50);
258 client
._updateState('normal');
260 expect(spy
).to
.not
.have
.been
.called
;
261 expect(client
._disconnTimer
).to
.be
.null;
266 describe('Page States', function () {
267 describe('loaded', function () {
269 beforeEach(function () { client
= make_rfb(); });
271 it('should close any open WebSocket connection', function () {
272 sinon
.spy(client
._sock
, 'close');
273 client
._updateState('loaded');
274 expect(client
._sock
.close
).to
.have
.been
.calledOnce
;
278 describe('disconnected', function () {
280 beforeEach(function () { client
= make_rfb(); });
282 it('should close any open WebSocket connection', function () {
283 sinon
.spy(client
._sock
, 'close');
284 client
._updateState('disconnected');
285 expect(client
._sock
.close
).to
.have
.been
.calledOnce
;
289 describe('connect', function () {
291 beforeEach(function () { client
= make_rfb(); });
293 it('should reset the variable states', function () {
294 sinon
.spy(client
, '_init_vars');
295 client
._updateState('connect');
296 expect(client
._init_vars
).to
.have
.been
.calledOnce
;
299 it('should actually connect to the websocket', function () {
300 sinon
.spy(client
._sock
, 'open');
301 client
._updateState('connect');
302 expect(client
._sock
.open
).to
.have
.been
.calledOnce
;
305 it('should use wss:// to connect if encryption is enabled', function () {
306 sinon
.spy(client
._sock
, 'open');
307 client
.set_encrypt(true);
308 client
._updateState('connect');
309 expect(client
._sock
.open
.args
[0][0]).to
.contain('wss://');
312 it('should use ws:// to connect if encryption is not enabled', function () {
313 sinon
.spy(client
._sock
, 'open');
314 client
.set_encrypt(true);
315 client
._updateState('connect');
316 expect(client
._sock
.open
.args
[0][0]).to
.contain('wss://');
319 it('should use a uri with the host, port, and path specified to connect', function () {
320 sinon
.spy(client
._sock
, 'open');
321 client
.set_encrypt(false);
322 client
._rfb_host
= 'HOST';
323 client
._rfb_port
= 8675;
324 client
._rfb_path
= 'PATH';
325 client
._updateState('connect');
326 expect(client
._sock
.open
).to
.have
.been
.calledWith('ws://HOST:8675/PATH');
329 it('should attempt to close the websocket before we open an new one', function () {
330 sinon
.spy(client
._sock
, 'close');
331 client
._updateState('connect');
332 expect(client
._sock
.close
).to
.have
.been
.calledOnce
;
336 describe('disconnect', function () {
338 beforeEach(function () {
339 this.clock
= sinon
.useFakeTimers();
341 client
.connect('host', 8675);
344 afterEach(function () {
345 this.clock
.restore();
348 it('should fail if we do not call Websock.onclose within the disconnection timeout', function () {
349 client
._sock
._websocket
.close = function () {}; // explicitly don't call onclose
350 client
._updateState('disconnect');
351 this.clock
.tick(client
.get_disconnectTimeout() * 1000);
352 expect(client
._rfb_state
).to
.equal('failed');
355 it('should not fail if Websock.onclose gets called within the disconnection timeout', function () {
356 client
._updateState('disconnect');
357 this.clock
.tick(client
.get_disconnectTimeout() * 500);
358 client
._sock
._websocket
.close();
359 this.clock
.tick(client
.get_disconnectTimeout() * 500 + 1);
360 expect(client
._rfb_state
).to
.equal('disconnected');
363 it('should close the WebSocket connection', function () {
364 sinon
.spy(client
._sock
, 'close');
365 client
._updateState('disconnect');
366 expect(client
._sock
.close
).to
.have
.been
.calledTwice
; // once on loaded, once on disconnect
370 describe('failed', function () {
372 beforeEach(function () {
373 this.clock
= sinon
.useFakeTimers();
375 client
.connect('host', 8675);
378 afterEach(function () {
379 this.clock
.restore();
382 it('should close the WebSocket connection', function () {
383 sinon
.spy(client
._sock
, 'close');
384 client
._updateState('failed');
385 expect(client
._sock
.close
).to
.have
.been
.called
;
388 it('should transition to disconnected but stay in failed state', function () {
389 client
.set_onUpdateState(sinon
.spy());
390 client
._updateState('failed');
392 expect(client
._rfb_state
).to
.equal('failed');
394 var onUpdateState
= client
.get_onUpdateState();
395 expect(onUpdateState
).to
.have
.been
.called
;
396 // it should be specifically the last call
397 expect(onUpdateState
.args
[onUpdateState
.args
.length
- 1][1]).to
.equal('disconnected');
398 expect(onUpdateState
.args
[onUpdateState
.args
.length
- 1][2]).to
.equal('failed');
403 describe('fatal', function () {
405 beforeEach(function () { client
= make_rfb(); });
407 it('should close any open WebSocket connection', function () {
408 sinon
.spy(client
._sock
, 'close');
409 client
._updateState('fatal');
410 expect(client
._sock
.close
).to
.have
.been
.calledOnce
;
414 // NB(directxman12): Normal does *nothing* in updateState
417 describe('Protocol Initialization States', function () {
418 describe('ProtocolVersion', function () {
419 beforeEach(function () {
420 this.clock
= sinon
.useFakeTimers();
423 afterEach(function () {
424 this.clock
.restore();
427 function send_ver (ver
, client
) {
428 var arr
= new Uint8Array(12);
429 for (var i
= 0; i
< ver
.length
; i
++) {
430 arr
[i
+4] = ver
.charCodeAt(i
);
432 arr
[0] = 'R'; arr
[1] = 'F'; arr
[2] = 'B'; arr
[3] = ' ';
434 client
._sock
._websocket
._receive_data(arr
);
437 describe('version parsing', function () {
439 beforeEach(function () {
441 client
.connect('host', 8675);
442 client
._sock
._websocket
._open();
445 it('should interpret version 000.000 as a repeater', function () {
446 client
._repeaterID
= '\x01\x02\x03\x04\x05';
447 send_ver('000.000', client
);
448 expect(client
._rfb_version
).to
.equal(0);
450 var sent_data
= client
._sock
._websocket
._get_sent_data();
451 expect(sent_data
.slice(0, 5)).to
.deep
.equal([1, 2, 3, 4, 5]);
454 it('should interpret version 003.003 as version 3.3', function () {
455 send_ver('003.003', client
);
456 expect(client
._rfb_version
).to
.equal(3.3);
459 it('should interpret version 003.006 as version 3.3', function () {
460 send_ver('003.006', client
);
461 expect(client
._rfb_version
).to
.equal(3.3);
464 it('should interpret version 003.889 as version 3.3', function () {
465 send_ver('003.889', client
);
466 expect(client
._rfb_version
).to
.equal(3.3);
469 it('should interpret version 003.007 as version 3.7', function () {
470 send_ver('003.007', client
);
471 expect(client
._rfb_version
).to
.equal(3.7);
474 it('should interpret version 003.008 as version 3.8', function () {
475 send_ver('003.008', client
);
476 expect(client
._rfb_version
).to
.equal(3.8);
479 it('should interpret version 004.000 as version 3.8', function () {
480 send_ver('004.000', client
);
481 expect(client
._rfb_version
).to
.equal(3.8);
484 it('should interpret version 004.001 as version 3.8', function () {
485 send_ver('004.001', client
);
486 expect(client
._rfb_version
).to
.equal(3.8);
489 it('should fail on an invalid version', function () {
490 send_ver('002.000', client
);
491 expect(client
._rfb_state
).to
.equal('failed');
496 beforeEach(function () {
498 client
.connect('host', 8675);
499 client
._sock
._websocket
._open();
502 it('should handle two step repeater negotiation', function () {
503 client
._repeaterID
= '\x01\x02\x03\x04\x05';
505 send_ver('000.000', client
);
506 expect(client
._rfb_version
).to
.equal(0);
507 var sent_data
= client
._sock
._websocket
._get_sent_data();
508 expect(sent_data
.slice(0, 5)).to
.deep
.equal([1, 2, 3, 4, 5]);
509 expect(sent_data
).to
.have
.length(250);
511 send_ver('003.008', client
);
512 expect(client
._rfb_version
).to
.equal(3.8);
515 it('should initialize the flush interval', function () {
516 client
._sock
.flush
= sinon
.spy();
517 send_ver('003.008', client
);
518 this.clock
.tick(100);
519 expect(client
._sock
.flush
).to
.have
.been
.calledThrice
;
522 it('should send back the interpreted version', function () {
523 send_ver('004.000', client
);
525 var expected_str
= 'RFB 003.008\n';
527 for (var i
= 0; i
< expected_str
.length
; i
++) {
528 expected
[i
] = expected_str
.charCodeAt(i
);
531 expect(client
._sock
).to
.have
.sent(expected
);
534 it('should transition to the Security state on successful negotiation', function () {
535 send_ver('003.008', client
);
536 expect(client
._rfb_state
).to
.equal('Security');
540 describe('Security', function () {
543 beforeEach(function () {
545 client
.connect('host', 8675);
546 client
._sock
._websocket
._open();
547 client
._rfb_state
= 'Security';
550 it('should simply receive the auth scheme when for versions < 3.7', function () {
551 client
._rfb_version
= 3.6;
552 var auth_scheme_raw
= [1, 2, 3, 4];
553 var auth_scheme
= (auth_scheme_raw
[0] << 24) + (auth_scheme_raw
[1] << 16) +
554 (auth_scheme_raw
[2] << 8) + auth_scheme_raw
[3];
555 client
._sock
._websocket
._receive_data(auth_scheme_raw
);
556 expect(client
._rfb_auth_scheme
).to
.equal(auth_scheme
);
559 it('should choose for the most prefered scheme possible for versions >= 3.7', function () {
560 client
._rfb_version
= 3.7;
561 var auth_schemes
= [2, 1, 2];
562 client
._sock
._websocket
._receive_data(auth_schemes
);
563 expect(client
._rfb_auth_scheme
).to
.equal(2);
564 expect(client
._sock
).to
.have
.sent([2]);
567 it('should fail if there are no supported schemes for versions >= 3.7', function () {
568 client
._rfb_version
= 3.7;
569 var auth_schemes
= [1, 32];
570 client
._sock
._websocket
._receive_data(auth_schemes
);
571 expect(client
._rfb_state
).to
.equal('failed');
574 it('should fail with the appropriate message if no types are sent for versions >= 3.7', function () {
575 client
._rfb_version
= 3.7;
576 var failure_data
= [0, 0, 0, 0, 6, 119, 104, 111, 111, 112, 115];
577 sinon
.spy(client
, '_fail');
578 client
._sock
._websocket
._receive_data(failure_data
);
580 expect(client
._fail
).to
.have
.been
.calledTwice
;
581 expect(client
._fail
).to
.have
.been
.calledWith('Security failure: whoops');
584 it('should transition to the Authentication state and continue on successful negotiation', function () {
585 client
._rfb_version
= 3.7;
586 var auth_schemes
= [1, 1];
587 client
._negotiate_authentication
= sinon
.spy();
588 client
._sock
._websocket
._receive_data(auth_schemes
);
589 expect(client
._rfb_state
).to
.equal('Authentication');
590 expect(client
._negotiate_authentication
).to
.have
.been
.calledOnce
;
594 describe('Authentication', function () {
597 beforeEach(function () {
599 client
.connect('host', 8675);
600 client
._sock
._websocket
._open();
601 client
._rfb_state
= 'Security';
604 function send_security(type
, cl
) {
605 cl
._sock
._websocket
._receive_data(new Uint8Array([1, type
]));
608 it('should fail on auth scheme 0 (pre 3.7) with the given message', function () {
609 client
._rfb_version
= 3.6;
610 var err_msg
= "Whoopsies";
611 var data
= [0, 0, 0, 0];
612 var err_len
= err_msg
.length
;
613 data
.push32(err_len
);
614 for (var i
= 0; i
< err_len
; i
++) {
615 data
.push(err_msg
.charCodeAt(i
));
618 sinon
.spy(client
, '_fail');
619 client
._sock
._websocket
._receive_data(new Uint8Array(data
));
620 expect(client
._rfb_state
).to
.equal('failed');
621 expect(client
._fail
).to
.have
.been
.calledWith('Auth failure: Whoopsies');
624 it('should transition straight to SecurityResult on "no auth" (1) for versions >= 3.8', function () {
625 client
._rfb_version
= 3.8;
626 send_security(1, client
);
627 expect(client
._rfb_state
).to
.equal('SecurityResult');
630 it('should transition straight to ClientInitialisation on "no auth" for versions < 3.8', function () {
631 client
._rfb_version
= 3.7;
632 sinon
.spy(client
, '_updateState');
633 send_security(1, client
);
634 expect(client
._updateState
).to
.have
.been
.calledWith('ClientInitialisation');
635 expect(client
._rfb_state
).to
.equal('ServerInitialisation');
638 it('should fail on an unknown auth scheme', function () {
639 client
._rfb_version
= 3.8;
640 send_security(57, client
);
641 expect(client
._rfb_state
).to
.equal('failed');
644 describe('VNC Authentication (type 2) Handler', function () {
647 beforeEach(function () {
649 client
.connect('host', 8675);
650 client
._sock
._websocket
._open();
651 client
._rfb_state
= 'Security';
652 client
._rfb_version
= 3.8;
655 it('should transition to the "password" state if missing a password', function () {
656 send_security(2, client
);
657 expect(client
._rfb_state
).to
.equal('password');
660 it('should encrypt the password with DES and then send it back', function () {
661 client
._rfb_password
= 'passwd';
662 send_security(2, client
);
663 client
._sock
._websocket
._get_sent_data(); // skip the choice of auth reply
666 for (var i
= 0; i
< 16; i
++) { challenge
[i
] = i
; }
667 client
._sock
._websocket
._receive_data(new Uint8Array(challenge
));
669 var des_pass
= RFB
.genDES('passwd', challenge
);
670 expect(client
._sock
).to
.have
.sent(des_pass
);
673 it('should transition to SecurityResult immediately after sending the password', function () {
674 client
._rfb_password
= 'passwd';
675 send_security(2, client
);
678 for (var i
= 0; i
< 16; i
++) { challenge
[i
] = i
; }
679 client
._sock
._websocket
._receive_data(new Uint8Array(challenge
));
681 expect(client
._rfb_state
).to
.equal('SecurityResult');
685 describe('XVP Authentication (type 22) Handler', function () {
688 beforeEach(function () {
690 client
.connect('host', 8675);
691 client
._sock
._websocket
._open();
692 client
._rfb_state
= 'Security';
693 client
._rfb_version
= 3.8;
696 it('should fall through to standard VNC authentication upon completion', function () {
697 client
.set_xvp_password_sep('#');
698 client
._rfb_password
= 'user#target#password';
699 client
._negotiate_std_vnc_auth
= sinon
.spy();
700 send_security(22, client
);
701 expect(client
._negotiate_std_vnc_auth
).to
.have
.been
.calledOnce
;
704 it('should transition to the "password" state if the passwords is missing', function() {
705 send_security(22, client
);
706 expect(client
._rfb_state
).to
.equal('password');
709 it('should transition to the "password" state if the passwords is improperly formatted', function() {
710 client
._rfb_password
= 'user@target';
711 send_security(22, client
);
712 expect(client
._rfb_state
).to
.equal('password');
715 it('should split the password, send the first two parts, and pass on the last part', function () {
716 client
.set_xvp_password_sep('#');
717 client
._rfb_password
= 'user#target#password';
718 client
._negotiate_std_vnc_auth
= sinon
.spy();
720 send_security(22, client
);
722 expect(client
._rfb_password
).to
.equal('password');
724 var expected
= [22, 4, 6]; // auth selection, len user, len target
725 for (var i
= 0; i
< 10; i
++) { expected
[i
+3] = 'usertarget'.charCodeAt(i
); }
727 expect(client
._sock
).to
.have
.sent(expected
);
731 describe('TightVNC Authentication (type 16) Handler', function () {
734 beforeEach(function () {
736 client
.connect('host', 8675);
737 client
._sock
._websocket
._open();
738 client
._rfb_state
= 'Security';
739 client
._rfb_version
= 3.8;
740 send_security(16, client
);
741 client
._sock
._websocket
._get_sent_data(); // skip the security reply
744 function send_num_str_pairs(pairs
, client
) {
745 var pairs_len
= pairs
.length
;
747 data
.push32(pairs_len
);
749 for (var i
= 0; i
< pairs_len
; i
++) {
750 data
.push32(pairs
[i
][0]);
752 for (j
= 0; j
< 4; j
++) {
753 data
.push(pairs
[i
][1].charCodeAt(j
));
755 for (j
= 0; j
< 8; j
++) {
756 data
.push(pairs
[i
][2].charCodeAt(j
));
760 client
._sock
._websocket
._receive_data(new Uint8Array(data
));
763 it('should skip tunnel negotiation if no tunnels are requested', function () {
764 client
._sock
._websocket
._receive_data(new Uint8Array([0, 0, 0, 0]));
765 expect(client
._rfb_tightvnc
).to
.be
.true;
768 it('should fail if no supported tunnels are listed', function () {
769 send_num_str_pairs([[123, 'OTHR', 'SOMETHNG']], client
);
770 expect(client
._rfb_state
).to
.equal('failed');
773 it('should choose the notunnel tunnel type', function () {
774 send_num_str_pairs([[0, 'TGHT', 'NOTUNNEL'], [123, 'OTHR', 'SOMETHNG']], client
);
775 expect(client
._sock
).to
.have
.sent([0, 0, 0, 0]);
778 it('should continue to sub-auth negotiation after tunnel negotiation', function () {
779 send_num_str_pairs([[0, 'TGHT', 'NOTUNNEL']], client
);
780 client
._sock
._websocket
._get_sent_data(); // skip the tunnel choice here
781 send_num_str_pairs([[1, 'STDV', 'NOAUTH__']], client
);
782 expect(client
._sock
).to
.have
.sent([0, 0, 0, 1]);
783 expect(client
._rfb_state
).to
.equal('SecurityResult');
786 /*it('should attempt to use VNC auth over no auth when possible', function () {
787 client._rfb_tightvnc = true;
788 client._negotiate_std_vnc_auth = sinon.spy();
789 send_num_str_pairs([[1, 'STDV', 'NOAUTH__'], [2, 'STDV', 'VNCAUTH_']], client);
790 expect(client._sock).to.have.sent([0, 0, 0, 1]);
791 expect(client._negotiate_std_vnc_auth).to.have.been.calledOnce;
792 expect(client._rfb_auth_scheme).to.equal(2);
793 });*/ // while this would make sense, the original code doesn't actually do this
795 it('should accept the "no auth" auth type and transition to SecurityResult', function () {
796 client
._rfb_tightvnc
= true;
797 send_num_str_pairs([[1, 'STDV', 'NOAUTH__']], client
);
798 expect(client
._sock
).to
.have
.sent([0, 0, 0, 1]);
799 expect(client
._rfb_state
).to
.equal('SecurityResult');
802 it('should accept VNC authentication and transition to that', function () {
803 client
._rfb_tightvnc
= true;
804 client
._negotiate_std_vnc_auth
= sinon
.spy();
805 send_num_str_pairs([[2, 'STDV', 'VNCAUTH__']], client
);
806 expect(client
._sock
).to
.have
.sent([0, 0, 0, 2]);
807 expect(client
._negotiate_std_vnc_auth
).to
.have
.been
.calledOnce
;
808 expect(client
._rfb_auth_scheme
).to
.equal(2);
811 it('should fail if there are no supported auth types', function () {
812 client
._rfb_tightvnc
= true;
813 send_num_str_pairs([[23, 'stdv', 'badval__']], client
);
814 expect(client
._rfb_state
).to
.equal('failed');
819 describe('SecurityResult', function () {
822 beforeEach(function () {
824 client
.connect('host', 8675);
825 client
._sock
._websocket
._open();
826 client
._rfb_state
= 'SecurityResult';
829 it('should fall through to ClientInitialisation on a response code of 0', function () {
830 client
._updateState
= sinon
.spy();
831 client
._sock
._websocket
._receive_data(new Uint8Array([0, 0, 0, 0]));
832 expect(client
._updateState
).to
.have
.been
.calledOnce
;
833 expect(client
._updateState
).to
.have
.been
.calledWith('ClientInitialisation');
836 it('should fail on an error code of 1 with the given message for versions >= 3.8', function () {
837 client
._rfb_version
= 3.8;
838 sinon
.spy(client
, '_fail');
839 var failure_data
= [0, 0, 0, 1, 0, 0, 0, 6, 119, 104, 111, 111, 112, 115];
840 client
._sock
._websocket
._receive_data(new Uint8Array(failure_data
));
841 expect(client
._rfb_state
).to
.equal('failed');
842 expect(client
._fail
).to
.have
.been
.calledWith('whoops');
845 it('should fail on an error code of 1 with a standard message for version < 3.8', function () {
846 client
._rfb_version
= 3.7;
847 client
._sock
._websocket
._receive_data(new Uint8Array([0, 0, 0, 1]));
848 expect(client
._rfb_state
).to
.equal('failed');
852 describe('ClientInitialisation', function () {
855 beforeEach(function () {
857 client
.connect('host', 8675);
858 client
._sock
._websocket
._open();
859 client
._rfb_state
= 'SecurityResult';
862 it('should transition to the ServerInitialisation state', function () {
863 client
._sock
._websocket
._receive_data(new Uint8Array([0, 0, 0, 0]));
864 expect(client
._rfb_state
).to
.equal('ServerInitialisation');
867 it('should send 1 if we are in shared mode', function () {
868 client
.set_shared(true);
869 client
._sock
._websocket
._receive_data(new Uint8Array([0, 0, 0, 0]));
870 expect(client
._sock
).to
.have
.sent([1]);
873 it('should send 0 if we are not in shared mode', function () {
874 client
.set_shared(false);
875 client
._sock
._websocket
._receive_data(new Uint8Array([0, 0, 0, 0]));
876 expect(client
._sock
).to
.have
.sent([0]);
880 describe('ServerInitialisation', function () {
883 beforeEach(function () {
885 client
.connect('host', 8675);
886 client
._sock
._websocket
._open();
887 client
._rfb_state
= 'ServerInitialisation';
890 function send_server_init(opts
, client
) {
891 var full_opts
= { width
: 10, height
: 12, bpp
: 24, depth
: 24, big_endian
: 0,
892 true_color
: 1, red_max
: 255, green_max
: 255, blue_max
: 255,
893 red_shift
: 16, green_shift
: 8, blue_shift
: 0, name
: 'a name' };
894 for (var opt
in opts
) {
895 full_opts
[opt
] = opts
[opt
];
899 data
.push16(full_opts
.width
);
900 data
.push16(full_opts
.height
);
902 data
.push(full_opts
.bpp
);
903 data
.push(full_opts
.depth
);
904 data
.push(full_opts
.big_endian
);
905 data
.push(full_opts
.true_color
);
907 data
.push16(full_opts
.red_max
);
908 data
.push16(full_opts
.green_max
);
909 data
.push16(full_opts
.blue_max
);
910 data
.push8(full_opts
.red_shift
);
911 data
.push8(full_opts
.green_shift
);
912 data
.push8(full_opts
.blue_shift
);
919 client
._sock
._websocket
._receive_data(new Uint8Array(data
));
922 name_data
.push32(full_opts
.name
.length
);
923 for (var i
= 0; i
< full_opts
.name
.length
; i
++) {
924 name_data
.push(full_opts
.name
.charCodeAt(i
));
926 client
._sock
._websocket
._receive_data(new Uint8Array(name_data
));
929 it('should set the framebuffer width and height', function () {
930 send_server_init({ width
: 32, height
: 84 }, client
);
931 expect(client
._fb_width
).to
.equal(32);
932 expect(client
._fb_height
).to
.equal(84);
935 // NB(sross): we just warn, not fail, for endian-ness and shifts, so we don't test them
937 it('should set the framebuffer name and call the callback', function () {
938 client
.set_onDesktopName(sinon
.spy());
939 send_server_init({ name
: 'some name' }, client
);
941 var spy
= client
.get_onDesktopName();
942 expect(client
._fb_name
).to
.equal('some name');
943 expect(spy
).to
.have
.been
.calledOnce
;
944 expect(spy
.args
[0][1]).to
.equal('some name');
947 it('should handle the extended init message of the tight encoding', function () {
948 // NB(sross): we don't actually do anything with it, so just test that we can
949 // read it w/o throwing an error
950 client
._rfb_tightvnc
= true;
951 send_server_init({}, client
);
954 tight_data
.push16(1);
955 tight_data
.push16(2);
956 tight_data
.push16(3);
957 tight_data
.push16(0);
958 for (var i
= 0; i
< 16 + 32 + 48; i
++) {
961 client
._sock
._websocket
._receive_data(tight_data
);
963 expect(client
._rfb_state
).to
.equal('normal');
966 it('should set the true color mode on the display to the configuration variable', function () {
967 client
.set_true_color(false);
968 sinon
.spy(client
._display
, 'set_true_color');
969 send_server_init({ true_color
: 1 }, client
);
970 expect(client
._display
.set_true_color
).to
.have
.been
.calledOnce
;
971 expect(client
._display
.set_true_color
).to
.have
.been
.calledWith(false);
974 it('should call the resize callback and resize the display', function () {
975 client
.set_onFBResize(sinon
.spy());
976 sinon
.spy(client
._display
, 'resize');
977 send_server_init({ width
: 27, height
: 32 }, client
);
979 var spy
= client
.get_onFBResize();
980 expect(client
._display
.resize
).to
.have
.been
.calledOnce
;
981 expect(client
._display
.resize
).to
.have
.been
.calledWith(27, 32);
982 expect(spy
).to
.have
.been
.calledOnce
;
983 expect(spy
.args
[0][1]).to
.equal(27);
984 expect(spy
.args
[0][2]).to
.equal(32);
987 it('should grab the mouse and keyboard', function () {
988 sinon
.spy(client
._keyboard
, 'grab');
989 sinon
.spy(client
._mouse
, 'grab');
990 send_server_init({}, client
);
991 expect(client
._keyboard
.grab
).to
.have
.been
.calledOnce
;
992 expect(client
._mouse
.grab
).to
.have
.been
.calledOnce
;
995 it('should set the BPP and depth to 4 and 3 respectively if in true color mode', function () {
996 client
.set_true_color(true);
997 send_server_init({}, client
);
998 expect(client
._fb_Bpp
).to
.equal(4);
999 expect(client
._fb_depth
).to
.equal(3);
1002 it('should set the BPP and depth to 1 and 1 respectively if not in true color mode', function () {
1003 client
.set_true_color(false);
1004 send_server_init({}, client
);
1005 expect(client
._fb_Bpp
).to
.equal(1);
1006 expect(client
._fb_depth
).to
.equal(1);
1009 // TODO(directxman12): test the various options in this configuration matrix
1010 it('should reply with the pixel format, client encodings, and initial update request', function () {
1011 client
.set_true_color(true);
1012 client
.set_local_cursor(false);
1013 var expected
= RFB
.messages
.pixelFormat(4, 3, true);
1014 expected
= expected
.concat(RFB
.messages
.clientEncodings(client
._encodings
, false, true));
1015 var expected_cdr
= { cleanBox
: { x
: 0, y
: 0, w
: 0, h
: 0 },
1016 dirtyBoxes
: [ { x
: 0, y
: 0, w
: 27, h
: 32 } ] };
1017 expected
= expected
.concat(RFB
.messages
.fbUpdateRequests(expected_cdr
, 27, 32));
1019 send_server_init({ width
: 27, height
: 32 }, client
);
1020 expect(client
._sock
).to
.have
.sent(expected
);
1023 it('should check for sending mouse events', function () {
1024 // be lazy with our checking so we don't have to check through the whole sent buffer
1025 sinon
.spy(client
, '_checkEvents');
1026 send_server_init({}, client
);
1027 expect(client
._checkEvents
).to
.have
.been
.calledOnce
;
1030 it('should transition to the "normal" state', function () {
1031 send_server_init({}, client
);
1032 expect(client
._rfb_state
).to
.equal('normal');
1037 describe('Protocol Message Processing After Completing Initialization', function () {
1040 beforeEach(function () {
1041 client
= make_rfb();
1042 client
.connect('host', 8675);
1043 client
._sock
._websocket
._open();
1044 client
._rfb_state
= 'normal';
1045 client
._fb_name
= 'some device';
1046 client
._fb_width
= 640;
1047 client
._fb_height
= 20;
1050 describe('Framebuffer Update Handling', function () {
1053 beforeEach(function () {
1054 client
= make_rfb();
1055 client
.connect('host', 8675);
1056 client
._sock
._websocket
._open();
1057 client
._rfb_state
= 'normal';
1058 client
._fb_name
= 'some device';
1059 client
._fb_width
= 640;
1060 client
._fb_height
= 20;
1063 var target_data_arr
= [
1064 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
1065 0x00, 0xff, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
1066 0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255,
1067 0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255
1071 var target_data_check_arr
= [
1072 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
1073 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
1074 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
1075 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
1077 var target_data_check
;
1079 before(function () {
1080 // NB(directxman12): PhantomJS 1.x doesn't implement Uint8ClampedArray
1081 target_data
= new Uint8Array(target_data_arr
);
1082 target_data_check
= new Uint8Array(target_data_check_arr
);
1085 function send_fbu_msg (rect_info
, rect_data
, client
, rect_cnt
) {
1088 if (!rect_cnt
|| rect_cnt
> -1) {
1090 data
.push(0); // msg type
1091 data
.push(0); // padding
1092 data
.push16(rect_cnt
|| rect_data
.length
);
1095 for (var i
= 0; i
< rect_data
.length
; i
++) {
1097 data
.push16(rect_info
[i
].x
);
1098 data
.push16(rect_info
[i
].y
);
1099 data
.push16(rect_info
[i
].width
);
1100 data
.push16(rect_info
[i
].height
);
1101 data
.push32(rect_info
[i
].encoding
);
1103 data
= data
.concat(rect_data
[i
]);
1106 client
._sock
._websocket
._receive_data(new Uint8Array(data
));
1109 it('should send an update request if there is sufficient data', function () {
1110 var expected_cdr
= { cleanBox
: { x
: 0, y
: 0, w
: 0, h
: 0 },
1111 dirtyBoxes
: [ { x
: 0, y
: 0, w
: 640, h
: 20 } ] };
1112 var expected_msg
= RFB
.messages
.fbUpdateRequests(expected_cdr
, 640, 20);
1114 client
._framebufferUpdate = function () { return true; };
1115 client
._sock
._websocket
._receive_data(new Uint8Array([0]));
1117 expect(client
._sock
).to
.have
.sent(expected_msg
);
1120 it('should not send an update request if we need more data', function () {
1121 client
._sock
._websocket
._receive_data(new Uint8Array([0]));
1122 expect(client
._sock
._websocket
._get_sent_data()).to
.have
.length(0);
1125 it('should resume receiving an update if we previously did not have enough data', function () {
1126 var expected_cdr
= { cleanBox
: { x
: 0, y
: 0, w
: 0, h
: 0 },
1127 dirtyBoxes
: [ { x
: 0, y
: 0, w
: 640, h
: 20 } ] };
1128 var expected_msg
= RFB
.messages
.fbUpdateRequests(expected_cdr
, 640, 20);
1130 // just enough to set FBU.rects
1131 client
._sock
._websocket
._receive_data(new Uint8Array([0, 0, 0, 3]));
1132 expect(client
._sock
._websocket
._get_sent_data()).to
.have
.length(0);
1134 client
._framebufferUpdate = function () { return true; }; // we magically have enough data
1135 // 247 should *not* be used as the message type here
1136 client
._sock
._websocket
._receive_data(new Uint8Array([247]));
1137 expect(client
._sock
).to
.have
.sent(expected_msg
);
1140 it('should parse out information from a header before any actual data comes in', function () {
1141 client
.set_onFBUReceive(sinon
.spy());
1142 var rect_info
= { x
: 8, y
: 11, width
: 27, height
: 32, encoding
: 0x02, encodingName
: 'RRE' };
1143 send_fbu_msg([rect_info
], [[]], client
);
1145 var spy
= client
.get_onFBUReceive();
1146 expect(spy
).to
.have
.been
.calledOnce
;
1147 expect(spy
).to
.have
.been
.calledWith(sinon
.match
.any
, rect_info
);
1150 it('should fire onFBUComplete when the update is complete', function () {
1151 client
.set_onFBUComplete(sinon
.spy());
1152 var rect_info
= { x
: 8, y
: 11, width
: 27, height
: 32, encoding
: -224, encodingName
: 'last_rect' };
1153 send_fbu_msg([rect_info
], [[]], client
); // last_rect
1155 var spy
= client
.get_onFBUComplete();
1156 expect(spy
).to
.have
.been
.calledOnce
;
1157 expect(spy
).to
.have
.been
.calledWith(sinon
.match
.any
, rect_info
);
1160 it('should not fire onFBUComplete if we have not finished processing the update', function () {
1161 client
.set_onFBUComplete(sinon
.spy());
1162 var rect_info
= { x
: 8, y
: 11, width
: 27, height
: 32, encoding
: 0x00, encodingName
: 'RAW' };
1163 send_fbu_msg([rect_info
], [[]], client
);
1164 expect(client
.get_onFBUComplete()).to
.not
.have
.been
.called
;
1167 it('should call the appropriate encoding handler', function () {
1168 client
._encHandlers
[0x02] = sinon
.spy();
1169 var rect_info
= { x
: 8, y
: 11, width
: 27, height
: 32, encoding
: 0x02 };
1170 send_fbu_msg([rect_info
], [[]], client
);
1171 expect(client
._encHandlers
[0x02]).to
.have
.been
.calledOnce
;
1174 it('should fail on an unsupported encoding', function () {
1175 client
.set_onFBUReceive(sinon
.spy());
1176 var rect_info
= { x
: 8, y
: 11, width
: 27, height
: 32, encoding
: 234 };
1177 send_fbu_msg([rect_info
], [[]], client
);
1178 expect(client
._rfb_state
).to
.equal('failed');
1181 it('should be able to pause and resume receiving rects if not enought data', function () {
1182 // seed some initial data to copy
1183 client
._fb_width
= 4;
1184 client
._fb_height
= 4;
1185 client
._display
.resize(4, 4);
1186 var initial_data
= client
._display
._drawCtx
.createImageData(4, 2);
1187 var initial_data_arr
= target_data_check_arr
.slice(0, 32);
1188 for (var i
= 0; i
< 32; i
++) { initial_data
.data
[i
] = initial_data_arr
[i
]; }
1189 client
._display
._drawCtx
.putImageData(initial_data
, 0, 0);
1191 var info
= [{ x
: 0, y
: 2, width
: 2, height
: 2, encoding
: 0x01},
1192 { x
: 2, y
: 2, width
: 2, height
: 2, encoding
: 0x01}];
1193 // data says [{ old_x: 0, old_y: 0 }, { old_x: 0, old_y: 0 }]
1194 var rects
= [[0, 2, 0, 0], [0, 0, 0, 0]];
1195 send_fbu_msg([info
[0]], [rects
[0]], client
, 2);
1196 send_fbu_msg([info
[1]], [rects
[1]], client
, -1);
1197 expect(client
._display
).to
.have
.displayed(target_data_check
);
1200 describe('Message Encoding Handlers', function () {
1203 beforeEach(function () {
1204 client
= make_rfb();
1205 client
.connect('host', 8675);
1206 client
._sock
._websocket
._open();
1207 client
._rfb_state
= 'normal';
1208 client
._fb_name
= 'some device';
1209 // a really small frame
1210 client
._fb_width
= 4;
1211 client
._fb_height
= 4;
1212 client
._display
._fb_width
= 4;
1213 client
._display
._fb_height
= 4;
1217 it('should handle the RAW encoding', function () {
1218 var info
= [{ x
: 0, y
: 0, width
: 2, height
: 2, encoding
: 0x00 },
1219 { x
: 2, y
: 0, width
: 2, height
: 2, encoding
: 0x00 },
1220 { x
: 0, y
: 2, width
: 4, height
: 1, encoding
: 0x00 },
1221 { x
: 0, y
: 3, width
: 4, height
: 1, encoding
: 0x00 }];
1224 [0x00, 0x00, 0xff, 0, 0x00, 0xff, 0x00, 0, 0x00, 0xff, 0x00, 0, 0x00, 0x00, 0xff, 0],
1225 [0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0],
1226 [0xff, 0x00, 0xee, 0, 0xff, 0xee, 0x00, 0, 0xff, 0xee, 0xaa, 0, 0xff, 0xee, 0xab, 0],
1227 [0xff, 0x00, 0xee, 0, 0xff, 0xee, 0x00, 0, 0xff, 0xee, 0xaa, 0, 0xff, 0xee, 0xab, 0]];
1228 send_fbu_msg(info
, rects
, client
);
1229 expect(client
._display
).to
.have
.displayed(target_data
);
1232 it('should handle the COPYRECT encoding', function () {
1233 // seed some initial data to copy
1234 var initial_data
= client
._display
._drawCtx
.createImageData(4, 2);
1235 var initial_data_arr
= target_data_check_arr
.slice(0, 32);
1236 for (var i
= 0; i
< 32; i
++) { initial_data
.data
[i
] = initial_data_arr
[i
]; }
1237 client
._display
._drawCtx
.putImageData(initial_data
, 0, 0);
1239 var info
= [{ x
: 0, y
: 2, width
: 2, height
: 2, encoding
: 0x01},
1240 { x
: 2, y
: 2, width
: 2, height
: 2, encoding
: 0x01}];
1241 // data says [{ old_x: 0, old_y: 0 }, { old_x: 0, old_y: 0 }]
1242 var rects
= [[0, 2, 0, 0], [0, 0, 0, 0]];
1243 send_fbu_msg(info
, rects
, client
);
1244 expect(client
._display
).to
.have
.displayed(target_data_check
);
1247 // TODO(directxman12): for encodings with subrects, test resuming on partial send?
1248 // TODO(directxman12): test rre_chunk_sz (related to above about subrects)?
1250 it('should handle the RRE encoding', function () {
1251 var info
= [{ x
: 0, y
: 0, width
: 4, height
: 4, encoding
: 0x02 }];
1253 rect
.push32(2); // 2 subrects
1254 rect
.push32(0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1255 rect
.push(0xff); // becomes ff0000ff --> #0000FF color
1259 rect
.push16(0); // x: 0
1260 rect
.push16(0); // y: 0
1261 rect
.push16(2); // width: 2
1262 rect
.push16(2); // height: 2
1263 rect
.push(0xff); // becomes ff0000ff --> #0000FF color
1267 rect
.push16(2); // x: 2
1268 rect
.push16(2); // y: 2
1269 rect
.push16(2); // width: 2
1270 rect
.push16(2); // height: 2
1272 send_fbu_msg(info
, [rect
], client
);
1273 expect(client
._display
).to
.have
.displayed(target_data_check
);
1276 describe('the HEXTILE encoding handler', function () {
1278 beforeEach(function () {
1279 client
= make_rfb();
1280 client
.connect('host', 8675);
1281 client
._sock
._websocket
._open();
1282 client
._rfb_state
= 'normal';
1283 client
._fb_name
= 'some device';
1284 // a really small frame
1285 client
._fb_width
= 4;
1286 client
._fb_height
= 4;
1287 client
._display
._fb_width
= 4;
1288 client
._display
._fb_height
= 4;
1292 it('should handle a tile with fg, bg specified, normal subrects', function () {
1293 var info
= [{ x
: 0, y
: 0, width
: 4, height
: 4, encoding
: 0x05 }];
1295 rect
.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects
1296 rect
.push32(0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1297 rect
.push(0xff); // becomes ff0000ff --> #0000FF fg color
1301 rect
.push(2); // 2 subrects
1302 rect
.push(0); // x: 0, y: 0
1303 rect
.push(1 | (1 << 4)); // width: 2, height: 2
1304 rect
.push(2 | (2 << 4)); // x: 2, y: 2
1305 rect
.push(1 | (1 << 4)); // width: 2, height: 2
1306 send_fbu_msg(info
, [rect
], client
);
1307 expect(client
._display
).to
.have
.displayed(target_data_check
);
1310 it('should handle a raw tile', function () {
1311 var info
= [{ x
: 0, y
: 0, width
: 4, height
: 4, encoding
: 0x05 }];
1313 rect
.push(0x01); // raw
1314 for (var i
= 0; i
< target_data
.length
; i
+= 4) {
1315 rect
.push(target_data
[i
+ 2]);
1316 rect
.push(target_data
[i
+ 1]);
1317 rect
.push(target_data
[i
]);
1318 rect
.push(target_data
[i
+ 3]);
1320 send_fbu_msg(info
, [rect
], client
);
1321 expect(client
._display
).to
.have
.displayed(target_data
);
1324 it('should handle a tile with only bg specified (solid bg)', function () {
1325 var info
= [{ x
: 0, y
: 0, width
: 4, height
: 4, encoding
: 0x05 }];
1328 rect
.push32(0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1329 send_fbu_msg(info
, [rect
], client
);
1332 for (var i
= 0; i
< 16; i
++) { expected
.push32(0xff00ff); }
1333 expect(client
._display
).to
.have
.displayed(new Uint8Array(expected
));
1336 it('should handle a tile with bg and coloured subrects', function () {
1337 var info
= [{ x
: 0, y
: 0, width
: 4, height
: 4, encoding
: 0x05 }];
1339 rect
.push(0x02 | 0x08 | 0x10); // bg spec, anysubrects, colouredsubrects
1340 rect
.push32(0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1341 rect
.push(2); // 2 subrects
1342 rect
.push(0xff); // becomes ff0000ff --> #0000FF fg color
1346 rect
.push(0); // x: 0, y: 0
1347 rect
.push(1 | (1 << 4)); // width: 2, height: 2
1348 rect
.push(0xff); // becomes ff0000ff --> #0000FF fg color
1352 rect
.push(2 | (2 << 4)); // x: 2, y: 2
1353 rect
.push(1 | (1 << 4)); // width: 2, height: 2
1354 send_fbu_msg(info
, [rect
], client
);
1355 expect(client
._display
).to
.have
.displayed(target_data_check
);
1358 it('should carry over fg and bg colors from the previous tile if not specified', function () {
1359 client
._fb_width
= 4;
1360 client
._fb_height
= 17;
1361 client
._display
.resize(4, 17);
1363 var info
= [{ x
: 0, y
: 0, width
: 4, height
: 17, encoding
: 0x05}];
1365 rect
.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects
1366 rect
.push32(0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1367 rect
.push(0xff); // becomes ff0000ff --> #0000FF fg color
1371 rect
.push(8); // 8 subrects
1373 for (i
= 0; i
< 4; i
++) {
1374 rect
.push((0 << 4) | (i
* 4)); // x: 0, y: i*4
1375 rect
.push(1 | (1 << 4)); // width: 2, height: 2
1376 rect
.push((2 << 4) | (i
* 4 + 2)); // x: 2, y: i * 4 + 2
1377 rect
.push(1 | (1 << 4)); // width: 2, height: 2
1379 rect
.push(0x08); // anysubrects
1380 rect
.push(1); // 1 subrect
1381 rect
.push(0); // x: 0, y: 0
1382 rect
.push(1 | (1 << 4)); // width: 2, height: 2
1383 send_fbu_msg(info
, [rect
], client
);
1386 for (i
= 0; i
< 4; i
++) { expected
= expected
.concat(target_data_check_arr
); }
1387 expected
= expected
.concat(target_data_check_arr
.slice(0, 16));
1388 expect(client
._display
).to
.have
.displayed(new Uint8Array(expected
));
1391 it('should fail on an invalid subencoding', function () {
1392 var info
= [{ x
: 0, y
: 0, width
: 4, height
: 4, encoding
: 0x05 }];
1393 var rects
= [[45]]; // an invalid subencoding
1394 send_fbu_msg(info
, rects
, client
);
1395 expect(client
._rfb_state
).to
.equal('failed');
1399 it
.skip('should handle the TIGHT encoding', function () {
1400 // TODO(directxman12): test this
1403 it
.skip('should handle the TIGHT_PNG encoding', function () {
1404 // TODO(directxman12): test this
1407 it('should handle the DesktopSize pseduo-encoding', function () {
1408 client
.set_onFBResize(sinon
.spy());
1409 sinon
.spy(client
._display
, 'resize');
1410 send_fbu_msg([{ x
: 0, y
: 0, width
: 20, height
: 50, encoding
: -223 }], [[]], client
);
1412 var spy
= client
.get_onFBResize();
1413 expect(spy
).to
.have
.been
.calledOnce
;
1414 expect(spy
).to
.have
.been
.calledWith(sinon
.match
.any
, 20, 50);
1416 expect(client
._fb_width
).to
.equal(20);
1417 expect(client
._fb_height
).to
.equal(50);
1419 expect(client
._display
.resize
).to
.have
.been
.calledOnce
;
1420 expect(client
._display
.resize
).to
.have
.been
.calledWith(20, 50);
1423 it
.skip('should handle the Cursor pseudo-encoding', function () {
1424 // TODO(directxman12): test
1427 it('should handle the last_rect pseudo-encoding', function () {
1428 client
.set_onFBUReceive(sinon
.spy());
1429 send_fbu_msg([{ x
: 0, y
: 0, width
: 0, height
: 0, encoding
: -224}], [[]], client
, 100);
1430 expect(client
._FBU
.rects
).to
.equal(0);
1431 expect(client
.get_onFBUReceive()).to
.have
.been
.calledOnce
;
1436 it('should set the colour map on the display on SetColourMapEntries', function () {
1437 var expected_cm
= [];
1438 var data
= [1, 0, 0, 1, 0, 4];
1440 for (i
= 0; i
< 4; i
++) {
1441 expected_cm
[i
+ 1] = [i
* 10, i
* 10 + 1, i
* 10 + 2];
1442 data
.push16(expected_cm
[i
+ 1][2] << 8);
1443 data
.push16(expected_cm
[i
+ 1][1] << 8);
1444 data
.push16(expected_cm
[i
+ 1][0] << 8);
1447 client
._sock
._websocket
._receive_data(new Uint8Array(data
));
1448 expect(client
._display
.get_colourMap()).to
.deep
.equal(expected_cm
);
1451 describe('XVP Message Handling', function () {
1452 beforeEach(function () {
1453 client
= make_rfb();
1454 client
.connect('host', 8675);
1455 client
._sock
._websocket
._open();
1456 client
._rfb_state
= 'normal';
1457 client
._fb_name
= 'some device';
1458 client
._fb_width
= 27;
1459 client
._fb_height
= 32;
1462 it('should call updateState with a message on XVP_FAIL, but keep the same state', function () {
1463 client
._updateState
= sinon
.spy();
1464 client
._sock
._websocket
._receive_data(new Uint8Array([250, 0, 10, 0]));
1465 expect(client
._updateState
).to
.have
.been
.calledOnce
;
1466 expect(client
._updateState
).to
.have
.been
.calledWith('normal', 'Operation Failed');
1469 it('should set the XVP version and fire the callback with the version on XVP_INIT', function () {
1470 client
.set_onXvpInit(sinon
.spy());
1471 client
._sock
._websocket
._receive_data(new Uint8Array([250, 0, 10, 1]));
1472 expect(client
._rfb_xvp_ver
).to
.equal(10);
1473 expect(client
.get_onXvpInit()).to
.have
.been
.calledOnce
;
1474 expect(client
.get_onXvpInit()).to
.have
.been
.calledWith(10);
1477 it('should fail on unknown XVP message types', function () {
1478 client
._sock
._websocket
._receive_data(new Uint8Array([250, 0, 10, 237]));
1479 expect(client
._rfb_state
).to
.equal('failed');
1483 it('should fire the clipboard callback with the retrieved text on ServerCutText', function () {
1484 var expected_str
= 'cheese!';
1485 var data
= [3, 0, 0, 0];
1486 data
.push32(expected_str
.length
);
1487 for (var i
= 0; i
< expected_str
.length
; i
++) { data
.push(expected_str
.charCodeAt(i
)); }
1488 client
.set_onClipboard(sinon
.spy());
1490 client
._sock
._websocket
._receive_data(new Uint8Array(data
));
1491 var spy
= client
.get_onClipboard();
1492 expect(spy
).to
.have
.been
.calledOnce
;
1493 expect(spy
.args
[0][1]).to
.equal(expected_str
);
1496 it('should fire the bell callback on Bell', function () {
1497 client
.set_onBell(sinon
.spy());
1498 client
._sock
._websocket
._receive_data(new Uint8Array([2]));
1499 expect(client
.get_onBell()).to
.have
.been
.calledOnce
;
1502 it('should fail on an unknown message type', function () {
1503 client
._sock
._websocket
._receive_data(new Uint8Array([87]));
1504 expect(client
._rfb_state
).to
.equal('failed');
1508 describe('Asynchronous Events', function () {
1509 describe('Mouse event handlers', function () {
1511 beforeEach(function () {
1512 client
= make_rfb();
1513 client
._sock
.send
= sinon
.spy();
1514 client
._rfb_state
= 'normal';
1517 it('should not send button messages in view-only mode', function () {
1518 client
._view_only
= true;
1519 client
._mouse
._onMouseButton(0, 0, 1, 0x001);
1520 expect(client
._sock
.send
).to
.not
.have
.been
.called
;
1523 it('should not send movement messages in view-only mode', function () {
1524 client
._view_only
= true;
1525 client
._mouse
._onMouseMove(0, 0);
1526 expect(client
._sock
.send
).to
.not
.have
.been
.called
;
1529 it('should send a pointer event on mouse button presses', function () {
1530 client
._mouse
._onMouseButton(10, 12, 1, 0x001);
1531 expect(client
._sock
.send
).to
.have
.been
.calledOnce
;
1532 var pointer_msg
= RFB
.messages
.pointerEvent(10, 12, 0x001);
1533 expect(client
._sock
.send
).to
.have
.been
.calledWith(pointer_msg
);
1536 it('should send a pointer event on mouse movement', function () {
1537 client
._mouse
._onMouseMove(10, 12);
1538 expect(client
._sock
.send
).to
.have
.been
.calledOnce
;
1539 var pointer_msg
= RFB
.messages
.pointerEvent(10, 12, 0);
1540 expect(client
._sock
.send
).to
.have
.been
.calledWith(pointer_msg
);
1543 it('should set the button mask so that future mouse movements use it', function () {
1544 client
._mouse
._onMouseButton(10, 12, 1, 0x010);
1545 client
._sock
.send
= sinon
.spy();
1546 client
._mouse
._onMouseMove(13, 9);
1547 expect(client
._sock
.send
).to
.have
.been
.calledOnce
;
1548 var pointer_msg
= RFB
.messages
.pointerEvent(13, 9, 0x010);
1549 expect(client
._sock
.send
).to
.have
.been
.calledWith(pointer_msg
);
1552 // NB(directxman12): we don't need to test not sending messages in
1553 // non-normal modes, since we haven't grabbed input
1554 // yet (grabbing input should be checked in the lifecycle tests).
1556 it('should not send movement messages when viewport dragging', function () {
1557 client
._viewportDragging
= true;
1558 client
._display
.viewportChange
= sinon
.spy();
1559 client
._mouse
._onMouseMove(13, 9);
1560 expect(client
._sock
.send
).to
.not
.have
.been
.called
;
1563 it('should not send button messages when initiating viewport dragging', function () {
1564 client
._viewportDrag
= true;
1565 client
._mouse
._onMouseButton(13, 9, 0x001);
1566 expect(client
._sock
.send
).to
.not
.have
.been
.called
;
1569 it('should be initiate viewport dragging on a button down event, if enabled', function () {
1570 client
._viewportDrag
= true;
1571 client
._mouse
._onMouseButton(13, 9, 0x001);
1572 expect(client
._viewportDragging
).to
.be
.true;
1573 expect(client
._viewportDragPos
).to
.deep
.equal({ x
: 13, y
: 9 });
1576 it('should terminate viewport dragging on a button up event, if enabled', function () {
1577 client
._viewportDrag
= true;
1578 client
._viewportDragging
= true;
1579 client
._mouse
._onMouseButton(13, 9, 0x000);
1580 expect(client
._viewportDragging
).to
.be
.false;
1583 it('if enabled, viewportDragging should occur on mouse movement while a button is down', function () {
1584 client
._viewportDrag
= true;
1585 client
._viewportDragging
= true;
1586 client
._viewportDragPos
= { x
: 13, y
: 9 };
1587 client
._display
.viewportChange
= sinon
.spy();
1589 client
._mouse
._onMouseMove(10, 4);
1591 expect(client
._viewportDragging
).to
.be
.true;
1592 expect(client
._viewportDragPos
).to
.deep
.equal({ x
: 10, y
: 4 });
1593 expect(client
._display
.viewportChange
).to
.have
.been
.calledOnce
;
1594 expect(client
._display
.viewportChange
).to
.have
.been
.calledWith(3, 5);
1598 describe('Keyboard Event Handlers', function () {
1600 beforeEach(function () {
1601 client
= make_rfb();
1602 client
._sock
.send
= sinon
.spy();
1605 it('should send a key message on a key press', function () {
1606 client
._keyboard
._onKeyPress(1234, 1);
1607 expect(client
._sock
.send
).to
.have
.been
.calledOnce
;
1608 var key_msg
= RFB
.messages
.keyEvent(1234, 1);
1609 expect(client
._sock
.send
).to
.have
.been
.calledWith(key_msg
);
1612 it('should not send messages in view-only mode', function () {
1613 client
._view_only
= true;
1614 client
._keyboard
._onKeyPress(1234, 1);
1615 expect(client
._sock
.send
).to
.not
.have
.been
.called
;
1619 describe('WebSocket event handlers', function () {
1621 beforeEach(function () {
1622 client
= make_rfb();
1623 this.clock
= sinon
.useFakeTimers();
1626 afterEach(function () { this.clock
.restore(); });
1629 it ('should do nothing if we receive an empty message and have nothing in the queue', function () {
1630 client
.connect('host', 8675);
1631 client
._rfb_state
= 'normal';
1632 client
._normal_msg
= sinon
.spy();
1633 client
._sock
._websocket
._receive_data(Base64
.encode([]));
1634 expect(client
._normal_msg
).to
.not
.have
.been
.called
;
1637 it('should handle a message in the normal state as a normal message', function () {
1638 client
.connect('host', 8675);
1639 client
._rfb_state
= 'normal';
1640 client
._normal_msg
= sinon
.spy();
1641 client
._sock
._websocket
._receive_data(Base64
.encode([1, 2, 3]));
1642 expect(client
._normal_msg
).to
.have
.been
.calledOnce
;
1645 it('should handle a message in any non-disconnected/failed state like an init message', function () {
1646 client
.connect('host', 8675);
1647 client
._rfb_state
= 'ProtocolVersion';
1648 client
._init_msg
= sinon
.spy();
1649 client
._sock
._websocket
._receive_data(Base64
.encode([1, 2, 3]));
1650 expect(client
._init_msg
).to
.have
.been
.calledOnce
;
1653 it('should split up the handling of muplitle normal messages across 10ms intervals', function () {
1654 client
.connect('host', 8675);
1655 client
._sock
._websocket
._open();
1656 client
._rfb_state
= 'normal';
1657 client
.set_onBell(sinon
.spy());
1658 client
._sock
._websocket
._receive_data(new Uint8Array([0x02, 0x02]));
1659 expect(client
.get_onBell()).to
.have
.been
.calledOnce
;
1660 this.clock
.tick(20);
1661 expect(client
.get_onBell()).to
.have
.been
.calledTwice
;
1665 it('should update the state to ProtocolVersion on open (if the state is "connect")', function () {
1666 client
.connect('host', 8675);
1667 client
._sock
._websocket
._open();
1668 expect(client
._rfb_state
).to
.equal('ProtocolVersion');
1671 it('should fail if we are not currently ready to connect and we get an "open" event', function () {
1672 client
.connect('host', 8675);
1673 client
._rfb_state
= 'some_other_state';
1674 client
._sock
._websocket
._open();
1675 expect(client
._rfb_state
).to
.equal('failed');
1679 it('should transition to "disconnected" from "disconnect" on a close event', function () {
1680 client
.connect('host', 8675);
1681 client
._rfb_state
= 'disconnect';
1682 client
._sock
._websocket
.close();
1683 expect(client
._rfb_state
).to
.equal('disconnected');
1686 it('should transition to failed if we get a close event from any non-"disconnection" state', function () {
1687 client
.connect('host', 8675);
1688 client
._rfb_state
= 'normal';
1689 client
._sock
._websocket
.close();
1690 expect(client
._rfb_state
).to
.equal('failed');
1693 // error events do nothing