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