kademlia-table
Version:
XOR based routing table used for P2P networks such as a Kademlia DHT.
124 lines (119 loc) • 4.12 kB
JavaScript
'use strict';
var uint8arrayTools = require('uint8array-tools');
// src/getBitwiseDistance.ts
var getBitwiseDistance = (a, b, byteLength = Math.min(a.byteLength, b.byteLength)) => {
for (let i = 0; i < byteLength; i++) {
const ai = a[i];
const bi = b[i];
if (ai !== bi)
return 8 * byteLength - (i * 8 + Math.clz32(ai ^ bi) - 24);
}
return 0;
};
// src/createCompareBitwiseDistance.ts
var createCompareBitwiseDistance = (id) => {
return (a, b) => {
const ad = getBitwiseDistance(id, a);
const bd = getBitwiseDistance(id, b);
return ad > bd ? 1 : ad < bd ? -1 : 0;
};
};
var KademliaTable = class {
constructor(id, options) {
this.id = id;
this.bucketSize = options.bucketSize || 20;
this.buckets = new Array(id.length * 8 + 1).fill(void 0).map(() => []);
this.compare = options.compare || (() => 0);
this.getId = options.getId;
}
get length() {
return this.buckets.reduce((sum, bucket) => sum + bucket.length, 0);
}
*[Symbol.iterator]() {
for (const bucket of this.buckets)
for (const node of bucket)
yield node;
}
add(node, d = this.getBitwiseDistance(this.getId(node))) {
if (this.has(this.getId(node), d))
return false;
if (this.buckets[d].length < this.bucketSize) {
this.buckets[d].push(node);
return true;
}
const bucket = this.buckets[d].slice(0);
bucket.push(node);
const sortedBucket = bucket.sort(this.compare);
this.buckets[d] = sortedBucket.slice(0, this.bucketSize);
const removedNode = sortedBucket.at(-1);
if (uint8arrayTools.compare(this.getId(node), this.getId(removedNode)) === 0)
return false;
return true;
}
clear() {
for (let i = 0; i < this.buckets.length; i++)
this.buckets[i] = [];
}
listClosestToId(id, limit = this.buckets.length * this.bucketSize) {
const d = this.getBitwiseDistance(id);
return this.getNodes(d, limit);
}
get(d) {
const node = this.buckets[d].shift();
if (node)
this.buckets[d].push(node);
return node;
}
getById(id, d = this.getBitwiseDistance(id)) {
const index = this.buckets[d].findIndex((node2) => uint8arrayTools.compare(this.getId(node2), id) === 0);
if (index === -1)
return void 0;
const node = this.buckets[d][index];
this.buckets[d].splice(index, 1);
this.buckets[d].push(node);
return node;
}
getBitwiseDistance(id) {
return getBitwiseDistance(this.id, id);
}
getNodes(d, limit, depth = 0) {
const offset = (depth % 2 === 0 ? 1 : -1) * Math.ceil(depth / 2);
if (Math.abs(offset) > Math.max(d, this.buckets.length - 1 - d))
return [];
const i = d + offset;
if (0 > i || i > this.buckets.length - 1)
return this.getNodes(d, limit, depth + 1);
const nodes = this.buckets[i].slice(0, limit);
if (nodes.length >= limit) {
this.buckets[i] = this.buckets[i].slice(nodes.length).concat(this.buckets[i].slice(0, nodes.length));
return nodes;
}
return nodes.concat(this.getNodes(d, limit - nodes.length, depth + 1));
}
has(id, d = this.getBitwiseDistance(id)) {
return !!this.buckets[d].find((node) => uint8arrayTools.compare(this.getId(node), id) === 0);
}
peek(d) {
return this.buckets[d].at(0);
}
peekById(id, d = this.getBitwiseDistance(id)) {
return this.buckets[d].find((node) => uint8arrayTools.compare(this.getId(node), id) === 0);
}
update(node, d = this.getBitwiseDistance(this.getId(node))) {
const index = this.buckets[d].findIndex((n) => uint8arrayTools.compare(this.getId(n), this.getId(node)) === 0);
if (index === -1)
return false;
this.buckets[d][index] = node;
return true;
}
remove(id, d = this.getBitwiseDistance(id)) {
const index = this.buckets[d].findIndex((node) => uint8arrayTools.compare(this.getId(node), id) === 0);
if (index === -1)
return false;
this.buckets[d].splice(index, 1);
return true;
}
};
exports.KademliaTable = KademliaTable;
exports.createCompareBitwiseDistance = createCompareBitwiseDistance;
exports.getBitwiseDistance = getBitwiseDistance;