noise

package module
v1.1.4-0...-860c0a0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 4, 2021 License: MIT Imports: 29 Imported by: 0

README

noise

GoDoc Discord MIT licensed Build Status Go Report Card Coverage Status

noise is an opinionated, easy-to-use P2P network stack for decentralized applications, and cryptographic protocols written in Go.

noise is made to be minimal, robust, developer-friendly, performant, secure, and cross-platform across multitudes of devices by making use of a small amount of well-tested, production-grade dependencies.

Features

  • Listen for incoming peers, query peers, and ping peers.
  • Request for/respond to messages, fire-and-forget messages, and optionally automatically serialize/deserialize messages across peers.
  • Optionally cancel/timeout pinging peers, sending messages to peers, receiving messages from peers, or requesting messages from peers via context support.
  • Fine-grained control over a node and peers lifecycle and goroutines and resources (synchronously/asynchronously/gracefully start listening for new peers, stop listening for new peers, send messages to a peer, disconnect an existing peer, wait for a peer to be ready, wait for a peer to have disconnected).
  • Limit resource consumption by pooling connections and specifying the max number of inbound/outbound connections allowed at any given time.
  • Reclaim resources exhaustively by timing out idle peers with a configurable timeout.
  • Establish a shared secret by performing an Elliptic-Curve Diffie-Hellman Handshake over Curve25519.
  • Establish an encrypted session amongst a pair of peers via authenticated-encryption-with-associated-data (AEAD). Built-in support for AES 256-bit Galois Counter Mode (GCM).
  • Peer-to-peer routing, discovery, identities, and handshake protocol via Kademlia overlay network protocol.

Defaults

  • No logs are printed by default. Set a logger via noise.WithNodeLogger(*zap.Logger).
  • A random Ed25519 key pair is generated for a new node.
  • Peers attempt to be dialed at most three times.
  • A total of 128 outbound connections are allowed at any time.
  • A total of 128 inbound connections are allowed at any time.
  • Peers may send in a single message, at most, 4MB worth of data.
  • Connections timeout after 10 seconds if no reads/writes occur.

Dependencies

Setup

noise was intended to be used in Go projects that utilize Go modules. You may incorporate noise into your project as a library dependency by executing the following:

% go get -u github.com/fermuch/noise

Example

package main

import (
    "context"
    "fmt"
    "github.com/fermuch/noise"
)

func check(err error) {
    if err != nil {
        panic(err)
    }
}

// This example demonstrates how to send/handle RPC requests across peers, how to listen for incoming
// peers, how to check if a message received is a request or not, how to reply to a RPC request, and
// how to cleanup node instances after you are done using them.
func main() { 
    // Let there be nodes Alice and Bob.

    alice, err := noise.NewNode()
    check(err)

    bob, err := noise.NewNode()
    check(err)

    // Gracefully release resources for Alice and Bob at the end of the example.

    defer alice.Close()
    defer bob.Close()

    // When Bob gets a message from Alice, print it out and respond to Alice with 'Hi Alice!'

    bob.Handle(func(ctx noise.HandlerContext) error {
        if !ctx.IsRequest() {
            return nil
        }

        fmt.Printf("Got a message from Alice: '%s'\n", string(ctx.Data()))

        return ctx.Send([]byte("Hi Alice!"))
    })

    // Have Alice and Bob start listening for new peers.

    check(alice.Listen())
    check(bob.Listen())

    // Have Alice send Bob a request with the message 'Hi Bob!'

    res, err := alice.Request(context.TODO(), bob.Addr(), []byte("Hi Bob!"))
    check(err)

    // Print out the response Bob got from Alice.

    fmt.Printf("Got a message from Bob: '%s'\n", string(res))

    // Output:
    // Got a message from Alice: 'Hi Bob!'
    // Got a message from Bob: 'Hi Alice!'
}

For documentation and more examples, refer to noise's godoc here.

Benchmarks

Benchmarks measure CPU time and allocations of a single node sending messages, requests, and responses to/from itself over 8 logical cores on a loopback adapter.

Take these benchmark numbers with a grain of salt.

% cat /proc/cpuinfo | grep 'model name' | uniq
model name : Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz

% go test -bench=. -benchtime=30s -benchmem
goos: linux
goarch: amd64
pkg: github.com/fermuch/noise
BenchmarkRPC-8           4074007              9967 ns/op             272 B/op          7 allocs/op
BenchmarkSend-8         31161464              1051 ns/op              13 B/op          2 allocs/op
PASS
ok      github.com/fermuch/noise 84.481s

Versioning

noise is currently in its initial development phase and therefore does not promise that subsequent releases will not comprise of breaking changes. Be aware of this should you choose to utilize Noise for projects that are in production.

Releases are marked with a version number formatted as MAJOR.MINOR.PATCH. Major breaking changes involve a bump in MAJOR, minor backward-compatible changes involve a bump in MINOR, and patches and bug fixes involve a bump in PATCH starting from v2.0.0.

Therefore, noise mostly respects semantic versioning.

The rationale behind this is due to improper tagging of prior releases (v0.1.0, v1.0.0, v1.1.0, and v1.1.1), which has caused for the improper caching of module information on proxy.golang.org and sum.golang.org.

