]> git.proxmox.com Git - mirror_novnc.git/blame - tests/test.rfb.js
Merge branch 'png_cursor' of https://github.com/CendioOssman/noVNC
[mirror_novnc.git] / tests / test.rfb.js
CommitLineData
b1dee947
SR
1/* jshint expr: true */
2var assert = chai.assert;
3var expect = chai.expect;
4
dfae3209
SR
5import RFB from '../core/rfb.js';
6import Websock from '../core/websock.js';
7
8import FakeWebSocket from './fake.websocket.js';
9import './assertions';
10import 'sinon';
11import sinonChai from '../node_modules/sinon-chai/lib/sinon-chai.js'
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
1160 it('should set the true color mode on the display to the configuration variable', function () {
1161 client.set_true_color(false);
1162 sinon.spy(client._display, 'set_true_color');
1163 send_server_init({ true_color: 1 }, client);
1164 expect(client._display.set_true_color).to.have.been.calledOnce;
1165 expect(client._display.set_true_color).to.have.been.calledWith(false);
1166 });
1167
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
1189 it('should set the BPP and depth to 4 and 3 respectively if in true color mode', function () {
1190 client.set_true_color(true);
1191 send_server_init({}, client);
1192 expect(client._fb_Bpp).to.equal(4);
1193 expect(client._fb_depth).to.equal(3);
1194 });
1195
1196 it('should set the BPP and depth to 1 and 1 respectively if not in true color mode', function () {
1197 client.set_true_color(false);
1198 send_server_init({}, client);
1199 expect(client._fb_Bpp).to.equal(1);
1200 expect(client._fb_depth).to.equal(1);
1201 });
1202
1203 // TODO(directxman12): test the various options in this configuration matrix
1204 it('should reply with the pixel format, client encodings, and initial update request', function () {
1205 client.set_true_color(true);
1206 client.set_local_cursor(false);
9ff86fb7 1207 // we skip the cursor encoding
89d2837f 1208 var expected = {_sQ: new Uint8Array(34 + 4 * (client._encodings.length - 1)),
1209 _sQlen: 0,
1210 flush: function () {}};
9ff86fb7
SR
1211 RFB.messages.pixelFormat(expected, 4, 3, true);
1212 RFB.messages.clientEncodings(expected, client._encodings, false, true);
37195e4b 1213 RFB.messages.fbUpdateRequest(expected, false, 0, 0, 27, 32);
b1dee947
SR
1214
1215 send_server_init({ width: 27, height: 32 }, client);
9ff86fb7 1216 expect(client._sock).to.have.sent(expected._sQ);
b1dee947
SR
1217 });
1218
c2a4d3ef 1219 it('should transition to the "connected" state', function () {
b1dee947 1220 send_server_init({}, client);
c2a4d3ef 1221 expect(client._rfb_connection_state).to.equal('connected');
b1dee947
SR
1222 });
1223 });
1224 });
1225
1226 describe('Protocol Message Processing After Completing Initialization', function () {
1227 var client;
1228
1229 beforeEach(function () {
1230 client = make_rfb();
1231 client.connect('host', 8675);
1232 client._sock._websocket._open();
c2a4d3ef 1233 client._rfb_connection_state = 'connected';
b1dee947
SR
1234 client._fb_name = 'some device';
1235 client._fb_width = 640;
1236 client._fb_height = 20;
1237 });
1238
1239 describe('Framebuffer Update Handling', function () {
1240 var client;
1241
1242 beforeEach(function () {
1243 client = make_rfb();
1244 client.connect('host', 8675);
1245 client._sock._websocket._open();
c2a4d3ef 1246 client._rfb_connection_state = 'connected';
b1dee947
SR
1247 client._fb_name = 'some device';
1248 client._fb_width = 640;
1249 client._fb_height = 20;
1250 });
1251
1252 var target_data_arr = [
1253 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
1254 0x00, 0xff, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
1255 0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255,
1256 0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255
1257 ];
1258 var target_data;
1259
1260 var target_data_check_arr = [
1261 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
1262 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
1263 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
1264 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
1265 ];
1266 var target_data_check;
1267
1268 before(function () {
1269 // NB(directxman12): PhantomJS 1.x doesn't implement Uint8ClampedArray
1270 target_data = new Uint8Array(target_data_arr);
1271 target_data_check = new Uint8Array(target_data_check_arr);
1272 });
1273
1274 function send_fbu_msg (rect_info, rect_data, client, rect_cnt) {
1275 var data = [];
1276
1277 if (!rect_cnt || rect_cnt > -1) {
1278 // header
1279 data.push(0); // msg type
1280 data.push(0); // padding
3949a095 1281 push16(data, rect_cnt || rect_data.length);
b1dee947
SR
1282 }
1283
1284 for (var i = 0; i < rect_data.length; i++) {
1285 if (rect_info[i]) {
3949a095
SR
1286 push16(data, rect_info[i].x);
1287 push16(data, rect_info[i].y);
1288 push16(data, rect_info[i].width);
1289 push16(data, rect_info[i].height);
1290 push32(data, rect_info[i].encoding);
b1dee947
SR
1291 }
1292 data = data.concat(rect_data[i]);
1293 }
1294
1295 client._sock._websocket._receive_data(new Uint8Array(data));
1296 }
1297
1298 it('should send an update request if there is sufficient 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 client._framebufferUpdate = function () { return true; };
1303 client._sock._websocket._receive_data(new Uint8Array([0]));
1304
9ff86fb7 1305 expect(client._sock).to.have.sent(expected_msg._sQ);
b1dee947
SR
1306 });
1307
1308 it('should not send an update request if we need more data', function () {
1309 client._sock._websocket._receive_data(new Uint8Array([0]));
1310 expect(client._sock._websocket._get_sent_data()).to.have.length(0);
1311 });
1312
1313 it('should resume receiving an update if we previously did not have enough data', function () {
89d2837f 1314 var expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: function() {}};
2ba767a7 1315 RFB.messages.fbUpdateRequest(expected_msg, true, 0, 0, 640, 20);
b1dee947
SR
1316
1317 // just enough to set FBU.rects
1318 client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 3]));
1319 expect(client._sock._websocket._get_sent_data()).to.have.length(0);
1320
9535539b 1321 client._framebufferUpdate = function () { this._sock.rQskip8(); return true; }; // we magically have enough data
b1dee947
SR
1322 // 247 should *not* be used as the message type here
1323 client._sock._websocket._receive_data(new Uint8Array([247]));
9ff86fb7 1324 expect(client._sock).to.have.sent(expected_msg._sQ);
b1dee947
SR
1325 });
1326
2ba767a7 1327 it('should not send a request in continuous updates mode', function () {
76a86ff5 1328 client._enabledContinuousUpdates = true;
1329 client._framebufferUpdate = function () { return true; };
76a86ff5 1330 client._sock._websocket._receive_data(new Uint8Array([0]));
1331
1332 expect(client._sock._websocket._get_sent_data()).to.have.length(0);
1333 });
1334
b1dee947
SR
1335 it('should parse out information from a header before any actual data comes in', function () {
1336 client.set_onFBUReceive(sinon.spy());
1337 var rect_info = { x: 8, y: 11, width: 27, height: 32, encoding: 0x02, encodingName: 'RRE' };
1338 send_fbu_msg([rect_info], [[]], client);
1339
1340 var spy = client.get_onFBUReceive();
1341 expect(spy).to.have.been.calledOnce;
1342 expect(spy).to.have.been.calledWith(sinon.match.any, rect_info);
1343 });
1344
1345 it('should fire onFBUComplete when the update is complete', function () {
1346 client.set_onFBUComplete(sinon.spy());
1347 var rect_info = { x: 8, y: 11, width: 27, height: 32, encoding: -224, encodingName: 'last_rect' };
1348 send_fbu_msg([rect_info], [[]], client); // last_rect
1349
1350 var spy = client.get_onFBUComplete();
1351 expect(spy).to.have.been.calledOnce;
1352 expect(spy).to.have.been.calledWith(sinon.match.any, rect_info);
1353 });
1354
1355 it('should not fire onFBUComplete if we have not finished processing the update', function () {
1356 client.set_onFBUComplete(sinon.spy());
1357 var rect_info = { x: 8, y: 11, width: 27, height: 32, encoding: 0x00, encodingName: 'RAW' };
1358 send_fbu_msg([rect_info], [[]], client);
1359 expect(client.get_onFBUComplete()).to.not.have.been.called;
1360 });
1361
1362 it('should call the appropriate encoding handler', function () {
1363 client._encHandlers[0x02] = sinon.spy();
1364 var rect_info = { x: 8, y: 11, width: 27, height: 32, encoding: 0x02 };
1365 send_fbu_msg([rect_info], [[]], client);
1366 expect(client._encHandlers[0x02]).to.have.been.calledOnce;
1367 });
1368
1369 it('should fail on an unsupported encoding', function () {
3bb12056 1370 sinon.spy(client, "_fail");
b1dee947
SR
1371 var rect_info = { x: 8, y: 11, width: 27, height: 32, encoding: 234 };
1372 send_fbu_msg([rect_info], [[]], client);
3bb12056 1373 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
1374 });
1375
1376 it('should be able to pause and resume receiving rects if not enought data', function () {
1377 // seed some initial data to copy
1378 client._fb_width = 4;
1379 client._fb_height = 4;
1380 client._display.resize(4, 4);
02329ab1 1381 client._display.blitRgbxImage(0, 0, 4, 2, new Uint8Array(target_data_check_arr.slice(0, 32)), 0);
b1dee947
SR
1382
1383 var info = [{ x: 0, y: 2, width: 2, height: 2, encoding: 0x01},
1384 { x: 2, y: 2, width: 2, height: 2, encoding: 0x01}];
02329ab1 1385 // data says [{ old_x: 2, old_y: 0 }, { old_x: 0, old_y: 0 }]
b1dee947
SR
1386 var rects = [[0, 2, 0, 0], [0, 0, 0, 0]];
1387 send_fbu_msg([info[0]], [rects[0]], client, 2);
1388 send_fbu_msg([info[1]], [rects[1]], client, -1);
1389 expect(client._display).to.have.displayed(target_data_check);
1390 });
1391
1392 describe('Message Encoding Handlers', function () {
1393 var client;
1394
1395 beforeEach(function () {
1396 client = make_rfb();
1397 client.connect('host', 8675);
1398 client._sock._websocket._open();
c2a4d3ef 1399 client._rfb_connection_state = 'connected';
b1dee947
SR
1400 client._fb_name = 'some device';
1401 // a really small frame
1402 client._fb_width = 4;
1403 client._fb_height = 4;
02329ab1 1404 client._display.resize(4, 4);
b1dee947
SR
1405 client._fb_Bpp = 4;
1406 });
1407
1408 it('should handle the RAW encoding', function () {
1409 var info = [{ x: 0, y: 0, width: 2, height: 2, encoding: 0x00 },
1410 { x: 2, y: 0, width: 2, height: 2, encoding: 0x00 },
1411 { x: 0, y: 2, width: 4, height: 1, encoding: 0x00 },
1412 { x: 0, y: 3, width: 4, height: 1, encoding: 0x00 }];
1413 // data is in bgrx
1414 var rects = [
1415 [0x00, 0x00, 0xff, 0, 0x00, 0xff, 0x00, 0, 0x00, 0xff, 0x00, 0, 0x00, 0x00, 0xff, 0],
1416 [0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0],
1417 [0xff, 0x00, 0xee, 0, 0xff, 0xee, 0x00, 0, 0xff, 0xee, 0xaa, 0, 0xff, 0xee, 0xab, 0],
1418 [0xff, 0x00, 0xee, 0, 0xff, 0xee, 0x00, 0, 0xff, 0xee, 0xaa, 0, 0xff, 0xee, 0xab, 0]];
1419 send_fbu_msg(info, rects, client);
1420 expect(client._display).to.have.displayed(target_data);
1421 });
1422
1423 it('should handle the COPYRECT encoding', function () {
1424 // seed some initial data to copy
02329ab1 1425 client._display.blitRgbxImage(0, 0, 4, 2, new Uint8Array(target_data_check_arr.slice(0, 32)), 0);
b1dee947
SR
1426
1427 var info = [{ x: 0, y: 2, width: 2, height: 2, encoding: 0x01},
1428 { x: 2, y: 2, width: 2, height: 2, encoding: 0x01}];
1429 // data says [{ old_x: 0, old_y: 0 }, { old_x: 0, old_y: 0 }]
1430 var rects = [[0, 2, 0, 0], [0, 0, 0, 0]];
1431 send_fbu_msg(info, rects, client);
1432 expect(client._display).to.have.displayed(target_data_check);
1433 });
1434
1435 // TODO(directxman12): for encodings with subrects, test resuming on partial send?
1436 // TODO(directxman12): test rre_chunk_sz (related to above about subrects)?
1437
1438 it('should handle the RRE encoding', function () {
1439 var info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x02 }];
1440 var rect = [];
3949a095
SR
1441 push32(rect, 2); // 2 subrects
1442 push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
b1dee947
SR
1443 rect.push(0xff); // becomes ff0000ff --> #0000FF color
1444 rect.push(0x00);
1445 rect.push(0x00);
1446 rect.push(0xff);
3949a095
SR
1447 push16(rect, 0); // x: 0
1448 push16(rect, 0); // y: 0
1449 push16(rect, 2); // width: 2
1450 push16(rect, 2); // height: 2
b1dee947
SR
1451 rect.push(0xff); // becomes ff0000ff --> #0000FF color
1452 rect.push(0x00);
1453 rect.push(0x00);
1454 rect.push(0xff);
3949a095
SR
1455 push16(rect, 2); // x: 2
1456 push16(rect, 2); // y: 2
1457 push16(rect, 2); // width: 2
1458 push16(rect, 2); // height: 2
b1dee947
SR
1459
1460 send_fbu_msg(info, [rect], client);
1461 expect(client._display).to.have.displayed(target_data_check);
1462 });
1463
1464 describe('the HEXTILE encoding handler', function () {
1465 var client;
1466 beforeEach(function () {
1467 client = make_rfb();
1468 client.connect('host', 8675);
1469 client._sock._websocket._open();
c2a4d3ef 1470 client._rfb_connection_state = 'connected';
b1dee947
SR
1471 client._fb_name = 'some device';
1472 // a really small frame
1473 client._fb_width = 4;
1474 client._fb_height = 4;
02329ab1 1475 client._display.resize(4, 4);
b1dee947
SR
1476 client._fb_Bpp = 4;
1477 });
1478
1479 it('should handle a tile with fg, bg specified, normal subrects', function () {
1480 var info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
1481 var rect = [];
1482 rect.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects
3949a095 1483 push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
b1dee947
SR
1484 rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
1485 rect.push(0x00);
1486 rect.push(0x00);
1487 rect.push(0xff);
1488 rect.push(2); // 2 subrects
1489 rect.push(0); // x: 0, y: 0
1490 rect.push(1 | (1 << 4)); // width: 2, height: 2
1491 rect.push(2 | (2 << 4)); // x: 2, y: 2
1492 rect.push(1 | (1 << 4)); // width: 2, height: 2
1493 send_fbu_msg(info, [rect], client);
1494 expect(client._display).to.have.displayed(target_data_check);
1495 });
1496
1497 it('should handle a raw tile', function () {
1498 var info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
1499 var rect = [];
1500 rect.push(0x01); // raw
1501 for (var i = 0; i < target_data.length; i += 4) {
1502 rect.push(target_data[i + 2]);
1503 rect.push(target_data[i + 1]);
1504 rect.push(target_data[i]);
1505 rect.push(target_data[i + 3]);
1506 }
1507 send_fbu_msg(info, [rect], client);
1508 expect(client._display).to.have.displayed(target_data);
1509 });
1510
1511 it('should handle a tile with only bg specified (solid bg)', function () {
1512 var info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
1513 var rect = [];
1514 rect.push(0x02);
3949a095 1515 push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
b1dee947
SR
1516 send_fbu_msg(info, [rect], client);
1517
1518 var expected = [];
3949a095 1519 for (var i = 0; i < 16; i++) { push32(expected, 0xff00ff); }
b1dee947
SR
1520 expect(client._display).to.have.displayed(new Uint8Array(expected));
1521 });
1522
40ac6f0a
RK
1523 it('should handle a tile with only bg specified and an empty frame afterwards', function () {
1524 // set the width so we can have two tiles
1525 client._fb_width = 8;
02329ab1 1526 client._display.resize(8, 4);
40ac6f0a 1527
4865278d 1528 var info = [{ x: 0, y: 0, width: 32, height: 4, encoding: 0x05 }];
40ac6f0a
RK
1529
1530 var rect = [];
1531
1532 // send a bg frame
1533 rect.push(0x02);
3949a095 1534 push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
40ac6f0a
RK
1535
1536 // send an empty frame
1537 rect.push(0x00);
1538
1539 send_fbu_msg(info, [rect], client);
1540
1541 var expected = [];
1542 var i;
3949a095
SR
1543 for (i = 0; i < 16; i++) { push32(expected, 0xff00ff); } // rect 1: solid
1544 for (i = 0; i < 16; i++) { push32(expected, 0xff00ff); } // rect 2: same bkground color
40ac6f0a
RK
1545 expect(client._display).to.have.displayed(new Uint8Array(expected));
1546 });
1547
b1dee947
SR
1548 it('should handle a tile with bg and coloured subrects', function () {
1549 var info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
1550 var rect = [];
1551 rect.push(0x02 | 0x08 | 0x10); // bg spec, anysubrects, colouredsubrects
3949a095 1552 push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
b1dee947
SR
1553 rect.push(2); // 2 subrects
1554 rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
1555 rect.push(0x00);
1556 rect.push(0x00);
1557 rect.push(0xff);
1558 rect.push(0); // x: 0, y: 0
1559 rect.push(1 | (1 << 4)); // width: 2, height: 2
1560 rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
1561 rect.push(0x00);
1562 rect.push(0x00);
1563 rect.push(0xff);
1564 rect.push(2 | (2 << 4)); // x: 2, y: 2
1565 rect.push(1 | (1 << 4)); // width: 2, height: 2
1566 send_fbu_msg(info, [rect], client);
1567 expect(client._display).to.have.displayed(target_data_check);
1568 });
1569
1570 it('should carry over fg and bg colors from the previous tile if not specified', function () {
1571 client._fb_width = 4;
1572 client._fb_height = 17;
1573 client._display.resize(4, 17);
1574
1575 var info = [{ x: 0, y: 0, width: 4, height: 17, encoding: 0x05}];
1576 var rect = [];
1577 rect.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects
3949a095 1578 push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
b1dee947
SR
1579 rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
1580 rect.push(0x00);
1581 rect.push(0x00);
1582 rect.push(0xff);
1583 rect.push(8); // 8 subrects
1584 var i;
1585 for (i = 0; i < 4; i++) {
1586 rect.push((0 << 4) | (i * 4)); // x: 0, y: i*4
1587 rect.push(1 | (1 << 4)); // width: 2, height: 2
1588 rect.push((2 << 4) | (i * 4 + 2)); // x: 2, y: i * 4 + 2
1589 rect.push(1 | (1 << 4)); // width: 2, height: 2
1590 }
1591 rect.push(0x08); // anysubrects
1592 rect.push(1); // 1 subrect
1593 rect.push(0); // x: 0, y: 0
1594 rect.push(1 | (1 << 4)); // width: 2, height: 2
1595 send_fbu_msg(info, [rect], client);
1596
1597 var expected = [];
1598 for (i = 0; i < 4; i++) { expected = expected.concat(target_data_check_arr); }
1599 expected = expected.concat(target_data_check_arr.slice(0, 16));
1600 expect(client._display).to.have.displayed(new Uint8Array(expected));
1601 });
1602
1603 it('should fail on an invalid subencoding', function () {
3bb12056 1604 sinon.spy(client,"_fail");
b1dee947
SR
1605 var info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
1606 var rects = [[45]]; // an invalid subencoding
1607 send_fbu_msg(info, rects, client);
3bb12056 1608 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
1609 });
1610 });
1611
1612 it.skip('should handle the TIGHT encoding', function () {
1613 // TODO(directxman12): test this
1614 });
1615
1616 it.skip('should handle the TIGHT_PNG encoding', function () {
1617 // TODO(directxman12): test this
1618 });
1619
1620 it('should handle the DesktopSize pseduo-encoding', function () {
1621 client.set_onFBResize(sinon.spy());
1622 sinon.spy(client._display, 'resize');
1623 send_fbu_msg([{ x: 0, y: 0, width: 20, height: 50, encoding: -223 }], [[]], client);
1624
1625 var spy = client.get_onFBResize();
1626 expect(spy).to.have.been.calledOnce;
1627 expect(spy).to.have.been.calledWith(sinon.match.any, 20, 50);
1628
1629 expect(client._fb_width).to.equal(20);
1630 expect(client._fb_height).to.equal(50);
1631
1632 expect(client._display.resize).to.have.been.calledOnce;
1633 expect(client._display.resize).to.have.been.calledWith(20, 50);
1634 });
1635
4dec490a 1636 describe('the ExtendedDesktopSize pseudo-encoding handler', function () {
1637 var client;
1638
1639 beforeEach(function () {
1640 client = make_rfb();
1641 client.connect('host', 8675);
1642 client._sock._websocket._open();
c2a4d3ef 1643 client._rfb_connection_state = 'connected';
4dec490a 1644 client._fb_name = 'some device';
1645 client._supportsSetDesktopSize = false;
1646 // a really small frame
1647 client._fb_width = 4;
1648 client._fb_height = 4;
02329ab1 1649 client._display.resize(4, 4);
4dec490a 1650 client._fb_Bpp = 4;
1651 sinon.spy(client._display, 'resize');
1652 client.set_onFBResize(sinon.spy());
1653 });
1654
1655 function make_screen_data (nr_of_screens) {
1656 var data = [];
3949a095
SR
1657 push8(data, nr_of_screens); // number-of-screens
1658 push8(data, 0); // padding
1659 push16(data, 0); // padding
4dec490a 1660 for (var i=0; i<nr_of_screens; i += 1) {
3949a095
SR
1661 push32(data, 0); // id
1662 push16(data, 0); // x-position
1663 push16(data, 0); // y-position
1664 push16(data, 20); // width
1665 push16(data, 50); // height
1666 push32(data, 0); // flags
4dec490a 1667 }
1668 return data;
1669 }
1670
1671 it('should handle a resize requested by this client', function () {
1672 var reason_for_change = 1; // requested by this client
1673 var status_code = 0; // No error
1674
1675 send_fbu_msg([{ x: reason_for_change, y: status_code,
1676 width: 20, height: 50, encoding: -308 }],
1677 make_screen_data(1), client);
1678
1679 expect(client._supportsSetDesktopSize).to.be.true;
1680 expect(client._fb_width).to.equal(20);
1681 expect(client._fb_height).to.equal(50);
1682
1683 expect(client._display.resize).to.have.been.calledOnce;
1684 expect(client._display.resize).to.have.been.calledWith(20, 50);
1685
1686 var spy = client.get_onFBResize();
1687 expect(spy).to.have.been.calledOnce;
1688 expect(spy).to.have.been.calledWith(sinon.match.any, 20, 50);
1689 });
1690
1691 it('should handle a resize requested by another client', function () {
1692 var reason_for_change = 2; // requested by another client
1693 var status_code = 0; // No error
1694
1695 send_fbu_msg([{ x: reason_for_change, y: status_code,
1696 width: 20, height: 50, encoding: -308 }],
1697 make_screen_data(1), client);
1698
1699 expect(client._supportsSetDesktopSize).to.be.true;
1700 expect(client._fb_width).to.equal(20);
1701 expect(client._fb_height).to.equal(50);
1702
1703 expect(client._display.resize).to.have.been.calledOnce;
1704 expect(client._display.resize).to.have.been.calledWith(20, 50);
1705
1706 var spy = client.get_onFBResize();
1707 expect(spy).to.have.been.calledOnce;
1708 expect(spy).to.have.been.calledWith(sinon.match.any, 20, 50);
1709 });
1710
1711 it('should be able to recieve requests which contain data for multiple screens', function () {
1712 var reason_for_change = 2; // requested by another client
1713 var status_code = 0; // No error
1714
1715 send_fbu_msg([{ x: reason_for_change, y: status_code,
1716 width: 60, height: 50, encoding: -308 }],
1717 make_screen_data(3), client);
1718
1719 expect(client._supportsSetDesktopSize).to.be.true;
1720 expect(client._fb_width).to.equal(60);
1721 expect(client._fb_height).to.equal(50);
1722
1723 expect(client._display.resize).to.have.been.calledOnce;
1724 expect(client._display.resize).to.have.been.calledWith(60, 50);
1725
1726 var spy = client.get_onFBResize();
1727 expect(spy).to.have.been.calledOnce;
1728 expect(spy).to.have.been.calledWith(sinon.match.any, 60, 50);
1729 });
1730
1731 it('should not handle a failed request', function () {
798340b9 1732 var reason_for_change = 1; // requested by this client
4dec490a 1733 var status_code = 1; // Resize is administratively prohibited
1734
1735 send_fbu_msg([{ x: reason_for_change, y: status_code,
1736 width: 20, height: 50, encoding: -308 }],
1737 make_screen_data(1), client);
1738
1739 expect(client._fb_width).to.equal(4);
1740 expect(client._fb_height).to.equal(4);
1741
1742 expect(client._display.resize).to.not.have.been.called;
1743
1744 var spy = client.get_onFBResize();
1745 expect(spy).to.not.have.been.called;
1746 });
1747 });
1748
b1dee947
SR
1749 it.skip('should handle the Cursor pseudo-encoding', function () {
1750 // TODO(directxman12): test
1751 });
1752
1753 it('should handle the last_rect pseudo-encoding', function () {
1754 client.set_onFBUReceive(sinon.spy());
1755 send_fbu_msg([{ x: 0, y: 0, width: 0, height: 0, encoding: -224}], [[]], client, 100);
1756 expect(client._FBU.rects).to.equal(0);
1757 expect(client.get_onFBUReceive()).to.have.been.calledOnce;
1758 });
1759 });
1760 });
1761
1762 it('should set the colour map on the display on SetColourMapEntries', function () {
1763 var expected_cm = [];
1764 var data = [1, 0, 0, 1, 0, 4];
1765 var i;
1766 for (i = 0; i < 4; i++) {
1767 expected_cm[i + 1] = [i * 10, i * 10 + 1, i * 10 + 2];
3949a095
SR
1768 push16(data, expected_cm[i + 1][2] << 8);
1769 push16(data, expected_cm[i + 1][1] << 8);
1770 push16(data, expected_cm[i + 1][0] << 8);
b1dee947
SR
1771 }
1772
1773 client._sock._websocket._receive_data(new Uint8Array(data));
1774 expect(client._display.get_colourMap()).to.deep.equal(expected_cm);
1775 });
1776
1777 describe('XVP Message Handling', function () {
1778 beforeEach(function () {
1779 client = make_rfb();
1780 client.connect('host', 8675);
1781 client._sock._websocket._open();
c2a4d3ef 1782 client._rfb_connection_state = 'connected';
b1dee947
SR
1783 client._fb_name = 'some device';
1784 client._fb_width = 27;
1785 client._fb_height = 32;
1786 });
1787
a7127fee
SM
1788 it('should send a notification on XVP_FAIL', function () {
1789 client.set_onNotification(sinon.spy());
b1dee947 1790 client._sock._websocket._receive_data(new Uint8Array([250, 0, 10, 0]));
a7127fee
SM
1791 var spy = client.get_onNotification();
1792 expect(spy).to.have.been.calledOnce;
1793 expect(spy.args[0][1]).to.equal('XVP Operation Failed');
b1dee947
SR
1794 });
1795
1796 it('should set the XVP version and fire the callback with the version on XVP_INIT', function () {
1797 client.set_onXvpInit(sinon.spy());
1798 client._sock._websocket._receive_data(new Uint8Array([250, 0, 10, 1]));
1799 expect(client._rfb_xvp_ver).to.equal(10);
1800 expect(client.get_onXvpInit()).to.have.been.calledOnce;
1801 expect(client.get_onXvpInit()).to.have.been.calledWith(10);
1802 });
1803
1804 it('should fail on unknown XVP message types', function () {
3bb12056 1805 sinon.spy(client, "_fail");
b1dee947 1806 client._sock._websocket._receive_data(new Uint8Array([250, 0, 10, 237]));
3bb12056 1807 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
1808 });
1809 });
1810
1811 it('should fire the clipboard callback with the retrieved text on ServerCutText', function () {
1812 var expected_str = 'cheese!';
1813 var data = [3, 0, 0, 0];
3949a095 1814 push32(data, expected_str.length);
b1dee947
SR
1815 for (var i = 0; i < expected_str.length; i++) { data.push(expected_str.charCodeAt(i)); }
1816 client.set_onClipboard(sinon.spy());
1817
1818 client._sock._websocket._receive_data(new Uint8Array(data));
1819 var spy = client.get_onClipboard();
1820 expect(spy).to.have.been.calledOnce;
1821 expect(spy.args[0][1]).to.equal(expected_str);
1822 });
1823
1824 it('should fire the bell callback on Bell', function () {
1825 client.set_onBell(sinon.spy());
1826 client._sock._websocket._receive_data(new Uint8Array([2]));
1827 expect(client.get_onBell()).to.have.been.calledOnce;
1828 });
1829
3df13262 1830 it('should respond correctly to ServerFence', function () {
1831 var expected_msg = {_sQ: new Uint8Array(16), _sQlen: 0, flush: function() {}};
1832 var incoming_msg = {_sQ: new Uint8Array(16), _sQlen: 0, flush: function() {}};
1833
1834 var payload = "foo\x00ab9";
1835
1836 // ClientFence and ServerFence are identical in structure
1837 RFB.messages.clientFence(expected_msg, (1<<0) | (1<<1), payload);
1838 RFB.messages.clientFence(incoming_msg, 0xffffffff, payload);
1839
1840 client._sock._websocket._receive_data(incoming_msg._sQ);
1841
1842 expect(client._sock).to.have.sent(expected_msg._sQ);
1843
1844 expected_msg._sQlen = 0;
1845 incoming_msg._sQlen = 0;
1846
1847 RFB.messages.clientFence(expected_msg, (1<<0), payload);
1848 RFB.messages.clientFence(incoming_msg, (1<<0) | (1<<31), payload);
1849
1850 client._sock._websocket._receive_data(incoming_msg._sQ);
1851
1852 expect(client._sock).to.have.sent(expected_msg._sQ);
1853 });
1854
76a86ff5 1855 it('should enable continuous updates on first EndOfContinousUpdates', function () {
1856 var expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: function() {}};
1857
1858 RFB.messages.enableContinuousUpdates(expected_msg, true, 0, 0, 640, 20);
1859
1860 expect(client._enabledContinuousUpdates).to.be.false;
1861
1862 client._sock._websocket._receive_data(new Uint8Array([150]));
1863
1864 expect(client._enabledContinuousUpdates).to.be.true;
1865 expect(client._sock).to.have.sent(expected_msg._sQ);
1866 });
1867
1868 it('should disable continuous updates on subsequent EndOfContinousUpdates', function () {
1869 client._enabledContinuousUpdates = true;
1870 client._supportsContinuousUpdates = true;
1871
1872 client._sock._websocket._receive_data(new Uint8Array([150]));
1873
1874 expect(client._enabledContinuousUpdates).to.be.false;
1875 });
1876
1877 it('should update continuous updates on resize', function () {
1878 var expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: function() {}};
1879 RFB.messages.enableContinuousUpdates(expected_msg, true, 0, 0, 90, 700);
1880
1881 client._FBU.width = 450;
1882 client._FBU.height = 160;
1883
1884 client._encHandlers.handle_FB_resize();
1885
1886 expect(client._sock._websocket._get_sent_data()).to.have.length(0);
1887
1888 client._enabledContinuousUpdates = true;
1889
1890 client._FBU.width = 90;
1891 client._FBU.height = 700;
1892
1893 client._encHandlers.handle_FB_resize();
1894
1895 expect(client._sock).to.have.sent(expected_msg._sQ);
1896 });
1897
b1dee947 1898 it('should fail on an unknown message type', function () {
3bb12056 1899 sinon.spy(client, "_fail");
b1dee947 1900 client._sock._websocket._receive_data(new Uint8Array([87]));
3bb12056 1901 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
1902 });
1903 });
1904
1905 describe('Asynchronous Events', function () {
1906 describe('Mouse event handlers', function () {
1907 var client;
1908 beforeEach(function () {
1909 client = make_rfb();
9ff86fb7
SR
1910 client._sock = new Websock();
1911 client._sock.open('ws://', 'binary');
1912 client._sock._websocket._open();
1913 sinon.spy(client._sock, 'flush');
c2a4d3ef 1914 client._rfb_connection_state = 'connected';
b1dee947
SR
1915 });
1916
1917 it('should not send button messages in view-only mode', function () {
1918 client._view_only = true;
1919 client._mouse._onMouseButton(0, 0, 1, 0x001);
9ff86fb7 1920 expect(client._sock.flush).to.not.have.been.called;
b1dee947
SR
1921 });
1922
1923 it('should not send movement messages in view-only mode', function () {
1924 client._view_only = true;
1925 client._mouse._onMouseMove(0, 0);
9ff86fb7 1926 expect(client._sock.flush).to.not.have.been.called;
b1dee947
SR
1927 });
1928
1929 it('should send a pointer event on mouse button presses', function () {
1930 client._mouse._onMouseButton(10, 12, 1, 0x001);
89d2837f 1931 var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: function () {}};
9ff86fb7
SR
1932 RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x001);
1933 expect(client._sock).to.have.sent(pointer_msg._sQ);
b1dee947
SR
1934 });
1935
d02a99f0
SR
1936 it('should send a mask of 1 on mousedown', function () {
1937 client._mouse._onMouseButton(10, 12, 1, 0x001);
89d2837f 1938 var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: function () {}};
cf0236de 1939 RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x001);
9ff86fb7 1940 expect(client._sock).to.have.sent(pointer_msg._sQ);
d02a99f0
SR
1941 });
1942
1943 it('should send a mask of 0 on mouseup', function () {
3b4fd003 1944 client._mouse_buttonMask = 0x001;
d02a99f0 1945 client._mouse._onMouseButton(10, 12, 0, 0x001);
89d2837f 1946 var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: function () {}};
9ff86fb7
SR
1947 RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x000);
1948 expect(client._sock).to.have.sent(pointer_msg._sQ);
d02a99f0
SR
1949 });
1950
b1dee947
SR
1951 it('should send a pointer event on mouse movement', function () {
1952 client._mouse._onMouseMove(10, 12);
89d2837f 1953 var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: function () {}};
9ff86fb7
SR
1954 RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x000);
1955 expect(client._sock).to.have.sent(pointer_msg._sQ);
b1dee947
SR
1956 });
1957
1958 it('should set the button mask so that future mouse movements use it', function () {
1959 client._mouse._onMouseButton(10, 12, 1, 0x010);
b1dee947 1960 client._mouse._onMouseMove(13, 9);
89d2837f 1961 var pointer_msg = {_sQ: new Uint8Array(12), _sQlen: 0, flush: function () {}};
9ff86fb7
SR
1962 RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x010);
1963 RFB.messages.pointerEvent(pointer_msg, 13, 9, 0x010);
1964 expect(client._sock).to.have.sent(pointer_msg._sQ);
b1dee947
SR
1965 });
1966
1967 // NB(directxman12): we don't need to test not sending messages in
1968 // non-normal modes, since we haven't grabbed input
1969 // yet (grabbing input should be checked in the lifecycle tests).
1970
1971 it('should not send movement messages when viewport dragging', function () {
1972 client._viewportDragging = true;
636be753 1973 client._display.viewportChangePos = sinon.spy();
b1dee947 1974 client._mouse._onMouseMove(13, 9);
9ff86fb7 1975 expect(client._sock.flush).to.not.have.been.called;
b1dee947
SR
1976 });
1977
1978 it('should not send button messages when initiating viewport dragging', function () {
1979 client._viewportDrag = true;
1980 client._mouse._onMouseButton(13, 9, 0x001);
9ff86fb7 1981 expect(client._sock.flush).to.not.have.been.called;
b1dee947
SR
1982 });
1983
1984 it('should be initiate viewport dragging on a button down event, if enabled', function () {
1985 client._viewportDrag = true;
1986 client._mouse._onMouseButton(13, 9, 0x001);
1987 expect(client._viewportDragging).to.be.true;
1988 expect(client._viewportDragPos).to.deep.equal({ x: 13, y: 9 });
1989 });
1990
1991 it('should terminate viewport dragging on a button up event, if enabled', function () {
1992 client._viewportDrag = true;
1993 client._viewportDragging = true;
1994 client._mouse._onMouseButton(13, 9, 0x000);
1995 expect(client._viewportDragging).to.be.false;
1996 });
1997
1998 it('if enabled, viewportDragging should occur on mouse movement while a button is down', function () {
0613d188
SM
1999 var oldX = 123;
2000 var oldY = 109;
2001 var newX = 123 + 11 * window.devicePixelRatio;
2002 var newY = 109 + 4 * window.devicePixelRatio;
2003
b1dee947
SR
2004 client._viewportDrag = true;
2005 client._viewportDragging = true;
12ae8b3d 2006 client._viewportHasMoved = false;
0613d188 2007 client._viewportDragPos = { x: oldX, y: oldY };
636be753 2008 client._display.viewportChangePos = sinon.spy();
b1dee947 2009
0613d188 2010 client._mouse._onMouseMove(newX, newY);
b1dee947
SR
2011
2012 expect(client._viewportDragging).to.be.true;
057cfc7c 2013 expect(client._viewportHasMoved).to.be.true;
0613d188 2014 expect(client._viewportDragPos).to.deep.equal({ x: newX, y: newY });
636be753 2015 expect(client._display.viewportChangePos).to.have.been.calledOnce;
0613d188 2016 expect(client._display.viewportChangePos).to.have.been.calledWith(oldX - newX, oldY - newY);
b1dee947
SR
2017 });
2018 });
2019
2020 describe('Keyboard Event Handlers', function () {
2021 var client;
2022 beforeEach(function () {
2023 client = make_rfb();
9ff86fb7
SR
2024 client._sock = new Websock();
2025 client._sock.open('ws://', 'binary');
2026 client._sock._websocket._open();
2027 sinon.spy(client._sock, 'flush');
94f5cf05
PO
2028 client._rfb_connection_state = 'connected';
2029 client._view_only = false;
b1dee947
SR
2030 });
2031
2032 it('should send a key message on a key press', function () {
8f06673a 2033 var keyevent = {};
d0703d1b 2034 client._keyboard._onKeyEvent(0x41, 'KeyA', true);
89d2837f 2035 var key_msg = {_sQ: new Uint8Array(8), _sQlen: 0, flush: function () {}};
d0703d1b 2036 RFB.messages.keyEvent(key_msg, 0x41, 1);
9ff86fb7 2037 expect(client._sock).to.have.sent(key_msg._sQ);
b1dee947
SR
2038 });
2039
2040 it('should not send messages in view-only mode', function () {
2041 client._view_only = true;
d0703d1b 2042 client._keyboard._onKeyEvent('a', 'KeyA', true);
9ff86fb7 2043 expect(client._sock.flush).to.not.have.been.called;
b1dee947
SR
2044 });
2045 });
2046
2047 describe('WebSocket event handlers', function () {
2048 var client;
2049 beforeEach(function () {
2050 client = make_rfb();
2051 this.clock = sinon.useFakeTimers();
2052 });
2053
2054 afterEach(function () { this.clock.restore(); });
2055
2056 // message events
2057 it ('should do nothing if we receive an empty message and have nothing in the queue', function () {
2058 client.connect('host', 8675);
c2a4d3ef 2059 client._rfb_connection_state = 'connected';
b1dee947 2060 client._normal_msg = sinon.spy();
38781d93 2061 client._sock._websocket._receive_data(new Uint8Array([]));
b1dee947
SR
2062 expect(client._normal_msg).to.not.have.been.called;
2063 });
2064
c2a4d3ef 2065 it('should handle a message in the connected state as a normal message', function () {
b1dee947 2066 client.connect('host', 8675);
c2a4d3ef 2067 client._rfb_connection_state = 'connected';
b1dee947 2068 client._normal_msg = sinon.spy();
38781d93 2069 client._sock._websocket._receive_data(new Uint8Array([1, 2, 3]));
b1dee947
SR
2070 expect(client._normal_msg).to.have.been.calledOnce;
2071 });
2072
2073 it('should handle a message in any non-disconnected/failed state like an init message', function () {
2074 client.connect('host', 8675);
c00ee156 2075 client._rfb_init_state = 'ProtocolVersion';
b1dee947 2076 client._init_msg = sinon.spy();
38781d93 2077 client._sock._websocket._receive_data(new Uint8Array([1, 2, 3]));
b1dee947
SR
2078 expect(client._init_msg).to.have.been.calledOnce;
2079 });
2080
9535539b 2081 it('should process all normal messages directly', function () {
b1dee947
SR
2082 client.connect('host', 8675);
2083 client._sock._websocket._open();
c2a4d3ef 2084 client._rfb_connection_state = 'connected';
b1dee947
SR
2085 client.set_onBell(sinon.spy());
2086 client._sock._websocket._receive_data(new Uint8Array([0x02, 0x02]));
b1dee947
SR
2087 expect(client.get_onBell()).to.have.been.calledTwice;
2088 });
2089
2090 // open events
c2a4d3ef 2091 it('should update the state to ProtocolVersion on open (if the state is "connecting")', function () {
b1dee947
SR
2092 client.connect('host', 8675);
2093 client._sock._websocket._open();
c00ee156 2094 expect(client._rfb_init_state).to.equal('ProtocolVersion');
b1dee947
SR
2095 });
2096
2097 it('should fail if we are not currently ready to connect and we get an "open" event', function () {
3bb12056 2098 sinon.spy(client, "_fail");
b1dee947 2099 client.connect('host', 8675);
c00ee156 2100 client._rfb_connection_state = 'some_other_state';
b1dee947 2101 client._sock._websocket._open();
3bb12056 2102 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
2103 });
2104
2105 // close events
c2a4d3ef 2106 it('should transition to "disconnected" from "disconnecting" on a close event', function () {
b1dee947 2107 client.connect('host', 8675);
c2a4d3ef 2108 client._rfb_connection_state = 'disconnecting';
b1dee947 2109 client._sock._websocket.close();
c00ee156 2110 expect(client._rfb_connection_state).to.equal('disconnected');
b1dee947
SR
2111 });
2112
b45905ab 2113 it('should fail if we get a close event while connecting', function () {
3bb12056 2114 sinon.spy(client, "_fail");
b1dee947 2115 client.connect('host', 8675);
b45905ab 2116 client._rfb_connection_state = 'connecting';
b1dee947 2117 client._sock._websocket.close();
3bb12056 2118 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
2119 });
2120
b2e961d4
SM
2121 it('should fail if we get a close event while disconnected', function () {
2122 sinon.spy(client, "_fail");
2123 client.connect('host', 8675);
2124 client._rfb_connection_state = 'disconnected';
2125 client._sock._websocket.close();
2126 expect(client._fail).to.have.been.calledOnce;
2127 });
2128
155d78b3
JS
2129 it('should unregister close event handler', function () {
2130 sinon.spy(client._sock, 'off');
2131 client.connect('host', 8675);
c2a4d3ef 2132 client._rfb_connection_state = 'disconnecting';
155d78b3
JS
2133 client._sock._websocket.close();
2134 expect(client._sock.off).to.have.been.calledWith('close');
2135 });
2136
b1dee947
SR
2137 // error events do nothing
2138 });
2139 });
2140});