From 5a20f425d4fddf67aef14c25ccea7d3fc2315677 Mon Sep 17 00:00:00 2001 From: samhed Date: Thu, 2 Jun 2016 14:53:22 +0200 Subject: [PATCH 1/8] Clean up encodings array List pseudo-encodings seperately and sorted by encoding number. --- include/rfb.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/include/rfb.js b/include/rfb.js index c022969b..f426c15e 100644 --- a/include/rfb.js +++ b/include/rfb.js @@ -43,18 +43,20 @@ var RFB; ['HEXTILE', 0x05 ], ['RRE', 0x02 ], ['RAW', 0x00 ], - ['DesktopSize', -223 ], - ['Cursor', -239 ], // Psuedo-encoding settings + //['JPEG_quality_lo', -32 ], ['JPEG_quality_med', -26 ], //['JPEG_quality_hi', -23 ], //['compress_lo', -255 ], ['compress_hi', -247 ], + + ['DesktopSize', -223 ], ['last_rect', -224 ], - ['xvp', -309 ], - ['ExtendedDesktopSize', -308 ] + ['Cursor', -239 ], + ['ExtendedDesktopSize', -308 ], + ['xvp', -309 ] ]; this._encHandlers = {}; From 3af1a3a05b8d251faaa552c8dee2b3294fc9d357 Mon Sep 17 00:00:00 2001 From: samhed Date: Thu, 2 Jun 2016 14:57:44 +0200 Subject: [PATCH 2/8] Avoid unnecessary delays We only use setTimeout() to avoid hanging the browser, not because we actually want a delay. So let's use the smallest delay there is. --- include/rfb.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/rfb.js b/include/rfb.js index f426c15e..fa2401dd 100644 --- a/include/rfb.js +++ b/include/rfb.js @@ -255,7 +255,7 @@ var RFB; sendPassword: function (passwd) { this._rfb_password = passwd; this._rfb_state = 'Authentication'; - setTimeout(this._init_msg.bind(this), 1); + setTimeout(this._init_msg.bind(this), 0); }, sendCtrlAltDel: function () { @@ -549,7 +549,7 @@ var RFB; this._msgTimer = setTimeout(function () { this._msgTimer = null; this._handle_message(); - }.bind(this), 10); + }.bind(this), 0); } else { Util.Debug("More data to process, existing timer"); } From cf0236de18a3790badd0892ed4de4c2dc95ce91d Mon Sep 17 00:00:00 2001 From: samhed Date: Fri, 3 Jun 2016 14:13:15 +0200 Subject: [PATCH 3/8] Fix typo in pointer event test --- tests/test.rfb.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test.rfb.js b/tests/test.rfb.js index a0f2fa70..08159040 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -1741,7 +1741,7 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should send a mask of 1 on mousedown', function () { client._mouse._onMouseButton(10, 12, 1, 0x001); var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0}; - RFB.messages.pointerEvent(pointer_msg, 0, 10, 12, 0x001); + RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x001); expect(client._sock).to.have.sent(pointer_msg._sQ); }); From b1538a0fa4d9e6e98fdd06eed1a8699d11c8fdc0 Mon Sep 17 00:00:00 2001 From: samhed Date: Fri, 3 Jun 2016 14:13:35 +0200 Subject: [PATCH 4/8] Fix 'sent' assertion We were completely mishandling the length of the data. Make sure we look at the length of the websocket rather than the websock object, and also compare with the expected length. --- tests/assertions.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/assertions.js b/tests/assertions.js index 4bd0cf40..fa122dc3 100644 --- a/tests/assertions.js +++ b/tests/assertions.js @@ -37,10 +37,14 @@ chai.use(function (_chai, utils) { }; var data = obj._websocket._get_sent_data(); var same = true; - for (var i = 0; i < obj.length; i++) { - if (data[i] != target_data[i]) { - same = false; - break; + if (data.length != target_data.length) { + same = false; + } else { + for (var i = 0; i < data.length; i++) { + if (data[i] != target_data[i]) { + same = false; + break; + } } } if (!same) { From 89d2837fa8e3f0498bbac7b7194f453d88bcbc1f Mon Sep 17 00:00:00 2001 From: samhed Date: Thu, 2 Jun 2016 15:09:00 +0200 Subject: [PATCH 5/8] Always flush socket after each message Make sure our messages go away right away, rather than having to remember to call flush from the caller, or causing extra delays by waiting for the send timer. This should result in a more responsive system. --- include/rfb.js | 25 +++++++------------------ tests/test.rfb.js | 35 +++++++++++++++-------------------- 2 files changed, 22 insertions(+), 38 deletions(-) diff --git a/include/rfb.js b/include/rfb.js index fa2401dd..14e0aa62 100644 --- a/include/rfb.js +++ b/include/rfb.js @@ -67,7 +67,6 @@ var RFB; this._display = null; // Display object this._keyboard = null; // Keyboard input handler object this._mouse = null; // Mouse input handler object - this._sendTimer = null; // Send Queue check timer this._disconnTimer = null; // disconnection timer this._msgTimer = null; // queued handle_msg timer @@ -268,8 +267,6 @@ var RFB; RFB.messages.keyEvent(this._sock, XK_Delete, 0); RFB.messages.keyEvent(this._sock, XK_Alt_L, 0); RFB.messages.keyEvent(this._sock, XK_Control_L, 0); - - this._sock.flush(); }, xvpOp: function (ver, op) { @@ -303,14 +300,11 @@ var RFB; RFB.messages.keyEvent(this._sock, code, 1); RFB.messages.keyEvent(this._sock, code, 0); } - - this._sock.flush(); }, clipboardPasteFrom: function (text) { if (this._rfb_state !== 'normal') { return; } RFB.messages.clientCutText(this._sock, text); - this._sock.flush(); }, // Requests a change of remote desktop size. This message is an extension @@ -386,11 +380,6 @@ var RFB; }, _cleanupSocket: function (state) { - if (this._sendTimer) { - clearInterval(this._sendTimer); - this._sendTimer = null; - } - if (this._msgTimer) { clearInterval(this._msgTimer); this._msgTimer = null; @@ -564,7 +553,6 @@ var RFB; _handleKeyPress: function (keysym, down) { if (this._view_only) { return; } // View only, skip keyboard, events RFB.messages.keyEvent(this._sock, keysym, down); - this._sock.flush(); }, _handleMouseButton: function (x, y, down, bmask) { @@ -670,10 +658,6 @@ var RFB; this._rfb_version = this._rfb_max_version; } - // Send updates either at a rate of 1 update per 50ms, or - // whatever slower rate the network can handle - this._sendTimer = setInterval(this._sock.flush.bind(this._sock), 50); - var cversion = "00" + parseInt(this._rfb_version, 10) + ".00" + ((this._rfb_version * 10) % 10); this._sock.send_string("RFB " + cversion + "\n"); @@ -992,7 +976,6 @@ var RFB; this._timing.fbu_rt_start = (new Date()).getTime(); this._timing.pixels = 0; - this._sock.flush(); if (this._encrypt) { this._updateState('normal', 'Connected (encrypted) to: ' + this._fb_name); @@ -1095,7 +1078,6 @@ var RFB; var ret = this._framebufferUpdate(); if (ret) { RFB.messages.fbUpdateRequests(this._sock, this._display.getCleanDirtyReset(), this._fb_width, this._fb_height); - this._sock.flush(); } return ret; @@ -1285,6 +1267,7 @@ var RFB; buff[offset + 7] = keysym; sock._sQlen += 8; + sock.flush(); }, pointerEvent: function (sock, x, y, mask) { @@ -1302,6 +1285,7 @@ var RFB; buff[offset + 5] = y; sock._sQlen += 6; + sock.flush(); }, // TODO(directxman12): make this unicode compatible? @@ -1327,6 +1311,7 @@ var RFB; } sock._sQlen += 8 + n; + sock.flush(); }, setDesktopSize: function (sock, width, height, id, flags) { @@ -1362,6 +1347,7 @@ var RFB; buff[offset + 23] = flags; sock._sQlen += 24; + sock.flush(); }, pixelFormat: function (sock, bpp, depth, true_color) { @@ -1397,6 +1383,7 @@ var RFB; buff[offset + 19] = 0; // padding sock._sQlen += 20; + sock.flush(); }, clientEncodings: function (sock, encodings, local_cursor, true_color) { @@ -1431,6 +1418,7 @@ var RFB; buff[offset + 3] = cnt; sock._sQlen += j - offset; + sock.flush(); }, fbUpdateRequests: function (sock, cleanDirty, fb_width, fb_height) { @@ -1477,6 +1465,7 @@ var RFB; buff[offset + 9] = h & 0xFF; sock._sQlen += 10; + sock.flush(); } }; diff --git a/tests/test.rfb.js b/tests/test.rfb.js index 08159040..aed339c8 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -132,7 +132,7 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should sent ctrl[down]-alt[down]-del[down] then del[up]-alt[up]-ctrl[up]', function () { - var expected = {_sQ: new Uint8Array(48), _sQlen: 0}; + var expected = {_sQ: new Uint8Array(48), _sQlen: 0, flush: function () {}}; RFB.messages.keyEvent(expected, 0xFFE3, 1); RFB.messages.keyEvent(expected, 0xFFE9, 1); RFB.messages.keyEvent(expected, 0xFFFF, 1); @@ -168,14 +168,14 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should send a single key with the given code and state (down = true)', function () { - var expected = {_sQ: new Uint8Array(8), _sQlen: 0}; + var expected = {_sQ: new Uint8Array(8), _sQlen: 0, flush: function () {}}; RFB.messages.keyEvent(expected, 123, 1); client.sendKey(123, true); expect(client._sock).to.have.sent(expected._sQ); }); it('should send both a down and up event if the state is not specified', function () { - var expected = {_sQ: new Uint8Array(16), _sQlen: 0}; + var expected = {_sQ: new Uint8Array(16), _sQlen: 0, flush: function () {}}; RFB.messages.keyEvent(expected, 123, 1); RFB.messages.keyEvent(expected, 123, 0); client.sendKey(123); @@ -206,7 +206,7 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should send the given text in a paste event', function () { - var expected = {_sQ: new Uint8Array(11), _sQlen: 0}; + var expected = {_sQ: new Uint8Array(11), _sQlen: 0, flush: function () {}}; RFB.messages.clientCutText(expected, 'abc'); client.clipboardPasteFrom('abc'); expect(client._sock).to.have.sent(expected._sQ); @@ -571,13 +571,6 @@ describe('Remote Frame Buffer Protocol Client', function() { expect(client._rfb_version).to.equal(3.8); }); - it('should initialize the flush interval', function () { - client._sock.flush = sinon.spy(); - send_ver('003.008', client); - this.clock.tick(100); - expect(client._sock.flush).to.have.been.calledThrice; - }); - it('should send back the interpreted version', function () { send_ver('004.000', client); @@ -1070,7 +1063,9 @@ describe('Remote Frame Buffer Protocol Client', function() { client.set_true_color(true); client.set_local_cursor(false); // we skip the cursor encoding - var expected = {_sQ: new Uint8Array(34 + 4 * (client._encodings.length - 1)), _sQlen: 0}; + var expected = {_sQ: new Uint8Array(34 + 4 * (client._encodings.length - 1)), + _sQlen: 0, + flush: function () {}}; RFB.messages.pixelFormat(expected, 4, 3, true); RFB.messages.clientEncodings(expected, client._encodings, false, true); var expected_cdr = { cleanBox: { x: 0, y: 0, w: 0, h: 0 }, @@ -1161,7 +1156,7 @@ describe('Remote Frame Buffer Protocol Client', function() { } it('should send an update request if there is sufficient data', function () { - var expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0}; + var expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: function() {}}; var expected_cdr = { cleanBox: { x: 0, y: 0, w: 0, h: 0 }, dirtyBoxes: [ { x: 0, y: 0, w: 240, h: 20 } ] }; RFB.messages.fbUpdateRequests(expected_msg, expected_cdr, 240, 20); @@ -1178,7 +1173,7 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should resume receiving an update if we previously did not have enough data', function () { - var expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0}; + var expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: function() {}}; var expected_cdr = { cleanBox: { x: 0, y: 0, w: 0, h: 0 }, dirtyBoxes: [ { x: 0, y: 0, w: 240, h: 20 } ] }; RFB.messages.fbUpdateRequests(expected_msg, expected_cdr, 240, 20); @@ -1733,14 +1728,14 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should send a pointer event on mouse button presses', function () { client._mouse._onMouseButton(10, 12, 1, 0x001); - var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0}; + var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: function () {}}; RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x001); expect(client._sock).to.have.sent(pointer_msg._sQ); }); it('should send a mask of 1 on mousedown', function () { client._mouse._onMouseButton(10, 12, 1, 0x001); - var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0}; + var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: function () {}}; RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x001); expect(client._sock).to.have.sent(pointer_msg._sQ); }); @@ -1748,14 +1743,14 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should send a mask of 0 on mouseup', function () { client._mouse_buttonMask = 0x001; client._mouse._onMouseButton(10, 12, 0, 0x001); - var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0}; + var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: function () {}}; RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x000); expect(client._sock).to.have.sent(pointer_msg._sQ); }); it('should send a pointer event on mouse movement', function () { client._mouse._onMouseMove(10, 12); - var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0}; + var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: function () {}}; RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x000); expect(client._sock).to.have.sent(pointer_msg._sQ); }); @@ -1763,7 +1758,7 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should set the button mask so that future mouse movements use it', function () { client._mouse._onMouseButton(10, 12, 1, 0x010); client._mouse._onMouseMove(13, 9); - var pointer_msg = {_sQ: new Uint8Array(12), _sQlen: 0}; + var pointer_msg = {_sQ: new Uint8Array(12), _sQlen: 0, flush: function () {}}; RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x010); RFB.messages.pointerEvent(pointer_msg, 13, 9, 0x010); expect(client._sock).to.have.sent(pointer_msg._sQ); @@ -1829,7 +1824,7 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should send a key message on a key press', function () { client._keyboard._onKeyPress(1234, 1); - var key_msg = {_sQ: new Uint8Array(8), _sQlen: 0}; + var key_msg = {_sQ: new Uint8Array(8), _sQlen: 0, flush: function () {}}; RFB.messages.keyEvent(key_msg, 1234, 1); expect(client._sock).to.have.sent(key_msg._sQ); }); From 37195e4b5e776d1ce19a65ba72009536de7dcd67 Mon Sep 17 00:00:00 2001 From: samhed Date: Fri, 3 Jun 2016 15:22:19 +0200 Subject: [PATCH 6/8] Lower level check for framebuffer update requests Try to avoid using helper functions with complex logic when verifying results as those helper functions are also something we want to verify. Also add a test for a mix of clean and dirty areas specifically to make sure that helper function behaves properly. --- tests/test.rfb.js | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/tests/test.rfb.js b/tests/test.rfb.js index aed339c8..a0fdf8f2 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -1068,9 +1068,7 @@ describe('Remote Frame Buffer Protocol Client', function() { flush: function () {}}; RFB.messages.pixelFormat(expected, 4, 3, true); RFB.messages.clientEncodings(expected, client._encodings, false, true); - var expected_cdr = { cleanBox: { x: 0, y: 0, w: 0, h: 0 }, - dirtyBoxes: [ { x: 0, y: 0, w: 27, h: 32 } ] }; - RFB.messages.fbUpdateRequests(expected, expected_cdr, 27, 32); + RFB.messages.fbUpdateRequest(expected, false, 0, 0, 27, 32); send_server_init({ width: 27, height: 32 }, client); expect(client._sock).to.have.sent(expected._sQ); @@ -1157,9 +1155,7 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should send an update request if there is sufficient data', function () { var expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: function() {}}; - var expected_cdr = { cleanBox: { x: 0, y: 0, w: 0, h: 0 }, - dirtyBoxes: [ { x: 0, y: 0, w: 240, h: 20 } ] }; - RFB.messages.fbUpdateRequests(expected_msg, expected_cdr, 240, 20); + RFB.messages.fbUpdateRequest(expected_msg, false, 0, 0, 240, 20); client._framebufferUpdate = function () { return true; }; client._sock._websocket._receive_data(new Uint8Array([0])); @@ -1174,9 +1170,7 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should resume receiving an update if we previously did not have enough data', function () { var expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: function() {}}; - var expected_cdr = { cleanBox: { x: 0, y: 0, w: 0, h: 0 }, - dirtyBoxes: [ { x: 0, y: 0, w: 240, h: 20 } ] }; - RFB.messages.fbUpdateRequests(expected_msg, expected_cdr, 240, 20); + RFB.messages.fbUpdateRequest(expected_msg, false, 0, 0, 240, 20); // just enough to set FBU.rects client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 3])); @@ -1188,6 +1182,21 @@ describe('Remote Frame Buffer Protocol Client', function() { expect(client._sock).to.have.sent(expected_msg._sQ); }); + it('should send a request for both clean and dirty areas', function () { + var expected_msg = {_sQ: new Uint8Array(20), _sQlen: 0, flush: function() {}}; + var expected_cdr = { cleanBox: { x: 0, y: 0, w: 120, h: 20 }, + dirtyBoxes: [ { x: 120, y: 0, w: 120, h: 20 } ] }; + + RFB.messages.fbUpdateRequest(expected_msg, true, 0, 0, 120, 20); + RFB.messages.fbUpdateRequest(expected_msg, false, 120, 0, 120, 20); + + client._framebufferUpdate = function () { return true; }; + client._display.getCleanDirtyReset = function () { return expected_cdr; }; + client._sock._websocket._receive_data(new Uint8Array([0])); + + expect(client._sock).to.have.sent(expected_msg._sQ); + }); + it('should parse out information from a header before any actual data comes in', function () { client.set_onFBUReceive(sinon.spy()); var rect_info = { x: 8, y: 11, width: 27, height: 32, encoding: 0x02, encodingName: 'RRE' }; From 3df13262394515efe5877ce33fd0dbdf35e1b743 Mon Sep 17 00:00:00 2001 From: samhed Date: Thu, 2 Jun 2016 16:00:33 +0200 Subject: [PATCH 7/8] Add support for fences We don't actually use these, but servers may require this for other features. --- include/rfb.js | 71 ++++++++++++++++++++++++++++++++++++++++++++++- tests/test.rfb.js | 25 +++++++++++++++++ 2 files changed, 95 insertions(+), 1 deletion(-) diff --git a/include/rfb.js b/include/rfb.js index 14e0aa62..71672ffe 100644 --- a/include/rfb.js +++ b/include/rfb.js @@ -56,7 +56,8 @@ var RFB; ['last_rect', -224 ], ['Cursor', -239 ], ['ExtendedDesktopSize', -308 ], - ['xvp', -309 ] + ['xvp', -309 ], + ['Fence', -312 ] ]; this._encHandlers = {}; @@ -70,6 +71,8 @@ var RFB; this._disconnTimer = null; // disconnection timer this._msgTimer = null; // queued handle_msg timer + this._supportsFence = false; + // Frame buffer update state this._FBU = { rects: 0, @@ -1041,6 +1044,42 @@ var RFB; return true; }, + _handle_server_fence_msg: function() { + if (this._sock.rQwait("ServerFence header", 8, 1)) { return false; } + this._sock.rQskipBytes(3); // Padding + var flags = this._sock.rQshift32(); + var length = this._sock.rQshift8(); + + if (this._sock.rQwait("ServerFence payload", length, 9)) { return false; } + var payload = this._sock.rQshiftStr(length); // FIXME: 64 bytes max + + this._supportsFence = true; + + /* + * Fence flags + * + * (1<<0) - BlockBefore + * (1<<1) - BlockAfter + * (1<<2) - SyncNext + * (1<<31) - Request + */ + + if (!(flags & (1<<31))) { + return this._fail("Unexpected fence response"); + } + + // Filter out unsupported flags + // FIXME: support syncNext + flags &= (1<<0) | (1<<1); + + // BlockBefore and BlockAfter are automatically handled by + // the fact that we process each incoming message + // synchronuosly. + RFB.messages.clientFence(this._sock, flags, payload); + + return true; + }, + _handle_xvp_msg: function () { if (this._sock.rQwait("XVP version and message", 3, 1)) { return false; } this._sock.rQskip8(); // Padding @@ -1092,6 +1131,9 @@ var RFB; case 3: // ServerCutText return this._handle_server_cut_text(); + case 248: // ServerFence + return this._handle_server_fence_msg(); + case 250: // XVP return this._handle_xvp_msg(); @@ -1350,6 +1392,33 @@ var RFB; sock.flush(); }, + clientFence: function (sock, flags, payload) { + var buff = sock._sQ; + var offset = sock._sQlen; + + buff[offset] = 248; // msg-type + + buff[offset + 1] = 0; // padding + buff[offset + 2] = 0; // padding + buff[offset + 3] = 0; // padding + + buff[offset + 4] = flags >> 24; // flags + buff[offset + 5] = flags >> 16; + buff[offset + 6] = flags >> 8; + buff[offset + 7] = flags; + + var n = payload.length; + + buff[offset + 8] = n; // length + + for (var i = 0; i < n; i++) { + buff[offset + 9 + i] = payload.charCodeAt(i); + } + + sock._sQlen += 9 + n; + sock.flush(); + }, + pixelFormat: function (sock, bpp, depth, true_color) { var buff = sock._sQ; var offset = sock._sQlen; diff --git a/tests/test.rfb.js b/tests/test.rfb.js index a0fdf8f2..be6aa1b2 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -1705,6 +1705,31 @@ describe('Remote Frame Buffer Protocol Client', function() { expect(client.get_onBell()).to.have.been.calledOnce; }); + it('should respond correctly to ServerFence', function () { + var expected_msg = {_sQ: new Uint8Array(16), _sQlen: 0, flush: function() {}}; + var incoming_msg = {_sQ: new Uint8Array(16), _sQlen: 0, flush: function() {}}; + + var payload = "foo\x00ab9"; + + // ClientFence and ServerFence are identical in structure + RFB.messages.clientFence(expected_msg, (1<<0) | (1<<1), payload); + RFB.messages.clientFence(incoming_msg, 0xffffffff, payload); + + client._sock._websocket._receive_data(incoming_msg._sQ); + + expect(client._sock).to.have.sent(expected_msg._sQ); + + expected_msg._sQlen = 0; + incoming_msg._sQlen = 0; + + RFB.messages.clientFence(expected_msg, (1<<0), payload); + RFB.messages.clientFence(incoming_msg, (1<<0) | (1<<31), payload); + + client._sock._websocket._receive_data(incoming_msg._sQ); + + expect(client._sock).to.have.sent(expected_msg._sQ); + }); + it('should fail on an unknown message type', function () { client._sock._websocket._receive_data(new Uint8Array([87])); expect(client._rfb_state).to.equal('failed'); From 76a86ff51436ed3e4cf5125ca8d58240b52e9c37 Mon Sep 17 00:00:00 2001 From: samhed Date: Thu, 2 Jun 2016 16:41:38 +0200 Subject: [PATCH 8/8] Add support for ContinuousUpdates Instead of requesting frame buffer updates we can, if the server supports it, continuously recieve frame buffer updates at a rate determined by the server. The server can use fencing messages and measure response times to determine how often it will continue to send updates. --- include/rfb.js | 67 +++++++++++++++++++++++++++++++++++++++++---- tests/test.rfb.js | 70 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+), 6 deletions(-) diff --git a/include/rfb.js b/include/rfb.js index 71672ffe..e6597af2 100644 --- a/include/rfb.js +++ b/include/rfb.js @@ -57,7 +57,8 @@ var RFB; ['Cursor', -239 ], ['ExtendedDesktopSize', -308 ], ['xvp', -309 ], - ['Fence', -312 ] + ['Fence', -312 ], + ['ContinuousUpdates', -313 ] ]; this._encHandlers = {}; @@ -73,6 +74,9 @@ var RFB; this._supportsFence = false; + this._supportsContinuousUpdates = false; + this._enabledContinuousUpdates = false; + // Frame buffer update state this._FBU = { rects: 0, @@ -975,7 +979,7 @@ var RFB; RFB.messages.pixelFormat(this._sock, this._fb_Bpp, this._fb_depth, this._true_color); RFB.messages.clientEncodings(this._sock, this._encodings, this._local_cursor, this._true_color); - RFB.messages.fbUpdateRequests(this._sock, this._display.getCleanDirtyReset(), this._fb_width, this._fb_height); + RFB.messages.fbUpdateRequests(this._sock, false, this._display.getCleanDirtyReset(), this._fb_width, this._fb_height); this._timing.fbu_rt_start = (new Date()).getTime(); this._timing.pixels = 0; @@ -1051,7 +1055,13 @@ var RFB; var length = this._sock.rQshift8(); if (this._sock.rQwait("ServerFence payload", length, 9)) { return false; } - var payload = this._sock.rQshiftStr(length); // FIXME: 64 bytes max + + if (length > 64) { + Util.Warn("Bad payload length (" + length + ") in fence response"); + length = 64; + } + + var payload = this._sock.rQshiftStr(length); this._supportsFence = true; @@ -1116,7 +1126,10 @@ var RFB; case 0: // FramebufferUpdate var ret = this._framebufferUpdate(); if (ret) { - RFB.messages.fbUpdateRequests(this._sock, this._display.getCleanDirtyReset(), this._fb_width, this._fb_height); + RFB.messages.fbUpdateRequests(this._sock, + this._enabledContinuousUpdates, + this._display.getCleanDirtyReset(), + this._fb_width, this._fb_height); } return ret; @@ -1131,6 +1144,20 @@ var RFB; case 3: // ServerCutText return this._handle_server_cut_text(); + case 150: // EndOfContinuousUpdates + var first = !(this._supportsContinuousUpdates); + this._supportsContinuousUpdates = true; + this._enabledContinuousUpdates = false; + if (first) { + this._enabledContinuousUpdates = true; + this._updateContinuousUpdates(); + Util.Info("Enabling continuous updates."); + } else { + // FIXME: We need to send a framebufferupdaterequest here + // if we add support for turning off continuous updates + } + return true; + case 248: // ServerFence return this._handle_server_fence_msg(); @@ -1245,6 +1272,13 @@ var RFB; return true; // We finished this FBU }, + + _updateContinuousUpdates: function() { + if (!this._enabledContinuousUpdates) { return; } + + RFB.messages.enableContinuousUpdates(this._sock, true, 0, 0, + this._fb_width, this._fb_height); + } }; Util.make_properties(RFB, [ @@ -1419,6 +1453,26 @@ var RFB; sock.flush(); }, + enableContinuousUpdates: function (sock, enable, x, y, width, height) { + var buff = sock._sQ; + var offset = sock._sQlen; + + buff[offset] = 150; // msg-type + buff[offset + 1] = enable; // enable-flag + + buff[offset + 2] = x >> 8; // x + buff[offset + 3] = x; + buff[offset + 4] = y >> 8; // y + buff[offset + 5] = y; + buff[offset + 6] = width >> 8; // width + buff[offset + 7] = width; + buff[offset + 8] = height >> 8; // height + buff[offset + 9] = height; + + sock._sQlen += 10; + sock.flush(); + }, + pixelFormat: function (sock, bpp, depth, true_color) { var buff = sock._sQ; var offset = sock._sQlen; @@ -1490,12 +1544,12 @@ var RFB; sock.flush(); }, - fbUpdateRequests: function (sock, cleanDirty, fb_width, fb_height) { + fbUpdateRequests: function (sock, onlyNonInc, cleanDirty, fb_width, fb_height) { var offsetIncrement = 0; var cb = cleanDirty.cleanBox; var w, h; - if (cb.w > 0 && cb.h > 0) { + if (!onlyNonInc && (cb.w > 0 && cb.h > 0)) { w = typeof cb.w === "undefined" ? fb_width : cb.w; h = typeof cb.h === "undefined" ? fb_height : cb.h; // Request incremental for clean box @@ -2102,6 +2156,7 @@ var RFB; this._display.resize(this._fb_width, this._fb_height); this._onFBResize(this, this._fb_width, this._fb_height); this._timing.fbu_rt_start = (new Date()).getTime(); + this._updateContinuousUpdates(); this._FBU.bytes = 0; this._FBU.rects -= 1; diff --git a/tests/test.rfb.js b/tests/test.rfb.js index be6aa1b2..65ce5f88 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -1197,6 +1197,33 @@ describe('Remote Frame Buffer Protocol Client', function() { expect(client._sock).to.have.sent(expected_msg._sQ); }); + it('should only request non-incremental rects in continuous updates mode', function () { + var expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: function() {}}; + var expected_cdr = { cleanBox: { x: 0, y: 0, w: 120, h: 20 }, + dirtyBoxes: [ { x: 120, y: 0, w: 120, h: 20 } ] }; + + RFB.messages.fbUpdateRequest(expected_msg, false, 120, 0, 120, 20); + + client._enabledContinuousUpdates = true; + client._framebufferUpdate = function () { return true; }; + client._display.getCleanDirtyReset = function () { return expected_cdr; }; + client._sock._websocket._receive_data(new Uint8Array([0])); + + expect(client._sock).to.have.sent(expected_msg._sQ); + }); + + it('should not send a request in continuous updates mode when clean', function () { + var expected_cdr = { cleanBox: { x: 0, y: 0, w: 240, h: 20 }, + dirtyBoxes: [] }; + + client._enabledContinuousUpdates = true; + client._framebufferUpdate = function () { return true; }; + client._display.getCleanDirtyReset = function () { return expected_cdr; }; + client._sock._websocket._receive_data(new Uint8Array([0])); + + expect(client._sock._websocket._get_sent_data()).to.have.length(0); + }); + it('should parse out information from a header before any actual data comes in', function () { client.set_onFBUReceive(sinon.spy()); var rect_info = { x: 8, y: 11, width: 27, height: 32, encoding: 0x02, encodingName: 'RRE' }; @@ -1730,6 +1757,49 @@ describe('Remote Frame Buffer Protocol Client', function() { expect(client._sock).to.have.sent(expected_msg._sQ); }); + it('should enable continuous updates on first EndOfContinousUpdates', function () { + var expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: function() {}}; + + RFB.messages.enableContinuousUpdates(expected_msg, true, 0, 0, 640, 20); + + expect(client._enabledContinuousUpdates).to.be.false; + + client._sock._websocket._receive_data(new Uint8Array([150])); + + expect(client._enabledContinuousUpdates).to.be.true; + expect(client._sock).to.have.sent(expected_msg._sQ); + }); + + it('should disable continuous updates on subsequent EndOfContinousUpdates', function () { + client._enabledContinuousUpdates = true; + client._supportsContinuousUpdates = true; + + client._sock._websocket._receive_data(new Uint8Array([150])); + + expect(client._enabledContinuousUpdates).to.be.false; + }); + + it('should update continuous updates on resize', function () { + var expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: function() {}}; + RFB.messages.enableContinuousUpdates(expected_msg, true, 0, 0, 90, 700); + + client._FBU.width = 450; + client._FBU.height = 160; + + client._encHandlers.handle_FB_resize(); + + expect(client._sock._websocket._get_sent_data()).to.have.length(0); + + client._enabledContinuousUpdates = true; + + client._FBU.width = 90; + client._FBU.height = 700; + + client._encHandlers.handle_FB_resize(); + + expect(client._sock).to.have.sent(expected_msg._sQ); + }); + it('should fail on an unknown message type', function () { client._sock._websocket._receive_data(new Uint8Array([87])); expect(client._rfb_state).to.equal('failed');