parent
dd8648e582
commit
84ad9eb2c2
|
@ -52,10 +52,6 @@ API documentation is available [here](https://bitchan.github.io/bitmessage/docs/
|
||||||
- [x] High-level classes
|
- [x] High-level classes
|
||||||
- [x] Address
|
- [x] Address
|
||||||
- [x] UserAgent
|
- [x] UserAgent
|
||||||
- [ ] Network transports
|
|
||||||
- [x] TCP (Node.js only)
|
|
||||||
- [x] WebSocket
|
|
||||||
- [ ] WebRTC
|
|
||||||
- [ ] PyBitmessage configs parsing
|
- [ ] PyBitmessage configs parsing
|
||||||
- [ ] keys.dat
|
- [ ] keys.dat
|
||||||
- [ ] knownnodes.dat
|
- [ ] knownnodes.dat
|
||||||
|
@ -129,9 +125,11 @@ console.log(structs.message.decode(verackmsg).command); // verack
|
||||||
|
|
||||||
### Networking
|
### Networking
|
||||||
|
|
||||||
|
You will need to install [bitmessage-transports](https://github.com/bitchan/bitmessage-transports) library.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
var messages = require("bitmessage").messages;
|
var messages = require("bitmessage").messages;
|
||||||
var TcpTransport = require("bitmessage/lib/net/tcp");
|
var TcpTransport = require("bitmessage-transports").TcpTransport;
|
||||||
|
|
||||||
var tcp = new TcpTransport({
|
var tcp = new TcpTransport({
|
||||||
dnsSeeds: [["bootstrap8444.bitmessage.org", 8444]],
|
dnsSeeds: [["bootstrap8444.bitmessage.org", 8444]],
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
process.env.CHROME_BIN = "chromium-browser";
|
process.env.CHROME_BIN = "chromium-browser";
|
||||||
var allTests = process.env.ALL_TESTS === "1";
|
var allTests = process.env.ALL_TESTS === "1";
|
||||||
if (process.env.TEST_MODE !== "unit") require("./tests/run-test-nodes.js")();
|
|
||||||
|
|
||||||
module.exports = function(config) {
|
module.exports = function(config) {
|
||||||
config.set({
|
config.set({
|
||||||
|
@ -15,20 +14,17 @@ module.exports = function(config) {
|
||||||
|
|
||||||
|
|
||||||
// list of files / patterns to load in the browser
|
// list of files / patterns to load in the browser
|
||||||
files: ["tests/index.js"],
|
files: ["test.js"],
|
||||||
|
|
||||||
|
|
||||||
// preprocess matching files before serving them to the browser
|
// preprocess matching files before serving them to the browser
|
||||||
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
|
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
|
||||||
preprocessors: {
|
preprocessors: {
|
||||||
"tests/index.js": ["browserify", "env"],
|
"test.js": ["browserify", "env"],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
envPreprocessor: [
|
envPreprocessor: ["ALL_TESTS"],
|
||||||
"ALL_TESTS",
|
|
||||||
"TEST_MODE",
|
|
||||||
],
|
|
||||||
|
|
||||||
|
|
||||||
// list of files to exclude
|
// list of files to exclude
|
||||||
|
|
182
lib/net/base.js
182
lib/net/base.js
|
@ -1,182 +0,0 @@
|
||||||
/**
|
|
||||||
* Networking base module. Defines base transport interface, useful for
|
|
||||||
* implementing new transports. End-users should import some transport
|
|
||||||
* instead in order to connect/accept connections to/from other nodes.
|
|
||||||
* **NOTE**: `BaseTransport` is exported as a module.
|
|
||||||
* @example var BaseTransport = require("bitmessage/lib/net/base");
|
|
||||||
* @module bitmessage/net/base
|
|
||||||
*/
|
|
||||||
// TODO(Kagami): Write some sort of tutorial.
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
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
|
|
||||||
* server modes (as separate instances).
|
|
||||||
* @constructor
|
|
||||||
* @static
|
|
||||||
*/
|
|
||||||
function BaseTransport() {
|
|
||||||
BaseTransport.super_.call(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
inherits(BaseTransport, EventEmitter);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Do the transport-specific bootstrap process and return promise that
|
|
||||||
* contains discovered nodes when fulfilled (both modes).
|
|
||||||
* NOTE: Do not use nodes received by this method in `addr` messages!
|
|
||||||
* This is meaningless.
|
|
||||||
* @return {Promise.<Array>}
|
|
||||||
* @abstract
|
|
||||||
*/
|
|
||||||
BaseTransport.prototype.bootstrap = function() {
|
|
||||||
return PPromise.reject(new Error("Not implemented"));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connect to the transport-specific address. Enters client mode. Should
|
|
||||||
* emit `open` event after successful connect and `established` event
|
|
||||||
* after `verack` messages exchange.
|
|
||||||
* @abstract
|
|
||||||
*/
|
|
||||||
BaseTransport.prototype.connect = function() {
|
|
||||||
throw new Error("Not implemented");
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Listen for the transport-specific incoming connections. Enters server
|
|
||||||
* mode. Should emit `connection` event with a transport instance for
|
|
||||||
* each new connection.
|
|
||||||
* @abstract
|
|
||||||
*/
|
|
||||||
BaseTransport.prototype.listen = function() {
|
|
||||||
throw new Error("Not implemented");
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send [message]{@link module:bitmessage/structs.message} over the
|
|
||||||
* wire (client mode only).
|
|
||||||
* @param {(Buffer|string)} msg - Encoded message or command string
|
|
||||||
* @param {Buffer=} payload - Message payload (used if the first
|
|
||||||
* argument is a string)
|
|
||||||
* @abstract
|
|
||||||
*/
|
|
||||||
BaseTransport.prototype.send = function() {
|
|
||||||
throw new Error("Not implemented");
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send [message]{@link module:bitmessage/structs.message} to all
|
|
||||||
* connected clients (server mode only).
|
|
||||||
* @param {(Buffer|string)} msg - Encoded message or command string
|
|
||||||
* @param {Buffer=} payload - Message payload (used if the first
|
|
||||||
* argument is a string)
|
|
||||||
* @abstract
|
|
||||||
*/
|
|
||||||
BaseTransport.prototype.broadcast = function() {
|
|
||||||
throw new Error("Not implemented");
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Close connection(s) and/or stop listening (both modes).
|
|
||||||
* @abstract
|
|
||||||
*/
|
|
||||||
BaseTransport.prototype.close = function() {
|
|
||||||
throw new Error("Not implemented");
|
|
||||||
};
|
|
||||||
|
|
||||||
// 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]);
|
|
||||||
} else {
|
|
||||||
return args[0];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Unmap IPv4-mapped IPv6 address.
|
|
||||||
BaseTransport._unmap = function(addr) {
|
|
||||||
if (addr.slice(0, 7) === "::ffff:") {
|
|
||||||
return addr.slice(7);
|
|
||||||
} else {
|
|
||||||
return 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;
|
|
324
lib/net/tcp.js
324
lib/net/tcp.js
|
@ -1,324 +0,0 @@
|
||||||
/**
|
|
||||||
* TCP transport compatible with PyBitmessage. Available only for Node
|
|
||||||
* platform.
|
|
||||||
* **NOTE**: `TcpTransport` is exported as a module.
|
|
||||||
* @module bitmessage/net/tcp
|
|
||||||
* @example
|
|
||||||
* var messages = require("bitmessage").messages;
|
|
||||||
* var TcpTransport = require("bitmessage/lib/net/tcp");
|
|
||||||
*
|
|
||||||
* var tcp = new TcpTransport({
|
|
||||||
* dnsSeeds: [["bootstrap8444.bitmessage.org", 8444]],
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* tcp.bootstrap().then(function(nodes) {
|
|
||||||
* var remoteHost = nodes[0][0];
|
|
||||||
* var remotePort = nodes[0][1];
|
|
||||||
* console.log("Connecting to", nodes[0]);
|
|
||||||
* tcp.connect(remotePort, remoteHost);
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* tcp.on("established", function(version) {
|
|
||||||
* console.log("Connection established to", version.userAgent);
|
|
||||||
*
|
|
||||||
* tcp.on("message", function(command, payload) {
|
|
||||||
* console.log("Got new", command, "message");
|
|
||||||
* var decoded;
|
|
||||||
* if (command === "addr") {
|
|
||||||
* decoded = messages.addr.decodePayload(payload);
|
|
||||||
* console.log("Got", decoded.addrs.length, "node addresses");
|
|
||||||
* }
|
|
||||||
* });
|
|
||||||
* });
|
|
||||||
*/
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
var objectAssign = Object.assign || require("object-assign");
|
|
||||||
var inherits = require("inherits");
|
|
||||||
var net = require("net");
|
|
||||||
var dns = require("dns");
|
|
||||||
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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TCP transport class. Implements [base transport interface]{@link
|
|
||||||
* module:bitmessage/net/base.BaseTransport}.
|
|
||||||
* @param {Object=} opts - Transport options
|
|
||||||
* @param {Array} opts.seeds - Bootstrap nodes (none by default)
|
|
||||||
* @param {Array} opts.dnsSeeds - Bootstrap DNS nodes (none by default)
|
|
||||||
* @param {Object} opts.services -
|
|
||||||
* [Service features]{@link module:bitmessage/structs.ServicesBitfield}
|
|
||||||
* provided by this node (`NODE_NETWORK` by default)
|
|
||||||
* @param {(Array|string|Buffer)} opts.userAgent -
|
|
||||||
* [User agent]{@link module:bitmessage/user-agent} of this node
|
|
||||||
* (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
|
|
||||||
* @static
|
|
||||||
*/
|
|
||||||
function TcpTransport(opts) {
|
|
||||||
TcpTransport.super_.call(this);
|
|
||||||
objectAssign(this, opts);
|
|
||||||
this.seeds = this.seeds || [];
|
|
||||||
this.dnsSeeds = this.dnsSeeds || [];
|
|
||||||
this.streams = this.streams || [1];
|
|
||||||
this._clients = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
inherits(TcpTransport, BaseTransport);
|
|
||||||
|
|
||||||
TcpTransport.prototype._sendVersion = function() {
|
|
||||||
return this.send(messages.version.encode({
|
|
||||||
services: this.services,
|
|
||||||
userAgent: this.userAgent,
|
|
||||||
streams: this.streams,
|
|
||||||
port: this.port,
|
|
||||||
remoteHost: this._client.remoteAddress,
|
|
||||||
remotePort: this._client.remotePort,
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
TcpTransport.prototype._setupClient = function(client, incoming) {
|
|
||||||
var self = this;
|
|
||||||
self._client = client;
|
|
||||||
var cache = Buffer(0);
|
|
||||||
var decoded;
|
|
||||||
var verackSent = false;
|
|
||||||
var verackReceived = false;
|
|
||||||
var established = false;
|
|
||||||
|
|
||||||
// Set default transport timeout per spec.
|
|
||||||
// TODO(Kagami): We may also want to close connection if it wasn't
|
|
||||||
// established within minute.
|
|
||||||
client.setTimeout(20000);
|
|
||||||
|
|
||||||
client.on("connect", function() {
|
|
||||||
// NOTE(Kagami): This handler shouldn't be called at all for
|
|
||||||
// incoming connections but let's be sure.
|
|
||||||
if (!incoming) {
|
|
||||||
self.emit("open");
|
|
||||||
self._sendVersion();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on("data", function(data) {
|
|
||||||
// TODO(Kagami): We may want to preallocate 1.6M buffer for each
|
|
||||||
// client instead (max size of the message) to not constantly
|
|
||||||
// allocate new buffers. Though this may lead to another issues: too
|
|
||||||
// many memory per client.
|
|
||||||
cache = Buffer.concat([cache, data]);
|
|
||||||
while (true) {
|
|
||||||
decoded = structs.message.tryDecode(cache);
|
|
||||||
if (!decoded) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
cache = decoded.rest;
|
|
||||||
if (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: " + decoded.error.message
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// High-level message processing.
|
|
||||||
self.on("message", function(command, payload) {
|
|
||||||
var version;
|
|
||||||
if (!established) {
|
|
||||||
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 (incoming) {
|
|
||||||
self._sendVersion();
|
|
||||||
} else if (verackReceived) {
|
|
||||||
self.emit("established", version);
|
|
||||||
}
|
|
||||||
} else if (command === "verack") {
|
|
||||||
verackReceived = true;
|
|
||||||
if (verackSent) {
|
|
||||||
self.emit("established", version);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
self.on("established", function() {
|
|
||||||
established = true;
|
|
||||||
// Raise timeout up to 10 minutes per spec.
|
|
||||||
// TODO(Kagami): Send pong messages every 5 minutes as PyBitmessage.
|
|
||||||
client.setTimeout(600000);
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on("timeout", function() {
|
|
||||||
client.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on("error", function(err) {
|
|
||||||
self.emit("error", err);
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on("close", function() {
|
|
||||||
self.emit("close");
|
|
||||||
delete self._client;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
function resolveDnsSeed(seed) {
|
|
||||||
var host = seed[0];
|
|
||||||
var port = seed[1];
|
|
||||||
var nodes = [];
|
|
||||||
// NOTE(Kagami):
|
|
||||||
// 1) Node's `getaddrinfo` (`dns.lookup`) returns only one address so
|
|
||||||
// we can't use it.
|
|
||||||
// 2) Node's `dig host any` (`dns.resolve`) doesn't return type of the
|
|
||||||
// record! So we resolve twice for A and AAAA.
|
|
||||||
// 3) We ignore any errors here, promise's result is always a list.
|
|
||||||
return new PPromise(function(resolve) {
|
|
||||||
dns.resolve4(host, function(err, nodes4) {
|
|
||||||
if (!err) {
|
|
||||||
nodes4.forEach(function(n) {
|
|
||||||
nodes.push([n, port]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
dns.resolve6(host, function(err, nodes6) {
|
|
||||||
if (!err) {
|
|
||||||
nodes6.forEach(function(n) {
|
|
||||||
nodes.push([n, port]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
resolve(nodes);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
TcpTransport.prototype.bootstrap = function() {
|
|
||||||
var hardcodedNodes = this.seeds;
|
|
||||||
// FIXME(Kagami): Filter incorrect/private IP range nodes?
|
|
||||||
// See also: <https://github.com/Bitmessage/PyBitmessage/issues/768>.
|
|
||||||
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).
|
|
||||||
*/
|
|
||||||
TcpTransport.prototype.connect = function() {
|
|
||||||
assert(!this._client, "Already connected");
|
|
||||||
assert(!this._server, "Already listening");
|
|
||||||
this._setupClient(net.connect.apply(null, arguments));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Listen for incoming TCP connections. Listen arguments are the same as
|
|
||||||
* for
|
|
||||||
* [server.listen](http://nodejs.org/api/net.html#net_server_listen_port_host_backlog_callback).
|
|
||||||
*/
|
|
||||||
TcpTransport.prototype.listen = function() {
|
|
||||||
assert(!this._client, "Already connected");
|
|
||||||
assert(!this._server, "Already listening");
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
var server = self._server = net.createServer();
|
|
||||||
server.listen.apply(server, arguments);
|
|
||||||
|
|
||||||
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;
|
|
||||||
self.emit("connection", transport, unmap(addr), port);
|
|
||||||
});
|
|
||||||
|
|
||||||
server.on("error", function(err) {
|
|
||||||
self.emit("error", err);
|
|
||||||
});
|
|
||||||
|
|
||||||
server.on("close", function() {
|
|
||||||
self.emit("close");
|
|
||||||
delete self._server;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
TcpTransport.prototype.send = function() {
|
|
||||||
if (this._client) {
|
|
||||||
this._client.write(getmsg(arguments));
|
|
||||||
} else {
|
|
||||||
throw new Error("Not connected");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
TcpTransport.prototype.broadcast = function() {
|
|
||||||
var data = getmsg(arguments);
|
|
||||||
if (this._server) {
|
|
||||||
Object.keys(this._clients).forEach(function(id) {
|
|
||||||
this._clients[id].write(data);
|
|
||||||
}, this);
|
|
||||||
} else {
|
|
||||||
throw new Error("Not listening");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
TcpTransport.prototype.close = function() {
|
|
||||||
if (this._client) {
|
|
||||||
this._client.end();
|
|
||||||
} else if (this._server) {
|
|
||||||
Object.keys(this._clients).forEach(function(id) {
|
|
||||||
this._clients[id].end();
|
|
||||||
}, this);
|
|
||||||
this._server.close();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = TcpTransport;
|
|
|
@ -1,125 +0,0 @@
|
||||||
/**
|
|
||||||
* WebSocket transport. Used in browser in client-mode only. Server
|
|
||||||
* handle incoming messages and wrap them into TCP data packets.
|
|
||||||
*/
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
var objectAssign = Object.assign || require("object-assign");
|
|
||||||
var inherits = require("inherits");
|
|
||||||
var assert = require("../_util").assert;
|
|
||||||
var structs = require("../structs");
|
|
||||||
var messages = require("../messages");
|
|
||||||
var BaseTransport = require("./base");
|
|
||||||
|
|
||||||
var ServicesBitfield = structs.ServicesBitfield;
|
|
||||||
var getmsg = BaseTransport._getmsg;
|
|
||||||
|
|
||||||
function WsTransport(opts) {
|
|
||||||
WsTransport.super_.call(this);
|
|
||||||
objectAssign(this, opts);
|
|
||||||
this.seeds = this.seeds || [];
|
|
||||||
this.services = this.services || ServicesBitfield().set([
|
|
||||||
ServicesBitfield.NODE_MOBILE,
|
|
||||||
]);
|
|
||||||
this.streams = this.streams || [1];
|
|
||||||
}
|
|
||||||
|
|
||||||
inherits(WsTransport, BaseTransport);
|
|
||||||
|
|
||||||
WsTransport.prototype.bootstrap = function() {
|
|
||||||
return Promise.resolve([].concat(this.seeds));
|
|
||||||
};
|
|
||||||
|
|
||||||
WsTransport.prototype.connect = function(url, protocols) {
|
|
||||||
var self = this;
|
|
||||||
assert(!self._client, "Already connected");
|
|
||||||
|
|
||||||
// TODO(Kagami): Handle timeouts!
|
|
||||||
var client = self._client = new WebSocket(url, protocols);
|
|
||||||
client.binaryType = "arraybuffer";
|
|
||||||
var verackSent = false;
|
|
||||||
var verackReceived = false;
|
|
||||||
var established = false;
|
|
||||||
|
|
||||||
client.onopen = function() {
|
|
||||||
self.emit("open");
|
|
||||||
self.send(messages.version.encode({
|
|
||||||
services: self.services,
|
|
||||||
userAgent: self.userAgent,
|
|
||||||
streams: self.streams,
|
|
||||||
// This parameters aren't used by the remote node so we fake them
|
|
||||||
// (because we can't resolve domain name in a Browser).
|
|
||||||
remoteHost: "127.0.0.1",
|
|
||||||
remotePort: 8444,
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
client.onmessage = function(e) {
|
|
||||||
var buf = new Buffer(new Uint8Array(e.data));
|
|
||||||
var decoded;
|
|
||||||
try {
|
|
||||||
decoded = structs.message.decode(buf);
|
|
||||||
} catch (err) {
|
|
||||||
return self.emit("warning", new Error(
|
|
||||||
"Message decoding error: " + err.message
|
|
||||||
));
|
|
||||||
}
|
|
||||||
self.emit("message", decoded.command, decoded.payload, decoded);
|
|
||||||
};
|
|
||||||
|
|
||||||
// High-level message processing.
|
|
||||||
self.on("message", function(command, payload) {
|
|
||||||
var version;
|
|
||||||
if (!established) {
|
|
||||||
if (command === "version") {
|
|
||||||
if (verackSent) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
version = self._decodeVersion(payload, {gateway: true});
|
|
||||||
} catch(err) {
|
|
||||||
self.emit("error", err);
|
|
||||||
return client.close();
|
|
||||||
}
|
|
||||||
self.send("verack");
|
|
||||||
verackSent = true;
|
|
||||||
if (verackReceived) {
|
|
||||||
established = true;
|
|
||||||
self.emit("established", version);
|
|
||||||
}
|
|
||||||
} else if (command === "verack") {
|
|
||||||
verackReceived = true;
|
|
||||||
if (verackSent) {
|
|
||||||
established = true;
|
|
||||||
self.emit("established", version);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
client.onerror = function(err) {
|
|
||||||
self.emit("error", err);
|
|
||||||
};
|
|
||||||
|
|
||||||
client.onclose = function() {
|
|
||||||
self.emit("close");
|
|
||||||
delete self._client;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
WsTransport.prototype.send = function() {
|
|
||||||
if (this._client) {
|
|
||||||
this._client.send(getmsg(arguments));
|
|
||||||
} else {
|
|
||||||
throw new Error("Not connected");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
WsTransport.prototype.close = function() {
|
|
||||||
if (this._client) {
|
|
||||||
this._client.close();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = WsTransport;
|
|
247
lib/net/ws.js
247
lib/net/ws.js
|
@ -1,247 +0,0 @@
|
||||||
/**
|
|
||||||
* WebSocket transport. Needed because browsers can't handle TCP sockets
|
|
||||||
* so we use separate WebSocket server to proxy messages into TCP data
|
|
||||||
* packets. Available for both Node.js and Browser platforms.
|
|
||||||
* **NOTE**: `WsTransport` is exported as a module.
|
|
||||||
* @example var WsTransport = require("bitmessage/lib/net/ws");
|
|
||||||
* @module bitmessage/net/ws
|
|
||||||
*/
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
var objectAssign = Object.assign || require("object-assign");
|
|
||||||
var inherits = require("inherits");
|
|
||||||
var WebSocket = require("ws"); // jshint ignore:line
|
|
||||||
var assert = require("../_util").assert;
|
|
||||||
var PPromise = require("../platform").Promise;
|
|
||||||
var structs = require("../structs");
|
|
||||||
var messages = require("../messages");
|
|
||||||
var BaseTransport = require("./base");
|
|
||||||
|
|
||||||
var WebSocketServer = WebSocket.Server;
|
|
||||||
var ServicesBitfield = structs.ServicesBitfield;
|
|
||||||
var getmsg = BaseTransport._getmsg;
|
|
||||||
var unmap = BaseTransport._unmap;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* WebSocket transport class. Implements
|
|
||||||
* [base transport interface]{@link
|
|
||||||
* module:bitmessage/net/base.BaseTransport}.
|
|
||||||
* @param {Object=} opts - Transport options
|
|
||||||
* @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_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
|
|
||||||
* (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
|
|
||||||
*/
|
|
||||||
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);
|
|
||||||
|
|
||||||
WsTransport.prototype._sendVersion = function() {
|
|
||||||
return this.send(messages.version.encode({
|
|
||||||
services: this.services,
|
|
||||||
userAgent: this.userAgent,
|
|
||||||
streams: this.streams,
|
|
||||||
port: this.port,
|
|
||||||
remoteHost: this._client._socket.remoteAddress,
|
|
||||||
remotePort: this._client._socket.remotePort,
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
WsTransport.prototype._handleTimeout = function() {
|
|
||||||
var client = this._client;
|
|
||||||
// TODO(Kagami): We may also want to close connection if it wasn't
|
|
||||||
// established within minute.
|
|
||||||
client._socket.setTimeout(20000);
|
|
||||||
client._socket.on("timeout", function() {
|
|
||||||
client.close();
|
|
||||||
});
|
|
||||||
this.on("established", function() {
|
|
||||||
// Raise timeout up to 10 minutes per spec.
|
|
||||||
// TODO(Kagami): Send ping frame every 5 minutes as PyBitmessage.
|
|
||||||
client._socket.setTimeout(600000);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
WsTransport.prototype._setupClient = function(client, incoming) {
|
|
||||||
var self = this;
|
|
||||||
self._client = client;
|
|
||||||
var verackSent = false;
|
|
||||||
var verackReceived = false;
|
|
||||||
var established = false;
|
|
||||||
|
|
||||||
client.on("open", function() {
|
|
||||||
// NOTE(Kagami): This handler shouldn't be called at all for
|
|
||||||
// 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.
|
|
||||||
self._handleTimeout();
|
|
||||||
self.emit("open");
|
|
||||||
self._sendVersion();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on("message", function(data, flags) {
|
|
||||||
var decoded;
|
|
||||||
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 sent non-binary data"));
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
decoded = structs.message.decode(data);
|
|
||||||
} catch (err) {
|
|
||||||
return self.emit("warning", new Error(
|
|
||||||
"Message decoding error: " + err.message
|
|
||||||
));
|
|
||||||
}
|
|
||||||
self.emit("message", decoded.command, decoded.payload);
|
|
||||||
});
|
|
||||||
|
|
||||||
// High-level message processing.
|
|
||||||
self.on("message", function(command, payload) {
|
|
||||||
var version;
|
|
||||||
var veropts = incoming ? {mobile: true} : {gateway: true};
|
|
||||||
if (!established) {
|
|
||||||
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 (incoming) {
|
|
||||||
self._sendVersion();
|
|
||||||
} else if (verackReceived) {
|
|
||||||
established = true;
|
|
||||||
self.emit("established", version);
|
|
||||||
}
|
|
||||||
} else if (command === "verack") {
|
|
||||||
verackReceived = true;
|
|
||||||
if (verackSent) {
|
|
||||||
established = true;
|
|
||||||
self.emit("established", version);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on("error", function(err) {
|
|
||||||
self.emit("error", err);
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on("close", function() {
|
|
||||||
self.emit("close");
|
|
||||||
delete self._client;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
WsTransport.prototype.bootstrap = function() {
|
|
||||||
return PPromise.resolve([].concat(this.seeds));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connect to a WebSocket node. Connection arguments are the same as for
|
|
||||||
* [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket).
|
|
||||||
*/
|
|
||||||
WsTransport.prototype.connect = function(address, protocols, options) {
|
|
||||||
assert(!this._client, "Already connected");
|
|
||||||
assert(!this._server, "Already listening");
|
|
||||||
// `new` doesn't work with `apply`, so passing all possible arguments
|
|
||||||
// manually.
|
|
||||||
this._setupClient(new WebSocket(address, protocols, options));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Listen for incoming WebSocket connections. Listen arguments are the
|
|
||||||
* same as for
|
|
||||||
* [WebSocketServer](https://github.com/websockets/ws#server-example).
|
|
||||||
* Available only for Node platform.
|
|
||||||
*/
|
|
||||||
WsTransport.prototype.listen = function(options, callback) {
|
|
||||||
assert(!this._client, "Already connected");
|
|
||||||
assert(!this._server, "Already listening");
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
var server = self._server = new WebSocketServer(options, callback);
|
|
||||||
|
|
||||||
server.on("connection", function(client) {
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
|
|
||||||
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() {
|
|
||||||
if (this._client) {
|
|
||||||
// TODO(Kagami): `mask: true` doesn't work with Chromium 40. File a
|
|
||||||
// bug to ws bugtracker.
|
|
||||||
this._client.send(getmsg(arguments), {binary: true});
|
|
||||||
} else {
|
|
||||||
throw new Error("Not connected");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
WsTransport.prototype.broadcast = function() {
|
|
||||||
var data = getmsg(arguments);
|
|
||||||
if (this._server) {
|
|
||||||
this._server.clients.forEach(function(client) {
|
|
||||||
client.send(data, {binary: true});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
throw new Error("Not listening");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
WsTransport.prototype.close = function() {
|
|
||||||
if (this._client) {
|
|
||||||
this._client.close();
|
|
||||||
} else if (this._server) {
|
|
||||||
this._server.close();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = WsTransport;
|
|
16
package.json
16
package.json
|
@ -4,19 +4,13 @@
|
||||||
"description": "JavaScript Bitmessage library",
|
"description": "JavaScript Bitmessage library",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
"browser": {
|
"browser": {
|
||||||
"./lib/platform.js": "./lib/platform.browser.js",
|
"./lib/platform.js": "./lib/platform.browser.js"
|
||||||
"./lib/net/tcp.js": false,
|
|
||||||
"./lib/net/ws.js": "./lib/net/ws.browser.js"
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"install": "node-gyp rebuild || exit 0",
|
"install": "node-gyp rebuild || exit 0",
|
||||||
"test": "ALL_TESTS=1 mocha tests/index.js && ALL_TESTS=1 xvfb-run -a karma start --browsers Chromium && ALL_TESTS=1 xvfb-run -a karma start --browsers Firefox && jshint .",
|
"test": "ALL_TESTS=1 mocha && ALL_TESTS=1 xvfb-run -a karma start --browsers Chromium && ALL_TESTS=1 xvfb-run -a karma start --browsers Firefox && jshint .",
|
||||||
"m": "mocha tests/index.js",
|
"m": "mocha",
|
||||||
"mu": "TEST_MODE=unit mocha tests/index.js",
|
|
||||||
"mf": "TEST_MODE=functional mocha tests/index.js",
|
|
||||||
"kc": "xvfb-run -a karma start --browsers Chromium",
|
"kc": "xvfb-run -a karma start --browsers Chromium",
|
||||||
"kcu": "TEST_MODE=unit xvfb-run -a karma start --browsers Chromium",
|
|
||||||
"kcf": "TEST_MODE=functional xvfb-run -a karma start --browsers Chromium",
|
|
||||||
"kf": "xvfb-run -a karma start --browsers Firefox",
|
"kf": "xvfb-run -a karma start --browsers Firefox",
|
||||||
"j": "jshint .",
|
"j": "jshint .",
|
||||||
"d": "jsdoc -c jsdoc.json",
|
"d": "jsdoc -c jsdoc.json",
|
||||||
|
@ -59,12 +53,10 @@
|
||||||
"eccrypto": "^0.9.7",
|
"eccrypto": "^0.9.7",
|
||||||
"es6-promise": "^2.0.1",
|
"es6-promise": "^2.0.1",
|
||||||
"hash.js": "^1.0.2",
|
"hash.js": "^1.0.2",
|
||||||
"inherits": "^2.0.1",
|
|
||||||
"nan": "^1.4.1",
|
"nan": "^1.4.1",
|
||||||
"object-assign": "^2.0.0",
|
"object-assign": "^2.0.0",
|
||||||
"sha.js": "^2.3.1",
|
"sha.js": "^2.3.1",
|
||||||
"webworkify": "^1.0.1",
|
"webworkify": "^1.0.1"
|
||||||
"ws": "^0.7.1"
|
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"bignum": "^0.9.0"
|
"bignum": "^0.9.0"
|
||||||
|
|
|
@ -2,8 +2,8 @@ var expect = require("chai").expect;
|
||||||
var allTests = (global.__env__ || process.env).ALL_TESTS === "1";
|
var allTests = (global.__env__ || process.env).ALL_TESTS === "1";
|
||||||
|
|
||||||
var bufferEqual = require("buffer-equal");
|
var bufferEqual = require("buffer-equal");
|
||||||
var bitmessage = require("../lib");
|
var bitmessage = require("./lib");
|
||||||
var bmcrypto = require("../lib/crypto");
|
var bmcrypto = require("./lib/crypto");
|
||||||
var structs = bitmessage.structs;
|
var structs = bitmessage.structs;
|
||||||
var message = structs.message;
|
var message = structs.message;
|
||||||
var object = structs.object;
|
var object = structs.object;
|
|
@ -1,157 +0,0 @@
|
||||||
var expect = require("chai").expect;
|
|
||||||
|
|
||||||
var bitmessage = require("../lib");
|
|
||||||
var structs = bitmessage.structs;
|
|
||||||
var ServicesBitfield = structs.ServicesBitfield;
|
|
||||||
var message = structs.message;
|
|
||||||
var WsTransport = require("../lib/net/ws");
|
|
||||||
|
|
||||||
var TcpTransport, tcp;
|
|
||||||
|
|
||||||
if (!process.browser) {
|
|
||||||
TcpTransport = require("../lib/net/tcp");
|
|
||||||
|
|
||||||
describe("TCP transport", function() {
|
|
||||||
before(function(done) {
|
|
||||||
tcp = new TcpTransport();
|
|
||||||
tcp.on("error", function(err) {
|
|
||||||
console.log("TCP transport error: " + err);
|
|
||||||
});
|
|
||||||
tcp.on("warning", function(warn) {
|
|
||||||
console.log("TCP transport warning: " + warn);
|
|
||||||
});
|
|
||||||
// Wait some time for the server.
|
|
||||||
setTimeout(done, 300);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return nothing on bootstrap by default", function() {
|
|
||||||
return tcp.bootstrap().then(function(nodes) {
|
|
||||||
expect(nodes).to.be.empty;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should resolve DNS seeds on bootstrap", function() {
|
|
||||||
this.timeout(10000);
|
|
||||||
var tcp2 = new TcpTransport({
|
|
||||||
dnsSeeds: [["bootstrap8444.bitmessage.org", 8444]],
|
|
||||||
});
|
|
||||||
return tcp2.bootstrap().then(function(nodes) {
|
|
||||||
expect(nodes).to.be.not.empty;
|
|
||||||
expect(nodes[0][1]).to.be.equal(8444);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return hardcoded seeds on bootstrap", function() {
|
|
||||||
this.timeout(10000);
|
|
||||||
var tcp3 = new TcpTransport({
|
|
||||||
seeds: [["1.1.1.1", 8080]],
|
|
||||||
dnsSeeds: [["bootstrap8444.bitmessage.org", 8444]],
|
|
||||||
});
|
|
||||||
return tcp3.bootstrap().then(function(nodes) {
|
|
||||||
expect(nodes).to.have.length.at.least(2);
|
|
||||||
expect(nodes[0][1]).to.be.equal(8444);
|
|
||||||
expect(nodes[nodes.length - 1][0]).to.equal("1.1.1.1");
|
|
||||||
expect(nodes[nodes.length - 1][1]).to.equal(8080);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should allow to interconnect two nodes", function(done) {
|
|
||||||
tcp.connect(22333, "127.0.0.1");
|
|
||||||
tcp.once("open", function() {
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should automatically establish connection", function(done) {
|
|
||||||
tcp.once("established", function(version) {
|
|
||||||
expect(version.protoVersion).to.equal(3);
|
|
||||||
expect(version.services.get(ServicesBitfield.NODE_NETWORK)).to.be.true;
|
|
||||||
expect(version.remoteHost).to.equal("127.0.0.1");
|
|
||||||
expect(version.port).to.equal(22333);
|
|
||||||
expect(version.userAgent).to.be.a("string");
|
|
||||||
expect(version.streams).to.deep.equal([1]);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should allow to communicate", function(done) {
|
|
||||||
tcp.on("message", function cb(command, payload) {
|
|
||||||
if (command === "echo-res") {
|
|
||||||
expect(payload.toString()).to.equal("test");
|
|
||||||
tcp.removeListener("message", cb);
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
tcp.send("echo-req", Buffer("test"));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should allow to close connection", function(done) {
|
|
||||||
tcp.close();
|
|
||||||
tcp.once("close", function() {
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("WebSocket transport", function() {
|
|
||||||
var ws;
|
|
||||||
|
|
||||||
before(function(done) {
|
|
||||||
ws = new WsTransport();
|
|
||||||
ws.on("error", function(err) {
|
|
||||||
console.log("WebSocket transport error: " + err);
|
|
||||||
});
|
|
||||||
ws.on("warning", function(warn) {
|
|
||||||
console.log("WebSocket transport warning: " + warn);
|
|
||||||
});
|
|
||||||
// Wait some time for the server.
|
|
||||||
setTimeout(done, 300);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return hardcoded seeds on bootstrap", function() {
|
|
||||||
var ws2 = new WsTransport({seeds: [["ws.example.com", 8080]]});
|
|
||||||
return ws2.bootstrap().then(function(nodes) {
|
|
||||||
expect(nodes).to.have.length(1);
|
|
||||||
expect(nodes[0][0]).to.be.equal("ws.example.com");
|
|
||||||
expect(nodes[0][1]).to.be.equal(8080);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should allow to interconnect two nodes", function(done) {
|
|
||||||
ws.connect("ws://127.0.0.1:22334");
|
|
||||||
ws.once("open", function() {
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should automatically establish connection", function(done) {
|
|
||||||
ws.once("established", function(version) {
|
|
||||||
expect(version.protoVersion).to.equal(3);
|
|
||||||
expect(version.services.get(ServicesBitfield.NODE_GATEWAY)).to.be.true;
|
|
||||||
expect(version.remoteHost).to.equal("127.0.0.1");
|
|
||||||
expect(version.port).to.equal(22334);
|
|
||||||
expect(version.userAgent).to.be.a("string");
|
|
||||||
expect(version.streams).to.deep.equal([1]);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should allow to communicate", function(done) {
|
|
||||||
ws.on("message", function cb(command, payload) {
|
|
||||||
if (command === "echo-res") {
|
|
||||||
expect(payload.toString()).to.equal("test");
|
|
||||||
ws.removeListener("message", cb);
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
ws.send("echo-req", Buffer("test"));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should allow to close connection", function(done) {
|
|
||||||
ws.close();
|
|
||||||
ws.once("close", function() {
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,17 +0,0 @@
|
||||||
var testMode = (global.__env__ || process.env).TEST_MODE;
|
|
||||||
|
|
||||||
if (testMode !== "functional") {
|
|
||||||
describe("Unit tests", function() {
|
|
||||||
require("./unit");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (testMode !== "unit") {
|
|
||||||
// For Browser tests nodes are being run from karma.conf.js because we
|
|
||||||
// are _already_ in browser context here.
|
|
||||||
if (!process.browser) require("./run-test-nodes.js")();
|
|
||||||
|
|
||||||
describe("Functional tests", function() {
|
|
||||||
require("./functional");
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
// Run test nodes for all known transports so we can test library in
|
|
||||||
// functional test suites. Stop nodes when testing is complete.
|
|
||||||
|
|
||||||
// Note that this file is executed only on Node.js platform.
|
|
||||||
|
|
||||||
var path = require("path");
|
|
||||||
var child = require("child_process");
|
|
||||||
|
|
||||||
function spawn(name) {
|
|
||||||
var p = child.spawn("node", [path.join(__dirname, name)]);
|
|
||||||
p.stdout.on("data", function(data) {
|
|
||||||
console.log("Info from " + name + ": " + data.toString().trim());
|
|
||||||
});
|
|
||||||
p.stderr.on("data", function(err) {
|
|
||||||
console.log("Error from " + name + ": " + err.toString().trim());
|
|
||||||
});
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = function() {
|
|
||||||
function cleanup(doExit) {
|
|
||||||
return function(err) {
|
|
||||||
try {
|
|
||||||
tcpNode.kill("SIGKILL");
|
|
||||||
} catch(e) {
|
|
||||||
console.log(e.stack);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
wsNode.kill("SIGKILL");
|
|
||||||
} catch(e) {
|
|
||||||
console.log(e.stack);
|
|
||||||
}
|
|
||||||
if (err && err.stack) console.log(err.stack);
|
|
||||||
if (doExit) process.exit(1);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
process.on("exit", cleanup());
|
|
||||||
process.on("SIGINT", cleanup(true));
|
|
||||||
|
|
||||||
var tcpNode = spawn("tcp-node.js");
|
|
||||||
var wsNode = spawn("ws-node.js");
|
|
||||||
};
|
|
|
@ -1,17 +0,0 @@
|
||||||
var TcpTransport = require("../lib/net/tcp");
|
|
||||||
|
|
||||||
function start() {
|
|
||||||
var server = new TcpTransport({port: 22333});
|
|
||||||
// In node 0.12/io 1.0 we can use {host: x, port: y} syntax so it'll
|
|
||||||
// be more compatible with the ws transport options.
|
|
||||||
server.listen(22333, "127.0.0.1");
|
|
||||||
server.on("connection", function(client) {
|
|
||||||
client.on("message", function(command, payload) {
|
|
||||||
if (command === "echo-req") {
|
|
||||||
client.send("echo-res", payload);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
start();
|
|
|
@ -1,15 +0,0 @@
|
||||||
var WsTransport = require("../lib/net/ws");
|
|
||||||
|
|
||||||
function start() {
|
|
||||||
var server = new WsTransport({port: 22334});
|
|
||||||
server.listen({host: "127.0.0.1", port: 22334});
|
|
||||||
server.on("connection", function(client) {
|
|
||||||
client.on("message", function(command, payload) {
|
|
||||||
if (command === "echo-req") {
|
|
||||||
client.send("echo-res", payload);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
start();
|
|
Loading…
Reference in New Issue
Block a user