As a result, noise's initial development phase starts from v1.1.2. Until Noise's API is stable, subsequent releases will only comprise of bumps in MINOR and PATCH.

License

noise, and all of its source code is released under the MIT License.

Documentation

Overview

Package noise is an opinionated, easy-to-use P2P network stack for decentralized applications, and cryptographic protocols written in Go.

noise is made to be minimal, robust, developer-friendly, performant, secure, and cross-platform across multitudes of devices by making use of a small amount of well-tested, production-grade dependencies.

Example (CodecMessaging)

This example demonstrates messaging with registering Go types to be serialized/deserialized on-the-wire provided marshal/unmarshal functions, how to decode serialized messages received from a peer, and how to send serialized messages.

package main

import (
	"context"
	"fmt"
	"strings"
	"sync"

	"github.com/fermuch/noise"
)

// ChatMessage is an example struct that is registered on example nodes, and serialized/deserialized on-the-fly.
type ChatMessage struct {
	content string
}

// Marshal serializes a chat message into bytes.
func (m ChatMessage) Marshal() []byte {
	return []byte(m.content)
}

// Unmarshal deserializes a slice of bytes into a chat message, and returns an error should deserialization
// fail, or the slice of bytes be malformed.
func UnmarshalChatMessage(buf []byte) (ChatMessage, error) {
	return ChatMessage{content: strings.ToValidUTF8(string(buf), "")}, nil
}

// This example demonstrates messaging with registering Go types to be serialized/deserialized on-the-wire provided
// marshal/unmarshal functions, how to decode serialized messages received from a peer, and how to send serialized
// messages.
func main() {
	// Let there be Alice and Bob.

	alice, err := noise.NewNode()
	if err != nil {
		panic(err)
	}

	bob, err := noise.NewNode()
	if err != nil {
		panic(err)
	}

	// Gracefully release resources for Alice and Bob at the end of the example.

	defer alice.Close()
	defer bob.Close()

	// Register the ChatMessage type to Alice and Bob so they know how to serialize/deserialize
	// them.

	alice.RegisterMessage(ChatMessage{}, UnmarshalChatMessage)
	bob.RegisterMessage(ChatMessage{}, UnmarshalChatMessage)

	var wg sync.WaitGroup

	// When Alice gets a ChatMessage from Bob, print it out.

	alice.Handle(func(ctx noise.HandlerContext) error {
		obj, err := ctx.DecodeMessage()
		if err != nil {
			return nil
		}

		msg, ok := obj.(ChatMessage)
		if !ok {
			return nil
		}

		fmt.Printf("Got a message from Bob: '%s'\n", msg.content)

		wg.Done()

		return nil
	})

	// When Bob gets a message from Alice, print it out.

	bob.Handle(func(ctx noise.HandlerContext) error {
		obj, err := ctx.DecodeMessage()
		if err != nil {
			return nil
		}

		msg, ok := obj.(ChatMessage)
		if !ok {
			return nil
		}

		fmt.Printf("Got a message from Alice: '%s'\n", msg.content)

		wg.Done()

		return nil
	})

	// Have Alice and Bob start listening for new peers.

	if err := alice.Listen(); err != nil {
		panic(err)
	}

	if err := bob.Listen(); err != nil {
		panic(err)
	}

	// Have Alice send Bob a ChatMessage with 'Hi Bob!'

	if err := alice.SendMessage(context.TODO(), bob.Addr(), ChatMessage{content: "Hi Bob!"}); err != nil {
		panic(err)
	}

	// Wait until Bob receives the message from Alice.

	wg.Add(1)
	wg.Wait()

	// Have Bob send Alice a ChatMessage with 'Hi Alice!'

	if err := bob.SendMessage(context.TODO(), alice.Addr(), ChatMessage{content: "Hi Alice!"}); err != nil {
		panic(err)
	}

	// Wait until Alice receives the message from Bob.

	wg.Add(1)
	wg.Wait()

}
Output:

Got a message from Alice: 'Hi Bob!'
Got a message from Bob: 'Hi Alice!'
Example (CodecRPC)

This example demonstrates sending registered serialized Go types as requests, decoding registered serialized Go types from peers, and sending registered serialized Go types as responses.

// Let there be Alice and Bob.

alice, err := noise.NewNode()
if err != nil {
	panic(err)
}

bob, err := noise.NewNode()
if err != nil {
	panic(err)
}

// Gracefully release resources for Alice and Bob at the end of the example.

defer alice.Close()
defer bob.Close()

// Register the ChatMessage type to Alice and Bob so they know how to serialize/deserialize
// them.

alice.RegisterMessage(ChatMessage{}, UnmarshalChatMessage)
bob.RegisterMessage(ChatMessage{}, UnmarshalChatMessage)

// When Bob gets a request from Alice, print it out and respond to Alice with 'Hi Alice!'.

