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