Implement net_addr
This commit is contained in:
parent
6fb61e72b6
commit
0a6aa667db
|
@ -30,7 +30,7 @@ API documentation is available [here](https://bitchan.github.io/bitmessage/docs/
|
|||
- [x] var_int
|
||||
- [x] var_str
|
||||
- [x] var_int_list
|
||||
- [ ] net_addr
|
||||
- [x] net_addr
|
||||
- [ ] encrypted
|
||||
- [x] message encodings
|
||||
- [x] service features
|
||||
|
@ -57,6 +57,7 @@ API documentation is available [here](https://bitchan.github.io/bitmessage/docs/
|
|||
- [x] getRipe
|
||||
- [x] fromRandom
|
||||
- [ ] fromPassphrase
|
||||
- [ ] UserAgent
|
||||
- [ ] Message
|
||||
- [ ] encrypt
|
||||
- [ ] decrypt
|
||||
|
|
195
lib/structs.js
195
lib/structs.js
|
@ -87,17 +87,17 @@ var message = exports.message = {
|
|||
|
||||
|
||||
/**
|
||||
* var_int.
|
||||
* Variable length integer.
|
||||
* @see {@link https://bitmessage.org/wiki/Protocol_specification#Variable_length_integer}
|
||||
* @namespace
|
||||
* @static
|
||||
*/
|
||||
var var_int = exports.var_int = {
|
||||
/**
|
||||
* Decode var_int.
|
||||
* @param {Buffer} buf - A buffer that starts with encoded var_int
|
||||
* Decode `var_int`.
|
||||
* @param {Buffer} buf - A buffer that starts with encoded `var_int`
|
||||
* @return {{value: number, length: number, rest: Buffer}}
|
||||
* Decoded var_int structure.
|
||||
* Decoded `var_int` structure.
|
||||
*/
|
||||
decode: function(buf) {
|
||||
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
|
||||
* @return {Buffer} Encoded var_int.
|
||||
* @return {Buffer} Encoded `var_int`.
|
||||
*/
|
||||
encode: function(value) {
|
||||
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}
|
||||
* @namespace
|
||||
*/
|
||||
exports.var_str = {
|
||||
/**
|
||||
* Decode var_str.
|
||||
* @param {Buffer} buf - A buffer that starts with encoded var_str
|
||||
* Decode `var_str`.
|
||||
* @param {Buffer} buf - A buffer that starts with encoded `var_str`
|
||||
* @return {{str: string, length: number, rest: Buffer}}
|
||||
* Decoded var_str structure.
|
||||
* Decoded `var_str` structure.
|
||||
*/
|
||||
decode: function(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
|
||||
* @return {Buffer} Encoded var_str.
|
||||
* @return {Buffer} Encoded `var_str`.
|
||||
*/
|
||||
encode: function(str) {
|
||||
// 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}
|
||||
* @namespace
|
||||
*/
|
||||
exports.var_int_list = {
|
||||
/**
|
||||
* Decode var_int_list.
|
||||
* Decode `var_int_list`.
|
||||
* @param {Buffer} buf - A buffer that starts with encoded
|
||||
* var_int_list
|
||||
* `var_int_list`
|
||||
* @return {{list: number[], length: number, rest: Buffer}}
|
||||
* Decoded var_int_list structure.
|
||||
* Decoded `var_int_list` structure.
|
||||
*/
|
||||
decode: function(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
|
||||
* @return {Buffer} Encoded var_int_list.
|
||||
* @return {Buffer} Encoded `var_int_list`.
|
||||
*/
|
||||
encode: function(list) {
|
||||
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
|
||||
* constants.
|
||||
* @see {@link https://bitmessage.org/wiki/Protocol_specification#Message_Encodings}
|
||||
* @namespace
|
||||
*/
|
||||
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
|
||||
* buffer object.
|
||||
* @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. */
|
||||
NODE_NETWORK: 0,
|
||||
});
|
||||
|
@ -318,6 +474,7 @@ exports.serviceFeatures = Object.assign(bitfield(64), {
|
|||
* Pubkey bitfield features. Implements encoding/decoding for a 4-byte
|
||||
* buffer object.
|
||||
* @see {@link https://bitmessage.org/wiki/Protocol_specification#Pubkey_bitfield_features}
|
||||
* @namespace
|
||||
*/
|
||||
exports.pubkeyFeatures = Object.assign(bitfield(32), {
|
||||
/**
|
||||
|
|
35
test.js
35
test.js
|
@ -10,6 +10,7 @@ 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 net_addr = structs.net_addr;
|
||||
var messageEncodings = structs.messageEncodings;
|
||||
var serviceFeatures = structs.serviceFeatures;
|
||||
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() {
|
||||
it("should decode", function() {
|
||||
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() {
|
||||
it("should decode Bitmessage address", function() {
|
||||
var addr = Address.decode("BM-2cTux3PGRqHTEH6wyUP2sWeT4LrsGgy63z")
|
||||
|
|
Loading…
Reference in New Issue
Block a user