From e15bdecfcbee22850ab622abe9b7e42db0acfb38 Mon Sep 17 00:00:00 2001 From: Kagami Hiiragi Date: Fri, 6 Feb 2015 20:44:27 +0300 Subject: [PATCH] Decode messages in stream mode --- lib/objects.js | 10 ++-- lib/structs.js | 128 +++++++++++++++++++++++++++++++++++++++++++++++++ tests/unit.js | 31 ++++++++++++ 3 files changed, 165 insertions(+), 4 deletions(-) diff --git a/lib/objects.js b/lib/objects.js index e94c3d1..1943dfc 100644 --- a/lib/objects.js +++ b/lib/objects.js @@ -437,8 +437,9 @@ var pubkey = exports.pubkey = { function tryDecryptMsg(identities, buf) { function inner(i) { if (i > last) { - var err = new Error("Failed to decrypt msg with given identities"); - return PPromise.reject(err); + return PPromise.reject( + new Error("Failed to decrypt msg with given identities") + ); } return bmcrypto .decrypt(identities[i].encPrivateKey, buf) @@ -748,8 +749,9 @@ var DEFAULT_ENCODING = msg.TRIVIAL; function tryDecryptBroadcastV4(subscriptions, buf) { function inner(i) { if (i > last) { - var err = new Error("Failed to decrypt broadcast with given identities"); - return PPromise.reject(err); + return PPromise.reject( + new Error("Failed to decrypt broadcast with given identities") + ); } return bmcrypto .decrypt(subscriptions[i].getBroadcastPrivateKey(), buf) diff --git a/lib/structs.js b/lib/structs.js index 005ed1f..6c9eb96 100644 --- a/lib/structs.js +++ b/lib/structs.js @@ -28,6 +28,48 @@ function getmsgchecksum(data) { return bmcrypto.sha512(data).slice(0, 4); } +// \ :3 / +function findMagic(buf) { + var i; + var len = buf.length; + var firstb = false; + var secondb = false; + var thirdb = false; + for (i = 0; i < len; ++i) { + switch (buf[i]) { + case 0xE9: + firstb = true; + break; + case 0xBE: + if (firstb) { secondb = true; } + break; + case 0xB4: + if (firstb && secondb) { thirdb = true; } + break; + case 0xD9: + if (firstb && secondb && thirdb) { return i - 3; } + break; + default: + firstb = false; + secondb = false; + thirdb = false; + } + } + // If we reached the end of the buffer but part of the magic matches + // we'll still return index of the magic's start position. + if (firstb) { + if (secondb) { + --i; + } + if (thirdb) { + --i; + } + return i - 1; // Compensate for last i's increment + } else { + return -1; + } +} + /** * Message structure. * @see {@link https://bitmessage.org/wiki/Protocol_specification#Message_structure} @@ -41,6 +83,92 @@ var message = exports.message = { */ MAGIC: 0xE9BEB4D9, + /** + * Decode message in "stream" mode. + * NOTE: message payload and `rest` are copied (so the runtime can GC + * processed buffer data). + * @param {Buffer} buf - Data buffer + * @return {?{?message: Object, ?error: Error, rest: Buffer}} Decoded + * result. + */ + tryDecode: function(buf) { + if (buf.length < 24) { + // Message is not yet fully received, just skip to next process + // cycle. + return; + } + var res = {}; + + // Magic. + var mindex = findMagic(buf); + if (mindex !== 0) { + if (mindex === -1) { + res.error = new Error("Magic not found, skipping buffer data"); + res.rest = new Buffer(0); + } else { + res.error = new Error( + "Magic in the middle of buffer, skipping some data at start" + ); + res.rest = new Buffer(buf.length - mindex); + buf.copy(res.rest, 0, mindex); + } + return res; + } + + // Payload length. + var payloadLength = buf.readUInt32BE(16, true); + var msgLength = 24 + payloadLength; + // See also: . + if (payloadLength > 1600003) { + res.error = new Error("Message is too large, skipping it"); + if (buf.length > msgLength) { + res.rest = new Buffer(buf.length - msgLength); + buf.copy(res.rest, 0, msgLength); + } else { + res.rest = new Buffer(0); + } + return res; + } + if (buf.length < msgLength) { + // Message is not yet fully received, just skip to next process + // cycle. + return; + } + + // Now we can set `rest` value. + res.rest = new Buffer(buf.length - msgLength); + buf.copy(res.rest, 0, msgLength); + + // Command. + var command = buf.slice(4, 16); + var firstNonNull = 0; + var i; + for (i = 11; i >=0; i--) { + if (command[i] > 127) { + res.error = new Error( + "Non-ASCII characters in command, skipping message" + ); + return res; + } + if (!firstNonNull && command[i] !== 0) { + firstNonNull = i + 1; + } + } + command = command.slice(0, firstNonNull).toString("ascii"); + + // Payload. + var payload = new Buffer(payloadLength); + buf.copy(payload, 0, 24, msgLength); + var checksum = buf.slice(20, 24); + if (!bufferEqual(checksum, getmsgchecksum(payload))) { + res.error = new Error("Bad checksum, skipping message"); + return res; + } + + res.message = {command: command, payload: payload, length: msgLength}; + return res; + }, + /** * Decode message structure. * NOTE: `payload` is copied, `rest` references input buffer. diff --git a/tests/unit.js b/tests/unit.js index 0d5340f..c8f739b 100644 --- a/tests/unit.js +++ b/tests/unit.js @@ -142,6 +142,37 @@ describe("Common structures", function() { expect(res.command).to.equal("ping"); expect(res.payload.toString("hex")).to.equal(""); }); + + it("should decode messages in stream mode", function() { + var res = message.tryDecode(Buffer("")); + expect(res).to.not.exist; + + res = message.tryDecode(Buffer(25)); + expect(res.error).to.match(/magic not found/i); + expect(res.rest.toString("hex")).to.equal(""); + expect(res).to.not.have.property("message"); + + res = message.tryDecode(message.encode("test", Buffer([1,2,3]))); + expect(res).to.not.have.property("error"); + expect(res.message.command).to.equal("test"); + expect(res.message.payload.toString("hex")).to.equal("010203"); + expect(res.rest.toString("hex")).to.equal(""); + + var encoded = message.encode("cmd", Buffer("buf")); + encoded[20] ^= 1; // Corrupt checksum + encoded = Buffer.concat([encoded, Buffer("rest")]); + res = message.tryDecode(encoded); + expect(res.error).to.match(/bad checksum/i); + expect(res.rest.toString()).to.equal("rest"); + expect(res).to.not.have.property("message"); + + encoded = Buffer.concat([Buffer(10), encoded]); + res = message.tryDecode(encoded); + expect(res.error).to.match(/magic in the middle/i); + expect(res.rest).to.have.length(31); + expect(res.rest.readUInt32BE(0)).to.equal(message.MAGIC); + expect(res).to.not.have.property("message"); + }); }); describe("object", function() {