]> git.proxmox.com Git - mirror_novnc.git/blob - tests/test.rfb.js
Enable noVNC to become Browserifiable
[mirror_novnc.git] / tests / test.rfb.js
1 // requires local modules: util, websock, rfb, keyboard, keysym, keysymdef, input, inflator, des, display
2 // requires test modules: fake.websocket, assertions
3 /* jshint expr: true */
4 var assert = chai.assert;
5 var expect = chai.expect;
6
7 function make_rfb (extra_opts) {
8 if (!extra_opts) {
9 extra_opts = {};
10 }
11
12 extra_opts.target = extra_opts.target || document.createElement('canvas');
13 return new RFB(extra_opts);
14 }
15
16 describe('Remote Frame Buffer Protocol Client', function() {
17 "use strict";
18 before(FakeWebSocket.replace);
19 after(FakeWebSocket.restore);
20
21 before(function () {
22 this.clock = sinon.useFakeTimers();
23 // Use a single set of buffers instead of reallocating to
24 // speed up tests
25 var sock = new Websock();
26 var _sQ = new Uint8Array(sock._sQbufferSize);
27 var rQ = new Uint8Array(sock._rQbufferSize);
28
29 Websock.prototype._old_allocate_buffers = Websock.prototype._allocate_buffers;
30 Websock.prototype._allocate_buffers = function () {
31 this._sQ = _sQ;
32 this._rQ = rQ;
33 };
34
35 });
36
37 after(function () {
38 Websock.prototype._allocate_buffers = Websock.prototype._old_allocate_buffers;
39 this.clock.restore();
40 });
41
42 describe('Public API Basic Behavior', function () {
43 var client;
44 beforeEach(function () {
45 client = make_rfb();
46 });
47
48 describe('#connect', function () {
49 beforeEach(function () { client._updateState = sinon.spy(); });
50
51 it('should set the current state to "connect"', function () {
52 client.connect('host', 8675);
53 expect(client._updateState).to.have.been.calledOnce;
54 expect(client._updateState).to.have.been.calledWith('connect');
55 });
56
57 it('should fail if we are missing a host', function () {
58 sinon.spy(client, '_fail');
59 client.connect(undefined, 8675);
60 expect(client._fail).to.have.been.calledOnce;
61 });
62
63 it('should fail if we are missing a port', function () {
64 sinon.spy(client, '_fail');
65 client.connect('abc');
66 expect(client._fail).to.have.been.calledOnce;
67 });
68
69 it('should not update the state if we are missing a host or port', function () {
70 sinon.spy(client, '_fail');
71 client.connect('abc');
72 expect(client._fail).to.have.been.calledOnce;
73 expect(client._updateState).to.have.been.calledOnce;
74 expect(client._updateState).to.have.been.calledWith('failed');
75 });
76 });
77
78 describe('#disconnect', function () {
79 beforeEach(function () { client._updateState = sinon.spy(); });
80
81 it('should set the current state to "disconnect"', function () {
82 client.disconnect();
83 expect(client._updateState).to.have.been.calledOnce;
84 expect(client._updateState).to.have.been.calledWith('disconnect');
85 });
86
87 it('should unregister error event handler', function () {
88 sinon.spy(client._sock, 'off');
89 client.disconnect();
90 expect(client._sock.off).to.have.been.calledWith('error');
91 });
92
93 it('should unregister message event handler', function () {
94 sinon.spy(client._sock, 'off');
95 client.disconnect();
96 expect(client._sock.off).to.have.been.calledWith('message');
97 });
98
99 it('should unregister open event handler', function () {
100 sinon.spy(client._sock, 'off');
101 client.disconnect();
102 expect(client._sock.off).to.have.been.calledWith('open');
103 });
104 });
105
106 describe('#sendPassword', function () {
107 beforeEach(function () { this.clock = sinon.useFakeTimers(); });
108 afterEach(function () { this.clock.restore(); });
109
110 it('should set the state to "Authentication"', function () {
111 client._rfb_state = "blah";
112 client.sendPassword('pass');
113 expect(client._rfb_state).to.equal('Authentication');
114 });
115
116 it('should call init_msg "soon"', function () {
117 client._init_msg = sinon.spy();
118 client.sendPassword('pass');
119 this.clock.tick(5);
120 expect(client._init_msg).to.have.been.calledOnce;
121 });
122 });
123
124 describe('#sendCtrlAlDel', function () {
125 beforeEach(function () {
126 client._sock = new Websock();
127 client._sock.open('ws://', 'binary');
128 client._sock._websocket._open();
129 sinon.spy(client._sock, 'flush');
130 client._rfb_state = "normal";
131 client._view_only = false;
132 });
133
134 it('should sent ctrl[down]-alt[down]-del[down] then del[up]-alt[up]-ctrl[up]', function () {
135 var expected = {_sQ: new Uint8Array(48), _sQlen: 0, flush: function () {}};
136 RFB.messages.keyEvent(expected, 0xFFE3, 1);
137 RFB.messages.keyEvent(expected, 0xFFE9, 1);
138 RFB.messages.keyEvent(expected, 0xFFFF, 1);
139 RFB.messages.keyEvent(expected, 0xFFFF, 0);
140 RFB.messages.keyEvent(expected, 0xFFE9, 0);
141 RFB.messages.keyEvent(expected, 0xFFE3, 0);
142
143 client.sendCtrlAltDel();
144 expect(client._sock).to.have.sent(expected._sQ);
145 });
146
147 it('should not send the keys if we are not in a normal state', function () {
148 client._rfb_state = "broken";
149 client.sendCtrlAltDel();
150 expect(client._sock.flush).to.not.have.been.called;
151 });
152
153 it('should not send the keys if we are set as view_only', function () {
154 client._view_only = true;
155 client.sendCtrlAltDel();
156 expect(client._sock.flush).to.not.have.been.called;
157 });
158 });
159
160 describe('#sendKey', function () {
161 beforeEach(function () {
162 client._sock = new Websock();
163 client._sock.open('ws://', 'binary');
164 client._sock._websocket._open();
165 sinon.spy(client._sock, 'flush');
166 client._rfb_state = "normal";
167 client._view_only = false;
168 });
169
170 it('should send a single key with the given code and state (down = true)', function () {
171 var expected = {_sQ: new Uint8Array(8), _sQlen: 0, flush: function () {}};
172 RFB.messages.keyEvent(expected, 123, 1);
173 client.sendKey(123, true);
174 expect(client._sock).to.have.sent(expected._sQ);
175 });
176
177 it('should send both a down and up event if the state is not specified', function () {
178 var expected = {_sQ: new Uint8Array(16), _sQlen: 0, flush: function () {}};
179 RFB.messages.keyEvent(expected, 123, 1);
180 RFB.messages.keyEvent(expected, 123, 0);
181 client.sendKey(123);
182 expect(client._sock).to.have.sent(expected._sQ);
183 });
184
185 it('should not send the key if we are not in a normal state', function () {
186 client._rfb_state = "broken";
187 client.sendKey(123);
188 expect(client._sock.flush).to.not.have.been.called;
189 });
190
191 it('should not send the key if we are set as view_only', function () {
192 client._view_only = true;
193 client.sendKey(123);
194 expect(client._sock.flush).to.not.have.been.called;
195 });
196 });
197
198 describe('#clipboardPasteFrom', function () {
199 beforeEach(function () {
200 client._sock = new Websock();
201 client._sock.open('ws://', 'binary');
202 client._sock._websocket._open();
203 sinon.spy(client._sock, 'flush');
204 client._rfb_state = "normal";
205 client._view_only = false;
206 });
207
208 it('should send the given text in a paste event', function () {
209 var expected = {_sQ: new Uint8Array(11), _sQlen: 0, flush: function () {}};
210 RFB.messages.clientCutText(expected, 'abc');
211 client.clipboardPasteFrom('abc');
212 expect(client._sock).to.have.sent(expected._sQ);
213 });
214
215 it('should not send the text if we are not in a normal state', function () {
216 client._rfb_state = "broken";
217 client.clipboardPasteFrom('abc');
218 expect(client._sock.flush).to.not.have.been.called;
219 });
220 });
221
222 describe("#requestDesktopSize", function () {
223 beforeEach(function() {
224 client._sock = new Websock();
225 client._sock.open('ws://', 'binary');
226 client._sock._websocket._open();
227 sinon.spy(client._sock, 'flush');
228 client._rfb_state = "normal";
229 client._view_only = false;
230 client._supportsSetDesktopSize = true;
231 });
232
233 it('should send the request with the given width and height', function () {
234 var expected = [251];
235 expected.push8(0); // padding
236 expected.push16(1); // width
237 expected.push16(2); // height
238 expected.push8(1); // number-of-screens
239 expected.push8(0); // padding before screen array
240 expected.push32(0); // id
241 expected.push16(0); // x-position
242 expected.push16(0); // y-position
243 expected.push16(1); // width
244 expected.push16(2); // height
245 expected.push32(0); // flags
246
247 client.requestDesktopSize(1, 2);
248 expect(client._sock).to.have.sent(new Uint8Array(expected));
249 });
250
251 it('should not send the request if the client has not recieved a ExtendedDesktopSize rectangle', function () {
252 client._supportsSetDesktopSize = false;
253 client.requestDesktopSize(1,2);
254 expect(client._sock.flush).to.not.have.been.called;
255 });
256
257 it('should not send the request if we are not in a normal state', function () {
258 client._rfb_state = "broken";
259 client.requestDesktopSize(1,2);
260 expect(client._sock.flush).to.not.have.been.called;
261 });
262 });
263
264 describe("XVP operations", function () {
265 beforeEach(function () {
266 client._sock = new Websock();
267 client._sock.open('ws://', 'binary');
268 client._sock._websocket._open();
269 sinon.spy(client._sock, 'flush');
270 client._rfb_state = "normal";
271 client._view_only = false;
272 client._rfb_xvp_ver = 1;
273 });
274
275 it('should send the shutdown signal on #xvpShutdown', function () {
276 client.xvpShutdown();
277 expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x02]));
278 });
279
280 it('should send the reboot signal on #xvpReboot', function () {
281 client.xvpReboot();
282 expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x03]));
283 });
284
285 it('should send the reset signal on #xvpReset', function () {
286 client.xvpReset();
287 expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x04]));
288 });
289
290 it('should support sending arbitrary XVP operations via #xvpOp', function () {
291 client.xvpOp(1, 7);
292 expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x07]));
293 });
294
295 it('should not send XVP operations with higher versions than we support', function () {
296 expect(client.xvpOp(2, 7)).to.be.false;
297 expect(client._sock.flush).to.not.have.been.called;
298 });
299 });
300 });
301
302 describe('Misc Internals', function () {
303 describe('#_updateState', function () {
304 var client;
305 beforeEach(function () {
306 this.clock = sinon.useFakeTimers();
307 client = make_rfb();
308 });
309
310 afterEach(function () {
311 this.clock.restore();
312 });
313
314 it('should clear the disconnect timer if the state is not disconnect', function () {
315 var spy = sinon.spy();
316 client._disconnTimer = setTimeout(spy, 50);
317 client._updateState('normal');
318 this.clock.tick(51);
319 expect(spy).to.not.have.been.called;
320 expect(client._disconnTimer).to.be.null;
321 });
322 });
323 });
324
325 describe('Page States', function () {
326 describe('loaded', function () {
327 var client;
328 beforeEach(function () { client = make_rfb(); });
329
330 it('should close any open WebSocket connection', function () {
331 sinon.spy(client._sock, 'close');
332 client._updateState('loaded');
333 expect(client._sock.close).to.have.been.calledOnce;
334 });
335 });
336
337 describe('disconnected', function () {
338 var client;
339 beforeEach(function () { client = make_rfb(); });
340
341 it('should close any open WebSocket connection', function () {
342 sinon.spy(client._sock, 'close');
343 client._updateState('disconnected');
344 expect(client._sock.close).to.have.been.calledOnce;
345 });
346 });
347
348 describe('connect', function () {
349 var client;
350 beforeEach(function () { client = make_rfb(); });
351
352 it('should reset the variable states', function () {
353 sinon.spy(client, '_init_vars');
354 client._updateState('connect');
355 expect(client._init_vars).to.have.been.calledOnce;
356 });
357
358 it('should actually connect to the websocket', function () {
359 sinon.spy(client._sock, 'open');
360 client._updateState('connect');
361 expect(client._sock.open).to.have.been.calledOnce;
362 });
363
364 it('should use wss:// to connect if encryption is enabled', function () {
365 sinon.spy(client._sock, 'open');
366 client.set_encrypt(true);
367 client._updateState('connect');
368 expect(client._sock.open.args[0][0]).to.contain('wss://');
369 });
370
371 it('should use ws:// to connect if encryption is not enabled', function () {
372 sinon.spy(client._sock, 'open');
373 client.set_encrypt(true);
374 client._updateState('connect');
375 expect(client._sock.open.args[0][0]).to.contain('wss://');
376 });
377
378 it('should use a uri with the host, port, and path specified to connect', function () {
379 sinon.spy(client._sock, 'open');
380 client.set_encrypt(false);
381 client._rfb_host = 'HOST';
382 client._rfb_port = 8675;
383 client._rfb_path = 'PATH';
384 client._updateState('connect');
385 expect(client._sock.open).to.have.been.calledWith('ws://HOST:8675/PATH');
386 });
387
388 it('should attempt to close the websocket before we open an new one', function () {
389 sinon.spy(client._sock, 'close');
390 client._updateState('connect');
391 expect(client._sock.close).to.have.been.calledOnce;
392 });
393 });
394
395 describe('disconnect', function () {
396 var client;
397 beforeEach(function () {
398 this.clock = sinon.useFakeTimers();
399 client = make_rfb();
400 client.connect('host', 8675);
401 });
402
403 afterEach(function () {
404 this.clock.restore();
405 });
406
407 it('should fail if we do not call Websock.onclose within the disconnection timeout', function () {
408 client._sock._websocket.close = function () {}; // explicitly don't call onclose
409 client._updateState('disconnect');
410 this.clock.tick(client.get_disconnectTimeout() * 1000);
411 expect(client._rfb_state).to.equal('failed');
412 });
413
414 it('should not fail if Websock.onclose gets called within the disconnection timeout', function () {
415 client._updateState('disconnect');
416 this.clock.tick(client.get_disconnectTimeout() * 500);
417 client._sock._websocket.close();
418 this.clock.tick(client.get_disconnectTimeout() * 500 + 1);
419 expect(client._rfb_state).to.equal('disconnected');
420 });
421
422 it('should close the WebSocket connection', function () {
423 sinon.spy(client._sock, 'close');
424 client._updateState('disconnect');
425 expect(client._sock.close).to.have.been.calledTwice; // once on loaded, once on disconnect
426 });
427 });
428
429 describe('failed', function () {
430 var client;
431 beforeEach(function () {
432 this.clock = sinon.useFakeTimers();
433 client = make_rfb();
434 client.connect('host', 8675);
435 });
436
437 afterEach(function () {
438 this.clock.restore();
439 });
440
441 it('should close the WebSocket connection', function () {
442 sinon.spy(client._sock, 'close');
443 client._updateState('failed');
444 expect(client._sock.close).to.have.been.called;
445 });
446
447 it('should transition to disconnected but stay in failed state', function () {
448 client.set_onUpdateState(sinon.spy());
449 client._updateState('failed');
450 this.clock.tick(50);
451 expect(client._rfb_state).to.equal('failed');
452
453 var onUpdateState = client.get_onUpdateState();
454 expect(onUpdateState).to.have.been.called;
455 // it should be specifically the last call
456 expect(onUpdateState.args[onUpdateState.args.length - 1][1]).to.equal('disconnected');
457 expect(onUpdateState.args[onUpdateState.args.length - 1][2]).to.equal('failed');
458 });
459
460 });
461
462 describe('fatal', function () {
463 var client;
464 beforeEach(function () { client = make_rfb(); });
465
466 it('should close any open WebSocket connection', function () {
467 sinon.spy(client._sock, 'close');
468 client._updateState('fatal');
469 expect(client._sock.close).to.have.been.calledOnce;
470 });
471 });
472
473 // NB(directxman12): Normal does *nothing* in updateState
474 });
475
476 describe('Protocol Initialization States', function () {
477 describe('ProtocolVersion', function () {
478 beforeEach(function () {
479 this.clock = sinon.useFakeTimers();
480 });
481
482 afterEach(function () {
483 this.clock.restore();
484 });
485
486 function send_ver (ver, client) {
487 var arr = new Uint8Array(12);
488 for (var i = 0; i < ver.length; i++) {
489 arr[i+4] = ver.charCodeAt(i);
490 }
491 arr[0] = 'R'; arr[1] = 'F'; arr[2] = 'B'; arr[3] = ' ';
492 arr[11] = '\n';
493 client._sock._websocket._receive_data(arr);
494 }
495
496 describe('version parsing', function () {
497 var client;
498 beforeEach(function () {
499 client = make_rfb();
500 client.connect('host', 8675);
501 client._sock._websocket._open();
502 });
503
504 it('should interpret version 000.000 as a repeater', function () {
505 client._repeaterID = '\x01\x02\x03\x04\x05';
506 send_ver('000.000', client);
507 expect(client._rfb_version).to.equal(0);
508
509 var sent_data = client._sock._websocket._get_sent_data();
510 expect(new Uint8Array(sent_data.buffer, 0, 5)).to.array.equal(new Uint8Array([1, 2, 3, 4, 5]));
511 });
512
513 it('should interpret version 003.003 as version 3.3', function () {
514 send_ver('003.003', client);
515 expect(client._rfb_version).to.equal(3.3);
516 });
517
518 it('should interpret version 003.006 as version 3.3', function () {
519 send_ver('003.006', client);
520 expect(client._rfb_version).to.equal(3.3);
521 });
522
523 it('should interpret version 003.889 as version 3.3', function () {
524 send_ver('003.889', client);
525 expect(client._rfb_version).to.equal(3.3);
526 });
527
528 it('should interpret version 003.007 as version 3.7', function () {
529 send_ver('003.007', client);
530 expect(client._rfb_version).to.equal(3.7);
531 });
532
533 it('should interpret version 003.008 as version 3.8', function () {
534 send_ver('003.008', client);
535 expect(client._rfb_version).to.equal(3.8);
536 });
537
538 it('should interpret version 004.000 as version 3.8', function () {
539 send_ver('004.000', client);
540 expect(client._rfb_version).to.equal(3.8);
541 });
542
543 it('should interpret version 004.001 as version 3.8', function () {
544 send_ver('004.001', client);
545 expect(client._rfb_version).to.equal(3.8);
546 });
547
548 it('should fail on an invalid version', function () {
549 send_ver('002.000', client);
550 expect(client._rfb_state).to.equal('failed');
551 });
552 });
553
554 var client;
555 beforeEach(function () {
556 client = make_rfb();
557 client.connect('host', 8675);
558 client._sock._websocket._open();
559 });
560
561 it('should handle two step repeater negotiation', function () {
562 client._repeaterID = '\x01\x02\x03\x04\x05';
563
564 send_ver('000.000', client);
565 expect(client._rfb_version).to.equal(0);
566 var sent_data = client._sock._websocket._get_sent_data();
567 expect(new Uint8Array(sent_data.buffer, 0, 5)).to.array.equal(new Uint8Array([1, 2, 3, 4, 5]));
568 expect(sent_data).to.have.length(250);
569
570 send_ver('003.008', client);
571 expect(client._rfb_version).to.equal(3.8);
572 });
573
574 it('should send back the interpreted version', function () {
575 send_ver('004.000', client);
576
577 var expected_str = 'RFB 003.008\n';
578 var expected = [];
579 for (var i = 0; i < expected_str.length; i++) {
580 expected[i] = expected_str.charCodeAt(i);
581 }
582
583 expect(client._sock).to.have.sent(new Uint8Array(expected));
584 });
585
586 it('should transition to the Security state on successful negotiation', function () {
587 send_ver('003.008', client);
588 expect(client._rfb_state).to.equal('Security');
589 });
590 });
591
592 describe('Security', function () {
593 var client;
594
595 beforeEach(function () {
596 client = make_rfb();
597 client.connect('host', 8675);
598 client._sock._websocket._open();
599 client._rfb_state = 'Security';
600 });
601
602 it('should simply receive the auth scheme when for versions < 3.7', function () {
603 client._rfb_version = 3.6;
604 var auth_scheme_raw = [1, 2, 3, 4];
605 var auth_scheme = (auth_scheme_raw[0] << 24) + (auth_scheme_raw[1] << 16) +
606 (auth_scheme_raw[2] << 8) + auth_scheme_raw[3];
607 client._sock._websocket._receive_data(auth_scheme_raw);
608 expect(client._rfb_auth_scheme).to.equal(auth_scheme);
609 });
610
611 it('should choose for the most prefered scheme possible for versions >= 3.7', function () {
612 client._rfb_version = 3.7;
613 var auth_schemes = [2, 1, 2];
614 client._sock._websocket._receive_data(auth_schemes);
615 expect(client._rfb_auth_scheme).to.equal(2);
616 expect(client._sock).to.have.sent(new Uint8Array([2]));
617 });
618
619 it('should fail if there are no supported schemes for versions >= 3.7', function () {
620 client._rfb_version = 3.7;
621 var auth_schemes = [1, 32];
622 client._sock._websocket._receive_data(auth_schemes);
623 expect(client._rfb_state).to.equal('failed');
624 });
625
626 it('should fail with the appropriate message if no types are sent for versions >= 3.7', function () {
627 client._rfb_version = 3.7;
628 var failure_data = [0, 0, 0, 0, 6, 119, 104, 111, 111, 112, 115];
629 sinon.spy(client, '_fail');
630 client._sock._websocket._receive_data(failure_data);
631
632 expect(client._fail).to.have.been.calledTwice;
633 expect(client._fail).to.have.been.calledWith('Security failure: whoops');
634 });
635
636 it('should transition to the Authentication state and continue on successful negotiation', function () {
637 client._rfb_version = 3.7;
638 var auth_schemes = [1, 1];
639 client._negotiate_authentication = sinon.spy();
640 client._sock._websocket._receive_data(auth_schemes);
641 expect(client._rfb_state).to.equal('Authentication');
642 expect(client._negotiate_authentication).to.have.been.calledOnce;
643 });
644 });
645
646 describe('Authentication', function () {
647 var client;
648
649 beforeEach(function () {
650 client = make_rfb();
651 client.connect('host', 8675);
652 client._sock._websocket._open();
653 client._rfb_state = 'Security';
654 });
655
656 function send_security(type, cl) {
657 cl._sock._websocket._receive_data(new Uint8Array([1, type]));
658 }
659
660 it('should fail on auth scheme 0 (pre 3.7) with the given message', function () {
661 client._rfb_version = 3.6;
662 var err_msg = "Whoopsies";
663 var data = [0, 0, 0, 0];
664 var err_len = err_msg.length;
665 data.push32(err_len);
666 for (var i = 0; i < err_len; i++) {
667 data.push(err_msg.charCodeAt(i));
668 }
669
670 sinon.spy(client, '_fail');
671 client._sock._websocket._receive_data(new Uint8Array(data));
672 expect(client._rfb_state).to.equal('failed');
673 expect(client._fail).to.have.been.calledWith('Auth failure: Whoopsies');
674 });
675
676 it('should transition straight to SecurityResult on "no auth" (1) for versions >= 3.8', function () {
677 client._rfb_version = 3.8;
678 send_security(1, client);
679 expect(client._rfb_state).to.equal('SecurityResult');
680 });
681
682 it('should transition straight to ClientInitialisation on "no auth" for versions < 3.8', function () {
683 client._rfb_version = 3.7;
684 sinon.spy(client, '_updateState');
685 send_security(1, client);
686 expect(client._updateState).to.have.been.calledWith('ClientInitialisation');
687 expect(client._rfb_state).to.equal('ServerInitialisation');
688 });
689
690 it('should fail on an unknown auth scheme', function () {
691 client._rfb_version = 3.8;
692 send_security(57, client);
693 expect(client._rfb_state).to.equal('failed');
694 });
695
696 describe('VNC Authentication (type 2) Handler', function () {
697 var client;
698
699 beforeEach(function () {
700 client = make_rfb();
701 client.connect('host', 8675);
702 client._sock._websocket._open();
703 client._rfb_state = 'Security';
704 client._rfb_version = 3.8;
705 });
706
707 it('should transition to the "password" state if missing a password', function () {
708 send_security(2, client);
709 expect(client._rfb_state).to.equal('password');
710 });
711
712 it('should encrypt the password with DES and then send it back', function () {
713 client._rfb_password = 'passwd';
714 send_security(2, client);
715 client._sock._websocket._get_sent_data(); // skip the choice of auth reply
716
717 var challenge = [];
718 for (var i = 0; i < 16; i++) { challenge[i] = i; }
719 client._sock._websocket._receive_data(new Uint8Array(challenge));
720
721 var des_pass = RFB.genDES('passwd', challenge);
722 expect(client._sock).to.have.sent(new Uint8Array(des_pass));
723 });
724
725 it('should transition to SecurityResult immediately after sending the password', function () {
726 client._rfb_password = 'passwd';
727 send_security(2, client);
728
729 var challenge = [];
730 for (var i = 0; i < 16; i++) { challenge[i] = i; }
731 client._sock._websocket._receive_data(new Uint8Array(challenge));
732
733 expect(client._rfb_state).to.equal('SecurityResult');
734 });
735 });
736
737 describe('XVP Authentication (type 22) Handler', function () {
738 var client;
739
740 beforeEach(function () {
741 client = make_rfb();
742 client.connect('host', 8675);
743 client._sock._websocket._open();
744 client._rfb_state = 'Security';
745 client._rfb_version = 3.8;
746 });
747
748 it('should fall through to standard VNC authentication upon completion', function () {
749 client.set_xvp_password_sep('#');
750 client._rfb_password = 'user#target#password';
751 client._negotiate_std_vnc_auth = sinon.spy();
752 send_security(22, client);
753 expect(client._negotiate_std_vnc_auth).to.have.been.calledOnce;
754 });
755
756 it('should transition to the "password" state if the passwords is missing', function() {
757 send_security(22, client);
758 expect(client._rfb_state).to.equal('password');
759 });
760
761 it('should transition to the "password" state if the passwords is improperly formatted', function() {
762 client._rfb_password = 'user@target';
763 send_security(22, client);
764 expect(client._rfb_state).to.equal('password');
765 });
766
767 it('should split the password, send the first two parts, and pass on the last part', function () {
768 client.set_xvp_password_sep('#');
769 client._rfb_password = 'user#target#password';
770 client._negotiate_std_vnc_auth = sinon.spy();
771
772 send_security(22, client);
773
774 expect(client._rfb_password).to.equal('password');
775
776 var expected = [22, 4, 6]; // auth selection, len user, len target
777 for (var i = 0; i < 10; i++) { expected[i+3] = 'usertarget'.charCodeAt(i); }
778
779 expect(client._sock).to.have.sent(new Uint8Array(expected));
780 });
781 });
782
783 describe('TightVNC Authentication (type 16) Handler', function () {
784 var client;
785
786 beforeEach(function () {
787 client = make_rfb();
788 client.connect('host', 8675);
789 client._sock._websocket._open();
790 client._rfb_state = 'Security';
791 client._rfb_version = 3.8;
792 send_security(16, client);
793 client._sock._websocket._get_sent_data(); // skip the security reply
794 });
795
796 function send_num_str_pairs(pairs, client) {
797 var pairs_len = pairs.length;
798 var data = [];
799 data.push32(pairs_len);
800
801 for (var i = 0; i < pairs_len; i++) {
802 data.push32(pairs[i][0]);
803 var j;
804 for (j = 0; j < 4; j++) {
805 data.push(pairs[i][1].charCodeAt(j));
806 }
807 for (j = 0; j < 8; j++) {
808 data.push(pairs[i][2].charCodeAt(j));
809 }
810 }
811
812 client._sock._websocket._receive_data(new Uint8Array(data));
813 }
814
815 it('should skip tunnel negotiation if no tunnels are requested', function () {
816 client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0]));
817 expect(client._rfb_tightvnc).to.be.true;
818 });
819
820 it('should fail if no supported tunnels are listed', function () {
821 send_num_str_pairs([[123, 'OTHR', 'SOMETHNG']], client);
822 expect(client._rfb_state).to.equal('failed');
823 });
824
825 it('should choose the notunnel tunnel type', function () {
826 send_num_str_pairs([[0, 'TGHT', 'NOTUNNEL'], [123, 'OTHR', 'SOMETHNG']], client);
827 expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 0]));
828 });
829
830 it('should continue to sub-auth negotiation after tunnel negotiation', function () {
831 send_num_str_pairs([[0, 'TGHT', 'NOTUNNEL']], client);
832 client._sock._websocket._get_sent_data(); // skip the tunnel choice here
833 send_num_str_pairs([[1, 'STDV', 'NOAUTH__']], client);
834 expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 1]));
835 expect(client._rfb_state).to.equal('SecurityResult');
836 });
837
838 /*it('should attempt to use VNC auth over no auth when possible', function () {
839 client._rfb_tightvnc = true;
840 client._negotiate_std_vnc_auth = sinon.spy();
841 send_num_str_pairs([[1, 'STDV', 'NOAUTH__'], [2, 'STDV', 'VNCAUTH_']], client);
842 expect(client._sock).to.have.sent([0, 0, 0, 1]);
843 expect(client._negotiate_std_vnc_auth).to.have.been.calledOnce;
844 expect(client._rfb_auth_scheme).to.equal(2);
845 });*/ // while this would make sense, the original code doesn't actually do this
846
847 it('should accept the "no auth" auth type and transition to SecurityResult', function () {
848 client._rfb_tightvnc = true;
849 send_num_str_pairs([[1, 'STDV', 'NOAUTH__']], client);
850 expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 1]));
851 expect(client._rfb_state).to.equal('SecurityResult');
852 });
853
854 it('should accept VNC authentication and transition to that', function () {
855 client._rfb_tightvnc = true;
856 client._negotiate_std_vnc_auth = sinon.spy();
857 send_num_str_pairs([[2, 'STDV', 'VNCAUTH__']], client);
858 expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 2]));
859 expect(client._negotiate_std_vnc_auth).to.have.been.calledOnce;
860 expect(client._rfb_auth_scheme).to.equal(2);
861 });
862
863 it('should fail if there are no supported auth types', function () {
864 client._rfb_tightvnc = true;
865 send_num_str_pairs([[23, 'stdv', 'badval__']], client);
866 expect(client._rfb_state).to.equal('failed');
867 });
868 });
869 });
870
871 describe('SecurityResult', function () {
872 var client;
873
874 beforeEach(function () {
875 client = make_rfb();
876 client.connect('host', 8675);
877 client._sock._websocket._open();
878 client._rfb_state = 'SecurityResult';
879 });
880
881 it('should fall through to ClientInitialisation on a response code of 0', function () {
882 client._updateState = sinon.spy();
883 client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0]));
884 expect(client._updateState).to.have.been.calledOnce;
885 expect(client._updateState).to.have.been.calledWith('ClientInitialisation');
886 });
887
888 it('should fail on an error code of 1 with the given message for versions >= 3.8', function () {
889 client._rfb_version = 3.8;
890 sinon.spy(client, '_fail');
891 var failure_data = [0, 0, 0, 1, 0, 0, 0, 6, 119, 104, 111, 111, 112, 115];
892 client._sock._websocket._receive_data(new Uint8Array(failure_data));
893 expect(client._rfb_state).to.equal('failed');
894 expect(client._fail).to.have.been.calledWith('whoops');
895 });
896
897 it('should fail on an error code of 1 with a standard message for version < 3.8', function () {
898 client._rfb_version = 3.7;
899 client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 1]));
900 expect(client._rfb_state).to.equal('failed');
901 });
902 });
903
904 describe('ClientInitialisation', function () {
905 var client;
906
907 beforeEach(function () {
908 client = make_rfb();
909 client.connect('host', 8675);
910 client._sock._websocket._open();
911 client._rfb_state = 'SecurityResult';
912 });
913
914 it('should transition to the ServerInitialisation state', function () {
915 client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0]));
916 expect(client._rfb_state).to.equal('ServerInitialisation');
917 });
918
919 it('should send 1 if we are in shared mode', function () {
920 client.set_shared(true);
921 client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0]));
922 expect(client._sock).to.have.sent(new Uint8Array([1]));
923 });
924
925 it('should send 0 if we are not in shared mode', function () {
926 client.set_shared(false);
927 client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0]));
928 expect(client._sock).to.have.sent(new Uint8Array([0]));
929 });
930 });
931
932 describe('ServerInitialisation', function () {
933 var client;
934
935 beforeEach(function () {
936 client = make_rfb();
937 client.connect('host', 8675);
938 client._sock._websocket._open();
939 client._rfb_state = 'ServerInitialisation';
940 });
941
942 function send_server_init(opts, client) {
943 var full_opts = { width: 10, height: 12, bpp: 24, depth: 24, big_endian: 0,
944 true_color: 1, red_max: 255, green_max: 255, blue_max: 255,
945 red_shift: 16, green_shift: 8, blue_shift: 0, name: 'a name' };
946 for (var opt in opts) {
947 full_opts[opt] = opts[opt];
948 }
949 var data = [];
950
951 data.push16(full_opts.width);
952 data.push16(full_opts.height);
953
954 data.push(full_opts.bpp);
955 data.push(full_opts.depth);
956 data.push(full_opts.big_endian);
957 data.push(full_opts.true_color);
958
959 data.push16(full_opts.red_max);
960 data.push16(full_opts.green_max);
961 data.push16(full_opts.blue_max);
962 data.push8(full_opts.red_shift);
963 data.push8(full_opts.green_shift);
964 data.push8(full_opts.blue_shift);
965
966 // padding
967 data.push8(0);
968 data.push8(0);
969 data.push8(0);
970
971 client._sock._websocket._receive_data(new Uint8Array(data));
972
973 var name_data = [];
974 name_data.push32(full_opts.name.length);
975 for (var i = 0; i < full_opts.name.length; i++) {
976 name_data.push(full_opts.name.charCodeAt(i));
977 }
978 client._sock._websocket._receive_data(new Uint8Array(name_data));
979 }
980
981 it('should set the framebuffer width and height', function () {
982 send_server_init({ width: 32, height: 84 }, client);
983 expect(client._fb_width).to.equal(32);
984 expect(client._fb_height).to.equal(84);
985 });
986
987 // NB(sross): we just warn, not fail, for endian-ness and shifts, so we don't test them
988
989 it('should set the framebuffer name and call the callback', function () {
990 client.set_onDesktopName(sinon.spy());
991 send_server_init({ name: 'some name' }, client);
992
993 var spy = client.get_onDesktopName();
994 expect(client._fb_name).to.equal('some name');
995 expect(spy).to.have.been.calledOnce;
996 expect(spy.args[0][1]).to.equal('some name');
997 });
998
999 it('should handle the extended init message of the tight encoding', function () {
1000 // NB(sross): we don't actually do anything with it, so just test that we can
1001 // read it w/o throwing an error
1002 client._rfb_tightvnc = true;
1003 send_server_init({}, client);
1004
1005 var tight_data = [];
1006 tight_data.push16(1);
1007 tight_data.push16(2);
1008 tight_data.push16(3);
1009 tight_data.push16(0);
1010 for (var i = 0; i < 16 + 32 + 48; i++) {
1011 tight_data.push(i);
1012 }
1013 client._sock._websocket._receive_data(tight_data);
1014
1015 expect(client._rfb_state).to.equal('normal');
1016 });
1017
1018 it('should set the true color mode on the display to the configuration variable', function () {
1019 client.set_true_color(false);
1020 sinon.spy(client._display, 'set_true_color');
1021 send_server_init({ true_color: 1 }, client);
1022 expect(client._display.set_true_color).to.have.been.calledOnce;
1023 expect(client._display.set_true_color).to.have.been.calledWith(false);
1024 });
1025
1026 it('should call the resize callback and resize the display', function () {
1027 client.set_onFBResize(sinon.spy());
1028 sinon.spy(client._display, 'resize');
1029 send_server_init({ width: 27, height: 32 }, client);
1030
1031 var spy = client.get_onFBResize();
1032 expect(client._display.resize).to.have.been.calledOnce;
1033 expect(client._display.resize).to.have.been.calledWith(27, 32);
1034 expect(spy).to.have.been.calledOnce;
1035 expect(spy.args[0][1]).to.equal(27);
1036 expect(spy.args[0][2]).to.equal(32);
1037 });
1038
1039 it('should grab the mouse and keyboard', function () {
1040 sinon.spy(client._keyboard, 'grab');
1041 sinon.spy(client._mouse, 'grab');
1042 send_server_init({}, client);
1043 expect(client._keyboard.grab).to.have.been.calledOnce;
1044 expect(client._mouse.grab).to.have.been.calledOnce;
1045 });
1046
1047 it('should set the BPP and depth to 4 and 3 respectively if in true color mode', function () {
1048 client.set_true_color(true);
1049 send_server_init({}, client);
1050 expect(client._fb_Bpp).to.equal(4);
1051 expect(client._fb_depth).to.equal(3);
1052 });
1053
1054 it('should set the BPP and depth to 1 and 1 respectively if not in true color mode', function () {
1055 client.set_true_color(false);
1056 send_server_init({}, client);
1057 expect(client._fb_Bpp).to.equal(1);
1058 expect(client._fb_depth).to.equal(1);
1059 });
1060
1061 // TODO(directxman12): test the various options in this configuration matrix
1062 it('should reply with the pixel format, client encodings, and initial update request', function () {
1063 client.set_true_color(true);
1064 client.set_local_cursor(false);
1065 // we skip the cursor encoding
1066 var expected = {_sQ: new Uint8Array(34 + 4 * (client._encodings.length - 1)),
1067 _sQlen: 0,
1068 flush: function () {}};
1069 RFB.messages.pixelFormat(expected, 4, 3, true);
1070 RFB.messages.clientEncodings(expected, client._encodings, false, true);
1071 RFB.messages.fbUpdateRequest(expected, false, 0, 0, 27, 32);
1072
1073 send_server_init({ width: 27, height: 32 }, client);
1074 expect(client._sock).to.have.sent(expected._sQ);
1075 });
1076
1077 it('should transition to the "normal" state', function () {
1078 send_server_init({}, client);
1079 expect(client._rfb_state).to.equal('normal');
1080 });
1081 });
1082 });
1083
1084 describe('Protocol Message Processing After Completing Initialization', function () {
1085 var client;
1086
1087 beforeEach(function () {
1088 client = make_rfb();
1089 client.connect('host', 8675);
1090 client._sock._websocket._open();
1091 client._rfb_state = 'normal';
1092 client._fb_name = 'some device';
1093 client._fb_width = 640;
1094 client._fb_height = 20;
1095 });
1096
1097 describe('Framebuffer Update Handling', function () {
1098 var client;
1099
1100 beforeEach(function () {
1101 client = make_rfb();
1102 client.connect('host', 8675);
1103 client._sock._websocket._open();
1104 client._rfb_state = 'normal';
1105 client._fb_name = 'some device';
1106 client._fb_width = 640;
1107 client._fb_height = 20;
1108 });
1109
1110 var target_data_arr = [
1111 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
1112 0x00, 0xff, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
1113 0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255,
1114 0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255
1115 ];
1116 var target_data;
1117
1118 var target_data_check_arr = [
1119 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
1120 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
1121 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
1122 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
1123 ];
1124 var target_data_check;
1125
1126 before(function () {
1127 // NB(directxman12): PhantomJS 1.x doesn't implement Uint8ClampedArray
1128 target_data = new Uint8Array(target_data_arr);
1129 target_data_check = new Uint8Array(target_data_check_arr);
1130 });
1131
1132 function send_fbu_msg (rect_info, rect_data, client, rect_cnt) {
1133 var data = [];
1134
1135 if (!rect_cnt || rect_cnt > -1) {
1136 // header
1137 data.push(0); // msg type
1138 data.push(0); // padding
1139 data.push16(rect_cnt || rect_data.length);
1140 }
1141
1142 for (var i = 0; i < rect_data.length; i++) {
1143 if (rect_info[i]) {
1144 data.push16(rect_info[i].x);
1145 data.push16(rect_info[i].y);
1146 data.push16(rect_info[i].width);
1147 data.push16(rect_info[i].height);
1148 data.push32(rect_info[i].encoding);
1149 }
1150 data = data.concat(rect_data[i]);
1151 }
1152
1153 client._sock._websocket._receive_data(new Uint8Array(data));
1154 }
1155
1156 it('should send an update request if there is sufficient data', function () {
1157 var expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: function() {}};
1158 RFB.messages.fbUpdateRequest(expected_msg, false, 0, 0, 240, 20);
1159
1160 client._framebufferUpdate = function () { return true; };
1161 client._sock._websocket._receive_data(new Uint8Array([0]));
1162
1163 expect(client._sock).to.have.sent(expected_msg._sQ);
1164 });
1165
1166 it('should not send an update request if we need more data', function () {
1167 client._sock._websocket._receive_data(new Uint8Array([0]));
1168 expect(client._sock._websocket._get_sent_data()).to.have.length(0);
1169 });
1170
1171 it('should resume receiving an update if we previously did not have enough data', function () {
1172 var expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: function() {}};
1173 RFB.messages.fbUpdateRequest(expected_msg, false, 0, 0, 240, 20);
1174
1175 // just enough to set FBU.rects
1176 client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 3]));
1177 expect(client._sock._websocket._get_sent_data()).to.have.length(0);
1178
1179 client._framebufferUpdate = function () { return true; }; // we magically have enough data
1180 // 247 should *not* be used as the message type here
1181 client._sock._websocket._receive_data(new Uint8Array([247]));
1182 expect(client._sock).to.have.sent(expected_msg._sQ);
1183 });
1184
1185 it('should send a request for both clean and dirty areas', function () {
1186 var expected_msg = {_sQ: new Uint8Array(20), _sQlen: 0, flush: function() {}};
1187 var expected_cdr = { cleanBox: { x: 0, y: 0, w: 120, h: 20 },
1188 dirtyBoxes: [ { x: 120, y: 0, w: 120, h: 20 } ] };
1189
1190 RFB.messages.fbUpdateRequest(expected_msg, true, 0, 0, 120, 20);
1191 RFB.messages.fbUpdateRequest(expected_msg, false, 120, 0, 120, 20);
1192
1193 client._framebufferUpdate = function () { return true; };
1194 client._display.getCleanDirtyReset = function () { return expected_cdr; };
1195 client._sock._websocket._receive_data(new Uint8Array([0]));
1196
1197 expect(client._sock).to.have.sent(expected_msg._sQ);
1198 });
1199
1200 it('should only request non-incremental rects in continuous updates mode', function () {
1201 var expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: function() {}};
1202 var expected_cdr = { cleanBox: { x: 0, y: 0, w: 120, h: 20 },
1203 dirtyBoxes: [ { x: 120, y: 0, w: 120, h: 20 } ] };
1204
1205 RFB.messages.fbUpdateRequest(expected_msg, false, 120, 0, 120, 20);
1206
1207 client._enabledContinuousUpdates = true;
1208 client._framebufferUpdate = function () { return true; };
1209 client._display.getCleanDirtyReset = function () { return expected_cdr; };
1210 client._sock._websocket._receive_data(new Uint8Array([0]));
1211
1212 expect(client._sock).to.have.sent(expected_msg._sQ);
1213 });
1214
1215 it('should not send a request in continuous updates mode when clean', function () {
1216 var expected_cdr = { cleanBox: { x: 0, y: 0, w: 240, h: 20 },
1217 dirtyBoxes: [] };
1218
1219 client._enabledContinuousUpdates = true;
1220 client._framebufferUpdate = function () { return true; };
1221 client._display.getCleanDirtyReset = function () { return expected_cdr; };
1222 client._sock._websocket._receive_data(new Uint8Array([0]));
1223
1224 expect(client._sock._websocket._get_sent_data()).to.have.length(0);
1225 });
1226
1227 it('should parse out information from a header before any actual data comes in', function () {
1228 client.set_onFBUReceive(sinon.spy());
1229 var rect_info = { x: 8, y: 11, width: 27, height: 32, encoding: 0x02, encodingName: 'RRE' };
1230 send_fbu_msg([rect_info], [[]], client);
1231
1232 var spy = client.get_onFBUReceive();
1233 expect(spy).to.have.been.calledOnce;
1234 expect(spy).to.have.been.calledWith(sinon.match.any, rect_info);
1235 });
1236
1237 it('should fire onFBUComplete when the update is complete', function () {
1238 client.set_onFBUComplete(sinon.spy());
1239 var rect_info = { x: 8, y: 11, width: 27, height: 32, encoding: -224, encodingName: 'last_rect' };
1240 send_fbu_msg([rect_info], [[]], client); // last_rect
1241
1242 var spy = client.get_onFBUComplete();
1243 expect(spy).to.have.been.calledOnce;
1244 expect(spy).to.have.been.calledWith(sinon.match.any, rect_info);
1245 });
1246
1247 it('should not fire onFBUComplete if we have not finished processing the update', function () {
1248 client.set_onFBUComplete(sinon.spy());
1249 var rect_info = { x: 8, y: 11, width: 27, height: 32, encoding: 0x00, encodingName: 'RAW' };
1250 send_fbu_msg([rect_info], [[]], client);
1251 expect(client.get_onFBUComplete()).to.not.have.been.called;
1252 });
1253
1254 it('should call the appropriate encoding handler', function () {
1255 client._encHandlers[0x02] = sinon.spy();
1256 var rect_info = { x: 8, y: 11, width: 27, height: 32, encoding: 0x02 };
1257 send_fbu_msg([rect_info], [[]], client);
1258 expect(client._encHandlers[0x02]).to.have.been.calledOnce;
1259 });
1260
1261 it('should fail on an unsupported encoding', function () {
1262 client.set_onFBUReceive(sinon.spy());
1263 var rect_info = { x: 8, y: 11, width: 27, height: 32, encoding: 234 };
1264 send_fbu_msg([rect_info], [[]], client);
1265 expect(client._rfb_state).to.equal('failed');
1266 });
1267
1268 it('should be able to pause and resume receiving rects if not enought data', function () {
1269 // seed some initial data to copy
1270 client._fb_width = 4;
1271 client._fb_height = 4;
1272 client._display.resize(4, 4);
1273 var initial_data = client._display._drawCtx.createImageData(4, 2);
1274 var initial_data_arr = target_data_check_arr.slice(0, 32);
1275 for (var i = 0; i < 32; i++) { initial_data.data[i] = initial_data_arr[i]; }
1276 client._display._drawCtx.putImageData(initial_data, 0, 0);
1277
1278 var info = [{ x: 0, y: 2, width: 2, height: 2, encoding: 0x01},
1279 { x: 2, y: 2, width: 2, height: 2, encoding: 0x01}];
1280 // data says [{ old_x: 0, old_y: 0 }, { old_x: 0, old_y: 0 }]
1281 var rects = [[0, 2, 0, 0], [0, 0, 0, 0]];
1282 send_fbu_msg([info[0]], [rects[0]], client, 2);
1283 send_fbu_msg([info[1]], [rects[1]], client, -1);
1284 expect(client._display).to.have.displayed(target_data_check);
1285 });
1286
1287 describe('Message Encoding Handlers', function () {
1288 var client;
1289
1290 beforeEach(function () {
1291 client = make_rfb();
1292 client.connect('host', 8675);
1293 client._sock._websocket._open();
1294 client._rfb_state = 'normal';
1295 client._fb_name = 'some device';
1296 // a really small frame
1297 client._fb_width = 4;
1298 client._fb_height = 4;
1299 client._display._fb_width = 4;
1300 client._display._fb_height = 4;
1301 client._display._viewportLoc.w = 4;
1302 client._display._viewportLoc.h = 4;
1303 client._fb_Bpp = 4;
1304 });
1305
1306 it('should handle the RAW encoding', function () {
1307 var info = [{ x: 0, y: 0, width: 2, height: 2, encoding: 0x00 },
1308 { x: 2, y: 0, width: 2, height: 2, encoding: 0x00 },
1309 { x: 0, y: 2, width: 4, height: 1, encoding: 0x00 },
1310 { x: 0, y: 3, width: 4, height: 1, encoding: 0x00 }];
1311 // data is in bgrx
1312 var rects = [
1313 [0x00, 0x00, 0xff, 0, 0x00, 0xff, 0x00, 0, 0x00, 0xff, 0x00, 0, 0x00, 0x00, 0xff, 0],
1314 [0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0],
1315 [0xff, 0x00, 0xee, 0, 0xff, 0xee, 0x00, 0, 0xff, 0xee, 0xaa, 0, 0xff, 0xee, 0xab, 0],
1316 [0xff, 0x00, 0xee, 0, 0xff, 0xee, 0x00, 0, 0xff, 0xee, 0xaa, 0, 0xff, 0xee, 0xab, 0]];
1317 send_fbu_msg(info, rects, client);
1318 expect(client._display).to.have.displayed(target_data);
1319 });
1320
1321 it('should handle the COPYRECT encoding', function () {
1322 // seed some initial data to copy
1323 var initial_data = client._display._drawCtx.createImageData(4, 2);
1324 var initial_data_arr = target_data_check_arr.slice(0, 32);
1325 for (var i = 0; i < 32; i++) { initial_data.data[i] = initial_data_arr[i]; }
1326 client._display._drawCtx.putImageData(initial_data, 0, 0);
1327
1328 var info = [{ x: 0, y: 2, width: 2, height: 2, encoding: 0x01},
1329 { x: 2, y: 2, width: 2, height: 2, encoding: 0x01}];
1330 // data says [{ old_x: 0, old_y: 0 }, { old_x: 0, old_y: 0 }]
1331 var rects = [[0, 2, 0, 0], [0, 0, 0, 0]];
1332 send_fbu_msg(info, rects, client);
1333 expect(client._display).to.have.displayed(target_data_check);
1334 });
1335
1336 // TODO(directxman12): for encodings with subrects, test resuming on partial send?
1337 // TODO(directxman12): test rre_chunk_sz (related to above about subrects)?
1338
1339 it('should handle the RRE encoding', function () {
1340 var info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x02 }];
1341 var rect = [];
1342 rect.push32(2); // 2 subrects
1343 rect.push32(0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1344 rect.push(0xff); // becomes ff0000ff --> #0000FF color
1345 rect.push(0x00);
1346 rect.push(0x00);
1347 rect.push(0xff);
1348 rect.push16(0); // x: 0
1349 rect.push16(0); // y: 0
1350 rect.push16(2); // width: 2
1351 rect.push16(2); // height: 2
1352 rect.push(0xff); // becomes ff0000ff --> #0000FF color
1353 rect.push(0x00);
1354 rect.push(0x00);
1355 rect.push(0xff);
1356 rect.push16(2); // x: 2
1357 rect.push16(2); // y: 2
1358 rect.push16(2); // width: 2
1359 rect.push16(2); // height: 2
1360
1361 send_fbu_msg(info, [rect], client);
1362 expect(client._display).to.have.displayed(target_data_check);
1363 });
1364
1365 describe('the HEXTILE encoding handler', function () {
1366 var client;
1367 beforeEach(function () {
1368 client = make_rfb();
1369 client.connect('host', 8675);
1370 client._sock._websocket._open();
1371 client._rfb_state = 'normal';
1372 client._fb_name = 'some device';
1373 // a really small frame
1374 client._fb_width = 4;
1375 client._fb_height = 4;
1376 client._display._fb_width = 4;
1377 client._display._fb_height = 4;
1378 client._display._viewportLoc.w = 4;
1379 client._display._viewportLoc.h = 4;
1380 client._fb_Bpp = 4;
1381 });
1382
1383 it('should handle a tile with fg, bg specified, normal subrects', function () {
1384 var info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
1385 var rect = [];
1386 rect.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects
1387 rect.push32(0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1388 rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
1389 rect.push(0x00);
1390 rect.push(0x00);
1391 rect.push(0xff);
1392 rect.push(2); // 2 subrects
1393 rect.push(0); // x: 0, y: 0
1394 rect.push(1 | (1 << 4)); // width: 2, height: 2
1395 rect.push(2 | (2 << 4)); // x: 2, y: 2
1396 rect.push(1 | (1 << 4)); // width: 2, height: 2
1397 send_fbu_msg(info, [rect], client);
1398 expect(client._display).to.have.displayed(target_data_check);
1399 });
1400
1401 it('should handle a raw tile', function () {
1402 var info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
1403 var rect = [];
1404 rect.push(0x01); // raw
1405 for (var i = 0; i < target_data.length; i += 4) {
1406 rect.push(target_data[i + 2]);
1407 rect.push(target_data[i + 1]);
1408 rect.push(target_data[i]);
1409 rect.push(target_data[i + 3]);
1410 }
1411 send_fbu_msg(info, [rect], client);
1412 expect(client._display).to.have.displayed(target_data);
1413 });
1414
1415 it('should handle a tile with only bg specified (solid bg)', function () {
1416 var info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
1417 var rect = [];
1418 rect.push(0x02);
1419 rect.push32(0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1420 send_fbu_msg(info, [rect], client);
1421
1422 var expected = [];
1423 for (var i = 0; i < 16; i++) { expected.push32(0xff00ff); }
1424 expect(client._display).to.have.displayed(new Uint8Array(expected));
1425 });
1426
1427 it('should handle a tile with only bg specified and an empty frame afterwards', function () {
1428 // set the width so we can have two tiles
1429 client._fb_width = 8;
1430 client._display._fb_width = 8;
1431 client._display._viewportLoc.w = 8;
1432
1433 var info = [{ x: 0, y: 0, width: 32, height: 4, encoding: 0x05 }];
1434
1435 var rect = [];
1436
1437 // send a bg frame
1438 rect.push(0x02);
1439 rect.push32(0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1440
1441 // send an empty frame
1442 rect.push(0x00);
1443
1444 send_fbu_msg(info, [rect], client);
1445
1446 var expected = [];
1447 var i;
1448 for (i = 0; i < 16; i++) { expected.push32(0xff00ff); } // rect 1: solid
1449 for (i = 0; i < 16; i++) { expected.push32(0xff00ff); } // rect 2: same bkground color
1450 expect(client._display).to.have.displayed(new Uint8Array(expected));
1451 });
1452
1453 it('should handle a tile with bg and coloured subrects', function () {
1454 var info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
1455 var rect = [];
1456 rect.push(0x02 | 0x08 | 0x10); // bg spec, anysubrects, colouredsubrects
1457 rect.push32(0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1458 rect.push(2); // 2 subrects
1459 rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
1460 rect.push(0x00);
1461 rect.push(0x00);
1462 rect.push(0xff);
1463 rect.push(0); // x: 0, y: 0
1464 rect.push(1 | (1 << 4)); // width: 2, height: 2
1465 rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
1466 rect.push(0x00);
1467 rect.push(0x00);
1468 rect.push(0xff);
1469 rect.push(2 | (2 << 4)); // x: 2, y: 2
1470 rect.push(1 | (1 << 4)); // width: 2, height: 2
1471 send_fbu_msg(info, [rect], client);
1472 expect(client._display).to.have.displayed(target_data_check);
1473 });
1474
1475 it('should carry over fg and bg colors from the previous tile if not specified', function () {
1476 client._fb_width = 4;
1477 client._fb_height = 17;
1478 client._display.resize(4, 17);
1479
1480 var info = [{ x: 0, y: 0, width: 4, height: 17, encoding: 0x05}];
1481 var rect = [];
1482 rect.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects
1483 rect.push32(0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1484 rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
1485 rect.push(0x00);
1486 rect.push(0x00);
1487 rect.push(0xff);
1488 rect.push(8); // 8 subrects
1489 var i;
1490 for (i = 0; i < 4; i++) {
1491 rect.push((0 << 4) | (i * 4)); // x: 0, y: i*4
1492 rect.push(1 | (1 << 4)); // width: 2, height: 2
1493 rect.push((2 << 4) | (i * 4 + 2)); // x: 2, y: i * 4 + 2
1494 rect.push(1 | (1 << 4)); // width: 2, height: 2
1495 }
1496 rect.push(0x08); // anysubrects
1497 rect.push(1); // 1 subrect
1498 rect.push(0); // x: 0, y: 0
1499 rect.push(1 | (1 << 4)); // width: 2, height: 2
1500 send_fbu_msg(info, [rect], client);
1501
1502 var expected = [];
1503 for (i = 0; i < 4; i++) { expected = expected.concat(target_data_check_arr); }
1504 expected = expected.concat(target_data_check_arr.slice(0, 16));
1505 expect(client._display).to.have.displayed(new Uint8Array(expected));
1506 });
1507
1508 it('should fail on an invalid subencoding', function () {
1509 var info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
1510 var rects = [[45]]; // an invalid subencoding
1511 send_fbu_msg(info, rects, client);
1512 expect(client._rfb_state).to.equal('failed');
1513 });
1514 });
1515
1516 it.skip('should handle the TIGHT encoding', function () {
1517 // TODO(directxman12): test this
1518 });
1519
1520 it.skip('should handle the TIGHT_PNG encoding', function () {
1521 // TODO(directxman12): test this
1522 });
1523
1524 it('should handle the DesktopSize pseduo-encoding', function () {
1525 client.set_onFBResize(sinon.spy());
1526 sinon.spy(client._display, 'resize');
1527 send_fbu_msg([{ x: 0, y: 0, width: 20, height: 50, encoding: -223 }], [[]], client);
1528
1529 var spy = client.get_onFBResize();
1530 expect(spy).to.have.been.calledOnce;
1531 expect(spy).to.have.been.calledWith(sinon.match.any, 20, 50);
1532
1533 expect(client._fb_width).to.equal(20);
1534 expect(client._fb_height).to.equal(50);
1535
1536 expect(client._display.resize).to.have.been.calledOnce;
1537 expect(client._display.resize).to.have.been.calledWith(20, 50);
1538 });
1539
1540 describe('the ExtendedDesktopSize pseudo-encoding handler', function () {
1541 var client;
1542
1543 beforeEach(function () {
1544 client = make_rfb();
1545 client.connect('host', 8675);
1546 client._sock._websocket._open();
1547 client._rfb_state = 'normal';
1548 client._fb_name = 'some device';
1549 client._supportsSetDesktopSize = false;
1550 // a really small frame
1551 client._fb_width = 4;
1552 client._fb_height = 4;
1553 client._display._fb_width = 4;
1554 client._display._fb_height = 4;
1555 client._display._viewportLoc.w = 4;
1556 client._display._viewportLoc.h = 4;
1557 client._fb_Bpp = 4;
1558 sinon.spy(client._display, 'resize');
1559 client.set_onFBResize(sinon.spy());
1560 });
1561
1562 function make_screen_data (nr_of_screens) {
1563 var data = [];
1564 data.push8(nr_of_screens); // number-of-screens
1565 data.push8(0); // padding
1566 data.push16(0); // padding
1567 for (var i=0; i<nr_of_screens; i += 1) {
1568 data.push32(0); // id
1569 data.push16(0); // x-position
1570 data.push16(0); // y-position
1571 data.push16(20); // width
1572 data.push16(50); // height
1573 data.push32(0); // flags
1574 }
1575 return data;
1576 }
1577
1578 it('should handle a resize requested by this client', function () {
1579 var reason_for_change = 1; // requested by this client
1580 var status_code = 0; // No error
1581
1582 send_fbu_msg([{ x: reason_for_change, y: status_code,
1583 width: 20, height: 50, encoding: -308 }],
1584 make_screen_data(1), client);
1585
1586 expect(client._supportsSetDesktopSize).to.be.true;
1587 expect(client._fb_width).to.equal(20);
1588 expect(client._fb_height).to.equal(50);
1589
1590 expect(client._display.resize).to.have.been.calledOnce;
1591 expect(client._display.resize).to.have.been.calledWith(20, 50);
1592
1593 var spy = client.get_onFBResize();
1594 expect(spy).to.have.been.calledOnce;
1595 expect(spy).to.have.been.calledWith(sinon.match.any, 20, 50);
1596 });
1597
1598 it('should handle a resize requested by another client', function () {
1599 var reason_for_change = 2; // requested by another client
1600 var status_code = 0; // No error
1601
1602 send_fbu_msg([{ x: reason_for_change, y: status_code,
1603 width: 20, height: 50, encoding: -308 }],
1604 make_screen_data(1), client);
1605
1606 expect(client._supportsSetDesktopSize).to.be.true;
1607 expect(client._fb_width).to.equal(20);
1608 expect(client._fb_height).to.equal(50);
1609
1610 expect(client._display.resize).to.have.been.calledOnce;
1611 expect(client._display.resize).to.have.been.calledWith(20, 50);
1612
1613 var spy = client.get_onFBResize();
1614 expect(spy).to.have.been.calledOnce;
1615 expect(spy).to.have.been.calledWith(sinon.match.any, 20, 50);
1616 });
1617
1618 it('should be able to recieve requests which contain data for multiple screens', function () {
1619 var reason_for_change = 2; // requested by another client
1620 var status_code = 0; // No error
1621
1622 send_fbu_msg([{ x: reason_for_change, y: status_code,
1623 width: 60, height: 50, encoding: -308 }],
1624 make_screen_data(3), client);
1625
1626 expect(client._supportsSetDesktopSize).to.be.true;
1627 expect(client._fb_width).to.equal(60);
1628 expect(client._fb_height).to.equal(50);
1629
1630 expect(client._display.resize).to.have.been.calledOnce;
1631 expect(client._display.resize).to.have.been.calledWith(60, 50);
1632
1633 var spy = client.get_onFBResize();
1634 expect(spy).to.have.been.calledOnce;
1635 expect(spy).to.have.been.calledWith(sinon.match.any, 60, 50);
1636 });
1637
1638 it('should not handle a failed request', function () {
1639 var reason_for_change = 1; // requested by this client
1640 var status_code = 1; // Resize is administratively prohibited
1641
1642 send_fbu_msg([{ x: reason_for_change, y: status_code,
1643 width: 20, height: 50, encoding: -308 }],
1644 make_screen_data(1), client);
1645
1646 expect(client._fb_width).to.equal(4);
1647 expect(client._fb_height).to.equal(4);
1648
1649 expect(client._display.resize).to.not.have.been.called;
1650
1651 var spy = client.get_onFBResize();
1652 expect(spy).to.not.have.been.called;
1653 });
1654 });
1655
1656 it.skip('should handle the Cursor pseudo-encoding', function () {
1657 // TODO(directxman12): test
1658 });
1659
1660 it('should handle the last_rect pseudo-encoding', function () {
1661 client.set_onFBUReceive(sinon.spy());
1662 send_fbu_msg([{ x: 0, y: 0, width: 0, height: 0, encoding: -224}], [[]], client, 100);
1663 expect(client._FBU.rects).to.equal(0);
1664 expect(client.get_onFBUReceive()).to.have.been.calledOnce;
1665 });
1666 });
1667 });
1668
1669 it('should set the colour map on the display on SetColourMapEntries', function () {
1670 var expected_cm = [];
1671 var data = [1, 0, 0, 1, 0, 4];
1672 var i;
1673 for (i = 0; i < 4; i++) {
1674 expected_cm[i + 1] = [i * 10, i * 10 + 1, i * 10 + 2];
1675 data.push16(expected_cm[i + 1][2] << 8);
1676 data.push16(expected_cm[i + 1][1] << 8);
1677 data.push16(expected_cm[i + 1][0] << 8);
1678 }
1679
1680 client._sock._websocket._receive_data(new Uint8Array(data));
1681 expect(client._display.get_colourMap()).to.deep.equal(expected_cm);
1682 });
1683
1684 describe('XVP Message Handling', function () {
1685 beforeEach(function () {
1686 client = make_rfb();
1687 client.connect('host', 8675);
1688 client._sock._websocket._open();
1689 client._rfb_state = 'normal';
1690 client._fb_name = 'some device';
1691 client._fb_width = 27;
1692 client._fb_height = 32;
1693 });
1694
1695 it('should call updateState with a message on XVP_FAIL, but keep the same state', function () {
1696 client._updateState = sinon.spy();
1697 client._sock._websocket._receive_data(new Uint8Array([250, 0, 10, 0]));
1698 expect(client._updateState).to.have.been.calledOnce;
1699 expect(client._updateState).to.have.been.calledWith('normal', 'Operation Failed');
1700 });
1701
1702 it('should set the XVP version and fire the callback with the version on XVP_INIT', function () {
1703 client.set_onXvpInit(sinon.spy());
1704 client._sock._websocket._receive_data(new Uint8Array([250, 0, 10, 1]));
1705 expect(client._rfb_xvp_ver).to.equal(10);
1706 expect(client.get_onXvpInit()).to.have.been.calledOnce;
1707 expect(client.get_onXvpInit()).to.have.been.calledWith(10);
1708 });
1709
1710 it('should fail on unknown XVP message types', function () {
1711 client._sock._websocket._receive_data(new Uint8Array([250, 0, 10, 237]));
1712 expect(client._rfb_state).to.equal('failed');
1713 });
1714 });
1715
1716 it('should fire the clipboard callback with the retrieved text on ServerCutText', function () {
1717 var expected_str = 'cheese!';
1718 var data = [3, 0, 0, 0];
1719 data.push32(expected_str.length);
1720 for (var i = 0; i < expected_str.length; i++) { data.push(expected_str.charCodeAt(i)); }
1721 client.set_onClipboard(sinon.spy());
1722
1723 client._sock._websocket._receive_data(new Uint8Array(data));
1724 var spy = client.get_onClipboard();
1725 expect(spy).to.have.been.calledOnce;
1726 expect(spy.args[0][1]).to.equal(expected_str);
1727 });
1728
1729 it('should fire the bell callback on Bell', function () {
1730 client.set_onBell(sinon.spy());
1731 client._sock._websocket._receive_data(new Uint8Array([2]));
1732 expect(client.get_onBell()).to.have.been.calledOnce;
1733 });
1734
1735 it('should respond correctly to ServerFence', function () {
1736 var expected_msg = {_sQ: new Uint8Array(16), _sQlen: 0, flush: function() {}};
1737 var incoming_msg = {_sQ: new Uint8Array(16), _sQlen: 0, flush: function() {}};
1738
1739 var payload = "foo\x00ab9";
1740
1741 // ClientFence and ServerFence are identical in structure
1742 RFB.messages.clientFence(expected_msg, (1<<0) | (1<<1), payload);
1743 RFB.messages.clientFence(incoming_msg, 0xffffffff, payload);
1744
1745 client._sock._websocket._receive_data(incoming_msg._sQ);
1746
1747 expect(client._sock).to.have.sent(expected_msg._sQ);
1748
1749 expected_msg._sQlen = 0;
1750 incoming_msg._sQlen = 0;
1751
1752 RFB.messages.clientFence(expected_msg, (1<<0), payload);
1753 RFB.messages.clientFence(incoming_msg, (1<<0) | (1<<31), payload);
1754
1755 client._sock._websocket._receive_data(incoming_msg._sQ);
1756
1757 expect(client._sock).to.have.sent(expected_msg._sQ);
1758 });
1759
1760 it('should enable continuous updates on first EndOfContinousUpdates', function () {
1761 var expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: function() {}};
1762
1763 RFB.messages.enableContinuousUpdates(expected_msg, true, 0, 0, 640, 20);
1764
1765 expect(client._enabledContinuousUpdates).to.be.false;
1766
1767 client._sock._websocket._receive_data(new Uint8Array([150]));
1768
1769 expect(client._enabledContinuousUpdates).to.be.true;
1770 expect(client._sock).to.have.sent(expected_msg._sQ);
1771 });
1772
1773 it('should disable continuous updates on subsequent EndOfContinousUpdates', function () {
1774 client._enabledContinuousUpdates = true;
1775 client._supportsContinuousUpdates = true;
1776
1777 client._sock._websocket._receive_data(new Uint8Array([150]));
1778
1779 expect(client._enabledContinuousUpdates).to.be.false;
1780 });
1781
1782 it('should update continuous updates on resize', function () {
1783 var expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: function() {}};
1784 RFB.messages.enableContinuousUpdates(expected_msg, true, 0, 0, 90, 700);
1785
1786 client._FBU.width = 450;
1787 client._FBU.height = 160;
1788
1789 client._encHandlers.handle_FB_resize();
1790
1791 expect(client._sock._websocket._get_sent_data()).to.have.length(0);
1792
1793 client._enabledContinuousUpdates = true;
1794
1795 client._FBU.width = 90;
1796 client._FBU.height = 700;
1797
1798 client._encHandlers.handle_FB_resize();
1799
1800 expect(client._sock).to.have.sent(expected_msg._sQ);
1801 });
1802
1803 it('should fail on an unknown message type', function () {
1804 client._sock._websocket._receive_data(new Uint8Array([87]));
1805 expect(client._rfb_state).to.equal('failed');
1806 });
1807 });
1808
1809 describe('Asynchronous Events', function () {
1810 describe('Mouse event handlers', function () {
1811 var client;
1812 beforeEach(function () {
1813 client = make_rfb();
1814 client._sock = new Websock();
1815 client._sock.open('ws://', 'binary');
1816 client._sock._websocket._open();
1817 sinon.spy(client._sock, 'flush');
1818 client._rfb_state = 'normal';
1819 });
1820
1821 it('should not send button messages in view-only mode', function () {
1822 client._view_only = true;
1823 client._mouse._onMouseButton(0, 0, 1, 0x001);
1824 expect(client._sock.flush).to.not.have.been.called;
1825 });
1826
1827 it('should not send movement messages in view-only mode', function () {
1828 client._view_only = true;
1829 client._mouse._onMouseMove(0, 0);
1830 expect(client._sock.flush).to.not.have.been.called;
1831 });
1832
1833 it('should send a pointer event on mouse button presses', function () {
1834 client._mouse._onMouseButton(10, 12, 1, 0x001);
1835 var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: function () {}};
1836 RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x001);
1837 expect(client._sock).to.have.sent(pointer_msg._sQ);
1838 });
1839
1840 it('should send a mask of 1 on mousedown', function () {
1841 client._mouse._onMouseButton(10, 12, 1, 0x001);
1842 var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: function () {}};
1843 RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x001);
1844 expect(client._sock).to.have.sent(pointer_msg._sQ);
1845 });
1846
1847 it('should send a mask of 0 on mouseup', function () {
1848 client._mouse_buttonMask = 0x001;
1849 client._mouse._onMouseButton(10, 12, 0, 0x001);
1850 var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: function () {}};
1851 RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x000);
1852 expect(client._sock).to.have.sent(pointer_msg._sQ);
1853 });
1854
1855 it('should send a pointer event on mouse movement', function () {
1856 client._mouse._onMouseMove(10, 12);
1857 var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: function () {}};
1858 RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x000);
1859 expect(client._sock).to.have.sent(pointer_msg._sQ);
1860 });
1861
1862 it('should set the button mask so that future mouse movements use it', function () {
1863 client._mouse._onMouseButton(10, 12, 1, 0x010);
1864 client._mouse._onMouseMove(13, 9);
1865 var pointer_msg = {_sQ: new Uint8Array(12), _sQlen: 0, flush: function () {}};
1866 RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x010);
1867 RFB.messages.pointerEvent(pointer_msg, 13, 9, 0x010);
1868 expect(client._sock).to.have.sent(pointer_msg._sQ);
1869 });
1870
1871 // NB(directxman12): we don't need to test not sending messages in
1872 // non-normal modes, since we haven't grabbed input
1873 // yet (grabbing input should be checked in the lifecycle tests).
1874
1875 it('should not send movement messages when viewport dragging', function () {
1876 client._viewportDragging = true;
1877 client._display.viewportChangePos = sinon.spy();
1878 client._mouse._onMouseMove(13, 9);
1879 expect(client._sock.flush).to.not.have.been.called;
1880 });
1881
1882 it('should not send button messages when initiating viewport dragging', function () {
1883 client._viewportDrag = true;
1884 client._mouse._onMouseButton(13, 9, 0x001);
1885 expect(client._sock.flush).to.not.have.been.called;
1886 });
1887
1888 it('should be initiate viewport dragging on a button down event, if enabled', function () {
1889 client._viewportDrag = true;
1890 client._mouse._onMouseButton(13, 9, 0x001);
1891 expect(client._viewportDragging).to.be.true;
1892 expect(client._viewportDragPos).to.deep.equal({ x: 13, y: 9 });
1893 });
1894
1895 it('should terminate viewport dragging on a button up event, if enabled', function () {
1896 client._viewportDrag = true;
1897 client._viewportDragging = true;
1898 client._mouse._onMouseButton(13, 9, 0x000);
1899 expect(client._viewportDragging).to.be.false;
1900 });
1901
1902 it('if enabled, viewportDragging should occur on mouse movement while a button is down', function () {
1903 client._viewportDrag = true;
1904 client._viewportDragging = true;
1905 client._viewportHasMoved = false;
1906 client._viewportDragPos = { x: 23, y: 9 };
1907 client._display.viewportChangePos = sinon.spy();
1908
1909 client._mouse._onMouseMove(10, 4);
1910
1911 expect(client._viewportDragging).to.be.true;
1912 expect(client._viewportHasMoved).to.be.true;
1913 expect(client._viewportDragPos).to.deep.equal({ x: 10, y: 4 });
1914 expect(client._display.viewportChangePos).to.have.been.calledOnce;
1915 expect(client._display.viewportChangePos).to.have.been.calledWith(13, 5);
1916 });
1917 });
1918
1919 describe('Keyboard Event Handlers', function () {
1920 var client;
1921 beforeEach(function () {
1922 client = make_rfb();
1923 client._sock = new Websock();
1924 client._sock.open('ws://', 'binary');
1925 client._sock._websocket._open();
1926 sinon.spy(client._sock, 'flush');
1927 });
1928
1929 it('should send a key message on a key press', function () {
1930 var keyevent = {};
1931 keyevent.type = 'keydown';
1932 keyevent.keysym = {};
1933 keyevent.keysym.keysym = 1234;
1934 client._keyboard._onKeyPress(keyevent);
1935 var key_msg = {_sQ: new Uint8Array(8), _sQlen: 0, flush: function () {}};
1936 RFB.messages.keyEvent(key_msg, 1234, 1);
1937 expect(client._sock).to.have.sent(key_msg._sQ);
1938 });
1939
1940 it('should not send messages in view-only mode', function () {
1941 client._view_only = true;
1942 client._keyboard._onKeyPress(1234, 1);
1943 expect(client._sock.flush).to.not.have.been.called;
1944 });
1945 });
1946
1947 describe('WebSocket event handlers', function () {
1948 var client;
1949 beforeEach(function () {
1950 client = make_rfb();
1951 this.clock = sinon.useFakeTimers();
1952 });
1953
1954 afterEach(function () { this.clock.restore(); });
1955
1956 // message events
1957 it ('should do nothing if we receive an empty message and have nothing in the queue', function () {
1958 client.connect('host', 8675);
1959 client._rfb_state = 'normal';
1960 client._normal_msg = sinon.spy();
1961 client._sock._websocket._receive_data(new Uint8Array([]));
1962 expect(client._normal_msg).to.not.have.been.called;
1963 });
1964
1965 it('should handle a message in the normal state as a normal message', function () {
1966 client.connect('host', 8675);
1967 client._rfb_state = 'normal';
1968 client._normal_msg = sinon.spy();
1969 client._sock._websocket._receive_data(new Uint8Array([1, 2, 3]));
1970 expect(client._normal_msg).to.have.been.calledOnce;
1971 });
1972
1973 it('should handle a message in any non-disconnected/failed state like an init message', function () {
1974 client.connect('host', 8675);
1975 client._rfb_state = 'ProtocolVersion';
1976 client._init_msg = sinon.spy();
1977 client._sock._websocket._receive_data(new Uint8Array([1, 2, 3]));
1978 expect(client._init_msg).to.have.been.calledOnce;
1979 });
1980
1981 it('should split up the handling of muplitle normal messages across 10ms intervals', function () {
1982 client.connect('host', 8675);
1983 client._sock._websocket._open();
1984 client._rfb_state = 'normal';
1985 client.set_onBell(sinon.spy());
1986 client._sock._websocket._receive_data(new Uint8Array([0x02, 0x02]));
1987 expect(client.get_onBell()).to.have.been.calledOnce;
1988 this.clock.tick(20);
1989 expect(client.get_onBell()).to.have.been.calledTwice;
1990 });
1991
1992 // open events
1993 it('should update the state to ProtocolVersion on open (if the state is "connect")', function () {
1994 client.connect('host', 8675);
1995 client._sock._websocket._open();
1996 expect(client._rfb_state).to.equal('ProtocolVersion');
1997 });
1998
1999 it('should fail if we are not currently ready to connect and we get an "open" event', function () {
2000 client.connect('host', 8675);
2001 client._rfb_state = 'some_other_state';
2002 client._sock._websocket._open();
2003 expect(client._rfb_state).to.equal('failed');
2004 });
2005
2006 // close events
2007 it('should transition to "disconnected" from "disconnect" on a close event', function () {
2008 client.connect('host', 8675);
2009 client._rfb_state = 'disconnect';
2010 client._sock._websocket.close();
2011 expect(client._rfb_state).to.equal('disconnected');
2012 });
2013
2014 it('should transition to failed if we get a close event from any non-"disconnection" state', function () {
2015 client.connect('host', 8675);
2016 client._rfb_state = 'normal';
2017 client._sock._websocket.close();
2018 expect(client._rfb_state).to.equal('failed');
2019 });
2020
2021 it('should unregister close event handler', function () {
2022 sinon.spy(client._sock, 'off');
2023 client.connect('host', 8675);
2024 client._rfb_state = 'disconnect';
2025 client._sock._websocket.close();
2026 expect(client._sock.off).to.have.been.calledWith('close');
2027 });
2028
2029 // error events do nothing
2030 });
2031 });
2032 });