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