nanorpc

package module
v0.4.99 Latest Latest
Warning

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

Go to latest
Published: Jul 27, 2025 License: MIT Imports: 18 Imported by: 0

README

nanorpc

[!WARNING] This package has been moved to protomcp.org

This package is now DEPRECATED. Please update your imports:

Old import (deprecated):

import "github.com/amery/nanorpc/pkg/nanorpc"

New import:

import "protomcp.org/nanorpc/pkg/nanorpc"

New repository: https://github.com/protomcp/nanorpc

pkg.go.dev Go Report Card codecov

Go client library for the NanoRPC protocol - a lightweight RPC framework designed for embedded systems and resource-constrained environments.

This package provides the client-side implementation. For server implementation, see the companion server package.

Features

  • Connection Management: Automatic reconnection with configurable backoff
  • Request/Response: Synchronous RPC calls with correlation IDs
  • Pub/Sub: Event-driven messaging with subscription callbacks
  • Hash Optimization: Reduced memory usage with path hashing
  • Protocol Support: Binary protocol using Protocol Buffers
  • Error Handling: Structured error responses and connection recovery

Installation

go get github.com/amery/nanorpc/pkg/nanorpc

Quick Start

Basic Client Usage
package main

import (
    "context"
    "log"

    "darvaza.org/slog"

    "github.com/amery/nanorpc/pkg/nanorpc"
)

func main() {
    // Create client configuration
    config := &nanorpc.ClientConfig{
        Remote: "localhost:8080",
        Logger: slog.Default(),
    }

    // Create and connect client
    client, err := config.New()
    if err != nil {
        log.Fatal(err)
    }
    defer client.Close()

    // Make a request
    ctx := context.Background()
    ch := make(chan *nanorpc.NanoRPCResponse, 1)
    callback := func(ctx context.Context, id int32,
        res *nanorpc.NanoRPCResponse) error {
        ch <- res
        return nil
    }

    _, err = client.Request("/status", nil, callback)
    if err != nil {
        log.Fatal(err)
    }

    // Wait for response
    response := <-ch
    log.Printf("Response: %+v", response)
}
Subscription Example
// Subscribe to events
callback := func(ctx context.Context, id int32,
    update *nanorpc.NanoRPCResponse) error {
    log.Printf("Received update: %+v", update)
    return nil
}

requestID, err := client.Subscribe("/events", nil, callback)
if err != nil {
    log.Fatal(err)
}

log.Printf("Subscribed with request ID: %d", requestID)

Protocol

The nanorpc protocol supports three communication patterns:

Ping-Pong

Health checks and connection validation:

// Simple ping (fire-and-forget)
if !client.Ping() {
    log.Printf("Client not connected")
}

// Ping with response (waits for pong)
ch := client.Pong()
select {
case err := <-ch:
    if err != nil {
        log.Printf("Ping failed: %v", err)
    } else {
        log.Printf("Ping successful")
    }
case <-time.After(5 * time.Second):
    log.Printf("Ping timeout")
}
Request-Response

Synchronous RPC calls:

callback := func(ctx context.Context, id int32,
    res *nanorpc.NanoRPCResponse) error {
    // Handle response
    return nil
}
requestID, err := client.Request("/api/data", requestData, callback)
Pub/Sub

Event-driven messaging:

callback := func(ctx context.Context, id int32,
    update *nanorpc.NanoRPCResponse) error {
    // Handle update
    return nil
}
requestID, err := client.Subscribe("/events", filter, callback)

Configuration

Client Options
config := &nanorpc.ClientConfig{
    Remote:          "localhost:8080", // Server address
    Logger:          slog.Default(),   // Logger instance

    // Connection settings
    DialTimeout:     2 * time.Second,  // Default: 2s
    ReadTimeout:     2 * time.Second,  // Default: 2s
    WriteTimeout:    2 * time.Second,  // Default: 2s
    IdleTimeout:     10 * time.Second, // Default: 10s
    KeepAlive:       5 * time.Second,  // Default: 5s

    // Reconnection settings
    ReconnectDelay:  5 * time.Second,  // Default: 5s

    // Hash optimization
    AlwaysHashPaths: false,            // Use path hashing
    QueueSize:       0,                // Request queue size
}
Hash Optimization

