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