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