For embedded targets with limited memory, paths can be hashed:

// Pre-register path to compute hash
nanorpc.RegisterPath("/long/api/path")

// Client can use string path (hashed automatically if AlwaysHashPaths=true)
requestID, err := client.Request("/long/api/path", data, callback)

// Or use pre-computed hash directly
requestID, err := client.RequestWithHash("/long/api/path", data, callback)

// Or use hash value directly
hash := nanorpc.HashCache{}.Hash("/long/api/path")
requestID, err := client.RequestByHash(hash, data, callback)

Error Handling

The library provides structured error handling:

callback := func(ctx context.Context, id int32,
    response *nanorpc.NanoRPCResponse) error {
    // Check for protocol errors first
    if err := nanorpc.ResponseAsError(response); err != nil {
        if nanorpc.IsNotFound(err) {
            log.Printf("Endpoint not found")
        } else if nanorpc.IsNotAuthorized(err) {
            log.Printf("Not authorized: %v", err)
        } else if nanorpc.IsNoResponse(err) {
            log.Printf("No response received: %v", err)
        } else {
            log.Printf("Server error: %v", err)
        }
        return err
    }

    // Response is OK, process data
    log.Printf("Success: %+v", response)
    return nil
}

_, err := client.Request("/api/endpoint", data, callback)
if err != nil {
    log.Printf("Request failed: %v", err)
}

Thread Safety

The client is thread-safe and supports concurrent usage:

var wg sync.WaitGroup

// Multiple goroutines can safely use the same client
for i := 0; i < 10; i++ {
    wg.Add(1)
    go func(id int) {
        defer wg.Done()

        callback := func(ctx context.Context, reqID int32,
            res *nanorpc.NanoRPCResponse) error {
            log.Printf("Goroutine %d got response for request %d", id, reqID)
            return nil
        }

        _, err := client.Request("/parallel", data, callback)
        if err != nil {
            log.Printf("Goroutine %d request failed: %v", id, err)
        }
    }(i)
}

wg.Wait()

Documentation

Overview

Package nanorpc provides the core types and utilities for the NanoRPC protocol.

This package contains:

  • Protocol buffer definitions and generated types
  • HashCache for efficient path hashing using FNV-1a
  • Request/response encoding and decoding utilities
  • Type aliases and helpers for working with protocol internals
  • Error handling utilities

The actual client and server implementations are in separate packages:

  • Client: github.com/amery/nanorpc/pkg/nanorpc/client
  • Server: github.com/amery/nanorpc/pkg/nanorpc/server

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrNoResponse indicates the server didn't answer before disconnection
	ErrNoResponse = core.NewTimeoutError(errors.New("no response"))

	// ErrInternalServerError indicates the server reported an internal error
	ErrInternalServerError = errors.New("internal server error")

	// ErrSessionClosed indicates the session has been closed
	ErrSessionClosed = errors.New("session closed")

	// ErrHashCollision indicates two different paths hash to the same value
	ErrHashCollision = errors.New("hash collision detected")
)
View Source
var (
	NanoRPCRequest_Type_name = map[int32]string{
		0: "TYPE_UNSPECIFIED",
		1: "TYPE_PING",
		2: "TYPE_REQUEST",
		3: "TYPE_SUBSCRIBE",
	}
	NanoRPCRequest_Type_value = map[string]int32{
		"TYPE_UNSPECIFIED": 0,
		"TYPE_PING":        1,
		"TYPE_REQUEST":     2,
		"TYPE_SUBSCRIBE":   3,
	}
)

Enum value maps for NanoRPCRequest_Type.

