]> git.proxmox.com Git - mirror_novnc.git/blame - tests/test.rfb.js
noVNC 1.0.0
[mirror_novnc.git] / tests / test.rfb.js
CommitLineData
b1dee947
SR
1var assert = chai.assert;
2var expect = chai.expect;
3
dfae3209
SR
4import RFB from '../core/rfb.js';
5import Websock from '../core/websock.js';
69411b9e 6import { encodings } from '../core/encodings.js';
dfae3209
SR
7
8import FakeWebSocket from './fake.websocket.js';
0aaf59c2 9import sinon from '../vendor/sinon.js';
dfae3209 10
9b84f516
PO
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
3949a095
SR
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
b1dee947 46describe('Remote Frame Buffer Protocol Client', function() {
2f4516f2 47 var clock;
9b84f516 48 var raf;
2f4516f2 49
b1dee947
SR
50 before(FakeWebSocket.replace);
51 after(FakeWebSocket.restore);
52
38781d93 53 before(function () {
2f4516f2 54 this.clock = clock = sinon.useFakeTimers();
9b84f516
PO
55 // sinon doesn't support this yet
56 raf = window.requestAnimationFrame;
57 window.requestAnimationFrame = setTimeout;
38781d93
SR
58 // Use a single set of buffers instead of reallocating to
59 // speed up tests
60 var sock = new Websock();
9ff86fb7 61 var _sQ = new Uint8Array(sock._sQbufferSize);
38781d93
SR
62 var rQ = new Uint8Array(sock._rQbufferSize);
63
64 Websock.prototype._old_allocate_buffers = Websock.prototype._allocate_buffers;
65 Websock.prototype._allocate_buffers = function () {
9ff86fb7 66 this._sQ = _sQ;
38781d93
SR
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();
9b84f516 75 window.requestAnimationFrame = raf;
38781d93
SR
76 });
77
9b84f516 78 var container;
bb25d3d6
PO
79 var rfbs;
80
81 beforeEach(function () {
9b84f516
PO
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
bb25d3d6
PO
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 = [];
9b84f516
PO
99
100 document.body.removeChild(container);
101 container = null;
bb25d3d6
PO
102 });
103
2f4516f2
PO
104 function make_rfb (url, options) {
105 url = url || 'wss://host:8675';
9b84f516 106 var rfb = new RFB(container, url, options);
2f4516f2
PO
107 clock.tick();
108 rfb._sock._websocket._open();
109 rfb._rfb_connection_state = 'connected';
bb25d3d6
PO
110 sinon.spy(rfb, "_disconnect");
111 rfbs.push(rfb);
2f4516f2
PO
112 return rfb;
113 }
b1dee947 114
2f4516f2
PO
115 describe('Connecting/Disconnecting', function () {
116 describe('#RFB', function () {
c2a4d3ef 117 it('should set the current state to "connecting"', function () {
9b84f516 118 var client = new RFB(document.createElement('div'), 'wss://host:8675');
ee5cae9f 119 client._rfb_connection_state = '';
2f4516f2 120 this.clock.tick();
ee5cae9f 121 expect(client._rfb_connection_state).to.equal('connecting');
b1dee947
SR
122 });
123
2f4516f2 124 it('should actually connect to the websocket', function () {
9b84f516 125 var client = new RFB(document.createElement('div'), 'ws://HOST:8675/PATH');
2f4516f2
PO
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');
b1dee947 130 });
b1dee947
SR
131 });
132
133 describe('#disconnect', function () {
2f4516f2
PO
134 var client;
135 beforeEach(function () {
136 client = make_rfb();
137 });
b1dee947 138
ee5cae9f
SM
139 it('should go to state "disconnecting" before "disconnected"', function () {
140 sinon.spy(client, '_updateConnectionState');
b1dee947 141 client.disconnect();
ee5cae9f
SM
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');
b1dee947 148 });
155d78b3
JS
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 });
b1dee947
SR
167 });
168
430f00d6 169 describe('#sendCredentials', function () {
2f4516f2
PO
170 var client;
171 beforeEach(function () {
172 client = make_rfb();
173 client._rfb_connection_state = 'connecting';
174 });
175
430f00d6
PO
176 it('should set the rfb credentials properly"', function () {
177 client.sendCredentials({ password: 'pass' });
178 expect(client._rfb_credentials).to.deep.equal({ password: 'pass' });
b1dee947
SR
179 });
180
181 it('should call init_msg "soon"', function () {
182 client._init_msg = sinon.spy();
430f00d6 183 client.sendCredentials({ password: 'pass' });
b1dee947
SR
184 this.clock.tick(5);
185 expect(client._init_msg).to.have.been.calledOnce;
186 });
187 });
057b8fec 188 });
b1dee947 189
057b8fec
PO
190 describe('Public API Basic Behavior', function () {
191 var client;
192 beforeEach(function () {
193 client = make_rfb();
057b8fec 194 });
b1dee947 195
057b8fec 196 describe('#sendCtrlAlDel', function () {
b1dee947 197 it('should sent ctrl[down]-alt[down]-del[down] then del[up]-alt[up]-ctrl[up]', function () {
89d2837f 198 var expected = {_sQ: new Uint8Array(48), _sQlen: 0, flush: function () {}};
9ff86fb7
SR
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);
b1dee947
SR
205
206 client.sendCtrlAltDel();
9ff86fb7 207 expect(client._sock).to.have.sent(expected._sQ);
b1dee947
SR
208 });
209
210 it('should not send the keys if we are not in a normal state', function () {
057b8fec 211 sinon.spy(client._sock, 'flush');
bb25d3d6 212 client._rfb_connection_state = "connecting";
b1dee947 213 client.sendCtrlAltDel();
9ff86fb7 214 expect(client._sock.flush).to.not.have.been.called;
b1dee947
SR
215 });
216
217 it('should not send the keys if we are set as view_only', function () {
057b8fec 218 sinon.spy(client._sock, 'flush');
747b4623 219 client._viewOnly = true;
b1dee947 220 client.sendCtrlAltDel();
9ff86fb7 221 expect(client._sock.flush).to.not.have.been.called;
b1dee947
SR
222 });
223 });
224
225 describe('#sendKey', function () {
b1dee947 226 it('should send a single key with the given code and state (down = true)', function () {
89d2837f 227 var expected = {_sQ: new Uint8Array(8), _sQlen: 0, flush: function () {}};
9ff86fb7 228 RFB.messages.keyEvent(expected, 123, 1);
94f5cf05 229 client.sendKey(123, 'Key123', true);
9ff86fb7 230 expect(client._sock).to.have.sent(expected._sQ);
b1dee947
SR
231 });
232
233 it('should send both a down and up event if the state is not specified', function () {
89d2837f 234 var expected = {_sQ: new Uint8Array(16), _sQlen: 0, flush: function () {}};
9ff86fb7
SR
235 RFB.messages.keyEvent(expected, 123, 1);
236 RFB.messages.keyEvent(expected, 123, 0);
94f5cf05 237 client.sendKey(123, 'Key123');
9ff86fb7 238 expect(client._sock).to.have.sent(expected._sQ);
b1dee947
SR
239 });
240
241 it('should not send the key if we are not in a normal state', function () {
057b8fec 242 sinon.spy(client._sock, 'flush');
bb25d3d6 243 client._rfb_connection_state = "connecting";
94f5cf05 244 client.sendKey(123, 'Key123');
9ff86fb7 245 expect(client._sock.flush).to.not.have.been.called;
b1dee947
SR
246 });
247
248 it('should not send the key if we are set as view_only', function () {
057b8fec 249 sinon.spy(client._sock, 'flush');
747b4623 250 client._viewOnly = true;
94f5cf05 251 client.sendKey(123, 'Key123');
9ff86fb7 252 expect(client._sock.flush).to.not.have.been.called;
b1dee947 253 });
94f5cf05
PO
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 });
be70fe0a
PO
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 });
b1dee947
SR
270 });
271
9b84f516
PO
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
b1dee947 288 describe('#clipboardPasteFrom', function () {
b1dee947 289 it('should send the given text in a paste event', function () {
89d2837f 290 var expected = {_sQ: new Uint8Array(11), _sQlen: 0, flush: function () {}};
9ff86fb7 291 RFB.messages.clientCutText(expected, 'abc');
b1dee947 292 client.clipboardPasteFrom('abc');
9ff86fb7 293 expect(client._sock).to.have.sent(expected._sQ);
b1dee947
SR
294 });
295
296 it('should not send the text if we are not in a normal state', function () {
057b8fec 297 sinon.spy(client._sock, 'flush');
bb25d3d6 298 client._rfb_connection_state = "connecting";
b1dee947 299 client.clipboardPasteFrom('abc');
9ff86fb7 300 expect(client._sock.flush).to.not.have.been.called;
b1dee947
SR
301 });
302 });
303
304 describe("XVP operations", function () {
305 beforeEach(function () {
b1dee947
SR
306 client._rfb_xvp_ver = 1;
307 });
308
cd523e8f
PO
309 it('should send the shutdown signal on #machineShutdown', function () {
310 client.machineShutdown();
9ff86fb7 311 expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x02]));
b1dee947
SR
312 });
313
cd523e8f
PO
314 it('should send the reboot signal on #machineReboot', function () {
315 client.machineReboot();
9ff86fb7 316 expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x03]));
b1dee947
SR
317 });
318
cd523e8f
PO
319 it('should send the reset signal on #machineReset', function () {
320 client.machineReset();
9ff86fb7 321 expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x04]));
b1dee947
SR
322 });
323
b1dee947 324 it('should not send XVP operations with higher versions than we support', function () {
057b8fec 325 sinon.spy(client._sock, 'flush');
cd523e8f 326 client._xvpOp(2, 7);
9ff86fb7 327 expect(client._sock.flush).to.not.have.been.called;
b1dee947
SR
328 });
329 });
330 });
331
9b84f516
PO
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
b1dee947 720 describe('Misc Internals', function () {
c00ee156 721 describe('#_updateConnectionState', function () {
b1dee947
SR
722 var client;
723 beforeEach(function () {
b1dee947
SR
724 client = make_rfb();
725 });
726
c2a4d3ef 727 it('should clear the disconnect timer if the state is not "disconnecting"', function () {
b1dee947
SR
728 var spy = sinon.spy();
729 client._disconnTimer = setTimeout(spy, 50);
2f4516f2
PO
730 client._rfb_connection_state = 'connecting';
731 client._updateConnectionState('connected');
b1dee947
SR
732 this.clock.tick(51);
733 expect(spy).to.not.have.been.called;
734 expect(client._disconnTimer).to.be.null;
735 });
3bb12056 736
3bb12056 737 it('should set the rfb_connection_state', function () {
bb25d3d6
PO
738 client._rfb_connection_state = 'connecting';
739 client._updateConnectionState('connected');
740 expect(client._rfb_connection_state).to.equal('connected');
3bb12056
SM
741 });
742
743 it('should not change the state when we are disconnected', function () {
bb25d3d6
PO
744 client.disconnect();
745 expect(client._rfb_connection_state).to.equal('disconnected');
b2e961d4
SM
746 client._updateConnectionState('connecting');
747 expect(client._rfb_connection_state).to.not.equal('connecting');
3bb12056
SM
748 });
749
750 it('should ignore state changes to the same state', function () {
ee5cae9f 751 var connectSpy = sinon.spy();
ee5cae9f 752 client.addEventListener("connect", connectSpy);
ee5cae9f 753
bb25d3d6 754 expect(client._rfb_connection_state).to.equal('connected');
ee5cae9f
SM
755 client._updateConnectionState('connected');
756 expect(connectSpy).to.not.have.been.called;
757
bb25d3d6
PO
758 client.disconnect();
759
760 var disconnectSpy = sinon.spy();
761 client.addEventListener("disconnect", disconnectSpy);
762
763 expect(client._rfb_connection_state).to.equal('disconnected');
ee5cae9f
SM
764 client._updateConnectionState('disconnected');
765 expect(disconnectSpy).to.not.have.been.called;
b2e961d4
SM
766 });
767
768 it('should ignore illegal state changes', function () {
e89eef94 769 var spy = sinon.spy();
ee5cae9f 770 client.addEventListener("disconnect", spy);
b2e961d4
SM
771 client._updateConnectionState('disconnected');
772 expect(client._rfb_connection_state).to.not.equal('disconnected');
3bb12056
SM
773 expect(spy).to.not.have.been.called;
774 });
775 });
776
777 describe('#_fail', function () {
778 var client;
779 beforeEach(function () {
3bb12056 780 client = make_rfb();
3bb12056
SM
781 });
782
3bb12056
SM
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
d472f3f1
SM
797 it('should set clean_disconnect variable', function () {
798 client._rfb_clean_disconnect = true;
b2e961d4 799 client._rfb_connection_state = 'connected';
d472f3f1
SM
800 client._fail();
801 expect(client._rfb_clean_disconnect).to.be.false;
67cd2072
SM
802 });
803
d472f3f1 804 it('should result in disconnect event with clean set to false', function () {
b2e961d4 805 client._rfb_connection_state = 'connected';
e89eef94
PO
806 var spy = sinon.spy();
807 client.addEventListener("disconnect", spy);
d472f3f1 808 client._fail();
3bb12056
SM
809 this.clock.tick(2000);
810 expect(spy).to.have.been.calledOnce;
d472f3f1 811 expect(spy.args[0][0].detail.clean).to.be.false;
3bb12056
SM
812 });
813
b1dee947
SR
814 });
815 });
816
c00ee156 817 describe('Connection States', function () {
ee5cae9f
SM
818 describe('connecting', function () {
819 it('should open the websocket connection', function () {
9b84f516 820 var client = new RFB(document.createElement('div'),
ee5cae9f
SM
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
c2a4d3ef 851 describe('disconnecting', function () {
b1dee947
SR
852 var client;
853 beforeEach(function () {
b1dee947 854 client = make_rfb();
b1dee947
SR
855 });
856
3bb12056
SM
857 it('should force disconnect if we do not call Websock.onclose within the disconnection timeout', function () {
858 sinon.spy(client, '_updateConnectionState');
b1dee947 859 client._sock._websocket.close = function () {}; // explicitly don't call onclose
c2a4d3ef 860 client._updateConnectionState('disconnecting');
68e09edc 861 this.clock.tick(3 * 1000);
3bb12056
SM
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");
b1dee947
SR
865 });
866
867 it('should not fail if Websock.onclose gets called within the disconnection timeout', function () {
c2a4d3ef 868 client._updateConnectionState('disconnecting');
68e09edc 869 this.clock.tick(3 * 1000 / 2);
b1dee947 870 client._sock._websocket.close();
68e09edc 871 this.clock.tick(3 * 1000 / 2 + 1);
c00ee156 872 expect(client._rfb_connection_state).to.equal('disconnected');
b1dee947
SR
873 });
874
875 it('should close the WebSocket connection', function () {
876 sinon.spy(client._sock, 'close');
c2a4d3ef 877 client._updateConnectionState('disconnecting');
3bb12056 878 expect(client._sock.close).to.have.been.calledOnce;
b1dee947 879 });
bb25d3d6
PO
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 });
b1dee947
SR
888 });
889
3bb12056 890 describe('disconnected', function () {
b1dee947 891 var client;
bb25d3d6 892 beforeEach(function () {
9b84f516 893 client = new RFB(document.createElement('div'), 'ws://HOST:8675/PATH');
bb25d3d6 894 });
b1dee947 895
d472f3f1 896 it('should result in a disconnect event if state becomes "disconnected"', function () {
e89eef94
PO
897 var spy = sinon.spy();
898 client.addEventListener("disconnect", spy);
3bb12056 899 client._rfb_connection_state = 'disconnecting';
3bb12056 900 client._updateConnectionState('disconnected');
3bb12056 901 expect(spy).to.have.been.calledOnce;
d472f3f1 902 expect(spy.args[0][0].detail.clean).to.be.true;
b1dee947
SR
903 });
904
d472f3f1 905 it('should result in a disconnect event without msg when no reason given', function () {
e89eef94
PO
906 var spy = sinon.spy();
907 client.addEventListener("disconnect", spy);
3bb12056
SM
908 client._rfb_connection_state = 'disconnecting';
909 client._rfb_disconnect_reason = "";
910 client._updateConnectionState('disconnected');
3bb12056
SM
911 expect(spy).to.have.been.calledOnce;
912 expect(spy.args[0].length).to.equal(1);
b1dee947 913 });
b1dee947 914 });
b1dee947
SR
915 });
916
917 describe('Protocol Initialization States', function () {
057b8fec
PO
918 var client;
919 beforeEach(function () {
920 client = make_rfb();
2f4516f2 921 client._rfb_connection_state = 'connecting';
057b8fec 922 });
b1dee947 923
057b8fec 924 describe('ProtocolVersion', function () {
b1dee947
SR
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 () {
b1dee947
SR
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
49aa5b81
LOH
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
b1dee947 976 it('should fail on an invalid version', function () {
3bb12056 977 sinon.spy(client, "_fail");
b1dee947 978 send_ver('002.000', client);
3bb12056 979 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
980 });
981 });
982
b1dee947
SR
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
9ff86fb7 992 expect(client._sock).to.have.sent(new Uint8Array(expected));
b1dee947
SR
993 });
994
995 it('should transition to the Security state on successful negotiation', function () {
996 send_ver('003.008', client);
c00ee156 997 expect(client._rfb_init_state).to.equal('Security');
b1dee947 998 });
3d7bb020
PO
999
1000 describe('Repeater', function () {
057b8fec 1001 beforeEach(function () {
2f4516f2
PO
1002 client = make_rfb('wss://host:8675', { repeaterID: "12345" });
1003 client._rfb_connection_state = 'connecting';
057b8fec
PO
1004 });
1005
1006 it('should interpret version 000.000 as a repeater', function () {
3d7bb020
PO
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 () {
3d7bb020
PO
1016 send_ver('000.000', client);
1017 send_ver('003.008', client);
1018 expect(client._rfb_version).to.equal(3.8);
1019 });
1020 });
b1dee947
SR
1021 });
1022
1023 describe('Security', function () {
b1dee947 1024 beforeEach(function () {
c00ee156 1025 client._rfb_init_state = 'Security';
b1dee947
SR
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
0ee5ca6e
PO
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
b1dee947
SR
1045 it('should choose for the most prefered scheme possible for versions >= 3.7', function () {
1046 client._rfb_version = 3.7;
0ee5ca6e 1047 var auth_schemes = [2, 22, 16];
b1dee947 1048 client._sock._websocket._receive_data(auth_schemes);
0ee5ca6e
PO
1049 expect(client._rfb_auth_scheme).to.equal(22);
1050 expect(client._sock).to.have.sent(new Uint8Array([22]));
b1dee947
SR
1051 });
1052
1053 it('should fail if there are no supported schemes for versions >= 3.7', function () {
3bb12056 1054 sinon.spy(client, "_fail");
b1dee947
SR
1055 client._rfb_version = 3.7;
1056 var auth_schemes = [1, 32];
1057 client._sock._websocket._receive_data(auth_schemes);
3bb12056 1058 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
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
159c50c0 1067 expect(client._fail).to.have.been.calledOnce;
67cd2072 1068 expect(client._fail).to.have.been.calledWith(
d472f3f1 1069 'Security negotiation failed on no security types (reason: whoops)');
b1dee947
SR
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);
c00ee156 1077 expect(client._rfb_init_state).to.equal('Authentication');
b1dee947
SR
1078 expect(client._negotiate_authentication).to.have.been.calledOnce;
1079 });
1080 });
1081
1082 describe('Authentication', function () {
b1dee947 1083 beforeEach(function () {
c00ee156 1084 client._rfb_init_state = 'Security';
b1dee947
SR
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;
3949a095 1096 push32(data, err_len);
b1dee947
SR
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));
67cd2072 1103 expect(client._fail).to.have.been.calledWith(
d472f3f1 1104 'Security negotiation failed on authentication scheme (reason: Whoopsies)');
b1dee947
SR
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);
c00ee156 1110 expect(client._rfb_init_state).to.equal('SecurityResult');
b1dee947
SR
1111 });
1112
c00ee156 1113 it('should transition straight to ServerInitialisation on "no auth" for versions < 3.8', function () {
b1dee947 1114 client._rfb_version = 3.7;
b1dee947 1115 send_security(1, client);
c00ee156 1116 expect(client._rfb_init_state).to.equal('ServerInitialisation');
b1dee947
SR
1117 });
1118
1119 it('should fail on an unknown auth scheme', function () {
3bb12056 1120 sinon.spy(client, "_fail");
b1dee947
SR
1121 client._rfb_version = 3.8;
1122 send_security(57, client);
3bb12056 1123 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
1124 });
1125
1126 describe('VNC Authentication (type 2) Handler', function () {
b1dee947 1127 beforeEach(function () {
c00ee156 1128 client._rfb_init_state = 'Security';
b1dee947
SR
1129 client._rfb_version = 3.8;
1130 });
1131
e89eef94
PO
1132 it('should fire the credentialsrequired event if missing a password', function () {
1133 var spy = sinon.spy();
1134 client.addEventListener("credentialsrequired", spy);
b1dee947 1135 send_security(2, client);
7d714b15 1136
aa5b3a35
SM
1137 var challenge = [];
1138 for (var i = 0; i < 16; i++) { challenge[i] = i; }
1139 client._sock._websocket._receive_data(new Uint8Array(challenge));
1140
430f00d6 1141 expect(client._rfb_credentials).to.be.empty;
7d714b15 1142 expect(spy).to.have.been.calledOnce;
e89eef94 1143 expect(spy.args[0][0].detail.types).to.have.members(["password"]);
b1dee947
SR
1144 });
1145
1146 it('should encrypt the password with DES and then send it back', function () {
430f00d6 1147 client._rfb_credentials = { password: 'passwd' };
b1dee947
SR
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);
9ff86fb7 1156 expect(client._sock).to.have.sent(new Uint8Array(des_pass));
b1dee947
SR
1157 });
1158
1159 it('should transition to SecurityResult immediately after sending the password', function () {
430f00d6 1160 client._rfb_credentials = { password: 'passwd' };
b1dee947
SR
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
c00ee156 1167 expect(client._rfb_init_state).to.equal('SecurityResult');
b1dee947
SR
1168 });
1169 });
1170
1171 describe('XVP Authentication (type 22) Handler', function () {
b1dee947 1172 beforeEach(function () {
c00ee156 1173 client._rfb_init_state = 'Security';
b1dee947
SR
1174 client._rfb_version = 3.8;
1175 });
1176
1177 it('should fall through to standard VNC authentication upon completion', function () {
430f00d6
PO
1178 client._rfb_credentials = { username: 'user',
1179 target: 'target',
1180 password: 'password' };
b1dee947
SR
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
e89eef94
PO
1186 it('should fire the credentialsrequired event if all credentials are missing', function() {
1187 var spy = sinon.spy();
1188 client.addEventListener("credentialsrequired", spy);
430f00d6 1189 client._rfb_credentials = {};
b1dee947 1190 send_security(22, client);
7d714b15 1191
430f00d6 1192 expect(client._rfb_credentials).to.be.empty;
7d714b15 1193 expect(spy).to.have.been.calledOnce;
e89eef94 1194 expect(spy.args[0][0].detail.types).to.have.members(["username", "password", "target"]);
b1dee947
SR
1195 });
1196
e89eef94
PO
1197 it('should fire the credentialsrequired event if some credentials are missing', function() {
1198 var spy = sinon.spy();
1199 client.addEventListener("credentialsrequired", spy);
430f00d6
PO
1200 client._rfb_credentials = { username: 'user',
1201 target: 'target' };
b1dee947 1202 send_security(22, client);
7d714b15 1203
7d714b15 1204 expect(spy).to.have.been.calledOnce;
e89eef94 1205 expect(spy.args[0][0].detail.types).to.have.members(["username", "password", "target"]);
b1dee947
SR
1206 });
1207
430f00d6
PO
1208 it('should send user and target separately', function () {
1209 client._rfb_credentials = { username: 'user',
1210 target: 'target',
1211 password: 'password' };
b1dee947
SR
1212 client._negotiate_std_vnc_auth = sinon.spy();
1213
1214 send_security(22, client);
1215
b1dee947
SR
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
9ff86fb7 1219 expect(client._sock).to.have.sent(new Uint8Array(expected));
b1dee947
SR
1220 });
1221 });
1222
1223 describe('TightVNC Authentication (type 16) Handler', function () {
b1dee947 1224 beforeEach(function () {
c00ee156 1225 client._rfb_init_state = 'Security';
b1dee947
SR
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 = [];
3949a095 1234 push32(data, pairs_len);
b1dee947
SR
1235
1236 for (var i = 0; i < pairs_len; i++) {
3949a095 1237 push32(data, pairs[i][0]);
b1dee947
SR
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 () {
3bb12056 1256 sinon.spy(client, "_fail");
b1dee947 1257 send_num_str_pairs([[123, 'OTHR', 'SOMETHNG']], client);
3bb12056 1258 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
1259 });
1260
1261 it('should choose the notunnel tunnel type', function () {
1262 send_num_str_pairs([[0, 'TGHT', 'NOTUNNEL'], [123, 'OTHR', 'SOMETHNG']], client);
9ff86fb7 1263 expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 0]));
b1dee947
SR
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);
9ff86fb7 1270 expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 1]));
c00ee156 1271 expect(client._rfb_init_state).to.equal('SecurityResult');
b1dee947
SR
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);
9ff86fb7 1286 expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 1]));
c00ee156 1287 expect(client._rfb_init_state).to.equal('SecurityResult');
b1dee947
SR
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);
9ff86fb7 1294 expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 2]));
b1dee947
SR
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 () {
3bb12056 1300 sinon.spy(client, "_fail");
b1dee947
SR
1301 client._rfb_tightvnc = true;
1302 send_num_str_pairs([[23, 'stdv', 'badval__']], client);
3bb12056 1303 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
1304 });
1305 });
1306 });
1307
1308 describe('SecurityResult', function () {
b1dee947 1309 beforeEach(function () {
c00ee156 1310 client._rfb_init_state = 'SecurityResult';
b1dee947
SR
1311 });
1312
c00ee156 1313 it('should fall through to ServerInitialisation on a response code of 0', function () {
b1dee947 1314 client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0]));
c00ee156 1315 expect(client._rfb_init_state).to.equal('ServerInitialisation');
b1dee947
SR
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));
67cd2072 1323 expect(client._fail).to.have.been.calledWith(
d472f3f1 1324 'Security negotiation failed on security result (reason: whoops)');
b1dee947
SR
1325 });
1326
1327 it('should fail on an error code of 1 with a standard message for version < 3.8', function () {
3bb12056 1328 sinon.spy(client, '_fail');
b1dee947
SR
1329 client._rfb_version = 3.7;
1330 client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 1]));
d472f3f1
SM
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;
b1dee947
SR
1371 });
1372 });
1373
1374 describe('ClientInitialisation', function () {
b1dee947 1375 it('should transition to the ServerInitialisation state', function () {
2f4516f2
PO
1376 var client = make_rfb();
1377 client._rfb_connection_state = 'connecting';
3d7bb020 1378 client._rfb_init_state = 'SecurityResult';
b1dee947 1379 client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0]));
c00ee156 1380 expect(client._rfb_init_state).to.equal('ServerInitialisation');
b1dee947
SR
1381 });
1382
1383 it('should send 1 if we are in shared mode', function () {
2f4516f2
PO
1384 var client = make_rfb('wss://host:8675', { shared: true });
1385 client._rfb_connection_state = 'connecting';
3d7bb020 1386 client._rfb_init_state = 'SecurityResult';
b1dee947 1387 client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0]));
9ff86fb7 1388 expect(client._sock).to.have.sent(new Uint8Array([1]));
b1dee947
SR
1389 });
1390
1391 it('should send 0 if we are not in shared mode', function () {
2f4516f2
PO
1392 var client = make_rfb('wss://host:8675', { shared: false });
1393 client._rfb_connection_state = 'connecting';
3d7bb020 1394 client._rfb_init_state = 'SecurityResult';
b1dee947 1395 client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0]));
9ff86fb7 1396 expect(client._sock).to.have.sent(new Uint8Array([0]));
b1dee947
SR
1397 });
1398 });
1399
1400 describe('ServerInitialisation', function () {
b1dee947 1401 beforeEach(function () {
c00ee156 1402 client._rfb_init_state = 'ServerInitialisation';
b1dee947
SR
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
3949a095
SR
1414 push16(data, full_opts.width);
1415 push16(data, full_opts.height);
b1dee947
SR
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
3949a095
SR
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);
b1dee947
SR
1428
1429 // padding
3949a095
SR
1430 push8(data, 0);
1431 push8(data, 0);
1432 push8(data, 0);
b1dee947
SR
1433
1434 client._sock._websocket._receive_data(new Uint8Array(data));
1435
1436 var name_data = [];
3949a095 1437 push32(name_data, full_opts.name.length);
b1dee947
SR
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 () {
e89eef94
PO
1453 var spy = sinon.spy();
1454 client.addEventListener("desktopname", spy);
b1dee947
SR
1455 send_server_init({ name: 'some name' }, client);
1456
b1dee947
SR
1457 expect(client._fb_name).to.equal('some name');
1458 expect(spy).to.have.been.calledOnce;
e89eef94 1459 expect(spy.args[0][0].detail.name).to.equal('some name');
b1dee947
SR
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 = [];
3949a095
SR
1469 push16(tight_data, 1);
1470 push16(tight_data, 2);
1471 push16(tight_data, 3);
1472 push16(tight_data, 0);
b1dee947
SR
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
c2a4d3ef 1478 expect(client._rfb_connection_state).to.equal('connected');
b1dee947
SR
1479 });
1480
9b84f516 1481 it('should resize the display', function () {
b1dee947
SR
1482 sinon.spy(client._display, 'resize');
1483 send_server_init({ width: 27, height: 32 }, client);
1484
b1dee947
SR
1485 expect(client._display.resize).to.have.been.calledOnce;
1486 expect(client._display.resize).to.have.been.calledWith(27, 32);
b1dee947
SR
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
69411b9e
PO
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 });
49a81837 1503
69411b9e
PO
1504 afterEach(function () {
1505 RFB.messages.pixelFormat.restore();
1506 RFB.messages.clientEncodings.restore();
1507 RFB.messages.fbUpdateRequest.restore();
1508 });
b1dee947 1509
69411b9e
PO
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 });
b1dee947
SR
1537 });
1538
c2a4d3ef 1539 it('should transition to the "connected" state', function () {
b1dee947 1540 send_server_init({}, client);
c2a4d3ef 1541 expect(client._rfb_connection_state).to.equal('connected');
b1dee947
SR
1542 });
1543 });
1544 });
1545
1546 describe('Protocol Message Processing After Completing Initialization', function () {
1547 var client;
1548
1549 beforeEach(function () {
1550 client = make_rfb();
b1dee947
SR
1551 client._fb_name = 'some device';
1552 client._fb_width = 640;
1553 client._fb_height = 20;
1554 });
1555
1556 describe('Framebuffer Update Handling', function () {
b1dee947
SR
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
3949a095 1586 push16(data, rect_cnt || rect_data.length);
b1dee947
SR
1587 }
1588
1589 for (var i = 0; i < rect_data.length; i++) {
1590 if (rect_info[i]) {
3949a095
SR
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);
b1dee947
SR
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 () {
89d2837f 1604 var expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: function() {}};
2ba767a7 1605 RFB.messages.fbUpdateRequest(expected_msg, true, 0, 0, 640, 20);
b1dee947
SR
1606
1607 client._framebufferUpdate = function () { return true; };
1608 client._sock._websocket._receive_data(new Uint8Array([0]));
1609
9ff86fb7 1610 expect(client._sock).to.have.sent(expected_msg._sQ);
b1dee947
SR
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 () {
89d2837f 1619 var expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: function() {}};
2ba767a7 1620 RFB.messages.fbUpdateRequest(expected_msg, true, 0, 0, 640, 20);
b1dee947
SR
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
9535539b 1626 client._framebufferUpdate = function () { this._sock.rQskip8(); return true; }; // we magically have enough data
b1dee947
SR
1627 // 247 should *not* be used as the message type here
1628 client._sock._websocket._receive_data(new Uint8Array([247]));
9ff86fb7 1629 expect(client._sock).to.have.sent(expected_msg._sQ);
b1dee947
SR
1630 });
1631
2ba767a7 1632 it('should not send a request in continuous updates mode', function () {
76a86ff5 1633 client._enabledContinuousUpdates = true;
1634 client._framebufferUpdate = function () { return true; };
76a86ff5 1635 client._sock._websocket._receive_data(new Uint8Array([0]));
1636
1637 expect(client._sock._websocket._get_sent_data()).to.have.length(0);
1638 });
1639
b1dee947 1640 it('should fail on an unsupported encoding', function () {
3bb12056 1641 sinon.spy(client, "_fail");
b1dee947
SR
1642 var rect_info = { x: 8, y: 11, width: 27, height: 32, encoding: 234 };
1643 send_fbu_msg([rect_info], [[]], client);
3bb12056 1644 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
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);
02329ab1 1652 client._display.blitRgbxImage(0, 0, 4, 2, new Uint8Array(target_data_check_arr.slice(0, 32)), 0);
b1dee947
SR
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}];
02329ab1 1656 // data says [{ old_x: 2, old_y: 0 }, { old_x: 0, old_y: 0 }]
b1dee947
SR
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 () {
b1dee947 1664 beforeEach(function () {
b1dee947
SR
1665 // a really small frame
1666 client._fb_width = 4;
1667 client._fb_height = 4;
69411b9e 1668 client._fb_depth = 24;
02329ab1 1669 client._display.resize(4, 4);
b1dee947
SR
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
69411b9e
PO
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
b1dee947
SR
1702 it('should handle the COPYRECT encoding', function () {
1703 // seed some initial data to copy
02329ab1 1704 client._display.blitRgbxImage(0, 0, 4, 2, new Uint8Array(target_data_check_arr.slice(0, 32)), 0);
b1dee947
SR
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 = [];
3949a095
SR
1720 push32(rect, 2); // 2 subrects
1721 push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
b1dee947
SR
1722 rect.push(0xff); // becomes ff0000ff --> #0000FF color
1723 rect.push(0x00);
1724 rect.push(0x00);
1725 rect.push(0xff);
3949a095
SR
1726 push16(rect, 0); // x: 0
1727 push16(rect, 0); // y: 0
1728 push16(rect, 2); // width: 2
1729 push16(rect, 2); // height: 2
b1dee947
SR
1730 rect.push(0xff); // becomes ff0000ff --> #0000FF color
1731 rect.push(0x00);
1732 rect.push(0x00);
1733 rect.push(0xff);
3949a095
SR
1734 push16(rect, 2); // x: 2
1735 push16(rect, 2); // y: 2
1736 push16(rect, 2); // width: 2
1737 push16(rect, 2); // height: 2
b1dee947
SR
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 () {
b1dee947
SR
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
3949a095 1748 push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
b1dee947
SR
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);
3949a095 1780 push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
b1dee947
SR
1781 send_fbu_msg(info, [rect], client);
1782
1783 var expected = [];
3949a095 1784 for (var i = 0; i < 16; i++) { push32(expected, 0xff00ff); }
b1dee947
SR
1785 expect(client._display).to.have.displayed(new Uint8Array(expected));
1786 });
1787
40ac6f0a
RK
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;
02329ab1 1791 client._display.resize(8, 4);
40ac6f0a 1792
4865278d 1793 var info = [{ x: 0, y: 0, width: 32, height: 4, encoding: 0x05 }];
40ac6f0a
RK
1794
1795 var rect = [];
1796
1797 // send a bg frame
1798 rect.push(0x02);
3949a095 1799 push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
40ac6f0a
RK
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;
3949a095
SR
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
40ac6f0a
RK
1810 expect(client._display).to.have.displayed(new Uint8Array(expected));
1811 });
1812
b1dee947
SR
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
3949a095 1817 push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
b1dee947
SR
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
3949a095 1843 push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
b1dee947
SR
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 () {
3bb12056 1869 sinon.spy(client,"_fail");
b1dee947
SR
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);
3bb12056 1873 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
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 () {
e89eef94 1886 var spy = sinon.spy();
b1dee947
SR
1887 sinon.spy(client._display, 'resize');
1888 send_fbu_msg([{ x: 0, y: 0, width: 20, height: 50, encoding: -223 }], [[]], client);
1889
b1dee947
SR
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
4dec490a 1897 describe('the ExtendedDesktopSize pseudo-encoding handler', function () {
e89eef94
PO
1898 var resizeSpy;
1899
4dec490a 1900 beforeEach(function () {
4dec490a 1901 // a really small frame
1902 client._fb_width = 4;
1903 client._fb_height = 4;
02329ab1 1904 client._display.resize(4, 4);
4dec490a 1905 sinon.spy(client._display, 'resize');
e89eef94 1906 resizeSpy = sinon.spy();
4dec490a 1907 });
1908
1909 function make_screen_data (nr_of_screens) {
1910 var data = [];
3949a095
SR
1911 push8(data, nr_of_screens); // number-of-screens
1912 push8(data, 0); // padding
1913 push16(data, 0); // padding
4dec490a 1914 for (var i=0; i<nr_of_screens; i += 1) {
3949a095
SR
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
4dec490a 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
4dec490a 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);
4dec490a 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
4dec490a 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);
4dec490a 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
4dec490a 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);
4dec490a 1968 });
1969
1970 it('should not handle a failed request', function () {
798340b9 1971 var reason_for_change = 1; // requested by this client
4dec490a 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;
4dec490a 1982 });
1983 });
1984
b1dee947
SR
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 () {
b1dee947
SR
1990 send_fbu_msg([{ x: 0, y: 0, width: 0, height: 0, encoding: -224}], [[]], client, 100);
1991 expect(client._FBU.rects).to.equal(0);
b1dee947
SR
1992 });
1993 });
1994 });
1995
b1dee947 1996 describe('XVP Message Handling', function () {
b1dee947 1997 it('should set the XVP version and fire the callback with the version on XVP_INIT', function () {
e89eef94
PO
1998 var spy = sinon.spy();
1999 client.addEventListener("capabilities", spy);
b1dee947
SR
2000 client._sock._websocket._receive_data(new Uint8Array([250, 0, 10, 1]));
2001 expect(client._rfb_xvp_ver).to.equal(10);
e89eef94
PO
2002 expect(spy).to.have.been.calledOnce;
2003 expect(spy.args[0][0].detail.capabilities.power).to.be.true;
747b4623 2004 expect(client.capabilities.power).to.be.true;
b1dee947
SR
2005 });
2006
2007 it('should fail on unknown XVP message types', function () {
3bb12056 2008 sinon.spy(client, "_fail");
b1dee947 2009 client._sock._websocket._receive_data(new Uint8Array([250, 0, 10, 237]));
3bb12056 2010 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
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];
3949a095 2017 push32(data, expected_str.length);
b1dee947 2018 for (var i = 0; i < expected_str.length; i++) { data.push(expected_str.charCodeAt(i)); }
e89eef94
PO
2019 var spy = sinon.spy();
2020 client.addEventListener("clipboard", spy);
b1dee947
SR
2021
2022 client._sock._websocket._receive_data(new Uint8Array(data));
b1dee947 2023 expect(spy).to.have.been.calledOnce;
e89eef94 2024 expect(spy.args[0][0].detail.text).to.equal(expected_str);
b1dee947
SR
2025 });
2026
2027 it('should fire the bell callback on Bell', function () {
e89eef94
PO
2028 var spy = sinon.spy();
2029 client.addEventListener("bell", spy);
b1dee947 2030 client._sock._websocket._receive_data(new Uint8Array([2]));
e89eef94 2031 expect(spy).to.have.been.calledOnce;
b1dee947
SR
2032 });
2033
3df13262 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
76a86ff5 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
91d5c625 2085 client._resize(450, 160);
76a86ff5 2086
2087 expect(client._sock._websocket._get_sent_data()).to.have.length(0);
2088
2089 client._enabledContinuousUpdates = true;
2090
91d5c625 2091 client._resize(90, 700);
76a86ff5 2092
2093 expect(client._sock).to.have.sent(expected_msg._sQ);
2094 });
2095
b1dee947 2096 it('should fail on an unknown message type', function () {
3bb12056 2097 sinon.spy(client, "_fail");
b1dee947 2098 client._sock._websocket._receive_data(new Uint8Array([87]));
3bb12056 2099 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
2100 });
2101 });
2102
2103 describe('Asynchronous Events', function () {
057b8fec
PO
2104 var client;
2105 beforeEach(function () {
2106 client = make_rfb();
057b8fec 2107 });
b1dee947 2108
057b8fec 2109 describe('Mouse event handlers', function () {
b1dee947 2110 it('should not send button messages in view-only mode', function () {
747b4623 2111 client._viewOnly = true;
057b8fec 2112 sinon.spy(client._sock, 'flush');
747b4623 2113 client._handleMouseButton(0, 0, 1, 0x001);
9ff86fb7 2114 expect(client._sock.flush).to.not.have.been.called;
b1dee947
SR
2115 });
2116
2117 it('should not send movement messages in view-only mode', function () {
747b4623 2118 client._viewOnly = true;
057b8fec 2119 sinon.spy(client._sock, 'flush');
747b4623 2120 client._handleMouseMove(0, 0);
9ff86fb7 2121 expect(client._sock.flush).to.not.have.been.called;
b1dee947
SR
2122 });
2123
2124 it('should send a pointer event on mouse button presses', function () {
747b4623 2125 client._handleMouseButton(10, 12, 1, 0x001);
89d2837f 2126 var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: function () {}};
9ff86fb7
SR
2127 RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x001);
2128 expect(client._sock).to.have.sent(pointer_msg._sQ);
b1dee947
SR
2129 });
2130
d02a99f0 2131 it('should send a mask of 1 on mousedown', function () {
747b4623 2132 client._handleMouseButton(10, 12, 1, 0x001);
89d2837f 2133 var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: function () {}};
cf0236de 2134 RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x001);
9ff86fb7 2135 expect(client._sock).to.have.sent(pointer_msg._sQ);
d02a99f0
SR
2136 });
2137
2138 it('should send a mask of 0 on mouseup', function () {
3b4fd003 2139 client._mouse_buttonMask = 0x001;
747b4623 2140 client._handleMouseButton(10, 12, 0, 0x001);
89d2837f 2141 var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: function () {}};
9ff86fb7
SR
2142 RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x000);
2143 expect(client._sock).to.have.sent(pointer_msg._sQ);
d02a99f0
SR
2144 });
2145
b1dee947 2146 it('should send a pointer event on mouse movement', function () {
747b4623 2147 client._handleMouseMove(10, 12);
89d2837f 2148 var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: function () {}};
9ff86fb7
SR
2149 RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x000);
2150 expect(client._sock).to.have.sent(pointer_msg._sQ);
b1dee947
SR
2151 });
2152
2153 it('should set the button mask so that future mouse movements use it', function () {
747b4623
PO
2154 client._handleMouseButton(10, 12, 1, 0x010);
2155 client._handleMouseMove(13, 9);
89d2837f 2156 var pointer_msg = {_sQ: new Uint8Array(12), _sQlen: 0, flush: function () {}};
9ff86fb7
SR
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);
b1dee947 2160 });
b1dee947
SR
2161 });
2162
2163 describe('Keyboard Event Handlers', function () {
b1dee947 2164 it('should send a key message on a key press', function () {
8f06673a 2165 var keyevent = {};
747b4623 2166 client._handleKeyEvent(0x41, 'KeyA', true);
89d2837f 2167 var key_msg = {_sQ: new Uint8Array(8), _sQlen: 0, flush: function () {}};
d0703d1b 2168 RFB.messages.keyEvent(key_msg, 0x41, 1);
9ff86fb7 2169 expect(client._sock).to.have.sent(key_msg._sQ);
b1dee947
SR
2170 });
2171
2172 it('should not send messages in view-only mode', function () {
747b4623 2173 client._viewOnly = true;
057b8fec 2174 sinon.spy(client._sock, 'flush');
747b4623 2175 client._handleKeyEvent('a', 'KeyA', true);
9ff86fb7 2176 expect(client._sock.flush).to.not.have.been.called;
b1dee947
SR
2177 });
2178 });
2179
2180 describe('WebSocket event handlers', function () {
b1dee947
SR
2181 // message events
2182 it ('should do nothing if we receive an empty message and have nothing in the queue', function () {
b1dee947 2183 client._normal_msg = sinon.spy();
38781d93 2184 client._sock._websocket._receive_data(new Uint8Array([]));
b1dee947
SR
2185 expect(client._normal_msg).to.not.have.been.called;
2186 });
2187
c2a4d3ef 2188 it('should handle a message in the connected state as a normal message', function () {
b1dee947 2189 client._normal_msg = sinon.spy();
38781d93 2190 client._sock._websocket._receive_data(new Uint8Array([1, 2, 3]));
b1dee947
SR
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 () {
2f4516f2 2195 client._rfb_connection_state = 'connecting';
c00ee156 2196 client._rfb_init_state = 'ProtocolVersion';
b1dee947 2197 client._init_msg = sinon.spy();
38781d93 2198 client._sock._websocket._receive_data(new Uint8Array([1, 2, 3]));
b1dee947
SR
2199 expect(client._init_msg).to.have.been.calledOnce;
2200 });
2201
9535539b 2202 it('should process all normal messages directly', function () {
e89eef94
PO
2203 var spy = sinon.spy();
2204 client.addEventListener("bell", spy);
b1dee947 2205 client._sock._websocket._receive_data(new Uint8Array([0x02, 0x02]));
e89eef94 2206 expect(spy).to.have.been.calledTwice;
b1dee947
SR
2207 });
2208
2209 // open events
c2a4d3ef 2210 it('should update the state to ProtocolVersion on open (if the state is "connecting")', function () {
9b84f516 2211 client = new RFB(document.createElement('div'), 'wss://host:8675');
2f4516f2 2212 this.clock.tick();
b1dee947 2213 client._sock._websocket._open();
c00ee156 2214 expect(client._rfb_init_state).to.equal('ProtocolVersion');
b1dee947
SR
2215 });
2216
2217 it('should fail if we are not currently ready to connect and we get an "open" event', function () {
3bb12056 2218 sinon.spy(client, "_fail");
bb25d3d6 2219 client._rfb_connection_state = 'connected';
b1dee947 2220 client._sock._websocket._open();
3bb12056 2221 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
2222 });
2223
2224 // close events
c2a4d3ef 2225 it('should transition to "disconnected" from "disconnecting" on a close event', function () {
bb25d3d6
PO
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;
b1dee947 2231 client._sock._websocket.close();
c00ee156 2232 expect(client._rfb_connection_state).to.equal('disconnected');
b1dee947
SR
2233 });
2234
b45905ab 2235 it('should fail if we get a close event while connecting', function () {
3bb12056 2236 sinon.spy(client, "_fail");
b45905ab 2237 client._rfb_connection_state = 'connecting';
b1dee947 2238 client._sock._websocket.close();
3bb12056 2239 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
2240 });
2241
155d78b3
JS
2242 it('should unregister close event handler', function () {
2243 sinon.spy(client._sock, 'off');
bb25d3d6 2244 client.disconnect();
155d78b3
JS
2245 client._sock._websocket.close();
2246 expect(client._sock.off).to.have.been.calledWith('close');
2247 });
2248
b1dee947
SR
2249 // error events do nothing
2250 });
2251 });
2252});