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