Implement messages.version
This commit is contained in:
parent
3d5968bcc1
commit
808fc5eaf1
|
@ -36,7 +36,7 @@ API documentation is available [here](https://bitchan.github.io/bitmessage/docs/
|
||||||
- [x] service features
|
- [x] service features
|
||||||
- [x] pubkey features
|
- [x] pubkey features
|
||||||
- [ ] Message types
|
- [ ] Message types
|
||||||
- [ ] version
|
- [x] version
|
||||||
- [ ] verack
|
- [ ] verack
|
||||||
- [ ] addr
|
- [ ] addr
|
||||||
- [ ] inv
|
- [ ] inv
|
||||||
|
|
|
@ -5,6 +5,9 @@
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
/** Current protocol version. */
|
||||||
|
exports.PROTOCOL_VERSION = 3;
|
||||||
|
|
||||||
/** [Common structures.]{@link module:bitmessage/structs} */
|
/** [Common structures.]{@link module:bitmessage/structs} */
|
||||||
exports.structs = require("./structs");
|
exports.structs = require("./structs");
|
||||||
/** [Messages.]{@link module:bitmessage/messages} */
|
/** [Messages.]{@link module:bitmessage/messages} */
|
||||||
|
|
|
@ -3,3 +3,97 @@
|
||||||
* @see {@link https://bitmessage.org/wiki/Protocol_specification#Message_types}
|
* @see {@link https://bitmessage.org/wiki/Protocol_specification#Message_types}
|
||||||
* @module bitmessage/messages
|
* @module bitmessage/messages
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var assert = require("./util").assert;
|
||||||
|
var structs = require("./structs");
|
||||||
|
var UserAgent = require("./user-agent");
|
||||||
|
var util = require("./util");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Version message.
|
||||||
|
* @see {@link https://bitmessage.org/wiki/Protocol_specification#version}
|
||||||
|
* @namespace
|
||||||
|
*/
|
||||||
|
exports.version = {
|
||||||
|
/** Random nonce used to detect connections to self. */
|
||||||
|
NONCE: new Buffer("20bde0a3355dad78", "hex"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode version payload.
|
||||||
|
* NOTE: `nonce` is copied.
|
||||||
|
* @param {Buffer} buf - Buffer that starts with encoded version
|
||||||
|
* payload
|
||||||
|
* @return {Object} Decoded version structure.
|
||||||
|
*/
|
||||||
|
decode: function(payload) {
|
||||||
|
// 4 + 8 + 8 + 26 + 26 + 8 + (1+) + (1+)
|
||||||
|
assert(payload.length >= 82, "Message payload is too small");
|
||||||
|
var protoVersion = payload.readUInt32BE(0, true);
|
||||||
|
var services = structs.serviceFeatures.decode(payload.slice(4, 12));
|
||||||
|
var time = util.readTime64BE(payload, 12);
|
||||||
|
var short = {short: true};
|
||||||
|
var addrRecv = structs.net_addr.decode(payload.slice(20, 46), short);
|
||||||
|
var addrFrom = structs.net_addr.decode(payload.slice(46, 72), short);
|
||||||
|
var nonce = new Buffer(8);
|
||||||
|
payload.copy(nonce, 0, 72, 80);
|
||||||
|
var decodedUa = UserAgent.decode(payload.slice(80));
|
||||||
|
var decodedStreamNumbers = structs.var_int_list.decode(decodedUa.rest);
|
||||||
|
return {
|
||||||
|
version: protoVersion,
|
||||||
|
services: services,
|
||||||
|
time: time,
|
||||||
|
remoteHost: addrRecv.host,
|
||||||
|
remotePort: addrRecv.port,
|
||||||
|
port: addrFrom.port,
|
||||||
|
nonce: nonce,
|
||||||
|
software: decodedUa.software,
|
||||||
|
streamNumbers: decodedStreamNumbers.list,
|
||||||
|
// NOTE(Kagami): Real data length. It may be some gap between end
|
||||||
|
// of stream numbers list and end of payload:
|
||||||
|
// [payload..............[stream numbers]xxxx]
|
||||||
|
// We are currently ignoring that.
|
||||||
|
length: 80 + decodedUa.length + decodedStreamNumbers.length,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode version payload.
|
||||||
|
* @param {Object} opts - Version options
|
||||||
|
* @return {Buffer} Encoded version payload.
|
||||||
|
*/
|
||||||
|
encode: function(opts) {
|
||||||
|
// Deal with default options.
|
||||||
|
var services = opts.services || [structs.serviceFeatures.NODE_NETWORK];
|
||||||
|
var time = opts.time || new Date();
|
||||||
|
var nonce = opts.nonce || exports.version.NONCE;
|
||||||
|
var software = opts.software || UserAgent.SELF;
|
||||||
|
var streamNumbers = opts.streamNumbers || [1];
|
||||||
|
// Start encoding.
|
||||||
|
var protoVersion = new Buffer(4);
|
||||||
|
protoVersion.writeUInt32BE(require("./").PROTOCOL_VERSION, 0);
|
||||||
|
var addrRecv = structs.net_addr.encode({
|
||||||
|
services: services,
|
||||||
|
host: opts.remoteHost,
|
||||||
|
port: opts.remotePort,
|
||||||
|
short: true,
|
||||||
|
});
|
||||||
|
var addrFrom = structs.net_addr.encode({
|
||||||
|
services: services,
|
||||||
|
host: "127.0.0.1",
|
||||||
|
port: opts.port,
|
||||||
|
short: true,
|
||||||
|
});
|
||||||
|
return Buffer.concat([
|
||||||
|
protoVersion,
|
||||||
|
structs.serviceFeatures.encode(services),
|
||||||
|
util.writeTime64BE(null, time),
|
||||||
|
addrRecv,
|
||||||
|
addrFrom,
|
||||||
|
nonce,
|
||||||
|
UserAgent.encode(software),
|
||||||
|
structs.var_int_list.encode(streamNumbers),
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
|
@ -59,13 +59,14 @@ var message = exports.message = {
|
||||||
command = command.slice(0, firstNonNull).toString("ascii");
|
command = command.slice(0, firstNonNull).toString("ascii");
|
||||||
var payloadLength = buf.readUInt32BE(16, true);
|
var payloadLength = buf.readUInt32BE(16, true);
|
||||||
assert(payloadLength <= 262144, "Payload is too big");
|
assert(payloadLength <= 262144, "Payload is too big");
|
||||||
var checksum = buf.slice(20, 24);
|
|
||||||
var length = 24 + payloadLength;
|
var length = 24 + payloadLength;
|
||||||
|
assert(buf.length >= length, "Truncated payload");
|
||||||
|
var checksum = buf.slice(20, 24);
|
||||||
// NOTE(Kagami): We do copy instead of slice to protect against
|
// NOTE(Kagami): We do copy instead of slice to protect against
|
||||||
// possible source buffer modification by user.
|
// possible source buffer modification by user.
|
||||||
var payload = new Buffer(payloadLength);
|
var payload = new Buffer(payloadLength);
|
||||||
buf.copy(payload, 0, 24, length);
|
buf.copy(payload, 0, 24, length);
|
||||||
assert(bufferEqual(checksum, getmsgchecksum(payload)), "Bad checkum");
|
assert(bufferEqual(checksum, getmsgchecksum(payload)), "Bad checksum");
|
||||||
var rest = buf.slice(length);
|
var rest = buf.slice(length);
|
||||||
return {command: command, payload: payload, length: length, rest: rest};
|
return {command: command, payload: payload, length: length, rest: rest};
|
||||||
},
|
},
|
||||||
|
@ -393,7 +394,7 @@ exports.net_addr = {
|
||||||
encode: function(opts) {
|
encode: function(opts) {
|
||||||
// Be aware of `Buffer.slice` quirk in browserify:
|
// Be aware of `Buffer.slice` quirk in browserify:
|
||||||
// <http://git.io/lNZF1A> (does not modify parent buffer's memory in
|
// <http://git.io/lNZF1A> (does not modify parent buffer's memory in
|
||||||
// old browsers).
|
// old browsers). So we use offset instead of `buf = buf.slice`.
|
||||||
var buf, shift;
|
var buf, shift;
|
||||||
if (opts.short) {
|
if (opts.short) {
|
||||||
buf = new Buffer(26);
|
buf = new Buffer(26);
|
||||||
|
@ -404,7 +405,8 @@ exports.net_addr = {
|
||||||
time = Math.floor(time.getTime() / 1000);
|
time = Math.floor(time.getTime() / 1000);
|
||||||
buf.writeUInt32BE(Math.floor(time / 4294967296), 0, true); // high32
|
buf.writeUInt32BE(Math.floor(time / 4294967296), 0, true); // high32
|
||||||
buf.writeUInt32BE(time % 4294967296, 4, true); // low32
|
buf.writeUInt32BE(time % 4294967296, 4, true); // low32
|
||||||
buf.writeUInt32BE(opts.stream, 8);
|
var stream = opts.stream || 1;
|
||||||
|
buf.writeUInt32BE(stream, 8);
|
||||||
shift = 12;
|
shift = 12;
|
||||||
}
|
}
|
||||||
var services = opts.services || [serviceFeatures.NODE_NETWORK];
|
var services = opts.services || [serviceFeatures.NODE_NETWORK];
|
||||||
|
|
53
lib/util.js
53
lib/util.js
|
@ -1,7 +1,58 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
exports.assert = function(condition, message) {
|
var assert = exports.assert = function(condition, message) {
|
||||||
if (!condition) {
|
if (!condition) {
|
||||||
throw new Error(message || "Assertion failed");
|
throw new Error(message || "Assertion failed");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Missing methods to read/write 64 bits integers from/to buffers.
|
||||||
|
// TODO(Kagami): Use this helpers in structs, pow, platform.
|
||||||
|
|
||||||
|
var MAX_SAFE_INTEGER = exports.MAX_SAFE_INTEGER = 9007199254740991;
|
||||||
|
|
||||||
|
exports.readUInt64BE = function(buf, offset, noAssert) {
|
||||||
|
offset = offset || 0;
|
||||||
|
var hi = buf.readUInt32BE(offset, noAssert);
|
||||||
|
var lo = buf.readUInt32BE(offset + 4, noAssert);
|
||||||
|
// Max safe number = 2^53 - 1 =
|
||||||
|
// 0b0000000000011111111111111111111111111111111111111111111111111111
|
||||||
|
// = 2097151*(2^32) + (2^32 - 1).
|
||||||
|
// So it's safe until hi <= 2097151. See
|
||||||
|
// <http://mdn.io/issafeinteger>, <https://stackoverflow.com/q/307179>
|
||||||
|
// for details.
|
||||||
|
assert(noAssert || hi <= 2097151, "Unsafe integer");
|
||||||
|
return hi * 4294967296 + lo;
|
||||||
|
};
|
||||||
|
|
||||||
|
var readTimestamp64BE = exports.readTimestamp64BE = function(buf, offset) {
|
||||||
|
offset = offset || 0;
|
||||||
|
var timeHi = buf.readUInt32BE(offset);
|
||||||
|
var timeLo = buf.readUInt32BE(offset + 4);
|
||||||
|
// JavaScript's Date object can't work with timestamps higher than
|
||||||
|
// 8640000000000 (~2^43, ~275760 year). Hope JavaScript will support
|
||||||
|
// 64-bit numbers up to this date.
|
||||||
|
assert(timeHi <= 2011, "Time is too high");
|
||||||
|
assert(timeHi !== 2011 || timeLo <= 2820767744, "Time is too high");
|
||||||
|
return timeHi * 4294967296 + timeLo;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.readTime64BE = function(buf, offset) {
|
||||||
|
var timestamp = readTimestamp64BE(buf, offset);
|
||||||
|
return new Date(timestamp * 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.writeUInt64BE = function(buf, value, offset, noAssert) {
|
||||||
|
buf = buf || new Buffer(8);
|
||||||
|
offset = offset || 0;
|
||||||
|
assert(noAssert || value <= MAX_SAFE_INTEGER, "Unsafe integer");
|
||||||
|
buf.writeUInt32BE(Math.floor(value / 4294967296), offset, noAssert);
|
||||||
|
buf.writeUInt32BE(value % 4294967296, offset + 4, noAssert);
|
||||||
|
return buf;
|
||||||
|
};
|
||||||
|
var writeUInt64BE = exports.writeUInt64BE;
|
||||||
|
|
||||||
|
exports.writeTime64BE = function(buf, time, offset, noAssert) {
|
||||||
|
var timestamp = Math.floor(time.getTime() / 1000);
|
||||||
|
return writeUInt64BE(buf, timestamp, offset, noAssert);
|
||||||
|
};
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
#ifndef BITCHAN_BITMESSAGE_POW_H_
|
#ifndef BITCHAN_BITMESSAGE_POW_H_
|
||||||
#define BITCHAN_BITMESSAGE_POW_H_
|
#define BITCHAN_BITMESSAGE_POW_H_
|
||||||
|
|
||||||
static const int MAX_POOL_SIZE = 1024;
|
static const size_t MAX_POOL_SIZE = 1024;
|
||||||
static const int HASH_SIZE = 64;
|
static const size_t HASH_SIZE = 64;
|
||||||
|
|
||||||
int pow(size_t pool_size,
|
int pow(size_t pool_size,
|
||||||
uint64_t target,
|
uint64_t target,
|
||||||
|
|
28
test.js
28
test.js
|
@ -16,6 +16,8 @@ var encrypted = structs.encrypted;
|
||||||
var messageEncodings = structs.messageEncodings;
|
var messageEncodings = structs.messageEncodings;
|
||||||
var serviceFeatures = structs.serviceFeatures;
|
var serviceFeatures = structs.serviceFeatures;
|
||||||
var pubkeyFeatures = structs.pubkeyFeatures;
|
var pubkeyFeatures = structs.pubkeyFeatures;
|
||||||
|
var messages = bitmessage.messages;
|
||||||
|
var version = messages.version;
|
||||||
var WIF = bitmessage.WIF;
|
var WIF = bitmessage.WIF;
|
||||||
var POW = bitmessage.POW;
|
var POW = bitmessage.POW;
|
||||||
var Address = bitmessage.Address;
|
var Address = bitmessage.Address;
|
||||||
|
@ -72,6 +74,10 @@ describe("Common structures", function() {
|
||||||
expect(res.command).to.equal("");
|
expect(res.command).to.equal("");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should throw when decoding message with truncated payload", function() {
|
||||||
|
expect(message.decode.bind(null, Buffer("e9beb4d97465737400000000000000000000000770b33ce97061796c6f61", "hex"))).to.throw(Error);
|
||||||
|
});
|
||||||
|
|
||||||
it("should encode", function() {
|
it("should encode", function() {
|
||||||
expect(message.encode({command: "test", payload: Buffer("payload")}).toString("hex")).to.equal("e9beb4d97465737400000000000000000000000770b33ce97061796c6f6164");
|
expect(message.encode({command: "test", payload: Buffer("payload")}).toString("hex")).to.equal("e9beb4d97465737400000000000000000000000770b33ce97061796c6f6164");
|
||||||
});
|
});
|
||||||
|
@ -275,6 +281,28 @@ describe("Common structures", function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("Message types", function() {
|
||||||
|
describe("version", function() {
|
||||||
|
it("should encode and decode", function() {
|
||||||
|
var res = version.decode(version.encode({
|
||||||
|
remoteHost: "1.2.3.4",
|
||||||
|
remotePort: 48444,
|
||||||
|
port: 8444,
|
||||||
|
}));
|
||||||
|
expect(res.version).to.equal(3);
|
||||||
|
expect(res.services).to.deep.equal([serviceFeatures.NODE_NETWORK]);
|
||||||
|
expect(res.time).to.be.instanceof(Date);
|
||||||
|
expect(res.remoteHost).to.equal("1.2.3.4");
|
||||||
|
expect(res.remotePort).to.equal(48444);
|
||||||
|
expect(res.port).to.equal(8444);
|
||||||
|
expect(bufferEqual(res.nonce, version.NONCE)).to.be.true;
|
||||||
|
expect(res.software).to.deep.equal(UserAgent.SELF);
|
||||||
|
expect(res.streamNumbers).to.deep.equal([1]);
|
||||||
|
expect(res.length).to.equal(101);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("WIF", function() {
|
describe("WIF", function() {
|
||||||
var wifSign = "5JgQ79vTBusc61xYPtUEHYQ38AXKdDZgQ5rFp7Cbb4ZjXUKFZEV";
|
var wifSign = "5JgQ79vTBusc61xYPtUEHYQ38AXKdDZgQ5rFp7Cbb4ZjXUKFZEV";
|
||||||
var wifEnc = "5K2aL8cnsEWHwHfHnUrPo8QdYyRfoYUBmhAnWY5GTpDLbeyusnE";
|
var wifEnc = "5K2aL8cnsEWHwHfHnUrPo8QdYyRfoYUBmhAnWY5GTpDLbeyusnE";
|
||||||
|
|
Loading…
Reference in New Issue
Block a user