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