Skip to content

Commit

Permalink
Merge pull request #2 from plprobelab/kad-import
Browse files Browse the repository at this point in the history
Kad Import
  • Loading branch information
guillaumemichel authored Sep 28, 2023
2 parents 9facd68 + 02aa909 commit 796722c
Show file tree
Hide file tree
Showing 25 changed files with 2,951 additions and 0 deletions.
11 changes: 11 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module github.com/plprobelab/go-libdht

go 1.20

require github.com/stretchr/testify v1.8.4

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
2 changes: 2 additions & 0 deletions kad/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Package kad provides interfaces defining core Kademlia types
package kad
93 changes: 93 additions & 0 deletions kad/kad.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package kad

// Key is the interface all Kademlia key types support.
//
// A Kademlia key is defined as a bit string of arbitrary size. In practice, different Kademlia implementations use
// different key sizes. For instance, the Kademlia paper (https://pdos.csail.mit.edu/~petar/papers/maymounkov-kademlia-lncs.pdf)
// defines keys as 160-bits long and IPFS uses 256-bit keys.
//
// Keys are usually generated using cryptographic hash functions, however the specifics of key generation
// do not matter for key operations.
//
// A Key is not necessarily used to identify a node in the network but a derived
// representation. Implementations may choose to hash a logical node identifier
// to derive a Kademlia Key. Therefore, there also exists the concept of a NodeID
// which just defines a method to return the associated Kademlia Key.
type Key[K any] interface {
// BitLen returns the length of the key in bits.
BitLen() int

// Bit returns the value of the i'th bit of the key from most significant to least. It is equivalent to (key>>(bitlen-i-1))&1.
// Bit will panic if i is out of the range [0,BitLen()-1].
Bit(i int) uint

// Xor returns the result of the eXclusive OR operation between the key and another key of the same type.
Xor(other K) K

// CommonPrefixLength returns the number of leading bits the key shares with another key of the same type.
// The CommonPrefixLength of a key with itself is equal to BitLen.
CommonPrefixLength(other K) int

// Compare compares the numeric value of the key with another key of the same type.
// It returns -1 if the key is numerically less than other, +1 if it is greater
// and 0 if both keys are equal.
Compare(other K) int
}

// RoutingTable is the interface all Kademlia Routing Tables types support.
type RoutingTable[K Key[K], N NodeID[K]] interface {
// AddNode tries to add a peer to the routing table. It returns true if
// the node was added and false if it wasn't added, e.g., because it
// was already part of the routing table.
//
// Because NodeID[K]'s are often preimages to Kademlia keys K
// there's no way to derive a NodeID[K] from just K. Therefore, to be
// able to return NodeID[K]'s from the `NearestNodes` method, this
// `AddNode` method signature takes a NodeID[K] instead of only K.
//
// Nodes added to the routing table are grouped into buckets based on their
// XOR distance to the local node's identifier. The details of the XOR
// arithmetics are defined on K.
AddNode(N) bool

// RemoveKey tries to remove a node identified by its Kademlia key from the
// routing table.
//
// It returns true if the key existed in the routing table and was removed.
// It returns false if the key didn't exist in the routing table and
// therefore, was not removed.
RemoveKey(K) bool

// NearestNodes returns the given number of closest nodes to a given
// Kademlia key that are currently present in the routing table.
// The returned list of nodes will be ordered from closest to furthest and
// contain at maximum the given number of entries, but also possibly less
// if the number exceeds the number of nodes in the routing table.
NearestNodes(K, int) []N

// GetNode returns the node identified by the supplied Kademlia key or a zero
// value if the node is not present in the routing table. The boolean second
// return value indicates whether the node was found in the table.
GetNode(K) (N, bool)
}

