Comparing version 2.0.1 to 3.0.0
159
index.js
@@ -7,3 +7,3 @@ /* | ||
Copyright (c) 2013-2015 Tristan Slominski | ||
Copyright (c) 2013-2016 Tristan Slominski | ||
@@ -34,3 +34,5 @@ Permission is hereby granted, free of charge, to any person | ||
var bufferEqual = require('buffer-equal'); | ||
var bufferEquals = require('buffer-equals'); | ||
var EventEmitter = require('events').EventEmitter; | ||
var inherits = require('inherits'); | ||
var randomBytes = require('randombytes'); | ||
@@ -52,13 +54,11 @@ | ||
ping when a bucket that should not be split becomes full. KBucket will | ||
call the `ping` callback that contains `numberOfNodesToPing` nodes that have | ||
emit a `ping` event that contains `numberOfNodesToPing` nodes that have | ||
not been contacted the longest. | ||
* `ping`: _Function_ _(Default: no-op)_ | ||
`function (oldContacts, newContact) {}` Callback called every time a | ||
contact is added that would exceed the capacity of a don't split k-bucket it belongs to. | ||
* `oldContacts`: _Array_ The array of contacts to ping. | ||
* `newContact`: _Object_ The new contact to be added if one of old contacts | ||
does not respond. | ||
* `root`: _Object_ _**CAUTION: reserved for internal use**_ Provides a | ||
reference to the root of the tree data structure as the k-bucket splits | ||
when new contacts are added. | ||
*/ | ||
var KBucket = module.exports = function KBucket (options) { | ||
var self = this; | ||
EventEmitter.call(self); | ||
options = options || {}; | ||
@@ -83,3 +83,3 @@ | ||
self.numberOfNodesToPing = options.numberOfNodesToPing || 3; | ||
self.ping = options.ping || function() {}; | ||
self.root = options.root || self; | ||
@@ -92,2 +92,4 @@ // V8 hints | ||
inherits(KBucket, EventEmitter); | ||
KBucket.distance = function distance (firstId, secondId) { | ||
@@ -109,3 +111,3 @@ var distance = 0; | ||
// the binary tree | ||
KBucket.prototype.add = function add (contact, bitIndex) { | ||
KBucket.prototype._add = function (contact, bitIndex) { | ||
var self = this; | ||
@@ -118,8 +120,6 @@ | ||
// delegate to the appropriate node for further processing | ||
bitIndex = bitIndex || 0; | ||
if (self.determineBucket(contact.id, bitIndex++) < 0) { | ||
return self.low.add(contact, bitIndex); | ||
if (self._determineBucket(contact.id, bitIndex++) < 0) { | ||
return self.low._add(contact, bitIndex); | ||
} else { | ||
return self.high.add(contact, bitIndex); | ||
return self.high._add(contact, bitIndex); | ||
} | ||
@@ -129,5 +129,5 @@ } | ||
// check if the contact already exists | ||
var index = self.indexOf(contact); | ||
var index = self._indexOf(contact.id); | ||
if (index >= 0) { | ||
self.update(contact, index); | ||
self._update(contact, index); | ||
return self; | ||
@@ -138,2 +138,3 @@ } | ||
self.bucket.push(contact); | ||
self.emit('added', contact); | ||
return self; | ||
@@ -149,15 +150,22 @@ } | ||
// be added (this prevents DoS flodding with new invalid contacts) | ||
self.ping(self.bucket.slice(0, self.numberOfNodesToPing), contact); | ||
self.root.emit('ping', self.bucket.slice(0, self.numberOfNodesToPing), contact); | ||
return self; | ||
} | ||
return self.splitAndAdd(contact, bitIndex); | ||
return self._splitAndAdd(contact, bitIndex); | ||
}; | ||
// contact: Object *required* contact object | ||
// id: Buffer *require* node id | ||
// contact: *required* the contact object to add | ||
KBucket.prototype.add = function add (contact) { | ||
if (!Buffer.isBuffer(contact.id)) { | ||
throw new TypeError("contact.id is not a Buffer"); | ||
} | ||
return this._add(contact, 0); | ||
}; | ||
// id: Buffer *required* node id | ||
// n: Integer *required* maximum number of closest contacts to return | ||
// bitIndex: Integer (Default: 0) | ||
// Return: Array of maximum of `n` closest contacts to the `contact` | ||
KBucket.prototype.closest = function closest (contact, n, bitIndex) { | ||
// Return: Array of maximum of `n` closest contacts to the node id | ||
KBucket.prototype._closest = function (id, n, bitIndex) { | ||
var self = this; | ||
@@ -168,13 +176,11 @@ | ||
if (!self.bucket) { | ||
bitIndex = bitIndex || 0; | ||
if (self.determineBucket(contact.id, bitIndex++) < 0) { | ||
contacts = self.low.closest(contact, n, bitIndex); | ||
if (self._determineBucket(id, bitIndex++) < 0) { | ||
contacts = self.low._closest(id, n, bitIndex); | ||
if (contacts.length < n) { | ||
contacts = contacts.concat(self.high.closest(contact, n, bitIndex)); | ||
contacts = contacts.concat(self.high._closest(id, n, bitIndex)); | ||
} | ||
} else { | ||
contacts = self.high.closest(contact, n, bitIndex); | ||
contacts = self.high._closest(id, n, bitIndex); | ||
if (contacts.length < n) { | ||
contacts = contacts.concat(self.low.closest(contact, n, bitIndex)); | ||
contacts = contacts.concat(self.low._closest(id, n, bitIndex)); | ||
} | ||
@@ -187,3 +193,3 @@ } | ||
contacts.forEach(function (storedContact) { | ||
storedContact.distance = KBucket.distance(storedContact.id, contact.id); | ||
storedContact.distance = KBucket.distance(storedContact.id, id); | ||
}); | ||
@@ -196,2 +202,12 @@ | ||
// id: Buffer *required* node id | ||
// n: Integer *required* maximum number of closest contacts to return | ||
// Return: Array of maximum of `n` closest contacts to the node id | ||
KBucket.prototype.closest = function (id, n) { | ||
if (!Buffer.isBuffer(id)) { | ||
throw new TypeError("id is not a Buffer"); | ||
} | ||
return this._closest(id, n, 0); | ||
}; | ||
// Counts the number of contacts recursively. | ||
@@ -213,3 +229,3 @@ // If this is a leaf, just return the number of contacts contained. Otherwise, | ||
// bitIndex: the bitIndex to which bit to check in the id Buffer | ||
KBucket.prototype.determineBucket = function determineBucket (id, bitIndex) { | ||
KBucket.prototype._determineBucket = function (id, bitIndex) { | ||
var self = this; | ||
@@ -255,19 +271,17 @@ | ||
// which branch of the tree to traverse and repeat. | ||
// id: *required* a Buffer specifying the ID of the contact to fetch | ||
// id: Buffer *required* The ID of the contact to fetch. | ||
// bitIndex: the bitIndex to which bit to check in the Buffer for navigating | ||
// the binary tree | ||
KBucket.prototype.get = function get (id, bitIndex) { | ||
KBucket.prototype._get = function (id, bitIndex) { | ||
var self = this; | ||
if (!self.bucket) { | ||
bitIndex = bitIndex || 0; | ||
if (self.determineBucket(id, bitIndex++) < 0) { | ||
return self.low.get(id, bitIndex); | ||
if (self._determineBucket(id, bitIndex++) < 0) { | ||
return self.low._get(id, bitIndex); | ||
} else { | ||
return self.high.get(id, bitIndex); | ||
return self.high._get(id, bitIndex); | ||
} | ||
} | ||
var index = self.indexOf({id: id}); // index of uses contact.id for matching | ||
var index = self._indexOf(id); // index of uses contact id for matching | ||
if (index < 0) { | ||
@@ -280,8 +294,19 @@ return null; // contact not found | ||
// Returns the index of the contact if it exists | ||
// **NOTE**: indexOf() does not compare vectorClock | ||
KBucket.prototype.indexOf = function indexOf (contact) { | ||
// Get a contact by its exact ID. | ||
// If this is a leaf, loop through the bucket contents and return the correct | ||
// contact if we have it or null if not. If this is an inner node, determine | ||
// which branch of the tree to traverse and repeat. | ||
// id: Buffer *required* The ID of the contact to fetch. | ||
KBucket.prototype.get = function get (id) { | ||
if (!Buffer.isBuffer(id)) { | ||
throw new TypeError("id is not a Buffer"); | ||
} | ||
return this._get(id, 0); | ||
}; | ||
// Returns the index of the contact with the given id if it exists | ||
KBucket.prototype._indexOf = function indexOf (id) { | ||
var self = this; | ||
for (var i = 0; i < self.bucket.length; i++) { | ||
if (bufferEqual(self.bucket[i].id, contact.id)) return i; | ||
if (bufferEquals(self.bucket[i].id, id)) return i; | ||
} | ||
@@ -291,6 +316,6 @@ return -1; | ||
// contact: *required* the contact object to remove | ||
// id: Buffer *required* The ID of the contact to remove. | ||
// bitIndex: the bitIndex to which bit to check in the Buffer for navigating | ||
// the binary tree | ||
KBucket.prototype.remove = function remove (contact, bitIndex) { | ||
KBucket.prototype._remove = function (id, bitIndex) { | ||
var self = this; | ||
@@ -303,16 +328,25 @@ | ||
// delegate to the appropriate node for further processing | ||
bitIndex = bitIndex || 0; | ||
if (self.determineBucket(contact.id, bitIndex++) < 0) { | ||
return self.low.remove(contact, bitIndex); | ||
if (self._determineBucket(id, bitIndex++) < 0) { | ||
return self.low._remove(id, bitIndex); | ||
} else { | ||
return self.high.remove(contact, bitIndex); | ||
return self.high._remove(id, bitIndex); | ||
} | ||
} | ||
var index = self.indexOf(contact); | ||
if (index >= 0) self.bucket.splice(index, 1); | ||
var index = self._indexOf(id); | ||
if (index >= 0) { | ||
var contact = self.bucket.splice(index, 1)[0]; | ||
self.emit('removed', contact); | ||
} | ||
return self; | ||
}; | ||
// id: Buffer *required* he ID of the contact to remove. | ||
KBucket.prototype.remove = function remove (id) { | ||
if (!Buffer.isBuffer(id)) { | ||
throw new TypeError("id is not a Buffer"); | ||
} | ||
return this._remove(id, 0); | ||
}; | ||
// Splits the bucket, redistributes contacts to the new buckets, and marks the | ||
@@ -324,3 +358,3 @@ // bucket that was split as an inner node of the binary tree of buckets by | ||
// binary tree | ||
KBucket.prototype.splitAndAdd = function splitAndAdd (contact, bitIndex) { | ||
KBucket.prototype._splitAndAdd = function (contact, bitIndex) { | ||
var self = this; | ||
@@ -332,3 +366,3 @@ self.low = new KBucket({ | ||
numberOfNodesToPing: self.numberOfNodesToPing, | ||
ping: self.ping | ||
root: self.root | ||
}); | ||
@@ -340,3 +374,3 @@ self.high = new KBucket({ | ||
numberOfNodesToPing: self.numberOfNodesToPing, | ||
ping: self.ping | ||
root: self.root | ||
}); | ||
@@ -348,3 +382,3 @@ | ||
self.bucket.forEach(function (storedContact) { | ||
if (self.determineBucket(storedContact.id, bitIndex) < 0) { | ||
if (self._determineBucket(storedContact.id, bitIndex) < 0) { | ||
self.low.add(storedContact); | ||
@@ -361,3 +395,3 @@ } else { | ||
// "dontSplit" (i.e. "far away") | ||
if (self.determineBucket(self.localNodeId, bitIndex) < 0) { | ||
if (self._determineBucket(self.localNodeId, bitIndex) < 0) { | ||
// local node belongs to "low" bucket, so mark the other one | ||
@@ -370,3 +404,3 @@ self.high.dontSplit = true; | ||
// add the contact being added | ||
self.add(contact, bitIndex); | ||
self._add(contact, bitIndex); | ||
@@ -402,6 +436,6 @@ return self; | ||
// (index has already been computed in a previous calculation) | ||
KBucket.prototype.update = function update (contact, index) { | ||
KBucket.prototype._update = function (contact, index) { | ||
var self = this; | ||
// sanity check | ||
if (!bufferEqual(self.bucket[index].id, contact.id)) { | ||
if (!bufferEquals(self.bucket[index].id, contact.id)) { | ||
throw new Error("indexOf() calculation resulted in wrong index") | ||
@@ -420,2 +454,3 @@ } | ||
self.bucket.push(selection); // add more recent contact version | ||
self.emit('updated', incumbent, selection); | ||
}; |
{ | ||
"name": "k-bucket", | ||
"version": "2.0.1", | ||
"version": "3.0.0", | ||
"description": "Kademlia DHT K-bucket implementation as a binary tree", | ||
"scripts": { | ||
"distance-benchmark": "node benchmarks/distance.js", | ||
"test": "node scripts/test.js" | ||
@@ -13,3 +14,4 @@ }, | ||
"dependencies": { | ||
"buffer-equal": "0.0.1", | ||
"buffer-equals": "^1.0.3", | ||
"inherits": "^2.0.1", | ||
"randombytes": "^2.0.3" | ||
@@ -16,0 +18,0 @@ }, |
135
README.md
@@ -41,3 +41,3 @@ # k-bucket | ||
This *k-bucket* implementation implements a conflict resolution mechanism using an `arbiter` function. The purpose of the `arbiter` is to choose between two `contact` objects with the same `id` but perhaps different properties and determine which one should be stored. As the `arbiter` function returns the actual object to be stored, it does not need to make an either/or choice, but instead could perform some sort of operation and return the result as a new object that would then be stored. See [kBucket.update(contact, index)](#kbucketupdatecontact-index) for detailed semantics of which `contact` (`incumbent` or `candidate`) is selected. | ||
This *k-bucket* implementation implements a conflict resolution mechanism using an `arbiter` function. The purpose of the `arbiter` is to choose between two `contact` objects with the same `id` but perhaps different properties and determine which one should be stored. As the `arbiter` function returns the actual object to be stored, it does not need to make an either/or choice, but instead could perform some sort of operation and return the result as a new object that would then be stored. See [kBucket._update(contact, index)](#kbucket_updatecontact-index) for detailed semantics of which `contact` (`incumbent` or `candidate`) is selected. | ||
@@ -103,3 +103,3 @@ For example, an `arbiter` function implementing a `vectorClock` mechanism would look something like: | ||
As more contacts are added to the "far" k-bucket and it reaches its capacity, it does not split. Instead, the k-bucket calls the "ping" callback (`function (oldContacts, newContact) {...});` and includes an array of old contact nodes that it hasn't heard from in a while and requires you to confirm that those contact nodes still respond (literally respond to a PING RPC). If an old contact node still responds, it should be re-added (`kBucket.add(oldContact)`) back to the k-bucket. This puts the old contact on the "recently heard from" end of the list of nodes in the k-bucket. If the old contact does not respond, it should be removed (`kBucket.remove(oldContact)`) and the new contact being added now has room to be stored (`kBucket.add(newContact)`). | ||
As more contacts are added to the "far" k-bucket and it reaches its capacity, it does not split. Instead, the k-bucket emits a "ping" event (register a listener: `kBucket.on('ping', function (oldContacts, newContact) {...});` and includes an array of old contact nodes that it hasn't heard from in a while and requires you to confirm that those contact nodes still respond (literally respond to a PING RPC). If an old contact node still responds, it should be re-added (`kBucket.add(oldContact)`) back to the k-bucket. This puts the old contact on the "recently heard from" end of the list of nodes in the k-bucket. If the old contact does not respond, it should be removed (`kBucket.remove(oldContact.id)`) and the new contact being added now has room to be stored (`kBucket.add(newContact)`). | ||
@@ -109,8 +109,12 @@ **Public API** | ||
* [new KBucket(options)](#new-kbucketoptions) | ||
* [kBucket.add(contact, \[bitIndex\])](#kbucketaddcontact-bitindex) | ||
* [kBucket.closest(contact, n, \[bitIndex\])](#kbucketclosestcontact-n-bitindex) | ||
* [kBucket.add(contact)](#kbucketaddcontact) | ||
* [kBucket.closest(id, n)](#kbucketclosestid-n) | ||
* [kBucket.count()](#kbucketcount) | ||
* [kBucket.get(id, \[bitIndex\])](#kbucketgetid-bitindex) | ||
* [kBucket.remove(contact, \[bitIndex\])](#kbucketremovecontact-bitindex) | ||
* [kBucket.get(id)](#kbucketgetid) | ||
* [kBucket.remove(id)](#kbucketremoveid) | ||
* [kBucket.toArray()](#kbuckettoarray) | ||
* [Event 'added'](#event-added) | ||
* [Event 'ping'](#event-ping) | ||
* [Event 'removed'](#event-removed) | ||
* [Event 'updated'](#event-updated) | ||
@@ -132,11 +136,8 @@ #### KBucket.distance(firstId, secondId) | ||
* `numberOfNodesPerKBucket`: _Integer_ _(Default: 20)_ The number of nodes that a k-bucket can contain before being full or split. | ||
* `numberOfNodesToPing`: _Integer_ _(Default: 3)_ The number of nodes to ping when a bucket that should not be split becomes full. KBucket will call the `ping` callback that contains `numberOfNodesToPing` nodes that have not been contacted the longest. | ||
* `ping`: _Function_ _(Default: no-op)_ | ||
`function (oldContacts, newContact) {}` Callback called every time a contact is added that would exceed the capacity of a don't split k-bucket it belongs to. | ||
* `oldContacts`: _Array_ The array of contacts to ping. | ||
* `newContact`: _Object_ The new contact to be added if one of old contacts does not respond. | ||
* `numberOfNodesToPing`: _Integer_ _(Default: 3)_ The number of nodes to ping when a bucket that should not be split becomes full. KBucket will emit a `ping` event that contains `numberOfNodesToPing` nodes that have not been contacted the longest. | ||
* `root`: _Object_ _**CAUTION: reserved for internal use**_ Provides a reference to the root of the tree data structure as the k-bucket splits when new contacts are added. | ||
Creates a new KBucket. | ||
#### kBucket.add(contact, [bitIndex]) | ||
#### kBucket.add(contact) | ||
@@ -146,3 +147,2 @@ * `contact`: _Object_ The contact object to add. | ||
* Any satellite data that is part of the `contact` object will not be altered, only `id` is used. | ||
* `bitIndex`: _Integer_ _(Default: 0)_ _**CAUTION: reserved for internal use**_ The bit index to which bit to check in the `id` Buffer. | ||
* Return: _Object_ The k-bucket itself. | ||
@@ -152,12 +152,9 @@ | ||
#### kBucket.closest(contact, n, [bitIndex]) | ||
#### kBucket.closest(id, n) | ||
* `contact`: _Object_ The contact object to find closest contacts to. | ||
* `id`: _Buffer_ Contact node id. | ||
* Any satellite data that is part of the `contact` object will not be altered, only `id` is used. | ||
* `id`: _Buffer_ Contact node id. | ||
* `n`: _Integer_ The maximum number of closest contacts to return. | ||
* `bitIndex`: _Integer_ _(Default: 0)_ _**CAUTION: reserved for internal use**_ The bit index to which bit to check in the `id` Buffer. | ||
* Return: _Array_ Maximum of `n` closest contacts to the `contact`. | ||
* Return: _Array_ Maximum of `n` closest contacts to the node id. | ||
Get the `n` closest contacts to the provided `contact`. "Closest" here means: closest according to the XOR metric of the `contact` node id. | ||
Get the `n` closest contacts to the provided node id. "Closest" here means: closest according to the XOR metric of the `contact` node id. | ||
@@ -170,4 +167,24 @@ #### kBucket.count() | ||
#### kBucket.determineBucket(id, [bitIndex]) | ||
#### kBucket.get(id) | ||
* `id`: _Buffer_ The ID of the `contact` to fetch. | ||
* Return: _Object_ The `contact` if available, otherwise null | ||
Retrieves the `contact`. | ||
#### kBucket.remove(id) | ||
* `id`: _Buffer_ The ID of the `contact` to remove. | ||
* Return: _Object_ The k-bucket itself. | ||
Removes `contact` with the provided `id`. | ||
#### kBucket.toArray() | ||
* Return: _Array_ All of the contacts in the tree, as an array | ||
Traverses the tree, putting all the contacts into one array. | ||
#### kBucket._determineBucket(id, [bitIndex]) | ||
_**CAUTION: reserved for internal use**_ | ||
@@ -181,12 +198,4 @@ | ||
#### kBucket.get(id, [bitIndex]) | ||
#### kBucket._indexOf(contact) | ||
* `id`: _Buffer_ The ID of the `contact` to fetch | ||
* `bitIndex`: _Integer_ _(Default: 0)_ _**CAUTION: reserved for internal use**_ The bit index to which bit to check in the `id` Buffer. | ||
* Return: _Object_ The `contact` if available, otherwise null | ||
Retrieves the `contact`. | ||
#### kBucket.indexOf(contact) | ||
_**CAUTION: reserved for internal use**_ | ||
@@ -201,16 +210,6 @@ | ||
_NOTE: `kBucket.indexOf(contact)` does not use `arbiter` in the comparison. | ||
_NOTE: `kBucket._indexOf(contact)` does not use `arbiter` in the comparison. | ||
#### kBucket.remove(contact, [bitIndex]) | ||
#### kBucket._splitAndAdd(contact, [bitIndex]) | ||
* `contact`: _Object_ The contact object to remove. | ||
* `id`: _Buffer_ contact node id. | ||
* Any satellite data can be part of the `contact` object, only `id` is used | ||
* `bitIndex`: _Integer_ _(Default: 0)_ _**CAUTION: reserved for internal use**_ The bit index to which bit to check in the `id` Buffer. | ||
* Return: _Object_ The k-bucket itself. | ||
Removes the `contact`. | ||
#### kBucket.splitAndAdd(contact, [bitIndex]) | ||
_**CAUTION: reserved for internal use**_ | ||
@@ -226,10 +225,4 @@ | ||
#### kBucket.toArray() | ||
#### kBucket._update(contact, index) | ||
* Return: _Array_ All of the contacts in the tree, as an array | ||
Traverses the tree, putting all the contacts into one array. | ||
#### kBucket.update(contact, index) | ||
_**CAUTION: reserved for internal use**_ | ||
@@ -244,2 +237,44 @@ | ||
#### Event: 'added' | ||
* `newContact`: _Object_ The new contact that was added. | ||
Emitted only when `newContact` was added to bucket and it was not stored in the bucket before. | ||
#### Event: 'ping' | ||
* `oldContacts`: _Array_ The array of contacts to ping. | ||
* `newContact`: _Object_ The new contact to be added if one of old contacts does not respond. | ||
Emitted every time a contact is added that would exceed the capacity of a _don't split_ k-bucket it belongs to. | ||
#### Event: 'removed' | ||
* `contact`: _Object_ The contact that was removed. | ||
Emitted when `contact` was removed from the bucket. | ||
#### Event: 'updated' | ||
* `oldContact`: _Object_ The contact that was stored prior to the update. | ||
* `newContact`: _Object_ The new contact that is now stored after the update. | ||
Emitted when a previously existing ("previously existing" means `oldContact.id` equals `newContact.id`) contact was added to the bucket and it was replaced with `newContact`. | ||
## Releases | ||
[Current releases](https://github.com/tristanls/k-bucket/releases). | ||
### Policy | ||
We follow the semantic versioning policy ([semver.org](http://semver.org/)) with a caveat: | ||
> Given a version number MAJOR.MINOR.PATCH, increment the: | ||
> | ||
>MAJOR version when you make incompatible API changes,<br/> | ||
>MINOR version when you add functionality in a backwards-compatible manner, and<br/> | ||
>PATCH version when you make backwards-compatible bug fixes. | ||
**caveat**: Major version zero is a special case indicating development version that may make incompatible API changes without incrementing MAJOR version. | ||
## Sources | ||
@@ -250,2 +285,2 @@ | ||
- [A formal specification of the Kademlia distributed hash table](http://maude.sip.ucm.es/kademlia/files/pita_kademlia.pdf) | ||
- [Distributed Hash Tables (part 2)](http://offthelip.org/?p=157) | ||
- [Distributed Hash Tables (part 2)](https://web.archive.org/web/20140217064545/http://offthelip.org/?p=157) |
@@ -7,2 +7,12 @@ "use strict"; | ||
test['throws TypeError if contact.id is not a Buffer'] = function (test) { | ||
test.expect(1); | ||
var kBucket = new KBucket(); | ||
var contact = {id: "foo"}; | ||
test.throws(function () { | ||
kBucket.add(contact); | ||
}, TypeError); | ||
test.done(); | ||
}; | ||
test['adding a contact places it in bucket'] = function (test) { | ||
@@ -48,3 +58,6 @@ test.expect(1); | ||
test.expect(3 /*numberOfNodesToPing*/ + 2); | ||
var ping = function (contacts, replacement) { | ||
var kBucket = new KBucket({ | ||
localNodeId: new Buffer('0000', 'hex') | ||
}); | ||
kBucket.on('ping', function (contacts, replacement) { | ||
test.equal(contacts.length, kBucket.numberOfNodesToPing); | ||
@@ -58,7 +71,3 @@ // console.dir(kBucket.high.bucket[0]); | ||
test.done(); | ||
}; | ||
var kBucket = new KBucket({ | ||
localNodeId: new Buffer('0000', 'hex'), | ||
ping: ping | ||
}); | ||
}) | ||
for (var j = 0; j < kBucket.numberOfNodesPerKBucket + 1; j++) { | ||
@@ -73,1 +82,13 @@ iString = j.toString('16'); | ||
}; | ||
test['should generate event "added" once'] = function (test) { | ||
test.expect(1); | ||
var kBucket = new KBucket(); | ||
var contact = {id: new Buffer("a")}; | ||
kBucket.on('added', function (newContact) { | ||
test.deepEqual(newContact, contact); | ||
}); | ||
kBucket.add(contact); | ||
kBucket.add(contact); | ||
test.done(); | ||
}; |
@@ -7,2 +7,12 @@ "use strict"; | ||
test['throws TypeError if contact.id is not a Buffer'] = function (test) { | ||
test.expect(1); | ||
var kBucket = new KBucket(); | ||
var contact = {id: "foo"}; | ||
test.throws(function () { | ||
kBucket.closest(contact.id, 4); | ||
}, TypeError); | ||
test.done(); | ||
}; | ||
test['closest nodes are returned'] = function (test) { | ||
@@ -30,3 +40,3 @@ test.expect(3); | ||
var contact = {id: new Buffer('15', 'hex')};// 00010101 | ||
var contacts = kBucket.closest(contact, 3); | ||
var contacts = kBucket.closest(contact.id, 3); | ||
test.deepEqual(contacts[0].id, new Buffer('11', 'hex')); // distance: 00000100 | ||
@@ -60,3 +70,3 @@ test.deepEqual(contacts[1].id, new Buffer('10', 'hex')); // distance: 00000101 | ||
var contact = {id: new Buffer('11', 'hex')};// 00010001 | ||
var contacts = kBucket.closest(contact, 3); | ||
var contacts = kBucket.closest(contact.id, 3); | ||
test.deepEqual(contacts[0].id, new Buffer('11', 'hex')); // distance: 00000000 | ||
@@ -84,3 +94,3 @@ test.deepEqual(contacts[1].id, new Buffer('10', 'hex')); // distance: 00000001 | ||
var contact = {id: new Buffer('0003', 'hex')}; // 0000000000000011 | ||
var contacts = kBucket.closest(contact, 22); | ||
var contacts = kBucket.closest(contact.id, 22); | ||
test.deepEqual(contacts[0].id, new Buffer('0001', 'hex')); // distance: 0000000000000010 | ||
@@ -110,2 +120,2 @@ test.deepEqual(contacts[1].id, new Buffer('0103', 'hex')); // distance: 0000000100000000 | ||
test.done(); | ||
}; | ||
}; |
"use strict"; | ||
var bufferEqual = require('buffer-equal'); | ||
var EventEmitter = require('events').EventEmitter; | ||
@@ -35,7 +36,14 @@ var KBucket = require('../index.js'); | ||
test['ping is \'Function\' if not provided'] = function (test) { | ||
test['root is \'self\' if not provided'] = function (test) { | ||
test.expect(1); | ||
var kBucket = new KBucket(); | ||
test.ok(kBucket.ping instanceof Function); | ||
test.strictEqual(kBucket.root, kBucket); | ||
test.done(); | ||
}; | ||
test['inherits from EventEmitter'] = function (test) { | ||
test.expect(1); | ||
var kBucket = new KBucket(); | ||
test.ok(kBucket instanceof EventEmitter); | ||
test.done(); | ||
}; |
@@ -10,3 +10,3 @@ "use strict"; | ||
var kBucket = new KBucket(); | ||
test.equal(kBucket.determineBucket(new Buffer("00", "hex"), 0), -1); | ||
test.equal(kBucket._determineBucket(new Buffer("00", "hex"), 0), -1); | ||
test.done(); | ||
@@ -18,3 +18,3 @@ }; | ||
var kBucket = new KBucket(); | ||
test.equal(kBucket.determineBucket(new Buffer("40", "hex"), 0), -1); | ||
test.equal(kBucket._determineBucket(new Buffer("40", "hex"), 0), -1); | ||
test.done(); | ||
@@ -26,3 +26,3 @@ }; | ||
var kBucket = new KBucket(); | ||
test.equal(kBucket.determineBucket(new Buffer("40", "hex"), 1), 1); | ||
test.equal(kBucket._determineBucket(new Buffer("40", "hex"), 1), 1); | ||
test.done(); | ||
@@ -34,3 +34,3 @@ }; | ||
var kBucket = new KBucket(); | ||
test.equal(kBucket.determineBucket(new Buffer("40", "hex"), 2), -1); | ||
test.equal(kBucket._determineBucket(new Buffer("40", "hex"), 2), -1); | ||
test.done(); | ||
@@ -42,3 +42,3 @@ }; | ||
var kBucket = new KBucket(); | ||
test.equal(kBucket.determineBucket(new Buffer("40", "hex"), 9), -1); | ||
test.equal(kBucket._determineBucket(new Buffer("40", "hex"), 9), -1); | ||
test.done(); | ||
@@ -50,3 +50,3 @@ }; | ||
var kBucket = new KBucket(); | ||
test.equal(kBucket.determineBucket(new Buffer("41", "hex"), 7), 1); | ||
test.equal(kBucket._determineBucket(new Buffer("41", "hex"), 7), 1); | ||
test.done(); | ||
@@ -58,3 +58,3 @@ }; | ||
var kBucket = new KBucket(); | ||
test.equal(kBucket.determineBucket(new Buffer("4100", "hex"), 7), 1); | ||
test.equal(kBucket._determineBucket(new Buffer("4100", "hex"), 7), 1); | ||
test.done(); | ||
@@ -66,4 +66,4 @@ }; | ||
var kBucket = new KBucket(); | ||
test.equal(kBucket.determineBucket(new Buffer("004100", "hex"), 15), 1); | ||
test.equal(kBucket._determineBucket(new Buffer("004100", "hex"), 15), 1); | ||
test.done(); | ||
}; | ||
}; |
@@ -45,2 +45,2 @@ "use strict"; | ||
test.done(); | ||
}; | ||
}; |
@@ -8,2 +8,11 @@ "use strict"; | ||
test['throws TypeError if id is not a Buffer'] = function (test) { | ||
test.expect(1); | ||
var kBucket = new KBucket(); | ||
test.throws(function () { | ||
kBucket.get("foo"); | ||
}, TypeError); | ||
test.done(); | ||
}; | ||
test['get retrieves null if no contacts'] = function (test) { | ||
@@ -54,2 +63,2 @@ test.expect(1); | ||
test.done(); | ||
}; | ||
}; |
@@ -12,3 +12,3 @@ "use strict"; | ||
kBucket.add({id: new Buffer("a")}); | ||
test.equal(kBucket.indexOf({id: new Buffer("a")}), 0); | ||
test.equal(kBucket._indexOf(new Buffer("a")), 0); | ||
test.done(); | ||
@@ -21,4 +21,4 @@ }; | ||
kBucket.add({id: new Buffer("a")}); | ||
test.equal(kBucket.indexOf({id: new Buffer("b")}), -1); | ||
test.equal(kBucket._indexOf(new Buffer("b")), -1); | ||
test.done(); | ||
}; | ||
}; |
@@ -7,2 +7,12 @@ "use strict"; | ||
test['throws TypeError if contact.id is not a Buffer'] = function (test) { | ||
test.expect(1); | ||
var kBucket = new KBucket(); | ||
var contact = {id: "foo"}; | ||
test.throws(function () { | ||
kBucket.remove(contact.id); | ||
}, TypeError); | ||
test.done(); | ||
}; | ||
test['removing a contact should remove contact from nested buckets'] = function (test) { | ||
@@ -23,6 +33,18 @@ test.expect(2); | ||
var contactToDelete = {id: new Buffer('8000', 'hex')}; | ||
test.equal(kBucket.high.indexOf(contactToDelete), 0); | ||
kBucket.remove({id: new Buffer('8000', 'hex')}); | ||
test.equal(kBucket.high.indexOf(contactToDelete), -1); | ||
test.equal(kBucket.high._indexOf(contactToDelete.id), 0); | ||
kBucket.remove(new Buffer('8000', 'hex')); | ||
test.equal(kBucket.high._indexOf(contactToDelete.id), -1); | ||
test.done(); | ||
}; | ||
}; | ||
test['should generate "removed"'] = function (test) { | ||
test.expect(1); | ||
var kBucket = new KBucket(); | ||
var contact = {id: new Buffer("a")}; | ||
kBucket.on('removed', function (removedContact) { | ||
test.deepEqual(removedContact, contact); | ||
}); | ||
kBucket.add(contact); | ||
kBucket.remove(contact.id); | ||
test.done(); | ||
}; |
@@ -14,3 +14,3 @@ "use strict"; | ||
try { | ||
kBucket.update(contact, 1); | ||
kBucket._update(contact, 1); | ||
} catch (exception) { | ||
@@ -27,3 +27,3 @@ test.ok(true); | ||
kBucket.add(contact); | ||
kBucket.update({id: new Buffer("a"), vectorClock: 2}, 0); | ||
kBucket._update({id: new Buffer("a"), vectorClock: 2}, 0); | ||
test.equal(kBucket.bucket[0].vectorClock, 3); | ||
@@ -39,3 +39,3 @@ test.done(); | ||
kBucket.add({id: new Buffer("b")}); | ||
kBucket.update(contact, 0); | ||
kBucket._update(contact, 0); | ||
test.equal(kBucket.bucket[1], contact); | ||
@@ -52,3 +52,3 @@ test.done(); | ||
kBucket.add({id: new Buffer("b")}); | ||
kBucket.update({id: new Buffer("a"), newer: 'property', vectorClock: 4}, 0); | ||
kBucket._update({id: new Buffer("a"), newer: 'property', vectorClock: 4}, 0); | ||
test.ok(bufferEqual(kBucket.bucket[1].id, contact.id)); | ||
@@ -59,2 +59,16 @@ test.equal(kBucket.bucket[1].vectorClock, 4); | ||
test.done(); | ||
}; | ||
}; | ||
test['should generate "updated"'] = function (test) { | ||
test.expect(2); | ||
var kBucket = new KBucket(); | ||
var contact1 = {id: new Buffer("a"), vectorClock: 1}; | ||
var contact2 = {id: new Buffer("a"), vectorClock: 2}; | ||
kBucket.on('updated', function (oldContact, newContact) { | ||
test.deepEqual(oldContact, contact1); | ||
test.deepEqual(newContact, contact2); | ||
}) | ||
kBucket.add(contact1); | ||
kBucket.add(contact2); | ||
test.done(); | ||
}; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
58975
19
1073
275
3
+ Addedbuffer-equals@^1.0.3
+ Addedinherits@^2.0.1
+ Addedbuffer-equals@1.0.4(transitive)
+ Addedinherits@2.0.4(transitive)
- Removedbuffer-equal@0.0.1
- Removedbuffer-equal@0.0.1(transitive)