]> git.proxmox.com Git - mirror_novnc.git/blame - tests/test.rfb.js
Initiate connection from RFB constructor
[mirror_novnc.git] / tests / test.rfb.js
CommitLineData
2b5f94fa 1const expect = chai.expect;
b1dee947 2
dfae3209
SR
3import RFB from '../core/rfb.js';
4import Websock from '../core/websock.js';
f73fdc3e
NL
5import ZStream from "../vendor/pako/lib/zlib/zstream.js";
6import { deflateInit, deflate } from "../vendor/pako/lib/zlib/deflate.js";
69411b9e 7import { encodings } from '../core/encodings.js';
f73fdc3e
NL
8import { toUnsigned32bit } from '../core/util/int.js';
9import { encodeUTF8 } from '../core/util/strings.js';
8be924c9 10import KeyTable from '../core/input/keysym.js';
dfae3209
SR
11
12import FakeWebSocket from './fake.websocket.js';
dfae3209 13
885363a3 14function push8(arr, num) {
3949a095
SR
15 "use strict";
16 arr.push(num & 0xFF);
885363a3 17}
3949a095 18
885363a3 19function push16(arr, num) {
3949a095
SR
20 "use strict";
21 arr.push((num >> 8) & 0xFF,
7b536961 22 num & 0xFF);
885363a3 23}
3949a095 24
885363a3 25function push32(arr, num) {
3949a095
SR
26 "use strict";
27 arr.push((num >> 24) & 0xFF,
7b536961
PO
28 (num >> 16) & 0xFF,
29 (num >> 8) & 0xFF,
30 num & 0xFF);
885363a3 31}
3949a095 32
ce66b469
NL
33function pushString(arr, string) {
34 let utf8 = unescape(encodeURIComponent(string));
35 for (let i = 0; i < utf8.length; i++) {
36 arr.push(utf8.charCodeAt(i));
37 }
38}
39
f73fdc3e
NL
40function deflateWithSize(data) {
41 // Adds the size of the string in front before deflating
42
43 let unCompData = [];
44 unCompData.push((data.length >> 24) & 0xFF,
45 (data.length >> 16) & 0xFF,
46 (data.length >> 8) & 0xFF,
47 (data.length & 0xFF));
48
49 for (let i = 0; i < data.length; i++) {
50 unCompData.push(data.charCodeAt(i));
51 }
52
53 let strm = new ZStream();
54 let chunkSize = 1024 * 10 * 10;
55 strm.output = new Uint8Array(chunkSize);
56 deflateInit(strm, 5);
57
cfb824ed 58 /* eslint-disable camelcase */
f73fdc3e
NL
59 strm.input = unCompData;
60 strm.avail_in = strm.input.length;
61 strm.next_in = 0;
62 strm.next_out = 0;
63 strm.avail_out = chunkSize;
cfb824ed 64 /* eslint-enable camelcase */
f73fdc3e
NL
65
66 deflate(strm, 3);
67
68 return new Uint8Array(strm.output.buffer, 0, strm.next_out);
69}
70
2c5491e1 71describe('Remote Frame Buffer Protocol Client', function () {
2b5f94fa
JD
72 let clock;
73 let raf;
2f4516f2 74
b1dee947
SR
75 before(FakeWebSocket.replace);
76 after(FakeWebSocket.restore);
77
38781d93 78 before(function () {
440ec8a0 79 this.clock = clock = sinon.useFakeTimers(Date.now());
9b84f516
PO
80 // sinon doesn't support this yet
81 raf = window.requestAnimationFrame;
82 window.requestAnimationFrame = setTimeout;
38781d93
SR
83 // Use a single set of buffers instead of reallocating to
84 // speed up tests
2b5f94fa
JD
85 const sock = new Websock();
86 const _sQ = new Uint8Array(sock._sQbufferSize);
87 const rQ = new Uint8Array(sock._rQbufferSize);
38781d93 88
ea858bfa
SM
89 Websock.prototype._oldAllocateBuffers = Websock.prototype._allocateBuffers;
90 Websock.prototype._allocateBuffers = function () {
9ff86fb7 91 this._sQ = _sQ;
38781d93
SR
92 this._rQ = rQ;
93 };
94
0a6aec35
PO
95 // Avoiding printing the entire Websock buffer on errors
96 Websock.prototype.toString = function () { return "[object Websock]"; };
38781d93
SR
97 });
98
99 after(function () {
0a6aec35 100 delete Websock.prototype.toString;
38781d93 101 this.clock.restore();
9b84f516 102 window.requestAnimationFrame = raf;
38781d93
SR
103 });
104
2b5f94fa
JD
105 let container;
106 let rfbs;
bb25d3d6
PO
107
108 beforeEach(function () {
9b84f516
PO
109 // Create a container element for all RFB objects to attach to
110 container = document.createElement('div');
111 container.style.width = "100%";
112 container.style.height = "100%";
113 document.body.appendChild(container);
114
115 // And track all created RFB objects
bb25d3d6
PO
116 rfbs = [];
117 });
118 afterEach(function () {
119 // Make sure every created RFB object is properly cleaned up
120 // or they might affect subsequent tests
121 rfbs.forEach(function (rfb) {
122 rfb.disconnect();
123 expect(rfb._disconnect).to.have.been.called;
124 });
125 rfbs = [];
9b84f516
PO
126
127 document.body.removeChild(container);
128 container = null;
bb25d3d6
PO
129 });
130
00674385 131 function makeRFB(url, options) {
2f4516f2 132 url = url || 'wss://host:8675';
2b5f94fa 133 const rfb = new RFB(container, url, options);
2f4516f2
PO
134 clock.tick();
135 rfb._sock._websocket._open();
e7dec527 136 rfb._rfbConnectionState = 'connected';
bb25d3d6
PO
137 sinon.spy(rfb, "_disconnect");
138 rfbs.push(rfb);
2f4516f2
PO
139 return rfb;
140 }
b1dee947 141
2f4516f2 142 describe('Connecting/Disconnecting', function () {
f0e4908d 143 describe('#RFB (constructor)', function () {
ae3c01f7 144 let open, attach;
f0e4908d
PO
145 beforeEach(function () {
146 open = sinon.spy(Websock.prototype, 'open');
ae3c01f7 147 attach = sinon.spy(Websock.prototype, 'attach');
f0e4908d
PO
148 });
149 afterEach(function () {
150 open.restore();
ae3c01f7 151 attach.restore();
f0e4908d
PO
152 });
153
2f4516f2 154 it('should actually connect to the websocket', function () {
f0e4908d 155 new RFB(document.createElement('div'), 'ws://HOST:8675/PATH');
f0e4908d 156 expect(open).to.have.been.calledOnceWithExactly('ws://HOST:8675/PATH', []);
b1dee947 157 });
ae3c01f7 158
dbd51955 159 it('should pass on connection problems', function () {
42100e82
PO
160 open.restore();
161 open = sinon.stub(Websock.prototype, 'open');
dbd51955
PO
162 open.throws(new Error('Failure'));
163 expect(() => new RFB(document.createElement('div'), 'ws://HOST:8675/PATH')).to.throw('Failure');
42100e82
PO
164 });
165
ae3c01f7
PO
166 it('should handle WebSocket/RTCDataChannel objects', function () {
167 let sock = new FakeWebSocket('ws://HOST:8675/PATH', []);
168 new RFB(document.createElement('div'), sock);
ae3c01f7
PO
169 expect(open).to.not.have.been.called;
170 expect(attach).to.have.been.calledOnceWithExactly(sock);
171 });
42100e82 172
de9fc950
PO
173 it('should handle already open WebSocket/RTCDataChannel objects', function () {
174 let sock = new FakeWebSocket('ws://HOST:8675/PATH', []);
175 sock._open();
176 const client = new RFB(document.createElement('div'), sock);
177 let callback = sinon.spy();
178 client.addEventListener('disconnect', callback);
de9fc950
PO
179 expect(open).to.not.have.been.called;
180 expect(attach).to.have.been.calledOnceWithExactly(sock);
181 // Check if it is ready for some data
182 sock._receiveData(new Uint8Array(['R', 'F', 'B', '0', '0', '3', '0', '0', '8']));
183 expect(callback).to.not.have.been.called;
184 });
185
9376191f
PO
186 it('should refuse closed WebSocket/RTCDataChannel objects', function () {
187 let sock = new FakeWebSocket('ws://HOST:8675/PATH', []);
188 sock.readyState = WebSocket.CLOSED;
dbd51955 189 expect(() => new RFB(document.createElement('div'), sock)).to.throw();
9376191f
PO
190 });
191
dbd51955 192 it('should pass on attach problems', function () {
42100e82
PO
193 attach.restore();
194 attach = sinon.stub(Websock.prototype, 'attach');
dbd51955 195 attach.throws(new Error('Failure'));
42100e82 196 let sock = new FakeWebSocket('ws://HOST:8675/PATH', []);
dbd51955 197 expect(() => new RFB(document.createElement('div'), sock)).to.throw('Failure');
42100e82 198 });
b1dee947
SR
199 });
200
201 describe('#disconnect', function () {
2b5f94fa 202 let client;
f0e4908d
PO
203 let close;
204
2f4516f2 205 beforeEach(function () {
00674385 206 client = makeRFB();
f0e4908d
PO
207 close = sinon.stub(Websock.prototype, "close");
208 });
209 afterEach(function () {
210 close.restore();
2f4516f2 211 });
b1dee947 212
f0e4908d
PO
213 it('should start closing WebSocket', function () {
214 let callback = sinon.spy();
215 client.addEventListener('disconnect', callback);
b1dee947 216 client.disconnect();
f0e4908d
PO
217 expect(close).to.have.been.calledOnceWithExactly();
218 expect(callback).to.not.have.been.called;
219 });
220
221 it('should send disconnect event', function () {
222 let callback = sinon.spy();
223 client.addEventListener('disconnect', callback);
224 client.disconnect();
225 close.thisValues[0]._eventHandlers.close(new CloseEvent("close", { 'code': 1000, 'reason': "", 'wasClean': true }));
226 expect(callback).to.have.been.calledOnce;
227 expect(callback.args[0][0].detail.clean).to.be.true;
228 });
229
230 it('should force disconnect if disconnecting takes too long', function () {
231 let callback = sinon.spy();
232 client.addEventListener('disconnect', callback);
233 client.disconnect();
234 this.clock.tick(3 * 1000);
235 expect(callback).to.have.been.calledOnce;
236 expect(callback.args[0][0].detail.clean).to.be.true;
237 });
238
239 it('should not fail if disconnect completes before timeout', function () {
240 let callback = sinon.spy();
241 client.addEventListener('disconnect', callback);
242 client.disconnect();
243 client._updateConnectionState('disconnecting');
244 this.clock.tick(3 * 1000 / 2);
245 close.thisValues[0]._eventHandlers.close(new CloseEvent("close", { 'code': 1000, 'reason': "", 'wasClean': true }));
246 this.clock.tick(3 * 1000 / 2 + 1);
247 expect(callback).to.have.been.calledOnce;
248 expect(callback.args[0][0].detail.clean).to.be.true;
b1dee947 249 });
155d78b3
JS
250
251 it('should unregister error event handler', function () {
252 sinon.spy(client._sock, 'off');
253 client.disconnect();
254 expect(client._sock.off).to.have.been.calledWith('error');
255 });
256
257 it('should unregister message event handler', function () {
258 sinon.spy(client._sock, 'off');
259 client.disconnect();
260 expect(client._sock.off).to.have.been.calledWith('message');
261 });
262
263 it('should unregister open event handler', function () {
264 sinon.spy(client._sock, 'off');
265 client.disconnect();
266 expect(client._sock.off).to.have.been.calledWith('open');
267 });
b1dee947
SR
268 });
269
430f00d6 270 describe('#sendCredentials', function () {
2b5f94fa 271 let client;
2f4516f2 272 beforeEach(function () {
00674385 273 client = makeRFB();
e7dec527 274 client._rfbConnectionState = 'connecting';
2f4516f2
PO
275 });
276
430f00d6
PO
277 it('should set the rfb credentials properly"', function () {
278 client.sendCredentials({ password: 'pass' });
e7dec527 279 expect(client._rfbCredentials).to.deep.equal({ password: 'pass' });
b1dee947
SR
280 });
281
00674385
SM
282 it('should call initMsg "soon"', function () {
283 client._initMsg = sinon.spy();
430f00d6 284 client.sendCredentials({ password: 'pass' });
b1dee947 285 this.clock.tick(5);
00674385 286 expect(client._initMsg).to.have.been.calledOnce;
b1dee947
SR
287 });
288 });
057b8fec 289 });
b1dee947 290
057b8fec 291 describe('Public API Basic Behavior', function () {
2b5f94fa 292 let client;
057b8fec 293 beforeEach(function () {
00674385 294 client = makeRFB();
057b8fec 295 });
b1dee947 296
057b8fec 297 describe('#sendCtrlAlDel', function () {
b1dee947 298 it('should sent ctrl[down]-alt[down]-del[down] then del[up]-alt[up]-ctrl[up]', function () {
0e4808bf 299 const expected = {_sQ: new Uint8Array(48), _sQlen: 0, flush: () => {}};
9ff86fb7
SR
300 RFB.messages.keyEvent(expected, 0xFFE3, 1);
301 RFB.messages.keyEvent(expected, 0xFFE9, 1);
302 RFB.messages.keyEvent(expected, 0xFFFF, 1);
303 RFB.messages.keyEvent(expected, 0xFFFF, 0);
304 RFB.messages.keyEvent(expected, 0xFFE9, 0);
305 RFB.messages.keyEvent(expected, 0xFFE3, 0);
b1dee947
SR
306
307 client.sendCtrlAltDel();
9ff86fb7 308 expect(client._sock).to.have.sent(expected._sQ);
b1dee947
SR
309 });
310
311 it('should not send the keys if we are not in a normal state', function () {
057b8fec 312 sinon.spy(client._sock, 'flush');
e7dec527 313 client._rfbConnectionState = "connecting";
b1dee947 314 client.sendCtrlAltDel();
9ff86fb7 315 expect(client._sock.flush).to.not.have.been.called;
b1dee947
SR
316 });
317
318 it('should not send the keys if we are set as view_only', function () {
057b8fec 319 sinon.spy(client._sock, 'flush');
747b4623 320 client._viewOnly = true;
b1dee947 321 client.sendCtrlAltDel();
9ff86fb7 322 expect(client._sock.flush).to.not.have.been.called;
b1dee947
SR
323 });
324 });
325
326 describe('#sendKey', function () {
b1dee947 327 it('should send a single key with the given code and state (down = true)', function () {
0e4808bf 328 const expected = {_sQ: new Uint8Array(8), _sQlen: 0, flush: () => {}};
9ff86fb7 329 RFB.messages.keyEvent(expected, 123, 1);
94f5cf05 330 client.sendKey(123, 'Key123', true);
9ff86fb7 331 expect(client._sock).to.have.sent(expected._sQ);
b1dee947
SR
332 });
333
334 it('should send both a down and up event if the state is not specified', function () {
0e4808bf 335 const expected = {_sQ: new Uint8Array(16), _sQlen: 0, flush: () => {}};
9ff86fb7
SR
336 RFB.messages.keyEvent(expected, 123, 1);
337 RFB.messages.keyEvent(expected, 123, 0);
94f5cf05 338 client.sendKey(123, 'Key123');
9ff86fb7 339 expect(client._sock).to.have.sent(expected._sQ);
b1dee947
SR
340 });
341
342 it('should not send the key if we are not in a normal state', function () {
057b8fec 343 sinon.spy(client._sock, 'flush');
e7dec527 344 client._rfbConnectionState = "connecting";
94f5cf05 345 client.sendKey(123, 'Key123');
9ff86fb7 346 expect(client._sock.flush).to.not.have.been.called;
b1dee947
SR
347 });
348
349 it('should not send the key if we are set as view_only', function () {
057b8fec 350 sinon.spy(client._sock, 'flush');
747b4623 351 client._viewOnly = true;
94f5cf05 352 client.sendKey(123, 'Key123');
9ff86fb7 353 expect(client._sock.flush).to.not.have.been.called;
b1dee947 354 });
94f5cf05
PO
355
356 it('should send QEMU extended events if supported', function () {
357 client._qemuExtKeyEventSupported = true;
0e4808bf 358 const expected = {_sQ: new Uint8Array(12), _sQlen: 0, flush: () => {}};
94f5cf05
PO
359 RFB.messages.QEMUExtendedKeyEvent(expected, 0x20, true, 0x0039);
360 client.sendKey(0x20, 'Space', true);
361 expect(client._sock).to.have.sent(expected._sQ);
362 });
be70fe0a
PO
363
364 it('should not send QEMU extended events if unknown key code', function () {
365 client._qemuExtKeyEventSupported = true;
0e4808bf 366 const expected = {_sQ: new Uint8Array(8), _sQlen: 0, flush: () => {}};
be70fe0a
PO
367 RFB.messages.keyEvent(expected, 123, 1);
368 client.sendKey(123, 'FooBar', true);
369 expect(client._sock).to.have.sent(expected._sQ);
370 });
b1dee947
SR
371 });
372
9b84f516
PO
373 describe('#focus', function () {
374 it('should move focus to canvas object', function () {
375 client._canvas.focus = sinon.spy();
376 client.focus();
b875486d 377 expect(client._canvas.focus).to.have.been.calledOnce;
9b84f516
PO
378 });
379 });
380
381 describe('#blur', function () {
382 it('should remove focus from canvas object', function () {
383 client._canvas.blur = sinon.spy();
384 client.blur();
b875486d 385 expect(client._canvas.blur).to.have.been.calledOnce;
9b84f516
PO
386 });
387 });
388
b1dee947 389 describe('#clipboardPasteFrom', function () {
f73fdc3e
NL
390 describe('Clipboard update handling', function () {
391 beforeEach(function () {
392 sinon.spy(RFB.messages, 'clientCutText');
393 sinon.spy(RFB.messages, 'extendedClipboardNotify');
394 });
3b562e8a 395
f73fdc3e
NL
396 afterEach(function () {
397 RFB.messages.clientCutText.restore();
398 RFB.messages.extendedClipboardNotify.restore();
399 });
3b562e8a 400
f73fdc3e
NL
401 it('should send the given text in an clipboard update', function () {
402 client.clipboardPasteFrom('abc');
403
404 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
405 expect(RFB.messages.clientCutText).to.have.been.calledWith(client._sock,
406 new Uint8Array([97, 98, 99]));
407 });
408
409 it('should send an notify if extended clipboard is supported by server', function () {
410 // Send our capabilities
411 let data = [3, 0, 0, 0];
412 const flags = [0x1F, 0x00, 0x00, 0x01];
413 let fileSizes = [0x00, 0x00, 0x00, 0x1E];
414
415 push32(data, toUnsigned32bit(-8));
416 data = data.concat(flags);
417 data = data.concat(fileSizes);
95632e41 418 client._sock._websocket._receiveData(new Uint8Array(data));
f73fdc3e
NL
419
420 client.clipboardPasteFrom('extended test');
421 expect(RFB.messages.extendedClipboardNotify).to.have.been.calledOnce;
422 });
b1dee947
SR
423 });
424
2bb8b28d
SM
425 it('should flush multiple times for large clipboards', function () {
426 sinon.spy(client._sock, 'flush');
e7dec527 427 let longText = "";
2bb8b28d 428 for (let i = 0; i < client._sock._sQbufferSize + 100; i++) {
e7dec527 429 longText += 'a';
2bb8b28d 430 }
e7dec527 431 client.clipboardPasteFrom(longText);
2bb8b28d
SM
432 expect(client._sock.flush).to.have.been.calledTwice;
433 });
434
b1dee947 435 it('should not send the text if we are not in a normal state', function () {
057b8fec 436 sinon.spy(client._sock, 'flush');
e7dec527 437 client._rfbConnectionState = "connecting";
b1dee947 438 client.clipboardPasteFrom('abc');
9ff86fb7 439 expect(client._sock.flush).to.not.have.been.called;
b1dee947
SR
440 });
441 });
442
443 describe("XVP operations", function () {
444 beforeEach(function () {
e7dec527 445 client._rfbXvpVer = 1;
b1dee947
SR
446 });
447
cd523e8f
PO
448 it('should send the shutdown signal on #machineShutdown', function () {
449 client.machineShutdown();
9ff86fb7 450 expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x02]));
b1dee947
SR
451 });
452
cd523e8f
PO
453 it('should send the reboot signal on #machineReboot', function () {
454 client.machineReboot();
9ff86fb7 455 expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x03]));
b1dee947
SR
456 });
457
cd523e8f
PO
458 it('should send the reset signal on #machineReset', function () {
459 client.machineReset();
9ff86fb7 460 expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x04]));
b1dee947
SR
461 });
462
b1dee947 463 it('should not send XVP operations with higher versions than we support', function () {
057b8fec 464 sinon.spy(client._sock, 'flush');
cd523e8f 465 client._xvpOp(2, 7);
9ff86fb7 466 expect(client._sock.flush).to.not.have.been.called;
b1dee947
SR
467 });
468 });
469 });
470
9b84f516 471 describe('Clipping', function () {
2b5f94fa 472 let client;
9b84f516 473 beforeEach(function () {
00674385 474 client = makeRFB();
9b84f516
PO
475 container.style.width = '70px';
476 container.style.height = '80px';
477 client.clipViewport = true;
478 });
479
480 it('should update display clip state when changing the property', function () {
2b5f94fa 481 const spy = sinon.spy(client._display, "clipViewport", ["set"]);
9b84f516
PO
482
483 client.clipViewport = false;
484 expect(spy.set).to.have.been.calledOnce;
485 expect(spy.set).to.have.been.calledWith(false);
c9765e50 486 spy.set.resetHistory();
9b84f516
PO
487
488 client.clipViewport = true;
489 expect(spy.set).to.have.been.calledOnce;
490 expect(spy.set).to.have.been.calledWith(true);
491 });
492
493 it('should update the viewport when the container size changes', function () {
494 sinon.spy(client._display, "viewportChangeSize");
495
496 container.style.width = '40px';
497 container.style.height = '50px';
2b5f94fa 498 const event = new UIEvent('resize');
9b84f516
PO
499 window.dispatchEvent(event);
500 clock.tick();
501
502 expect(client._display.viewportChangeSize).to.have.been.calledOnce;
503 expect(client._display.viewportChangeSize).to.have.been.calledWith(40, 50);
504 });
505
506 it('should update the viewport when the remote session resizes', function () {
507 // Simple ExtendedDesktopSize FBU message
2b5f94fa 508 const incoming = [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
7b536961
PO
509 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0xfe, 0xcc,
510 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
511 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff,
512 0x00, 0x00, 0x00, 0x00 ];
9b84f516
PO
513
514 sinon.spy(client._display, "viewportChangeSize");
515
95632e41 516 client._sock._websocket._receiveData(new Uint8Array(incoming));
9b84f516
PO
517
518 // FIXME: Display implicitly calls viewportChangeSize() when
519 // resizing the framebuffer, hence calledTwice.
520 expect(client._display.viewportChangeSize).to.have.been.calledTwice;
521 expect(client._display.viewportChangeSize).to.have.been.calledWith(70, 80);
522 });
523
524 it('should not update the viewport if not clipping', function () {
525 client.clipViewport = false;
526 sinon.spy(client._display, "viewportChangeSize");
527
528 container.style.width = '40px';
529 container.style.height = '50px';
2b5f94fa 530 const event = new UIEvent('resize');
9b84f516
PO
531 window.dispatchEvent(event);
532 clock.tick();
533
534 expect(client._display.viewportChangeSize).to.not.have.been.called;
535 });
536
537 it('should not update the viewport if scaling', function () {
538 client.scaleViewport = true;
539 sinon.spy(client._display, "viewportChangeSize");
540
541 container.style.width = '40px';
542 container.style.height = '50px';
2b5f94fa 543 const event = new UIEvent('resize');
9b84f516
PO
544 window.dispatchEvent(event);
545 clock.tick();
546
547 expect(client._display.viewportChangeSize).to.not.have.been.called;
548 });
549
550 describe('Dragging', function () {
551 beforeEach(function () {
552 client.dragViewport = true;
553 sinon.spy(RFB.messages, "pointerEvent");
554 });
555
556 afterEach(function () {
557 RFB.messages.pointerEvent.restore();
558 });
559
560 it('should not send button messages when initiating viewport dragging', function () {
561 client._handleMouseButton(13, 9, 0x001);
562 expect(RFB.messages.pointerEvent).to.not.have.been.called;
563 });
564
565 it('should send button messages when release without movement', function () {
566 // Just up and down
567 client._handleMouseButton(13, 9, 0x001);
568 client._handleMouseButton(13, 9, 0x000);
569 expect(RFB.messages.pointerEvent).to.have.been.calledTwice;
570
c9765e50 571 RFB.messages.pointerEvent.resetHistory();
9b84f516
PO
572
573 // Small movement
574 client._handleMouseButton(13, 9, 0x001);
575 client._handleMouseMove(15, 14);
576 client._handleMouseButton(15, 14, 0x000);
577 expect(RFB.messages.pointerEvent).to.have.been.calledTwice;
578 });
579
11a22dbf
SM
580 it('should not send button messages when in view only', function () {
581 client._viewOnly = true;
582 client._handleMouseButton(13, 9, 0x001);
583 client._handleMouseButton(13, 9, 0x000);
584 expect(RFB.messages.pointerEvent).to.not.have.been.called;
585 });
586
9b84f516
PO
587 it('should send button message directly when drag is disabled', function () {
588 client.dragViewport = false;
589 client._handleMouseButton(13, 9, 0x001);
590 expect(RFB.messages.pointerEvent).to.have.been.calledOnce;
591 });
592
593 it('should be initiate viewport dragging on sufficient movement', function () {
594 sinon.spy(client._display, "viewportChangePos");
595
596 // Too small movement
597
598 client._handleMouseButton(13, 9, 0x001);
599 client._handleMouseMove(18, 9);
600
601 expect(RFB.messages.pointerEvent).to.not.have.been.called;
602 expect(client._display.viewportChangePos).to.not.have.been.called;
603
604 // Sufficient movement
605
606 client._handleMouseMove(43, 9);
607
608 expect(RFB.messages.pointerEvent).to.not.have.been.called;
609 expect(client._display.viewportChangePos).to.have.been.calledOnce;
610 expect(client._display.viewportChangePos).to.have.been.calledWith(-30, 0);
611
c9765e50 612 client._display.viewportChangePos.resetHistory();
9b84f516
PO
613
614 // Now a small movement should move right away
615
616 client._handleMouseMove(43, 14);
617
618 expect(RFB.messages.pointerEvent).to.not.have.been.called;
619 expect(client._display.viewportChangePos).to.have.been.calledOnce;
620 expect(client._display.viewportChangePos).to.have.been.calledWith(0, -5);
621 });
622
623 it('should not send button messages when dragging ends', function () {
624 // First the movement
625
626 client._handleMouseButton(13, 9, 0x001);
627 client._handleMouseMove(43, 9);
628 client._handleMouseButton(43, 9, 0x000);
629
630 expect(RFB.messages.pointerEvent).to.not.have.been.called;
631 });
632
633 it('should terminate viewport dragging on a button up event', function () {
634 // First the dragging movement
635
636 client._handleMouseButton(13, 9, 0x001);
637 client._handleMouseMove(43, 9);
638 client._handleMouseButton(43, 9, 0x000);
639
640 // Another movement now should not move the viewport
641
642 sinon.spy(client._display, "viewportChangePos");
643
644 client._handleMouseMove(43, 59);
645
646 expect(client._display.viewportChangePos).to.not.have.been.called;
647 });
648 });
649 });
650
651 describe('Scaling', function () {
2b5f94fa 652 let client;
9b84f516 653 beforeEach(function () {
00674385 654 client = makeRFB();
9b84f516
PO
655 container.style.width = '70px';
656 container.style.height = '80px';
657 client.scaleViewport = true;
658 });
659
660 it('should update display scale factor when changing the property', function () {
2b5f94fa 661 const spy = sinon.spy(client._display, "scale", ["set"]);
9b84f516
PO
662 sinon.spy(client._display, "autoscale");
663
664 client.scaleViewport = false;
665 expect(spy.set).to.have.been.calledOnce;
666 expect(spy.set).to.have.been.calledWith(1.0);
667 expect(client._display.autoscale).to.not.have.been.called;
668
669 client.scaleViewport = true;
670 expect(client._display.autoscale).to.have.been.calledOnce;
671 expect(client._display.autoscale).to.have.been.calledWith(70, 80);
672 });
673
674 it('should update the clipping setting when changing the property', function () {
675 client.clipViewport = true;
676
2b5f94fa 677 const spy = sinon.spy(client._display, "clipViewport", ["set"]);
9b84f516
PO
678
679 client.scaleViewport = false;
680 expect(spy.set).to.have.been.calledOnce;
681 expect(spy.set).to.have.been.calledWith(true);
682
c9765e50 683 spy.set.resetHistory();
9b84f516
PO
684
685 client.scaleViewport = true;
686 expect(spy.set).to.have.been.calledOnce;
687 expect(spy.set).to.have.been.calledWith(false);
688 });
689
690 it('should update the scaling when the container size changes', function () {
691 sinon.spy(client._display, "autoscale");
692
693 container.style.width = '40px';
694 container.style.height = '50px';
2b5f94fa 695 const event = new UIEvent('resize');
9b84f516
PO
696 window.dispatchEvent(event);
697 clock.tick();
698
699 expect(client._display.autoscale).to.have.been.calledOnce;
700 expect(client._display.autoscale).to.have.been.calledWith(40, 50);
701 });
702
703 it('should update the scaling when the remote session resizes', function () {
704 // Simple ExtendedDesktopSize FBU message
2b5f94fa 705 const incoming = [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
7b536961
PO
706 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0xfe, 0xcc,
707 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
708 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff,
709 0x00, 0x00, 0x00, 0x00 ];
9b84f516
PO
710
711 sinon.spy(client._display, "autoscale");
712
95632e41 713 client._sock._websocket._receiveData(new Uint8Array(incoming));
9b84f516
PO
714
715 expect(client._display.autoscale).to.have.been.calledOnce;
716 expect(client._display.autoscale).to.have.been.calledWith(70, 80);
717 });
718
719 it('should not update the display scale factor if not scaling', function () {
720 client.scaleViewport = false;
721
722 sinon.spy(client._display, "autoscale");
723
724 container.style.width = '40px';
725 container.style.height = '50px';
2b5f94fa 726 const event = new UIEvent('resize');
9b84f516
PO
727 window.dispatchEvent(event);
728 clock.tick();
729
730 expect(client._display.autoscale).to.not.have.been.called;
731 });
732 });
733
734 describe('Remote resize', function () {
2b5f94fa 735 let client;
9b84f516 736 beforeEach(function () {
00674385 737 client = makeRFB();
9b84f516
PO
738 client._supportsSetDesktopSize = true;
739 client.resizeSession = true;
740 container.style.width = '70px';
741 container.style.height = '80px';
742 sinon.spy(RFB.messages, "setDesktopSize");
743 });
744
745 afterEach(function () {
746 RFB.messages.setDesktopSize.restore();
747 });
748
749 it('should only request a resize when turned on', function () {
750 client.resizeSession = false;
751 expect(RFB.messages.setDesktopSize).to.not.have.been.called;
752 client.resizeSession = true;
753 expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;
754 });
755
756 it('should request a resize when initially connecting', function () {
757 // Simple ExtendedDesktopSize FBU message
2b5f94fa 758 const incoming = [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
7b536961
PO
759 0x00, 0x04, 0x00, 0x04, 0xff, 0xff, 0xfe, 0xcc,
760 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
761 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04,
762 0x00, 0x00, 0x00, 0x00 ];
9b84f516
PO
763
764 // First message should trigger a resize
765
766 client._supportsSetDesktopSize = false;
767
95632e41 768 client._sock._websocket._receiveData(new Uint8Array(incoming));
9b84f516
PO
769
770 expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;
771 expect(RFB.messages.setDesktopSize).to.have.been.calledWith(sinon.match.object, 70, 80, 0, 0);
772
c9765e50 773 RFB.messages.setDesktopSize.resetHistory();
9b84f516
PO
774
775 // Second message should not trigger a resize
776
95632e41 777 client._sock._websocket._receiveData(new Uint8Array(incoming));
9b84f516
PO
778
779 expect(RFB.messages.setDesktopSize).to.not.have.been.called;
780 });
781
782 it('should request a resize when the container resizes', function () {
783 container.style.width = '40px';
784 container.style.height = '50px';
2b5f94fa 785 const event = new UIEvent('resize');
9b84f516
PO
786 window.dispatchEvent(event);
787 clock.tick(1000);
788
789 expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;
790 expect(RFB.messages.setDesktopSize).to.have.been.calledWith(sinon.match.object, 40, 50, 0, 0);
791 });
792
793 it('should not resize until the container size is stable', function () {
794 container.style.width = '20px';
795 container.style.height = '30px';
2b5f94fa 796 const event1 = new UIEvent('resize');
8727f598 797 window.dispatchEvent(event1);
9b84f516
PO
798 clock.tick(400);
799
800 expect(RFB.messages.setDesktopSize).to.not.have.been.called;
801
802 container.style.width = '40px';
803 container.style.height = '50px';
2b5f94fa 804 const event2 = new UIEvent('resize');
8727f598 805 window.dispatchEvent(event2);
9b84f516
PO
806 clock.tick(400);
807
808 expect(RFB.messages.setDesktopSize).to.not.have.been.called;
809
810 clock.tick(200);
811
812 expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;
813 expect(RFB.messages.setDesktopSize).to.have.been.calledWith(sinon.match.object, 40, 50, 0, 0);
814 });
815
816 it('should not resize when resize is disabled', function () {
817 client._resizeSession = false;
818
819 container.style.width = '40px';
820 container.style.height = '50px';
2b5f94fa 821 const event = new UIEvent('resize');
9b84f516
PO
822 window.dispatchEvent(event);
823 clock.tick(1000);
824
825 expect(RFB.messages.setDesktopSize).to.not.have.been.called;
826 });
827
828 it('should not resize when resize is not supported', function () {
829 client._supportsSetDesktopSize = false;
830
831 container.style.width = '40px';
832 container.style.height = '50px';
2b5f94fa 833 const event = new UIEvent('resize');
9b84f516
PO
834 window.dispatchEvent(event);
835 clock.tick(1000);
836
837 expect(RFB.messages.setDesktopSize).to.not.have.been.called;
838 });
839
840 it('should not resize when in view only mode', function () {
841 client._viewOnly = true;
842
843 container.style.width = '40px';
844 container.style.height = '50px';
2b5f94fa 845 const event = new UIEvent('resize');
9b84f516
PO
846 window.dispatchEvent(event);
847 clock.tick(1000);
848
849 expect(RFB.messages.setDesktopSize).to.not.have.been.called;
850 });
851
852 it('should not try to override a server resize', function () {
853 // Simple ExtendedDesktopSize FBU message
2b5f94fa 854 const incoming = [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
7b536961
PO
855 0x00, 0x04, 0x00, 0x04, 0xff, 0xff, 0xfe, 0xcc,
856 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
857 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04,
858 0x00, 0x00, 0x00, 0x00 ];
9b84f516 859
95632e41 860 client._sock._websocket._receiveData(new Uint8Array(incoming));
9b84f516
PO
861
862 expect(RFB.messages.setDesktopSize).to.not.have.been.called;
863 });
864 });
865
b1dee947 866 describe('Misc Internals', function () {
3bb12056 867 describe('#_fail', function () {
2b5f94fa 868 let client;
3bb12056 869 beforeEach(function () {
00674385 870 client = makeRFB();
3bb12056
SM
871 });
872
3bb12056
SM
873 it('should close the WebSocket connection', function () {
874 sinon.spy(client._sock, 'close');
875 client._fail();
876 expect(client._sock.close).to.have.been.calledOnce;
877 });
878
879 it('should transition to disconnected', function () {
880 sinon.spy(client, '_updateConnectionState');
881 client._fail();
882 this.clock.tick(2000);
883 expect(client._updateConnectionState).to.have.been.called;
e7dec527 884 expect(client._rfbConnectionState).to.equal('disconnected');
3bb12056
SM
885 });
886
d472f3f1 887 it('should set clean_disconnect variable', function () {
e7dec527
SM
888 client._rfbCleanDisconnect = true;
889 client._rfbConnectionState = 'connected';
d472f3f1 890 client._fail();
e7dec527 891 expect(client._rfbCleanDisconnect).to.be.false;
67cd2072
SM
892 });
893
d472f3f1 894 it('should result in disconnect event with clean set to false', function () {
e7dec527 895 client._rfbConnectionState = 'connected';
2b5f94fa 896 const spy = sinon.spy();
e89eef94 897 client.addEventListener("disconnect", spy);
d472f3f1 898 client._fail();
3bb12056
SM
899 this.clock.tick(2000);
900 expect(spy).to.have.been.calledOnce;
d472f3f1 901 expect(spy.args[0][0].detail.clean).to.be.false;
3bb12056
SM
902 });
903
b1dee947
SR
904 });
905 });
906
b1dee947 907 describe('Protocol Initialization States', function () {
2b5f94fa 908 let client;
057b8fec 909 beforeEach(function () {
00674385 910 client = makeRFB();
e7dec527 911 client._rfbConnectionState = 'connecting';
057b8fec 912 });
b1dee947 913
057b8fec 914 describe('ProtocolVersion', function () {
00674385 915 function sendVer(ver, client) {
2b5f94fa
JD
916 const arr = new Uint8Array(12);
917 for (let i = 0; i < ver.length; i++) {
b1dee947
SR
918 arr[i+4] = ver.charCodeAt(i);
919 }
920 arr[0] = 'R'; arr[1] = 'F'; arr[2] = 'B'; arr[3] = ' ';
921 arr[11] = '\n';
95632e41 922 client._sock._websocket._receiveData(arr);
b1dee947
SR
923 }
924
925 describe('version parsing', function () {
b1dee947 926 it('should interpret version 003.003 as version 3.3', function () {
00674385 927 sendVer('003.003', client);
e7dec527 928 expect(client._rfbVersion).to.equal(3.3);
b1dee947
SR
929 });
930
931 it('should interpret version 003.006 as version 3.3', function () {
00674385 932 sendVer('003.006', client);
e7dec527 933 expect(client._rfbVersion).to.equal(3.3);
b1dee947
SR
934 });
935
936 it('should interpret version 003.889 as version 3.3', function () {
00674385 937 sendVer('003.889', client);
e7dec527 938 expect(client._rfbVersion).to.equal(3.3);
b1dee947
SR
939 });
940
941 it('should interpret version 003.007 as version 3.7', function () {
00674385 942 sendVer('003.007', client);
e7dec527 943 expect(client._rfbVersion).to.equal(3.7);
b1dee947
SR
944 });
945
946 it('should interpret version 003.008 as version 3.8', function () {
00674385 947 sendVer('003.008', client);
e7dec527 948 expect(client._rfbVersion).to.equal(3.8);
b1dee947
SR
949 });
950
951 it('should interpret version 004.000 as version 3.8', function () {
00674385 952 sendVer('004.000', client);
e7dec527 953 expect(client._rfbVersion).to.equal(3.8);
b1dee947
SR
954 });
955
956 it('should interpret version 004.001 as version 3.8', function () {
00674385 957 sendVer('004.001', client);
e7dec527 958 expect(client._rfbVersion).to.equal(3.8);
b1dee947
SR
959 });
960
49aa5b81 961 it('should interpret version 005.000 as version 3.8', function () {
00674385 962 sendVer('005.000', client);
e7dec527 963 expect(client._rfbVersion).to.equal(3.8);
49aa5b81
LOH
964 });
965
b1dee947 966 it('should fail on an invalid version', function () {
3bb12056 967 sinon.spy(client, "_fail");
00674385 968 sendVer('002.000', client);
3bb12056 969 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
970 });
971 });
972
b1dee947 973 it('should send back the interpreted version', function () {
00674385 974 sendVer('004.000', client);
b1dee947 975
e7dec527 976 const expectedStr = 'RFB 003.008\n';
2b5f94fa 977 const expected = [];
e7dec527
SM
978 for (let i = 0; i < expectedStr.length; i++) {
979 expected[i] = expectedStr.charCodeAt(i);
b1dee947
SR
980 }
981
9ff86fb7 982 expect(client._sock).to.have.sent(new Uint8Array(expected));
b1dee947
SR
983 });
984
985 it('should transition to the Security state on successful negotiation', function () {
00674385 986 sendVer('003.008', client);
e7dec527 987 expect(client._rfbInitState).to.equal('Security');
b1dee947 988 });
3d7bb020
PO
989
990 describe('Repeater', function () {
057b8fec 991 beforeEach(function () {
00674385 992 client = makeRFB('wss://host:8675', { repeaterID: "12345" });
e7dec527 993 client._rfbConnectionState = 'connecting';
057b8fec
PO
994 });
995
996 it('should interpret version 000.000 as a repeater', function () {
00674385 997 sendVer('000.000', client);
e7dec527 998 expect(client._rfbVersion).to.equal(0);
3d7bb020 999
95632e41 1000 const sentData = client._sock._websocket._getSentData();
e7dec527
SM
1001 expect(new Uint8Array(sentData.buffer, 0, 9)).to.array.equal(new Uint8Array([73, 68, 58, 49, 50, 51, 52, 53, 0]));
1002 expect(sentData).to.have.length(250);
3d7bb020
PO
1003 });
1004
1005 it('should handle two step repeater negotiation', function () {
00674385
SM
1006 sendVer('000.000', client);
1007 sendVer('003.008', client);
e7dec527 1008 expect(client._rfbVersion).to.equal(3.8);
3d7bb020
PO
1009 });
1010 });
b1dee947
SR
1011 });
1012
1013 describe('Security', function () {
b1dee947 1014 beforeEach(function () {
e7dec527 1015 client._rfbInitState = 'Security';
b1dee947
SR
1016 });
1017
1018 it('should simply receive the auth scheme when for versions < 3.7', function () {
e7dec527
SM
1019 client._rfbVersion = 3.6;
1020 const authSchemeRaw = [1, 2, 3, 4];
1021 const authScheme = (authSchemeRaw[0] << 24) + (authSchemeRaw[1] << 16) +
1022 (authSchemeRaw[2] << 8) + authSchemeRaw[3];
95632e41 1023 client._sock._websocket._receiveData(new Uint8Array(authSchemeRaw));
e7dec527 1024 expect(client._rfbAuthScheme).to.equal(authScheme);
b1dee947
SR
1025 });
1026
0ee5ca6e 1027 it('should prefer no authentication is possible', function () {
e7dec527
SM
1028 client._rfbVersion = 3.7;
1029 const authSchemes = [2, 1, 3];
95632e41 1030 client._sock._websocket._receiveData(new Uint8Array(authSchemes));
e7dec527 1031 expect(client._rfbAuthScheme).to.equal(1);
0ee5ca6e
PO
1032 expect(client._sock).to.have.sent(new Uint8Array([1, 1]));
1033 });
1034
b1dee947 1035 it('should choose for the most prefered scheme possible for versions >= 3.7', function () {
e7dec527
SM
1036 client._rfbVersion = 3.7;
1037 const authSchemes = [2, 22, 16];
95632e41 1038 client._sock._websocket._receiveData(new Uint8Array(authSchemes));
e7dec527 1039 expect(client._rfbAuthScheme).to.equal(22);
0ee5ca6e 1040 expect(client._sock).to.have.sent(new Uint8Array([22]));
b1dee947
SR
1041 });
1042
1043 it('should fail if there are no supported schemes for versions >= 3.7', function () {
3bb12056 1044 sinon.spy(client, "_fail");
e7dec527
SM
1045 client._rfbVersion = 3.7;
1046 const authSchemes = [1, 32];
95632e41 1047 client._sock._websocket._receiveData(new Uint8Array(authSchemes));
3bb12056 1048 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
1049 });
1050
1051 it('should fail with the appropriate message if no types are sent for versions >= 3.7', function () {
e7dec527
SM
1052 client._rfbVersion = 3.7;
1053 const failureData = [0, 0, 0, 0, 6, 119, 104, 111, 111, 112, 115];
b1dee947 1054 sinon.spy(client, '_fail');
95632e41 1055 client._sock._websocket._receiveData(new Uint8Array(failureData));
b1dee947 1056
159c50c0 1057 expect(client._fail).to.have.been.calledOnce;
67cd2072 1058 expect(client._fail).to.have.been.calledWith(
d472f3f1 1059 'Security negotiation failed on no security types (reason: whoops)');
b1dee947
SR
1060 });
1061
1062 it('should transition to the Authentication state and continue on successful negotiation', function () {
e7dec527
SM
1063 client._rfbVersion = 3.7;
1064 const authSchemes = [1, 1];
00674385 1065 client._negotiateAuthentication = sinon.spy();
95632e41 1066 client._sock._websocket._receiveData(new Uint8Array(authSchemes));
e7dec527 1067 expect(client._rfbInitState).to.equal('Authentication');
00674385 1068 expect(client._negotiateAuthentication).to.have.been.calledOnce;
b1dee947
SR
1069 });
1070 });
1071
1072 describe('Authentication', function () {
b1dee947 1073 beforeEach(function () {
e7dec527 1074 client._rfbInitState = 'Security';
b1dee947
SR
1075 });
1076
00674385 1077 function sendSecurity(type, cl) {
95632e41 1078 cl._sock._websocket._receiveData(new Uint8Array([1, type]));
b1dee947
SR
1079 }
1080
1081 it('should fail on auth scheme 0 (pre 3.7) with the given message', function () {
e7dec527
SM
1082 client._rfbVersion = 3.6;
1083 const errMsg = "Whoopsies";
2b5f94fa 1084 const data = [0, 0, 0, 0];
e7dec527
SM
1085 const errLen = errMsg.length;
1086 push32(data, errLen);
1087 for (let i = 0; i < errLen; i++) {
1088 data.push(errMsg.charCodeAt(i));
b1dee947
SR
1089 }
1090
1091 sinon.spy(client, '_fail');
95632e41 1092 client._sock._websocket._receiveData(new Uint8Array(data));
67cd2072 1093 expect(client._fail).to.have.been.calledWith(
d472f3f1 1094 'Security negotiation failed on authentication scheme (reason: Whoopsies)');
b1dee947
SR
1095 });
1096
1097 it('should transition straight to SecurityResult on "no auth" (1) for versions >= 3.8', function () {
e7dec527 1098 client._rfbVersion = 3.8;
00674385 1099 sendSecurity(1, client);
e7dec527 1100 expect(client._rfbInitState).to.equal('SecurityResult');
b1dee947
SR
1101 });
1102
c00ee156 1103 it('should transition straight to ServerInitialisation on "no auth" for versions < 3.8', function () {
e7dec527 1104 client._rfbVersion = 3.7;
00674385 1105 sendSecurity(1, client);
e7dec527 1106 expect(client._rfbInitState).to.equal('ServerInitialisation');
b1dee947
SR
1107 });
1108
1109 it('should fail on an unknown auth scheme', function () {
3bb12056 1110 sinon.spy(client, "_fail");
e7dec527 1111 client._rfbVersion = 3.8;
00674385 1112 sendSecurity(57, client);
3bb12056 1113 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
1114 });
1115
1116 describe('VNC Authentication (type 2) Handler', function () {
b1dee947 1117 beforeEach(function () {
e7dec527
SM
1118 client._rfbInitState = 'Security';
1119 client._rfbVersion = 3.8;
b1dee947
SR
1120 });
1121
e89eef94 1122 it('should fire the credentialsrequired event if missing a password', function () {
2b5f94fa 1123 const spy = sinon.spy();
e89eef94 1124 client.addEventListener("credentialsrequired", spy);
00674385 1125 sendSecurity(2, client);
7d714b15 1126
2b5f94fa
JD
1127 const challenge = [];
1128 for (let i = 0; i < 16; i++) { challenge[i] = i; }
95632e41 1129 client._sock._websocket._receiveData(new Uint8Array(challenge));
aa5b3a35 1130
e7dec527 1131 expect(client._rfbCredentials).to.be.empty;
7d714b15 1132 expect(spy).to.have.been.calledOnce;
e89eef94 1133 expect(spy.args[0][0].detail.types).to.have.members(["password"]);
b1dee947
SR
1134 });
1135
1136 it('should encrypt the password with DES and then send it back', function () {
e7dec527 1137 client._rfbCredentials = { password: 'passwd' };
00674385 1138 sendSecurity(2, client);
95632e41 1139 client._sock._websocket._getSentData(); // skip the choice of auth reply
b1dee947 1140
2b5f94fa
JD
1141 const challenge = [];
1142 for (let i = 0; i < 16; i++) { challenge[i] = i; }
95632e41 1143 client._sock._websocket._receiveData(new Uint8Array(challenge));
b1dee947 1144
e7dec527
SM
1145 const desPass = RFB.genDES('passwd', challenge);
1146 expect(client._sock).to.have.sent(new Uint8Array(desPass));
b1dee947
SR
1147 });
1148
1149 it('should transition to SecurityResult immediately after sending the password', function () {
e7dec527 1150 client._rfbCredentials = { password: 'passwd' };
00674385 1151 sendSecurity(2, client);
b1dee947 1152
2b5f94fa
JD
1153 const challenge = [];
1154 for (let i = 0; i < 16; i++) { challenge[i] = i; }
95632e41 1155 client._sock._websocket._receiveData(new Uint8Array(challenge));
b1dee947 1156
e7dec527 1157 expect(client._rfbInitState).to.equal('SecurityResult');
b1dee947
SR
1158 });
1159 });
1160
1161 describe('XVP Authentication (type 22) Handler', function () {
b1dee947 1162 beforeEach(function () {
e7dec527
SM
1163 client._rfbInitState = 'Security';
1164 client._rfbVersion = 3.8;
b1dee947
SR
1165 });
1166
1167 it('should fall through to standard VNC authentication upon completion', function () {
e7dec527
SM
1168 client._rfbCredentials = { username: 'user',
1169 target: 'target',
1170 password: 'password' };
00674385
SM
1171 client._negotiateStdVNCAuth = sinon.spy();
1172 sendSecurity(22, client);
1173 expect(client._negotiateStdVNCAuth).to.have.been.calledOnce;
b1dee947
SR
1174 });
1175
2c5491e1 1176 it('should fire the credentialsrequired event if all credentials are missing', function () {
2b5f94fa 1177 const spy = sinon.spy();
e89eef94 1178 client.addEventListener("credentialsrequired", spy);
e7dec527 1179 client._rfbCredentials = {};
00674385 1180 sendSecurity(22, client);
7d714b15 1181
e7dec527 1182 expect(client._rfbCredentials).to.be.empty;
7d714b15 1183 expect(spy).to.have.been.calledOnce;
e89eef94 1184 expect(spy.args[0][0].detail.types).to.have.members(["username", "password", "target"]);
b1dee947
SR
1185 });
1186
2c5491e1 1187 it('should fire the credentialsrequired event if some credentials are missing', function () {
2b5f94fa 1188 const spy = sinon.spy();
e89eef94 1189 client.addEventListener("credentialsrequired", spy);
e7dec527
SM
1190 client._rfbCredentials = { username: 'user',
1191 target: 'target' };
00674385 1192 sendSecurity(22, client);
7d714b15 1193
7d714b15 1194 expect(spy).to.have.been.calledOnce;
e89eef94 1195 expect(spy.args[0][0].detail.types).to.have.members(["username", "password", "target"]);
b1dee947
SR
1196 });
1197
430f00d6 1198 it('should send user and target separately', function () {
e7dec527
SM
1199 client._rfbCredentials = { username: 'user',
1200 target: 'target',
1201 password: 'password' };
00674385 1202 client._negotiateStdVNCAuth = sinon.spy();
b1dee947 1203
00674385 1204 sendSecurity(22, client);
b1dee947 1205
2b5f94fa
JD
1206 const expected = [22, 4, 6]; // auth selection, len user, len target
1207 for (let i = 0; i < 10; i++) { expected[i+3] = 'usertarget'.charCodeAt(i); }
b1dee947 1208
9ff86fb7 1209 expect(client._sock).to.have.sent(new Uint8Array(expected));
b1dee947
SR
1210 });
1211 });
1212
1213 describe('TightVNC Authentication (type 16) Handler', function () {
b1dee947 1214 beforeEach(function () {
e7dec527
SM
1215 client._rfbInitState = 'Security';
1216 client._rfbVersion = 3.8;
00674385 1217 sendSecurity(16, client);
95632e41 1218 client._sock._websocket._getSentData(); // skip the security reply
b1dee947
SR
1219 });
1220
00674385 1221 function sendNumStrPairs(pairs, client) {
2b5f94fa 1222 const data = [];
651c23ec 1223 push32(data, pairs.length);
b1dee947 1224
651c23ec 1225 for (let i = 0; i < pairs.length; i++) {
3949a095 1226 push32(data, pairs[i][0]);
2b5f94fa 1227 for (let j = 0; j < 4; j++) {
b1dee947
SR
1228 data.push(pairs[i][1].charCodeAt(j));
1229 }
2b5f94fa 1230 for (let j = 0; j < 8; j++) {
b1dee947
SR
1231 data.push(pairs[i][2].charCodeAt(j));
1232 }
1233 }
1234
95632e41 1235 client._sock._websocket._receiveData(new Uint8Array(data));
b1dee947
SR
1236 }
1237
1238 it('should skip tunnel negotiation if no tunnels are requested', function () {
95632e41 1239 client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 0]));
e7dec527 1240 expect(client._rfbTightVNC).to.be.true;
b1dee947
SR
1241 });
1242
1243 it('should fail if no supported tunnels are listed', function () {
3bb12056 1244 sinon.spy(client, "_fail");
00674385 1245 sendNumStrPairs([[123, 'OTHR', 'SOMETHNG']], client);
3bb12056 1246 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
1247 });
1248
1249 it('should choose the notunnel tunnel type', function () {
00674385 1250 sendNumStrPairs([[0, 'TGHT', 'NOTUNNEL'], [123, 'OTHR', 'SOMETHNG']], client);
9ff86fb7 1251 expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 0]));
b1dee947
SR
1252 });
1253
8f47bd29 1254 it('should choose the notunnel tunnel type for Siemens devices', function () {
00674385 1255 sendNumStrPairs([[1, 'SICR', 'SCHANNEL'], [2, 'SICR', 'SCHANLPW']], client);
8f47bd29
PO
1256 expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 0]));
1257 });
1258
b1dee947 1259 it('should continue to sub-auth negotiation after tunnel negotiation', function () {
00674385 1260 sendNumStrPairs([[0, 'TGHT', 'NOTUNNEL']], client);
95632e41 1261 client._sock._websocket._getSentData(); // skip the tunnel choice here
00674385 1262 sendNumStrPairs([[1, 'STDV', 'NOAUTH__']], client);
9ff86fb7 1263 expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 1]));
e7dec527 1264 expect(client._rfbInitState).to.equal('SecurityResult');
b1dee947
SR
1265 });
1266
1267 /*it('should attempt to use VNC auth over no auth when possible', function () {
e7dec527 1268 client._rfbTightVNC = true;
00674385
SM
1269 client._negotiateStdVNCAuth = sinon.spy();
1270 sendNumStrPairs([[1, 'STDV', 'NOAUTH__'], [2, 'STDV', 'VNCAUTH_']], client);
b1dee947 1271 expect(client._sock).to.have.sent([0, 0, 0, 1]);
00674385 1272 expect(client._negotiateStdVNCAuth).to.have.been.calledOnce;
e7dec527 1273 expect(client._rfbAuthScheme).to.equal(2);
b1dee947
SR
1274 });*/ // while this would make sense, the original code doesn't actually do this
1275
1276 it('should accept the "no auth" auth type and transition to SecurityResult', function () {
e7dec527 1277 client._rfbTightVNC = true;
00674385 1278 sendNumStrPairs([[1, 'STDV', 'NOAUTH__']], client);
9ff86fb7 1279 expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 1]));
e7dec527 1280 expect(client._rfbInitState).to.equal('SecurityResult');
b1dee947
SR
1281 });
1282
1283 it('should accept VNC authentication and transition to that', function () {
e7dec527 1284 client._rfbTightVNC = true;
00674385
SM
1285 client._negotiateStdVNCAuth = sinon.spy();
1286 sendNumStrPairs([[2, 'STDV', 'VNCAUTH__']], client);
9ff86fb7 1287 expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 2]));
00674385 1288 expect(client._negotiateStdVNCAuth).to.have.been.calledOnce;
e7dec527 1289 expect(client._rfbAuthScheme).to.equal(2);
b1dee947
SR
1290 });
1291
1292 it('should fail if there are no supported auth types', function () {
3bb12056 1293 sinon.spy(client, "_fail");
e7dec527 1294 client._rfbTightVNC = true;
00674385 1295 sendNumStrPairs([[23, 'stdv', 'badval__']], client);
3bb12056 1296 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
1297 });
1298 });
f9a8c4cc 1299
1300 describe('VeNCrypt Authentication (type 19) Handler', function () {
1301 beforeEach(function () {
1302 client._rfbInitState = 'Security';
1303 client._rfbVersion = 3.8;
1304 sendSecurity(19, client);
1305 expect(client._sock).to.have.sent(new Uint8Array([19]));
1306 });
1307
1308 it('should fail with non-0.2 versions', function () {
1309 sinon.spy(client, "_fail");
1310 client._sock._websocket._receiveData(new Uint8Array([0, 1]));
1311 expect(client._fail).to.have.been.calledOnce;
1312 });
1313
1314 it('should fail if the Plain authentication is not present', function () {
1315 // VeNCrypt version
1316 client._sock._websocket._receiveData(new Uint8Array([0, 2]));
1317 expect(client._sock).to.have.sent(new Uint8Array([0, 2]));
1318 // Server ACK.
1319 client._sock._websocket._receiveData(new Uint8Array([0]));
1320 // Subtype list, only list subtype 1.
1321 sinon.spy(client, "_fail");
1322 client._sock._websocket._receiveData(new Uint8Array([1, 0, 0, 0, 1]));
1323 expect(client._fail).to.have.been.calledOnce;
1324 });
1325
1326 it('should support Plain authentication', function () {
1327 client._rfbCredentials = { username: 'username', password: 'password' };
1328 // VeNCrypt version
1329 client._sock._websocket._receiveData(new Uint8Array([0, 2]));
1330 expect(client._sock).to.have.sent(new Uint8Array([0, 2]));
1331 // Server ACK.
1332 client._sock._websocket._receiveData(new Uint8Array([0]));
1333 // Subtype list.
1334 client._sock._websocket._receiveData(new Uint8Array([1, 0, 0, 1, 0]));
1335
1336 const expectedResponse = [];
1337 push32(expectedResponse, 256); // Chosen subtype.
1338 push32(expectedResponse, client._rfbCredentials.username.length);
1339 push32(expectedResponse, client._rfbCredentials.password.length);
1340 pushString(expectedResponse, client._rfbCredentials.username);
1341 pushString(expectedResponse, client._rfbCredentials.password);
1342 expect(client._sock).to.have.sent(new Uint8Array(expectedResponse));
1343
1344 client._initMsg = sinon.spy();
1345 client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 0]));
1346 expect(client._initMsg).to.have.been.called;
1347 });
1348
1349 it('should support Plain authentication with an empty password', function () {
1350 client._rfbCredentials = { username: 'username', password: '' };
1351 // VeNCrypt version
1352 client._sock._websocket._receiveData(new Uint8Array([0, 2]));
1353 expect(client._sock).to.have.sent(new Uint8Array([0, 2]));
1354 // Server ACK.
1355 client._sock._websocket._receiveData(new Uint8Array([0]));
1356 // Subtype list.
1357 client._sock._websocket._receiveData(new Uint8Array([1, 0, 0, 1, 0]));
1358
1359 const expectedResponse = [];
1360 push32(expectedResponse, 256); // Chosen subtype.
1361 push32(expectedResponse, client._rfbCredentials.username.length);
1362 push32(expectedResponse, client._rfbCredentials.password.length);
1363 pushString(expectedResponse, client._rfbCredentials.username);
1364 pushString(expectedResponse, client._rfbCredentials.password);
1365 expect(client._sock).to.have.sent(new Uint8Array(expectedResponse));
1366
1367 client._initMsg = sinon.spy();
1368 client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 0]));
1369 expect(client._initMsg).to.have.been.called;
1370 });
1371
1372 it('should support Plain authentication with a very long username and password', function () {
1373 client._rfbCredentials = { username: 'a'.repeat(300), password: 'a'.repeat(300) };
1374 // VeNCrypt version
1375 client._sock._websocket._receiveData(new Uint8Array([0, 2]));
1376 expect(client._sock).to.have.sent(new Uint8Array([0, 2]));
1377 // Server ACK.
1378 client._sock._websocket._receiveData(new Uint8Array([0]));
1379 // Subtype list.
1380 client._sock._websocket._receiveData(new Uint8Array([1, 0, 0, 1, 0]));
1381
1382 const expectedResponse = [];
1383 push32(expectedResponse, 256); // Chosen subtype.
1384 push32(expectedResponse, client._rfbCredentials.username.length);
1385 push32(expectedResponse, client._rfbCredentials.password.length);
1386 pushString(expectedResponse, client._rfbCredentials.username);
1387 pushString(expectedResponse, client._rfbCredentials.password);
1388 expect(client._sock).to.have.sent(new Uint8Array(expectedResponse));
1389
1390 client._initMsg = sinon.spy();
1391 client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 0]));
1392 expect(client._initMsg).to.have.been.called;
1393 });
1394 });
b1dee947
SR
1395 });
1396
1397 describe('SecurityResult', function () {
b1dee947 1398 beforeEach(function () {
e7dec527 1399 client._rfbInitState = 'SecurityResult';
b1dee947
SR
1400 });
1401
c00ee156 1402 it('should fall through to ServerInitialisation on a response code of 0', function () {
95632e41 1403 client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 0]));
e7dec527 1404 expect(client._rfbInitState).to.equal('ServerInitialisation');
b1dee947
SR
1405 });
1406
1407 it('should fail on an error code of 1 with the given message for versions >= 3.8', function () {
e7dec527 1408 client._rfbVersion = 3.8;
b1dee947 1409 sinon.spy(client, '_fail');
e7dec527 1410 const failureData = [0, 0, 0, 1, 0, 0, 0, 6, 119, 104, 111, 111, 112, 115];
95632e41 1411 client._sock._websocket._receiveData(new Uint8Array(failureData));
67cd2072 1412 expect(client._fail).to.have.been.calledWith(
d472f3f1 1413 'Security negotiation failed on security result (reason: whoops)');
b1dee947
SR
1414 });
1415
1416 it('should fail on an error code of 1 with a standard message for version < 3.8', function () {
3bb12056 1417 sinon.spy(client, '_fail');
e7dec527 1418 client._rfbVersion = 3.7;
95632e41 1419 client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 1]));
d472f3f1
SM
1420 expect(client._fail).to.have.been.calledWith(
1421 'Security handshake failed');
1422 });
1423
1424 it('should result in securityfailure event when receiving a non zero status', function () {
2b5f94fa 1425 const spy = sinon.spy();
d472f3f1 1426 client.addEventListener("securityfailure", spy);
95632e41 1427 client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 2]));
d472f3f1
SM
1428 expect(spy).to.have.been.calledOnce;
1429 expect(spy.args[0][0].detail.status).to.equal(2);
1430 });
1431
1432 it('should include reason when provided in securityfailure event', function () {
e7dec527 1433 client._rfbVersion = 3.8;
2b5f94fa 1434 const spy = sinon.spy();
d472f3f1 1435 client.addEventListener("securityfailure", spy);
e7dec527
SM
1436 const failureData = [0, 0, 0, 1, 0, 0, 0, 12, 115, 117, 99, 104,
1437 32, 102, 97, 105, 108, 117, 114, 101];
95632e41 1438 client._sock._websocket._receiveData(new Uint8Array(failureData));
d472f3f1
SM
1439 expect(spy.args[0][0].detail.status).to.equal(1);
1440 expect(spy.args[0][0].detail.reason).to.equal('such failure');
1441 });
1442
1443 it('should not include reason when length is zero in securityfailure event', function () {
e7dec527 1444 client._rfbVersion = 3.9;
2b5f94fa 1445 const spy = sinon.spy();
d472f3f1 1446 client.addEventListener("securityfailure", spy);
e7dec527 1447 const failureData = [0, 0, 0, 1, 0, 0, 0, 0];
95632e41 1448 client._sock._websocket._receiveData(new Uint8Array(failureData));
d472f3f1
SM
1449 expect(spy.args[0][0].detail.status).to.equal(1);
1450 expect('reason' in spy.args[0][0].detail).to.be.false;
1451 });
1452
1453 it('should not include reason in securityfailure event for version < 3.8', function () {
e7dec527 1454 client._rfbVersion = 3.6;
2b5f94fa 1455 const spy = sinon.spy();
d472f3f1 1456 client.addEventListener("securityfailure", spy);
95632e41 1457 client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 2]));
d472f3f1
SM
1458 expect(spy.args[0][0].detail.status).to.equal(2);
1459 expect('reason' in spy.args[0][0].detail).to.be.false;
b1dee947
SR
1460 });
1461 });
1462
1463 describe('ClientInitialisation', function () {
b1dee947 1464 it('should transition to the ServerInitialisation state', function () {
00674385 1465 const client = makeRFB();
e7dec527
SM
1466 client._rfbConnectionState = 'connecting';
1467 client._rfbInitState = 'SecurityResult';
95632e41 1468 client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 0]));
e7dec527 1469 expect(client._rfbInitState).to.equal('ServerInitialisation');
b1dee947
SR
1470 });
1471
1472 it('should send 1 if we are in shared mode', function () {
00674385 1473 const client = makeRFB('wss://host:8675', { shared: true });
e7dec527
SM
1474 client._rfbConnectionState = 'connecting';
1475 client._rfbInitState = 'SecurityResult';
95632e41 1476 client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 0]));
9ff86fb7 1477 expect(client._sock).to.have.sent(new Uint8Array([1]));
b1dee947
SR
1478 });
1479
1480 it('should send 0 if we are not in shared mode', function () {
00674385 1481 const client = makeRFB('wss://host:8675', { shared: false });
e7dec527
SM
1482 client._rfbConnectionState = 'connecting';
1483 client._rfbInitState = 'SecurityResult';
95632e41 1484 client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 0]));
9ff86fb7 1485 expect(client._sock).to.have.sent(new Uint8Array([0]));
b1dee947
SR
1486 });
1487 });
1488
1489 describe('ServerInitialisation', function () {
b1dee947 1490 beforeEach(function () {
e7dec527 1491 client._rfbInitState = 'ServerInitialisation';
b1dee947
SR
1492 });
1493
00674385 1494 function sendServerInit(opts, client) {
80187d15
SM
1495 const fullOpts = { width: 10, height: 12, bpp: 24, depth: 24, bigEndian: 0,
1496 trueColor: 1, redMax: 255, greenMax: 255, blueMax: 255,
e7dec527 1497 redShift: 16, greenShift: 8, blueShift: 0, name: 'a name' };
2b5f94fa 1498 for (let opt in opts) {
e7dec527 1499 fullOpts[opt] = opts[opt];
b1dee947 1500 }
2b5f94fa 1501 const data = [];
b1dee947 1502
e7dec527
SM
1503 push16(data, fullOpts.width);
1504 push16(data, fullOpts.height);
b1dee947 1505
e7dec527
SM
1506 data.push(fullOpts.bpp);
1507 data.push(fullOpts.depth);
80187d15
SM
1508 data.push(fullOpts.bigEndian);
1509 data.push(fullOpts.trueColor);
b1dee947 1510
e7dec527
SM
1511 push16(data, fullOpts.redMax);
1512 push16(data, fullOpts.greenMax);
1513 push16(data, fullOpts.blueMax);
1514 push8(data, fullOpts.redShift);
1515 push8(data, fullOpts.greenShift);
1516 push8(data, fullOpts.blueShift);
b1dee947
SR
1517
1518 // padding
3949a095
SR
1519 push8(data, 0);
1520 push8(data, 0);
1521 push8(data, 0);
b1dee947 1522
95632e41 1523 client._sock._websocket._receiveData(new Uint8Array(data));
b1dee947 1524
e7dec527
SM
1525 const nameData = [];
1526 let nameLen = [];
1527 pushString(nameData, fullOpts.name);
1528 push32(nameLen, nameData.length);
8d6f686b 1529
95632e41
SM
1530 client._sock._websocket._receiveData(new Uint8Array(nameLen));
1531 client._sock._websocket._receiveData(new Uint8Array(nameData));
b1dee947
SR
1532 }
1533
1534 it('should set the framebuffer width and height', function () {
00674385 1535 sendServerInit({ width: 32, height: 84 }, client);
e7dec527
SM
1536 expect(client._fbWidth).to.equal(32);
1537 expect(client._fbHeight).to.equal(84);
b1dee947
SR
1538 });
1539
1540 // NB(sross): we just warn, not fail, for endian-ness and shifts, so we don't test them
1541
1542 it('should set the framebuffer name and call the callback', function () {
2b5f94fa 1543 const spy = sinon.spy();
e89eef94 1544 client.addEventListener("desktopname", spy);
00674385 1545 sendServerInit({ name: 'som€ nam€' }, client);
b1dee947 1546
e7dec527 1547 expect(client._fbName).to.equal('som€ nam€');
b1dee947 1548 expect(spy).to.have.been.calledOnce;
8d6f686b 1549 expect(spy.args[0][0].detail.name).to.equal('som€ nam€');
b1dee947
SR
1550 });
1551
1552 it('should handle the extended init message of the tight encoding', function () {
1553 // NB(sross): we don't actually do anything with it, so just test that we can
1554 // read it w/o throwing an error
e7dec527 1555 client._rfbTightVNC = true;
00674385 1556 sendServerInit({}, client);
b1dee947 1557
e7dec527
SM
1558 const tightData = [];
1559 push16(tightData, 1);
1560 push16(tightData, 2);
1561 push16(tightData, 3);
1562 push16(tightData, 0);
2b5f94fa 1563 for (let i = 0; i < 16 + 32 + 48; i++) {
e7dec527 1564 tightData.push(i);
b1dee947 1565 }
95632e41 1566 client._sock._websocket._receiveData(new Uint8Array(tightData));
b1dee947 1567
e7dec527 1568 expect(client._rfbConnectionState).to.equal('connected');
b1dee947
SR
1569 });
1570
9b84f516 1571 it('should resize the display', function () {
b1dee947 1572 sinon.spy(client._display, 'resize');
00674385 1573 sendServerInit({ width: 27, height: 32 }, client);
b1dee947 1574
b1dee947
SR
1575 expect(client._display.resize).to.have.been.calledOnce;
1576 expect(client._display.resize).to.have.been.calledWith(27, 32);
b1dee947
SR
1577 });
1578
50cde2fa 1579 it('should grab the keyboard', function () {
b1dee947 1580 sinon.spy(client._keyboard, 'grab');
00674385 1581 sendServerInit({}, client);
b1dee947 1582 expect(client._keyboard.grab).to.have.been.calledOnce;
b1dee947
SR
1583 });
1584
69411b9e
PO
1585 describe('Initial Update Request', function () {
1586 beforeEach(function () {
1587 sinon.spy(RFB.messages, "pixelFormat");
1588 sinon.spy(RFB.messages, "clientEncodings");
1589 sinon.spy(RFB.messages, "fbUpdateRequest");
1590 });
49a81837 1591
69411b9e
PO
1592 afterEach(function () {
1593 RFB.messages.pixelFormat.restore();
1594 RFB.messages.clientEncodings.restore();
1595 RFB.messages.fbUpdateRequest.restore();
1596 });
b1dee947 1597
69411b9e
PO
1598 // TODO(directxman12): test the various options in this configuration matrix
1599 it('should reply with the pixel format, client encodings, and initial update request', function () {
00674385 1600 sendServerInit({ width: 27, height: 32 }, client);
69411b9e
PO
1601
1602 expect(RFB.messages.pixelFormat).to.have.been.calledOnce;
1603 expect(RFB.messages.pixelFormat).to.have.been.calledWith(client._sock, 24, true);
1604 expect(RFB.messages.pixelFormat).to.have.been.calledBefore(RFB.messages.clientEncodings);
1605 expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
1606 expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.encodingTight);
1607 expect(RFB.messages.clientEncodings).to.have.been.calledBefore(RFB.messages.fbUpdateRequest);
1608 expect(RFB.messages.fbUpdateRequest).to.have.been.calledOnce;
1609 expect(RFB.messages.fbUpdateRequest).to.have.been.calledWith(client._sock, false, 0, 0, 27, 32);
1610 });
1611
1612 it('should reply with restricted settings for Intel AMT servers', function () {
00674385 1613 sendServerInit({ width: 27, height: 32, name: "Intel(r) AMT KVM"}, client);
69411b9e
PO
1614
1615 expect(RFB.messages.pixelFormat).to.have.been.calledOnce;
1616 expect(RFB.messages.pixelFormat).to.have.been.calledWith(client._sock, 8, true);
1617 expect(RFB.messages.pixelFormat).to.have.been.calledBefore(RFB.messages.clientEncodings);
1618 expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
1619 expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.not.include(encodings.encodingTight);
1620 expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.not.include(encodings.encodingHextile);
1621 expect(RFB.messages.clientEncodings).to.have.been.calledBefore(RFB.messages.fbUpdateRequest);
1622 expect(RFB.messages.fbUpdateRequest).to.have.been.calledOnce;
1623 expect(RFB.messages.fbUpdateRequest).to.have.been.calledWith(client._sock, false, 0, 0, 27, 32);
1624 });
b1dee947
SR
1625 });
1626
f0e4908d
PO
1627 it('should send the "connect" event', function () {
1628 let spy = sinon.spy();
1629 client.addEventListener('connect', spy);
00674385 1630 sendServerInit({}, client);
f0e4908d 1631 expect(spy).to.have.been.calledOnce;
b1dee947
SR
1632 });
1633 });
1634 });
1635
1636 describe('Protocol Message Processing After Completing Initialization', function () {
2b5f94fa 1637 let client;
b1dee947
SR
1638
1639 beforeEach(function () {
00674385 1640 client = makeRFB();
e7dec527
SM
1641 client._fbName = 'some device';
1642 client._fbWidth = 640;
1643 client._fbHeight = 20;
b1dee947
SR
1644 });
1645
1646 describe('Framebuffer Update Handling', function () {
00674385 1647 function sendFbuMsg(rectInfo, rectData, client, rectCnt) {
2b5f94fa 1648 let data = [];
b1dee947 1649
e7dec527 1650 if (!rectCnt || rectCnt > -1) {
b1dee947
SR
1651 // header
1652 data.push(0); // msg type
1653 data.push(0); // padding
e7dec527 1654 push16(data, rectCnt || rectData.length);
b1dee947
SR
1655 }
1656
e7dec527
SM
1657 for (let i = 0; i < rectData.length; i++) {
1658 if (rectInfo[i]) {
1659 push16(data, rectInfo[i].x);
1660 push16(data, rectInfo[i].y);
1661 push16(data, rectInfo[i].width);
1662 push16(data, rectInfo[i].height);
1663 push32(data, rectInfo[i].encoding);
b1dee947 1664 }
e7dec527 1665 data = data.concat(rectData[i]);
b1dee947
SR
1666 }
1667
95632e41 1668 client._sock._websocket._receiveData(new Uint8Array(data));
b1dee947
SR
1669 }
1670
1671 it('should send an update request if there is sufficient data', function () {
e7dec527
SM
1672 const expectedMsg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: () => {}};
1673 RFB.messages.fbUpdateRequest(expectedMsg, true, 0, 0, 640, 20);
b1dee947 1674
651c23ec 1675 client._framebufferUpdate = () => true;
95632e41 1676 client._sock._websocket._receiveData(new Uint8Array([0]));
b1dee947 1677
e7dec527 1678 expect(client._sock).to.have.sent(expectedMsg._sQ);
b1dee947
SR
1679 });
1680
1681 it('should not send an update request if we need more data', function () {
95632e41
SM
1682 client._sock._websocket._receiveData(new Uint8Array([0]));
1683 expect(client._sock._websocket._getSentData()).to.have.length(0);
b1dee947
SR
1684 });
1685
1686 it('should resume receiving an update if we previously did not have enough data', function () {
e7dec527
SM
1687 const expectedMsg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: () => {}};
1688 RFB.messages.fbUpdateRequest(expectedMsg, true, 0, 0, 640, 20);
b1dee947
SR
1689
1690 // just enough to set FBU.rects
95632e41
SM
1691 client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 3]));
1692 expect(client._sock._websocket._getSentData()).to.have.length(0);
b1dee947 1693
8a189a62 1694 client._framebufferUpdate = function () { this._sock.rQskipBytes(1); return true; }; // we magically have enough data
b1dee947 1695 // 247 should *not* be used as the message type here
95632e41 1696 client._sock._websocket._receiveData(new Uint8Array([247]));
e7dec527 1697 expect(client._sock).to.have.sent(expectedMsg._sQ);
b1dee947
SR
1698 });
1699
2ba767a7 1700 it('should not send a request in continuous updates mode', function () {
76a86ff5 1701 client._enabledContinuousUpdates = true;
651c23ec 1702 client._framebufferUpdate = () => true;
95632e41 1703 client._sock._websocket._receiveData(new Uint8Array([0]));
76a86ff5 1704
95632e41 1705 expect(client._sock._websocket._getSentData()).to.have.length(0);
76a86ff5 1706 });
1707
b1dee947 1708 it('should fail on an unsupported encoding', function () {
3bb12056 1709 sinon.spy(client, "_fail");
e7dec527 1710 const rectInfo = { x: 8, y: 11, width: 27, height: 32, encoding: 234 };
00674385 1711 sendFbuMsg([rectInfo], [[]], client);
3bb12056 1712 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
1713 });
1714
b1dee947 1715 describe('Message Encoding Handlers', function () {
b1dee947 1716 beforeEach(function () {
b1dee947 1717 // a really small frame
e7dec527
SM
1718 client._fbWidth = 4;
1719 client._fbHeight = 4;
1720 client._fbDepth = 24;
02329ab1 1721 client._display.resize(4, 4);
b1dee947
SR
1722 });
1723
b1dee947 1724 it('should handle the DesktopSize pseduo-encoding', function () {
b1dee947 1725 sinon.spy(client._display, 'resize');
00674385 1726 sendFbuMsg([{ x: 0, y: 0, width: 20, height: 50, encoding: -223 }], [[]], client);
b1dee947 1727
e7dec527
SM
1728 expect(client._fbWidth).to.equal(20);
1729 expect(client._fbHeight).to.equal(50);
b1dee947
SR
1730
1731 expect(client._display.resize).to.have.been.calledOnce;
1732 expect(client._display.resize).to.have.been.calledWith(20, 50);
1733 });
1734
4dec490a 1735 describe('the ExtendedDesktopSize pseudo-encoding handler', function () {
4dec490a 1736 beforeEach(function () {
4dec490a 1737 // a really small frame
e7dec527
SM
1738 client._fbWidth = 4;
1739 client._fbHeight = 4;
02329ab1 1740 client._display.resize(4, 4);
4dec490a 1741 sinon.spy(client._display, 'resize');
4dec490a 1742 });
1743
00674385 1744 function makeScreenData(nrOfScreens) {
2b5f94fa 1745 const data = [];
e7dec527 1746 push8(data, nrOfScreens); // number-of-screens
3949a095
SR
1747 push8(data, 0); // padding
1748 push16(data, 0); // padding
e7dec527 1749 for (let i=0; i<nrOfScreens; i += 1) {
3949a095
SR
1750 push32(data, 0); // id
1751 push16(data, 0); // x-position
1752 push16(data, 0); // y-position
1753 push16(data, 20); // width
1754 push16(data, 50); // height
1755 push32(data, 0); // flags
4dec490a 1756 }
1757 return data;
1758 }
1759
1760 it('should handle a resize requested by this client', function () {
e7dec527
SM
1761 const reasonForChange = 1; // requested by this client
1762 const statusCode = 0; // No error
4dec490a 1763
00674385
SM
1764 sendFbuMsg([{ x: reasonForChange, y: statusCode,
1765 width: 20, height: 50, encoding: -308 }],
1766 makeScreenData(1), client);
4dec490a 1767
e7dec527
SM
1768 expect(client._fbWidth).to.equal(20);
1769 expect(client._fbHeight).to.equal(50);
4dec490a 1770
1771 expect(client._display.resize).to.have.been.calledOnce;
1772 expect(client._display.resize).to.have.been.calledWith(20, 50);
4dec490a 1773 });
1774
1775 it('should handle a resize requested by another client', function () {
e7dec527
SM
1776 const reasonForChange = 2; // requested by another client
1777 const statusCode = 0; // No error
4dec490a 1778
00674385
SM
1779 sendFbuMsg([{ x: reasonForChange, y: statusCode,
1780 width: 20, height: 50, encoding: -308 }],
1781 makeScreenData(1), client);
4dec490a 1782
e7dec527
SM
1783 expect(client._fbWidth).to.equal(20);
1784 expect(client._fbHeight).to.equal(50);
4dec490a 1785
1786 expect(client._display.resize).to.have.been.calledOnce;
1787 expect(client._display.resize).to.have.been.calledWith(20, 50);
4dec490a 1788 });
1789
1790 it('should be able to recieve requests which contain data for multiple screens', function () {
e7dec527
SM
1791 const reasonForChange = 2; // requested by another client
1792 const statusCode = 0; // No error
4dec490a 1793
00674385
SM
1794 sendFbuMsg([{ x: reasonForChange, y: statusCode,
1795 width: 60, height: 50, encoding: -308 }],
1796 makeScreenData(3), client);
4dec490a 1797
e7dec527
SM
1798 expect(client._fbWidth).to.equal(60);
1799 expect(client._fbHeight).to.equal(50);
4dec490a 1800
1801 expect(client._display.resize).to.have.been.calledOnce;
1802 expect(client._display.resize).to.have.been.calledWith(60, 50);
4dec490a 1803 });
1804
1805 it('should not handle a failed request', function () {
e7dec527
SM
1806 const reasonForChange = 1; // requested by this client
1807 const statusCode = 1; // Resize is administratively prohibited
4dec490a 1808
00674385
SM
1809 sendFbuMsg([{ x: reasonForChange, y: statusCode,
1810 width: 20, height: 50, encoding: -308 }],
1811 makeScreenData(1), client);
4dec490a 1812
e7dec527
SM
1813 expect(client._fbWidth).to.equal(4);
1814 expect(client._fbHeight).to.equal(4);
4dec490a 1815
1816 expect(client._display.resize).to.not.have.been.called;
4dec490a 1817 });
1818 });
1819
d1050405
PO
1820 describe('the Cursor pseudo-encoding handler', function () {
1821 beforeEach(function () {
1822 sinon.spy(client._cursor, 'change');
1823 });
1824
1825 it('should handle a standard cursor', function () {
1826 const info = { x: 5, y: 7,
1827 width: 4, height: 4,
1828 encoding: -239};
1829 let rect = [];
1830 let expected = [];
1831
1832 for (let i = 0;i < info.width*info.height;i++) {
1833 push32(rect, 0x11223300);
1834 }
1835 push32(rect, 0xa0a0a0a0);
1836
1837 for (let i = 0;i < info.width*info.height/2;i++) {
1838 push32(expected, 0x332211ff);
1839 push32(expected, 0x33221100);
1840 }
1841 expected = new Uint8Array(expected);
1842
00674385 1843 sendFbuMsg([info], [rect], client);
d1050405
PO
1844
1845 expect(client._cursor.change).to.have.been.calledOnce;
1846 expect(client._cursor.change).to.have.been.calledWith(expected, 5, 7, 4, 4);
1847 });
1848
1849 it('should handle an empty cursor', function () {
1850 const info = { x: 0, y: 0,
1851 width: 0, height: 0,
1852 encoding: -239};
1853 const rect = [];
1854
00674385 1855 sendFbuMsg([info], [rect], client);
d1050405
PO
1856
1857 expect(client._cursor.change).to.have.been.calledOnce;
1858 expect(client._cursor.change).to.have.been.calledWith(new Uint8Array, 0, 0, 0, 0);
1859 });
1860
1861 it('should handle a transparent cursor', function () {
1862 const info = { x: 5, y: 7,
1863 width: 4, height: 4,
1864 encoding: -239};
1865 let rect = [];
1866 let expected = [];
1867
1868 for (let i = 0;i < info.width*info.height;i++) {
1869 push32(rect, 0x11223300);
1870 }
1871 push32(rect, 0x00000000);
1872
1873 for (let i = 0;i < info.width*info.height;i++) {
1874 push32(expected, 0x33221100);
1875 }
1876 expected = new Uint8Array(expected);
1877
00674385 1878 sendFbuMsg([info], [rect], client);
d1050405
PO
1879
1880 expect(client._cursor.change).to.have.been.calledOnce;
1881 expect(client._cursor.change).to.have.been.calledWith(expected, 5, 7, 4, 4);
1882 });
1883
1884 describe('dot for empty cursor', function () {
1885 beforeEach(function () {
1886 client.showDotCursor = true;
1887 // Was called when we enabled dot cursor
c9765e50 1888 client._cursor.change.resetHistory();
d1050405
PO
1889 });
1890
1891 it('should show a standard cursor', function () {
1892 const info = { x: 5, y: 7,
1893 width: 4, height: 4,
1894 encoding: -239};
1895 let rect = [];
1896 let expected = [];
1897
1898 for (let i = 0;i < info.width*info.height;i++) {
1899 push32(rect, 0x11223300);
1900 }
1901 push32(rect, 0xa0a0a0a0);
1902
1903 for (let i = 0;i < info.width*info.height/2;i++) {
1904 push32(expected, 0x332211ff);
1905 push32(expected, 0x33221100);
1906 }
1907 expected = new Uint8Array(expected);
1908
00674385 1909 sendFbuMsg([info], [rect], client);
d1050405
PO
1910
1911 expect(client._cursor.change).to.have.been.calledOnce;
1912 expect(client._cursor.change).to.have.been.calledWith(expected, 5, 7, 4, 4);
1913 });
1914
1915 it('should handle an empty cursor', function () {
1916 const info = { x: 0, y: 0,
1917 width: 0, height: 0,
1918 encoding: -239};
1919 const rect = [];
1920 const dot = RFB.cursors.dot;
1921
00674385 1922 sendFbuMsg([info], [rect], client);
d1050405
PO
1923
1924 expect(client._cursor.change).to.have.been.calledOnce;
1925 expect(client._cursor.change).to.have.been.calledWith(dot.rgbaPixels,
1926 dot.hotx,
1927 dot.hoty,
1928 dot.w,
1929 dot.h);
1930 });
1931
1932 it('should handle a transparent cursor', function () {
1933 const info = { x: 5, y: 7,
1934 width: 4, height: 4,
1935 encoding: -239};
1936 let rect = [];
1937 const dot = RFB.cursors.dot;
1938
1939 for (let i = 0;i < info.width*info.height;i++) {
1940 push32(rect, 0x11223300);
1941 }
1942 push32(rect, 0x00000000);
1943
00674385 1944 sendFbuMsg([info], [rect], client);
d1050405
PO
1945
1946 expect(client._cursor.change).to.have.been.calledOnce;
1947 expect(client._cursor.change).to.have.been.calledWith(dot.rgbaPixels,
1948 dot.hotx,
1949 dot.hoty,
1950 dot.w,
1951 dot.h);
1952 });
1953 });
b1dee947
SR
1954 });
1955
296ba51f
NL
1956 describe('the VMware Cursor pseudo-encoding handler', function () {
1957 beforeEach(function () {
1958 sinon.spy(client._cursor, 'change');
1959 });
1960 afterEach(function () {
1961 client._cursor.change.resetHistory();
1962 });
1963
1964 it('should handle the VMware cursor pseudo-encoding', function () {
1965 let data = [0x00, 0x00, 0xff, 0,
1966 0x00, 0xff, 0x00, 0,
1967 0x00, 0xff, 0x00, 0,
1968 0x00, 0x00, 0xff, 0];
1969 let rect = [];
1970 push8(rect, 0);
1971 push8(rect, 0);
1972
1973 //AND-mask
1974 for (let i = 0; i < data.length; i++) {
1975 push8(rect, data[i]);
1976 }
1977 //XOR-mask
1978 for (let i = 0; i < data.length; i++) {
1979 push8(rect, data[i]);
1980 }
1981
00674385
SM
1982 sendFbuMsg([{ x: 0, y: 0, width: 2, height: 2,
1983 encoding: 0x574d5664}],
1984 [rect], client);
296ba51f
NL
1985 expect(client._FBU.rects).to.equal(0);
1986 });
1987
1988 it('should handle insufficient cursor pixel data', function () {
1989
1990 // Specified 14x23 pixels for the cursor,
1991 // but only send 2x2 pixels worth of data
1992 let w = 14;
1993 let h = 23;
1994 let data = [0x00, 0x00, 0xff, 0,
1995 0x00, 0xff, 0x00, 0];
1996 let rect = [];
1997
1998 push8(rect, 0);
1999 push8(rect, 0);
2000
2001 //AND-mask
2002 for (let i = 0; i < data.length; i++) {
2003 push8(rect, data[i]);
2004 }
2005 //XOR-mask
2006 for (let i = 0; i < data.length; i++) {
2007 push8(rect, data[i]);
2008 }
2009
00674385
SM
2010 sendFbuMsg([{ x: 0, y: 0, width: w, height: h,
2011 encoding: 0x574d5664}],
2012 [rect], client);
296ba51f
NL
2013
2014 // expect one FBU to remain unhandled
2015 expect(client._FBU.rects).to.equal(1);
2016 });
2017
2018 it('should update the cursor when type is classic', function () {
e7dec527 2019 let andMask =
296ba51f
NL
2020 [0xff, 0xff, 0xff, 0xff, //Transparent
2021 0xff, 0xff, 0xff, 0xff, //Transparent
2022 0x00, 0x00, 0x00, 0x00, //Opaque
2023 0xff, 0xff, 0xff, 0xff]; //Inverted
2024
e7dec527 2025 let xorMask =
296ba51f
NL
2026 [0x00, 0x00, 0x00, 0x00, //Transparent
2027 0x00, 0x00, 0x00, 0x00, //Transparent
2028 0x11, 0x22, 0x33, 0x44, //Opaque
2029 0xff, 0xff, 0xff, 0x44]; //Inverted
2030
2031 let rect = [];
2032 push8(rect, 0); //cursor_type
2033 push8(rect, 0); //padding
2034 let hotx = 0;
2035 let hoty = 0;
2036 let w = 2;
2037 let h = 2;
2038
2039 //AND-mask
e7dec527
SM
2040 for (let i = 0; i < andMask.length; i++) {
2041 push8(rect, andMask[i]);
296ba51f
NL
2042 }
2043 //XOR-mask
e7dec527
SM
2044 for (let i = 0; i < xorMask.length; i++) {
2045 push8(rect, xorMask[i]);
296ba51f
NL
2046 }
2047
e7dec527
SM
2048 let expectedRgba = [0x00, 0x00, 0x00, 0x00,
2049 0x00, 0x00, 0x00, 0x00,
2050 0x33, 0x22, 0x11, 0xff,
2051 0x00, 0x00, 0x00, 0xff];
296ba51f 2052
00674385
SM
2053 sendFbuMsg([{ x: hotx, y: hoty,
2054 width: w, height: h,
2055 encoding: 0x574d5664}],
2056 [rect], client);
296ba51f
NL
2057
2058 expect(client._cursor.change)
2059 .to.have.been.calledOnce;
2060 expect(client._cursor.change)
e7dec527 2061 .to.have.been.calledWith(expectedRgba,
296ba51f
NL
2062 hotx, hoty,
2063 w, h);
2064 });
2065
2066 it('should update the cursor when type is alpha', function () {
71bb3fdf 2067 let data = [0xee, 0x55, 0xff, 0x00, // rgba
296ba51f
NL
2068 0x00, 0xff, 0x00, 0xff,
2069 0x00, 0xff, 0x00, 0x22,
2070 0x00, 0xff, 0x00, 0x22,
2071 0x00, 0xff, 0x00, 0x22,
2072 0x00, 0x00, 0xff, 0xee];
2073 let rect = [];
2074 push8(rect, 1); //cursor_type
2075 push8(rect, 0); //padding
2076 let hotx = 0;
2077 let hoty = 0;
2078 let w = 3;
2079 let h = 2;
2080
2081 for (let i = 0; i < data.length; i++) {
2082 push8(rect, data[i]);
2083 }
2084
e7dec527
SM
2085 let expectedRgba = [0xee, 0x55, 0xff, 0x00,
2086 0x00, 0xff, 0x00, 0xff,
2087 0x00, 0xff, 0x00, 0x22,
2088 0x00, 0xff, 0x00, 0x22,
2089 0x00, 0xff, 0x00, 0x22,
2090 0x00, 0x00, 0xff, 0xee];
296ba51f 2091
00674385
SM
2092 sendFbuMsg([{ x: hotx, y: hoty,
2093 width: w, height: h,
2094 encoding: 0x574d5664}],
2095 [rect], client);
296ba51f
NL
2096
2097 expect(client._cursor.change)
2098 .to.have.been.calledOnce;
2099 expect(client._cursor.change)
e7dec527 2100 .to.have.been.calledWith(expectedRgba,
296ba51f
NL
2101 hotx, hoty,
2102 w, h);
2103 });
2104
2105 it('should not update cursor when incorrect cursor type given', function () {
2106 let rect = [];
2107 push8(rect, 3); // invalid cursor type
2108 push8(rect, 0); // padding
2109
2110 client._cursor.change.resetHistory();
00674385
SM
2111 sendFbuMsg([{ x: 0, y: 0, width: 2, height: 2,
2112 encoding: 0x574d5664}],
2113 [rect], client);
296ba51f
NL
2114
2115 expect(client._cursor.change)
2116 .to.not.have.been.called;
2117 });
2118 });
2119
b1dee947 2120 it('should handle the last_rect pseudo-encoding', function () {
00674385 2121 sendFbuMsg([{ x: 0, y: 0, width: 0, height: 0, encoding: -224}], [[]], client, 100);
b1dee947 2122 expect(client._FBU.rects).to.equal(0);
b1dee947 2123 });
ce66b469
NL
2124
2125 it('should handle the DesktopName pseudo-encoding', function () {
2126 let data = [];
8d6f686b
NL
2127 push32(data, 13);
2128 pushString(data, "som€ nam€");
ce66b469
NL
2129
2130 const spy = sinon.spy();
2131 client.addEventListener("desktopname", spy);
2132
00674385 2133 sendFbuMsg([{ x: 0, y: 0, width: 0, height: 0, encoding: -307 }], [data], client);
ce66b469 2134
e7dec527 2135 expect(client._fbName).to.equal('som€ nam€');
ce66b469 2136 expect(spy).to.have.been.calledOnce;
8d6f686b 2137 expect(spy.args[0][0].detail.name).to.equal('som€ nam€');
ce66b469 2138 });
b1dee947
SR
2139 });
2140 });
2141
b1dee947 2142 describe('XVP Message Handling', function () {
b1dee947 2143 it('should set the XVP version and fire the callback with the version on XVP_INIT', function () {
2b5f94fa 2144 const spy = sinon.spy();
e89eef94 2145 client.addEventListener("capabilities", spy);
95632e41 2146 client._sock._websocket._receiveData(new Uint8Array([250, 0, 10, 1]));
e7dec527 2147 expect(client._rfbXvpVer).to.equal(10);
e89eef94
PO
2148 expect(spy).to.have.been.calledOnce;
2149 expect(spy.args[0][0].detail.capabilities.power).to.be.true;
747b4623 2150 expect(client.capabilities.power).to.be.true;
b1dee947
SR
2151 });
2152
2153 it('should fail on unknown XVP message types', function () {
3bb12056 2154 sinon.spy(client, "_fail");
95632e41 2155 client._sock._websocket._receiveData(new Uint8Array([250, 0, 10, 237]));
3bb12056 2156 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
2157 });
2158 });
2159
f73fdc3e
NL
2160 describe('Normal Clipboard Handling Receive', function () {
2161 it('should fire the clipboard callback with the retrieved text on ServerCutText', function () {
e7dec527 2162 const expectedStr = 'cheese!';
f73fdc3e 2163 const data = [3, 0, 0, 0];
e7dec527
SM
2164 push32(data, expectedStr.length);
2165 for (let i = 0; i < expectedStr.length; i++) { data.push(expectedStr.charCodeAt(i)); }
f73fdc3e
NL
2166 const spy = sinon.spy();
2167 client.addEventListener("clipboard", spy);
2168
95632e41 2169 client._sock._websocket._receiveData(new Uint8Array(data));
f73fdc3e 2170 expect(spy).to.have.been.calledOnce;
e7dec527 2171 expect(spy.args[0][0].detail.text).to.equal(expectedStr);
f73fdc3e
NL
2172 });
2173 });
2174
2175 describe('Extended clipboard Handling', function () {
2176
2177 describe('Extended clipboard initialization', function () {
2178 beforeEach(function () {
2179 sinon.spy(RFB.messages, 'extendedClipboardCaps');
2180 });
2181
2182 afterEach(function () {
2183 RFB.messages.extendedClipboardCaps.restore();
2184 });
2185
2186 it('should update capabilities when receiving a Caps message', function () {
2187 let data = [3, 0, 0, 0];
2188 const flags = [0x1F, 0x00, 0x00, 0x03];
2189 let fileSizes = [0x00, 0x00, 0x00, 0x1E,
2190 0x00, 0x00, 0x00, 0x3C];
2191
2192 push32(data, toUnsigned32bit(-12));
2193 data = data.concat(flags);
2194 data = data.concat(fileSizes);
95632e41 2195 client._sock._websocket._receiveData(new Uint8Array(data));
f73fdc3e
NL
2196
2197 // Check that we give an response caps when we receive one
2198 expect(RFB.messages.extendedClipboardCaps).to.have.been.calledOnce;
2199
2200 // FIXME: Can we avoid checking internal variables?
2201 expect(client._clipboardServerCapabilitiesFormats[0]).to.not.equal(true);
2202 expect(client._clipboardServerCapabilitiesFormats[1]).to.equal(true);
2203 expect(client._clipboardServerCapabilitiesFormats[2]).to.equal(true);
2204 expect(client._clipboardServerCapabilitiesActions[(1 << 24)]).to.equal(true);
2205 });
2206
2207
2208 });
2209
2210 describe('Extended Clipboard Handling Receive', function () {
2211
2212 beforeEach(function () {
2213 // Send our capabilities
2214 let data = [3, 0, 0, 0];
2215 const flags = [0x1F, 0x00, 0x00, 0x01];
2216 let fileSizes = [0x00, 0x00, 0x00, 0x1E];
2217
2218 push32(data, toUnsigned32bit(-8));
2219 data = data.concat(flags);
2220 data = data.concat(fileSizes);
95632e41 2221 client._sock._websocket._receiveData(new Uint8Array(data));
f73fdc3e
NL
2222 });
2223
2224 describe('Handle Provide', function () {
2225 it('should update clipboard with correct Unicode data from a Provide message', function () {
2226 let expectedData = "Aå漢字!";
2227 let data = [3, 0, 0, 0];
2228 const flags = [0x10, 0x00, 0x00, 0x01];
2229
2230 /* The size 10 (utf8 encoded string size) and the
2231 string "Aå漢字!" utf8 encoded and deflated. */
2232 let deflatedData = [120, 94, 99, 96, 96, 224, 114, 60,
2233 188, 244, 217, 158, 69, 79, 215,
2234 78, 87, 4, 0, 35, 207, 6, 66];
2235
2236 // How much data we are sending.
2237 push32(data, toUnsigned32bit(-(4 + deflatedData.length)));
2238
2239 data = data.concat(flags);
2240 data = data.concat(deflatedData);
2241
2242 const spy = sinon.spy();
2243 client.addEventListener("clipboard", spy);
2244
95632e41 2245 client._sock._websocket._receiveData(new Uint8Array(data));
f73fdc3e
NL
2246 expect(spy).to.have.been.calledOnce;
2247 expect(spy.args[0][0].detail.text).to.equal(expectedData);
2248 client.removeEventListener("clipboard", spy);
2249 });
2250
2251 it('should update clipboard with correct escape characters from a Provide message ', function () {
2252 let expectedData = "Oh\nmy!";
2253 let data = [3, 0, 0, 0];
2254 const flags = [0x10, 0x00, 0x00, 0x01];
2255
2256 let text = encodeUTF8("Oh\r\nmy!\0");
2257
2258 let deflatedText = deflateWithSize(text);
2259
2260 // How much data we are sending.
2261 push32(data, toUnsigned32bit(-(4 + deflatedText.length)));
ceb8ef4e
AT
2262
2263 data = data.concat(flags);
2264
2265 let sendData = new Uint8Array(data.length + deflatedText.length);
2266 sendData.set(data);
2267 sendData.set(deflatedText, data.length);
2268
2269 const spy = sinon.spy();
2270 client.addEventListener("clipboard", spy);
2271
95632e41 2272 client._sock._websocket._receiveData(sendData);
ceb8ef4e
AT
2273 expect(spy).to.have.been.calledOnce;
2274 expect(spy.args[0][0].detail.text).to.equal(expectedData);
2275 client.removeEventListener("clipboard", spy);
2276 });
2277
2278 it('should be able to handle large Provide messages', function () {
5b5b7474 2279 let expectedData = "hello".repeat(100000);
ceb8ef4e
AT
2280 let data = [3, 0, 0, 0];
2281 const flags = [0x10, 0x00, 0x00, 0x01];
2282
2283 let text = encodeUTF8(expectedData + "\0");
2284
2285 let deflatedText = deflateWithSize(text);
2286
2287 // How much data we are sending.
2288 push32(data, toUnsigned32bit(-(4 + deflatedText.length)));
f73fdc3e
NL
2289
2290 data = data.concat(flags);
2291
2292 let sendData = new Uint8Array(data.length + deflatedText.length);
2293 sendData.set(data);
2294 sendData.set(deflatedText, data.length);
2295
2296 const spy = sinon.spy();
2297 client.addEventListener("clipboard", spy);
2298
95632e41 2299 client._sock._websocket._receiveData(sendData);
f73fdc3e
NL
2300 expect(spy).to.have.been.calledOnce;
2301 expect(spy.args[0][0].detail.text).to.equal(expectedData);
2302 client.removeEventListener("clipboard", spy);
2303 });
2304
2305 });
2306
2307 describe('Handle Notify', function () {
2308 beforeEach(function () {
2309 sinon.spy(RFB.messages, 'extendedClipboardRequest');
2310 });
2311
2312 afterEach(function () {
2313 RFB.messages.extendedClipboardRequest.restore();
2314 });
2315
2316 it('should make a request with supported formats when receiving a notify message', function () {
2317 let data = [3, 0, 0, 0];
2318 const flags = [0x08, 0x00, 0x00, 0x07];
2319 push32(data, toUnsigned32bit(-4));
2320 data = data.concat(flags);
2321 let expectedData = [0x01];
2322
95632e41 2323 client._sock._websocket._receiveData(new Uint8Array(data));
f73fdc3e
NL
2324
2325 expect(RFB.messages.extendedClipboardRequest).to.have.been.calledOnce;
2326 expect(RFB.messages.extendedClipboardRequest).to.have.been.calledWith(client._sock, expectedData);
2327 });
2328 });
2329
2330 describe('Handle Peek', function () {
2331 beforeEach(function () {
2332 sinon.spy(RFB.messages, 'extendedClipboardNotify');
2333 });
2334
2335 afterEach(function () {
2336 RFB.messages.extendedClipboardNotify.restore();
2337 });
2338
2339 it('should send an empty Notify when receiving a Peek and no excisting clipboard data', function () {
2340 let data = [3, 0, 0, 0];
2341 const flags = [0x04, 0x00, 0x00, 0x00];
2342 push32(data, toUnsigned32bit(-4));
2343 data = data.concat(flags);
2344 let expectedData = [];
2345
95632e41 2346 client._sock._websocket._receiveData(new Uint8Array(data));
f73fdc3e
NL
2347
2348 expect(RFB.messages.extendedClipboardNotify).to.have.been.calledOnce;
2349 expect(RFB.messages.extendedClipboardNotify).to.have.been.calledWith(client._sock, expectedData);
2350 });
2351
2352 it('should send a Notify message with supported formats when receiving a Peek', function () {
2353 let data = [3, 0, 0, 0];
2354 const flags = [0x04, 0x00, 0x00, 0x00];
2355 push32(data, toUnsigned32bit(-4));
2356 data = data.concat(flags);
2357 let expectedData = [0x01];
2358
2359 // Needed to have clipboard data to read.
2360 // This will trigger a call to Notify, reset history
2361 client.clipboardPasteFrom("HejHej");
2362 RFB.messages.extendedClipboardNotify.resetHistory();
2363
95632e41 2364 client._sock._websocket._receiveData(new Uint8Array(data));
f73fdc3e
NL
2365
2366 expect(RFB.messages.extendedClipboardNotify).to.have.been.calledOnce;
2367 expect(RFB.messages.extendedClipboardNotify).to.have.been.calledWith(client._sock, expectedData);
2368 });
2369 });
2370
2371 describe('Handle Request', function () {
2372 beforeEach(function () {
2373 sinon.spy(RFB.messages, 'extendedClipboardProvide');
2374 });
2375
2376 afterEach(function () {
2377 RFB.messages.extendedClipboardProvide.restore();
2378 });
2379
2380 it('should send a Provide message with supported formats when receiving a Request', function () {
2381 let data = [3, 0, 0, 0];
2382 const flags = [0x02, 0x00, 0x00, 0x01];
2383 push32(data, toUnsigned32bit(-4));
2384 data = data.concat(flags);
2385 let expectedData = [0x01];
2386
2387 client.clipboardPasteFrom("HejHej");
2388 expect(RFB.messages.extendedClipboardProvide).to.not.have.been.called;
2389
95632e41 2390 client._sock._websocket._receiveData(new Uint8Array(data));
f73fdc3e
NL
2391
2392 expect(RFB.messages.extendedClipboardProvide).to.have.been.calledOnce;
2393 expect(RFB.messages.extendedClipboardProvide).to.have.been.calledWith(client._sock, expectedData, ["HejHej"]);
2394 });
2395 });
2396 });
b1dee947 2397
b1dee947
SR
2398 });
2399
2400 it('should fire the bell callback on Bell', function () {
2b5f94fa 2401 const spy = sinon.spy();
e89eef94 2402 client.addEventListener("bell", spy);
95632e41 2403 client._sock._websocket._receiveData(new Uint8Array([2]));
e89eef94 2404 expect(spy).to.have.been.calledOnce;
b1dee947
SR
2405 });
2406
3df13262 2407 it('should respond correctly to ServerFence', function () {
e7dec527
SM
2408 const expectedMsg = {_sQ: new Uint8Array(16), _sQlen: 0, flush: () => {}};
2409 const incomingMsg = {_sQ: new Uint8Array(16), _sQlen: 0, flush: () => {}};
3df13262 2410
2b5f94fa 2411 const payload = "foo\x00ab9";
3df13262 2412
2413 // ClientFence and ServerFence are identical in structure
e7dec527
SM
2414 RFB.messages.clientFence(expectedMsg, (1<<0) | (1<<1), payload);
2415 RFB.messages.clientFence(incomingMsg, 0xffffffff, payload);
3df13262 2416
95632e41 2417 client._sock._websocket._receiveData(incomingMsg._sQ);
3df13262 2418
e7dec527 2419 expect(client._sock).to.have.sent(expectedMsg._sQ);
3df13262 2420
e7dec527
SM
2421 expectedMsg._sQlen = 0;
2422 incomingMsg._sQlen = 0;
3df13262 2423
e7dec527
SM
2424 RFB.messages.clientFence(expectedMsg, (1<<0), payload);
2425 RFB.messages.clientFence(incomingMsg, (1<<0) | (1<<31), payload);
3df13262 2426
95632e41 2427 client._sock._websocket._receiveData(incomingMsg._sQ);
3df13262 2428
e7dec527 2429 expect(client._sock).to.have.sent(expectedMsg._sQ);
3df13262 2430 });
2431
76a86ff5 2432 it('should enable continuous updates on first EndOfContinousUpdates', function () {
e7dec527 2433 const expectedMsg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: () => {}};
76a86ff5 2434
e7dec527 2435 RFB.messages.enableContinuousUpdates(expectedMsg, true, 0, 0, 640, 20);
76a86ff5 2436
2437 expect(client._enabledContinuousUpdates).to.be.false;
2438
95632e41 2439 client._sock._websocket._receiveData(new Uint8Array([150]));
76a86ff5 2440
2441 expect(client._enabledContinuousUpdates).to.be.true;
e7dec527 2442 expect(client._sock).to.have.sent(expectedMsg._sQ);
76a86ff5 2443 });
2444
2445 it('should disable continuous updates on subsequent EndOfContinousUpdates', function () {
2446 client._enabledContinuousUpdates = true;
2447 client._supportsContinuousUpdates = true;
2448
95632e41 2449 client._sock._websocket._receiveData(new Uint8Array([150]));
76a86ff5 2450
2451 expect(client._enabledContinuousUpdates).to.be.false;
2452 });
2453
2454 it('should update continuous updates on resize', function () {
e7dec527
SM
2455 const expectedMsg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: () => {}};
2456 RFB.messages.enableContinuousUpdates(expectedMsg, true, 0, 0, 90, 700);
76a86ff5 2457
91d5c625 2458 client._resize(450, 160);
76a86ff5 2459
95632e41 2460 expect(client._sock._websocket._getSentData()).to.have.length(0);
76a86ff5 2461
2462 client._enabledContinuousUpdates = true;
2463
91d5c625 2464 client._resize(90, 700);
76a86ff5 2465
e7dec527 2466 expect(client._sock).to.have.sent(expectedMsg._sQ);
76a86ff5 2467 });
2468
b1dee947 2469 it('should fail on an unknown message type', function () {
3bb12056 2470 sinon.spy(client, "_fail");
95632e41 2471 client._sock._websocket._receiveData(new Uint8Array([87]));
3bb12056 2472 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
2473 });
2474 });
2475
2476 describe('Asynchronous Events', function () {
2b5f94fa 2477 let client;
f84bc57b
PO
2478 let pointerEvent;
2479 let keyEvent;
2480 let qemuKeyEvent;
2481
057b8fec 2482 beforeEach(function () {
00674385 2483 client = makeRFB();
f84bc57b
PO
2484 client._display.resize(100, 100);
2485
50cde2fa
PO
2486 // We need to disable this as focusing the canvas will
2487 // cause the browser to scoll to it, messing up our
2488 // client coordinate calculations
2489 client.focusOnClick = false;
2490
f84bc57b
PO
2491 pointerEvent = sinon.spy(RFB.messages, 'pointerEvent');
2492 keyEvent = sinon.spy(RFB.messages, 'keyEvent');
2493 qemuKeyEvent = sinon.spy(RFB.messages, 'QEMUExtendedKeyEvent');
057b8fec 2494 });
b1dee947 2495
f84bc57b
PO
2496 afterEach(function () {
2497 pointerEvent.restore();
2498 keyEvent.restore();
2499 qemuKeyEvent.restore();
2500 });
150596be 2501
f84bc57b
PO
2502 function elementToClient(x, y) {
2503 let res = { x: 0, y: 0 };
2504
2505 let bounds = client._canvas.getBoundingClientRect();
2506
2507 /*
2508 * If the canvas is on a fractional position we will calculate
2509 * a fractional mouse position. But that gets truncated when we
2510 * send the event, AND the same thing happens in RFB when it
2511 * generates the PointerEvent message. To compensate for that
2512 * fact we round the value upwards here.
2513 */
2514 res.x = Math.ceil(bounds.left + x);
2515 res.y = Math.ceil(bounds.top + y);
2516
2517 return res;
2518 }
2519
2520 describe('Mouse Events', function () {
50cde2fa
PO
2521 function sendMouseMoveEvent(x, y) {
2522 let pos = elementToClient(x, y);
2523 let ev;
2524
6a4c4119
PO
2525 ev = new MouseEvent('mousemove',
2526 { 'screenX': pos.x + window.screenX,
2527 'screenY': pos.y + window.screenY,
2528 'clientX': pos.x,
2529 'clientY': pos.y });
50cde2fa
PO
2530 client._canvas.dispatchEvent(ev);
2531 }
2532
2533 function sendMouseButtonEvent(x, y, down, button) {
2534 let pos = elementToClient(x, y);
2535 let ev;
2536
6a4c4119
PO
2537 ev = new MouseEvent(down ? 'mousedown' : 'mouseup',
2538 { 'screenX': pos.x + window.screenX,
2539 'screenY': pos.y + window.screenY,
2540 'clientX': pos.x,
2541 'clientY': pos.y,
2542 'button': button,
2543 'buttons': 1 << button });
50cde2fa
PO
2544 client._canvas.dispatchEvent(ev);
2545 }
2546
b1dee947 2547 it('should not send button messages in view-only mode', function () {
747b4623 2548 client._viewOnly = true;
50cde2fa
PO
2549 sendMouseButtonEvent(10, 10, true, 0);
2550 clock.tick(50);
2551 expect(pointerEvent).to.not.have.been.called;
b1dee947
SR
2552 });
2553
2554 it('should not send movement messages in view-only mode', function () {
747b4623 2555 client._viewOnly = true;
50cde2fa
PO
2556 sendMouseMoveEvent(10, 10);
2557 clock.tick(50);
2558 expect(pointerEvent).to.not.have.been.called;
b1dee947
SR
2559 });
2560
50cde2fa
PO
2561 it('should handle left mouse button', function () {
2562 sendMouseButtonEvent(10, 10, true, 0);
2563
2564 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2565 10, 10, 0x1);
2566 pointerEvent.resetHistory();
2567
2568 sendMouseButtonEvent(10, 10, false, 0);
b1dee947 2569
50cde2fa
PO
2570 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2571 10, 10, 0x0);
d02a99f0
SR
2572 });
2573
50cde2fa
PO
2574 it('should handle middle mouse button', function () {
2575 sendMouseButtonEvent(10, 10, true, 1);
2576
2577 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2578 10, 10, 0x2);
2579 pointerEvent.resetHistory();
2580
2581 sendMouseButtonEvent(10, 10, false, 1);
2582
2583 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2584 10, 10, 0x0);
d02a99f0
SR
2585 });
2586
50cde2fa
PO
2587 it('should handle right mouse button', function () {
2588 sendMouseButtonEvent(10, 10, true, 2);
2589
2590 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2591 10, 10, 0x4);
2592 pointerEvent.resetHistory();
2593
2594 sendMouseButtonEvent(10, 10, false, 2);
2595
2596 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2597 10, 10, 0x0);
b1dee947
SR
2598 });
2599
50cde2fa
PO
2600 it('should handle multiple mouse buttons', function () {
2601 sendMouseButtonEvent(10, 10, true, 0);
2602 sendMouseButtonEvent(10, 10, true, 2);
2603
2604 expect(pointerEvent).to.have.been.calledTwice;
2605 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2606 10, 10, 0x1);
2607 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2608 10, 10, 0x5);
2609
2610 pointerEvent.resetHistory();
2611
2612 sendMouseButtonEvent(10, 10, false, 0);
2613 sendMouseButtonEvent(10, 10, false, 2);
2614
2615 expect(pointerEvent).to.have.been.calledTwice;
2616 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2617 10, 10, 0x4);
2618 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2619 10, 10, 0x0);
150596be
SM
2620 });
2621
50cde2fa
PO
2622 it('should handle mouse movement', function () {
2623 sendMouseMoveEvent(50, 70);
2624 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2625 50, 70, 0x0);
150596be
SM
2626 });
2627
50cde2fa
PO
2628 it('should handle click and drag', function () {
2629 sendMouseButtonEvent(10, 10, true, 0);
2630 sendMouseMoveEvent(50, 70);
2631
2632 expect(pointerEvent).to.have.been.calledTwice;
2633 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2634 10, 10, 0x1);
2635 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2636 50, 70, 0x1);
2637
2638 pointerEvent.resetHistory();
2639
2640 sendMouseButtonEvent(50, 70, false, 0);
2641
2642 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2643 50, 70, 0x0);
150596be
SM
2644 });
2645
50cde2fa
PO
2646 describe('Event Aggregation', function () {
2647 it('should send a single pointer event on mouse movement', function () {
2648 sendMouseMoveEvent(50, 70);
2649 clock.tick(100);
2650 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2651 50, 70, 0x0);
2652 });
150596be 2653
50cde2fa
PO
2654 it('should delay one move if two events are too close', function () {
2655 sendMouseMoveEvent(18, 30);
2656 sendMouseMoveEvent(20, 50);
150596be 2657
50cde2fa
PO
2658 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2659 18, 30, 0x0);
2660 pointerEvent.resetHistory();
150596be 2661
50cde2fa 2662 clock.tick(100);
150596be 2663
50cde2fa
PO
2664 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2665 20, 50, 0x0);
2666 });
2667
2668 it('should only send first and last move of many close events', function () {
2669 sendMouseMoveEvent(18, 30);
2670 sendMouseMoveEvent(20, 50);
2671 sendMouseMoveEvent(21, 55);
2672
2673 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2674 18, 30, 0x0);
2675 pointerEvent.resetHistory();
2676
2677 clock.tick(100);
2678
2679 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2680 21, 55, 0x0);
2681 });
2682
2683 // We selected the 17ms since that is ~60 FPS
2684 it('should send move events every 17 ms', function () {
2685 sendMouseMoveEvent(1, 10); // instant send
2686 clock.tick(10);
2687
2688 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2689 1, 10, 0x0);
2690 pointerEvent.resetHistory();
2691
2692 sendMouseMoveEvent(2, 20); // delayed
2693 clock.tick(10); // timeout send
2694
2695 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2696 2, 20, 0x0);
2697 pointerEvent.resetHistory();
2698
2699 sendMouseMoveEvent(3, 30); // delayed
2700 clock.tick(10);
2701 sendMouseMoveEvent(4, 40); // delayed
2702 clock.tick(10); // timeout send
2703
2704 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2705 4, 40, 0x0);
2706 pointerEvent.resetHistory();
2707
2708 sendMouseMoveEvent(5, 50); // delayed
2709
2710 expect(pointerEvent).to.not.have.been.called;
2711 });
2712
2713 it('should send waiting move events before a button press', function () {
2714 sendMouseMoveEvent(13, 9);
2715
2716 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2717 13, 9, 0x0);
2718 pointerEvent.resetHistory();
2719
2720 sendMouseMoveEvent(20, 70);
2721
2722 expect(pointerEvent).to.not.have.been.called;
2723
2724 sendMouseButtonEvent(20, 70, true, 0);
2725
2726 expect(pointerEvent).to.have.been.calledTwice;
2727 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2728 20, 70, 0x0);
2729 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2730 20, 70, 0x1);
2731 });
2732
2733 it('should send move events with enough time apart normally', function () {
2734 sendMouseMoveEvent(58, 60);
2735
2736 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2737 58, 60, 0x0);
2738 pointerEvent.resetHistory();
2739
2740 clock.tick(20);
2741
2742 sendMouseMoveEvent(25, 60);
2743
2744 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2745 25, 60, 0x0);
2746 pointerEvent.resetHistory();
2747 });
2748
2749 it('should not send waiting move events if disconnected', function () {
2750 sendMouseMoveEvent(88, 99);
2751
2752 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
2753 88, 99, 0x0);
2754 pointerEvent.resetHistory();
2755
2756 sendMouseMoveEvent(66, 77);
2757 client.disconnect();
2758 clock.tick(20);
2759
2760 expect(pointerEvent).to.not.have.been.called;
2761 });
150596be
SM
2762 });
2763
50cde2fa
PO
2764 it.skip('should block click events', function () {
2765 /* FIXME */
2766 });
2767
2768 it.skip('should block contextmenu events', function () {
2769 /* FIXME */
b1dee947 2770 });
b1dee947
SR
2771 });
2772
f84bc57b
PO
2773 describe('Wheel Events', function () {
2774 function sendWheelEvent(x, y, dx, dy, mode=0) {
2775 let pos = elementToClient(x, y);
2776 let ev;
2777
6a4c4119
PO
2778 ev = new WheelEvent('wheel',
2779 { 'screenX': pos.x + window.screenX,
2780 'screenY': pos.y + window.screenY,
2781 'clientX': pos.x,
2782 'clientY': pos.y,
2783 'deltaX': dx,
2784 'deltaY': dy,
2785 'deltaMode': mode });
f84bc57b
PO
2786 client._canvas.dispatchEvent(ev);
2787 }
2788
2789 it('should handle wheel up event', function () {
2790 sendWheelEvent(10, 10, 0, -50);
2791
2792 expect(pointerEvent).to.have.been.calledTwice;
2793 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2794 10, 10, 1<<3);
2795 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2796 10, 10, 0);
2797 });
2798
2799 it('should handle wheel down event', function () {
2800 sendWheelEvent(10, 10, 0, 50);
2801
2802 expect(pointerEvent).to.have.been.calledTwice;
2803 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2804 10, 10, 1<<4);
2805 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2806 10, 10, 0);
2807 });
2808
2809 it('should handle wheel left event', function () {
2810 sendWheelEvent(10, 10, -50, 0);
2811
2812 expect(pointerEvent).to.have.been.calledTwice;
2813 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2814 10, 10, 1<<5);
2815 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2816 10, 10, 0);
2817 });
2818
2819 it('should handle wheel right event', function () {
2820 sendWheelEvent(10, 10, 50, 0);
2821
2822 expect(pointerEvent).to.have.been.calledTwice;
2823 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2824 10, 10, 1<<6);
2825 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2826 10, 10, 0);
2827 });
2828
2829 it('should ignore wheel when in view only', function () {
2830 client._viewOnly = true;
2831
2832 sendWheelEvent(10, 10, 50, 0);
2833
2834 expect(pointerEvent).to.not.have.been.called;
2835 });
2836
2837 it('should accumulate wheel events if small enough', function () {
88589a44
PO
2838 sendWheelEvent(10, 10, 0, 20);
2839 sendWheelEvent(10, 10, 0, 20);
f84bc57b
PO
2840
2841 expect(pointerEvent).to.not.have.been.called;
2842
88589a44 2843 sendWheelEvent(10, 10, 0, 20);
f84bc57b
PO
2844
2845 expect(pointerEvent).to.have.been.calledTwice;
2846 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2847 10, 10, 1<<4);
2848 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2849 10, 10, 0);
2850 });
2851
2852 it('should not accumulate large wheel events', function () {
2853 sendWheelEvent(10, 10, 0, 400);
2854
2855 expect(pointerEvent).to.have.been.calledTwice;
2856 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2857 10, 10, 1<<4);
2858 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2859 10, 10, 0);
2860 });
2861
2862 it('should handle line based wheel event', function () {
88589a44 2863 sendWheelEvent(10, 10, 0, 3, 1);
f84bc57b
PO
2864
2865 expect(pointerEvent).to.have.been.calledTwice;
2866 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2867 10, 10, 1<<4);
2868 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2869 10, 10, 0);
2870 });
2871
2872 it('should handle page based wheel event', function () {
88589a44 2873 sendWheelEvent(10, 10, 0, 3, 2);
f84bc57b
PO
2874
2875 expect(pointerEvent).to.have.been.calledTwice;
2876 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2877 10, 10, 1<<4);
2878 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2879 10, 10, 0);
2880 });
2881 });
2882
2883 describe('Keyboard Events', function () {
b1dee947 2884 it('should send a key message on a key press', function () {
747b4623 2885 client._handleKeyEvent(0x41, 'KeyA', true);
e7dec527
SM
2886 const keyMsg = {_sQ: new Uint8Array(8), _sQlen: 0, flush: () => {}};
2887 RFB.messages.keyEvent(keyMsg, 0x41, 1);
2888 expect(client._sock).to.have.sent(keyMsg._sQ);
b1dee947
SR
2889 });
2890
2891 it('should not send messages in view-only mode', function () {
747b4623 2892 client._viewOnly = true;
057b8fec 2893 sinon.spy(client._sock, 'flush');
747b4623 2894 client._handleKeyEvent('a', 'KeyA', true);
9ff86fb7 2895 expect(client._sock.flush).to.not.have.been.called;
b1dee947
SR
2896 });
2897 });
2898
8be924c9 2899 describe('Gesture event handlers', function () {
8be924c9
PO
2900 function gestureStart(gestureType, x, y,
2901 magnitudeX = 0, magnitudeY = 0) {
2902 let pos = elementToClient(x, y);
2903 let detail = {type: gestureType, clientX: pos.x, clientY: pos.y};
2904
2905 detail.magnitudeX = magnitudeX;
2906 detail.magnitudeY = magnitudeY;
2907
2908 let ev = new CustomEvent('gesturestart', { detail: detail });
2909 client._canvas.dispatchEvent(ev);
2910 }
2911
2912 function gestureMove(gestureType, x, y,
2913 magnitudeX = 0, magnitudeY = 0) {
2914 let pos = elementToClient(x, y);
2915 let detail = {type: gestureType, clientX: pos.x, clientY: pos.y};
2916
2917 detail.magnitudeX = magnitudeX;
2918 detail.magnitudeY = magnitudeY;
2919
2920 let ev = new CustomEvent('gesturemove', { detail: detail });
2921 client._canvas.dispatchEvent(ev);
2922 }
2923
2924 function gestureEnd(gestureType, x, y) {
2925 let pos = elementToClient(x, y);
2926 let detail = {type: gestureType, clientX: pos.x, clientY: pos.y};
2927 let ev = new CustomEvent('gestureend', { detail: detail });
2928 client._canvas.dispatchEvent(ev);
2929 }
2930
2931 describe('Gesture onetap', function () {
2932 it('should handle onetap events', function () {
2933 let bmask = 0x1;
2934
2935 gestureStart('onetap', 20, 40);
2936 gestureEnd('onetap', 20, 40);
2937
2938 expect(pointerEvent).to.have.been.calledThrice;
2939 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2940 20, 40, 0x0);
2941 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2942 20, 40, bmask);
2943 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
2944 20, 40, 0x0);
2945 });
2946
2947 it('should keep same position for multiple onetap events', function () {
2948 let bmask = 0x1;
2949
2950 gestureStart('onetap', 20, 40);
2951 gestureEnd('onetap', 20, 40);
2952
2953 expect(pointerEvent).to.have.been.calledThrice;
2954 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2955 20, 40, 0x0);
2956 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2957 20, 40, bmask);
2958 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
2959 20, 40, 0x0);
2960
2961 pointerEvent.resetHistory();
2962
2963 gestureStart('onetap', 20, 50);
2964 gestureEnd('onetap', 20, 50);
2965
2966 expect(pointerEvent).to.have.been.calledThrice;
2967 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2968 20, 40, 0x0);
2969 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2970 20, 40, bmask);
2971 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
2972 20, 40, 0x0);
2973
2974 pointerEvent.resetHistory();
2975
2976 gestureStart('onetap', 30, 50);
2977 gestureEnd('onetap', 30, 50);
2978
2979 expect(pointerEvent).to.have.been.calledThrice;
2980 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2981 20, 40, 0x0);
2982 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2983 20, 40, bmask);
2984 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
2985 20, 40, 0x0);
2986 });
2987
2988 it('should not keep same position for onetap events when too far apart', function () {
2989 let bmask = 0x1;
2990
2991 gestureStart('onetap', 20, 40);
2992 gestureEnd('onetap', 20, 40);
2993
2994 expect(pointerEvent).to.have.been.calledThrice;
2995 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
2996 20, 40, 0x0);
2997 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
2998 20, 40, bmask);
2999 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3000 20, 40, 0x0);
3001
3002 pointerEvent.resetHistory();
3003
3004 gestureStart('onetap', 80, 95);
3005 gestureEnd('onetap', 80, 95);
3006
3007 expect(pointerEvent).to.have.been.calledThrice;
3008 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3009 80, 95, 0x0);
3010 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3011 80, 95, bmask);
3012 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3013 80, 95, 0x0);
3014 });
3015
3016 it('should not keep same position for onetap events when enough time inbetween', function () {
3017 let bmask = 0x1;
3018
3019 gestureStart('onetap', 10, 20);
3020 gestureEnd('onetap', 10, 20);
3021
3022 expect(pointerEvent).to.have.been.calledThrice;
3023 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3024 10, 20, 0x0);
3025 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3026 10, 20, bmask);
3027 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3028 10, 20, 0x0);
3029
3030 pointerEvent.resetHistory();
3031 this.clock.tick(1500);
3032
3033 gestureStart('onetap', 15, 20);
3034 gestureEnd('onetap', 15, 20);
3035
3036 expect(pointerEvent).to.have.been.calledThrice;
3037 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3038 15, 20, 0x0);
3039 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3040 15, 20, bmask);
3041 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3042 15, 20, 0x0);
3043
3044 pointerEvent.resetHistory();
3045 });
3046 });
3047
3048 describe('Gesture twotap', function () {
3049 it('should handle gesture twotap events', function () {
3050 let bmask = 0x4;
3051
3052 gestureStart("twotap", 20, 40);
3053
3054 expect(pointerEvent).to.have.been.calledThrice;
3055 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3056 20, 40, 0x0);
3057 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3058 20, 40, bmask);
3059 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3060 20, 40, 0x0);
3061 });
3062
3063 it('should keep same position for multiple twotap events', function () {
3064 let bmask = 0x4;
3065
3066 for (let offset = 0;offset < 30;offset += 10) {
3067 pointerEvent.resetHistory();
3068
3069 gestureStart('twotap', 20, 40 + offset);
3070 gestureEnd('twotap', 20, 40 + offset);
3071
3072 expect(pointerEvent).to.have.been.calledThrice;
3073 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3074 20, 40, 0x0);
3075 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3076 20, 40, bmask);
3077 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3078 20, 40, 0x0);
3079 }
3080 });
3081 });
3082
3083 describe('Gesture threetap', function () {
3084 it('should handle gesture start for threetap events', function () {
3085 let bmask = 0x2;
3086
3087 gestureStart("threetap", 20, 40);
3088
3089 expect(pointerEvent).to.have.been.calledThrice;
3090 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3091 20, 40, 0x0);
3092 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3093 20, 40, bmask);
3094 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3095 20, 40, 0x0);
3096 });
3097
3098 it('should keep same position for multiple threetap events', function () {
3099 let bmask = 0x2;
3100
3101 for (let offset = 0;offset < 30;offset += 10) {
3102 pointerEvent.resetHistory();
3103
3104 gestureStart('threetap', 20, 40 + offset);
3105 gestureEnd('threetap', 20, 40 + offset);
3106
3107 expect(pointerEvent).to.have.been.calledThrice;
3108 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3109 20, 40, 0x0);
3110 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3111 20, 40, bmask);
3112 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3113 20, 40, 0x0);
3114 }
3115 });
3116 });
3117
3118 describe('Gesture drag', function () {
3119 it('should handle gesture drag events', function () {
3120 let bmask = 0x1;
3121
3122 gestureStart('drag', 20, 40);
3123
3124 expect(pointerEvent).to.have.been.calledTwice;
3125 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3126 20, 40, 0x0);
3127 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3128 20, 40, bmask);
3129
3130 pointerEvent.resetHistory();
3131
3132 gestureMove('drag', 30, 50);
3133 clock.tick(50);
3134
3135 expect(pointerEvent).to.have.been.calledOnce;
3136 expect(pointerEvent).to.have.been.calledWith(client._sock,
3137 30, 50, bmask);
3138
3139 pointerEvent.resetHistory();
3140
3141 gestureEnd('drag', 30, 50);
3142
3143 expect(pointerEvent).to.have.been.calledTwice;
3144 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3145 30, 50, bmask);
3146 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3147 30, 50, 0x0);
3148 });
3149 });
3150
3151 describe('Gesture long press', function () {
3152 it('should handle long press events', function () {
3153 let bmask = 0x4;
3154
3155 gestureStart('longpress', 20, 40);
3156
3157 expect(pointerEvent).to.have.been.calledTwice;
3158 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3159 20, 40, 0x0);
3160 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3161 20, 40, bmask);
3162 pointerEvent.resetHistory();
3163
3164 gestureMove('longpress', 40, 60);
3165 clock.tick(50);
3166
3167 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
3168 40, 60, bmask);
3169
3170 pointerEvent.resetHistory();
3171
3172 gestureEnd('longpress', 40, 60);
3173
3174 expect(pointerEvent).to.have.been.calledTwice;
3175 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3176 40, 60, bmask);
3177 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3178 40, 60, 0x0);
3179 });
3180 });
3181
3182 describe('Gesture twodrag', function () {
3183 it('should handle gesture twodrag up events', function () {
3184 let bmask = 0x10; // Button mask for scroll down
3185
3186 gestureStart('twodrag', 20, 40, 0, 0);
3187
3188 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
3189 20, 40, 0x0);
3190
3191 pointerEvent.resetHistory();
3192
3193 gestureMove('twodrag', 20, 40, 0, -60);
3194
3195 expect(pointerEvent).to.have.been.calledThrice;
3196 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3197 20, 40, 0x0);
3198 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3199 20, 40, bmask);
3200 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3201 20, 40, 0x0);
3202 });
3203
3204 it('should handle gesture twodrag down events', function () {
3205 let bmask = 0x8; // Button mask for scroll up
3206
3207 gestureStart('twodrag', 20, 40, 0, 0);
3208
3209 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
3210 20, 40, 0x0);
3211
3212 pointerEvent.resetHistory();
3213
3214 gestureMove('twodrag', 20, 40, 0, 60);
3215
3216 expect(pointerEvent).to.have.been.calledThrice;
3217 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3218 20, 40, 0x0);
3219 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3220 20, 40, bmask);
3221 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3222 20, 40, 0x0);
3223 });
3224
3225 it('should handle gesture twodrag right events', function () {
3226 let bmask = 0x20; // Button mask for scroll right
3227
3228 gestureStart('twodrag', 20, 40, 0, 0);
3229
3230 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
3231 20, 40, 0x0);
3232
3233 pointerEvent.resetHistory();
3234
3235 gestureMove('twodrag', 20, 40, 60, 0);
3236
3237 expect(pointerEvent).to.have.been.calledThrice;
3238 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3239 20, 40, 0x0);
3240 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3241 20, 40, bmask);
3242 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3243 20, 40, 0x0);
3244 });
3245
3246 it('should handle gesture twodrag left events', function () {
3247 let bmask = 0x40; // Button mask for scroll left
3248
3249 gestureStart('twodrag', 20, 40, 0, 0);
3250
3251 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
3252 20, 40, 0x0);
3253
3254 pointerEvent.resetHistory();
3255
3256 gestureMove('twodrag', 20, 40, -60, 0);
3257
3258 expect(pointerEvent).to.have.been.calledThrice;
3259 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3260 20, 40, 0x0);
3261 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3262 20, 40, bmask);
3263 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3264 20, 40, 0x0);
3265 });
3266
3267 it('should handle gesture twodrag diag events', function () {
3268 let scrlUp = 0x8; // Button mask for scroll up
3269 let scrlRight = 0x20; // Button mask for scroll right
3270
3271 gestureStart('twodrag', 20, 40, 0, 0);
3272
3273 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
3274 20, 40, 0x0);
3275
3276 pointerEvent.resetHistory();
3277
3278 gestureMove('twodrag', 20, 40, 60, 60);
3279
3280 expect(pointerEvent).to.have.been.callCount(5);
3281 expect(pointerEvent.getCall(0)).to.have.been.calledWith(client._sock,
3282 20, 40, 0x0);
3283 expect(pointerEvent.getCall(1)).to.have.been.calledWith(client._sock,
3284 20, 40, scrlUp);
3285 expect(pointerEvent.getCall(2)).to.have.been.calledWith(client._sock,
3286 20, 40, 0x0);
3287 expect(pointerEvent.getCall(3)).to.have.been.calledWith(client._sock,
3288 20, 40, scrlRight);
3289 expect(pointerEvent.getCall(4)).to.have.been.calledWith(client._sock,
3290 20, 40, 0x0);
3291 });
3292
3293 it('should handle multiple small gesture twodrag events', function () {
3294 let bmask = 0x8; // Button mask for scroll up
3295
3296 gestureStart('twodrag', 20, 40, 0, 0);
3297
3298 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
3299 20, 40, 0x0);
3300
3301 pointerEvent.resetHistory();
3302
3303 gestureMove('twodrag', 20, 40, 0, 10);
3304 clock.tick(50);
3305
3306 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
3307 20, 40, 0x0);
3308
3309 pointerEvent.resetHistory();
3310
3311 gestureMove('twodrag', 20, 40, 0, 20);
3312 clock.tick(50);
3313
3314 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
3315 20, 40, 0x0);
3316
3317 pointerEvent.resetHistory();
3318
3319 gestureMove('twodrag', 20, 40, 0, 60);
3320
3321 expect(pointerEvent).to.have.been.calledThrice;
3322 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3323 20, 40, 0x0);
3324 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3325 20, 40, bmask);
3326 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3327 20, 40, 0x0);
3328 });
3329
3330 it('should handle large gesture twodrag events', function () {
3331 let bmask = 0x8; // Button mask for scroll up
3332
3333 gestureStart('twodrag', 30, 50, 0, 0);
3334
3335 expect(pointerEvent).
3336 to.have.been.calledOnceWith(client._sock, 30, 50, 0x0);
3337
3338 pointerEvent.resetHistory();
3339
3340 gestureMove('twodrag', 30, 50, 0, 200);
3341
3342 expect(pointerEvent).to.have.callCount(7);
3343 expect(pointerEvent.getCall(0)).to.have.been.calledWith(client._sock,
3344 30, 50, 0x0);
3345 expect(pointerEvent.getCall(1)).to.have.been.calledWith(client._sock,
3346 30, 50, bmask);
3347 expect(pointerEvent.getCall(2)).to.have.been.calledWith(client._sock,
3348 30, 50, 0x0);
3349 expect(pointerEvent.getCall(3)).to.have.been.calledWith(client._sock,
3350 30, 50, bmask);
3351 expect(pointerEvent.getCall(4)).to.have.been.calledWith(client._sock,
3352 30, 50, 0x0);
3353 expect(pointerEvent.getCall(5)).to.have.been.calledWith(client._sock,
3354 30, 50, bmask);
3355 expect(pointerEvent.getCall(6)).to.have.been.calledWith(client._sock,
3356 30, 50, 0x0);
3357 });
3358 });
3359
3360 describe('Gesture pinch', function () {
8be924c9
PO
3361 it('should handle gesture pinch in events', function () {
3362 let keysym = KeyTable.XK_Control_L;
3363 let bmask = 0x10; // Button mask for scroll down
3364
3365 gestureStart('pinch', 20, 40, 90, 90);
3366
3367 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
3368 20, 40, 0x0);
3369 expect(keyEvent).to.not.have.been.called;
3370
3371 pointerEvent.resetHistory();
3372
3373 gestureMove('pinch', 20, 40, 30, 30);
3374
3375 expect(pointerEvent).to.have.been.calledThrice;
3376 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3377 20, 40, 0x0);
3378 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3379 20, 40, bmask);
3380 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3381 20, 40, 0x0);
3382
3383 expect(keyEvent).to.have.been.calledTwice;
3384 expect(keyEvent.firstCall).to.have.been.calledWith(client._sock,
3385 keysym, 1);
3386 expect(keyEvent.secondCall).to.have.been.calledWith(client._sock,
3387 keysym, 0);
3388
3389 expect(keyEvent.firstCall).to.have.been.calledBefore(pointerEvent.secondCall);
3390 expect(keyEvent.lastCall).to.have.been.calledAfter(pointerEvent.lastCall);
3391
3392 pointerEvent.resetHistory();
3393 keyEvent.resetHistory();
3394
3395 gestureEnd('pinch', 20, 40);
3396
3397 expect(pointerEvent).to.not.have.been.called;
3398 expect(keyEvent).to.not.have.been.called;
3399 });
3400
3401 it('should handle gesture pinch out events', function () {
3402 let keysym = KeyTable.XK_Control_L;
3403 let bmask = 0x8; // Button mask for scroll up
3404
3405 gestureStart('pinch', 10, 20, 10, 20);
3406
3407 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
3408 10, 20, 0x0);
3409 expect(keyEvent).to.not.have.been.called;
3410
3411 pointerEvent.resetHistory();
3412
3413 gestureMove('pinch', 10, 20, 70, 80);
3414
3415 expect(pointerEvent).to.have.been.calledThrice;
3416 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3417 10, 20, 0x0);
3418 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3419 10, 20, bmask);
3420 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3421 10, 20, 0x0);
3422
3423 expect(keyEvent).to.have.been.calledTwice;
3424 expect(keyEvent.firstCall).to.have.been.calledWith(client._sock,
3425 keysym, 1);
3426 expect(keyEvent.secondCall).to.have.been.calledWith(client._sock,
3427 keysym, 0);
3428
3429 expect(keyEvent.firstCall).to.have.been.calledBefore(pointerEvent.secondCall);
3430 expect(keyEvent.lastCall).to.have.been.calledAfter(pointerEvent.lastCall);
3431
3432 pointerEvent.resetHistory();
3433 keyEvent.resetHistory();
3434
3435 gestureEnd('pinch', 10, 20);
3436
3437 expect(pointerEvent).to.not.have.been.called;
3438 expect(keyEvent).to.not.have.been.called;
3439 });
3440
3441 it('should handle large gesture pinch', function () {
3442 let keysym = KeyTable.XK_Control_L;
3443 let bmask = 0x10; // Button mask for scroll down
3444
3445 gestureStart('pinch', 20, 40, 150, 150);
3446
3447 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
3448 20, 40, 0x0);
3449 expect(keyEvent).to.not.have.been.called;
3450
3451 pointerEvent.resetHistory();
3452
3453 gestureMove('pinch', 20, 40, 30, 30);
3454
3455 expect(pointerEvent).to.have.been.callCount(5);
3456 expect(pointerEvent.getCall(0)).to.have.been.calledWith(client._sock,
3457 20, 40, 0x0);
3458 expect(pointerEvent.getCall(1)).to.have.been.calledWith(client._sock,
3459 20, 40, bmask);
3460 expect(pointerEvent.getCall(2)).to.have.been.calledWith(client._sock,
3461 20, 40, 0x0);
3462 expect(pointerEvent.getCall(3)).to.have.been.calledWith(client._sock,
3463 20, 40, bmask);
3464 expect(pointerEvent.getCall(4)).to.have.been.calledWith(client._sock,
3465 20, 40, 0x0);
3466
3467 expect(keyEvent).to.have.been.calledTwice;
3468 expect(keyEvent.firstCall).to.have.been.calledWith(client._sock,
3469 keysym, 1);
3470 expect(keyEvent.secondCall).to.have.been.calledWith(client._sock,
3471 keysym, 0);
3472
3473 expect(keyEvent.firstCall).to.have.been.calledBefore(pointerEvent.secondCall);
3474 expect(keyEvent.lastCall).to.have.been.calledAfter(pointerEvent.lastCall);
3475
3476 pointerEvent.resetHistory();
3477 keyEvent.resetHistory();
3478
3479 gestureEnd('pinch', 20, 40);
3480
3481 expect(pointerEvent).to.not.have.been.called;
3482 expect(keyEvent).to.not.have.been.called;
3483 });
3484
3485 it('should handle multiple small gesture pinch out events', function () {
3486 let keysym = KeyTable.XK_Control_L;
3487 let bmask = 0x8; // Button mask for scroll down
3488
3489 gestureStart('pinch', 20, 40, 0, 10);
3490
3491 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
3492 20, 40, 0x0);
3493 expect(keyEvent).to.not.have.been.called;
3494
3495 pointerEvent.resetHistory();
3496
3497 gestureMove('pinch', 20, 40, 0, 30);
3498 clock.tick(50);
3499
3500 expect(pointerEvent).to.have.been.calledWith(client._sock,
3501 20, 40, 0x0);
3502
3503 pointerEvent.resetHistory();
3504
3505 gestureMove('pinch', 20, 40, 0, 60);
3506 clock.tick(50);
3507
3508 expect(pointerEvent).to.have.been.calledWith(client._sock,
3509 20, 40, 0x0);
3510
3511 pointerEvent.resetHistory();
3512 keyEvent.resetHistory();
3513
3514 gestureMove('pinch', 20, 40, 0, 90);
3515
3516 expect(pointerEvent).to.have.been.calledThrice;
3517 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3518 20, 40, 0x0);
3519 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3520 20, 40, bmask);
3521 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3522 20, 40, 0x0);
3523
3524 expect(keyEvent).to.have.been.calledTwice;
3525 expect(keyEvent.firstCall).to.have.been.calledWith(client._sock,
3526 keysym, 1);
3527 expect(keyEvent.secondCall).to.have.been.calledWith(client._sock,
3528 keysym, 0);
3529
3530 expect(keyEvent.firstCall).to.have.been.calledBefore(pointerEvent.secondCall);
3531 expect(keyEvent.lastCall).to.have.been.calledAfter(pointerEvent.lastCall);
3532
3533 pointerEvent.resetHistory();
3534 keyEvent.resetHistory();
3535
3536 gestureEnd('pinch', 20, 40);
3537
3538 expect(keyEvent).to.not.have.been.called;
3539 });
3540
3541 it('should send correct key control code', function () {
3542 let keysym = KeyTable.XK_Control_L;
3543 let code = 0x1d;
3544 let bmask = 0x10; // Button mask for scroll down
3545
3546 client._qemuExtKeyEventSupported = true;
3547
3548 gestureStart('pinch', 20, 40, 90, 90);
3549
3550 expect(pointerEvent).to.have.been.calledOnceWith(client._sock,
3551 20, 40, 0x0);
3552 expect(qemuKeyEvent).to.not.have.been.called;
3553
3554 pointerEvent.resetHistory();
3555
3556 gestureMove('pinch', 20, 40, 30, 30);
3557
3558 expect(pointerEvent).to.have.been.calledThrice;
3559 expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,
3560 20, 40, 0x0);
3561 expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,
3562 20, 40, bmask);
3563 expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,
3564 20, 40, 0x0);
3565
3566 expect(qemuKeyEvent).to.have.been.calledTwice;
3567 expect(qemuKeyEvent.firstCall).to.have.been.calledWith(client._sock,
3568 keysym,
3569 true,
3570 code);
3571 expect(qemuKeyEvent.secondCall).to.have.been.calledWith(client._sock,
3572 keysym,
3573 false,
3574 code);
3575
3576 expect(qemuKeyEvent.firstCall).to.have.been.calledBefore(pointerEvent.secondCall);
3577 expect(qemuKeyEvent.lastCall).to.have.been.calledAfter(pointerEvent.lastCall);
3578
3579 pointerEvent.resetHistory();
3580 qemuKeyEvent.resetHistory();
3581
3582 gestureEnd('pinch', 20, 40);
3583
3584 expect(pointerEvent).to.not.have.been.called;
3585 expect(qemuKeyEvent).to.not.have.been.called;
3586 });
3587 });
3588 });
3589
3590 describe('WebSocket Events', function () {
b1dee947 3591 // message events
d80d9d37 3592 it('should do nothing if we receive an empty message and have nothing in the queue', function () {
00674385 3593 client._normalMsg = sinon.spy();
95632e41 3594 client._sock._websocket._receiveData(new Uint8Array([]));
00674385 3595 expect(client._normalMsg).to.not.have.been.called;
b1dee947
SR
3596 });
3597
c2a4d3ef 3598 it('should handle a message in the connected state as a normal message', function () {
00674385 3599 client._normalMsg = sinon.spy();
95632e41 3600 client._sock._websocket._receiveData(new Uint8Array([1, 2, 3]));
00674385 3601 expect(client._normalMsg).to.have.been.called;
b1dee947
SR
3602 });
3603
3604 it('should handle a message in any non-disconnected/failed state like an init message', function () {
e7dec527
SM
3605 client._rfbConnectionState = 'connecting';
3606 client._rfbInitState = 'ProtocolVersion';
00674385 3607 client._initMsg = sinon.spy();
95632e41 3608 client._sock._websocket._receiveData(new Uint8Array([1, 2, 3]));
00674385 3609 expect(client._initMsg).to.have.been.called;
b1dee947
SR
3610 });
3611
9535539b 3612 it('should process all normal messages directly', function () {
2b5f94fa 3613 const spy = sinon.spy();
e89eef94 3614 client.addEventListener("bell", spy);
95632e41 3615 client._sock._websocket._receiveData(new Uint8Array([0x02, 0x02]));
e89eef94 3616 expect(spy).to.have.been.calledTwice;
b1dee947
SR
3617 });
3618
3619 // open events
c2a4d3ef 3620 it('should update the state to ProtocolVersion on open (if the state is "connecting")', function () {
9b84f516 3621 client = new RFB(document.createElement('div'), 'wss://host:8675');
2f4516f2 3622 this.clock.tick();
b1dee947 3623 client._sock._websocket._open();
e7dec527 3624 expect(client._rfbInitState).to.equal('ProtocolVersion');
b1dee947
SR
3625 });
3626
3627 it('should fail if we are not currently ready to connect and we get an "open" event', function () {
3bb12056 3628 sinon.spy(client, "_fail");
e7dec527 3629 client._rfbConnectionState = 'connected';
b1dee947 3630 client._sock._websocket._open();
3bb12056 3631 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
3632 });
3633
3634 // close events
c2a4d3ef 3635 it('should transition to "disconnected" from "disconnecting" on a close event', function () {
2b5f94fa 3636 const real = client._sock._websocket.close;
651c23ec 3637 client._sock._websocket.close = () => {};
bb25d3d6 3638 client.disconnect();
e7dec527 3639 expect(client._rfbConnectionState).to.equal('disconnecting');
bb25d3d6 3640 client._sock._websocket.close = real;
b1dee947 3641 client._sock._websocket.close();
e7dec527 3642 expect(client._rfbConnectionState).to.equal('disconnected');
b1dee947
SR
3643 });
3644
b45905ab 3645 it('should fail if we get a close event while connecting', function () {
3bb12056 3646 sinon.spy(client, "_fail");
e7dec527 3647 client._rfbConnectionState = 'connecting';
b1dee947 3648 client._sock._websocket.close();
3bb12056 3649 expect(client._fail).to.have.been.calledOnce;
b1dee947
SR
3650 });
3651
155d78b3
JS
3652 it('should unregister close event handler', function () {
3653 sinon.spy(client._sock, 'off');
bb25d3d6 3654 client.disconnect();
155d78b3
JS
3655 client._sock._websocket.close();
3656 expect(client._sock.off).to.have.been.calledWith('close');
3657 });
3658
b1dee947
SR
3659 // error events do nothing
3660 });
efd1f8a4
AT
3661 });
3662
3663 describe('Quality level setting', function () {
3664 const defaultQuality = 6;
3665
3666 let client;
3667
3668 beforeEach(function () {
00674385 3669 client = makeRFB();
efd1f8a4
AT
3670 sinon.spy(RFB.messages, "clientEncodings");
3671 });
3672
3673 afterEach(function () {
3674 RFB.messages.clientEncodings.restore();
3675 });
3676
3677 it(`should equal ${defaultQuality} by default`, function () {
3678 expect(client._qualityLevel).to.equal(defaultQuality);
3679 });
3680
3681 it('should ignore non-integers when set', function () {
3682 client.qualityLevel = '1';
3683 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3684
3685 RFB.messages.clientEncodings.resetHistory();
3686
3687 client.qualityLevel = 1.5;
3688 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3689
3690 RFB.messages.clientEncodings.resetHistory();
3691
3692 client.qualityLevel = null;
3693 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3694
3695 RFB.messages.clientEncodings.resetHistory();
3696
3697 client.qualityLevel = undefined;
3698 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3699
3700 RFB.messages.clientEncodings.resetHistory();
3701
3702 client.qualityLevel = {};
3703 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3704 });
3705
3706 it('should ignore integers out of range [0, 9]', function () {
3707 client.qualityLevel = -1;
3708 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3709
3710 RFB.messages.clientEncodings.resetHistory();
3711
3712 client.qualityLevel = 10;
3713 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3714 });
3715
3716 it('should send clientEncodings with new quality value', function () {
3717 let newQuality;
3718
3719 newQuality = 8;
3720 client.qualityLevel = newQuality;
3721 expect(client.qualityLevel).to.equal(newQuality);
3722 expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
3723 expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.pseudoEncodingQualityLevel0 + newQuality);
3724 });
3725
3726 it('should not send clientEncodings if quality is the same', function () {
3727 let newQuality;
3728
3729 newQuality = 2;
3730 client.qualityLevel = newQuality;
3731 expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
3732 expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.pseudoEncodingQualityLevel0 + newQuality);
3733
3734 RFB.messages.clientEncodings.resetHistory();
3735
3736 client.qualityLevel = newQuality;
3737 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3738 });
3739
3740 it('should not send clientEncodings if not in connected state', function () {
3741 let newQuality;
3742
e7dec527 3743 client._rfbConnectionState = '';
efd1f8a4
AT
3744 newQuality = 2;
3745 client.qualityLevel = newQuality;
3746 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3747
3748 RFB.messages.clientEncodings.resetHistory();
3749
e7dec527 3750 client._rfbConnectionState = 'connnecting';
efd1f8a4
AT
3751 newQuality = 6;
3752 client.qualityLevel = newQuality;
3753 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3754
3755 RFB.messages.clientEncodings.resetHistory();
3756
e7dec527 3757 client._rfbConnectionState = 'connected';
efd1f8a4
AT
3758 newQuality = 5;
3759 client.qualityLevel = newQuality;
3760 expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
3761 expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.pseudoEncodingQualityLevel0 + newQuality);
3762 });
479d8cef
SM
3763 });
3764
3765 describe('Compression level setting', function () {
3766 const defaultCompression = 2;
3767
3768 let client;
3769
3770 beforeEach(function () {
00674385 3771 client = makeRFB();
479d8cef
SM
3772 sinon.spy(RFB.messages, "clientEncodings");
3773 });
3774
3775 afterEach(function () {
3776 RFB.messages.clientEncodings.restore();
3777 });
3778
3779 it(`should equal ${defaultCompression} by default`, function () {
3780 expect(client._compressionLevel).to.equal(defaultCompression);
3781 });
3782
3783 it('should ignore non-integers when set', function () {
3784 client.compressionLevel = '1';
3785 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3786
3787 RFB.messages.clientEncodings.resetHistory();
3788
3789 client.compressionLevel = 1.5;
3790 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3791
3792 RFB.messages.clientEncodings.resetHistory();
3793
3794 client.compressionLevel = null;
3795 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3796
3797 RFB.messages.clientEncodings.resetHistory();
3798
3799 client.compressionLevel = undefined;
3800 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3801
3802 RFB.messages.clientEncodings.resetHistory();
3803
3804 client.compressionLevel = {};
3805 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3806 });
3807
3808 it('should ignore integers out of range [0, 9]', function () {
3809 client.compressionLevel = -1;
3810 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3811
3812 RFB.messages.clientEncodings.resetHistory();
3813
3814 client.compressionLevel = 10;
3815 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3816 });
3817
3818 it('should send clientEncodings with new compression value', function () {
3819 let newCompression;
3820
3821 newCompression = 5;
3822 client.compressionLevel = newCompression;
3823 expect(client.compressionLevel).to.equal(newCompression);
3824 expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
3825 expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.pseudoEncodingCompressLevel0 + newCompression);
3826 });
3827
3828 it('should not send clientEncodings if compression is the same', function () {
3829 let newCompression;
3830
3831 newCompression = 9;
3832 client.compressionLevel = newCompression;
3833 expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
3834 expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.pseudoEncodingCompressLevel0 + newCompression);
3835
3836 RFB.messages.clientEncodings.resetHistory();
3837
3838 client.compressionLevel = newCompression;
3839 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3840 });
3841
3842 it('should not send clientEncodings if not in connected state', function () {
3843 let newCompression;
3844
e7dec527 3845 client._rfbConnectionState = '';
479d8cef
SM
3846 newCompression = 7;
3847 client.compressionLevel = newCompression;
3848 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3849
3850 RFB.messages.clientEncodings.resetHistory();
3851
e7dec527 3852 client._rfbConnectionState = 'connnecting';
479d8cef
SM
3853 newCompression = 6;
3854 client.compressionLevel = newCompression;
3855 expect(RFB.messages.clientEncodings).to.not.have.been.called;
3856
3857 RFB.messages.clientEncodings.resetHistory();
3858
e7dec527 3859 client._rfbConnectionState = 'connected';
479d8cef
SM
3860 newCompression = 5;
3861 client.compressionLevel = newCompression;
3862 expect(RFB.messages.clientEncodings).to.have.been.calledOnce;
3863 expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.pseudoEncodingCompressLevel0 + newCompression);
3864 });
b1dee947
SR
3865 });
3866});
f73fdc3e
NL
3867
3868describe('RFB messages', function () {
3869 let sock;
3870
3871 before(function () {
3872 FakeWebSocket.replace();
3873 sock = new Websock();
3874 sock.open();
3875 });
3876
3877 after(function () {
3878 FakeWebSocket.restore();
3879 });
3880
3881 describe('Extended Clipboard Handling Send', function () {
3882 beforeEach(function () {
3883 sinon.spy(RFB.messages, 'clientCutText');
3884 });
3885
3886 afterEach(function () {
3887 RFB.messages.clientCutText.restore();
3888 });
3889
3890 it('should call clientCutText with correct Caps data', function () {
3891 let formats = {
3892 0: 2,
3893 2: 4121
3894 };
3895 let expectedData = new Uint8Array([0x1F, 0x00, 0x00, 0x05,
3896 0x00, 0x00, 0x00, 0x02,
3897 0x00, 0x00, 0x10, 0x19]);
3898 let actions = [
3899 1 << 24, // Caps
3900 1 << 25, // Request
3901 1 << 26, // Peek
3902 1 << 27, // Notify
3903 1 << 28 // Provide
3904 ];
3905
3906 RFB.messages.extendedClipboardCaps(sock, actions, formats);
3907 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
3908 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData);
3909 });
3910
3911 it('should call clientCutText with correct Request data', function () {
3912 let formats = new Uint8Array([0x01]);
3913 let expectedData = new Uint8Array([0x02, 0x00, 0x00, 0x01]);
3914
3915 RFB.messages.extendedClipboardRequest(sock, formats);
3916 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
3917 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData);
3918 });
3919
3920 it('should call clientCutText with correct Notify data', function () {
3921 let formats = new Uint8Array([0x01]);
3922 let expectedData = new Uint8Array([0x08, 0x00, 0x00, 0x01]);
3923
3924 RFB.messages.extendedClipboardNotify(sock, formats);
3925 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
3926 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData);
3927 });
3928
3929 it('should call clientCutText with correct Provide data', function () {
3930 let testText = "Test string";
3931 let expectedText = encodeUTF8(testText + "\0");
3932
3933 let deflatedData = deflateWithSize(expectedText);
3934
3935 // Build Expected with flags and deflated data
3936 let expectedData = new Uint8Array(4 + deflatedData.length);
3937 expectedData[0] = 0x10; // The client capabilities
3938 expectedData[1] = 0x00; // Reserved flags
3939 expectedData[2] = 0x00; // Reserved flags
3940 expectedData[3] = 0x01; // The formats client supports
3941 expectedData.set(deflatedData, 4);
3942
3943 RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);
3944 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
3945 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData, true);
3946
3947 });
3948
3949 describe('End of line characters', function () {
3950 it('Carriage return', function () {
3951
3952 let testText = "Hello\rworld\r\r!";
3953 let expectedText = encodeUTF8("Hello\r\nworld\r\n\r\n!\0");
3954
3955 let deflatedData = deflateWithSize(expectedText);
3956
3957 // Build Expected with flags and deflated data
3958 let expectedData = new Uint8Array(4 + deflatedData.length);
3959 expectedData[0] = 0x10; // The client capabilities
3960 expectedData[1] = 0x00; // Reserved flags
3961 expectedData[2] = 0x00; // Reserved flags
3962 expectedData[3] = 0x01; // The formats client supports
3963 expectedData.set(deflatedData, 4);
3964
3965 RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);
3966 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
3967 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData, true);
3968 });
3969
3970 it('Carriage return Line feed', function () {
3971
3972 let testText = "Hello\r\n\r\nworld\r\n!";
3973 let expectedText = encodeUTF8(testText + "\0");
3974
3975 let deflatedData = deflateWithSize(expectedText);
3976
3977 // Build Expected with flags and deflated data
3978 let expectedData = new Uint8Array(4 + deflatedData.length);
3979 expectedData[0] = 0x10; // The client capabilities
3980 expectedData[1] = 0x00; // Reserved flags
3981 expectedData[2] = 0x00; // Reserved flags
3982 expectedData[3] = 0x01; // The formats client supports
3983 expectedData.set(deflatedData, 4);
3984
3985 RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);
3986 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
3987 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData, true);
3988 });
3989
3990 it('Line feed', function () {
3991 let testText = "Hello\n\n\nworld\n!";
3992 let expectedText = encodeUTF8("Hello\r\n\r\n\r\nworld\r\n!\0");
3993
3994 let deflatedData = deflateWithSize(expectedText);
3995
3996 // Build Expected with flags and deflated data
3997 let expectedData = new Uint8Array(4 + deflatedData.length);
3998 expectedData[0] = 0x10; // The client capabilities
3999 expectedData[1] = 0x00; // Reserved flags
4000 expectedData[2] = 0x00; // Reserved flags
4001 expectedData[3] = 0x01; // The formats client supports
4002 expectedData.set(deflatedData, 4);
4003
4004 RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);
4005 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
4006 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData, true);
4007 });
4008
4009 it('Carriage return and Line feed mixed', function () {
4010 let testText = "\rHello\r\n\rworld\n\n!";
4011 let expectedText = encodeUTF8("\r\nHello\r\n\r\nworld\r\n\r\n!\0");
4012
4013 let deflatedData = deflateWithSize(expectedText);
4014
4015 // Build Expected with flags and deflated data
4016 let expectedData = new Uint8Array(4 + deflatedData.length);
4017 expectedData[0] = 0x10; // The client capabilities
4018 expectedData[1] = 0x00; // Reserved flags
4019 expectedData[2] = 0x00; // Reserved flags
4020 expectedData[3] = 0x01; // The formats client supports
4021 expectedData.set(deflatedData, 4);
4022
4023 RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);
4024 expect(RFB.messages.clientCutText).to.have.been.calledOnce;
4025 expect(RFB.messages.clientCutText).to.have.been.calledWith(sock, expectedData, true);
4026 });
4027 });
4028 });
4029});