bob.Handle(func(ctx noise.HandlerContext) error {
	if !ctx.IsRequest() {
		return nil
	}

	req, err := ctx.DecodeMessage()
	if err != nil {
		return nil
	}

	fmt.Printf("Got a message from Alice: '%s'\n", req.(ChatMessage).content)

	return ctx.SendMessage(ChatMessage{content: "Hi Alice!"})
})

// Have Alice and Bob start listening for new peers.

if err := alice.Listen(); err != nil {
	panic(err)
}

if err := bob.Listen(); err != nil {
	panic(err)
}

// Have Alice send Bob a ChatMessage request with the message 'Hi Bob!'

res, err := alice.RequestMessage(context.TODO(), bob.Addr(), ChatMessage{content: "Hi Bob!"})
if err != nil {
	panic(err)
}

// Print out the ChatMessage response Bob got from Alice.

fmt.Printf("Got a message from Bob: '%s'\n", res.(ChatMessage).content)
Output:

Got a message from Alice: 'Hi Bob!'
Got a message from Bob: 'Hi Alice!'
Example (Messaging)

This example demonstrates how to send and receive raw bytes across peers, how to listen for incoming peers, and how to cleanup node instances after you are done using them.

package main

import (
	"context"
	"fmt"
	"sync"

	"github.com/fermuch/noise"
)

func main() {
	// Let there be nodes Alice and Bob.

	alice, err := noise.NewNode()
	if err != nil {
		panic(err)
	}

	bob, err := noise.NewNode()
	if err != nil {
		panic(err)
	}

	// Gracefully release resources for Alice and Bob at the end of the example.

	defer alice.Close()
	defer bob.Close()

	var wg sync.WaitGroup

	// When Alice gets a message from Bob, print it out.

	alice.Handle(func(ctx noise.HandlerContext) error {
		fmt.Printf("Got a message from Bob: '%s'\n", string(ctx.Data()))
		wg.Done()
		return nil
	})

	// When Bob gets a message from Alice, print it out.

	bob.Handle(func(ctx noise.HandlerContext) error {
		fmt.Printf("Got a message from Alice: '%s'\n", string(ctx.Data()))
		wg.Done()
		return nil
	})

	// Have Alice and Bob start listening for new peers.

	if err := alice.Listen(); err != nil {
		panic(err)
	}

	if err := bob.Listen(); err != nil {
		panic(err)
	}

	// Have Alice send Bob 'Hi Bob!'

	if err := alice.Send(context.TODO(), bob.Addr(), []byte("Hi Bob!")); err != nil {
		panic(err)
	}

	// Wait until Bob receives the message from Alice.

	wg.Add(1)
	wg.Wait()

	// Have Bob send Alice 'Hi Alice!'

	if err := bob.Send(context.TODO(), alice.Addr(), []byte("Hi Alice!")); err != nil {
		panic(err)
	}

	// Wait until Alice receives the message from Bob.

	wg.Add(1)
	wg.Wait()

}
Output:

Got a message from Alice: 'Hi Bob!'
Got a message from Bob: 'Hi Alice!'
Example (PeerDiscovery)

This example demonstrates how to use Kademlia to have three peers Alice, Charlie and bob discover each other in an open, trustless network.

package main

import (
	"context"
	"fmt"

	"github.com/fermuch/noise"
	"github.com/fermuch/noise/kademlia"
)

func main() {
	// Let there be Alice, Bob, and Charlie.

	alice, err := noise.NewNode()
	if err != nil {
		panic(err)
	}

	bob, err := noise.NewNode()
	if err != nil {
		panic(err)
	}

	charlie, err := noise.NewNode()
	if err != nil {
		panic(err)
	}

	// Alice, Bob, and Charlie are following an overlay network protocol called Kademlia to discover, interact, and
	// manage each others peer connections.

	ka, kb, kc := kademlia.New(), kademlia.New(), kademlia.New()

	alice.Bind(ka.Protocol())
	bob.Bind(kb.Protocol())
	charlie.Bind(kc.Protocol())

	if err := alice.Listen(); err != nil {
		panic(err)
	}

	if err := bob.Listen(); err != nil {
		panic(err)
	}

	if err := charlie.Listen(); err != nil {
		panic(err)
	}

	// Have Bob and Charlie learn about Alice. Bob and Charlie do not yet know of each other.

	if _, err := bob.Ping(context.TODO(), alice.Addr()); err != nil {
		panic(err)
	}

	if _, err := charlie.Ping(context.TODO(), bob.Addr()); err != nil {
		panic(err)
	}

	// Using Kademlia, Bob and Charlie will learn of each other. Alice, Bob, and Charlie should
	// learn about each other once they run (*kademlia.Protocol).Discover().

	fmt.Printf("Alice discovered %d peer(s).\n", len(ka.Discover()))
	fmt.Printf("Bob discovered %d peer(s).\n", len(kb.Discover()))
	fmt.Printf("Charlie discovered %d peer(s).\n", len(kc.Discover()))

}
Output:

Alice discovered 2 peer(s).
Bob discovered 2 peer(s).
Charlie discovered 2 peer(s).
Example (RPC)

This example demonstrates how to send/handle RPC requests across peers, how to listen for incoming peers, how to check if a message received is a request or not, how to reply to a RPC request, and how to cleanup node instances after you are done using them.