// NodeID is a generic node identifier and not equal to a Kademlia key. Some
// implementations use NodeID's as preimages for Kademlia keys. Kademlia keys
// are used for calculating distances between nodes while NodeID's are the
// original logical identifier of a node.
//
// The NodeID interface only defines a method that returns the Kademlia key
// for the given NodeID. E.g., the operation to go from a NodeID to a Kademlia key
// can be as simple as hashing the NodeID.
//
// Implementations may choose to equate NodeID's and Kademlia keys.
type NodeID[K Key[K]] interface {
// Key returns the Kademlia key of the given NodeID. E.g., NodeID's can be
// preimages to Kademlia keys, in which case, Key() could return the SHA256
// of NodeID.
Key() K

// String returns a string reprensentation for this NodeID.
// TODO: Try to get rid of this as it's also used for map keys which is not great.
String() string
}
11 changes: 11 additions & 0 deletions kad/kadtest/bench.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//go:build go1.20

package kadtest

import "testing"

// ReportTimePerItemMetric adds a custom metric to a benchmark that reports the number of nanoseconds taken per item.
func ReportTimePerItemMetric(b *testing.B, n int, name string) {
// b.Elapsed was added in Go 1.20
b.ReportMetric(float64(b.Elapsed().Nanoseconds())/float64(n), "ns/"+name)
}
24 changes: 24 additions & 0 deletions kad/kadtest/context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package kadtest

import (
"context"
"testing"
"time"
)

// Ctx returns a Context and a CancelFunc. The context will be
// cancelled just before the test binary deadline (as
// specified by the -timeout flag when running the test). The
// CancelFunc may be called to cancel the context earlier than
// the deadline.
func Ctx(t *testing.T) (context.Context, context.CancelFunc) {
t.Helper()

deadline, ok := t.Deadline()
if !ok {
deadline = time.Now().Add(time.Minute)
} else {
deadline = deadline.Add(-time.Second)
}
return context.WithDeadline(context.Background(), deadline)
}
67 changes: 67 additions & 0 deletions kad/kadtest/ids.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package kadtest

import (
"crypto/sha256"

"github.com/plprobelab/go-libdht/kad"
"github.com/plprobelab/go-libdht/kad/key"
"github.com/plprobelab/go-libdht/kad/key/bit256"
)

// ID is a concrete implementation of the NodeID interface.
type ID[K kad.Key[K]] struct {
key K
}

// interface assertion. Using the concrete key type of key.Key8 does not
// limit the validity of the assertion for other key types.
var _ kad.NodeID[Key8] = (*ID[Key8])(nil)

// NewID returns a new Kademlia identifier that implements the NodeID interface.
// Instead of deriving the Kademlia key from a NodeID, this method directly takes
// the Kademlia key.
func NewID[K kad.Key[K]](k K) *ID[K] {
return &ID[K]{key: k}
}

// Key returns the Kademlia key that is used by, e.g., the routing table
// implementation to group nodes into buckets. The returned key was manually
// defined in the ID constructor NewID and not derived via, e.g., hashing
// a preimage.
func (i ID[K]) Key() K {
return i.key
}

func (i ID[K]) Equal(other K) bool {
return i.key.Compare(other) == 0
}

func (i ID[K]) String() string {
return key.HexString(i.key)
}

type StringID string

var _ kad.NodeID[bit256.Key] = (*StringID)(nil)

func NewStringID(s string) *StringID {
return (*StringID)(&s)
}

func (s StringID) Key() bit256.Key {
h := sha256.New()
h.Write([]byte(s))
return bit256.NewKey(h.Sum(nil))
}

func (s StringID) NodeID() kad.NodeID[bit256.Key] {
return &s
}

func (s StringID) Equal(other string) bool {
return string(s) == other
}

func (s StringID) String() string {
return string(s)
}
131 changes: 131 additions & 0 deletions kad/kadtest/key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package kadtest

import (
"fmt"

"github.com/plprobelab/go-libdht/kad"
)

const bitPanicMsg = "bit index out of range"

// Key32 is a 32-bit Kademlia key, suitable for testing and simulation of small networks.
type Key32 uint32

