diff --git a/README.md b/README.md index c76ab4e..4608b52 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ API documentation is available [here](https://bitchan.github.io/bitmessage/docs/ - [x] inv - [x] getdata - [ ] error - - [ ] object + - [x] object - [ ] Object types - [ ] getpubkey - [ ] pubkey diff --git a/lib/messages.js b/lib/messages.js index 71d06c5..3994187 100644 --- a/lib/messages.js +++ b/lib/messages.js @@ -68,6 +68,7 @@ exports.version = { var services = opts.services || [structs.serviceFeatures.NODE_NETWORK]; var time = opts.time || new Date(); var nonce = opts.nonce || exports.version.NONCE; + assert(nonce.length === 8, "Bad nonce"); var software = opts.software || UserAgent.SELF; var streamNumbers = opts.streamNumbers || [1]; // Start encoding. @@ -190,3 +191,66 @@ var inv = exports.inv = { * @namespace */ exports.getdata = inv; + +/** + * `object` message. An `object` is a message which is shared throughout + * a stream. It is the only message which propagates; all others are + * only between two nodes. + * @see {@link https://bitmessage.org/wiki/Protocol_specification#object} + * @namespace + */ +exports.object = { + /** + * Decode `object` payload. + * NOTE: `nonce` and `payload` are copied. + * @param {Buffer} buf - Buffer that starts with encoded `object` + * payload + * @return {Object} Decoded `object` structure. + */ + decode: function(buf) { + // 8 + 8 + 4 + (1+) + (1+) + assert(buf.length >= 22, "Buffer is too small"); + var nonce = new Buffer(8); + buf.copy(nonce, 0, 0, 8); + var expiresTime = util.readTimestamp64BE(buf.slice(8, 16)); + var ttl = expiresTime - util.tnow(); + assert(ttl >= -3600, "Object expired more than a hour ago"); + assert(ttl <= 2430000, "expiresTime is too far in the future"); + var type = buf.readUInt32BE(16); + var decodedVersion = structs.var_int.decode(buf.slice(20)); + var decodedStream = structs.var_int.decode(decodedVersion.rest); + var payload = new Buffer(decodedStream.rest.length); + decodedStream.rest.copy(payload); + return { + nonce: nonce, + ttl: ttl, + type: type, + version: decodedVersion.value, + stream: decodedStream.value, + payload: payload, + }; + }, + + /** + * Encode `object` payload. + * @param {Object} opts - Object options + * @return {Buffer} Encoded `object` payload. + */ + encode: function(opts) { + assert(opts.nonce.length === 8, "Bad nonce"); + assert(opts.ttl > 0, "Bad TTL"); + assert(opts.ttl <= 2430000, "TTL may not be larger than 28 days + 3 hours"); + var expiresTime = util.tnow() + opts.ttl; + var type = new Buffer(4); + type.writeUInt32BE(opts.type, 0); + var stream = opts.stream || 1; + return Buffer.concat([ + opts.nonce, + util.writeUInt64BE(null, expiresTime), + type, + structs.var_int.encode(opts.version), + structs.var_int.encode(stream), + opts.payload, + ]); + }, +}; diff --git a/lib/util.js b/lib/util.js index fad58d0..0ee87a8 100644 --- a/lib/util.js +++ b/lib/util.js @@ -56,3 +56,8 @@ exports.writeTime64BE = function(buf, time, offset, noAssert) { var timestamp = Math.floor(time.getTime() / 1000); return writeUInt64BE(buf, timestamp, offset, noAssert); }; + +exports.tnow = function() { + var time = new Date(); + return Math.floor(time.getTime() / 1000); +}; diff --git a/test.js b/test.js index 44c601b..9d17ef6 100644 --- a/test.js +++ b/test.js @@ -21,6 +21,7 @@ var messages = bitmessage.messages; var version = messages.version; var addr = messages.addr; var inv = messages.inv; +var object = messages.object; var WIF = bitmessage.WIF; var POW = bitmessage.POW; var Address = bitmessage.Address; @@ -347,11 +348,41 @@ describe("Message types", function() { expect(res.length).to.equal(65); }); - it("shouldn't encode/decode more than 1000 entires", function() { + it("shouldn't encode/decode more than 50000 entires", function() { expect(inv.encode.bind(null, Array(60000))).to.throw(/too many/i); expect(inv.decode.bind(null, var_int.encode(60000))).to.throw(/too many/i); }); }); + + describe("object", function() { + it("should encode and decode", function() { + var nonce = Buffer(8); + var res = object.decode(object.encode({ + nonce: nonce, + ttl: 100, + type: 2, + version: 1, + payload: Buffer("test"), + })); + + expect(bufferEqual(nonce, res.nonce)).to.be.true; + expect(res.ttl).to.be.at.least(100); + expect(res.type).to.equal(2); + expect(res.version).to.equal(1); + expect(res.stream).to.equal(1); + expect(res.payload.toString()).to.equal("test"); + }); + + it("shouldn't encode too big TTL", function() { + expect(object.encode.bind(null, { + nonce: Buffer(8), + ttl: 10000000, + type: 2, + version: 1, + payload: Buffer("test"), + })).to.throw(Error); + }); + }); }); describe("WIF", function() {