View Source
var (
	NanoRPCResponse_Type_name = map[int32]string{
		0: "TYPE_UNSPECIFIED",
		1: "TYPE_PONG",
		2: "TYPE_RESPONSE",
		3: "TYPE_UPDATE",
	}
	NanoRPCResponse_Type_value = map[string]int32{
		"TYPE_UNSPECIFIED": 0,
		"TYPE_PONG":        1,
		"TYPE_RESPONSE":    2,
		"TYPE_UPDATE":      3,
	}
)

Enum value maps for NanoRPCResponse_Type.

View Source
var (
	NanoRPCResponse_Status_name = map[int32]string{
		0: "STATUS_UNSPECIFIED",
		1: "STATUS_OK",
		2: "STATUS_NOT_FOUND",
		3: "STATUS_NOT_AUTHORIZED",
		4: "STATUS_INTERNAL_ERROR",
	}
	NanoRPCResponse_Status_value = map[string]int32{
		"STATUS_UNSPECIFIED":    0,
		"STATUS_OK":             1,
		"STATUS_NOT_FOUND":      2,
		"STATUS_NOT_AUTHORIZED": 3,
		"STATUS_INTERNAL_ERROR": 4,
	}
)

Enum value maps for NanoRPCResponse_Status.

View Source
var File_nanorpc_proto protoreflect.FileDescriptor

Functions

func AsPathOneOfHash added in v0.4.99

func AsPathOneOfHash(p PathOneOf) (uint32, bool)

AsPathOneOfHash extracts the path hash from a PathOneOf if it contains one. Returns the hash value and true if the PathOneOf contains a path hash, or 0 and false if it contains a string path or is nil.

func AsPathOneOfString added in v0.4.99

func AsPathOneOfString(p PathOneOf) (string, bool)

AsPathOneOfString extracts the string path from a PathOneOf if it contains one. Returns the path string and true if the PathOneOf contains a string path, or an empty string and false if it contains a hash or is nil.

func DecodeRequestData added in v0.3.1

func DecodeRequestData[T proto.Message](req *NanoRPCRequest, out T) (T, bool, error)

DecodeRequestData attempts to decode the payload of a NanoRPC request.

func DecodeResponseData added in v0.3.1

func DecodeResponseData[T proto.Message](res *NanoRPCResponse, out T) (T, bool, error)

DecodeResponseData attempts to decode the payload of a NanoRPC response.

func DecodeSplit

func DecodeSplit(data []byte) (prefixLen, totalLen int, err error)

DecodeSplit identifies the size of the wrapped message and if enough data is already buffered.

func EncodeRequest added in v0.1.2

func EncodeRequest(req *NanoRPCRequest, data proto.Message) ([]byte, error)

EncodeRequest encodes a wrapped NanoRPC request. If request data is provided, it will be encoded into the NanoRPCRequest, otherwise the request will be used as-is.

func EncodeRequestTo added in v0.2.1

func EncodeRequestTo(w io.Writer, req *NanoRPCRequest, data proto.Message) (int, error)

EncodeRequestTo encodes a wrapped NanoRPC request. If request data is provided, it will be encoded into the NanoRPCRequest, otherwise the request will be used as-is.

func EncodeResponse added in v0.1.2

func EncodeResponse(res *NanoRPCResponse, data proto.Message) ([]byte, error)

EncodeResponse encodes a wrapped NanoRPC response. If response data is provided, it will be encoded into the NanoRPCResponse, otherwise the response will be used as-is.

func EncodeResponseTo added in v0.2.1

func EncodeResponseTo(w io.Writer, res *NanoRPCResponse, data proto.Message) (int, error)

EncodeResponseTo encodes a wrapped NanoRPC response. If response data is provided, it will be encoded into the NanoRPCResponse, otherwise the response will be used as-is.

func IsNoResponse added in v0.3.7

func IsNoResponse(err error) bool