package main

import (
	"context"
	"fmt"

	"github.com/fermuch/noise"
)

func main() {
	// Let there be nodes Alice and Bob.

	alice, err := noise.NewNode()
	if err != nil {
		panic(err)
	}

	bob, err := noise.NewNode()
	if err != nil {
		panic(err)
	}

	// Gracefully release resources for Alice and Bob at the end of the example.

	defer alice.Close()
	defer bob.Close()

	// When Bob gets a message from Alice, print it out and respond to Alice with 'Hi Alice!'

	bob.Handle(func(ctx noise.HandlerContext) error {
		if !ctx.IsRequest() {
			return nil
		}

		fmt.Printf("Got a message from Alice: '%s'\n", string(ctx.Data()))

		return ctx.Send([]byte("Hi Alice!"))
	})

	// Have Alice and Bob start listening for new peers.

	if err := alice.Listen(); err != nil {
		panic(err)
	}

	if err := bob.Listen(); err != nil {
		panic(err)
	}

	// Have Alice send Bob a request with the message 'Hi Bob!'

	res, err := alice.Request(context.TODO(), bob.Addr(), []byte("Hi Bob!"))
	if err != nil {
		panic(err)
	}

	// Print out the response Bob got from Alice.

	fmt.Printf("Got a message from Bob: '%s'\n", string(res))

}
Output:

Got a message from Alice: 'Hi Bob!'
Got a message from Bob: 'Hi Alice!'

Index

Examples

Constants

View Source
const (
	// SizePublicKey is the size in bytes of a nodes/peers public key.
	SizePublicKey = ed25519.PublicKeySize

	// SizePrivateKey is the size in bytes of a nodes/peers private key.
	SizePrivateKey = ed25519.PrivateKeySize

	// SizeSignature is the size in bytes of a cryptographic signature.
	SizeSignature = ed25519.SignatureSize
)

Variables

View Source
var (
	// ZeroPublicKey is the zero-value for a node/peer public key.
	ZeroPublicKey PublicKey

	// ZeroPrivateKey is the zero-value for a node/peer private key.
	ZeroPrivateKey PrivateKey

	// ZeroSignature is the zero-value for a cryptographic signature.
	ZeroSignature Signature
)
View Source
var (
	// ErrMessageTooLarge is reported by a client when it receives a message from a peer that exceeds the max
	// receivable message size limit configured on a node.
	ErrMessageTooLarge = errors.New("msg from peer is too large")
)

Functions

func ECDH

func ECDH(ourPrivateKey PrivateKey, peerPublicKey PublicKey) ([]byte, error)

ECDH transform all Ed25519 points to Curve25519 points and performs a Diffie-Hellman handshake to derive a shared key. It throws an error should the Ed25519 points be invalid.

func GenerateKeys

func GenerateKeys(rand io.Reader) (publicKey PublicKey, privateKey PrivateKey, err error)

GenerateKeys randomly generates a new pair of cryptographic keys. Nil may be passed to rand in order to use crypto/rand by default. It returns an error if rand is invalid.

func ResolveAddress

func ResolveAddress(address string) (string, error)

ResolveAddress resolves an address using net.ResolveTCPAddress("tcp", (*net.Conn).RemoteAddr()) and nullifies the IP if the IP is unspecified or is a loopback address. It then returns the string representation of the address, or an error if the resolution of the address fails.

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client represents an pooled inbound/outbound connection under some node. Should a client successfully undergo noise's protocol handshake, information about the peer representative of this client, such as its ID is available.

A clients connection may be closed through (*Client).Close, through the result of a failed handshake, through exceeding the max inbound/outbound connection count configured on the clients associated node, through a node gracefully being stopped, through a Handler configured on the node returning an error upon recipient of some data, or through receiving unexpected/suspicious data.

The lifecycle of a client may be controlled through (*Client).WaitUntilReady, and (*Client).WaitUntilClosed. It provably has been useful in writing unit tests where a client instance is used under high concurrency scenarios.

A client in total has two goroutines associated to it: a goroutine responsible for handling writing messages, and a goroutine responsible for handling the recipient of messages.

func (*Client) Close

func (c *Client) Close()

Close asynchronously kills the underlying connection and signals all goroutines to stop underlying this client.

Close may be called concurrently.

func (*Client) Error

func (c *Client) Error() error

Error returns the very first error that has caused this clients connection to have dropped.

Error may be called concurrently.

func (*Client) ID

func (c *Client) ID() ID

ID returns an immutable copy of the ID of this client, which is established once the client has successfully completed the handshake protocol configured from this clients associated node.

ID may be called concurrently.

func (*Client) Logger

func (c *Client) Logger() *zap.Logger

Logger returns the underlying logger associated to this client. It may optionally be set via (*Client).SetLogger.

Logger may be called concurrently.

func (*Client) SetLogger

func (c *Client) SetLogger(logger *zap.Logger)

SetLogger updates the logger instance of this client.

SetLogger may be called concurrently.

func (*Client) WaitUntilClosed

func (c *Client) WaitUntilClosed()

