Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kad Import #2

Merged
merged 3 commits into from
Sep 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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