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