WaitUntilClosed pauses the goroutine to which it was called within until all goroutines associated to this client has been closed. The goroutines associated to this client would only close should:

1) handshaking failed/succeeded, 2) the connection was dropped, or 3) (*Client).Close was called.

WaitUntilReady may be called concurrently.

func (*Client) WaitUntilReady

func (c *Client) WaitUntilReady()

WaitUntilReady pauses the goroutine to which it was called within until/unless the client has successfully completed/failed the handshake protocol configured under the node instance to which this peer was derived from.

It pauses the goroutine by reading from a channel that is closed when the client has successfully completed/failed the aforementioned handshake protocol.

WaitUntilReady may be called concurrently.

type Handler

type Handler func(ctx HandlerContext) error

Handler is called whenever a node receives data from either an inbound/outbound peer connection. Multiple handlers may be registered to a node by (*Node).Handle before the node starts listening for new peers.

Returning an error in a handler closes the connection and marks the connection to have closed unexpectedly or due to error. Should you intend to wish to skip a handler from processing some given data, return a nil error.

type HandlerContext

type HandlerContext struct {
	// contains filtered or unexported fields
}

HandlerContext provides contextual information upon the recipient of data from an inbound/outbound connection. It provides the option of responding to a request should the data received be of a request.

func (*HandlerContext) Data

func (ctx *HandlerContext) Data() []byte

Data returns the raw bytes that some peer has sent to you.

Data may be called concurrently.

func (*HandlerContext) DecodeMessage

func (ctx *HandlerContext) DecodeMessage() (Serializable, error)

DecodeMessage decodes the raw bytes that some peer has sent you into a Go type. The Go type must have previously been registered to the node to which the handler this context is under was registered on. An error is thrown otherwise.

It is highly recommended that should you choose to have your application utilize noise's serialization/ deserialization framework for data over-the-wire, that all handlers use them by default.

DecodeMessage may be called concurrently.

func (*HandlerContext) ID

func (ctx *HandlerContext) ID() ID

ID returns the ID of the inbound/outbound peer that sent you the data that is currently being handled.

func (*HandlerContext) IsRequest

func (ctx *HandlerContext) IsRequest() bool

IsRequest marks whether or not the data received was intended to be of a request.

IsRequest may be called concurrently.

func (*HandlerContext) Logger

func (ctx *HandlerContext) Logger() *zap.Logger

Logger returns the logger instance associated to the inbound/outbound peer being handled.

func (*HandlerContext) Send

func (ctx *HandlerContext) Send(data []byte) error

Send sends data back to the peer that has sent you data. Should the data the peer send you be of a request, Send will send data back as a response. It returns an error if multiple responses attempt to be sent to a single request, or if an error occurred while attempting to send the peer a message.

Send may be called concurrently.

func (*HandlerContext) SendMessage

func (ctx *HandlerContext) SendMessage(msg Serializable) error

SendMessage encodes and serializes a Go type into a byte slice, and sends data back to the peer that has sent you data as either a response or message. Refer to (*HandlerContext).Send for more details. An error is thrown if the Go type passed in has not been registered to the node to which the handler this context is under was registered on.

It is highly recommended that should you choose to have your application utilize noise's serialization/deserialization framework for data over-the-wire, that all handlers use them by default.

SendMessage may be called concurrently.

type ID

type ID struct {
	// The Ed25519 public key of the bearer of this ID.
	ID PublicKey `json:"public_key"`

	// Public host of the bearer of this ID.
	Host net.IP `json:"address"`

	// Public port of the bearer of this ID.
	Port uint16

	// 'host:port'
	Address string
}

ID represents a peer ID. It comprises of a cryptographic public key, and a public, reachable network address specified by a IPv4/IPv6 host and 16-bit port number. The size of an ID in terms of its byte representation is static, with its contents being deterministic.

func NewID

func NewID(id PublicKey, host net.IP, port uint16) ID

NewID instantiates a new, immutable cryptographic user ID.

func UnmarshalID

func UnmarshalID(buf []byte) (ID, error)

UnmarshalID deserializes buf, representing a slice of bytes, ID instance. It throws io.ErrUnexpectedEOF if the contents of buf is malformed.

func (ID) Marshal

func (e ID) Marshal() []byte

Marshal serializes this ID into its byte representation.

func (ID) Size

func (e ID) Size() int

Size returns the number of bytes this ID comprises of.

func (ID) String

func (e ID) String() string

String returns a JSON representation of this ID.

type Node

type Node struct {
	// contains filtered or unexported fields
}

Node keeps track of a users ID, all of a users outgoing/incoming connections to/from peers as *Client instances under a bounded connection pool whose bounds may be configured, the TCP listener which accepts new incoming peer connections, and all Go types that may be serialized/deserialized at will on-the-wire or through a Handler.

A node at most will only have one goroutine + num configured worker goroutines associated to it which represents the listener looking to accept new incoming peer connections, and workers responsible for handling incoming peer messages. A node once closed or once started (as in, (*Node).Listen was called) should not be reused.

func NewNode

func NewNode(opts ...NodeOption) (*Node, error)

