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