Implement net_addr

This commit is contained in:
Kagami Hiiragi 2015-01-06 02:32:10 +03:00
parent 6fb61e72b6
commit 0a6aa667db
3 changed files with 213 additions and 20 deletions

View File

@ -30,7 +30,7 @@ API documentation is available [here](https://bitchan.github.io/bitmessage/docs/
- [x] var_int - [x] var_int
- [x] var_str - [x] var_str
- [x] var_int_list - [x] var_int_list
- [ ] net_addr - [x] net_addr
- [ ] encrypted - [ ] encrypted
- [x] message encodings - [x] message encodings
- [x] service features - [x] service features
@ -57,6 +57,7 @@ API documentation is available [here](https://bitchan.github.io/bitmessage/docs/
- [x] getRipe - [x] getRipe
- [x] fromRandom - [x] fromRandom
- [ ] fromPassphrase - [ ] fromPassphrase
- [ ] UserAgent
- [ ] Message - [ ] Message
- [ ] encrypt - [ ] encrypt
- [ ] decrypt - [ ] decrypt

View File

@ -87,17 +87,17 @@ var message = exports.message = {
/** /**
* var_int. * Variable length integer.
* @see {@link https://bitmessage.org/wiki/Protocol_specification#Variable_length_integer} * @see {@link https://bitmessage.org/wiki/Protocol_specification#Variable_length_integer}
* @namespace * @namespace
* @static * @static
*/ */
var var_int = exports.var_int = { var var_int = exports.var_int = {
/** /**
* Decode var_int. * Decode `var_int`.
* @param {Buffer} buf - A buffer that starts with encoded var_int * @param {Buffer} buf - A buffer that starts with encoded `var_int`
* @return {{value: number, length: number, rest: Buffer}} * @return {{value: number, length: number, rest: Buffer}}
* Decoded var_int structure. * Decoded `var_int` structure.
*/ */
decode: function(buf) { decode: function(buf) {
var value, length; var value, length;
@ -140,9 +140,9 @@ var var_int = exports.var_int = {
}, },
/** /**
* Encode number into var_int. * Encode number into `var_int`.
* @param {(number|Buffer)} value - Input number * @param {(number|Buffer)} value - Input number
* @return {Buffer} Encoded var_int. * @return {Buffer} Encoded `var_int`.
*/ */
encode: function(value) { encode: function(value) {
var buf, targetStart; var buf, targetStart;
@ -180,16 +180,16 @@ var var_int = exports.var_int = {
}; };
/** /**
* var_str. * Variable length string.
* @see {@link https://bitmessage.org/wiki/Protocol_specification#Variable_length_string} * @see {@link https://bitmessage.org/wiki/Protocol_specification#Variable_length_string}
* @namespace * @namespace
*/ */
exports.var_str = { exports.var_str = {
/** /**
* Decode var_str. * Decode `var_str`.
* @param {Buffer} buf - A buffer that starts with encoded var_str * @param {Buffer} buf - A buffer that starts with encoded `var_str`
* @return {{str: string, length: number, rest: Buffer}} * @return {{str: string, length: number, rest: Buffer}}
* Decoded var_str structure. * Decoded `var_str` structure.
*/ */
decode: function(buf) { decode: function(buf) {
var decoded = var_int.decode(buf); var decoded = var_int.decode(buf);
@ -201,9 +201,9 @@ exports.var_str = {
}, },
/** /**
* Encode string into var_str. * Encode string into `var_str`.
* @param {string} str - A string * @param {string} str - A string
* @return {Buffer} Encoded var_str. * @return {Buffer} Encoded `var_str`.
*/ */
encode: function(str) { encode: function(str) {
// XXX(Kagami): Spec doesn't mention encoding, using UTF-8. // XXX(Kagami): Spec doesn't mention encoding, using UTF-8.
@ -213,17 +213,17 @@ exports.var_str = {
}; };
/** /**
* var_int_list. * Variable length list of integers.
* @see {@link https://bitmessage.org/wiki/Protocol_specification#Variable_length_list_of_integers} * @see {@link https://bitmessage.org/wiki/Protocol_specification#Variable_length_list_of_integers}
* @namespace * @namespace
*/ */
exports.var_int_list = { exports.var_int_list = {
/** /**
* Decode var_int_list. * Decode `var_int_list`.
* @param {Buffer} buf - A buffer that starts with encoded * @param {Buffer} buf - A buffer that starts with encoded
* var_int_list * `var_int_list`
* @return {{list: number[], length: number, rest: Buffer}} * @return {{list: number[], length: number, rest: Buffer}}
* Decoded var_int_list structure. * Decoded `var_int_list` structure.
*/ */
decode: function(buf) { decode: function(buf) {
var decoded = var_int.decode(buf); var decoded = var_int.decode(buf);
@ -241,9 +241,9 @@ exports.var_int_list = {
}, },
/** /**
* Encode list of numbers into var_int_list. * Encode list of numbers into `var_int_list`.
* @param {number[]} list - A number list * @param {number[]} list - A number list
* @return {Buffer} Encoded var_int_list. * @return {Buffer} Encoded `var_int_list`.
*/ */
encode: function(list) { encode: function(list) {
var listBuf = Buffer.concat(list.map(var_int.encode)); var listBuf = Buffer.concat(list.map(var_int.encode));
@ -251,10 +251,164 @@ exports.var_int_list = {
}, },
}; };
// See https://en.wikipedia.org/wiki/IPv6#IPv4-mapped_IPv6_addresses
var IPv4_MAPPING = new Buffer([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255]);
// Very simple equivalent of inet_ntop(3).
function inet_ntop(buf) {
assert(buf.length === 16, "Bad buffer size");
// IPv4 mapped to IPv6.
if (bufferEqual(buf.slice(0, 12), IPv4_MAPPING)) {
return Array.prototype.join.call(buf.slice(12), ".");
// IPv6.
} else {
var groups = [];
for (var i = 0; i < 8; i++) {
groups.push(buf.readUInt16BE(i * 2, true).toString(16));
}
return groups.join(":");
}
}
// Very simple equivalent of inet_pton(3).
function inet_pton(str) {
var buf = new Buffer(16);
buf.fill(0);
// IPv4.
if (str.indexOf(":") === -1) {
IPv4_MAPPING.copy(buf);
var octets = str.split(/\./g).map(function(s) {return parseInt(s, 10);});
// Support short form from inet_aton(3) man page.
if (octets.length === 1) {
buf.writeUInt32BE(octets[0], 12);
} else {
// To check against 1000.bad.addr
octets.forEach(function(octet) {
assert(octet >= 0, "Bad IPv4 address");
assert(octet <= 255, "Bad IPv4 address");
});
if (octets.length === 2) {
buf[12] = octets[0];
buf[15] = octets[1];
} else if (octets.length === 3) {
buf[12] = octets[0];
buf[13] = octets[1];
buf[15] = octets[2];
} else if (octets.length === 4) {
buf[12] = octets[0];
buf[13] = octets[1];
buf[14] = octets[2];
buf[15] = octets[3];
} else {
throw new Error("Bad IPv4 address");
}
}
// IPv6.
} else {
var dgroups = str.split(/::/g);
// To check against 1::1::1
assert(dgroups.length <= 2, "Bad IPv6 address");
var groups = [];
var i;
if (dgroups[0]) {
groups.push.apply(groups, dgroups[0].split(/:/g));
}
if (dgroups.length === 2) {
if (dgroups[1]) {
var splitted = dgroups[1].split(/:/g);
var fill = 8 - (groups.length + splitted.length);
// To check against 1:1:1:1::1:1:1:1
assert(fill > 0, "Bad IPv6 address");
for (i = 0; i < fill; i++) {
groups.push(0);
}
groups.push.apply(groups, splitted);
} else {
// To check against 1:1:1:1:1:1:1:1::
assert(groups.length <= 7, "Bad IPv6 address");
}
} else {
// To check against 1:1:1
assert(groups.length === 8, "Bad IPv6 address");
}
for (i = 0; i < Math.min(groups.length, 8); i++) {
buf.writeUInt16BE(parseInt(groups[i], 16), i * 2);
}
}
return buf;
}
/**
* Network address.
* @see {@link https://bitmessage.org/wiki/Protocol_specification#Network_address}
* @namespace
*/
exports.net_addr = {
/**
* Decode `net_addr`.
* @param {Buffer} buf - A buffer that contains encoded `net_addr`
* @param {?Object} opts - Decode options; use `short` option to
* decode `net_addr` used in version message
* @return {Object} Decoded `net_addr` structure.
*/
decode: function(buf, opts) {
var short = !!(opts || {}).short;
var res = {};
if (short) {
assert(buf.length === 26, "Bad buffer size");
} else {
assert(buf.length === 38, "Bad buffer size");
var timeHi = buf.readUInt32BE(0, true);
var timeLo = buf.readUInt32BE(4, true);
// JavaScript's Date object can't work with timestamps higher than
// 8640000000000 (~2^43, ~275760 year).
assert(timeHi <= 2011, "Time is too high");
assert(timeHi !== 2011 || timeLo <= 2820767744, "Time is too high");
res.time = new Date((timeHi * 4294967296 + timeLo) * 1000);
res.stream = buf.readUInt32BE(8, true);
buf = buf.slice(12);
}
res.services = serviceFeatures.decode(buf.slice(0, 8));
res.host = inet_ntop(buf.slice(8, 24));
res.port = buf.readUInt16BE(24, true);
return res;
},
/**
* Encode `net_addr`.
* @param {Object} opts - Encode options; use `short` option to encode
* `net_addr` used in version message
* @return {Buffer} Encoded `net_addr`.
*/
encode: function(opts) {
// Be aware of `Buffer.slice` quirk in browserify:
// <http://git.io/lNZF1A> (does not modify parent buffer's memory in
// old browsers).
var buf, shift;
if (opts.short) {
buf = new Buffer(26);
shift = 0;
} else {
buf = new Buffer(38);
var time = opts.time || new Date();
time = Math.floor(time.getTime() / 1000);
buf.writeUInt32BE(Math.floor(time / 4294967296), 0, true); // high32
buf.writeUInt32BE(time % 4294967296, 4, true); // low32
buf.writeUInt32BE(opts.stream, 8);
shift = 12;
}
serviceFeatures.encode(opts.services).copy(buf, shift);
inet_pton(opts.host).copy(buf, shift + 8);
buf.writeUInt16BE(opts.port, shift + 24);
return buf;
},
};
/** /**
* Message encodings. Extends {@link var_int} by adding known encoding type * Message encodings. Extends {@link var_int} by adding known encoding type
* constants. * constants.
* @see {@link https://bitmessage.org/wiki/Protocol_specification#Message_Encodings} * @see {@link https://bitmessage.org/wiki/Protocol_specification#Message_Encodings}
* @namespace
*/ */
exports.messageEncodings = Object.assign(Object.create(var_int), { exports.messageEncodings = Object.assign(Object.create(var_int), {
/** /**
@ -308,8 +462,10 @@ var bitfield = function(size) {
* Service bitfield features. Implements encoding/decoding for a 8-byte * Service bitfield features. Implements encoding/decoding for a 8-byte
* buffer object. * buffer object.
* @see {@link https://bitmessage.org/wiki/Protocol_specification#version} * @see {@link https://bitmessage.org/wiki/Protocol_specification#version}
* @namespace
* @static
*/ */
exports.serviceFeatures = Object.assign(bitfield(64), { var serviceFeatures = exports.serviceFeatures = Object.assign(bitfield(64), {
/** This is a normal network node. */ /** This is a normal network node. */
NODE_NETWORK: 0, NODE_NETWORK: 0,
}); });
@ -318,6 +474,7 @@ exports.serviceFeatures = Object.assign(bitfield(64), {
* Pubkey bitfield features. Implements encoding/decoding for a 4-byte * Pubkey bitfield features. Implements encoding/decoding for a 4-byte
* buffer object. * buffer object.
* @see {@link https://bitmessage.org/wiki/Protocol_specification#Pubkey_bitfield_features} * @see {@link https://bitmessage.org/wiki/Protocol_specification#Pubkey_bitfield_features}
* @namespace
*/ */
exports.pubkeyFeatures = Object.assign(bitfield(32), { exports.pubkeyFeatures = Object.assign(bitfield(32), {
/** /**

35
test.js
View File

@ -10,6 +10,7 @@ var message = structs.message;
var var_int = structs.var_int; var var_int = structs.var_int;
var var_str = structs.var_str; var var_str = structs.var_str;
var var_int_list = structs.var_int_list; var var_int_list = structs.var_int_list;
var net_addr = structs.net_addr;
var messageEncodings = structs.messageEncodings; var messageEncodings = structs.messageEncodings;
var serviceFeatures = structs.serviceFeatures; var serviceFeatures = structs.serviceFeatures;
var pubkeyFeatures = structs.pubkeyFeatures; var pubkeyFeatures = structs.pubkeyFeatures;
@ -171,6 +172,39 @@ describe("Common structures", function() {
}); });
}); });
// FIXME(Kagami): Add more tests for inet_pton, inet_ntop; add more
// fail tests.
describe("net_addr", function() {
it("should decode", function() {
var res;
res = net_addr.decode(Buffer("0000000054aaf6c000000001000000000000000100000000000000000000ffff7f00000120fc", "hex"));
expect(res.time.getTime()).to.equal(1420490432000);
expect(res.stream).to.equal(1);
expect(res.services).to.have.members([serviceFeatures.NODE_NETWORK]);
expect(res.host).to.equal("127.0.0.1");
expect(res.port).to.equal(8444);
expect(net_addr.decode.bind(null, Buffer("000000000000000100000000000000000000ffff7f00000120fc", "hex"))).to.throw(Error);;
res = net_addr.decode(Buffer("000000000000000100000000000000000000ffff7f00000120fc", "hex"), {short: true});
expect(res.services).to.have.members([serviceFeatures.NODE_NETWORK]);
expect(res.host).to.equal("127.0.0.1");
expect(res.port).to.equal(8444);
res = net_addr.decode(Buffer("000000000000000100000000000000000000000000000001fde8", "hex"), {short: true});
expect(res.services).to.have.members([serviceFeatures.NODE_NETWORK]);
expect(res.host).to.equal("0:0:0:0:0:0:0:1");
expect(res.port).to.equal(65000);
});
it("should encode", function() {
var time = new Date(1420490432000);
expect(net_addr.encode({time: time, stream: 1, services: [serviceFeatures.NODE_NETWORK], host: "127.0.0.1", port: 8444}).toString("hex")).to.equal("0000000054aaf6c000000001000000000000000100000000000000000000ffff7f00000120fc");
expect(net_addr.encode({short: true, services: [serviceFeatures.NODE_NETWORK], host: "127.0.0.1", port: 8444}).toString("hex")).to.equal("000000000000000100000000000000000000ffff7f00000120fc");
expect(net_addr.encode({short: true, services: [serviceFeatures.NODE_NETWORK], host: "::1", port: 65000}).toString("hex")).to.equal("000000000000000100000000000000000000000000000001fde8");
});
});
describe("Message encodings", function() { describe("Message encodings", function() {
it("should decode", function() { it("should decode", function() {
expect(messageEncodings.decode(Buffer([2])).value).to.equal(messageEncodings.SIMPLE); expect(messageEncodings.decode(Buffer([2])).value).to.equal(messageEncodings.SIMPLE);
@ -229,6 +263,7 @@ describe("WIF", function() {
}); });
}); });
// FIXME(Kagami): Add more fail tests.
describe("Address", function() { describe("Address", function() {
it("should decode Bitmessage address", function() { it("should decode Bitmessage address", function() {
var addr = Address.decode("BM-2cTux3PGRqHTEH6wyUP2sWeT4LrsGgy63z") var addr = Address.decode("BM-2cTux3PGRqHTEH6wyUP2sWeT4LrsGgy63z")