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