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