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