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