To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to this software to the public domain worldwide. This software is distributed without any warranty.
You should have received a copy of the CC0 Public Domain Dedication along with this software. If not, see .
@@ -205,7 +204,7 @@ tcp.on("established", function() {
diff --git a/docs/index.js.html b/docs/index.js.html
index d7a263e..67ea590 100644
--- a/docs/index.js.html
+++ b/docs/index.js.html
@@ -71,7 +71,7 @@ exports.UserAgent = require("./user-agent");
diff --git a/docs/messages.js.html b/docs/messages.js.html
index 45e789f..9e433cb 100644
--- a/docs/messages.js.html
+++ b/docs/messages.js.html
@@ -38,7 +38,6 @@
*
* // Simple encoding and decoding:
* var vermsg = messages.version.encode({
- * nonce: Buffer(8), // Hack detection connection to self
* remoteHost: "1.1.1.1",
* remotePort: 8444,
* });
@@ -63,7 +62,6 @@
"use strict";
var objectAssign = Object.assign || require("object-assign");
-var bufferEqual = require("buffer-equal");
var assert = require("./_util").assert;
var structs = require("./structs");
var bmcrypto = require("./crypto");
@@ -97,9 +95,6 @@ exports.getCommand = function(buf) {
return command.slice(0, firstNonNull).toString("ascii");
};
-// Random nonce used to detect connections to self.
-var randomNonce = bmcrypto.randomBytes(8);
-
/**
* `version` message.
* @see {@link https://bitmessage.org/wiki/Protocol_specification#version}
@@ -111,10 +106,10 @@ var randomNonce = bmcrypto.randomBytes(8);
var version = exports.version = {
/**
* @typedef {Object} DecodeResult
- * @property {number} version - Identifies protocol version being used
- * by the node. Should equal 3. Nodes should disconnect if the remote
- * node's version is lower but continue with the connection if it is
- * higher.
+ * @property {number} protoVersion - Identifies protocol version being
+ * used by the node. Should equal 3. Nodes should disconnect if the
+ * remote node's version is lower but continue with the connection if
+ * it is higher.
* @property {Object} services -
* [Service]{@link module:bitmessage/structs.ServicesBitfield}
* features to be enabled for this connection
@@ -125,15 +120,21 @@ var version = exports.version = {
* message
* @property {number} port - Incoming port of the node sending this
* message
- * @property {Buffer} nonce - Random nonce used to detect connection
- * to self
- * @property {(Array|string|Buffer)} userAgent -
- * [User agent]{@link module:bitmessage/user-agent} of the node
- * @property {number[]} streamNumbers - Streams accepted by the node
+ * @property {Buffer} nonce - An 8-byte random nonce used to detect
+ * connection to self
+ * @property {string} userAgent - [User agent]{@link
+ * module:bitmessage/user-agent} of the node
+ * @property {number[]} streams - Streams accepted by the node
* @property {number} length - Real data length
* @memberof module:bitmessage/messages.version
*/
+ /**
+ * Random nonce used to detect connections to self.
+ * @constant {Buffer}
+ */
+ randomNonce: bmcrypto.randomBytes(8),
+
/**
* Decode `version` message.
* NOTE: `nonce` is copied.
@@ -162,11 +163,12 @@ var version = exports.version = {
var addrFrom = structs.net_addr.decode(buf.slice(46, 72), short);
var nonce = new Buffer(8);
buf.copy(nonce, 0, 72, 80);
- assert(!bufferEqual(nonce, randomNonce), "Connection to self");
var decodedUa = UserAgent.decode(buf.slice(80));
- var decodedStreamNumbers = structs.var_int_list.decode(decodedUa.rest);
+ assert(decodedUa.length <= 5000, "User agent is too long");
+ var decodedStreams = structs.var_int_list.decode(decodedUa.rest);
+ assert(decodedStreams.list.length <= 160000, "Too many streams");
return {
- version: protoVersion,
+ protoVersion: protoVersion,
services: services,
time: time,
remoteHost: addrRecv.host,
@@ -174,12 +176,12 @@ var version = exports.version = {
port: addrFrom.port,
nonce: nonce,
userAgent: decodedUa.str,
- streamNumbers: decodedStreamNumbers.list,
+ streams: decodedStreams.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,
+ length: 80 + decodedUa.length + decodedStreams.length,
};
},
@@ -197,13 +199,13 @@ var version = exports.version = {
* message
* @param {number=} opts.port - Incoming port of the node (8444 by
* default)
- * @param {Buffer=} opts.nonce - Random nonce used to detect connection
- * to self (unique per node.js process by default)
+ * @param {Buffer=} opts.nonce - An 8-byte random nonce used to detect
+ * connection to self (unique per node.js process by default)
* @param {(Array|string|Buffer)=} opts.userAgent -
* [User agent]{@link module:bitmessage/user-agent} of the node
- * (bitmessage's by default)
- * @param {Array<number>=} opts.streamNumbers - Streams accepted by the
- * node (1 by default)
+ * (user agent of bitmessage library by default)
+ * @param {Array<number>=} opts.streams - Streams accepted by the node
+ * ([1] by default)
* @return {Buffer} Encoded message.
*/
encode: function(opts) {
@@ -220,11 +222,13 @@ var version = exports.version = {
var services = opts.services ||
ServicesBitfield().set(ServicesBitfield.NODE_NETWORK);
var time = opts.time || new Date();
- var nonce = opts.nonce || randomNonce;
+ var nonce = opts.nonce || version.randomNonce;
assert(nonce.length === 8, "Bad nonce");
var port = opts.port || 8444;
- var userAgent = opts.userAgent || UserAgent.SELF;
- var streamNumbers = opts.streamNumbers || [1];
+ var userAgent = UserAgent.encode(opts.userAgent || UserAgent.SELF);
+ assert(userAgent.length <= 5000, "User agent is too long");
+ var streams = opts.streams || [1];
+ assert(streams.length <= 160000, "Too many streams");
// Start encoding.
var protoVersion = new Buffer(4);
protoVersion.writeUInt32BE(util.PROTOCOL_VERSION, 0);
@@ -247,8 +251,8 @@ var version = exports.version = {
addrRecv,
addrFrom,
nonce,
- UserAgent.encode(userAgent),
- structs.var_int_list.encode(streamNumbers),
+ userAgent,
+ structs.var_int_list.encode(streams),
]);
},
};
@@ -480,9 +484,23 @@ var error = exports.error = {
*/
FATAL: 2,
+ /**
+ * Convert error type to a human-readable string.
+ * @param {number} type - Type of the error
+ * @return {string}
+ */
+ type2str: function(type) {
+ switch (type) {
+ case error.WARNING: return "warning";
+ case error.ERROR: return "error";
+ case error.FATAL: return "fatal";
+ default: return "unknown";
+ }
+ },
+
/**
* @typedef {Object} DecodeResult
- * @property {number} fatal - Type of the error
+ * @property {number} type - Type of the error
* @property {number} banTime - The other node informs that it will
* not accept further connections for this number of seconds
* @property {?Buffer} vector -
@@ -511,8 +529,8 @@ var error = exports.error = {
*/
decodePayload: 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 decodedType = structs.var_int.decode(buf);
+ var decodedBanTime = structs.var_int.decode(decodedType.rest);
var decodedVectorLength = structs.var_int.decode(decodedBanTime.rest);
// NOTE(Kagami): Inventory vector should be only 32-byte in size but
@@ -529,13 +547,13 @@ var error = exports.error = {
var decodedErrorText = structs.var_str.decode(rest);
var length = (
- decodedFatal.length +
+ decodedType.length +
decodedBanTime.length +
decodedVectorLength.length + vectorLength +
decodedErrorText.length
);
return {
- fatal: decodedFatal.value,
+ type: decodedType.value,
banTime: decodedBanTime.value,
vector: vector,
errorText: decodedErrorText.str,
@@ -547,7 +565,7 @@ var error = exports.error = {
/**
* Encode `error` message.
* @param {Object} opts - Error options
- * @param {number=} opts.fatal - Type of the error
+ * @param {number=} opts.type - Type of the error
* ([warning]{@link module:bitmessage/messages.error.WARNING} by
* default)
* @param {number=} opts.banTime - Inform the other node, that you
@@ -569,12 +587,12 @@ var error = exports.error = {
* The same as [encode]{@link module:bitmessage/messages.error.encode}.
*/
encodePayload: function(opts) {
- var fatal = opts.fatal || error.WARNING;
+ var type = opts.type || error.WARNING;
var banTime = opts.banTime || 0;
// TODO(Kagami): Validate vector length.
var vector = opts.vector || new Buffer(0);
return Buffer.concat([
- structs.var_int.encode(fatal),
+ structs.var_int.encode(type),
structs.var_int.encode(banTime),
structs.var_int.encode(vector.length),
vector,
@@ -598,7 +616,7 @@ var error = exports.error = {
diff --git a/docs/module-bitmessage.html b/docs/module-bitmessage.html
index 429f92c..eb67a48 100644
--- a/docs/module-bitmessage.html
+++ b/docs/module-bitmessage.html
@@ -660,7 +660,7 @@
diff --git a/docs/module-bitmessage_address.Address.html b/docs/module-bitmessage_address.Address.html
index 7f5aa5f..c9466f3 100644
--- a/docs/module-bitmessage_address.Address.html
+++ b/docs/module-bitmessage_address.Address.html
@@ -2206,7 +2206,7 @@ encrypt/decrypt
diff --git a/docs/module-bitmessage_address.html b/docs/module-bitmessage_address.html
index 6ec678a..b2a710a 100644
--- a/docs/module-bitmessage_address.html
+++ b/docs/module-bitmessage_address.html
@@ -178,7 +178,7 @@ console.log("Deterministic Bitmessage address:", addr2.encode());
diff --git a/docs/module-bitmessage_crypto.html b/docs/module-bitmessage_crypto.html
index 9d1ddf3..e9d41a2 100644
--- a/docs/module-bitmessage_crypto.html
+++ b/docs/module-bitmessage_crypto.html
@@ -2034,7 +2034,7 @@ and rejects on bad key or signature.
diff --git a/docs/module-bitmessage_messages.addr.html b/docs/module-bitmessage_messages.addr.html
index ef23d7e..01647e9 100644
--- a/docs/module-bitmessage_messages.addr.html
+++ b/docs/module-bitmessage_messages.addr.html
@@ -76,7 +76,7 @@
@@ -1245,7 +1394,7 @@ related to the error
diff --git a/docs/module-bitmessage_messages.getdata.html b/docs/module-bitmessage_messages.getdata.html
index 8d43103..88efb0d 100644
--- a/docs/module-bitmessage_messages.getdata.html
+++ b/docs/module-bitmessage_messages.getdata.html
@@ -78,7 +78,7 @@ content of a specific object after filtering known elements.
Identifies protocol version being used
-by the node. Should equal 3. Nodes should disconnect if the remote
-node's version is lower but continue with the connection if it is
-higher.
+
Identifies protocol version being
+used by the node. Should equal 3. Nodes should disconnect if the
+remote node's version is lower but continue with the connection if
+it is higher.
@@ -1104,8 +1180,8 @@ message
-
Random nonce used to detect connection
-to self
+
An 8-byte random nonce used to detect
+connection to self
@@ -872,7 +872,7 @@ argument is a string)
diff --git a/docs/module-bitmessage_net_base.html b/docs/module-bitmessage_net_base.html
index be79221..27f0a2b 100644
--- a/docs/module-bitmessage_net_base.html
+++ b/docs/module-bitmessage_net_base.html
@@ -164,7 +164,7 @@ instead in order to connect/accept connections to/from other nodes. N
diff --git a/docs/module-bitmessage_net_tcp.TcpTransport.html b/docs/module-bitmessage_net_tcp.TcpTransport.html
index c1e48c5..67c9a25 100644
--- a/docs/module-bitmessage_net_tcp.TcpTransport.html
+++ b/docs/module-bitmessage_net_tcp.TcpTransport.html
@@ -234,14 +234,14 @@ provided by this node (NODE_NETWORK by default)
User agent of this node
-(bitmessage's by default)
+(user agent of bitmessage library by default)
-
streamNumbers
+
streams
@@ -257,8 +257,8 @@ provided by this node (NODE_NETWORK by default)
Service features
-provided by this node (NODE_NETWORK by default)
+provided by this node (NODE_MOBILE for Browser and NODE_MOBILE +
+NODE_GATEWAY for Node by default)
@@ -212,14 +213,14 @@ provided by this node (NODE_NETWORK by default)
User agent of this node
-(bitmessage's by default)
+(user agent of bitmessage library by default)
-
streamNumbers
+
streams
@@ -235,8 +236,8 @@ provided by this node (NODE_NETWORK by default)
-
Streams accepted by this node
-(1 by default)
+
Streams accepted by this node ([1]
+by default)
@@ -259,8 +260,8 @@ provided by this node (NODE_NETWORK by default)
-
Incoming port of this node (8444 by
-default)
+
Incoming port of this node, makes sence
+only on Node platform (18444 by default)
@@ -542,7 +543,7 @@ Available only for Node platform.
diff --git a/docs/module-bitmessage_net_ws.html b/docs/module-bitmessage_net_ws.html
index 66c957d..d93192e 100644
--- a/docs/module-bitmessage_net_ws.html
+++ b/docs/module-bitmessage_net_ws.html
@@ -164,7 +164,7 @@ packets. Available for both Node.js and Browser platforms. NOTE
diff --git a/docs/module-bitmessage_objects.broadcast.html b/docs/module-bitmessage_objects.broadcast.html
index 570f71d..23eb44b 100644
--- a/docs/module-bitmessage_objects.broadcast.html
+++ b/docs/module-bitmessage_objects.broadcast.html
@@ -1586,7 +1586,7 @@ unparsed buffer data for other encodings
diff --git a/docs/module-bitmessage_objects.getpubkey.html b/docs/module-bitmessage_objects.getpubkey.html
index 65d591b..3db13d3 100644
--- a/docs/module-bitmessage_objects.getpubkey.html
+++ b/docs/module-bitmessage_objects.getpubkey.html
@@ -1090,7 +1090,7 @@ for address version >= 4
diff --git a/docs/module-bitmessage_objects.html b/docs/module-bitmessage_objects.html
index efc36ad..3fb7cfa 100644
--- a/docs/module-bitmessage_objects.html
+++ b/docs/module-bitmessage_objects.html
@@ -419,7 +419,7 @@ then call decode function of the appropriate object handler.
diff --git a/docs/module-bitmessage_objects.msg.html b/docs/module-bitmessage_objects.msg.html
index 8cea95a..c1132f3 100644
--- a/docs/module-bitmessage_objects.msg.html
+++ b/docs/module-bitmessage_objects.msg.html
@@ -1896,7 +1896,7 @@ unparsed buffer data for other encodings
diff --git a/docs/module-bitmessage_objects.pubkey.html b/docs/module-bitmessage_objects.pubkey.html
index f6e853f..46d6fc2 100644
--- a/docs/module-bitmessage_objects.pubkey.html
+++ b/docs/module-bitmessage_objects.pubkey.html
@@ -1296,7 +1296,7 @@ only for pubkey version >= 3)
diff --git a/docs/module-bitmessage_pow.html b/docs/module-bitmessage_pow.html
index b7d5402..afa39df 100644
--- a/docs/module-bitmessage_pow.html
+++ b/docs/module-bitmessage_pow.html
@@ -1095,7 +1095,7 @@ raised to 1000.
diff --git a/docs/module-bitmessage_structs.PubkeyBitfield.html b/docs/module-bitmessage_structs.PubkeyBitfield.html
index c9a9aa8..7216c3f 100644
--- a/docs/module-bitmessage_structs.PubkeyBitfield.html
+++ b/docs/module-bitmessage_structs.PubkeyBitfield.html
@@ -190,7 +190,7 @@ not provided or will be copied if opts.copy is true)
@@ -804,7 +804,7 @@ bound for them.
diff --git a/docs/module-bitmessage_structs.ServicesBitfield.html b/docs/module-bitmessage_structs.ServicesBitfield.html
index 3a23de0..609004b 100644
--- a/docs/module-bitmessage_structs.ServicesBitfield.html
+++ b/docs/module-bitmessage_structs.ServicesBitfield.html
@@ -190,7 +190,7 @@ not provided or will be copied if opts.copy is true)
@@ -612,7 +612,7 @@
diff --git a/docs/module-bitmessage_user-agent.html b/docs/module-bitmessage_user-agent.html
index 7af304b..194789b 100644
--- a/docs/module-bitmessage_user-agent.html
+++ b/docs/module-bitmessage_user-agent.html
@@ -474,7 +474,7 @@ software to encode or just raw user agent string/Buffer
-
Encode bitmessage's library user agent.
+
Encode user agent of bitmessage library.
@@ -578,7 +578,7 @@ software to encode or just raw user agent string/Buffer
-
Encode user agent with bitmessage's library user agent underneath.
+
Encode user agent with user agent of bitmessage library underneath.
Most underlying software comes first.
@@ -907,7 +907,7 @@ format because it's not that important.
diff --git a/docs/module-bitmessage_wif.html b/docs/module-bitmessage_wif.html
index 9c30659..887cdc6 100644
--- a/docs/module-bitmessage_wif.html
+++ b/docs/module-bitmessage_wif.html
@@ -469,7 +469,7 @@ key).
diff --git a/docs/net_base.js.html b/docs/net_base.js.html
index 585b46c..c270f0a 100644
--- a/docs/net_base.js.html
+++ b/docs/net_base.js.html
@@ -40,8 +40,13 @@
var inherits = require("inherits");
var EventEmitter = require("events").EventEmitter;
+var bufferEqual = require("buffer-equal");
+var util = require("../_util");
var PPromise = require("../platform").Promise;
var structs = require("../structs");
+var messages = require("../messages");
+
+var ServicesBitfield = structs.ServicesBitfield;
/**
* Base transport class. Allows to use single class for both client and
@@ -119,8 +124,9 @@ BaseTransport.prototype.close = function() {
throw new Error("Not implemented");
};
-// Static private helpers.
+// Private helpers.
+// Make a message from variable number of arguments.
BaseTransport._getmsg = function(args) {
if (typeof args[0] === "string") {
return structs.message.encode(args[0], args[1]);
@@ -138,6 +144,69 @@ BaseTransport._unmap = function(addr) {
}
};
+// Check whether two given arrays intersect.
+// NOTE(Kagami): It has O(n*m) complexity in the worst case but:
+// * Max length of stream list = 160,000
+// * One of the arrays (our streams) should have reasonable length
+function intersects(a, b) {
+ var alen = a.length;
+ var blen = b.length;
+ if (!alen || !blen) {
+ return false;
+ }
+ var i, j;
+ for (i = 0; i < alen; ++i) {
+ for (j = 0; j < blen; ++j) {
+ if (a[i] === b[j]) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+// Decode and validate version message.
+BaseTransport.prototype._decodeVersion = function(payload, opts) {
+ opts = opts || {};
+ var version;
+ try {
+ version = messages.version.decodePayload(payload);
+ } catch(err) {
+ throw new Error("Version decode error: " + err.message);
+ }
+ if (version.version < util.PROTOCOL_VERSION) {
+ throw new Error("Peer uses old protocol v" + version.version);
+ }
+ // TODO(Kagami): We may want to send error message describing the time
+ // offset problem to this node as PyBitmessage.
+ var delta = (version.time.getTime() - new Date().getTime()) / 1000;
+ if (delta > 3600) {
+ throw new Error("Peer's time is too far in the future: +" + delta + "s");
+ }
+ if (delta < -3600) {
+ throw new Error("Peer's time is too far in the past: " + delta + "s");
+ }
+ if (bufferEqual(version.nonce, messages.version.randomNonce)) {
+ throw new Error("Connection to self");
+ }
+ if (!intersects(this.streams, version.streams)) {
+ throw new Error(
+ "Peer isn't interested in our streams; " +
+ "first 10 peer's streams: " + version.streams.slice(0, 10)
+ );
+ }
+ if (opts.network && !version.services.get(ServicesBitfield.NODE_NETWORK)) {
+ throw new Error("Not a normal network node: " + version.services);
+ }
+ if (opts.gateway && !version.services.get(ServicesBitfield.NODE_GATEWAY)) {
+ throw new Error("Not a gateway node: " + version.services);
+ }
+ if (opts.mobile && !version.services.get(ServicesBitfield.NODE_MOBILE)) {
+ throw new Error("Not a mobile node: " + version.services);
+ }
+ return version;
+};
+
module.exports = BaseTransport;
@@ -155,7 +224,7 @@ module.exports = BaseTransport;
diff --git a/docs/net_tcp.js.html b/docs/net_tcp.js.html
index 4ae9ee6..5b086e7 100644
--- a/docs/net_tcp.js.html
+++ b/docs/net_tcp.js.html
@@ -66,12 +66,13 @@ var objectAssign = Object.assign || require("object-assign");
var inherits = require("inherits");
var net = require("net");
var dns = require("dns");
-var assert = require("../_util").assert;
+var util = require("../_util");
var PPromise = require("../platform").Promise;
var structs = require("../structs");
var messages = require("../messages");
var BaseTransport = require("./base");
+var assert = util.assert;
var getmsg = BaseTransport._getmsg;
var unmap = BaseTransport._unmap;
@@ -86,9 +87,9 @@ var unmap = BaseTransport._unmap;
* provided by this node (`NODE_NETWORK` by default)
* @param {(Array|string|Buffer)} opts.userAgent -
* [User agent]{@link module:bitmessage/user-agent} of this node
- * (bitmessage's by default)
- * @param {number[]} opts.streamNumbers - Streams accepted by this node
- * (1 by default)
+ * (user agent of bitmessage library by default)
+ * @param {number[]} opts.streams - Streams accepted by this node ([1]
+ * by default)
* @param {number} opts.port - Incoming port of this node (8444 by
* default)
* @constructor
@@ -99,27 +100,24 @@ function TcpTransport(opts) {
objectAssign(this, opts);
this.seeds = this.seeds || [];
this.dnsSeeds = this.dnsSeeds || [];
+ this.streams = this.streams || [1];
this._clients = {};
}
inherits(TcpTransport, BaseTransport);
-function getfrom(client) {
- return unmap(client.remoteAddress) + ":" + client.remotePort;
-}
-
TcpTransport.prototype._sendVersion = function() {
return this.send(messages.version.encode({
services: this.services,
userAgent: this.userAgent,
- streamNumbers: this.streamNumbers,
+ streams: this.streams,
port: this.port,
remoteHost: this._client.remoteAddress,
remotePort: this._client.remotePort,
}));
};
-TcpTransport.prototype._setupClient = function(client, accepted) {
+TcpTransport.prototype._setupClient = function(client, incoming) {
var self = this;
self._client = client;
var cache = Buffer(0);
@@ -135,8 +133,8 @@ TcpTransport.prototype._setupClient = function(client, accepted) {
client.on("connect", function() {
// NOTE(Kagami): This handler shouldn't be called at all for
- // accepted sockets but let's be sure.
- if (!accepted) {
+ // incoming connections but let's be sure.
+ if (!incoming) {
self.emit("open");
self._sendVersion();
}
@@ -155,43 +153,43 @@ TcpTransport.prototype._setupClient = function(client, accepted) {
}
cache = decoded.rest;
if (decoded.message) {
- self.emit(
- "message",
- decoded.message.command,
- decoded.message.payload,
- decoded.message);
+ self.emit("message", decoded.message.command, decoded.message.payload);
} else if (decoded.error) {
// TODO(Kagami): Wrap it in custom error class?
// TODO(Kagami): Send `error` message and ban node for some time
// if there were too many errors?
self.emit("warning", new Error(
- "Message decoding error from " + getfrom(client) + ": " +
- decoded.error
+ "Message decoding error: " + decoded.error.message
));
}
}
});
// High-level message processing.
- self.on("message", function(command) {
+ self.on("message", function(command, payload) {
+ var version;
if (!established) {
- // TODO: Process version data.
- // TODO: Disconnect if proto version < 3.
if (command === "version") {
if (verackSent) {
return;
}
+ try {
+ version = self._decodeVersion(payload, {network: true});
+ } catch(err) {
+ self.emit("error", err);
+ return client.end();
+ }
self.send("verack");
verackSent = true;
- if (accepted) {
+ if (incoming) {
self._sendVersion();
} else if (verackReceived) {
- self.emit("established");
+ self.emit("established", version);
}
} else if (command === "verack") {
verackReceived = true;
if (verackSent) {
- self.emit("established");
+ self.emit("established", version);
}
}
}
@@ -248,19 +246,28 @@ function resolveDnsSeed(seed) {
}
TcpTransport.prototype.bootstrap = function() {
- var promises = this.dnsSeeds.map(resolveDnsSeed);
var hardcodedNodes = this.seeds;
// FIXME(Kagami): Filter incorrect/private IP range nodes?
// See also: <https://github.com/Bitmessage/PyBitmessage/issues/768>.
- return PPromise.all(promises).then(function(dnsNodes) {
- // Flatten array of arrays.
- dnsNodes = Array.prototype.concat.apply([], dnsNodes);
+ return this.bootstrapDns().then(function(dnsNodes) {
// Add hardcoded nodes to the end of list because DNS nodes should
// be more up-to-date.
return dnsNodes.concat(hardcodedNodes);
});
};
+/**
+ * Do only DNS-specific bootstrap.
+ * @return {Promise.<Array>} Discovered seed nodes.
+ */
+TcpTransport.prototype.bootstrapDns = function() {
+ var promises = this.dnsSeeds.map(resolveDnsSeed);
+ return PPromise.all(promises).then(function(dnsNodes) {
+ // Flatten array of arrays.
+ return Array.prototype.concat.apply([], dnsNodes);
+ });
+};
+
/**
* Connect to a TCP node. Connection arguments are the same as for
* [net.connect](http://nodejs.org/api/net.html#net_net_connect_port_host_connectlistener).
@@ -284,31 +291,22 @@ TcpTransport.prototype.listen = function() {
var server = self._server = net.createServer();
server.listen.apply(server, arguments);
- // TODO(Kagami): We may want to specify some limits for number of
- // connected users.
+ var clientIdCounter = 0;
+
server.on("connection", function(client) {
+ var id = client.id = clientIdCounter++;
+ self._clients[id] = client;
+ client.on("close", function() {
+ delete self._clients[id];
+ });
+ var opts = objectAssign({}, self);
+ delete opts._server;
+ var transport = new self.constructor(opts);
+ var incoming = true;
+ transport._setupClient(client, incoming);
var addr = client.remoteAddress;
var port = client.remotePort;
- if (self._clients[addr]) {
- // NOTE(Kagami): Doesn't allow more than one connection per IP.
- // This may obstruct people behind NAT but we copy PyBitmessage's
- // behavior here.
- client.end();
- return self.emit("warning", new Error(
- unmap(addr) + " was tried to create second connection"
- ));
- }
- self._clients[addr] = client;
- client.on("close", function() {
- delete self._clients[addr];
- });
- var transport = new self.constructor(self);
- var accepted = true;
- transport._setupClient(client, accepted);
self.emit("connection", transport, unmap(addr), port);
- // Emit "open" manually because "connect" for this socket won't be
- // fired.
- transport.emit("open");
});
server.on("error", function(err) {
@@ -332,8 +330,8 @@ TcpTransport.prototype.send = function() {
TcpTransport.prototype.broadcast = function() {
var data = getmsg(arguments);
if (this._server) {
- Object.keys(this._clients).forEach(function(ip) {
- this._clients[ip].write(data);
+ Object.keys(this._clients).forEach(function(id) {
+ this._clients[id].write(data);
}, this);
} else {
throw new Error("Not listening");
@@ -344,8 +342,8 @@ TcpTransport.prototype.close = function() {
if (this._client) {
this._client.end();
} else if (this._server) {
- Object.keys(this._clients).forEach(function(ip) {
- this._clients[ip].end();
+ Object.keys(this._clients).forEach(function(id) {
+ this._clients[id].end();
}, this);
this._server.close();
}
@@ -368,7 +366,7 @@ module.exports = TcpTransport;
diff --git a/docs/net_ws.js.html b/docs/net_ws.js.html
index 9e5d581..3ead8ba 100644
--- a/docs/net_ws.js.html
+++ b/docs/net_ws.js.html
@@ -47,6 +47,7 @@ var messages = require("../messages");
var BaseTransport = require("./base");
var WebSocketServer = WebSocket.Server;
+var ServicesBitfield = structs.ServicesBitfield;
var getmsg = BaseTransport._getmsg;
var unmap = BaseTransport._unmap;
@@ -58,14 +59,15 @@ var unmap = BaseTransport._unmap;
* @param {Array} opts.seeds - Bootstrap nodes (none by default)
* @param {Object} opts.services -
* [Service features]{@link module:bitmessage/structs.ServicesBitfield}
- * provided by this node (`NODE_NETWORK` by default)
+ * provided by this node (`NODE_MOBILE` for Browser and `NODE_MOBILE` +
+ * `NODE_GATEWAY` for Node by default)
* @param {(Array|string|Buffer)} opts.userAgent -
* [User agent]{@link module:bitmessage/user-agent} of this node
- * (bitmessage's by default)
- * @param {number[]} opts.streamNumbers - Streams accepted by this node
- * (1 by default)
- * @param {number} opts.port - Incoming port of this node (8444 by
- * default)
+ * (user agent of bitmessage library by default)
+ * @param {number[]} opts.streams - Streams accepted by this node ([1]
+ * by default)
+ * @param {number} opts.port - Incoming port of this node, makes sence
+ * only on Node platform (18444 by default)
* @constructor
* @static
*/
@@ -73,19 +75,21 @@ function WsTransport(opts) {
WsTransport.super_.call(this);
objectAssign(this, opts);
this.seeds = this.seeds || [];
+ this.services = this.services || ServicesBitfield().set([
+ ServicesBitfield.NODE_MOBILE,
+ ServicesBitfield.NODE_GATEWAY,
+ ]);
+ this.streams = this.streams || [1];
+ this.port = this.port || 18444;
}
inherits(WsTransport, BaseTransport);
-function getfrom(client) {
- return unmap(client._socket.remoteAddress) + ":" + client._socket.remotePort;
-}
-
WsTransport.prototype._sendVersion = function() {
return this.send(messages.version.encode({
services: this.services,
userAgent: this.userAgent,
- streamNumbers: this.streamNumbers,
+ streams: this.streams,
port: this.port,
remoteHost: this._client._socket.remoteAddress,
remotePort: this._client._socket.remotePort,
@@ -107,7 +111,7 @@ WsTransport.prototype._handleTimeout = function() {
});
};
-WsTransport.prototype._setupClient = function(client, accepted) {
+WsTransport.prototype._setupClient = function(client, incoming) {
var self = this;
self._client = client;
var verackSent = false;
@@ -116,8 +120,8 @@ WsTransport.prototype._setupClient = function(client, accepted) {
client.on("open", function() {
// NOTE(Kagami): This handler shouldn't be called at all for
- // accepted sockets but let's be sure.
- if (!accepted) {
+ // incoming connections but let's be sure.
+ if (!incoming) {
// NOTE(Kagami): We may set timeout only after connection was
// opened because socket may not yet be available when
// `_setupClient` is called.
@@ -132,42 +136,46 @@ WsTransport.prototype._setupClient = function(client, accepted) {
if (!flags.binary) {
// TODO(Kagami): Send `error` message and ban node for some time
// if there were too many errors?
- return self.emit("warning", new Error(
- "Peer " + getfrom(client) + " sent non-binary data"
- ));
+ return self.emit("warning", new Error("Peer sent non-binary data"));
}
try {
decoded = structs.message.decode(data);
} catch (err) {
return self.emit("warning", new Error(
- "Message decoding error from " + getfrom(client) + ": " + err
+ "Message decoding error: " + err.message
));
}
- self.emit("message", decoded.command, decoded.payload, decoded);
+ self.emit("message", decoded.command, decoded.payload);
});
// High-level message processing.
- self.on("message", function(command) {
+ self.on("message", function(command, payload) {
+ var version;
+ var veropts = incoming ? {mobile: true} : {gateway: true};
if (!established) {
- // TODO: Process version data.
- // TODO: Disconnect if proto version < 3.
if (command === "version") {
if (verackSent) {
return;
}
+ try {
+ version = self._decodeVersion(payload, veropts);
+ } catch(err) {
+ self.emit("error", err);
+ return client.close();
+ }
self.send("verack");
verackSent = true;
- if (accepted) {
+ if (incoming) {
self._sendVersion();
} else if (verackReceived) {
established = true;
- self.emit("established");
+ self.emit("established", version);
}
} else if (command === "verack") {
verackReceived = true;
if (verackSent) {
established = true;
- self.emit("established");
+ self.emit("established", version);
}
}
}
@@ -212,42 +220,27 @@ WsTransport.prototype.listen = function(options, callback) {
var self = this;
var server = self._server = new WebSocketServer(options, callback);
- // TODO(Kagami): We may want to specify some limits for number of
- // connected users.
server.on("connection", function(client) {
- var addr = client._socket.remoteAddress;
- var port = client._remotePort;
- var i;
-
- // NOTE(Kagami): O(n) search because `clients` array is already
- // provided by `ws` library. We may want to optmize it though and
- // also disable `clientTracking` option.
- for (i = 0; i < server.clients.length; i++) {
- if (server.clients[i] !== client &&
- server.clients[i]._socket.remoteAddress === addr) {
- // NOTE(Kagami): Doesn't allow more than one connection per IP.
- // This may obstruct people behind NAT but we copy
- // PyBitmessage's behavior here.
- client.close();
- return self.emit("warning", new Error(
- unmap(addr) + " was tried to create second connection"
- ));
- }
- }
-
- var transport = new self.constructor(self);
- var accepted = true;
- transport._setupClient(client, accepted);
+ var opts = objectAssign({}, self);
+ delete opts._server;
+ var transport = new self.constructor(opts);
+ var incoming = true;
+ transport._setupClient(client, incoming);
transport._handleTimeout();
+ var addr = client._socket.remoteAddress;
+ var port = client._socket.remotePort;
self.emit("connection", transport, unmap(addr), port);
- // Emit "open" manually because it won't be fired for already opened
- // socket.
- transport.emit("open");
});
server.on("error", function(err) {
self.emit("error", err);
});
+
+ // `ws` doesn't emit "close" event by default.
+ server._server.on("close", function() {
+ self.emit("close");
+ delete self._server;
+ });
};
WsTransport.prototype.send = function() {
@@ -275,10 +268,7 @@ WsTransport.prototype.close = function() {
if (this._client) {
this._client.close();
} else if (this._server) {
- // `ws` server terminates immediately without any events.
this._server.close();
- this.emit("close");
- delete this._server;
}
};
@@ -299,7 +289,7 @@ module.exports = WsTransport;
diff --git a/docs/objects.js.html b/docs/objects.js.html
index c9a0de1..977bc67 100644
--- a/docs/objects.js.html
+++ b/docs/objects.js.html
@@ -1176,7 +1176,7 @@ var broadcast = exports.broadcast = {
diff --git a/docs/pow.js.html b/docs/pow.js.html
index 8f633a0..6ccbcb8 100644
--- a/docs/pow.js.html
+++ b/docs/pow.js.html
@@ -156,7 +156,7 @@ exports.doAsync = function(opts) {
diff --git a/docs/structs.js.html b/docs/structs.js.html
index 44538ef..37f47b1 100644
--- a/docs/structs.js.html
+++ b/docs/structs.js.html
@@ -344,12 +344,13 @@ var object = exports.object = {
* @param {Object=} opts - Decoding options
* @param {boolean} opts.allowExpired - Allow expired objects
* @param {boolean} opts.skipPow - Do not validate object POW
- * @return {DecodeResult}
- * [Decoded `object` structure.]{@link module:bitmessage/structs.object.DecodeResult}
+ * @return {DecodeResult} [Decoded `object` structure.]{@link
+ * module:bitmessage/structs.object.DecodeResult}
+ * @throws {Error} Invalid object
*/
decode: function(buf, opts) {
var decoded = message.decode(buf);
- assert(decoded.command === "object", "Bad command");
+ assert(decoded.command === "object", "Given message is not an object");
return object.decodePayload(decoded.payload, opts);
},
@@ -359,11 +360,15 @@ var object = exports.object = {
*/
decodePayload: function(buf, opts) {
opts = opts || {};
+
// 8 + 8 + 4 + (1+) + (1+)
assert(buf.length >= 22, "object message payload is too small");
assert(buf.length <= 262144, "object message payload is too big");
- var nonce = new Buffer(8);
- buf.copy(nonce, 0, 0, 8);
+ var nonce;
+ if (!opts._validate) {
+ nonce = new Buffer(8);
+ buf.copy(nonce, 0, 0, 8);
+ }
// TTL.
var expiresTime = util.readTimestamp64BE(buf.slice(8, 16));
@@ -386,6 +391,9 @@ var object = exports.object = {
var decodedVersion = var_int.decode(buf.slice(20));
var decodedStream = var_int.decode(decodedVersion.rest);
var headerLength = 20 + decodedVersion.length + decodedStream.length;
+
+ if (opts._validate) { return; }
+
var objectPayload = new Buffer(decodedStream.rest.length);
decodedStream.rest.copy(objectPayload);
@@ -400,6 +408,41 @@ var object = exports.object = {
};
},
+ /**
+ * Check whether given `object` message is valid.
+ * @param {Buffer} buf - Message
+ * @param {Object=} opts - Any of [object.decode]{@link
+ * module:bitmessage/structs.object.decode} options
+ * @return {?Error} Return an error with description if object is
+ * invalid.
+ */
+ validate: function(buf, opts) {
+ var decoded;
+ try {
+ decoded = message.decode(buf);
+ } catch(e) {
+ return e;
+ }
+ if (decoded.command !== "object") {
+ return new Error("Given message is not an object");
+ }
+ return object.validatePayload(decoded.payload, opts);
+ },
+
+ /**
+ * Check whether `object` message payload is valid.
+ * The same as [validate]{@link
+ * module:bitmessage/structs.object.validate}.
+ */
+ validatePayload: function(buf, opts) {
+ opts = objectAssign({}, opts, {_validate: true});
+ try {
+ object.decodePayload(buf, opts);
+ } catch(e) {
+ return e;
+ }
+ },
+
/**
* Encode `object` message.
* @param {Object} opts - Object options
@@ -969,6 +1012,16 @@ var Bitfield = function(size) {
return this;
};
+ BitfieldInner.prototype.toString = function() {
+ var i;
+ var str = "";
+ for (i = 0; i < this.buffer.length; i++) {
+ // Should be faster than pushing to array and joining on v8.
+ str += ("0000000" + this.buffer[i].toString(2)).slice(-8);
+ }
+ return "<Bitfield:" + str + ">";
+ };
+
return BitfieldInner;
};
@@ -1020,6 +1073,22 @@ var ServicesBitfield = exports.ServicesBitfield = objectAssign(Bitfield(64), {
* @constant {number}
*/
NODE_NETWORK: 63,
+ /**
+ * Bit index indicating web/mobile client with limited network
+ * capabilities (proposal feature).
+ * @memberof module:bitmessage/structs.ServicesBitfield
+ * @see {@link https://bitmessage.org/wiki/Mobile_Protocol_specification}
+ * @constant {number}
+ */
+ NODE_MOBILE: 62,
+ /**
+ * Bit index indicating node which can work as a WebSocket gateway for
+ * web/mobile clients (proposal feature).
+ * @memberof module:bitmessage/structs.ServicesBitfield
+ * @see {@link https://bitmessage.org/wiki/Mobile_Protocol_specification}
+ * @constant {number}
+ */
+ NODE_GATEWAY: 61,
});
/**
@@ -1063,6 +1132,14 @@ exports.PubkeyBitfield = objectAssign(Bitfield(32), {
* @memberof module:bitmessage/structs.PubkeyBitfield
*/
+ /**
+ * Bit index.
+ * If set, the receiving node does send acknowledgements (rather than
+ * dropping them).
+ * @memberof module:bitmessage/structs.PubkeyBitfield
+ * @constant {number}
+ */
+ DOES_ACK: 31,
/**
* Bit index.
* If set, the receiving node expects that the RIPEMD hash encoded in
@@ -1072,14 +1149,6 @@ exports.PubkeyBitfield = objectAssign(Bitfield(32), {
* @constant {number}
*/
INCLUDE_DESTINATION: 30,
- /**
- * Bit index.
- * If set, the receiving node does send acknowledgements (rather than
- * dropping them).
- * @memberof module:bitmessage/structs.PubkeyBitfield
- * @constant {number}
- */
- DOES_ACK: 31,
});
@@ -1097,7 +1166,7 @@ exports.PubkeyBitfield = objectAssign(Bitfield(32), {
diff --git a/docs/user-agent.js.html b/docs/user-agent.js.html
index 6c0845c..a0a29b7 100644
--- a/docs/user-agent.js.html
+++ b/docs/user-agent.js.html
@@ -120,7 +120,7 @@ var encode = exports.encode = function(software) {
};
/**
- * Encode bitmessage's library user agent.
+ * Encode user agent of bitmessage library.
* @return {Buffer} Encoded user agent.
*/
exports.encodeSelf = function() {
@@ -128,7 +128,7 @@ exports.encodeSelf = function() {
};
/**
- * Encode user agent with bitmessage's library user agent underneath.
+ * Encode user agent with user agent of bitmessage library underneath.
* Most underlying software comes first.
* @param {(Object[]|string[]|Object|string)} software - List of
* software to encode
@@ -154,7 +154,7 @@ exports.encodeSelfWith = function(software) {
diff --git a/docs/wif.js.html b/docs/wif.js.html
index 54b3261..ceb8457 100644
--- a/docs/wif.js.html
+++ b/docs/wif.js.html
@@ -87,7 +87,7 @@ exports.encode = function(privateKey) {