From 9ff86fb718477515ede2d6457f06643935d76bcd Mon Sep 17 00:00:00 2001 From: Solly Ross Date: Thu, 28 May 2015 15:28:30 -0400 Subject: [PATCH] Use Typed Arrays for the send queue This commit converts the send queue to use typed arrays, and converts message creation functions in 'rfb.js' to create messages directly into the socket's send queue. This commit also removes the separate mouse array, which is no longer needed. --- include/rfb.js | 262 +++++++++++++++++++++++----------------- include/websock.js | 20 +-- tests/assertions.js | 10 +- tests/fake.websocket.js | 9 +- tests/test.rfb.js | 180 ++++++++++++++------------- tests/test.websock.js | 18 +-- 6 files changed, 280 insertions(+), 219 deletions(-) diff --git a/include/rfb.js b/include/rfb.js index e2dd42f..b8615af 100644 --- a/include/rfb.js +++ b/include/rfb.js @@ -259,14 +259,14 @@ var RFB; if (this._rfb_state !== 'normal' || this._view_only) { return false; } Util.Info("Sending Ctrl-Alt-Del"); - var arr = []; - arr = arr.concat(RFB.messages.keyEvent(XK_Control_L, 1)); - arr = arr.concat(RFB.messages.keyEvent(XK_Alt_L, 1)); - arr = arr.concat(RFB.messages.keyEvent(XK_Delete, 1)); - arr = arr.concat(RFB.messages.keyEvent(XK_Delete, 0)); - arr = arr.concat(RFB.messages.keyEvent(XK_Alt_L, 0)); - arr = arr.concat(RFB.messages.keyEvent(XK_Control_L, 0)); - this._sock.send(arr); + RFB.messages.keyEvent(this._sock, XK_Control_L, 1); + RFB.messages.keyEvent(this._sock, XK_Alt_L, 1); + RFB.messages.keyEvent(this._sock, XK_Delete, 1); + 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) { @@ -292,21 +292,22 @@ var RFB; // followed by an up key. sendKey: function (code, down) { if (this._rfb_state !== "normal" || this._view_only) { return false; } - var arr = []; if (typeof down !== 'undefined') { Util.Info("Sending key code (" + (down ? "down" : "up") + "): " + code); - arr = arr.concat(RFB.messages.keyEvent(code, down ? 1 : 0)); + RFB.messages.keyEvent(this._sock, code, down ? 1 : 0); } else { Util.Info("Sending key code (down + up): " + code); - arr = arr.concat(RFB.messages.keyEvent(code, 1)); - arr = arr.concat(RFB.messages.keyEvent(code, 0)); + RFB.messages.keyEvent(this._sock, code, 1); + RFB.messages.keyEvent(this._sock, code, 0); } - this._sock.send(arr); + + this._sock.flush(); }, clipboardPasteFrom: function (text) { if (this._rfb_state !== 'normal') { return; } - this._sock.send(RFB.messages.clientCutText(text)); + RFB.messages.clientCutText(this._sock, text); + this._sock.flush(); }, setDesktopSize: function (width, height) { @@ -572,16 +573,10 @@ var RFB; } }, - _checkEvents: function () { - if (this._rfb_state === 'normal' && !this._viewportDragging && this._mouse_arr.length > 0) { - this._sock.send(this._mouse_arr); - this._mouse_arr = []; - } - }, - _handleKeyPress: function (keysym, down) { if (this._view_only) { return; } // View only, skip keyboard, events - this._sock.send(RFB.messages.keyEvent(keysym, down)); + RFB.messages.keyEvent(this._sock, keysym, down); + this._sock.flush(); }, _handleMouseButton: function (x, y, down, bmask) { @@ -605,10 +600,8 @@ var RFB; if (this._view_only) { return; } // View only, skip mouse events - this._mouse_arr = this._mouse_arr.concat( - RFB.messages.pointerEvent(this._display.absX(x), this._display.absY(y), this._mouse_buttonMask)); - this._sock.send(this._mouse_arr); - this._mouse_arr = []; + if (this._rfb_state !== "normal") { return; } + RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), this._mouse_buttonMask); }, _handleMouseMove: function (x, y) { @@ -625,10 +618,8 @@ var RFB; if (this._view_only) { return; } // View only, skip mouse events - this._mouse_arr = this._mouse_arr.concat( - RFB.messages.pointerEvent(this._display.absX(x), this._display.absY(y), this._mouse_buttonMask)); - - this._checkEvents(); + if (this._rfb_state !== "normal") { return; } + RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), this._mouse_buttonMask); }, // Message Handlers @@ -895,7 +886,7 @@ var RFB; /* Screen size */ this._fb_width = this._sock.rQshift16(); this._fb_height = this._sock.rQshift16(); - this._dest_buff = new Uint8Array(this._fb_width * this._fb_height * 4); + this._destBuff = new Uint8Array(this._fb_width * this._fb_height * 4); /* PIXEL_FORMAT */ var bpp = this._sock.rQshift8(); @@ -991,18 +982,13 @@ var RFB; this._fb_depth = 1; } - var response = RFB.messages.pixelFormat(this._fb_Bpp, this._fb_depth, this._true_color); - response = response.concat( - RFB.messages.clientEncodings(this._encodings, this._local_cursor, this._true_color)); - response = response.concat( - RFB.messages.fbUpdateRequests(this._display.getCleanDirtyReset(), - this._fb_width, this._fb_height)); + 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); this._timing.fbu_rt_start = (new Date()).getTime(); this._timing.pixels = 0; - this._sock.send(response); - - this._checkEvents(); + this._sock.flush(); if (this._encrypt) { this._updateState('normal', 'Connected (encrypted) to: ' + this._fb_name); @@ -1104,8 +1090,8 @@ var RFB; case 0: // FramebufferUpdate var ret = this._framebufferUpdate(); if (ret) { - this._sock.send(RFB.messages.fbUpdateRequests(this._display.getCleanDirtyReset(), - this._fb_width, this._fb_height)); + RFB.messages.fbUpdateRequests(this._sock, this._display.getCleanDirtyReset(), this._fb_width, this._fb_height); + this._sock.flush(); } return ret; @@ -1279,64 +1265,111 @@ var RFB; // Class Methods RFB.messages = { - keyEvent: function (keysym, down) { - var arr = [4]; - arr.push8(down); - arr.push16(0); - arr.push32(keysym); - return arr; + keyEvent: function (sock, keysym, down) { + var buff = sock._sQ; + var offset = sock._sQlen; + + buff[offset] = 4; // msg-type + buff[offset + 1] = down; + + buff[offset + 2] = 0; + buff[offset + 3] = 0; + + buff[offset + 4] = (keysym >> 24); + buff[offset + 5] = (keysym >> 16); + buff[offset + 6] = (keysym >> 8); + buff[offset + 7] = keysym; + + sock._sQlen += 8; }, - pointerEvent: function (x, y, mask) { - var arr = [5]; // msg-type - arr.push8(mask); - arr.push16(x); - arr.push16(y); - return arr; + pointerEvent: function (sock, x, y, mask) { + var buff = sock._sQ; + var offset = sock._sQlen; + + buff[offset] = 5; // msg-type + + buff[offset + 1] = mask; + + buff[offset + 2] = x >> 8; + buff[offset + 3] = x; + + buff[offset + 4] = y >> 8; + buff[offset + 5] = y; + + sock._sQlen += 6; }, // TODO(directxman12): make this unicode compatible? - clientCutText: function (text) { - var arr = [6]; // msg-type - arr.push8(0); // padding - arr.push8(0); // padding - arr.push8(0); // padding - arr.push32(text.length); + clientCutText: function (sock, text) { + var buff = sock._sQ; + var offset = sock._sQlen; + + buff[offset] = 6; // msg-type + + buff[offset + 1] = 0; // padding + buff[offset + 2] = 0; // padding + buff[offset + 3] = 0; // padding + var n = text.length; + + buff[offset + 4] = n >> 24; + buff[offset + 5] = n >> 16; + buff[offset + 6] = n >> 8; + buff[offset + 7] = n; + for (var i = 0; i < n; i++) { - arr.push(text.charCodeAt(i)); + buff[offset + 8 + i] = text.charCodeAt(i); } - return arr; + sock._sQlen += 8 + n; }, - pixelFormat: function (bpp, depth, true_color) { - var arr = [0]; // msg-type - arr.push8(0); // padding - arr.push8(0); // padding - arr.push8(0); // padding - - arr.push8(bpp * 8); // bits-per-pixel - arr.push8(depth * 8); // depth - arr.push8(0); // little-endian - arr.push8(true_color ? 1 : 0); // true-color - - arr.push16(255); // red-max - arr.push16(255); // green-max - arr.push16(255); // blue-max - arr.push8(16); // red-shift - arr.push8(8); // green-shift - arr.push8(0); // blue-shift - - arr.push8(0); // padding - arr.push8(0); // padding - arr.push8(0); // padding - return arr; + pixelFormat: function (sock, bpp, depth, true_color) { + var buff = sock._sQ; + var offset = sock._sQlen; + + buff[offset] = 0; // msg-type + + buff[offset + 1] = 0; // padding + buff[offset + 2] = 0; // padding + buff[offset + 3] = 0; // padding + + buff[offset + 4] = bpp * 8; // bits-per-pixel + buff[offset + 5] = depth * 8; // depth + buff[offset + 6] = 0; // little-endian + buff[offset + 7] = true_color ? 1 : 0; // true-color + + buff[offset + 8] = 0; // red-max + buff[offset + 9] = 255; // red-max + + buff[offset + 10] = 0; // green-max + buff[offset + 11] = 255; // green-max + + buff[offset + 12] = 0; // blue-max + buff[offset + 13] = 255; // blue-max + + buff[offset + 14] = 16; // red-shift + buff[offset + 15] = 8; // green-shift + buff[offset + 16] = 0; // blue-shift + + buff[offset + 17] = 0; // padding + buff[offset + 18] = 0; // padding + buff[offset + 19] = 0; // padding + + sock._sQlen += 20; }, - clientEncodings: function (encodings, local_cursor, true_color) { - var i, encList = []; + clientEncodings: function (sock, encodings, local_cursor, true_color) { + var buff = sock._sQ; + var offset = sock._sQlen; + buff[offset] = 2; // msg-type + buff[offset + 1] = 0; // padding + + // offset + 2 and offset + 3 are encoding count + + var i, j = offset + 4, cnt = 0; for (i = 0; i < encodings.length; i++) { if (encodings[i][0] === "Cursor" && !local_cursor) { Util.Debug("Skipping Cursor pseudo-encoding"); @@ -1344,23 +1377,25 @@ var RFB; // TODO: remove this when we have tight+non-true-color Util.Warn("Skipping tight as it is only supported with true color"); } else { - encList.push(encodings[i][1]); + var enc = encodings[i][1]; + buff[j] = enc >> 24; + buff[j + 1] = enc >> 16; + buff[j + 2] = enc >> 8; + buff[j + 3] = enc; + + j += 4; + cnt++; } } - var arr = [2]; // msg-type - arr.push8(0); // padding + buff[offset + 2] = cnt >> 8; + buff[offset + 3] = cnt; - arr.push16(encList.length); // encoding count - for (i = 0; i < encList.length; i++) { - arr.push32(encList[i]); - } - - return arr; + sock._sQlen += j - offset; }, - fbUpdateRequests: function (cleanDirty, fb_width, fb_height) { - var arr = []; + fbUpdateRequests: function (sock, cleanDirty, fb_width, fb_height) { + var offsetIncrement = 0; var cb = cleanDirty.cleanBox; var w, h; @@ -1368,7 +1403,7 @@ var RFB; w = typeof cb.w === "undefined" ? fb_width : cb.w; h = typeof cb.h === "undefined" ? fb_height : cb.h; // Request incremental for clean box - arr = arr.concat(RFB.messages.fbUpdateRequest(1, cb.x, cb.y, w, h)); + RFB.messages.fbUpdateRequest(sock, 1, cb.x, cb.y, w, h); } for (var i = 0; i < cleanDirty.dirtyBoxes.length; i++) { @@ -1376,24 +1411,33 @@ var RFB; // Force all (non-incremental) for dirty box w = typeof db.w === "undefined" ? fb_width : db.w; h = typeof db.h === "undefined" ? fb_height : db.h; - arr = arr.concat(RFB.messages.fbUpdateRequest(0, db.x, db.y, w, h)); + RFB.messages.fbUpdateRequest(sock, 0, db.x, db.y, w, h); } - - return arr; }, - fbUpdateRequest: function (incremental, x, y, w, h) { + fbUpdateRequest: function (sock, incremental, x, y, w, h) { + var buff = sock._sQ; + var offset = sock._sQlen; + if (typeof(x) === "undefined") { x = 0; } if (typeof(y) === "undefined") { y = 0; } - var arr = [3]; // msg-type - arr.push8(incremental); - arr.push16(x); - arr.push16(y); - arr.push16(w); - arr.push16(h); + buff[offset] = 3; // msg-type + buff[offset + 1] = incremental; + + buff[offset + 2] = (x >> 8) & 0xFF; + buff[offset + 3] = x & 0xFF; + + buff[offset + 4] = (y >> 8) & 0xFF; + buff[offset + 5] = y & 0xFF; + + buff[offset + 6] = (w >> 8) & 0xFF; + buff[offset + 7] = w & 0xFF; + + buff[offset + 8] = (h >> 8) & 0xFF; + buff[offset + 9] = h & 0xFF; - return arr; + sock._sQlen += 10; } }; diff --git a/include/websock.js b/include/websock.js index 71ae36c..61d9467 100644 --- a/include/websock.js +++ b/include/websock.js @@ -45,10 +45,14 @@ function Websock() { this._rQlen = 0; // Next write position in the receive queue this._rQbufferSize = 1024 * 1024 * 4; // Receive queue buffer size (4 MiB) this._rQmax = this._rQbufferSize / 8; - this._sQ = []; // Send queue // called in init: this._rQ = new Uint8Array(this._rQbufferSize); this._rQ = null; // Receive queue + this._sQbufferSize = 1024 * 10; // 10 KiB + // called in init: this._sQ = new Uint8Array(this._sQbufferSize); + this._sQlen = 0; + this._sQ = null; // Send queue + this._mode = 'binary'; // Current WebSocket mode: 'binary', 'base64' this.maxBufferedAmount = 200; @@ -183,9 +187,9 @@ function Websock() { } if (this._websocket.bufferedAmount < this.maxBufferedAmount) { - if (this._sQ.length > 0) { + if (this._sQlen > 0) { this._websocket.send(this._encode_message()); - this._sQ = []; + this._sQlen = 0; } return true; @@ -197,8 +201,9 @@ function Websock() { }, send: function (arr) { - this._sQ = this._sQ.concat(arr); - return this.flush(); + this._sQ.set(arr, this._sQlen); + this._sQlen += arr.length; + return this.flush(); }, send_string: function (str) { @@ -218,12 +223,12 @@ function Websock() { _allocate_buffers: function () { this._rQ = new Uint8Array(this._rQbufferSize); + this._sQ = new Uint8Array(this._sQbufferSize); }, init: function (protocols, ws_schema) { this._allocate_buffers(); this._rQi = 0; - this._sQ = []; this._websocket = null; // Check for full typed array support @@ -327,7 +332,8 @@ function Websock() { // private methods _encode_message: function () { // Put in a binary arraybuffer - return (new Uint8Array(this._sQ)).buffer; + // according to the spec, you can send ArrayBufferViews with the send method + return new Uint8Array(this._sQ.buffer, 0, this._sQlen); }, _decode_message: function (data) { diff --git a/tests/assertions.js b/tests/assertions.js index 6c01c4c..930e146 100644 --- a/tests/assertions.js +++ b/tests/assertions.js @@ -24,6 +24,12 @@ chai.use(function (_chai, utils) { _chai.Assertion.addMethod('sent', function (target_data) { var obj = this._obj; + obj.inspect = function () { + var res = { _websocket: obj._websocket, rQi: obj._rQi, _rQ: new Uint8Array(obj._rQ.buffer, 0, obj._rQlen), + _sQ: new Uint8Array(obj._sQ.buffer, 0, obj._sQlen) }; + res.prototype = obj; + return res; + }; var data = obj._websocket._get_sent_data(); var same = true; for (var i = 0; i < obj.length; i++) { @@ -38,8 +44,8 @@ chai.use(function (_chai, utils) { this.assert(same, "expected #{this} to have sent the data #{exp}, but it actually sent #{act}", "expected #{this} not to have sent the data #{act}", - target_data, - data); + Array.prototype.slice.call(target_data), + Array.prototype.slice.call(data)); }); _chai.Assertion.addProperty('array', function () { diff --git a/tests/fake.websocket.js b/tests/fake.websocket.js index 749c0ea..2101205 100644 --- a/tests/fake.websocket.js +++ b/tests/fake.websocket.js @@ -51,14 +51,9 @@ var FakeWebSocket; }, _get_sent_data: function () { - var arr = []; - for (var i = 0; i < this.bufferedAmount; i++) { - arr[i] = this._send_queue[i]; - } - + var res = new Uint8Array(this._send_queue.buffer, 0, this.bufferedAmount); this.bufferedAmount = 0; - - return arr; + return res; }, _open: function (data) { diff --git a/tests/test.rfb.js b/tests/test.rfb.js index f22314d..961d9eb 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -23,10 +23,12 @@ describe('Remote Frame Buffer Protocol Client', function() { // Use a single set of buffers instead of reallocating to // speed up tests var sock = new Websock(); + var _sQ = new Uint8Array(sock._sQbufferSize); var rQ = new Uint8Array(sock._rQbufferSize); Websock.prototype._old_allocate_buffers = Websock.prototype._allocate_buffers; Websock.prototype._allocate_buffers = function () { + this._sQ = _sQ; this._rQ = rQ; }; @@ -124,34 +126,34 @@ describe('Remote Frame Buffer Protocol Client', function() { client._sock = new Websock(); client._sock.open('ws://', 'binary'); client._sock._websocket._open(); - sinon.spy(client._sock, 'send'); + sinon.spy(client._sock, 'flush'); client._rfb_state = "normal"; client._view_only = false; }); it('should sent ctrl[down]-alt[down]-del[down] then del[up]-alt[up]-ctrl[up]', function () { - var expected = []; - expected = expected.concat(RFB.messages.keyEvent(0xFFE3, 1)); - expected = expected.concat(RFB.messages.keyEvent(0xFFE9, 1)); - expected = expected.concat(RFB.messages.keyEvent(0xFFFF, 1)); - expected = expected.concat(RFB.messages.keyEvent(0xFFFF, 0)); - expected = expected.concat(RFB.messages.keyEvent(0xFFE9, 0)); - expected = expected.concat(RFB.messages.keyEvent(0xFFE3, 0)); + var expected = {_sQ: new Uint8Array(48), _sQlen: 0}; + RFB.messages.keyEvent(expected, 0xFFE3, 1); + RFB.messages.keyEvent(expected, 0xFFE9, 1); + RFB.messages.keyEvent(expected, 0xFFFF, 1); + RFB.messages.keyEvent(expected, 0xFFFF, 0); + RFB.messages.keyEvent(expected, 0xFFE9, 0); + RFB.messages.keyEvent(expected, 0xFFE3, 0); client.sendCtrlAltDel(); - expect(client._sock).to.have.sent(expected); + expect(client._sock).to.have.sent(expected._sQ); }); it('should not send the keys if we are not in a normal state', function () { client._rfb_state = "broken"; client.sendCtrlAltDel(); - expect(client._sock.send).to.not.have.been.called; + expect(client._sock.flush).to.not.have.been.called; }); it('should not send the keys if we are set as view_only', function () { client._view_only = true; client.sendCtrlAltDel(); - expect(client._sock.send).to.not.have.been.called; + expect(client._sock.flush).to.not.have.been.called; }); }); @@ -160,34 +162,36 @@ describe('Remote Frame Buffer Protocol Client', function() { client._sock = new Websock(); client._sock.open('ws://', 'binary'); client._sock._websocket._open(); - sinon.spy(client._sock, 'send'); + sinon.spy(client._sock, 'flush'); client._rfb_state = "normal"; client._view_only = false; }); it('should send a single key with the given code and state (down = true)', function () { - var expected = RFB.messages.keyEvent(123, 1); + var expected = {_sQ: new Uint8Array(8), _sQlen: 0}; + RFB.messages.keyEvent(expected, 123, 1); client.sendKey(123, true); - expect(client._sock).to.have.sent(expected); + 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 = RFB.messages.keyEvent(123, 1); - expected = expected.concat(RFB.messages.keyEvent(123, 0)); + var expected = {_sQ: new Uint8Array(16), _sQlen: 0}; + RFB.messages.keyEvent(expected, 123, 1); + RFB.messages.keyEvent(expected, 123, 0); client.sendKey(123); - expect(client._sock).to.have.sent(expected); + expect(client._sock).to.have.sent(expected._sQ); }); it('should not send the key if we are not in a normal state', function () { client._rfb_state = "broken"; client.sendKey(123); - expect(client._sock.send).to.not.have.been.called; + expect(client._sock.flush).to.not.have.been.called; }); it('should not send the key if we are set as view_only', function () { client._view_only = true; client.sendKey(123); - expect(client._sock.send).to.not.have.been.called; + expect(client._sock.flush).to.not.have.been.called; }); }); @@ -196,21 +200,22 @@ describe('Remote Frame Buffer Protocol Client', function() { client._sock = new Websock(); client._sock.open('ws://', 'binary'); client._sock._websocket._open(); - sinon.spy(client._sock, 'send'); + sinon.spy(client._sock, 'flush'); client._rfb_state = "normal"; client._view_only = false; }); it('should send the given text in a paste event', function () { - var expected = RFB.messages.clientCutText('abc'); + var expected = {_sQ: new Uint8Array(11), _sQlen: 0}; + RFB.messages.clientCutText(expected, 'abc'); client.clipboardPasteFrom('abc'); - expect(client._sock).to.have.sent(expected); + expect(client._sock).to.have.sent(expected._sQ); }); it('should not send the text if we are not in a normal state', function () { client._rfb_state = "broken"; client.clipboardPasteFrom('abc'); - expect(client._sock.send).to.not.have.been.called; + expect(client._sock.flush).to.not.have.been.called; }); }); @@ -219,7 +224,7 @@ describe('Remote Frame Buffer Protocol Client', function() { client._sock = new Websock(); client._sock.open('ws://', 'binary'); client._sock._websocket._open(); - sinon.spy(client._sock, 'send'); + sinon.spy(client._sock, 'flush'); client._rfb_state = "normal"; client._view_only = false; client._supportsSetDesktopSize = true; @@ -240,19 +245,19 @@ describe('Remote Frame Buffer Protocol Client', function() { expected.push32(0); // flags client.setDesktopSize(1, 2); - expect(client._sock).to.have.sent(expected); + expect(client._sock).to.have.sent(new Uint8Array(expected)); }); it('should not send the request if the client has not recieved a ExtendedDesktopSize rectangle', function () { client._supportsSetDesktopSize = false; client.setDesktopSize(1,2); - expect(client._sock.send).to.not.have.been.called; + expect(client._sock.flush).to.not.have.been.called; }); it('should not send the request if we are not in a normal state', function () { client._rfb_state = "broken"; client.setDesktopSize(1,2); - expect(client._sock.send).to.not.have.been.called; + expect(client._sock.flush).to.not.have.been.called; }); }); @@ -261,7 +266,7 @@ describe('Remote Frame Buffer Protocol Client', function() { client._sock = new Websock(); client._sock.open('ws://', 'binary'); client._sock._websocket._open(); - sinon.spy(client._sock, 'send'); + sinon.spy(client._sock, 'flush'); client._rfb_state = "normal"; client._view_only = false; client._rfb_xvp_ver = 1; @@ -269,27 +274,27 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should send the shutdown signal on #xvpShutdown', function () { client.xvpShutdown(); - expect(client._sock).to.have.sent([0xFA, 0x00, 0x01, 0x02]); + expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x02])); }); it('should send the reboot signal on #xvpReboot', function () { client.xvpReboot(); - expect(client._sock).to.have.sent([0xFA, 0x00, 0x01, 0x03]); + expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x03])); }); it('should send the reset signal on #xvpReset', function () { client.xvpReset(); - expect(client._sock).to.have.sent([0xFA, 0x00, 0x01, 0x04]); + expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x04])); }); it('should support sending arbitrary XVP operations via #xvpOp', function () { client.xvpOp(1, 7); - expect(client._sock).to.have.sent([0xFA, 0x00, 0x01, 0x07]); + expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x07])); }); it('should not send XVP operations with higher versions than we support', function () { expect(client.xvpOp(2, 7)).to.be.false; - expect(client._sock.send).to.not.have.been.called; + expect(client._sock.flush).to.not.have.been.called; }); }); }); @@ -502,7 +507,7 @@ describe('Remote Frame Buffer Protocol Client', function() { expect(client._rfb_version).to.equal(0); var sent_data = client._sock._websocket._get_sent_data(); - expect(sent_data.slice(0, 5)).to.deep.equal([1, 2, 3, 4, 5]); + expect(new Uint8Array(sent_data.buffer, 0, 5)).to.array.equal(new Uint8Array([1, 2, 3, 4, 5])); }); it('should interpret version 003.003 as version 3.3', function () { @@ -559,7 +564,7 @@ describe('Remote Frame Buffer Protocol Client', function() { send_ver('000.000', client); expect(client._rfb_version).to.equal(0); var sent_data = client._sock._websocket._get_sent_data(); - expect(sent_data.slice(0, 5)).to.deep.equal([1, 2, 3, 4, 5]); + expect(new Uint8Array(sent_data.buffer, 0, 5)).to.array.equal(new Uint8Array([1, 2, 3, 4, 5])); expect(sent_data).to.have.length(250); send_ver('003.008', client); @@ -582,7 +587,7 @@ describe('Remote Frame Buffer Protocol Client', function() { expected[i] = expected_str.charCodeAt(i); } - expect(client._sock).to.have.sent(expected); + expect(client._sock).to.have.sent(new Uint8Array(expected)); }); it('should transition to the Security state on successful negotiation', function () { @@ -615,7 +620,7 @@ describe('Remote Frame Buffer Protocol Client', function() { var auth_schemes = [2, 1, 2]; client._sock._websocket._receive_data(auth_schemes); expect(client._rfb_auth_scheme).to.equal(2); - expect(client._sock).to.have.sent([2]); + expect(client._sock).to.have.sent(new Uint8Array([2])); }); it('should fail if there are no supported schemes for versions >= 3.7', function () { @@ -721,7 +726,7 @@ describe('Remote Frame Buffer Protocol Client', function() { client._sock._websocket._receive_data(new Uint8Array(challenge)); var des_pass = RFB.genDES('passwd', challenge); - expect(client._sock).to.have.sent(des_pass); + expect(client._sock).to.have.sent(new Uint8Array(des_pass)); }); it('should transition to SecurityResult immediately after sending the password', function () { @@ -778,7 +783,7 @@ describe('Remote Frame Buffer Protocol Client', function() { var expected = [22, 4, 6]; // auth selection, len user, len target for (var i = 0; i < 10; i++) { expected[i+3] = 'usertarget'.charCodeAt(i); } - expect(client._sock).to.have.sent(expected); + expect(client._sock).to.have.sent(new Uint8Array(expected)); }); }); @@ -826,14 +831,14 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should choose the notunnel tunnel type', function () { send_num_str_pairs([[0, 'TGHT', 'NOTUNNEL'], [123, 'OTHR', 'SOMETHNG']], client); - expect(client._sock).to.have.sent([0, 0, 0, 0]); + expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 0])); }); it('should continue to sub-auth negotiation after tunnel negotiation', function () { send_num_str_pairs([[0, 'TGHT', 'NOTUNNEL']], client); client._sock._websocket._get_sent_data(); // skip the tunnel choice here send_num_str_pairs([[1, 'STDV', 'NOAUTH__']], client); - expect(client._sock).to.have.sent([0, 0, 0, 1]); + expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 1])); expect(client._rfb_state).to.equal('SecurityResult'); }); @@ -849,7 +854,7 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should accept the "no auth" auth type and transition to SecurityResult', function () { client._rfb_tightvnc = true; send_num_str_pairs([[1, 'STDV', 'NOAUTH__']], client); - expect(client._sock).to.have.sent([0, 0, 0, 1]); + expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 1])); expect(client._rfb_state).to.equal('SecurityResult'); }); @@ -857,7 +862,7 @@ describe('Remote Frame Buffer Protocol Client', function() { client._rfb_tightvnc = true; client._negotiate_std_vnc_auth = sinon.spy(); send_num_str_pairs([[2, 'STDV', 'VNCAUTH__']], client); - expect(client._sock).to.have.sent([0, 0, 0, 2]); + expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 2])); expect(client._negotiate_std_vnc_auth).to.have.been.calledOnce; expect(client._rfb_auth_scheme).to.equal(2); }); @@ -921,13 +926,13 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should send 1 if we are in shared mode', function () { client.set_shared(true); client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0])); - expect(client._sock).to.have.sent([1]); + expect(client._sock).to.have.sent(new Uint8Array([1])); }); it('should send 0 if we are not in shared mode', function () { client.set_shared(false); client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0])); - expect(client._sock).to.have.sent([0]); + expect(client._sock).to.have.sent(new Uint8Array([0])); }); }); @@ -1064,21 +1069,16 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should reply with the pixel format, client encodings, and initial update request', function () { client.set_true_color(true); client.set_local_cursor(false); - var expected = RFB.messages.pixelFormat(4, 3, true); - expected = expected.concat(RFB.messages.clientEncodings(client._encodings, false, true)); + // we skip the cursor encoding + var expected = {_sQ: new Uint8Array(34 + 4 * (client._encodings.length - 1)), _sQlen: 0}; + 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 } ] }; - expected = expected.concat(RFB.messages.fbUpdateRequests(expected_cdr, 27, 32)); + RFB.messages.fbUpdateRequests(expected, expected_cdr, 27, 32); send_server_init({ width: 27, height: 32 }, client); - expect(client._sock).to.have.sent(expected); - }); - - it('should check for sending mouse events', function () { - // be lazy with our checking so we don't have to check through the whole sent buffer - sinon.spy(client, '_checkEvents'); - send_server_init({}, client); - expect(client._checkEvents).to.have.been.calledOnce; + expect(client._sock).to.have.sent(expected._sQ); }); it('should transition to the "normal" state', function () { @@ -1161,14 +1161,15 @@ 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 } ] }; - var expected_msg = RFB.messages.fbUpdateRequests(expected_cdr, 240, 20); + RFB.messages.fbUpdateRequests(expected_msg, expected_cdr, 240, 20); client._framebufferUpdate = function () { return true; }; client._sock._websocket._receive_data(new Uint8Array([0])); - expect(client._sock).to.have.sent(expected_msg); + expect(client._sock).to.have.sent(expected_msg._sQ); }); it('should not send an update request if we need more data', function () { @@ -1177,9 +1178,10 @@ 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 } ] }; - var expected_msg = RFB.messages.fbUpdateRequests(expected_cdr, 240, 20); + RFB.messages.fbUpdateRequests(expected_msg, expected_cdr, 240, 20); // just enough to set FBU.rects client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 3])); @@ -1188,7 +1190,7 @@ describe('Remote Frame Buffer Protocol Client', function() { client._framebufferUpdate = function () { return true; }; // we magically have enough data // 247 should *not* be used as the message type here client._sock._websocket._receive_data(new Uint8Array([247])); - expect(client._sock).to.have.sent(expected_msg); + expect(client._sock).to.have.sent(expected_msg._sQ); }); it('should parse out information from a header before any actual data comes in', function () { @@ -1710,58 +1712,61 @@ describe('Remote Frame Buffer Protocol Client', function() { var client; beforeEach(function () { client = make_rfb(); - client._sock.send = sinon.spy(); + client._sock = new Websock(); + client._sock.open('ws://', 'binary'); + client._sock._websocket._open(); + sinon.spy(client._sock, 'flush'); client._rfb_state = 'normal'; }); it('should not send button messages in view-only mode', function () { client._view_only = true; client._mouse._onMouseButton(0, 0, 1, 0x001); - expect(client._sock.send).to.not.have.been.called; + expect(client._sock.flush).to.not.have.been.called; }); it('should not send movement messages in view-only mode', function () { client._view_only = true; client._mouse._onMouseMove(0, 0); - expect(client._sock.send).to.not.have.been.called; + expect(client._sock.flush).to.not.have.been.called; }); it('should send a pointer event on mouse button presses', function () { client._mouse._onMouseButton(10, 12, 1, 0x001); - expect(client._sock.send).to.have.been.calledOnce; - var pointer_msg = RFB.messages.pointerEvent(10, 12, 0x001); - expect(client._sock.send).to.have.been.calledWith(pointer_msg); + var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0}; + 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); - expect(client._sock.send).to.have.been.calledOnce; - var pointer_msg = RFB.messages.pointerEvent(10, 12, 0x001); - expect(client._sock.send).to.have.been.calledWith(pointer_msg); + var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0}; + RFB.messages.pointerEvent(pointer_msg, 0, 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); - expect(client._sock.send).to.have.been.calledOnce; - var pointer_msg = RFB.messages.pointerEvent(10, 12, 0x000); - expect(client._sock.send).to.have.been.calledWith(pointer_msg); + var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0}; + 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); - expect(client._sock.send).to.have.been.calledOnce; - var pointer_msg = RFB.messages.pointerEvent(10, 12, 0); - expect(client._sock.send).to.have.been.calledWith(pointer_msg); + var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0}; + RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x000); + expect(client._sock).to.have.sent(pointer_msg._sQ); }); it('should set the button mask so that future mouse movements use it', function () { client._mouse._onMouseButton(10, 12, 1, 0x010); - client._sock.send = sinon.spy(); client._mouse._onMouseMove(13, 9); - expect(client._sock.send).to.have.been.calledOnce; - var pointer_msg = RFB.messages.pointerEvent(13, 9, 0x010); - expect(client._sock.send).to.have.been.calledWith(pointer_msg); + var pointer_msg = {_sQ: new Uint8Array(12), _sQlen: 0}; + 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); }); // NB(directxman12): we don't need to test not sending messages in @@ -1772,13 +1777,13 @@ describe('Remote Frame Buffer Protocol Client', function() { client._viewportDragging = true; client._display.viewportChangePos = sinon.spy(); client._mouse._onMouseMove(13, 9); - expect(client._sock.send).to.not.have.been.called; + expect(client._sock.flush).to.not.have.been.called; }); it('should not send button messages when initiating viewport dragging', function () { client._viewportDrag = true; client._mouse._onMouseButton(13, 9, 0x001); - expect(client._sock.send).to.not.have.been.called; + expect(client._sock.flush).to.not.have.been.called; }); it('should be initiate viewport dragging on a button down event, if enabled', function () { @@ -1814,20 +1819,23 @@ describe('Remote Frame Buffer Protocol Client', function() { var client; beforeEach(function () { client = make_rfb(); - client._sock.send = sinon.spy(); + client._sock = new Websock(); + client._sock.open('ws://', 'binary'); + client._sock._websocket._open(); + sinon.spy(client._sock, 'flush'); }); it('should send a key message on a key press', function () { client._keyboard._onKeyPress(1234, 1); - expect(client._sock.send).to.have.been.calledOnce; - var key_msg = RFB.messages.keyEvent(1234, 1); - expect(client._sock.send).to.have.been.calledWith(key_msg); + var key_msg = {_sQ: new Uint8Array(8), _sQlen: 0}; + RFB.messages.keyEvent(key_msg, 1234, 1); + expect(client._sock).to.have.sent(key_msg._sQ); }); it('should not send messages in view-only mode', function () { client._view_only = true; client._keyboard._onKeyPress(1234, 1); - expect(client._sock.send).to.not.have.been.called; + expect(client._sock.flush).to.not.have.been.called; }); }); diff --git a/tests/test.websock.js b/tests/test.websock.js index a81f75d..14d5783 100644 --- a/tests/test.websock.js +++ b/tests/test.websock.js @@ -173,7 +173,8 @@ describe('Websock', function() { it('should actually send on the websocket if the websocket does not have too much buffered', function () { sock.maxBufferedAmount = 10; sock._websocket.bufferedAmount = 8; - sock._sQ = [1, 2, 3]; + sock._sQ = new Uint8Array([1, 2, 3]); + sock._sQlen = 3; var encoded = sock._encode_message(); sock.flush(); @@ -189,7 +190,7 @@ describe('Websock', function() { }); it('should not call send if we do not have anything queued up', function () { - sock._sQ = []; + sock._sQlen = 0; sock.maxBufferedAmount = 10; sock._websocket.bufferedAmount = 8; @@ -215,7 +216,7 @@ describe('Websock', function() { it('should add to the send queue', function () { sock.send([1, 2, 3]); var sq = sock.get_sQ(); - expect(sock.get_sQ().slice(sq.length - 3)).to.deep.equal([1, 2, 3]); + expect(new Uint8Array(sq.buffer, sock._sQlen - 3, 3)).to.array.equal(new Uint8Array([1, 2, 3])); }); it('should call flush', function () { @@ -425,15 +426,16 @@ describe('Websock', function() { sock._websocket._open(); }); - it('should convert the send queue into an ArrayBuffer', function () { - sock._sQ = [1, 2, 3]; - var res = sock._encode_message(); // An ArrayBuffer - expect(new Uint8Array(res)).to.deep.equal(new Uint8Array(res)); + it('should only send the send queue up to the send queue length', function () { + sock._sQ = new Uint8Array([1, 2, 3, 4, 5]); + sock._sQlen = 3; + var res = sock._encode_message(); + expect(res).to.array.equal(new Uint8Array([1, 2, 3])); }); it('should properly pass the encoded data off to the actual WebSocket', function () { sock.send([1, 2, 3]); - expect(sock._websocket._get_sent_data()).to.deep.equal([1, 2, 3]); + expect(sock._websocket._get_sent_data()).to.array.equal(new Uint8Array([1, 2, 3])); }); }); }); -- 2.39.2