IsNoResponse checks if the error represents no response being received. This error is also used to notify the connection was closed.

func IsNotAuthorized added in v0.3.7

func IsNotAuthorized(err error) bool

IsNotAuthorized checks if the error represents a STATUS_NOT_AUTHORIZED response.

func IsNotFound added in v0.3.7

func IsNotFound(err error) bool

IsNotFound checks if the error represents a STATUS_NOT_FOUND response.

func RegisterPath added in v0.1.1

func RegisterPath(path string)

RegisterPath pre-computes the path_hash for a given path into a the global cache.

func ResponseAsError added in v0.2.0

func ResponseAsError(res *NanoRPCResponse) error

ResponseAsError extracts an error from the status of a response.

func Split

func Split(data []byte, atEOF bool) (advance int, msg []byte, err error)

Split identifies a NanoRPC wrapped message from a buffer.

Types

type HashCache added in v0.1.1

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

HashCache stores and computes path_hash values for [NanoRPCRequest]s.

func (*HashCache) DehashRequest added in v0.1.1

func (hc *HashCache) DehashRequest(r *NanoRPCRequest) (*NanoRPCRequest, bool)

DehashRequest attempts to convert path_hash in a NanoRPCRequest into a string path.

func (*HashCache) Hash added in v0.1.1

func (hc *HashCache) Hash(path string) (uint32, error)

Hash returns the path_hash for a given path, and stores it if new. Returns an error if a hash collision is detected.

func (*HashCache) Path added in v0.1.1

func (hc *HashCache) Path(value uint32) (string, bool)

Path returns a known path for a given path_hash.

type NanoRPCRequest

type NanoRPCRequest struct {
	RequestId   int32               `protobuf:"varint,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"`
	RequestType NanoRPCRequest_Type `protobuf:"varint,2,opt,name=request_type,json=requestType,proto3,enum=NanoRPCRequest_Type" json:"request_type,omitempty"`
	// Types that are assignable to PathOneof:
	//
	//	*NanoRPCRequest_PathHash
	//	*NanoRPCRequest_Path
	PathOneof isNanoRPCRequest_PathOneof `protobuf_oneof:"path_oneof"`
	Data      []byte                     `protobuf:"bytes,10,opt,name=data,proto3" json:"data,omitempty"`
	// contains filtered or unexported fields
}

func DecodeRequest

func DecodeRequest(data []byte) (*NanoRPCRequest, int, error)

DecodeRequest attempts to decode a wrapped NanoRPC request from a buffer

func DehashRequest added in v0.1.1

func DehashRequest(r *NanoRPCRequest) (*NanoRPCRequest, bool)

DehashRequest attempts to convert path_hash in a NanoRPCRequest into a string path.

func (*NanoRPCRequest) Descriptor deprecated

func (*NanoRPCRequest) Descriptor() ([]byte, []int)

Deprecated: Use NanoRPCRequest.ProtoReflect.Descriptor instead.

func (*NanoRPCRequest) GetData

func (x *NanoRPCRequest) GetData() []byte

func (*NanoRPCRequest) GetPath

func (x *NanoRPCRequest) GetPath() string

func (*NanoRPCRequest) GetPathHash

func (x *NanoRPCRequest) GetPathHash() uint32

func (*NanoRPCRequest) GetPathOneof

func (m *NanoRPCRequest) GetPathOneof() isNanoRPCRequest_PathOneof

func (*NanoRPCRequest) GetRequestId

func (x *NanoRPCRequest) GetRequestId() int32

func (*NanoRPCRequest) GetRequestType

func (x *NanoRPCRequest) GetRequestType() NanoRPCRequest_Type

func (*NanoRPCRequest) ProtoMessage

func (*NanoRPCRequest) ProtoMessage()

func (*NanoRPCRequest) ProtoReflect

func (x *NanoRPCRequest) ProtoReflect() protoreflect.Message

func (*NanoRPCRequest) Reset

