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