kademlia-dht
Version:
Network-agnostic Kademlia Distributed Hash Table
153 lines (135 loc) • 4.27 kB
JavaScript
;
var crypto = require('crypto');
var util = require('util');
// Create an id from a `buffer`.
//
var Id = function (buf) {
if (!(buf instanceof Buffer) || buf.length !== Id.SIZE)
throw new Error('invalid buffer');
this._buf = buf;
};
// Standard size for ids, in bytes. It matchs the length of a SHA-1.
//
Object.defineProperty(Id, 'SIZE', {value: 20});
Object.defineProperty(Id, 'BIT_SIZE', {value: Id.SIZE * 8});
Object.defineProperty(Id, 'SHORT_STR_PRE_LEN', {value: 5});
Object.defineProperty(Id, 'SHORT_STR_SUF_LEN', {value: 2});
// Compute the distance between two node ids `a` and `b` expressed as Buffer.
//
Id.prototype.distanceTo = function (other) {
if (!(other instanceof Id))
throw new Error('can only compare to another identifier');
var res = new Buffer(Id.SIZE);
for (var i = 0; i < Id.SIZE; ++i) {
res[i] = this._buf[i] ^ other._buf[i];
}
return res;
};
// Compare the difference of distance of two ids from this one. Return a
// Number equal to 0 if the distance is identical, >0 if `first` is closer,
// <0 otherwise.
//
Id.prototype.compareDistance = function (first, second) {
if (!(first instanceof Id && second instanceof Id))
throw new Error(util.format('invalid operand identifiers "%s" and "%s"',
first, second));
for (var i = 0; i < Id.SIZE; ++i) {
var bt1 = this._buf[i] ^ first._buf[i];
var bt2 = this._buf[i] ^ second._buf[i];
if (bt1 > bt2) return -1;
if (bt1 < bt2) return 1;
}
return 0;
};
// Test if the id is equal to another.
//
Id.prototype.equal = function (other) {
if (!(other instanceof Id))
throw new Error('can only compare to another identifier');
for (var i = 0; i < Id.SIZE; ++i) {
if (this._buf[i] != other._buf[i]) return false;
}
return true;
};
// Extract the bit at the specified index. The index must be between the
// range [0, id.BIT_SIZE[.
//
Id.prototype.at = function (i) {
return (this._buf[i / 8 | 0] & (1 << (7 - i % 8))) > 0;
};
// Set the bit at the specified index. The index must be between the
// range [0, id.BIT_SIZE[.
//
Id.prototype.set = function (i, v) {
var index = i / 8 | 0;
var mask = 1 << (7 - i % 8);
if (v)
this._buf[index] |= mask;
else
this._buf[index] &= 255 ^ mask;
};
// Get the hex representation of the ID.
//
Id.prototype.toString = function (short) {
var str = this._buf.toString('hex');
if (short) {
return util.format('%s..%s',
str.slice(0, Id.SHORT_STR_PRE_LEN),
str.slice(str.length - Id.SHORT_STR_SUF_LEN));
}
return str;
};
// Generate randomly a node identifer and call `cb(err, id)`.
//
Id.generate = function (cb) {
crypto.randomBytes(Id.SIZE, function gotRndBytes(err, buf) {
if (err) cb(err);
cb(null, new Id(buf));
});
};
// Create an identifier from a String `key`.
//
Id.fromKey = function (key) {
var shasum = crypto.createHash('sha1');
shasum.update(key);
return new Id(shasum.digest());
};
// Create an Array prefix from a String composed of 1s and 0s.
// Eg. '101' gives [true, false, true].
//
function convertPrefix(str) {
var res = new Array(str.length);
for (var i = 0; i < str.length; ++i)
res[i] = str[i] === '1';
return res;
}
// Create a predictable identifier from an Array of Boolean `prefix`. The
// prefix can also be a String of 0s and 1s.
//
Id.fromPrefix = function (prefix) {
var id = Id.zero();
if (typeof prefix === 'string')
prefix = convertPrefix(prefix);
if (prefix.length >= Id.BIT_SIZE)
throw new Error('id prefix is too long');
for (var j = 0; j < prefix.length; ++j)
id.set(j, prefix[j]);
return id;
};
Id.fromHex = function (prefix, suffix) {
var id = Id.zero();
id._buf.write(prefix, 0, 0, 'hex');
if (suffix)
id._buf.write(suffix, Id.SIZE - suffix.length / 2, 0, 'hex');
return id;
};
// Create the zero identifier.
//
Id.zero = function () {
var buf = new Buffer(Id.SIZE);
for (var i = 0; i < Id.SIZE; ++i) {
buf[i] = 0;
}
return new Id(buf);
};
module.exports = Id;