diff --git a/include/rfb.js b/include/rfb.js index 694c14ba..8d268b04 100644 --- a/include/rfb.js +++ b/include/rfb.js @@ -43,18 +43,22 @@ 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 ], + ['Cursor', -239 ], + ['ExtendedDesktopSize', -308 ], ['xvp', -309 ], - ['ExtendedDesktopSize', -308 ] + ['Fence', -312 ], + ['ContinuousUpdates', -313 ] ]; this._encHandlers = {}; @@ -65,10 +69,14 @@ 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 + this._supportsFence = false; + + this._supportsContinuousUpdates = false; + this._enabledContinuousUpdates = false; + // Frame buffer update state this._FBU = { rects: 0, @@ -253,7 +261,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 () { @@ -266,8 +274,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) { @@ -301,14 +307,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 @@ -384,11 +387,6 @@ var RFB; }, _cleanupSocket: function (state) { - if (this._sendTimer) { - clearInterval(this._sendTimer); - this._sendTimer = null; - } - if (this._msgTimer) { clearInterval(this._msgTimer); this._msgTimer = null; @@ -547,7 +545,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"); } @@ -562,7 +560,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) { @@ -668,10 +665,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"); @@ -986,11 +979,10 @@ 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; - this._sock.flush(); if (this._encrypt) { this._updateState('normal', 'Connected (encrypted) to: ' + this._fb_name); @@ -1056,6 +1048,48 @@ 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; } + + if (length > 64) { + Util.Warn("Bad payload length (" + length + ") in fence response"); + length = 64; + } + + var payload = this._sock.rQshiftStr(length); + + 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,8 +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); - this._sock.flush(); + RFB.messages.fbUpdateRequests(this._sock, + this._enabledContinuousUpdates, + this._display.getCleanDirtyReset(), + this._fb_width, this._fb_height); } return ret; @@ -1108,6 +1144,23 @@ 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(); + case 250: // XVP return this._handle_xvp_msg(); @@ -1212,6 +1265,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, [ @@ -1276,6 +1336,7 @@ var RFB; buff[offset + 7] = keysym; sock._sQlen += 8; + sock.flush(); }, pointerEvent: function (sock, x, y, mask) { @@ -1293,6 +1354,7 @@ var RFB; buff[offset + 5] = y; sock._sQlen += 6; + sock.flush(); }, // TODO(directxman12): make this unicode compatible? @@ -1318,6 +1380,7 @@ var RFB; } sock._sQlen += 8 + n; + sock.flush(); }, setDesktopSize: function (sock, width, height, id, flags) { @@ -1353,6 +1416,54 @@ var RFB; buff[offset + 23] = flags; sock._sQlen += 24; + 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(); + }, + + 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) { @@ -1388,6 +1499,7 @@ var RFB; buff[offset + 19] = 0; // padding sock._sQlen += 20; + sock.flush(); }, clientEncodings: function (sock, encodings, local_cursor, true_color) { @@ -1422,14 +1534,15 @@ var RFB; buff[offset + 3] = cnt; sock._sQlen += j - offset; + 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 @@ -1468,6 +1581,7 @@ var RFB; buff[offset + 9] = h & 0xFF; sock._sQlen += 10; + sock.flush(); } }; @@ -2034,6 +2148,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/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) { diff --git a/tests/test.rfb.js b/tests/test.rfb.js index a0f2fa70..65ce5f88 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,12 +1063,12 @@ 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 }, - 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); @@ -1161,10 +1154,8 @@ 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_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); + var expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: function() {}}; + RFB.messages.fbUpdateRequest(expected_msg, false, 0, 0, 240, 20); client._framebufferUpdate = function () { return true; }; client._sock._websocket._receive_data(new Uint8Array([0])); @@ -1178,10 +1169,8 @@ 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_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); + var expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: function() {}}; + 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])); @@ -1193,6 +1182,48 @@ 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 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' }; @@ -1701,6 +1732,74 @@ 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 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'); @@ -1733,29 +1832,29 @@ 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}; - RFB.messages.pointerEvent(pointer_msg, 0, 10, 12, 0x001); + 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 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 +1862,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 +1928,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); });