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