NewNode instantiates a new node instance, and pre-configures the node with provided options. Default values for some non-specified options are instantiated as well, which may yield an error.

func (*Node) Addr

func (n *Node) Addr() string

Addr returns the public address of this node. The public address, should it not be configured through the WithNodeAddress functional option when calling NewNode, is initialized to 'host:port' after a successful call to (*Node).Listen.

Addr may be called concurrently.

func (*Node) Bind

func (n *Node) Bind(protocols ...Protocol)

Bind registers a Protocol to this node, which implements callbacks for all events this node can emit throughout its lifecycle. For more information on how to implement Protocol, refer to the documentation for Protocol. Bind only registers Protocol's should the node not yet be listening for new connections. If the node is already listening for new peers, Bind silently returns and does nothing.

Bind may be called concurrently.

func (*Node) Close

func (n *Node) Close() error

Close gracefully stops all live inbound/outbound peer connections registered on this node, and stops the node from handling/accepting new incoming peer connections. It returns an error if an error occurs closing the nodes listener. Nodes that are closed should not ever be re-used.

Close may be called concurrently.

func (*Node) DecodeMessage

func (n *Node) DecodeMessage(data []byte) (Serializable, error)

DecodeMessage decodes data into its registered Go type T should it be well-formed. It throws an error if the opcode at the head of data has yet to be registered/associated to a Go type via (*Node).RegisterMessage. For more details, refer to (*Node).RegisterMessage.

DecodeMessage may be called concurrently.

func (*Node) EncodeMessage

func (n *Node) EncodeMessage(msg Serializable) ([]byte, error)

EncodeMessage encodes msg which must be a registered Go type T into its wire representation. It throws an error if the Go type of msg has not yet been registered through (*Node).RegisterMessage. For more details, refer to (*Node).RegisterMessage.

EncodeMessage may be called concurrently.

func (*Node) Handle

func (n *Node) Handle(handlers ...Handler)

Handle registers a Handler to this node, which is executed every time this node receives a message from an inbound/outbound connection. For more information on how to write a Handler, refer to the documentation for Handler. Handle only registers Handler's should the node not yet be listening for new connections. If the node is already listening for new peers, Handle silently returns and does nothing.

Handle may be called concurrently.

func (*Node) ID

func (n *Node) ID() ID

ID returns an immutable copy of the ID of this node. The ID of the node is set after a successful call to (*Node).Listen, or otherwise passed through the WithNodeID functional option when calling NewNode.

ID may be called concurrently.

func (*Node) Inbound

func (n *Node) Inbound() []*Client

Inbound returns a cloned slice of all inbound connections to this node as Client instances. It is useful while writing unit tests where you would want to block the current goroutine via (*Client).WaitUntilReady and (*Client).WaitUntilClosed to test scenarios where you want to be sure some inbound client is open/closed.

func (*Node) Listen

func (n *Node) Listen() error

Listen has the node start listening for new peers. If an error occurs while starting the listener due to misconfigured options or resource exhaustion, an error is returned. If the node is already listening for new connections, an error is thrown.

Listen must not be called concurrently, and should only ever be called once per node instance.

func (*Node) Logger

func (n *Node) Logger() *zap.Logger

Logger returns the underlying logger associated to this node. The logger, should it not be configured through the WithNodeLogger functional option when calling NewNode, is by default zap.NewNop().

Logger may be called concurrently.

func (*Node) Outbound

func (n *Node) Outbound() []*Client

Outbound returns a cloned slice of all outbound connections to this node as Client instances. It is useful while writing unit tests where you would want to block the current goroutine via (*Client).WaitUntilClosed to test scenarios where you want to be sure some outbound client has resources associated to it completely released.

func (*Node) Ping

func (n *Node) Ping(ctx context.Context, addr string) (*Client, error)

Ping takes an available connection from this nodes connection pool if the peer at addr has never been connected to before, connects to it, handshakes with the peer, and returns a *Client instance should the entire process be successful.

If there already exists a live connection to the peer at addr, no new connection is established and the *Client instance associated to the peer is returned. An error is returned if connecting to the peer should it not have been connected to before fails, or if ctx was canceled/expired, or if handshaking fails.

If there is no available connection from this nodes connection pool, the connection that is at the tail of the pool is closed and evicted and used to ping addr.

It is safe to call Ping concurrently.

func (*Node) RegisterMessage

func (n *Node) RegisterMessage(ser Serializable, de interface{}) uint16

RegisterMessage registers a Go type T that implements the Serializable interface with an associated deserialize function whose signature comprises of func([]byte) (T, error). RegisterMessage should be called in the following manner:

RegisterMessage(T{}, func([]byte) (T, error) { ... })

It returns a 16-bit unsigned integer (opcode) that is associated to the type T on-the-wire. Once a Go type has been registered, it may be used in a Handler, or via (*Node).EncodeMessage, (*Node).DecodeMessage, (*Node).SendMessage, and (*Node).RequestMessage.

The wire format of a type registered comprises of append([]byte{16-bit big-endian integer (opcode)}, ser.Marshal()...).

RegisterMessage may be called concurrently, though is discouraged.

func (*Node) Request

