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