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