func (x *NanoRPCRequest) Reset()

func (*NanoRPCRequest) String

func (x *NanoRPCRequest) String() string

type NanoRPCRequest_Path

type NanoRPCRequest_Path struct {
	Path string `protobuf:"bytes,4,opt,name=path,proto3,oneof"`
}

type NanoRPCRequest_PathHash

type NanoRPCRequest_PathHash struct {
	PathHash uint32 `protobuf:"varint,3,opt,name=path_hash,json=pathHash,proto3,oneof"` // FNV-1a of path
}

type NanoRPCRequest_Type

type NanoRPCRequest_Type int32
const (
	NanoRPCRequest_TYPE_UNSPECIFIED NanoRPCRequest_Type = 0
	NanoRPCRequest_TYPE_PING        NanoRPCRequest_Type = 1
	NanoRPCRequest_TYPE_REQUEST     NanoRPCRequest_Type = 2
	NanoRPCRequest_TYPE_SUBSCRIBE   NanoRPCRequest_Type = 3
)

func (NanoRPCRequest_Type) Descriptor

func (NanoRPCRequest_Type) Enum

func (NanoRPCRequest_Type) EnumDescriptor deprecated

func (NanoRPCRequest_Type) EnumDescriptor() ([]byte, []int)

Deprecated: Use NanoRPCRequest_Type.Descriptor instead.

func (NanoRPCRequest_Type) Number

func (NanoRPCRequest_Type) String

func (x NanoRPCRequest_Type) String() string

func (NanoRPCRequest_Type) Type

type NanoRPCResponse

type NanoRPCResponse struct {
	RequestId       int32                  `protobuf:"varint,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"`
	ResponseType    NanoRPCResponse_Type   `protobuf:"varint,2,opt,name=response_type,json=responseType,proto3,enum=NanoRPCResponse_Type" json:"response_type,omitempty"`
	ResponseStatus  NanoRPCResponse_Status `` /* 132-byte string literal not displayed */
	ResponseMessage string                 `protobuf:"bytes,4,opt,name=response_message,json=responseMessage,proto3" json:"response_message,omitempty"`
	Data            []byte                 `protobuf:"bytes,10,opt,name=data,proto3" json:"data,omitempty"`
	// contains filtered or unexported fields
}

func DecodeResponse

func DecodeResponse(data []byte) (*NanoRPCResponse, int, error)

DecodeResponse attempts to decode a wrapped NanoRPC response from a buffer.

func (*NanoRPCResponse) Descriptor deprecated

func (*NanoRPCResponse) Descriptor() ([]byte, []int)

Deprecated: Use NanoRPCResponse.ProtoReflect.Descriptor instead.

func (*NanoRPCResponse) GetData

func (x *NanoRPCResponse) GetData() []byte

func (*NanoRPCResponse) GetRequestId

func (x *NanoRPCResponse) GetRequestId() int32

func (*NanoRPCResponse) GetResponseMessage

func (x *NanoRPCResponse) GetResponseMessage() string

func (*NanoRPCResponse) GetResponseStatus

func (x *NanoRPCResponse) GetResponseStatus() NanoRPCResponse_Status

func (*NanoRPCResponse) GetResponseType

func (x *NanoRPCResponse) GetResponseType() NanoRPCResponse_Type

func (*NanoRPCResponse) ProtoMessage

func (*NanoRPCResponse) ProtoMessage()

func (*NanoRPCResponse) ProtoReflect

func (x *NanoRPCResponse) ProtoReflect() protoreflect.Message

func (*NanoRPCResponse) Reset

func (x *NanoRPCResponse) Reset()

func (*NanoRPCResponse) String

func (x *NanoRPCResponse) String() string

type NanoRPCResponse_Status

