Implement messages.version

This commit is contained in:
Kagami Hiiragi 2015-01-15 20:13:02 +03:00
parent 3d5968bcc1
commit 808fc5eaf1
7 changed files with 186 additions and 8 deletions

View File

@ -36,7 +36,7 @@ API documentation is available [here](https://bitchan.github.io/bitmessage/docs/
- [x] service features
- [x] pubkey features
- [ ] Message types
- [ ] version
- [x] version
- [ ] verack
- [ ] addr
- [ ] inv

View File

@ -5,6 +5,9 @@
"use strict";
/** Current protocol version. */
exports.PROTOCOL_VERSION = 3;
/** [Common structures.]{@link module:bitmessage/structs} */
exports.structs = require("./structs");
/** [Messages.]{@link module:bitmessage/messages} */

View File

@ -3,3 +3,97 @@
* @see {@link https://bitmessage.org/wiki/Protocol_specification#Message_types}
* @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),
]);
},
};

View File

@ -59,13 +59,14 @@ var message = exports.message = {
command = command.slice(0, firstNonNull).toString("ascii");
var payloadLength = buf.readUInt32BE(16, true);
assert(payloadLength <= 262144, "Payload is too big");
var checksum = buf.slice(20, 24);
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
// possible source buffer modification by user.
var payload = new Buffer(payloadLength);
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);
return {command: command, payload: payload, length: length, rest: rest};
},
@ -393,7 +394,7 @@ exports.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).
// old browsers). So we use offset instead of `buf = buf.slice`.
var buf, shift;
if (opts.short) {
buf = new Buffer(26);
@ -404,7 +405,8 @@ exports.net_addr = {
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);
var stream = opts.stream || 1;
buf.writeUInt32BE(stream, 8);
shift = 12;
}
var services = opts.services || [serviceFeatures.NODE_NETWORK];

View File

@ -1,7 +1,58 @@
"use strict";
exports.assert = function(condition, message) {
var assert = exports.assert = function(condition, message) {
if (!condition) {
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);
};

View File

@ -1,8 +1,8 @@
#ifndef BITCHAN_BITMESSAGE_POW_H_
#define BITCHAN_BITMESSAGE_POW_H_
static const int MAX_POOL_SIZE = 1024;
static const int HASH_SIZE = 64;
static const size_t MAX_POOL_SIZE = 1024;
static const size_t HASH_SIZE = 64;
int pow(size_t pool_size,
uint64_t target,

28
test.js
View File

@ -16,6 +16,8 @@ var encrypted = structs.encrypted;
var messageEncodings = structs.messageEncodings;
var serviceFeatures = structs.serviceFeatures;
var pubkeyFeatures = structs.pubkeyFeatures;
var messages = bitmessage.messages;
var version = messages.version;
var WIF = bitmessage.WIF;
var POW = bitmessage.POW;
var Address = bitmessage.Address;
@ -72,6 +74,10 @@ describe("Common structures", function() {
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() {
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() {
var wifSign = "5JgQ79vTBusc61xYPtUEHYQ38AXKdDZgQ5rFp7Cbb4ZjXUKFZEV";
var wifEnc = "5K2aL8cnsEWHwHfHnUrPo8QdYyRfoYUBmhAnWY5GTpDLbeyusnE";