diff --git a/README.md b/README.md index 1987e3b..6d37c93 100644 --- a/README.md +++ b/README.md @@ -33,8 +33,8 @@ API documentation is available [here](https://bitchan.github.io/bitmessage/docs/ - [ ] net_addr - [ ] encrypted - [x] message encodings - - [ ] service features - - [ ] pubkey features + - [x] service features + - [x] pubkey features - [ ] Message types - [ ] version - [ ] verack diff --git a/lib/structs.js b/lib/structs.js index 07f5c30..ae13c98 100644 --- a/lib/structs.js +++ b/lib/structs.js @@ -251,18 +251,84 @@ exports.var_int_list = { }, }; -exports.messageEncodings = Object.create(var_int); -Object.assign(exports.messageEncodings, { +/** + * Message encodings. Extends {@link var_int} by adding known encoding type + * constants. + * @see {@link https://bitmessage.org/wiki/Protocol_specification#Message_Encodings} + */ +exports.messageEncodings = Object.assign(Object.create(var_int), { + /** + * Any data with this number may be ignored. The sending node might + * simply be sharing its public key with you. + */ IGNORE: 0, + /** + * UTF-8. No 'Subject' or 'Body' sections. Useful for simple strings + * of data, like URIs or magnet links. + */ TRIVIAL: 1, + /** + * UTF-8. Uses 'Subject' and 'Body' sections. No MIME is used. + */ SIMPLE: 2, }); -exports.serviceFeatures = { - NODE_NETWORK: 1, +// Creates bitfield class of the specified size. +var bitfield = function(size) { + var bytesize = size / 8; + return { + decode: function(buf) { + assert(buf.length === bytesize, "Bad buffer size"); + var features = []; + var index; + for (var i = 0; i < size; i++) { + index = bytesize - Math.floor(i / 8) - 1; + if ((buf[index] & (1 << (i % 8))) !== 0) { // jshint ignore:line + features.push(i); + } + } + return features; + }, + + encode: function(features) { + var buf = new Buffer(bytesize); + buf.fill(0); + features.forEach(function(feature) { + assert(feature >= 0, "Bad feature"); + assert(feature <= (size - 1), "Bad feature"); + var index = bytesize - Math.floor(feature / 8) - 1; + buf[index] |= 1 << (feature % 8); // jshint ignore:line + }); + return buf; + }, + }; }; -exports.pubkeyFeatures = { +/** + * Service bitfield features. Implements encoding/decoding for a 8-byte + * buffer object. + * @see {@link https://bitmessage.org/wiki/Protocol_specification#version} + */ +exports.serviceFeatures = Object.assign(bitfield(64), { + /** This is a normal network node. */ + NODE_NETWORK: 0, +}); + +/** + * Pubkey bitfield features. Implements encoding/decoding for a 4-byte + * buffer object. + * @see {@link https://bitmessage.org/wiki/Protocol_specification#Pubkey_bitfield_features} + */ +exports.pubkeyFeatures = Object.assign(bitfield(32), { + /** + * Receiving node expects that the RIPE hash encoded in their address + * preceedes the encrypted message data of msg messages bound for + * them. + */ INCLUDE_DESTINATION: 30, + /** + * If true, the receiving node does send acknowledgements (rather than + * dropping them). + */ DOES_ACK: 31, -}; +}); diff --git a/test.js b/test.js index b741f22..b6d9148 100644 --- a/test.js +++ b/test.js @@ -5,10 +5,14 @@ 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; +var structs = bitmessage.structs; +var message = structs.message; +var var_int = structs.var_int; +var var_str = structs.var_str; +var var_int_list = structs.var_int_list; +var messageEncodings = structs.messageEncodings; +var serviceFeatures = structs.serviceFeatures; +var pubkeyFeatures = structs.pubkeyFeatures; var WIF = bitmessage.WIF; var Address = bitmessage.Address; @@ -166,6 +170,36 @@ describe("Common structures", function() { expect(var_int_list.encode([1, 1024, 1125899906842624, 40000, 100000]).toString("hex")).to.equal("0501fd0400ff0004000000000000fd9c40fe000186a0"); }); }); + + describe("Message encodings", function() { + it("should decode", function() { + expect(messageEncodings.decode(Buffer([2])).value).to.equal(messageEncodings.SIMPLE); + }); + + it("should encode", function() { + expect(messageEncodings.encode(messageEncodings.SIMPLE).toString("hex")).to.equal("02"); + }); + }); + + describe("Service features", function() { + it("should decode", function() { + expect(serviceFeatures.decode(Buffer("0000000000000001", "hex"))).to.have.members([serviceFeatures.NODE_NETWORK]); + }); + + it("should encode", function() { + expect(serviceFeatures.encode([serviceFeatures.NODE_NETWORK]).toString("hex")).to.equal("0000000000000001"); + }); + }); + + describe("Pubkey features", function() { + it("should decode", function() { + expect(pubkeyFeatures.decode(Buffer("c0000000", "hex"))).to.have.members([pubkeyFeatures.DOES_ACK, pubkeyFeatures.INCLUDE_DESTINATION]); + }); + + it("should encode", function() { + expect(pubkeyFeatures.encode([pubkeyFeatures.INCLUDE_DESTINATION, pubkeyFeatures.DOES_ACK]).toString("hex")).to.equal("c0000000"); + }); + }); }); describe("WIF", function() {