func (n *Node) Request(ctx context.Context, addr string, data []byte) ([]byte, error)

Request takes an available connection from this nodes connection pool if the peer at addr has never been connected to before, connects to it, handshakes with the peer, and sends it a request should the entire process be successful.

Once the request has been sent, the current goroutine Request was called in will block until either a response has been received which will be subsequently returned, ctx was canceled/expired, or the connection was dropped.

If there already exists a live connection to the peer at addr, no new connection is established and the request will follow through. An error is returned if connecting to the peer should it not have been connected to before fails, or if handshaking fails.

If there is no available connection from this nodes connection pool, the connection that is at the tail of the pool is closed and evicted and used to send a request to addr.

func (*Node) RequestMessage

func (n *Node) RequestMessage(ctx context.Context, addr string, req Serializable) (Serializable, error)

RequestMessage encodes msg which is a Go type registered via (*Node).RegisterMessage, and sends it as a request to addr, and returns a decoded response from the peer at addr. For more details, refer to (*Node).Request and (*Node).RegisterMessage.

func (*Node) Send

func (n *Node) Send(ctx context.Context, addr string, data []byte) error

Send takes an available connection from this nodes connection pool if the peer at addr has never been connected to before, connects to it, handshakes with the peer, and sends it data.

If there already exists a live connection to the peer at addr, no new connection is established and data will be sent through. An error is returned if connecting to the peer should it not have been connected to before fails, or if handshaking fails, or if the connection is closed.

If there is no available connection from this nodes connection pool, the connection that is at the tail of the pool is closed and evicted and used to send data to addr.

func (*Node) SendMessage

func (n *Node) SendMessage(ctx context.Context, addr string, msg Serializable) error

SendMessage encodes msg which is a Go type registered via (*Node).RegisterMessage, and sends it to addr. For more details, refer to (*Node).Send and (*Node).RegisterMessage.

func (*Node) Sign

func (n *Node) Sign(data []byte) Signature

Sign uses the nodes private key to sign data and return its cryptographic signature as a slice of bytes.

type NodeOption

type NodeOption func(n *Node)

NodeOption represents a functional option that may be passed to NewNode for instantiating a new node instance with configured values.

func WithNodeAddress

func WithNodeAddress(addr string) NodeOption

WithNodeAddress sets the public address of this node which is advertised on the ID sent to peers during a handshake protocol which is performed when interacting with peers this node has had no live connection to beforehand. By default, it is left blank, and initialized to 'binding host:binding port' upon calling (*Node).Listen.

func WithNodeBindHost

func WithNodeBindHost(host net.IP) NodeOption

WithNodeBindHost sets the TCP host IP address which the node binds itself to and listens for new incoming peer connections on. By default, it is unspecified (0.0.0.0).

func WithNodeBindPort

func WithNodeBindPort(port uint16) NodeOption

WithNodeBindPort sets the TCP port which the node binds itself to and listens for new incoming peer connections on. By default, a random port is assigned by the operating system.

func WithNodeID

func WithNodeID(id ID) NodeOption

WithNodeID sets the nodes ID, and public address. By default, the ID is set with an address that is set to the binding host and port upon calling (*Node).Listen should the address not be configured.

func WithNodeIdleTimeout

func WithNodeIdleTimeout(idleTimeout time.Duration) NodeOption

WithNodeIdleTimeout sets the duration in which should there be no subsequent reads/writes on a connection, the connection shall timeout and have resources related to it released. By default, the timeout is set to be 3 seconds. If an idle timeout of 0 is specified, idle timeouts will be disabled.

func WithNodeLogger

func WithNodeLogger(logger *zap.Logger) NodeOption

WithNodeLogger sets the logger implementation that the node shall use. By default, zap.NewNop() is assigned which disables any logs.

func WithNodeMaxDialAttempts

func WithNodeMaxDialAttempts(maxDialAttempts uint) NodeOption

WithNodeMaxDialAttempts sets the max number of attempts a connection is dialed before it is determined to have failed. By default, the max number of attempts a connection is dialed is 3.

func WithNodeMaxInboundConnections

func WithNodeMaxInboundConnections(maxInboundConnections uint) NodeOption

WithNodeMaxInboundConnections sets the max number of inbound connections the connection pool a node maintains allows at any given moment in time. By default, the max number of inbound connections is 128. Exceeding the max number causes the connection pool to release the oldest inbound connection in the pool.

func WithNodeMaxOutboundConnections

func WithNodeMaxOutboundConnections(maxOutboundConnections uint) NodeOption

WithNodeMaxOutboundConnections sets the max number of outbound connections the connection pool a node maintains allows at any given moment in time. By default, the maximum number of outbound connections is 128. Exceeding the max number causes the connection pool to release the oldest outbound connection in the pool.

func WithNodeMaxRecvMessageSize

func WithNodeMaxRecvMessageSize(maxRecvMessageSize uint32) NodeOption

WithNodeMaxRecvMessageSize sets the max number of bytes a node is willing to receive from a peer. If the limit is ever exceeded, the peer is disconnected with an error. Setting this option to zero will disable the limit. By default, the max number of bytes a node is willing to receive from a peer is set to 4MB.

