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