var _ kad.Key[Key32] = Key32(0)

// BitLen returns the length of the key in bits, which is always 32.
func (Key32) BitLen() int {
return 32
}

// Bit returns the value of the i'th bit of the key from most significant to least.
func (k Key32) Bit(i int) uint {
if i < 0 || i > 31 {
panic(bitPanicMsg)
}
return uint((k >> (31 - i)) & 1)
}

// Xor returns the result of the eXclusive OR operation between the key and another key of the same type.
func (k Key32) Xor(o Key32) Key32 {
return k ^ o
}

// CommonPrefixLength returns the number of leading bits the key shares with another key of the same type.
func (k Key32) CommonPrefixLength(o Key32) int {
a := uint32(k)
b := uint32(o)
for i := 32; i > 0; i-- {
if a == b {
return i
}
a >>= 1
b >>= 1
}
return 0
}

// Compare compares the numeric value of the key with another key of the same type.
func (k Key32) Compare(o Key32) int {
if k < o {
return -1
} else if k > o {
return 1
}
return 0
}

// HexString returns a string containing the hexadecimal representation of the key.
func (k Key32) HexString() string {
return fmt.Sprintf("%04x", uint32(k))
}

// BitString returns a string containing the binary representation of the key.
func (k Key32) BitString() string {
return fmt.Sprintf("%032b", uint32(k))
}

func (k Key32) String() string {
return k.HexString()
}

// Key8 is an 8-bit Kademlia key, suitable for testing and simulation of very small networks.
type Key8 uint8

var _ kad.Key[Key8] = Key8(0)

// BitLen returns the length of the key in bits, which is always 8.
func (Key8) BitLen() int {
return 8
}

// Bit returns the value of the i'th bit of the key from most significant to least.
func (k Key8) Bit(i int) uint {
if i < 0 || i > 7 {
panic(bitPanicMsg)
}
return uint((k >> (7 - i)) & 1)
}

// Xor returns the result of the eXclusive OR operation between the key and another key of the same type.
func (k Key8) Xor(o Key8) Key8 {
return k ^ o
}

// CommonPrefixLength returns the number of leading bits the key shares with another key of the same type.
func (k Key8) CommonPrefixLength(o Key8) int {
a := uint8(k)
b := uint8(o)
for i := 8; i > 0; i-- {
if a == b {
return i
}
a >>= 1
b >>= 1
}
return 0
}

// Compare compares the numeric value of the key with another key of the same type.
func (k Key8) Compare(o Key8) int {
if k < o {
return -1
} else if k > o {
return 1
}
return 0
}

// HexString returns a string containing the hexadecimal representation of the key.
func (k Key8) HexString() string {
return fmt.Sprintf("%x", uint8(k))
}

func (k Key8) String() string {
return k.HexString()
}

// HexString returns a string containing the binary representation of the key.
func (k Key8) BitString() string {
return fmt.Sprintf("%08b", uint8(k))
}
35 changes: 35 additions & 0 deletions kad/kadtest/key_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package kadtest

import (
"testing"

"github.com/plprobelab/go-libdht/kad/key/test"
)

func TestKey32(t *testing.T) {
tester := &test.KeyTester[Key32]{
Key0: Key32(0),
Key1: Key32(1),
Key2: Key32(2),
Key1xor2: Key32(3),
Key100: Key32(0x80000000),
Key010: Key32(0x40000000),
KeyX: Key32(0x23e4dd03),
}

tester.RunTests(t)
}

func TestKey8(t *testing.T) {
tester := &test.KeyTester[Key8]{
Key0: Key8(0),
Key1: Key8(1),
Key2: Key8(2),
Key1xor2: Key8(3),
Key100: Key8(0x80),
Key010: Key8(0x40),
KeyX: Key8(0x23),
}

tester.RunTests(t)
}
Loading

0 comments on commit 796722c

Please sign in to comment.