func WithNodeNumWorkers

func WithNodeNumWorkers(numWorkers uint) NodeOption

WithNodeNumWorkers sets the max number of workers a node will spawn to handle incoming peer messages. By default, the max number of workers a node will spawn is the number of CPUs available to the Go runtime specified by runtime.NumCPU(). The minimum number of workers which need to be spawned is 1.

func WithNodePrivateKey

func WithNodePrivateKey(privateKey PrivateKey) NodeOption

WithNodePrivateKey sets the private key of the node. By default, a random private key is generated using GenerateKeys should no private key be configured.

type PrivateKey

type PrivateKey [SizePrivateKey]byte

PrivateKey is the default node/peer private key type.

func LoadKeysFromHex

func LoadKeysFromHex(secretHex string) (PrivateKey, error)

LoadKeysFromHex loads a private key from a hex string. It returns an error if secretHex is not hex-encoded or is an invalid number of bytes. In the case of the latter error, the error is wrapped as io.ErrUnexpectedEOF. Calling this function performs 1 allocation.

func (PrivateKey) MarshalJSON

func (k PrivateKey) MarshalJSON() ([]byte, error)

MarshalJSON returns the hexadecimal representation of this private key in JSON. It should never throw an error.

func (PrivateKey) Public

func (k PrivateKey) Public() PublicKey

Public returns the public key associated to this private key.

func (PrivateKey) Sign

func (k PrivateKey) Sign(data []byte) Signature

Sign uses this private key to sign data and return its cryptographic signature as a slice of bytes.

func (PrivateKey) String

func (k PrivateKey) String() string

String returns the hexadecimal representation of this private key.

type Protocol

type Protocol struct {
	// VersionMajor, VersionMinor, and VersionPatch mark the version of this protocol with respect to semantic
	// versioning.
	VersionMajor, VersionMinor, VersionPatch uint

	// Bind is called when the node has successfully started listening for new peers. Important node information
	// such as the nodes binding host, binding port, public address, and ID are not initialized until after
	// (*Node).Listen has successfully been called. Bind gets called the very moment such information has successfully
	// been initialized.
	//
	// Errors returned from implementations of Bind will propagate back up to (*Node).Listen as a returned error.
	Bind func(node *Node) error

	// OnPeerConnected is called when a node successfully receives an incoming peer/connects to an outgoing peer, and
	// completes noise's protocol handshake.
	OnPeerConnected func(client *Client)

	// OnPeerDisconnected is called whenever any inbound/outbound connection that has successfully connected to a node
	// has been terminated.
	OnPeerDisconnected func(client *Client)

	// OnPingFailed is called whenever any attempt by a node to dial a peer at addr fails.
	OnPingFailed func(addr string, err error)

	// OnMessageSent is called whenever bytes of a message or request or response have been flushed/sent to a peer.
	OnMessageSent func(client *Client)

	// OnMessageRecv is called whenever a message or response is received from a peer.
	OnMessageRecv func(client *Client)
}

Protocol is an interface that may be implemented by libraries and projects built on top of Noise to hook callbacks onto a series of events that are emitted throughout a nodes lifecycle. They may be registered to a node by (*Node).Bind before the node starts listening for new peers.

type PublicKey

type PublicKey [SizePublicKey]byte

PublicKey is the default node/peer public key type.

func (PublicKey) MarshalJSON

func (k PublicKey) MarshalJSON() ([]byte, error)

MarshalJSON returns the hexadecimal representation of this public key in JSON. It should never throw an error.

func (PublicKey) String

func (k PublicKey) String() string

String returns the hexadecimal representation of this public key.

func (PublicKey) Verify

func (k PublicKey) Verify(data []byte, signature Signature) bool

Verify returns true if the cryptographic signature of data is representative of this public key.

type Serializable

type Serializable interface {
	// Marshal converts this type into it's byte representation as a slice.
	Marshal() []byte
}

Serializable attributes whether or not a type has a byte representation that it may be serialized into.

type Signature

type Signature [SizeSignature]byte

Signature is the default node/peer cryptographic signature type.

func UnmarshalSignature

func UnmarshalSignature(data []byte) Signature

UnmarshalSignature decodes data into a Signature instance. It panics if data is not of expected length by instilling a bound check hint to the compiler. It uses unsafe hackery to zero-alloc convert data into a Signature.

func (Signature) MarshalJSON

func (s Signature) MarshalJSON() ([]byte, error)

MarshalJSON returns the hexadecimal representation of this signature in JSON. It should never throw an error.

func (Signature) String

func (s Signature) String() string

String returns the hexadecimal representation of this signature.

Directories

Path Synopsis
cmd
Package gossip is a simple implementation of a gossip protocol for noise.
Package gossip is a simple implementation of a gossip protocol for noise.
Package kademlia is a noise implementation of the routing and discovery portion of the Kademlia protocol, with minor improvements suggested by the S/Kademlia paper.
Package kademlia is a noise implementation of the routing and discovery portion of the Kademlia protocol, with minor improvements suggested by the S/Kademlia paper.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL