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