]> git.proxmox.com Git - mirror_novnc.git/blame - tests/test.rfb.js
Lower level check for framebuffer update requests
[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 () {
89d2837f 135 var expected = {_sQ: new Uint8Array(48), _sQlen: 0, flush: function () {}};
9ff86fb7
SR
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 () {
89d2837f 171 var expected = {_sQ: new Uint8Array(8), _sQlen: 0, flush: function () {}};
9ff86fb7 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 () {
89d2837f 178 var expected = {_sQ: new Uint8Array(16), _sQlen: 0, flush: function () {}};
9ff86fb7
SR
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 () {
89d2837f 209 var expected = {_sQ: new Uint8Array(11), _sQlen: 0, flush: function () {}};
9ff86fb7 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
ae116051 222 describe("#requestDesktopSize", function () {
4dec490a 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
ae116051 247 client.requestDesktopSize(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;
ae116051 253 client.requestDesktopSize(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";
ae116051 259 client.requestDesktopSize(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
b1dee947
SR
574 it('should send back the interpreted version', function () {
575 send_ver('004.000', client);
576
577 var expected_str = 'RFB 003.008\n';
578 var expected = [];
579 for (var i = 0; i < expected_str.length; i++) {
580 expected[i] = expected_str.charCodeAt(i);
581 }
582
9ff86fb7 583 expect(client._sock).to.have.sent(new Uint8Array(expected));
b1dee947
SR
584 });
585
586 it('should transition to the Security state on successful negotiation', function () {
587 send_ver('003.008', client);
588 expect(client._rfb_state).to.equal('Security');
589 });
590 });
591
592 describe('Security', function () {
593 var client;
594
595 beforeEach(function () {
596 client = make_rfb();
597 client.connect('host', 8675);
598 client._sock._websocket._open();
599 client._rfb_state = 'Security';
600 });
601
602 it('should simply receive the auth scheme when for versions < 3.7', function () {
603 client._rfb_version = 3.6;
604 var auth_scheme_raw = [1, 2, 3, 4];
605 var auth_scheme = (auth_scheme_raw[0] << 24) + (auth_scheme_raw[1] << 16) +
606 (auth_scheme_raw[2] << 8) + auth_scheme_raw[3];
607 client._sock._websocket._receive_data(auth_scheme_raw);
608 expect(client._rfb_auth_scheme).to.equal(auth_scheme);
609 });
610
611 it('should choose for the most prefered scheme possible for versions >= 3.7', function () {
612 client._rfb_version = 3.7;
613 var auth_schemes = [2, 1, 2];
614 client._sock._websocket._receive_data(auth_schemes);
615 expect(client._rfb_auth_scheme).to.equal(2);
9ff86fb7 616 expect(client._sock).to.have.sent(new Uint8Array([2]));
b1dee947
SR
617 });
618
619 it('should fail if there are no supported schemes for versions >= 3.7', function () {
620 client._rfb_version = 3.7;
621 var auth_schemes = [1, 32];
622 client._sock._websocket._receive_data(auth_schemes);
623 expect(client._rfb_state).to.equal('failed');
624 });
625
626 it('should fail with the appropriate message if no types are sent for versions >= 3.7', function () {
627 client._rfb_version = 3.7;
628 var failure_data = [0, 0, 0, 0, 6, 119, 104, 111, 111, 112, 115];
629 sinon.spy(client, '_fail');
630 client._sock._websocket._receive_data(failure_data);
631
632 expect(client._fail).to.have.been.calledTwice;
633 expect(client._fail).to.have.been.calledWith('Security failure: whoops');
634 });
635
636 it('should transition to the Authentication state and continue on successful negotiation', function () {
637 client._rfb_version = 3.7;
638 var auth_schemes = [1, 1];
639 client._negotiate_authentication = sinon.spy();
640 client._sock._websocket._receive_data(auth_schemes);
641 expect(client._rfb_state).to.equal('Authentication');
642 expect(client._negotiate_authentication).to.have.been.calledOnce;
643 });
644 });
645
646 describe('Authentication', function () {
647 var client;
648
649 beforeEach(function () {
650 client = make_rfb();
651 client.connect('host', 8675);
652 client._sock._websocket._open();
653 client._rfb_state = 'Security';
654 });
655
656 function send_security(type, cl) {
657 cl._sock._websocket._receive_data(new Uint8Array([1, type]));
658 }
659
660 it('should fail on auth scheme 0 (pre 3.7) with the given message', function () {
661 client._rfb_version = 3.6;
662 var err_msg = "Whoopsies";
663 var data = [0, 0, 0, 0];
664 var err_len = err_msg.length;
665 data.push32(err_len);
666 for (var i = 0; i < err_len; i++) {
667 data.push(err_msg.charCodeAt(i));
668 }
669
670 sinon.spy(client, '_fail');
671 client._sock._websocket._receive_data(new Uint8Array(data));
672 expect(client._rfb_state).to.equal('failed');
673 expect(client._fail).to.have.been.calledWith('Auth failure: Whoopsies');
674 });
675
676 it('should transition straight to SecurityResult on "no auth" (1) for versions >= 3.8', function () {
677 client._rfb_version = 3.8;
678 send_security(1, client);
679 expect(client._rfb_state).to.equal('SecurityResult');
680 });
681
682 it('should transition straight to ClientInitialisation on "no auth" for versions < 3.8', function () {
683 client._rfb_version = 3.7;
684 sinon.spy(client, '_updateState');
685 send_security(1, client);
686 expect(client._updateState).to.have.been.calledWith('ClientInitialisation');
687 expect(client._rfb_state).to.equal('ServerInitialisation');
688 });
689
690 it('should fail on an unknown auth scheme', function () {
691 client._rfb_version = 3.8;
692 send_security(57, client);
693 expect(client._rfb_state).to.equal('failed');
694 });
695
696 describe('VNC Authentication (type 2) Handler', function () {
697 var client;
698
699 beforeEach(function () {
700 client = make_rfb();
701 client.connect('host', 8675);
702 client._sock._websocket._open();
703 client._rfb_state = 'Security';
704 client._rfb_version = 3.8;
705 });
706
707 it('should transition to the "password" state if missing a password', function () {
708 send_security(2, client);
709 expect(client._rfb_state).to.equal('password');
710 });
711
712 it('should encrypt the password with DES and then send it back', function () {
713 client._rfb_password = 'passwd';
714 send_security(2, client);
715 client._sock._websocket._get_sent_data(); // skip the choice of auth reply
716
717 var challenge = [];
718 for (var i = 0; i < 16; i++) { challenge[i] = i; }
719 client._sock._websocket._receive_data(new Uint8Array(challenge));
720
721 var des_pass = RFB.genDES('passwd', challenge);
9ff86fb7 722 expect(client._sock).to.have.sent(new Uint8Array(des_pass));
b1dee947
SR
723 });
724
725 it('should transition to SecurityResult immediately after sending the password', function () {
726 client._rfb_password = 'passwd';
727 send_security(2, client);
728
729 var challenge = [];
730 for (var i = 0; i < 16; i++) { challenge[i] = i; }
731 client._sock._websocket._receive_data(new Uint8Array(challenge));
732
733 expect(client._rfb_state).to.equal('SecurityResult');
734 });
735 });
736
737 describe('XVP Authentication (type 22) Handler', function () {
738 var client;
739
740 beforeEach(function () {
741 client = make_rfb();
742 client.connect('host', 8675);
743 client._sock._websocket._open();
744 client._rfb_state = 'Security';
745 client._rfb_version = 3.8;
746 });
747
748 it('should fall through to standard VNC authentication upon completion', function () {
749 client.set_xvp_password_sep('#');
750 client._rfb_password = 'user#target#password';
751 client._negotiate_std_vnc_auth = sinon.spy();
752 send_security(22, client);
753 expect(client._negotiate_std_vnc_auth).to.have.been.calledOnce;
754 });
755
756 it('should transition to the "password" state if the passwords is missing', function() {
757 send_security(22, client);
758 expect(client._rfb_state).to.equal('password');
759 });
760
761 it('should transition to the "password" state if the passwords is improperly formatted', function() {
762 client._rfb_password = 'user@target';
763 send_security(22, client);
764 expect(client._rfb_state).to.equal('password');
765 });
766
767 it('should split the password, send the first two parts, and pass on the last part', function () {
768 client.set_xvp_password_sep('#');
769 client._rfb_password = 'user#target#password';
770 client._negotiate_std_vnc_auth = sinon.spy();
771
772 send_security(22, client);
773
774 expect(client._rfb_password).to.equal('password');
775
776 var expected = [22, 4, 6]; // auth selection, len user, len target
777 for (var i = 0; i < 10; i++) { expected[i+3] = 'usertarget'.charCodeAt(i); }
778
9ff86fb7 779 expect(client._sock).to.have.sent(new Uint8Array(expected));
b1dee947
SR
780 });
781 });
782
783 describe('TightVNC Authentication (type 16) Handler', function () {
784 var client;
785
786 beforeEach(function () {
787 client = make_rfb();
788 client.connect('host', 8675);
789 client._sock._websocket._open();
790 client._rfb_state = 'Security';
791 client._rfb_version = 3.8;
792 send_security(16, client);
793 client._sock._websocket._get_sent_data(); // skip the security reply
794 });
795
796 function send_num_str_pairs(pairs, client) {
797 var pairs_len = pairs.length;
798 var data = [];
799 data.push32(pairs_len);
800
801 for (var i = 0; i < pairs_len; i++) {
802 data.push32(pairs[i][0]);
803 var j;
804 for (j = 0; j < 4; j++) {
805 data.push(pairs[i][1].charCodeAt(j));
806 }
807 for (j = 0; j < 8; j++) {
808 data.push(pairs[i][2].charCodeAt(j));
809 }
810 }
811
812 client._sock._websocket._receive_data(new Uint8Array(data));
813 }
814
815 it('should skip tunnel negotiation if no tunnels are requested', function () {
816 client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0]));
817 expect(client._rfb_tightvnc).to.be.true;
818 });
819
820 it('should fail if no supported tunnels are listed', function () {
821 send_num_str_pairs([[123, 'OTHR', 'SOMETHNG']], client);
822 expect(client._rfb_state).to.equal('failed');
823 });
824
825 it('should choose the notunnel tunnel type', function () {
826 send_num_str_pairs([[0, 'TGHT', 'NOTUNNEL'], [123, 'OTHR', 'SOMETHNG']], client);
9ff86fb7 827 expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 0]));
b1dee947
SR
828 });
829
830 it('should continue to sub-auth negotiation after tunnel negotiation', function () {
831 send_num_str_pairs([[0, 'TGHT', 'NOTUNNEL']], client);
832 client._sock._websocket._get_sent_data(); // skip the tunnel choice here
833 send_num_str_pairs([[1, 'STDV', 'NOAUTH__']], client);
9ff86fb7 834 expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 1]));
b1dee947
SR
835 expect(client._rfb_state).to.equal('SecurityResult');
836 });
837
838 /*it('should attempt to use VNC auth over no auth when possible', function () {
839 client._rfb_tightvnc = true;
840 client._negotiate_std_vnc_auth = sinon.spy();
841 send_num_str_pairs([[1, 'STDV', 'NOAUTH__'], [2, 'STDV', 'VNCAUTH_']], client);
842 expect(client._sock).to.have.sent([0, 0, 0, 1]);
843 expect(client._negotiate_std_vnc_auth).to.have.been.calledOnce;
844 expect(client._rfb_auth_scheme).to.equal(2);
845 });*/ // while this would make sense, the original code doesn't actually do this
846
847 it('should accept the "no auth" auth type and transition to SecurityResult', function () {
848 client._rfb_tightvnc = true;
849 send_num_str_pairs([[1, 'STDV', 'NOAUTH__']], client);
9ff86fb7 850 expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 1]));
b1dee947
SR
851 expect(client._rfb_state).to.equal('SecurityResult');
852 });
853
854 it('should accept VNC authentication and transition to that', function () {
855 client._rfb_tightvnc = true;
856 client._negotiate_std_vnc_auth = sinon.spy();
857 send_num_str_pairs([[2, 'STDV', 'VNCAUTH__']], client);
9ff86fb7 858 expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 2]));
b1dee947
SR
859 expect(client._negotiate_std_vnc_auth).to.have.been.calledOnce;
860 expect(client._rfb_auth_scheme).to.equal(2);
861 });
862
863 it('should fail if there are no supported auth types', function () {
864 client._rfb_tightvnc = true;
865 send_num_str_pairs([[23, 'stdv', 'badval__']], client);
866 expect(client._rfb_state).to.equal('failed');
867 });
868 });
869 });
870
871 describe('SecurityResult', function () {
872 var client;
873
874 beforeEach(function () {
875 client = make_rfb();
876 client.connect('host', 8675);
877 client._sock._websocket._open();
878 client._rfb_state = 'SecurityResult';
879 });
880
881 it('should fall through to ClientInitialisation on a response code of 0', function () {
882 client._updateState = sinon.spy();
883 client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0]));
884 expect(client._updateState).to.have.been.calledOnce;
885 expect(client._updateState).to.have.been.calledWith('ClientInitialisation');
886 });
887
888 it('should fail on an error code of 1 with the given message for versions >= 3.8', function () {
889 client._rfb_version = 3.8;
890 sinon.spy(client, '_fail');
891 var failure_data = [0, 0, 0, 1, 0, 0, 0, 6, 119, 104, 111, 111, 112, 115];
892 client._sock._websocket._receive_data(new Uint8Array(failure_data));
893 expect(client._rfb_state).to.equal('failed');
894 expect(client._fail).to.have.been.calledWith('whoops');
895 });
896
897 it('should fail on an error code of 1 with a standard message for version < 3.8', function () {
898 client._rfb_version = 3.7;
899 client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 1]));
900 expect(client._rfb_state).to.equal('failed');
901 });
902 });
903
904 describe('ClientInitialisation', function () {
905 var client;
906
907 beforeEach(function () {
908 client = make_rfb();
909 client.connect('host', 8675);
910 client._sock._websocket._open();
911 client._rfb_state = 'SecurityResult';
912 });
913
914 it('should transition to the ServerInitialisation state', function () {
915 client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0]));
916 expect(client._rfb_state).to.equal('ServerInitialisation');
917 });
918
919 it('should send 1 if we are in shared mode', function () {
920 client.set_shared(true);
921 client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0]));
9ff86fb7 922 expect(client._sock).to.have.sent(new Uint8Array([1]));
b1dee947
SR
923 });
924
925 it('should send 0 if we are not in shared mode', function () {
926 client.set_shared(false);
927 client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0]));
9ff86fb7 928 expect(client._sock).to.have.sent(new Uint8Array([0]));
b1dee947
SR
929 });
930 });
931
932 describe('ServerInitialisation', function () {
933 var client;
934
935 beforeEach(function () {
936 client = make_rfb();
937 client.connect('host', 8675);
938 client._sock._websocket._open();
939 client._rfb_state = 'ServerInitialisation';
940 });
941
942 function send_server_init(opts, client) {
943 var full_opts = { width: 10, height: 12, bpp: 24, depth: 24, big_endian: 0,
944 true_color: 1, red_max: 255, green_max: 255, blue_max: 255,
945 red_shift: 16, green_shift: 8, blue_shift: 0, name: 'a name' };
946 for (var opt in opts) {
947 full_opts[opt] = opts[opt];
948 }
949 var data = [];
950
951 data.push16(full_opts.width);
952 data.push16(full_opts.height);
953
954 data.push(full_opts.bpp);
955 data.push(full_opts.depth);
956 data.push(full_opts.big_endian);
957 data.push(full_opts.true_color);
958
959 data.push16(full_opts.red_max);
960 data.push16(full_opts.green_max);
961 data.push16(full_opts.blue_max);
962 data.push8(full_opts.red_shift);
963 data.push8(full_opts.green_shift);
964 data.push8(full_opts.blue_shift);
965
966 // padding
967 data.push8(0);
968 data.push8(0);
969 data.push8(0);
970
971 client._sock._websocket._receive_data(new Uint8Array(data));
972
973 var name_data = [];
974 name_data.push32(full_opts.name.length);
975 for (var i = 0; i < full_opts.name.length; i++) {
976 name_data.push(full_opts.name.charCodeAt(i));
977 }
978 client._sock._websocket._receive_data(new Uint8Array(name_data));
979 }
980
981 it('should set the framebuffer width and height', function () {
982 send_server_init({ width: 32, height: 84 }, client);
983 expect(client._fb_width).to.equal(32);
984 expect(client._fb_height).to.equal(84);
985 });
986
987 // NB(sross): we just warn, not fail, for endian-ness and shifts, so we don't test them
988
989 it('should set the framebuffer name and call the callback', function () {
990 client.set_onDesktopName(sinon.spy());
991 send_server_init({ name: 'some name' }, client);
992
993 var spy = client.get_onDesktopName();
994 expect(client._fb_name).to.equal('some name');
995 expect(spy).to.have.been.calledOnce;
996 expect(spy.args[0][1]).to.equal('some name');
997 });
998
999 it('should handle the extended init message of the tight encoding', function () {
1000 // NB(sross): we don't actually do anything with it, so just test that we can
1001 // read it w/o throwing an error
1002 client._rfb_tightvnc = true;
1003 send_server_init({}, client);
1004
1005 var tight_data = [];
1006 tight_data.push16(1);
1007 tight_data.push16(2);
1008 tight_data.push16(3);
1009 tight_data.push16(0);
1010 for (var i = 0; i < 16 + 32 + 48; i++) {
1011 tight_data.push(i);
1012 }
1013 client._sock._websocket._receive_data(tight_data);
1014
1015 expect(client._rfb_state).to.equal('normal');
1016 });
1017
1018 it('should set the true color mode on the display to the configuration variable', function () {
1019 client.set_true_color(false);
1020 sinon.spy(client._display, 'set_true_color');
1021 send_server_init({ true_color: 1 }, client);
1022 expect(client._display.set_true_color).to.have.been.calledOnce;
1023 expect(client._display.set_true_color).to.have.been.calledWith(false);
1024 });
1025
1026 it('should call the resize callback and resize the display', function () {
1027 client.set_onFBResize(sinon.spy());
1028 sinon.spy(client._display, 'resize');
1029 send_server_init({ width: 27, height: 32 }, client);
1030
1031 var spy = client.get_onFBResize();
1032 expect(client._display.resize).to.have.been.calledOnce;
1033 expect(client._display.resize).to.have.been.calledWith(27, 32);
1034 expect(spy).to.have.been.calledOnce;
1035 expect(spy.args[0][1]).to.equal(27);
1036 expect(spy.args[0][2]).to.equal(32);
1037 });
1038
1039 it('should grab the mouse and keyboard', function () {
1040 sinon.spy(client._keyboard, 'grab');
1041 sinon.spy(client._mouse, 'grab');
1042 send_server_init({}, client);
1043 expect(client._keyboard.grab).to.have.been.calledOnce;
1044 expect(client._mouse.grab).to.have.been.calledOnce;
1045 });
1046
1047 it('should set the BPP and depth to 4 and 3 respectively if in true color mode', function () {
1048 client.set_true_color(true);
1049 send_server_init({}, client);
1050 expect(client._fb_Bpp).to.equal(4);
1051 expect(client._fb_depth).to.equal(3);
1052 });
1053
1054 it('should set the BPP and depth to 1 and 1 respectively if not in true color mode', function () {
1055 client.set_true_color(false);
1056 send_server_init({}, client);
1057 expect(client._fb_Bpp).to.equal(1);
1058 expect(client._fb_depth).to.equal(1);
1059 });
1060
1061 // TODO(directxman12): test the various options in this configuration matrix
1062 it('should reply with the pixel format, client encodings, and initial update request', function () {
1063 client.set_true_color(true);
1064 client.set_local_cursor(false);
9ff86fb7 1065 // we skip the cursor encoding
89d2837f 1066 var expected = {_sQ: new Uint8Array(34 + 4 * (client._encodings.length - 1)),
1067 _sQlen: 0,
1068 flush: function () {}};
9ff86fb7
SR
1069 RFB.messages.pixelFormat(expected, 4, 3, true);
1070 RFB.messages.clientEncodings(expected, client._encodings, false, true);
37195e4b 1071 RFB.messages.fbUpdateRequest(expected, false, 0, 0, 27, 32);
b1dee947
SR
1072
1073 send_server_init({ width: 27, height: 32 }, client);
9ff86fb7 1074 expect(client._sock).to.have.sent(expected._sQ);
b1dee947
SR
1075 });
1076
1077 it('should transition to the "normal" state', function () {
1078 send_server_init({}, client);
1079 expect(client._rfb_state).to.equal('normal');
1080 });
1081 });
1082 });
1083
1084 describe('Protocol Message Processing After Completing Initialization', function () {
1085 var client;
1086
1087 beforeEach(function () {
1088 client = make_rfb();
1089 client.connect('host', 8675);
1090 client._sock._websocket._open();
1091 client._rfb_state = 'normal';
1092 client._fb_name = 'some device';
1093 client._fb_width = 640;
1094 client._fb_height = 20;
1095 });
1096
1097 describe('Framebuffer Update Handling', function () {
1098 var client;
1099
1100 beforeEach(function () {
1101 client = make_rfb();
1102 client.connect('host', 8675);
1103 client._sock._websocket._open();
1104 client._rfb_state = 'normal';
1105 client._fb_name = 'some device';
1106 client._fb_width = 640;
1107 client._fb_height = 20;
1108 });
1109
1110 var target_data_arr = [
1111 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
1112 0x00, 0xff, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
1113 0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255,
1114 0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255
1115 ];
1116 var target_data;
1117
1118 var target_data_check_arr = [
1119 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
1120 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
1121 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
1122 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
1123 ];
1124 var target_data_check;
1125
1126 before(function () {
1127 // NB(directxman12): PhantomJS 1.x doesn't implement Uint8ClampedArray
1128 target_data = new Uint8Array(target_data_arr);
1129 target_data_check = new Uint8Array(target_data_check_arr);
1130 });
1131
1132 function send_fbu_msg (rect_info, rect_data, client, rect_cnt) {
1133 var data = [];
1134
1135 if (!rect_cnt || rect_cnt > -1) {
1136 // header
1137 data.push(0); // msg type
1138 data.push(0); // padding
1139 data.push16(rect_cnt || rect_data.length);
1140 }
1141
1142 for (var i = 0; i < rect_data.length; i++) {
1143 if (rect_info[i]) {
1144 data.push16(rect_info[i].x);
1145 data.push16(rect_info[i].y);
1146 data.push16(rect_info[i].width);
1147 data.push16(rect_info[i].height);
1148 data.push32(rect_info[i].encoding);
1149 }
1150 data = data.concat(rect_data[i]);
1151 }
1152
1153 client._sock._websocket._receive_data(new Uint8Array(data));
1154 }
1155
1156 it('should send an update request if there is sufficient data', function () {
89d2837f 1157 var expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: function() {}};
37195e4b 1158 RFB.messages.fbUpdateRequest(expected_msg, false, 0, 0, 240, 20);
b1dee947
SR
1159
1160 client._framebufferUpdate = function () { return true; };
1161 client._sock._websocket._receive_data(new Uint8Array([0]));
1162
9ff86fb7 1163 expect(client._sock).to.have.sent(expected_msg._sQ);
b1dee947
SR
1164 });
1165
1166 it('should not send an update request if we need more data', function () {
1167 client._sock._websocket._receive_data(new Uint8Array([0]));
1168 expect(client._sock._websocket._get_sent_data()).to.have.length(0);
1169 });
1170
1171 it('should resume receiving an update if we previously did not have enough data', function () {
89d2837f 1172 var expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: function() {}};
37195e4b 1173 RFB.messages.fbUpdateRequest(expected_msg, false, 0, 0, 240, 20);
b1dee947
SR
1174
1175 // just enough to set FBU.rects
1176 client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 3]));
1177 expect(client._sock._websocket._get_sent_data()).to.have.length(0);
1178
1179 client._framebufferUpdate = function () { return true; }; // we magically have enough data
1180 // 247 should *not* be used as the message type here
1181 client._sock._websocket._receive_data(new Uint8Array([247]));
9ff86fb7 1182 expect(client._sock).to.have.sent(expected_msg._sQ);
b1dee947
SR
1183 });
1184
37195e4b 1185 it('should send a request for both clean and dirty areas', function () {
1186 var expected_msg = {_sQ: new Uint8Array(20), _sQlen: 0, flush: function() {}};
1187 var expected_cdr = { cleanBox: { x: 0, y: 0, w: 120, h: 20 },
1188 dirtyBoxes: [ { x: 120, y: 0, w: 120, h: 20 } ] };
1189
1190 RFB.messages.fbUpdateRequest(expected_msg, true, 0, 0, 120, 20);
1191 RFB.messages.fbUpdateRequest(expected_msg, false, 120, 0, 120, 20);
1192
1193 client._framebufferUpdate = function () { return true; };
1194 client._display.getCleanDirtyReset = function () { return expected_cdr; };
1195 client._sock._websocket._receive_data(new Uint8Array([0]));
1196
1197 expect(client._sock).to.have.sent(expected_msg._sQ);
1198 });
1199
b1dee947
SR
1200 it('should parse out information from a header before any actual data comes in', function () {
1201 client.set_onFBUReceive(sinon.spy());
1202 var rect_info = { x: 8, y: 11, width: 27, height: 32, encoding: 0x02, encodingName: 'RRE' };
1203 send_fbu_msg([rect_info], [[]], client);
1204
1205 var spy = client.get_onFBUReceive();
1206 expect(spy).to.have.been.calledOnce;
1207 expect(spy).to.have.been.calledWith(sinon.match.any, rect_info);
1208 });
1209
1210 it('should fire onFBUComplete when the update is complete', function () {
1211 client.set_onFBUComplete(sinon.spy());
1212 var rect_info = { x: 8, y: 11, width: 27, height: 32, encoding: -224, encodingName: 'last_rect' };
1213 send_fbu_msg([rect_info], [[]], client); // last_rect
1214
1215 var spy = client.get_onFBUComplete();
1216 expect(spy).to.have.been.calledOnce;
1217 expect(spy).to.have.been.calledWith(sinon.match.any, rect_info);
1218 });
1219
1220 it('should not fire onFBUComplete if we have not finished processing the update', function () {
1221 client.set_onFBUComplete(sinon.spy());
1222 var rect_info = { x: 8, y: 11, width: 27, height: 32, encoding: 0x00, encodingName: 'RAW' };
1223 send_fbu_msg([rect_info], [[]], client);
1224 expect(client.get_onFBUComplete()).to.not.have.been.called;
1225 });
1226
1227 it('should call the appropriate encoding handler', function () {
1228 client._encHandlers[0x02] = sinon.spy();
1229 var rect_info = { x: 8, y: 11, width: 27, height: 32, encoding: 0x02 };
1230 send_fbu_msg([rect_info], [[]], client);
1231 expect(client._encHandlers[0x02]).to.have.been.calledOnce;
1232 });
1233
1234 it('should fail on an unsupported encoding', function () {
1235 client.set_onFBUReceive(sinon.spy());
1236 var rect_info = { x: 8, y: 11, width: 27, height: 32, encoding: 234 };
1237 send_fbu_msg([rect_info], [[]], client);
1238 expect(client._rfb_state).to.equal('failed');
1239 });
1240
1241 it('should be able to pause and resume receiving rects if not enought data', function () {
1242 // seed some initial data to copy
1243 client._fb_width = 4;
1244 client._fb_height = 4;
1245 client._display.resize(4, 4);
1246 var initial_data = client._display._drawCtx.createImageData(4, 2);
1247 var initial_data_arr = target_data_check_arr.slice(0, 32);
1248 for (var i = 0; i < 32; i++) { initial_data.data[i] = initial_data_arr[i]; }
1249 client._display._drawCtx.putImageData(initial_data, 0, 0);
1250
1251 var info = [{ x: 0, y: 2, width: 2, height: 2, encoding: 0x01},
1252 { x: 2, y: 2, width: 2, height: 2, encoding: 0x01}];
1253 // data says [{ old_x: 0, old_y: 0 }, { old_x: 0, old_y: 0 }]
1254 var rects = [[0, 2, 0, 0], [0, 0, 0, 0]];
1255 send_fbu_msg([info[0]], [rects[0]], client, 2);
1256 send_fbu_msg([info[1]], [rects[1]], client, -1);
1257 expect(client._display).to.have.displayed(target_data_check);
1258 });
1259
1260 describe('Message Encoding Handlers', function () {
1261 var client;
1262
1263 beforeEach(function () {
1264 client = make_rfb();
1265 client.connect('host', 8675);
1266 client._sock._websocket._open();
1267 client._rfb_state = 'normal';
1268 client._fb_name = 'some device';
1269 // a really small frame
1270 client._fb_width = 4;
1271 client._fb_height = 4;
1272 client._display._fb_width = 4;
1273 client._display._fb_height = 4;
2c9623b5
SR
1274 client._display._viewportLoc.w = 4;
1275 client._display._viewportLoc.h = 4;
b1dee947
SR
1276 client._fb_Bpp = 4;
1277 });
1278
1279 it('should handle the RAW encoding', function () {
1280 var info = [{ x: 0, y: 0, width: 2, height: 2, encoding: 0x00 },
1281 { x: 2, y: 0, width: 2, height: 2, encoding: 0x00 },
1282 { x: 0, y: 2, width: 4, height: 1, encoding: 0x00 },
1283 { x: 0, y: 3, width: 4, height: 1, encoding: 0x00 }];
1284 // data is in bgrx
1285 var rects = [
1286 [0x00, 0x00, 0xff, 0, 0x00, 0xff, 0x00, 0, 0x00, 0xff, 0x00, 0, 0x00, 0x00, 0xff, 0],
1287 [0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0],
1288 [0xff, 0x00, 0xee, 0, 0xff, 0xee, 0x00, 0, 0xff, 0xee, 0xaa, 0, 0xff, 0xee, 0xab, 0],
1289 [0xff, 0x00, 0xee, 0, 0xff, 0xee, 0x00, 0, 0xff, 0xee, 0xaa, 0, 0xff, 0xee, 0xab, 0]];
1290 send_fbu_msg(info, rects, client);
1291 expect(client._display).to.have.displayed(target_data);
1292 });
1293
1294 it('should handle the COPYRECT encoding', function () {
1295 // seed some initial data to copy
1296 var initial_data = client._display._drawCtx.createImageData(4, 2);
1297 var initial_data_arr = target_data_check_arr.slice(0, 32);
1298 for (var i = 0; i < 32; i++) { initial_data.data[i] = initial_data_arr[i]; }
1299 client._display._drawCtx.putImageData(initial_data, 0, 0);
1300
1301 var info = [{ x: 0, y: 2, width: 2, height: 2, encoding: 0x01},
1302 { x: 2, y: 2, width: 2, height: 2, encoding: 0x01}];
1303 // data says [{ old_x: 0, old_y: 0 }, { old_x: 0, old_y: 0 }]
1304 var rects = [[0, 2, 0, 0], [0, 0, 0, 0]];
1305 send_fbu_msg(info, rects, client);
1306 expect(client._display).to.have.displayed(target_data_check);
1307 });
1308
1309 // TODO(directxman12): for encodings with subrects, test resuming on partial send?
1310 // TODO(directxman12): test rre_chunk_sz (related to above about subrects)?
1311
1312 it('should handle the RRE encoding', function () {
1313 var info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x02 }];
1314 var rect = [];
1315 rect.push32(2); // 2 subrects
1316 rect.push32(0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1317 rect.push(0xff); // becomes ff0000ff --> #0000FF color
1318 rect.push(0x00);
1319 rect.push(0x00);
1320 rect.push(0xff);
1321 rect.push16(0); // x: 0
1322 rect.push16(0); // y: 0
1323 rect.push16(2); // width: 2
1324 rect.push16(2); // height: 2
1325 rect.push(0xff); // becomes ff0000ff --> #0000FF color
1326 rect.push(0x00);
1327 rect.push(0x00);
1328 rect.push(0xff);
1329 rect.push16(2); // x: 2
1330 rect.push16(2); // y: 2
1331 rect.push16(2); // width: 2
1332 rect.push16(2); // height: 2
1333
1334 send_fbu_msg(info, [rect], client);
1335 expect(client._display).to.have.displayed(target_data_check);
1336 });
1337
1338 describe('the HEXTILE encoding handler', function () {
1339 var client;
1340 beforeEach(function () {
1341 client = make_rfb();
1342 client.connect('host', 8675);
1343 client._sock._websocket._open();
1344 client._rfb_state = 'normal';
1345 client._fb_name = 'some device';
1346 // a really small frame
1347 client._fb_width = 4;
1348 client._fb_height = 4;
1349 client._display._fb_width = 4;
1350 client._display._fb_height = 4;
2c9623b5
SR
1351 client._display._viewportLoc.w = 4;
1352 client._display._viewportLoc.h = 4;
b1dee947
SR
1353 client._fb_Bpp = 4;
1354 });
1355
1356 it('should handle a tile with fg, bg specified, normal subrects', function () {
1357 var info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
1358 var rect = [];
1359 rect.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects
1360 rect.push32(0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1361 rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
1362 rect.push(0x00);
1363 rect.push(0x00);
1364 rect.push(0xff);
1365 rect.push(2); // 2 subrects
1366 rect.push(0); // x: 0, y: 0
1367 rect.push(1 | (1 << 4)); // width: 2, height: 2
1368 rect.push(2 | (2 << 4)); // x: 2, y: 2
1369 rect.push(1 | (1 << 4)); // width: 2, height: 2
1370 send_fbu_msg(info, [rect], client);
1371 expect(client._display).to.have.displayed(target_data_check);
1372 });
1373
1374 it('should handle a raw tile', function () {
1375 var info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
1376 var rect = [];
1377 rect.push(0x01); // raw
1378 for (var i = 0; i < target_data.length; i += 4) {
1379 rect.push(target_data[i + 2]);
1380 rect.push(target_data[i + 1]);
1381 rect.push(target_data[i]);
1382 rect.push(target_data[i + 3]);
1383 }
1384 send_fbu_msg(info, [rect], client);
1385 expect(client._display).to.have.displayed(target_data);
1386 });
1387
1388 it('should handle a tile with only bg specified (solid bg)', function () {
1389 var info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
1390 var rect = [];
1391 rect.push(0x02);
1392 rect.push32(0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1393 send_fbu_msg(info, [rect], client);
1394
1395 var expected = [];
1396 for (var i = 0; i < 16; i++) { expected.push32(0xff00ff); }
1397 expect(client._display).to.have.displayed(new Uint8Array(expected));
1398 });
1399
40ac6f0a
RK
1400 it('should handle a tile with only bg specified and an empty frame afterwards', function () {
1401 // set the width so we can have two tiles
1402 client._fb_width = 8;
1403 client._display._fb_width = 8;
1404 client._display._viewportLoc.w = 8;
1405
4865278d 1406 var info = [{ x: 0, y: 0, width: 32, height: 4, encoding: 0x05 }];
40ac6f0a
RK
1407
1408 var rect = [];
1409
1410 // send a bg frame
1411 rect.push(0x02);
1412 rect.push32(0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1413
1414 // send an empty frame
1415 rect.push(0x00);
1416
1417 send_fbu_msg(info, [rect], client);
1418
1419 var expected = [];
1420 var i;
1421 for (i = 0; i < 16; i++) { expected.push32(0xff00ff); } // rect 1: solid
1422 for (i = 0; i < 16; i++) { expected.push32(0xff00ff); } // rect 2: same bkground color
1423 expect(client._display).to.have.displayed(new Uint8Array(expected));
1424 });
1425
b1dee947
SR
1426 it('should handle a tile with bg and coloured subrects', function () {
1427 var info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
1428 var rect = [];
1429 rect.push(0x02 | 0x08 | 0x10); // bg spec, anysubrects, colouredsubrects
1430 rect.push32(0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1431 rect.push(2); // 2 subrects
1432 rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
1433 rect.push(0x00);
1434 rect.push(0x00);
1435 rect.push(0xff);
1436 rect.push(0); // x: 0, y: 0
1437 rect.push(1 | (1 << 4)); // width: 2, height: 2
1438 rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
1439 rect.push(0x00);
1440 rect.push(0x00);
1441 rect.push(0xff);
1442 rect.push(2 | (2 << 4)); // x: 2, y: 2
1443 rect.push(1 | (1 << 4)); // width: 2, height: 2
1444 send_fbu_msg(info, [rect], client);
1445 expect(client._display).to.have.displayed(target_data_check);
1446 });
1447
1448 it('should carry over fg and bg colors from the previous tile if not specified', function () {
1449 client._fb_width = 4;
1450 client._fb_height = 17;
1451 client._display.resize(4, 17);
1452
1453 var info = [{ x: 0, y: 0, width: 4, height: 17, encoding: 0x05}];
1454 var rect = [];
1455 rect.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects
1456 rect.push32(0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
1457 rect.push(0xff); // becomes ff0000ff --> #0000FF fg color
1458 rect.push(0x00);
1459 rect.push(0x00);
1460 rect.push(0xff);
1461 rect.push(8); // 8 subrects
1462 var i;
1463 for (i = 0; i < 4; i++) {
1464 rect.push((0 << 4) | (i * 4)); // x: 0, y: i*4
1465 rect.push(1 | (1 << 4)); // width: 2, height: 2
1466 rect.push((2 << 4) | (i * 4 + 2)); // x: 2, y: i * 4 + 2
1467 rect.push(1 | (1 << 4)); // width: 2, height: 2
1468 }
1469 rect.push(0x08); // anysubrects
1470 rect.push(1); // 1 subrect
1471 rect.push(0); // x: 0, y: 0
1472 rect.push(1 | (1 << 4)); // width: 2, height: 2
1473 send_fbu_msg(info, [rect], client);
1474
1475 var expected = [];
1476 for (i = 0; i < 4; i++) { expected = expected.concat(target_data_check_arr); }
1477 expected = expected.concat(target_data_check_arr.slice(0, 16));
1478 expect(client._display).to.have.displayed(new Uint8Array(expected));
1479 });
1480
1481 it('should fail on an invalid subencoding', function () {
1482 var info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
1483 var rects = [[45]]; // an invalid subencoding
1484 send_fbu_msg(info, rects, client);
1485 expect(client._rfb_state).to.equal('failed');
1486 });
1487 });
1488
1489 it.skip('should handle the TIGHT encoding', function () {
1490 // TODO(directxman12): test this
1491 });
1492
1493 it.skip('should handle the TIGHT_PNG encoding', function () {
1494 // TODO(directxman12): test this
1495 });
1496
1497 it('should handle the DesktopSize pseduo-encoding', function () {
1498 client.set_onFBResize(sinon.spy());
1499 sinon.spy(client._display, 'resize');
1500 send_fbu_msg([{ x: 0, y: 0, width: 20, height: 50, encoding: -223 }], [[]], client);
1501
1502 var spy = client.get_onFBResize();
1503 expect(spy).to.have.been.calledOnce;
1504 expect(spy).to.have.been.calledWith(sinon.match.any, 20, 50);
1505
1506 expect(client._fb_width).to.equal(20);
1507 expect(client._fb_height).to.equal(50);
1508
1509 expect(client._display.resize).to.have.been.calledOnce;
1510 expect(client._display.resize).to.have.been.calledWith(20, 50);
1511 });
1512
4dec490a 1513 describe('the ExtendedDesktopSize pseudo-encoding handler', function () {
1514 var client;
1515
1516 beforeEach(function () {
1517 client = make_rfb();
1518 client.connect('host', 8675);
1519 client._sock._websocket._open();
1520 client._rfb_state = 'normal';
1521 client._fb_name = 'some device';
1522 client._supportsSetDesktopSize = false;
1523 // a really small frame
1524 client._fb_width = 4;
1525 client._fb_height = 4;
1526 client._display._fb_width = 4;
1527 client._display._fb_height = 4;
1528 client._display._viewportLoc.w = 4;
1529 client._display._viewportLoc.h = 4;
1530 client._fb_Bpp = 4;
1531 sinon.spy(client._display, 'resize');
1532 client.set_onFBResize(sinon.spy());
1533 });
1534
1535 function make_screen_data (nr_of_screens) {
1536 var data = [];
1537 data.push8(nr_of_screens); // number-of-screens
1538 data.push8(0); // padding
1539 data.push16(0); // padding
1540 for (var i=0; i<nr_of_screens; i += 1) {
1541 data.push32(0); // id
1542 data.push16(0); // x-position
1543 data.push16(0); // y-position
1544 data.push16(20); // width
1545 data.push16(50); // height
1546 data.push32(0); // flags
1547 }
1548 return data;
1549 }
1550
1551 it('should handle a resize requested by this client', function () {
1552 var reason_for_change = 1; // requested by this client
1553 var status_code = 0; // No error
1554
1555 send_fbu_msg([{ x: reason_for_change, y: status_code,
1556 width: 20, height: 50, encoding: -308 }],
1557 make_screen_data(1), client);
1558
1559 expect(client._supportsSetDesktopSize).to.be.true;
1560 expect(client._fb_width).to.equal(20);
1561 expect(client._fb_height).to.equal(50);
1562
1563 expect(client._display.resize).to.have.been.calledOnce;
1564 expect(client._display.resize).to.have.been.calledWith(20, 50);
1565
1566 var spy = client.get_onFBResize();
1567 expect(spy).to.have.been.calledOnce;
1568 expect(spy).to.have.been.calledWith(sinon.match.any, 20, 50);
1569 });
1570
1571 it('should handle a resize requested by another client', function () {
1572 var reason_for_change = 2; // requested by another client
1573 var status_code = 0; // No error
1574
1575 send_fbu_msg([{ x: reason_for_change, y: status_code,
1576 width: 20, height: 50, encoding: -308 }],
1577 make_screen_data(1), client);
1578
1579 expect(client._supportsSetDesktopSize).to.be.true;
1580 expect(client._fb_width).to.equal(20);
1581 expect(client._fb_height).to.equal(50);
1582
1583 expect(client._display.resize).to.have.been.calledOnce;
1584 expect(client._display.resize).to.have.been.calledWith(20, 50);
1585
1586 var spy = client.get_onFBResize();
1587 expect(spy).to.have.been.calledOnce;
1588 expect(spy).to.have.been.calledWith(sinon.match.any, 20, 50);
1589 });
1590
1591 it('should be able to recieve requests which contain data for multiple screens', function () {
1592 var reason_for_change = 2; // requested by another client
1593 var status_code = 0; // No error
1594
1595 send_fbu_msg([{ x: reason_for_change, y: status_code,
1596 width: 60, height: 50, encoding: -308 }],
1597 make_screen_data(3), client);
1598
1599 expect(client._supportsSetDesktopSize).to.be.true;
1600 expect(client._fb_width).to.equal(60);
1601 expect(client._fb_height).to.equal(50);
1602
1603 expect(client._display.resize).to.have.been.calledOnce;
1604 expect(client._display.resize).to.have.been.calledWith(60, 50);
1605
1606 var spy = client.get_onFBResize();
1607 expect(spy).to.have.been.calledOnce;
1608 expect(spy).to.have.been.calledWith(sinon.match.any, 60, 50);
1609 });
1610
1611 it('should not handle a failed request', function () {
798340b9 1612 var reason_for_change = 1; // requested by this client
4dec490a 1613 var status_code = 1; // Resize is administratively prohibited
1614
1615 send_fbu_msg([{ x: reason_for_change, y: status_code,
1616 width: 20, height: 50, encoding: -308 }],
1617 make_screen_data(1), client);
1618
1619 expect(client._fb_width).to.equal(4);
1620 expect(client._fb_height).to.equal(4);
1621
1622 expect(client._display.resize).to.not.have.been.called;
1623
1624 var spy = client.get_onFBResize();
1625 expect(spy).to.not.have.been.called;
1626 });
1627 });
1628
b1dee947
SR
1629 it.skip('should handle the Cursor pseudo-encoding', function () {
1630 // TODO(directxman12): test
1631 });
1632
1633 it('should handle the last_rect pseudo-encoding', function () {
1634 client.set_onFBUReceive(sinon.spy());
1635 send_fbu_msg([{ x: 0, y: 0, width: 0, height: 0, encoding: -224}], [[]], client, 100);
1636 expect(client._FBU.rects).to.equal(0);
1637 expect(client.get_onFBUReceive()).to.have.been.calledOnce;
1638 });
1639 });
1640 });
1641
1642 it('should set the colour map on the display on SetColourMapEntries', function () {
1643 var expected_cm = [];
1644 var data = [1, 0, 0, 1, 0, 4];
1645 var i;
1646 for (i = 0; i < 4; i++) {
1647 expected_cm[i + 1] = [i * 10, i * 10 + 1, i * 10 + 2];
1648 data.push16(expected_cm[i + 1][2] << 8);
1649 data.push16(expected_cm[i + 1][1] << 8);
1650 data.push16(expected_cm[i + 1][0] << 8);
1651 }
1652
1653 client._sock._websocket._receive_data(new Uint8Array(data));
1654 expect(client._display.get_colourMap()).to.deep.equal(expected_cm);
1655 });
1656
1657 describe('XVP Message Handling', function () {
1658 beforeEach(function () {
1659 client = make_rfb();
1660 client.connect('host', 8675);
1661 client._sock._websocket._open();
1662 client._rfb_state = 'normal';
1663 client._fb_name = 'some device';
1664 client._fb_width = 27;
1665 client._fb_height = 32;
1666 });
1667
1668 it('should call updateState with a message on XVP_FAIL, but keep the same state', function () {
1669 client._updateState = sinon.spy();
1670 client._sock._websocket._receive_data(new Uint8Array([250, 0, 10, 0]));
1671 expect(client._updateState).to.have.been.calledOnce;
1672 expect(client._updateState).to.have.been.calledWith('normal', 'Operation Failed');
1673 });
1674
1675 it('should set the XVP version and fire the callback with the version on XVP_INIT', function () {
1676 client.set_onXvpInit(sinon.spy());
1677 client._sock._websocket._receive_data(new Uint8Array([250, 0, 10, 1]));
1678 expect(client._rfb_xvp_ver).to.equal(10);
1679 expect(client.get_onXvpInit()).to.have.been.calledOnce;
1680 expect(client.get_onXvpInit()).to.have.been.calledWith(10);
1681 });
1682
1683 it('should fail on unknown XVP message types', function () {
1684 client._sock._websocket._receive_data(new Uint8Array([250, 0, 10, 237]));
1685 expect(client._rfb_state).to.equal('failed');
1686 });
1687 });
1688
1689 it('should fire the clipboard callback with the retrieved text on ServerCutText', function () {
1690 var expected_str = 'cheese!';
1691 var data = [3, 0, 0, 0];
1692 data.push32(expected_str.length);
1693 for (var i = 0; i < expected_str.length; i++) { data.push(expected_str.charCodeAt(i)); }
1694 client.set_onClipboard(sinon.spy());
1695
1696 client._sock._websocket._receive_data(new Uint8Array(data));
1697 var spy = client.get_onClipboard();
1698 expect(spy).to.have.been.calledOnce;
1699 expect(spy.args[0][1]).to.equal(expected_str);
1700 });
1701
1702 it('should fire the bell callback on Bell', function () {
1703 client.set_onBell(sinon.spy());
1704 client._sock._websocket._receive_data(new Uint8Array([2]));
1705 expect(client.get_onBell()).to.have.been.calledOnce;
1706 });
1707
1708 it('should fail on an unknown message type', function () {
1709 client._sock._websocket._receive_data(new Uint8Array([87]));
1710 expect(client._rfb_state).to.equal('failed');
1711 });
1712 });
1713
1714 describe('Asynchronous Events', function () {
1715 describe('Mouse event handlers', function () {
1716 var client;
1717 beforeEach(function () {
1718 client = make_rfb();
9ff86fb7
SR
1719 client._sock = new Websock();
1720 client._sock.open('ws://', 'binary');
1721 client._sock._websocket._open();
1722 sinon.spy(client._sock, 'flush');
b1dee947
SR
1723 client._rfb_state = 'normal';
1724 });
1725
1726 it('should not send button messages in view-only mode', function () {
1727 client._view_only = true;
1728 client._mouse._onMouseButton(0, 0, 1, 0x001);
9ff86fb7 1729 expect(client._sock.flush).to.not.have.been.called;
b1dee947
SR
1730 });
1731
1732 it('should not send movement messages in view-only mode', function () {
1733 client._view_only = true;
1734 client._mouse._onMouseMove(0, 0);
9ff86fb7 1735 expect(client._sock.flush).to.not.have.been.called;
b1dee947
SR
1736 });
1737
1738 it('should send a pointer event on mouse button presses', function () {
1739 client._mouse._onMouseButton(10, 12, 1, 0x001);
89d2837f 1740 var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: function () {}};
9ff86fb7
SR
1741 RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x001);
1742 expect(client._sock).to.have.sent(pointer_msg._sQ);
b1dee947
SR
1743 });
1744
d02a99f0
SR
1745 it('should send a mask of 1 on mousedown', function () {
1746 client._mouse._onMouseButton(10, 12, 1, 0x001);
89d2837f 1747 var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: function () {}};
cf0236de 1748 RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x001);
9ff86fb7 1749 expect(client._sock).to.have.sent(pointer_msg._sQ);
d02a99f0
SR
1750 });
1751
1752 it('should send a mask of 0 on mouseup', function () {
3b4fd003 1753 client._mouse_buttonMask = 0x001;
d02a99f0 1754 client._mouse._onMouseButton(10, 12, 0, 0x001);
89d2837f 1755 var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: function () {}};
9ff86fb7
SR
1756 RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x000);
1757 expect(client._sock).to.have.sent(pointer_msg._sQ);
d02a99f0
SR
1758 });
1759
b1dee947
SR
1760 it('should send a pointer event on mouse movement', function () {
1761 client._mouse._onMouseMove(10, 12);
89d2837f 1762 var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: function () {}};
9ff86fb7
SR
1763 RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x000);
1764 expect(client._sock).to.have.sent(pointer_msg._sQ);
b1dee947
SR
1765 });
1766
1767 it('should set the button mask so that future mouse movements use it', function () {
1768 client._mouse._onMouseButton(10, 12, 1, 0x010);
b1dee947 1769 client._mouse._onMouseMove(13, 9);
89d2837f 1770 var pointer_msg = {_sQ: new Uint8Array(12), _sQlen: 0, flush: function () {}};
9ff86fb7
SR
1771 RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x010);
1772 RFB.messages.pointerEvent(pointer_msg, 13, 9, 0x010);
1773 expect(client._sock).to.have.sent(pointer_msg._sQ);
b1dee947
SR
1774 });
1775
1776 // NB(directxman12): we don't need to test not sending messages in
1777 // non-normal modes, since we haven't grabbed input
1778 // yet (grabbing input should be checked in the lifecycle tests).
1779
1780 it('should not send movement messages when viewport dragging', function () {
1781 client._viewportDragging = true;
636be753 1782 client._display.viewportChangePos = sinon.spy();
b1dee947 1783 client._mouse._onMouseMove(13, 9);
9ff86fb7 1784 expect(client._sock.flush).to.not.have.been.called;
b1dee947
SR
1785 });
1786
1787 it('should not send button messages when initiating viewport dragging', function () {
1788 client._viewportDrag = true;
1789 client._mouse._onMouseButton(13, 9, 0x001);
9ff86fb7 1790 expect(client._sock.flush).to.not.have.been.called;
b1dee947
SR
1791 });
1792
1793 it('should be initiate viewport dragging on a button down event, if enabled', function () {
1794 client._viewportDrag = true;
1795 client._mouse._onMouseButton(13, 9, 0x001);
1796 expect(client._viewportDragging).to.be.true;
1797 expect(client._viewportDragPos).to.deep.equal({ x: 13, y: 9 });
1798 });
1799
1800 it('should terminate viewport dragging on a button up event, if enabled', function () {
1801 client._viewportDrag = true;
1802 client._viewportDragging = true;
1803 client._mouse._onMouseButton(13, 9, 0x000);
1804 expect(client._viewportDragging).to.be.false;
1805 });
1806
1807 it('if enabled, viewportDragging should occur on mouse movement while a button is down', function () {
1808 client._viewportDrag = true;
1809 client._viewportDragging = true;
12ae8b3d
SM
1810 client._viewportHasMoved = false;
1811 client._viewportDragPos = { x: 23, y: 9 };
636be753 1812 client._display.viewportChangePos = sinon.spy();
b1dee947
SR
1813
1814 client._mouse._onMouseMove(10, 4);
1815
1816 expect(client._viewportDragging).to.be.true;
057cfc7c 1817 expect(client._viewportHasMoved).to.be.true;
b1dee947 1818 expect(client._viewportDragPos).to.deep.equal({ x: 10, y: 4 });
636be753 1819 expect(client._display.viewportChangePos).to.have.been.calledOnce;
12ae8b3d 1820 expect(client._display.viewportChangePos).to.have.been.calledWith(13, 5);
b1dee947
SR
1821 });
1822 });
1823
1824 describe('Keyboard Event Handlers', function () {
1825 var client;
1826 beforeEach(function () {
1827 client = make_rfb();
9ff86fb7
SR
1828 client._sock = new Websock();
1829 client._sock.open('ws://', 'binary');
1830 client._sock._websocket._open();
1831 sinon.spy(client._sock, 'flush');
b1dee947
SR
1832 });
1833
1834 it('should send a key message on a key press', function () {
1835 client._keyboard._onKeyPress(1234, 1);
89d2837f 1836 var key_msg = {_sQ: new Uint8Array(8), _sQlen: 0, flush: function () {}};
9ff86fb7
SR
1837 RFB.messages.keyEvent(key_msg, 1234, 1);
1838 expect(client._sock).to.have.sent(key_msg._sQ);
b1dee947
SR
1839 });
1840
1841 it('should not send messages in view-only mode', function () {
1842 client._view_only = true;
1843 client._keyboard._onKeyPress(1234, 1);
9ff86fb7 1844 expect(client._sock.flush).to.not.have.been.called;
b1dee947
SR
1845 });
1846 });
1847
1848 describe('WebSocket event handlers', function () {
1849 var client;
1850 beforeEach(function () {
1851 client = make_rfb();
1852 this.clock = sinon.useFakeTimers();
1853 });
1854
1855 afterEach(function () { this.clock.restore(); });
1856
1857 // message events
1858 it ('should do nothing if we receive an empty message and have nothing in the queue', function () {
1859 client.connect('host', 8675);
1860 client._rfb_state = 'normal';
1861 client._normal_msg = sinon.spy();
38781d93 1862 client._sock._websocket._receive_data(new Uint8Array([]));
b1dee947
SR
1863 expect(client._normal_msg).to.not.have.been.called;
1864 });
1865
1866 it('should handle a message in the normal state as a normal message', function () {
1867 client.connect('host', 8675);
1868 client._rfb_state = 'normal';
1869 client._normal_msg = sinon.spy();
38781d93 1870 client._sock._websocket._receive_data(new Uint8Array([1, 2, 3]));
b1dee947
SR
1871 expect(client._normal_msg).to.have.been.calledOnce;
1872 });
1873
1874 it('should handle a message in any non-disconnected/failed state like an init message', function () {
1875 client.connect('host', 8675);
1876 client._rfb_state = 'ProtocolVersion';
1877 client._init_msg = sinon.spy();
38781d93 1878 client._sock._websocket._receive_data(new Uint8Array([1, 2, 3]));
b1dee947
SR
1879 expect(client._init_msg).to.have.been.calledOnce;
1880 });
1881
1882 it('should split up the handling of muplitle normal messages across 10ms intervals', function () {
1883 client.connect('host', 8675);
1884 client._sock._websocket._open();
1885 client._rfb_state = 'normal';
1886 client.set_onBell(sinon.spy());
1887 client._sock._websocket._receive_data(new Uint8Array([0x02, 0x02]));
1888 expect(client.get_onBell()).to.have.been.calledOnce;
1889 this.clock.tick(20);
1890 expect(client.get_onBell()).to.have.been.calledTwice;
1891 });
1892
1893 // open events
1894 it('should update the state to ProtocolVersion on open (if the state is "connect")', function () {
1895 client.connect('host', 8675);
1896 client._sock._websocket._open();
1897 expect(client._rfb_state).to.equal('ProtocolVersion');
1898 });
1899
1900 it('should fail if we are not currently ready to connect and we get an "open" event', function () {
1901 client.connect('host', 8675);
1902 client._rfb_state = 'some_other_state';
1903 client._sock._websocket._open();
1904 expect(client._rfb_state).to.equal('failed');
1905 });
1906
1907 // close events
1908 it('should transition to "disconnected" from "disconnect" on a close event', function () {
1909 client.connect('host', 8675);
1910 client._rfb_state = 'disconnect';
1911 client._sock._websocket.close();
1912 expect(client._rfb_state).to.equal('disconnected');
1913 });
1914
1915 it('should transition to failed if we get a close event from any non-"disconnection" state', function () {
1916 client.connect('host', 8675);
1917 client._rfb_state = 'normal';
1918 client._sock._websocket.close();
1919 expect(client._rfb_state).to.equal('failed');
1920 });
1921
155d78b3
JS
1922 it('should unregister close event handler', function () {
1923 sinon.spy(client._sock, 'off');
1924 client.connect('host', 8675);
1925 client._rfb_state = 'disconnect';
1926 client._sock._websocket.close();
1927 expect(client._sock.off).to.have.been.calledWith('close');
1928 });
1929
b1dee947
SR
1930 // error events do nothing
1931 });
1932 });
1933});