From aeffd56ea02509037f640b14f82c0a7cd6487b1a 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 c022969..f426c15 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 05ce666c4c40d390e610bb80e848092b3b703f34 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 f426c15..fa2401d 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 c74482cc9b24d57cff4abc98905144d7af94b2a3 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 a0f2fa7..0815904 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 32da983a3904a0096a7dcc6a2f5587b501a6405d 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 4bd0cf4..fa122dc 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 7f2867eef6139c2d2842a1542297325c31eb322f 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 fa2401d..14e0aa6 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 0815904..aed339c 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 6ad67e3f834e3556d8f709018aced6e6056e28fc 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 aed339c..a0fdf8f 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 d6b88ea7ac74069ebb7f8563d0bc59930fbf8f3f 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 14e0aa6..71672ff 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 a0fdf8f..be6aa1b 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 f14f05caff44f5fc2315eb3f49d112fe4dfe9524 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 71672ff..e6597af 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 be6aa1b..65ce5f8 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');