2015-01-02 22:45:56 +01:00
|
|
|
/**
|
|
|
|
* Working with messages.
|
|
|
|
* @see {@link https://bitmessage.org/wiki/Protocol_specification#Message_types}
|
2015-01-16 01:08:56 +01:00
|
|
|
* @see {@link https://bitmessage.org/wiki/Protocol_specification_v3#Message_types}
|
|
|
|
* @see {@link https://bitmessage.org/Bitmessage%20Technical%20Paper.pdf}
|
2015-01-03 15:52:27 +01:00
|
|
|
* @module bitmessage/messages
|
2015-01-02 22:45:56 +01:00
|
|
|
*/
|
2015-01-18 12:37:09 +01:00
|
|
|
// TODO(Kagami): Document object-like params.
|
2015-01-15 18:13:02 +01:00
|
|
|
|
|
|
|
"use strict";
|
|
|
|
|
|
|
|
var assert = require("./util").assert;
|
|
|
|
var structs = require("./structs");
|
|
|
|
var UserAgent = require("./user-agent");
|
|
|
|
var util = require("./util");
|
|
|
|
|
|
|
|
/**
|
2015-01-15 22:00:27 +01:00
|
|
|
* `version` message.
|
2015-01-15 18:13:02 +01:00
|
|
|
* @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"),
|
|
|
|
|
|
|
|
/**
|
2015-01-16 19:36:57 +01:00
|
|
|
* Decode `version` message payload.
|
2015-01-15 18:13:02 +01:00
|
|
|
* NOTE: `nonce` is copied.
|
2015-01-16 19:36:57 +01:00
|
|
|
* @param {Buffer} buf - Message payload
|
2015-01-15 19:11:33 +01:00
|
|
|
* @return {Object} Decoded `version` structure.
|
2015-01-15 18:13:02 +01:00
|
|
|
*/
|
2015-01-15 23:43:15 +01:00
|
|
|
decode: function(buf) {
|
2015-01-15 18:13:02 +01:00
|
|
|
// 4 + 8 + 8 + 26 + 26 + 8 + (1+) + (1+)
|
2015-01-15 23:43:15 +01:00
|
|
|
assert(buf.length >= 82, "Buffer is too small");
|
|
|
|
var protoVersion = buf.readUInt32BE(0, true);
|
|
|
|
var services = structs.serviceFeatures.decode(buf.slice(4, 12));
|
|
|
|
var time = util.readTime64BE(buf, 12);
|
2015-01-15 18:13:02 +01:00
|
|
|
var short = {short: true};
|
2015-01-15 23:43:15 +01:00
|
|
|
var addrRecv = structs.net_addr.decode(buf.slice(20, 46), short);
|
|
|
|
var addrFrom = structs.net_addr.decode(buf.slice(46, 72), short);
|
2015-01-15 18:13:02 +01:00
|
|
|
var nonce = new Buffer(8);
|
2015-01-15 23:43:15 +01:00
|
|
|
buf.copy(nonce, 0, 72, 80);
|
|
|
|
var decodedUa = UserAgent.decode(buf.slice(80));
|
2015-01-15 18:13:02 +01:00
|
|
|
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,
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2015-01-16 19:36:57 +01:00
|
|
|
* Encode `version` message payload.
|
2015-01-15 18:13:02 +01:00
|
|
|
* @param {Object} opts - Version options
|
2015-01-16 19:36:57 +01:00
|
|
|
* @return {Buffer} Encoded payload.
|
2015-01-15 18:13:02 +01:00
|
|
|
*/
|
|
|
|
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;
|
2015-01-16 00:14:30 +01:00
|
|
|
assert(nonce.length === 8, "Bad nonce");
|
2015-01-15 18:13:02 +01:00
|
|
|
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),
|
|
|
|
]);
|
|
|
|
},
|
|
|
|
};
|
2015-01-15 19:11:33 +01:00
|
|
|
|
|
|
|
/**
|
2015-01-15 22:00:27 +01:00
|
|
|
* `addr` message. Provide information on known nodes of the network.
|
2015-01-15 19:11:33 +01:00
|
|
|
* @see {@link https://bitmessage.org/wiki/Protocol_specification#addr}
|
|
|
|
* @namespace
|
|
|
|
*/
|
|
|
|
exports.addr = {
|
|
|
|
/**
|
2015-01-16 19:36:57 +01:00
|
|
|
* Decode `addr` message payload.
|
|
|
|
* @param {Buffer} buf - Message payload
|
2015-01-15 19:11:33 +01:00
|
|
|
* @return {Object} Decoded `addr` structure.
|
|
|
|
*/
|
|
|
|
decode: function(buf) {
|
|
|
|
var decoded = structs.var_int.decode(buf);
|
|
|
|
var listLength = decoded.value;
|
2015-01-15 21:19:52 +01:00
|
|
|
assert(listLength <= 1000, "Too many address entires");
|
2015-01-15 19:11:33 +01:00
|
|
|
var length = decoded.length + listLength * 38;
|
|
|
|
assert(buf.length >= length, "Buffer is too small");
|
|
|
|
var rest = decoded.rest;
|
|
|
|
var addrs = new Array(listLength);
|
|
|
|
for (var i = 0; i < listLength; i++) {
|
|
|
|
addrs[i] = structs.net_addr.decode(rest.slice(i*38, (i+1)*38));
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
addrs: addrs,
|
|
|
|
// Real data length.
|
|
|
|
length: length,
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2015-01-16 19:36:57 +01:00
|
|
|
* Encode `addr` message payload.
|
2015-01-15 19:11:33 +01:00
|
|
|
* @param {Object[]} addrs - Network addresses
|
2015-01-16 19:36:57 +01:00
|
|
|
* @return {Buffer} Encoded payload.
|
2015-01-15 19:11:33 +01:00
|
|
|
*/
|
|
|
|
encode: function(addrs) {
|
2015-01-15 21:19:52 +01:00
|
|
|
assert(addrs.length <= 1000, "Too many address entires");
|
2015-01-15 19:11:33 +01:00
|
|
|
var addrsBuf = Buffer.concat(addrs.map(structs.net_addr.encode));
|
|
|
|
return Buffer.concat([structs.var_int.encode(addrs.length), addrsBuf]);
|
|
|
|
},
|
|
|
|
};
|
2015-01-15 22:00:27 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* `inv` message. Allows a node to advertise its knowledge of one or
|
|
|
|
* more objects.
|
|
|
|
* @see {@link https://bitmessage.org/wiki/Protocol_specification#inv}
|
|
|
|
* @namespace
|
2015-01-15 22:07:49 +01:00
|
|
|
* @static
|
2015-01-15 22:00:27 +01:00
|
|
|
*/
|
2015-01-15 22:07:49 +01:00
|
|
|
var inv = exports.inv = {
|
2015-01-15 22:00:27 +01:00
|
|
|
/**
|
2015-01-16 19:36:57 +01:00
|
|
|
* Decode `inv` message payload.
|
|
|
|
* @param {Buffer} buf - Message payload
|
2015-01-15 22:00:27 +01:00
|
|
|
* @return {Object} Decoded `inv` structure.
|
|
|
|
*/
|
|
|
|
decode: function(buf) {
|
|
|
|
var decoded = structs.var_int.decode(buf);
|
|
|
|
var listLength = decoded.value;
|
|
|
|
assert(listLength <= 50000, "Too many inventory entires");
|
|
|
|
var length = decoded.length + listLength * 32;
|
|
|
|
assert(buf.length >= length, "Buffer is too small");
|
|
|
|
var rest = decoded.rest;
|
|
|
|
var inventory = new Array(listLength);
|
|
|
|
for (var i = 0; i < listLength; i++) {
|
|
|
|
inventory[i] = rest.slice(i*32, (i+1)*32);
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
inventory: inventory,
|
|
|
|
// Real data length.
|
|
|
|
length: length,
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2015-01-16 19:36:57 +01:00
|
|
|
* Encode `inv` message payload.
|
2015-01-16 01:08:56 +01:00
|
|
|
* @param {Buffer[]} inventory - Inventory vector list (encoded)
|
2015-01-16 19:36:57 +01:00
|
|
|
* @return {Buffer} Encoded payload.
|
2015-01-15 22:00:27 +01:00
|
|
|
*/
|
|
|
|
encode: function(inventory) {
|
|
|
|
assert(inventory.length <= 50000, "Too many inventory entires");
|
|
|
|
var invBuf = Buffer.concat(inventory);
|
|
|
|
return Buffer.concat([structs.var_int.encode(inventory.length), invBuf]);
|
|
|
|
},
|
|
|
|
};
|
2015-01-15 22:07:49 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* `getdata` message. `getdata` is used in response to an `inv` message
|
|
|
|
* to retrieve the content of a specific object after filtering known
|
|
|
|
* elements.
|
|
|
|
* @see {@link https://bitmessage.org/wiki/Protocol_specification#getdata}
|
|
|
|
* @namespace
|
|
|
|
*/
|
|
|
|
exports.getdata = inv;
|
2015-01-16 00:14:30 +01:00
|
|
|
|
2015-01-16 01:08:56 +01:00
|
|
|
/**
|
|
|
|
* `error` message.
|
|
|
|
* @see {@link https://bitmessage.org/wiki/Protocol_specification_v3#error}
|
|
|
|
* @namespace
|
|
|
|
*/
|
|
|
|
var error = exports.error = {
|
|
|
|
/**
|
|
|
|
* Just a warning.
|
|
|
|
*/
|
|
|
|
WARNING: 0,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* It's an error, something was going wrong (e.g. an object got lost).
|
|
|
|
*/
|
|
|
|
ERROR: 1,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* It's a fatal error. The node will drop the line for that error and
|
|
|
|
* maybe ban you for some time.
|
|
|
|
*/
|
|
|
|
FATAL: 2,
|
|
|
|
|
|
|
|
/**
|
2015-01-16 19:36:57 +01:00
|
|
|
* Decode `error` message payload.
|
|
|
|
* @param {Buffer} buf - Message payload
|
2015-01-16 01:08:56 +01:00
|
|
|
* @return {Object} Decoded `error` structure.
|
|
|
|
*/
|
|
|
|
decode: function(buf) {
|
|
|
|
assert(buf.length >= 4, "Buffer is too small");
|
|
|
|
var decodedFatal = structs.var_int.decode(buf);
|
|
|
|
var decodedBanTime = structs.var_int.decode(decodedFatal.rest);
|
|
|
|
var decodedVector = structs.var_str.decode(decodedBanTime.rest);
|
|
|
|
var decodedErrorText = structs.var_str.decode(decodedVector.rest);
|
|
|
|
var length = (
|
|
|
|
decodedFatal.length +
|
|
|
|
decodedBanTime.length +
|
|
|
|
decodedVector.length +
|
|
|
|
decodedErrorText.length
|
|
|
|
);
|
|
|
|
return {
|
|
|
|
fatal: decodedFatal.value,
|
|
|
|
banTime: decodedBanTime.value,
|
|
|
|
vector: decodedVector.str,
|
|
|
|
errorText: decodedErrorText.str,
|
|
|
|
// Real data length.
|
|
|
|
length: length,
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2015-01-16 19:36:57 +01:00
|
|
|
* Encode `error` message payload.
|
2015-01-16 01:08:56 +01:00
|
|
|
* @param {Object} opts - Error options
|
2015-01-16 19:36:57 +01:00
|
|
|
* @return {Buffer} Encoded payload.
|
2015-01-16 01:08:56 +01:00
|
|
|
*/
|
|
|
|
encode: function(opts) {
|
|
|
|
var fatal = opts.fatal || error.WARNING;
|
|
|
|
var banTime = opts.banTime || 0;
|
|
|
|
var vector = opts.vector || "";
|
|
|
|
var errorText = opts.errorText || "";
|
|
|
|
return Buffer.concat([
|
|
|
|
structs.var_int.encode(fatal),
|
|
|
|
structs.var_int.encode(banTime),
|
|
|
|
structs.var_str.encode(vector),
|
|
|
|
structs.var_str.encode(errorText),
|
|
|
|
]);
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2015-01-16 00:14:30 +01:00
|
|
|
/**
|
|
|
|
* `object` message. An `object` is a message which is shared throughout
|
|
|
|
* a stream. It is the only message which propagates; all others are
|
2015-01-16 19:36:57 +01:00
|
|
|
* only between two nodes.
|
|
|
|
* NOTE: You shouldn't use `encode` and `decode` methods directly.
|
2015-01-18 12:37:09 +01:00
|
|
|
* Instead, get type of the object and process it using appropriate
|
|
|
|
* namespace from `objects` module.
|
2015-01-16 00:14:30 +01:00
|
|
|
* @see {@link https://bitmessage.org/wiki/Protocol_specification#object}
|
|
|
|
* @namespace
|
|
|
|
*/
|
|
|
|
exports.object = {
|
2015-01-16 19:36:57 +01:00
|
|
|
// Known types.
|
|
|
|
GETPUBKEY: 0,
|
|
|
|
PUBKEY: 1,
|
|
|
|
MSG: 2,
|
|
|
|
BROADCAST: 3,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns type of the `object` message if we can parse it. Per spec
|
|
|
|
* we should still relay unknown objects.
|
|
|
|
* @param {Buffer} buf - Message payload
|
|
|
|
* @return {?number} Object type.
|
|
|
|
*/
|
|
|
|
getType: function(buf) {
|
2015-01-18 12:37:09 +01:00
|
|
|
assert(buf.length >= 22, "object message payload is too small");
|
|
|
|
return buf.readUInt32BE(16, true);
|
2015-01-16 19:36:57 +01:00
|
|
|
},
|
|
|
|
|
2015-01-16 00:14:30 +01:00
|
|
|
/**
|
2015-01-16 19:36:57 +01:00
|
|
|
* Decode `object` message payload.
|
2015-01-18 12:37:09 +01:00
|
|
|
* NOTE: `nonce` and `payload` are copied.
|
2015-01-16 19:36:57 +01:00
|
|
|
* @param {Buffer} buf - Message payload
|
2015-01-16 00:14:30 +01:00
|
|
|
* @return {Object} Decoded `object` structure.
|
|
|
|
*/
|
2015-01-18 12:37:09 +01:00
|
|
|
// FIXME(Kagami): Check for POW.
|
|
|
|
// TODO(Kagami): Option to not fail on bad POW (may be useful for
|
|
|
|
// bitchan).
|
|
|
|
// TODO(Kagami): Option to not fail on expired object (would be useful
|
|
|
|
// for bitchan).
|
2015-01-16 00:14:30 +01:00
|
|
|
decode: function(buf) {
|
|
|
|
// 8 + 8 + 4 + (1+) + (1+)
|
2015-01-16 19:36:57 +01:00
|
|
|
assert(buf.length >= 22, "object message payload is too small");
|
2015-01-16 00:14:30 +01:00
|
|
|
var nonce = new Buffer(8);
|
|
|
|
buf.copy(nonce, 0, 0, 8);
|
|
|
|
var expiresTime = util.readTimestamp64BE(buf.slice(8, 16));
|
|
|
|
var ttl = expiresTime - util.tnow();
|
|
|
|
assert(ttl >= -3600, "Object expired more than a hour ago");
|
|
|
|
assert(ttl <= 2430000, "expiresTime is too far in the future");
|
2015-01-16 19:36:57 +01:00
|
|
|
var type = buf.readUInt32BE(16, true);
|
2015-01-16 00:14:30 +01:00
|
|
|
var decodedVersion = structs.var_int.decode(buf.slice(20));
|
|
|
|
var decodedStream = structs.var_int.decode(decodedVersion.rest);
|
2015-01-18 12:37:09 +01:00
|
|
|
var payload = new Buffer(decodedStream.rest.length);
|
|
|
|
decodedStream.rest.copy(payload);
|
2015-01-16 00:14:30 +01:00
|
|
|
return {
|
|
|
|
nonce: nonce,
|
|
|
|
ttl: ttl,
|
|
|
|
type: type,
|
|
|
|
version: decodedVersion.value,
|
|
|
|
stream: decodedStream.value,
|
2015-01-18 12:37:09 +01:00
|
|
|
payload: payload,
|
2015-01-16 00:14:30 +01:00
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2015-01-16 19:36:57 +01:00
|
|
|
* Encode `object` message payload.
|
2015-01-16 00:14:30 +01:00
|
|
|
* @param {Object} opts - Object options
|
2015-01-16 19:36:57 +01:00
|
|
|
* @return {Buffer} Encoded payload.
|
2015-01-16 00:14:30 +01:00
|
|
|
*/
|
2015-01-18 12:37:09 +01:00
|
|
|
// TODO(Kagami): Do a POW if nonce is not provided.
|
2015-01-16 00:14:30 +01:00
|
|
|
encode: function(opts) {
|
|
|
|
assert(opts.nonce.length === 8, "Bad nonce");
|
|
|
|
assert(opts.ttl > 0, "Bad TTL");
|
|
|
|
assert(opts.ttl <= 2430000, "TTL may not be larger than 28 days + 3 hours");
|
|
|
|
var expiresTime = util.tnow() + opts.ttl;
|
|
|
|
var type = new Buffer(4);
|
|
|
|
type.writeUInt32BE(opts.type, 0);
|
|
|
|
var stream = opts.stream || 1;
|
|
|
|
return Buffer.concat([
|
|
|
|
opts.nonce,
|
|
|
|
util.writeUInt64BE(null, expiresTime),
|
|
|
|
type,
|
|
|
|
structs.var_int.encode(opts.version),
|
|
|
|
structs.var_int.encode(stream),
|
|
|
|
opts.payload,
|
|
|
|
]);
|
|
|
|
},
|
|
|
|
};
|