diff --git a/lib/structs.js b/lib/structs.js index f152493..364b47c 100644 --- a/lib/structs.js +++ b/lib/structs.js @@ -7,6 +7,83 @@ "use strict"; var assert = require("assert"); +var bufferEqual = require("buffer-equal"); +var bmcrypto = require("./crypto"); + +function isAscii(str) { + for (var i = 0; i < str.length; i++) { + if (str.charCodeAt(i) > 127) { + return false; + } + } + return true; +} + +function getchecksum(data) { + return bmcrypto.sha512(data).slice(0, 4); +} + +/** + * Message structure. + * @see {@link https://bitmessage.org/wiki/Protocol_specification#Message_structure} + * @namespace + * @static + */ +var message = exports.message = { + /** Bitmessage magic value. */ + MAGIC: 0xE9BEB4D9, + + /** + * Decode message structure. + * @param {Buffer} buf - Buffer that starts with encoded message + * structure + * @return {{command: string, payload: Buffer, length: number, rest: Buffer}} + * Decoded message structure. + */ + decode: function(buf) { + assert(buf.length >= 24, "Buffer is too small"); + assert(buf.readUInt32BE(0, true) === message.MAGIC, "Wrong magic"); + var command = buf.slice(4, 16); + var firstNonNull; + for (var i = 11; i >=0; i--) { + assert(command[i] <= 127, "Non-ASCII characters in command"); + if (firstNonNull === undefined && command[i] !== 0) { + firstNonNull = i; + } + } + command = command.slice(0, firstNonNull + 1).toString("ascii"); + var payloadLength = buf.readUInt32BE(16, true); + assert(payloadLength <= 262144, "Payload is too big"); + var checksum = buf.slice(20, 24); + var length = 24 + payloadLength; + var payload = buf.slice(24, length); + assert(bufferEqual(checksum, getchecksum(payload)), "Bad checkum"); + var rest = buf.slice(length); + return {command: command, payload: payload, length: length, rest: rest}; + }, + + /** + * Encode message structure. + * @param {string} command - ASCII string identifying the packet + * content + * @param {Buffer} payload - The actual data, a message or an object + * @return {Buffer} Encoded message structure. + */ + encode: function(command, payload) { + assert(command.length <= 12, "Command is too long"); + assert(isAscii(command), "Non-ASCII characters in command"); + assert(payload.length <= 262144, "Payload is too big"); + var buf = new Buffer(24 + payload.length); + buf.fill(0); + buf.writeUInt32BE(message.MAGIC, 0, true); + buf.write(command, 4); + buf.writeUInt32BE(payload.length, 16, true); + getchecksum(payload).copy(buf, 20); + payload.copy(buf, 24); + return buf; + }, +}; + /** * var_int. diff --git a/test.js b/test.js index 035b7c6..b741f22 100644 --- a/test.js +++ b/test.js @@ -5,6 +5,7 @@ var allTests = typeof window === "undefined" ? var bmcrypto = require("./lib/crypto"); var bitmessage = require("./lib"); +var message = bitmessage.structs.message; var var_int = bitmessage.structs.var_int; var var_str = bitmessage.structs.var_str; var var_int_list = bitmessage.structs.var_int_list; @@ -49,6 +50,21 @@ describe("Crypto", function() { }); describe("Common structures", function() { + describe("message", function() { + it("should encode", function() { + expect(message.encode("test", Buffer("payload")).toString("hex")).to.equal("e9beb4d97465737400000000000000000000000770b33ce97061796c6f6164"); + }); + + it("should decode", function() { + var res; + res = message.decode(Buffer("e9beb4d97465737400000000000000000000000770b33ce97061796c6f6164", "hex")); + expect(res.command).to.equal("test"); + expect(res.payload.toString()).to.equal("payload"); + expect(res.length).to.equal(31); + expect(res.rest.toString("hex")).to.equal(""); + }); + }); + describe("var_int", function() { it("should decode", function() { var res;