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