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