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