diff --git a/lib/net/pool.js b/lib/net/pool.js index b2b718eb..5d370595 100644 --- a/lib/net/pool.js +++ b/lib/net/pool.js @@ -1036,7 +1036,7 @@ Pool.prototype.handleAddr = function handleAddr(addrs, peer) { for (i = 0; i < addrs.length; i++) { addr = addrs[i]; - if (addr.isNull()) + if (!addr.isRoutable()) continue; if (!addr.hasServices(this.needed)) @@ -1574,7 +1574,7 @@ Pool.prototype.getHost = function getHost(unique) { continue; } - if (addr.isNull()) + if (!addr.isValid()) continue; if (!addr.hasServices(this.needed)) @@ -1820,7 +1820,6 @@ Pool.prototype.hasBlock = co(function* hasBlock(hash) { */ Pool.prototype.getTX = function getTX(peer, hash) { - var self = this; var item; if (!this.loaded) @@ -2889,6 +2888,136 @@ HostList.prototype.populate = co(function* populate(seed) { } }); +/** + * Convert host list to json-friendly object. + * @returns {Object} + */ + +HostList.prototype.toJSON = function toJSON() { + var addrs = []; + var fresh = []; + var used = []; + var i, keys, key, bucket, entry; + + keys = Object.keys(this.map); + + for (i = 0; i < keys.length; i++) { + key = keys[i]; + entry = this.map[key]; + addrs.push(entry.toJSON()); + } + + for (i = 0; i < this.fresh.length; i++) { + bucket = this.fresh[i]; + keys = bucket.keys(); + fresh.push(keys); + } + + for (i = 0; i < this.used.length; i++) { + bucket = this.used[i]; + keys = []; + for (entry = bucket.head; entry; entry = entry.next) + keys.push(entry.key()); + used.push(keys); + } + + return { + version: 1, + addrs: addrs, + fresh: fresh, + used: used + }; +}; + +/** + * Inject properties from json object. + * @private + * @param {Object} json + * @returns {HostList} + */ + +HostList.prototype.fromJSON = function fromJSON(json) { + var sources = {}; + var i, j, bucket, keys, key, addr, entry, src; + + assert(json && typeof json === 'object'); + assert(json.version === 1, 'Bad address serialization version.'); + + assert(Array.isArray(json.addrs)); + + for (i = 0; i < json.addrs.length; i++) { + addr = json.addrs[i]; + entry = HostEntry.fromJSON(addr, this.network); + src = sources[entry.src.hostname]; + + // Save some memory. + if (!src) { + src = entry.src; + sources[src.hostname] = src; + } + + entry.src = src; + + this.map[entry.key()] = entry; + } + + assert(Array.isArray(json.fresh)); + + for (i = 0; i < json.fresh.length; i++) { + keys = json.fresh[i]; + bucket = this.fresh[i]; + assert(bucket, 'No bucket available.'); + for (j = 0; j < keys.length; j++) { + key = keys[j]; + entry = this.map[key]; + assert(entry); + if (entry.refCount === 0) + this.totalFresh++; + entry.refCount++; + bucket.set(key, entry); + } + } + + assert(Array.isArray(json.used)); + + for (i = 0; i < json.used.length; i++) { + keys = json.used[i]; + bucket = this.used[i]; + assert(bucket, 'No bucket available.'); + for (j = 0; j < keys.length; j++) { + key = keys[j]; + entry = this.map[key]; + assert(entry); + assert(entry.refCount === 0); + assert(!entry.used); + entry.used = true; + this.totalUsed++; + bucket.push(entry); + } + } + + keys = Object.keys(this.map); + + for (i = 0; i < keys.length; i++) { + key = keys[i]; + entry = this.map[key]; + assert(entry.used || entry.refCount > 0); + } + + return this; +}; + +/** + * Instantiate host list from json object. + * @param {Object} options + * @param {Object} json + * @returns {HostList} + */ + +HostList.fromJSON = function fromJSON(options, json) { + return new HostEntry(options).fromJSON(json); +}; + /** * MapBucket * @constructor @@ -2981,14 +3110,15 @@ MapBucket.prototype.reset = function reset() { * HostEntry * @constructor * @param {NetAddress} addr + * @param {NetAddress} src */ function HostEntry(addr, src) { - assert(addr instanceof NetAddress); - assert(src instanceof NetAddress); + if (!(this instanceof HostEntry)) + return new HostEntry(addr, src); - this.addr = addr; - this.src = src; + this.addr = addr || new NetAddress(); + this.src = src || new NetAddress(); this.prev = null; this.next = null; this.used = false; @@ -2996,8 +3126,38 @@ function HostEntry(addr, src) { this.attempts = 0; this.lastSuccess = 0; this.lastAttempt = 0; + + if (addr) + this.fromOptions(addr, src); } +/** + * Inject properties from options. + * @private + * @param {NetAddress} addr + * @param {NetAddress} src + * @returns {HostEntry} + */ + +HostEntry.prototype.fromOptions = function fromOptions(addr, src) { + assert(addr instanceof NetAddress); + assert(src instanceof NetAddress); + this.addr = addr; + this.src = src; + return this; +}; + +/** + * Instantiate host entry from options. + * @param {NetAddress} addr + * @param {NetAddress} src + * @returns {HostEntry} + */ + +HostEntry.fromOptions = function fromOptions(addr, src) { + return new HostEntry().fromOptions(addr, src); +}; + /** * Get key suitable for a hash table (hostname). * @returns {String} @@ -3042,6 +3202,84 @@ HostEntry.prototype.inspect = function inspect() { }; }; +/** + * Convert host entry to json-friendly object. + * @returns {Object} + */ + +HostEntry.prototype.toJSON = function toJSON() { + return { + addr: this.addr.hostname, + src: this.src.hostname, + services: this.addr.services.toString(2), + ts: this.addr.ts, + attempts: this.attempts, + lastSuccess: this.lastSuccess, + lastAttempt: this.lastAttempt + }; +}; + +/** + * Inject properties from json object. + * @private + * @param {Object} json + * @param {Network} network + * @returns {HostEntry} + */ + +HostEntry.prototype.fromJSON = function fromJSON(json, network) { + assert(json && typeof json === 'object'); + assert(typeof json.addr === 'string'); + assert(typeof json.src === 'string'); + + this.addr.fromHostname(json.addr, network); + + if (json.services != null) { + assert(typeof json.services === 'string'); + assert(json.services.length > 0); + assert(json.services.length < 64); + this.addr.services = parseInt(json.services, 2); + } + + if (json.ts != null) { + assert(util.isNumber(json.ts)); + this.addr.ts = json.ts; + } + + if (json.src != null) { + assert(typeof json.src === 'string'); + this.src.fromHostname(json.src, network); + } + + if (json.attempts != null) { + assert(util.isNumber(json.attempts)); + this.attempts = json.attempts; + } + + if (json.lastSuccess != null) { + assert(util.isNumber(json.lastSuccess)); + this.lastSuccess = json.lastSuccess; + } + + if (json.lastAttempt != null) { + assert(util.isNumber(json.lastAttempt)); + this.lastAttempt = json.lastAttempt; + } + + return this; +}; + +/** + * Instantiate host entry from json object. + * @param {Object} json + * @param {Network} network + * @returns {HostEntry} + */ + +HostEntry.fromJSON = function fromJSON(json, network) { + return new HostEntry().fromJSON(json, network); +}; + /** * Represents an in-flight block or transaction. * @exports LoadRequest diff --git a/lib/primitives/netaddress.js b/lib/primitives/netaddress.js index d4831b69..648f3fd1 100644 --- a/lib/primitives/netaddress.js +++ b/lib/primitives/netaddress.js @@ -135,7 +135,52 @@ NetAddress.prototype.hasServices = function hasServices(services) { */ NetAddress.prototype.isNull = function isNull() { - return this.host === '0.0.0.0' || this.host === '::'; + return IP.isNull(this.host); +}; + +/** + * Test whether the host is a broadcast address. + * @returns {Boolean} + */ + +NetAddress.prototype.isBroadcast = function isBroadcast() { + return IP.isBroadcast(this.host); +}; + +/** + * Test whether the host is a local address. + * @returns {Boolean} + */ + +NetAddress.prototype.isLoopback = function isLoopback() { + return IP.isLoopback(this.host); +}; + +/** + * Test whether the host is a private address. + * @returns {Boolean} + */ + +NetAddress.prototype.isPrivate = function isPrivate() { + return IP.isPrivate(this.host); +}; + +/** + * Test whether the host is valid. + * @returns {Boolean} + */ + +NetAddress.prototype.isValid = function isValid() { + return IP.isValid(this.host); +}; + +/** + * Test whether the host is routable. + * @returns {Boolean} + */ + +NetAddress.prototype.isRoutable = function isRoutable() { + return IP.isRoutable(this.host); }; /** @@ -335,6 +380,51 @@ NetAddress.prototype.toRaw = function toRaw(full) { return this.toWriter(new StaticWriter(size), full).render(); }; +/** + * Convert net address to json-friendly object. + * @returns {Object} + */ + +NetAddress.prototype.toJSON = function toJSON() { + return { + host: this.host, + port: this.port, + services: this.services, + ts: this.ts + }; +}; + +/** + * Inject properties from json object. + * @private + * @param {Object} json + * @returns {NetAddress} + */ + +NetAddress.prototype.fromJSON = function fromJSON(json) { + assert(IP.version(json.host) !== -1); + assert(util.isNumber(json.port)); + assert(json.port >= 0 && json.port <= 0xffff); + assert(util.isNumber(json.services)); + assert(util.isNumber(json.ts)); + this.host = json.host; + this.port = json.port; + this.services = json.services; + this.ts = json.ts; + this.hostname = IP.hostname(this.host, this.port); + return this; +}; + +/** + * Instantiate net address from json object. + * @param {Object} json + * @returns {NetAddress} + */ + +NetAddress.fromJSON = function fromJSON(json) { + return new NetAddress().fromJSON(json); +}; + /** * Inspect the network address. * @returns {Object} diff --git a/lib/utils/ip.js b/lib/utils/ip.js index 36ca499a..2c22084d 100644 --- a/lib/utils/ip.js +++ b/lib/utils/ip.js @@ -157,7 +157,7 @@ IP.version = function version(str) { * @returns {Boolean} */ -IP.isV4Format = function(str) { +IP.isV4Format = function isV4Format(str) { assert(typeof str === 'string'); if (str.length < 7) @@ -175,7 +175,7 @@ IP.isV4Format = function(str) { * @returns {Boolean} */ -IP.isV6Format = function(str) { +IP.isV6Format = function isV6Format(str) { assert(typeof str === 'string'); if (str.length < 2) @@ -216,7 +216,7 @@ IP.isMapped = function isMapped(buf) { * @returns {Buffer} */ -IP.toBuffer = function(str) { +IP.toBuffer = function toBuffer(str) { var buf = new Buffer(16); assert(typeof str === 'string'); @@ -336,7 +336,7 @@ IP.parseV6 = function parseV6(str, buf, offset) { * @returns {String} */ -IP.toString = function(buf) { +IP.toString = function toString(buf) { var str = ''; var i; @@ -385,13 +385,54 @@ IP.normalize = function normalize(str) { return IP.toString(IP.toBuffer(str)); }; +/** + * Test whether an IP is null. + * @param {String} str - Normalized ip. + * @returns {Boolean} + */ + +IP.isNull = function isNull(str) { + return str === '0.0.0.0' || str === '::'; +}; + +/** + * Test whether the IP is a broadcast address. + * @param {String} str - Normalized ip. + * @returns {Boolean} + */ + +IP.isBroadcast = function isBroadcast(str) { + return str === '255.255.255.255' + || str === '::ffff:ffff:ffff'; +}; + +/** + * Test whether the IP is valid. + * @param {String} str - Normalized ip. + * @returns {Boolean} + */ + +IP.isValid = function isValid(str) { + return !IP.isNull(str) && !IP.isBroadcast(str); +}; + +/** + * Test whether the IP is routable. + * @param {String} str - Normalized ip. + * @returns {Boolean} + */ + +IP.isRoutable = function isRoutable(str) { + return IP.isValid(str) && !IP.isLoopback(str); +}; + /** * Test whether a string is a private address. * @param {String} str * @returns {Boolean} */ -IP.isPrivate = function(str) { +IP.isPrivate = function isPrivate(str) { assert(typeof str === 'string'); return /^(::f{4}:)?10\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(str) @@ -411,7 +452,7 @@ IP.isPrivate = function(str) { * @returns {Boolean} */ -IP.isPublic = function(str) { +IP.isPublic = function isPublic(str) { return !IP.isPrivate(str); }; @@ -421,7 +462,7 @@ IP.isPublic = function(str) { * @returns {Boolean} */ -IP.isLoopback = function(str) { +IP.isLoopback = function isLoopback(str) { assert(typeof str === 'string'); return /^(::f{4}:)?127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/.test(str) @@ -436,7 +477,7 @@ IP.isLoopback = function(str) { * @returns {String} */ -IP.loopback = function(family) { +IP.loopback = function loopback(family) { if (!family) family = 'ipv4';