type NanoRPCResponse_Status int32
const (
	NanoRPCResponse_STATUS_UNSPECIFIED    NanoRPCResponse_Status = 0
	NanoRPCResponse_STATUS_OK             NanoRPCResponse_Status = 1
	NanoRPCResponse_STATUS_NOT_FOUND      NanoRPCResponse_Status = 2
	NanoRPCResponse_STATUS_NOT_AUTHORIZED NanoRPCResponse_Status = 3
	NanoRPCResponse_STATUS_INTERNAL_ERROR NanoRPCResponse_Status = 4
)

func (NanoRPCResponse_Status) Descriptor

func (NanoRPCResponse_Status) Enum

func (NanoRPCResponse_Status) EnumDescriptor deprecated

func (NanoRPCResponse_Status) EnumDescriptor() ([]byte, []int)

Deprecated: Use NanoRPCResponse_Status.Descriptor instead.

func (NanoRPCResponse_Status) Number

func (NanoRPCResponse_Status) String

func (x NanoRPCResponse_Status) String() string

func (NanoRPCResponse_Status) Type

type NanoRPCResponse_Type

type NanoRPCResponse_Type int32
const (
	NanoRPCResponse_TYPE_UNSPECIFIED NanoRPCResponse_Type = 0
	NanoRPCResponse_TYPE_PONG        NanoRPCResponse_Type = 1
	NanoRPCResponse_TYPE_RESPONSE    NanoRPCResponse_Type = 2
	NanoRPCResponse_TYPE_UPDATE      NanoRPCResponse_Type = 3
)

func (NanoRPCResponse_Type) Descriptor

func (NanoRPCResponse_Type) Enum

func (NanoRPCResponse_Type) EnumDescriptor deprecated

func (NanoRPCResponse_Type) EnumDescriptor() ([]byte, []int)

Deprecated: Use NanoRPCResponse_Type.Descriptor instead.

func (NanoRPCResponse_Type) Number

func (NanoRPCResponse_Type) String

func (x NanoRPCResponse_Type) String() string

func (NanoRPCResponse_Type) Type

type PathOneOf added in v0.4.99

type PathOneOf = isNanoRPCRequest_PathOneof

PathOneOf is a type alias for the internal isNanoRPCRequest_PathOneof interface. This interface represents the oneof field in NanoRPCRequest that can contain either a string path or a uint32 path hash. It is used by both client and server packages for flexible path handling.

func GetPathOneOfHash added in v0.4.99

func GetPathOneOfHash(hash uint32) PathOneOf

GetPathOneOfHash creates a PathOneOf containing a path hash. This is used when only the 32-bit FNV-1a hash of the path should be sent, which is more efficient for embedded systems with limited bandwidth.

func GetPathOneOfString added in v0.4.99

func GetPathOneOfString(path string) PathOneOf

GetPathOneOfString creates a PathOneOf containing a string path. This is used when the full path string should be sent in the request.

type ResponseError added in v0.3.9

type ResponseError struct {
	Err    error
	Msg    string
	Status NanoRPCResponse_Status
}

ResponseError represents a NanoRPC error response.

func (ResponseError) Error added in v0.3.9

func (e ResponseError) Error() string

func (ResponseError) String added in v0.3.9

func (e ResponseError) String() string

func (ResponseError) Unwrap added in v0.3.9

func (e ResponseError) Unwrap() error

Directories

Path Synopsis
Package client implements a reconnecting NanoRPC client.
Package client implements a reconnecting NanoRPC client.
Package common provides shared constants, types, and utilities used by both the nanorpc client and server implementations.
Package common provides shared constants, types, and utilities used by both the nanorpc client and server implementations.
testutils
Package testutils provides test utilities for nanorpc including assertion helpers and mock implementations for testing.
Package testutils provides test utilities for nanorpc including assertion helpers and mock implementations for testing.
Package server implements the NanoRPC server for handling lightweight RPC requests in embedded systems and resource-constrained environments.
Package server implements the NanoRPC server for handling lightweight RPC requests in embedded systems and resource-constrained environments.

Jump to

Keyboard shortcuts

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