Skip to content

Commit

Permalink
feat: cleaned up Needle structure, made more performant
Browse files Browse the repository at this point in the history
  • Loading branch information
n2p5 committed Oct 17, 2024
1 parent 97498d8 commit cdb128b
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 68 deletions.
6 changes: 0 additions & 6 deletions errors/errors.go

This file was deleted.

12 changes: 0 additions & 12 deletions errors/errors_test.go

This file was deleted.

6 changes: 3 additions & 3 deletions haystack.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ package haystack

import (
"bufio"
"errors"
"net"

"github.com/nomasters/haystack/errors"
"github.com/nomasters/haystack/needle"
)

Expand All @@ -13,9 +13,9 @@ import (
// - logger primitives, this most likely needs to carry over from server implementation
// - a way to process responses in a scalable way. This might be best to keep a local storage buffer keyed by the hash. more thought needs to go into this.

const (
var (
// ErrTimestampExceedsThreshold is an error returned with the timestamp exceeds the acceptable threshold
ErrTimestampExceedsThreshold = errors.Error("Timestamp exceeds threshold")
ErrTimestampExceedsThreshold = errors.New("Timestamp exceeds threshold")
)

type options struct {
Expand Down
78 changes: 41 additions & 37 deletions needle/needle.go
Original file line number Diff line number Diff line change
@@ -1,37 +1,43 @@
package needle

import (
"crypto/subtle"
"bytes"
"errors"

"github.com/nomasters/haystack/errors"
"lukechampine.com/blake3"
)

// Hash represents an array of length HashLength
type Hash [32]byte

// Payload represents an array of length PayloadLength
type Payload [160]byte

const (
// HashLength is the length in bytes of the hash prefix in any message
HashLength = len(Hash{})
HashLength = 32
// PayloadLength is the length of the remaining bytes of the message.
PayloadLength = len(Payload{})
PayloadLength = 160
// NeedleLength is the number of bytes required for a valid needle.
NeedleLength = HashLength + PayloadLength
)

// Hash represents an array of length HashLength
type Hash [HashLength]byte

// Payload represents an array of length PayloadLength
type Payload [PayloadLength]byte

// Needle is a container for a 160 byte payload
// and a 32 byte blake3 hash of the payload.
type Needle struct {
hash Hash
payload Payload
}

var (
// ErrorDNE is returned when a key/value par does not exist
ErrorDNE = errors.Error("Does Not Exist")
ErrorDNE = errors.New("Does Not Exist")
// ErrorInvalidHash is an error for in invalid hash
ErrorInvalidHash = errors.Error("invalid blake3-256 hash")
ErrorInvalidHash = errors.New("invalid blake3-256 hash")
// ErrorByteSliceLength is an error for an invalid byte slice length passed in to New or FromBytes
ErrorByteSliceLength = errors.Error("invalid byte slice length")
ErrorByteSliceLength = errors.New("invalid byte slice length")
)

// Needle is an immutable container for a [192]byte array that containers a 160 byte payload
// and a 32 byte blake3 hash of the payload.
type Needle struct{ internal [NeedleLength]byte }

// New creates a Needle used for submitting a payload to a Haystack sever. It takes a Payload
// byte slice that is 160 bytes in length and returns a reference to a
// Needle and an error. The purpose of this function is to make it
Expand All @@ -41,11 +47,10 @@ func New(payload []byte) (*Needle, error) {
if len(payload) != PayloadLength {
return nil, ErrorByteSliceLength
}
var n Needle
h := blake3.Sum256(payload)
copy(n.internal[:HashLength], h[:])
copy(n.internal[HashLength:], payload)
return &n, nil
return &Needle{
hash: Hash(blake3.Sum256(payload)),
payload: Payload(payload),
}, nil
}

// FromBytes is intended convert raw bytes (from UDP or storage) into a Needle.
Expand All @@ -58,39 +63,38 @@ func FromBytes(b []byte) (*Needle, error) {
if len(b) != NeedleLength {
return nil, ErrorByteSliceLength
}
var n Needle
copy(n.internal[:], b)
n := Needle{
hash: Hash(b[:HashLength]),
payload: Payload(b[HashLength:]),
}
if err := n.validate(); err != nil {
return nil, err
}
return &n, nil
}

// Hash returns a copy of the bytes of the blake3 256 hash of the Needle payload.
func (n Needle) Hash() Hash {
var h Hash
copy(h[:], n.internal[:HashLength])
return h
func (n *Needle) Hash() Hash {
return n.hash
}

// Payload returns a byte slice of the Needle payload
func (n Needle) Payload() Payload {
var p Payload
copy(p[:], n.internal[HashLength:])
return p
func (n *Needle) Payload() Payload {
return n.payload
}

// Bytes returns a byte slice of the entire 192 byte hash + payload
func (n Needle) Bytes() []byte {
return n.internal[:]
func (n *Needle) Bytes() []byte {
b := make([]byte, NeedleLength)
copy(b, n.hash[:])
copy(b[HashLength:], n.payload[:])
return b
}

// validate checks that a Needle has a valid hash and that it meets the entropy
// threshold, it returns either nil or an error.
func (n *Needle) validate() error {
p := n.Payload()
h := n.Hash()
if hash := blake3.Sum256(p[:]); subtle.ConstantTimeCompare(h[:], hash[:]) == 0 {
if hash := blake3.Sum256(n.payload[:]); !bytes.Equal(n.hash[:], hash[:]) {
return ErrorInvalidHash
}
return nil
Expand Down
43 changes: 43 additions & 0 deletions needle/needle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ func TestNeedle(t *testing.T) {
if !bytes.Equal(p, payload[:]) {
t.Error("payload imported by New does not match needle.Payload()")
}
payload[0] = 0
pl := n.Payload()
if bytes.Equal(pl[:], payload[:]) {
t.Error("mutating Payload() changed needle payload")
}
})
t.Run("Hash", func(t *testing.T) {
t.Parallel()
Expand All @@ -38,6 +43,11 @@ func TestNeedle(t *testing.T) {
if !bytes.Equal(h[:], hash[:]) {
t.Error("exported hash is invalid")
}
hash[0] = 0
h2 := n.Hash()
if bytes.Equal(h2[:], hash[:]) {
t.Error("mutating Hash() changed needle hash")
}
})
}

Expand Down Expand Up @@ -161,3 +171,36 @@ func BenchmarkValidate(b *testing.B) {
n1.validate()
}
}

func BenchmarkBytes(b *testing.B) {
p, _ := hex.DecodeString("40e4350b03d8b0c9e340321210b259d9a20b19632929b4a219254a4269c11f820c75168c6a91d309f4b134a7d715a5ac408991e1cf9415995053cf8a4e185dae22a06617ac51ebf7d232bc49e567f90be4db815c2b88ca0d9a4ef7a5119c0e592c88dfb96706e6510fb8a657c0f70f6695ea310d24786e6d980e9b33cf2665342b965b2391f6bb982c4c5f6058b9cba58038d32452e07cdee9420a8bd7f514e1")
n1, _ := New(p)
for n := 0; n < b.N; n++ {
n1.Bytes()
}
}

func BenchmarkHash(b *testing.B) {
p, _ := hex.DecodeString("40e4350b03d8b0c9e340321210b259d9a20b19632929b4a219254a4269c11f820c75168c6a91d309f4b134a7d715a5ac408991e1cf9415995053cf8a4e185dae22a06617ac51ebf7d232bc49e567f90be4db815c2b88ca0d9a4ef7a5119c0e592c88dfb96706e6510fb8a657c0f70f6695ea310d24786e6d980e9b33cf2665342b965b2391f6bb982c4c5f6058b9cba58038d32452e07cdee9420a8bd7f514e1")
n1, _ := New(p)
for n := 0; n < b.N; n++ {
n1.Hash()
}
}

func BenchmarkFromBytes(b *testing.B) {
validRaw, _ := hex.DecodeString("e431c3b024c54b8a8f03a1da5f81678300b3bf5d13fd3fb4969a6bfb85cdf1ae40e4350b03d8b0c9e340321210b259d9a20b19632929b4a219254a4269c11f820c75168c6a91d309f4b134a7d715a5ac408991e1cf9415995053cf8a4e185dae22a06617ac51ebf7d232bc49e567f90be4db815c2b88ca0d9a4ef7a5119c0e592c88dfb96706e6510fb8a657c0f70f6695ea310d24786e6d980e9b33cf2665342b965b2391f6bb982c4c5f6058b9cba58038d32452e07cdee9420a8bd7f514e1")
for n := 0; n < b.N; n++ {
FromBytes(validRaw)
}
}

func BenchmarkFullFlow(b *testing.B) {
p, _ := hex.DecodeString("40e4350b03d8b0c9e340321210b259d9a20b19632929b4a219254a4269c11f820c75168c6a91d309f4b134a7d715a5ac408991e1cf9415995053cf8a4e185dae22a06617ac51ebf7d232bc49e567f90be4db815c2b88ca0d9a4ef7a5119c0e592c88dfb96706e6510fb8a657c0f70f6695ea310d24786e6d980e9b33cf2665342b965b2391f6bb982c4c5f6058b9cba58038d32452e07cdee9420a8bd7f514e1")
for n := 0; n < b.N; n++ {
n1, _ := New(p)
n1.Bytes()
n1.Hash()
n1.Payload()
}
}
8 changes: 7 additions & 1 deletion storage/memory/memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,19 @@ package memory

import (
"context"
"errors"
"sync"
"time"

"github.com/nomasters/haystack/needle"
"github.com/nomasters/haystack/storage"
)

var (
// ErrorStoreFull is used when the Set method receives a nil pointer
ErrorStoreFull = errors.New("Store is full")
)

type value struct {
payload needle.Payload
expiration time.Time
Expand Down Expand Up @@ -38,7 +44,7 @@ func (s *Store) Set(n *needle.Needle) error {
s.Lock()
if len(s.internal) > s.maxItems {
s.Unlock()
return storage.ErrorStoreFull
return ErrorStoreFull
}
hash := n.Hash()
expiration := time.Now().Add(s.ttl)
Expand Down
11 changes: 3 additions & 8 deletions storage/storage.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
package storage

import (
"time"
"errors"

"github.com/nomasters/haystack/errors"
"github.com/nomasters/haystack/needle"
)

const (
// DefaultTTL of 1 day
DefaultTTL = 24 * time.Hour
var (
// ErrorNeedleIsNil is used when the Set method receives a nil pointer
ErrorNeedleIsNil = errors.Error("Cannot Set a nil *Needle")
// ErrorStoreFull is used when the Set method receives a nil pointer
ErrorStoreFull = errors.Error("Store is full")
ErrorNeedleIsNil = errors.New("Cannot Set a nil *Needle")
)

// Getter takes a needle.Hash and returns a reference to needle.Needle and an error.
Expand Down
2 changes: 1 addition & 1 deletion server/server.go → x/udp/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ func ListenAndServe(address string, opts ...Option) error {
address: address,
protocol: defaultProtocol,
workers: uint64(runtime.NumCPU()),
storage: memory.New(ctx, storage.DefaultTTL, 2000000),
storage: memory.New(ctx, 24*time.Hour, 2000000),
ctx: context.Background(),
gracePeriod: defaultGracePeriod,
logger: logger.New(),
Expand Down

0 comments on commit cdb128b

Please sign in to comment.