UNPKG

kademlia-table

Version:

XOR based routing table used for P2P networks such as a Kademlia DHT.

120 lines (116 loc) 3.92 kB
import { compare } from '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 (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) => 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) => 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) => compare(this.getId(node), id) === 0); } update(node, d = this.getBitwiseDistance(this.getId(node))) { const index = this.buckets[d].findIndex((n) => 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) => compare(this.getId(node), id) === 0); if (index === -1) return false; this.buckets[d].splice(index, 1); return true; } }; export { KademliaTable, createCompareBitwiseDistance, getBitwiseDistance };