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