Skip to content

Commit

Permalink
feat: make KBucket a generic
Browse files Browse the repository at this point in the history
  • Loading branch information
richardschneider committed Nov 22, 2018
1 parent babdfe5 commit 35aa222
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 47 deletions.
17 changes: 9 additions & 8 deletions src/Bucket.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,17 @@
namespace Makaretu.Collections
{
/// <summary>
/// A node in the <see cref="KBucket"/>.
/// A node in the <see cref="KBucket{T}"/>.
/// </summary>
class Bucket
class Bucket<T>
where T: class, IContact
{
public List<IContact> Contacts = new List<IContact>();
public List<T> Contacts = new List<T>();
public bool DontSplit;
public Bucket Left;
public Bucket Right;
public Bucket<T> Left;
public Bucket<T> Right;

public bool Contains(IContact item)
public bool Contains(T item)
{
if (Contacts == null)
{
Expand All @@ -25,7 +26,7 @@ public bool Contains(IContact item)
return Contacts.Any(c => c.Id.SequenceEqual(item.Id));
}

public IContact Get(byte[] id)
public T Get(byte[] id)
{
return Contacts?.FirstOrDefault(c => c.Id.SequenceEqual(id));
}
Expand All @@ -50,7 +51,7 @@ public int DeepCount()
return n;
}

public IEnumerable<IContact> AllContacts()
public IEnumerable<T> AllContacts()
{
if (Contacts != null)
{
Expand Down
44 changes: 24 additions & 20 deletions src/KBucket.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,16 @@ namespace Makaretu.Collections
/// <summary>
/// Implementation of a Kademlia DHT k-bucket used for storing contact (peer node) information.
/// </summary>
/// <typeparam name="T">
/// A contact type that implements <see cref="IContact"/> .
/// </typeparam>
/// <remarks>
/// All public methods and properties are thead-safe.
/// </remarks>
public class KBucket : ICollection<IContact>
public class KBucket<T> : ICollection<T>
where T: class, IContact
{
Bucket root = new Bucket();
Bucket<T> root = new Bucket<T>();
readonly ReaderWriterLockSlim rwlock = new ReaderWriterLockSlim();
byte[] localContactId;

Expand Down Expand Up @@ -53,7 +57,7 @@ public byte[] LocalContactId {
/// <summary>
/// Finds the XOR distance between the two contacts.
/// </summary>
public int Distance(IContact a, IContact b)
public int Distance(T a, T b)
{
Validate(a);
Validate(b);
Expand Down Expand Up @@ -90,7 +94,7 @@ public int Distance(byte[] a, byte[] b)
/// <remarks>
/// "Closest" is the XOR metric of the contact.
/// </remarks>
public IEnumerable<IContact> Closest(IContact contact)
public IEnumerable<T> Closest(T contact)
{
Validate(contact);
return Closest(contact.Id);
Expand All @@ -108,7 +112,7 @@ public IEnumerable<IContact> Closest(IContact contact)
/// <remarks>
/// "Closest" is the XOR metric of the contact.
/// </remarks>
public IEnumerable<IContact> Closest(byte[] id)
public IEnumerable<T> Closest(byte[] id)
{
return this
.Select(c => new { distance = Distance(c.Id, id), contact = c })
Expand All @@ -123,7 +127,7 @@ public IEnumerable<IContact> Closest(byte[] id)
public bool IsReadOnly => false;

/// <inheritdoc />
public void Add(IContact item)
public void Add(T item)
{
Validate(item);

Expand All @@ -141,11 +145,11 @@ public void Add(IContact item)
/// <inheritdoc />
public void Clear()
{
root = new Bucket();
root = new Bucket<T>();
}

/// <inheritdoc />
public bool Contains(IContact item)
public bool Contains(T item)
{
Validate(item);

Expand Down Expand Up @@ -174,7 +178,7 @@ public bool Contains(IContact item)
/// <returns>
/// <b>true</b> if the <paramref name="id"/> is found; otherwise <b>false</b>.
/// </returns>
public bool TryGet(byte[] id, out IContact contact)
public bool TryGet(byte[] id, out T contact)
{
rwlock.EnterReadLock();
try
Expand All @@ -189,7 +193,7 @@ public bool TryGet(byte[] id, out IContact contact)
}

/// <inheritdoc />
public void CopyTo(IContact[] array, int arrayIndex)
public void CopyTo(T[] array, int arrayIndex)
{
foreach (var contact in this)
{
Expand All @@ -198,7 +202,7 @@ public void CopyTo(IContact[] array, int arrayIndex)
}

/// <inheritdoc />
public IEnumerator<IContact> GetEnumerator()
public IEnumerator<T> GetEnumerator()
{
rwlock.EnterReadLock();
try
Expand All @@ -215,7 +219,7 @@ public IEnumerator<IContact> GetEnumerator()
}

/// <inheritdoc />
public bool Remove(IContact item)
public bool Remove(T item)
{
Validate(item);

Expand Down Expand Up @@ -246,15 +250,15 @@ IEnumerator IEnumerable.GetEnumerator()
/// When <paramref name="contact"/> is null or its <see cref="IContact.Id"/>
/// is null or empty.
/// </exception>
void Validate(IContact contact)
void Validate(T contact)
{
if (contact == null)
throw new ArgumentNullException("contact");
if (contact.Id == null || contact.Id.Length == 0)
throw new ArgumentNullException("contact.Id");
}

void _Add(IContact contact)
void _Add(T contact)
{
var bitIndex = 0;
var node = root;
Expand Down Expand Up @@ -302,10 +306,10 @@ void _Add(IContact contact)
/// node that was split as an inner node of the binary tree of nodes by
/// setting this.root.contacts = null
/// </summary>
void _Split(Bucket node, int bitIndex)
void _Split(Bucket<T> node, int bitIndex)
{
node.Left = new Bucket();
node.Right = new Bucket();
node.Left = new Bucket<T>();
node.Right = new Bucket<T>();

// redistribute existing contacts amongst the two newly created nodes
foreach (var contact in node.Contacts)
Expand All @@ -324,7 +328,7 @@ void _Split(Bucket node, int bitIndex)
// TODO: otherNode.DontSplit = true;
}

private void _Update(Bucket node, IContact contact)
private void _Update(Bucket<T> node, T contact)
{
// TODO
}
Expand All @@ -335,7 +339,7 @@ private void _Update(Bucket node, IContact contact)
/// <returns>
/// Left leaf if `id` at `bitIndex` is 0, right leaf otherwise
/// </returns>
Bucket _DetermineNode(Bucket node, byte[]id, int bitIndex)
Bucket<T> _DetermineNode(Bucket<T> node, byte[]id, int bitIndex)
{

// id's that are too short are put in low bucket (1 byte = 8 bits)
Expand Down Expand Up @@ -377,7 +381,7 @@ Bucket _DetermineNode(Bucket node, byte[]id, int bitIndex)
/// <returns>
/// <b>null</b> or the found contact.
/// </returns>
IContact _Get(byte[] id)
T _Get(byte[] id)
{
/*
* If this is a leaf, loop through the bucket contents and return the correct
Expand Down
8 changes: 4 additions & 4 deletions test/ClosestTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class ClosestTest
[TestMethod]
public void ClosestNodes()
{
var kBucket = new KBucket();
var kBucket = new KBucket<Contact>();
for (var i = 0; i < 0x12; ++i)
{
kBucket.Add(new Contact((byte)i));
Expand All @@ -32,7 +32,7 @@ public void ClosestNodes()
[TestMethod]
public void All()
{
var kBucket = new KBucket();
var kBucket = new KBucket<Contact>();
for (var i = 0; i < 100; ++i)
{
kBucket.Add(new Contact((byte)(i / 256), (byte)(i % 256)));
Expand All @@ -45,7 +45,7 @@ public void All()
[TestMethod]
public void ClosestNodes_ExactMatch()
{
var kBucket = new KBucket();
var kBucket = new KBucket<Contact>();
for (var i = 0; i < 0x12; ++i)
{
kBucket.Add(new Contact((byte)i));
Expand All @@ -60,7 +60,7 @@ public void ClosestNodes_ExactMatch()
[TestMethod]
public void ClosestNodes_PartialBuckets()
{
var kBucket = new KBucket();
var kBucket = new KBucket<Contact>();
for (var i = 0; i < kBucket.ContactsPerBucket; ++i)
{
kBucket.Add(new Contact((byte)0x80, (byte)i));
Expand Down
28 changes: 14 additions & 14 deletions test/CollectionTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class CollectionTest
[TestMethod]
public void Add()
{
var bucket = new KBucket();
var bucket = new KBucket<Contact>();
var x = new Contact("1");
bucket.Add(x);
Assert.AreEqual(1, bucket.Count);
Expand All @@ -23,7 +23,7 @@ public void Add()
[TestMethod]
public void AddDuplicate()
{
var bucket = new KBucket();
var bucket = new KBucket<Contact>();
var x = new Contact("1");
bucket.Add(x);
bucket.Add(x);
Expand All @@ -34,7 +34,7 @@ public void AddDuplicate()
[TestMethod]
public void AddBadContact()
{
var bucket = new KBucket();
var bucket = new KBucket<Contact>();
ExceptionAssert.Throws<ArgumentNullException>(() => bucket.Add(null));
ExceptionAssert.Throws<ArgumentNullException>(() => bucket.Add(new Contact("a") { Id = null }));
ExceptionAssert.Throws<ArgumentNullException>(() => bucket.Add(new Contact("a") { Id = new byte[0] }));
Expand All @@ -43,24 +43,24 @@ public void AddBadContact()
[TestMethod]
public void TryGet()
{
var bucket = new KBucket();
var bucket = new KBucket<Contact>();
var alpha = new Contact("alpha");
var beta = new Contact("beta");
bucket.Add(alpha);

var q = bucket.TryGet(alpha.Id, out IContact found);
var q = bucket.TryGet(alpha.Id, out Contact found);
Assert.IsTrue(q);
Assert.AreSame(alpha, found);

q = bucket.TryGet(beta.Id, out IContact notfound);
q = bucket.TryGet(beta.Id, out Contact notfound);
Assert.IsFalse(q);
Assert.IsNull(notfound);
}

[TestMethod]
public void Count()
{
var bucket = new KBucket();
var bucket = new KBucket<Contact>();
Assert.AreEqual(0, bucket.Count);

bucket.Add(new Contact("a"));
Expand All @@ -81,7 +81,7 @@ public void Count()
[TestMethod]
public void Clear()
{
var bucket = new KBucket();
var bucket = new KBucket<Contact>();
Assert.AreEqual(0, bucket.Count);

bucket.Add(new Contact("a"));
Expand All @@ -96,7 +96,7 @@ public void Clear()
[TestMethod]
public void Remove()
{
var bucket = new KBucket();
var bucket = new KBucket<Contact>();
Assert.AreEqual(0, bucket.Count);

bucket.Add(new Contact("a"));
Expand All @@ -115,7 +115,7 @@ public void Remove()
[TestMethod]
public void CopyTo()
{
var bucket = new KBucket();
var bucket = new KBucket<Contact>();
Assert.AreEqual(0, bucket.Count);

bucket.Add(new Contact("a"));
Expand All @@ -135,7 +135,7 @@ public void CopyTo()
[TestMethod]
public void Enumerate()
{
var bucket = new KBucket();
var bucket = new KBucket<Contact>();
var nContacts = 4000;
for (var i = 0; i < nContacts; ++i)
{
Expand All @@ -154,13 +154,13 @@ public void Enumerate()
[TestMethod]
public void CanBeModified()
{
Assert.IsFalse(new KBucket().IsReadOnly);
Assert.IsFalse(new KBucket<Contact>().IsReadOnly);
}

[TestMethod]
public async Task ThreadSafe()
{
var bucket = new KBucket();
var bucket = new KBucket<Contact>();
var nContacts = 1000;
var nTasks = 100;
var tasks = new Task[nTasks];
Expand All @@ -175,7 +175,7 @@ public async Task ThreadSafe()
Assert.AreEqual(nTasks * nContacts, bucket.Count);
}

public void AddTask(KBucket bucket, int start, int count)
public void AddTask(KBucket<Contact> bucket, int start, int count)
{
for (var i = 0; i < count; ++i)
{
Expand Down
2 changes: 1 addition & 1 deletion test/DistanceTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class DistanceTest
[TestMethod]
public void Tristanls()
{
var bucket = new KBucket();
var bucket = new KBucket<Contact>();

Assert.AreEqual(0, bucket.Distance(new byte[] { 0x00 }, new byte[] { 0x00 }));
Assert.AreEqual(1, bucket.Distance(new byte[] { 0x00 }, new byte[] { 0x01 }));
Expand Down

0 comments on commit 35aa222

Please sign in to comment.