hop

package module
v0.0.2 Latest Latest
Warning

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

Go to latest
Published: Jul 30, 2026 License: Apache-2.0 Imports: 20 Imported by: 0

README

Hop

hop-sdk-go

Receive Hop messages in your Go service.
A net/http-shaped endpoint on the Hop mesh, over the libhop C ABI.

Go Reference license


Hop is a delay-tolerant mesh: end-to-end encrypted datagrams that hop device to device, over BLE, Wi-Fi, and the internet, until they reach the person or service you meant. Held, never dropped.

hop-sdk-go is the server side: your Go service becomes a first-class address on the mesh, so senders hand messages straight to it. Self-host is an import, not an ops project. No inbound port to open to the world, no bearer tokens to rotate, no message queue to run: the sender identity is authenticated by the ratchet, and delivery is durable and store-and-forward.

Install

Install the signed native core to a stable user prefix, export the emitted environment, then add the same module version:

go run github.com/hopmesh/hop-sdk-go/cmd/hop-install@v0.0.1 --version v0.0.1

export HOP_PREFIX="$HOME/.local/hop/v0.0.1"
export PKG_CONFIG_PATH="$HOP_PREFIX/lib/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}"

# macOS
export DYLD_LIBRARY_PATH="$HOP_PREFIX/lib${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}"
# Linux
export LD_LIBRARY_PATH="$HOP_PREFIX/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"

go get github.com/hopmesh/hop-sdk-go@v0.0.1

The versioned command runs directly from Go's read-only module cache but writes only to $HOME/.local/hop/v0.0.1 (override the base with --prefix). It verifies the detached signature over the canonical native manifest, canonical builder identity, release tag, source SHA, exact host target, archive inventory, size, and every SHA-256 before installation. The installed hop.pc supplies both hop.h and libhop; cgo has no parent-checkout or writable-module-cache assumption. The release job also verifies the attached GitHub OIDC SLSA provenance bundle before publishing these signed assets.

Quick start

package main

import (
	"fmt"

	hop "github.com/hopmesh/hop-sdk-go"
)

func main() {
	server, _ := hop.New()

	server.On("acme/orders", func(req *hop.Request, reply hop.Reply) {
		// req.From is a VERIFIED identity, not a spoofable header
		reply(201, req.Args) // uint16 status + bytes body
	})

	hop.Listen(server, 9944)      // reachable by any device
	fmt.Println(server.Address()) // publish this (or its name); senders reach you by it
}

The DX looks like HTTP; the semantics are better. Inbound is a durable, store-and-forward consume; a reply is a new addressed message that may arrive later, even after a restart. It works when the peer is offline, and there is no auth layer to bolt on, the identity is cryptographic. core is poll-model, so the endpoint runs a pump goroutine (the node is thread-safe).

Reachable by name

Make an endpoint reachable at myaddress.com with no new port. In Go a WS upgrade is a plain http.Handler, so Attach wires the WSS bearer (/_hop) and the discovery route (/.well-known/hop) into your mux in one call:

httpsServer := hop.NewHTTPServer(":443", appHandler)
if err := server.Attach(httpsServer, "wss://myaddress.com/_hop"); err != nil {
    log.Fatal(err)
}
log.Fatal(httpsServer.ListenAndServeTLS(cert, key))

(*Endpoint).Attach is mandatory and must run before any serve method. The returned server path installs raw ConnState admission before TLS, one absolute five-second TLS plus HTTP-head deadline, a 16 KiB header cap, and bounded pending and WebSocket workers. Starting an unattached server or attaching after start returns an error; these limits cannot be left as optional caller configuration.

A client reaches it by name, verified end to end:

address, _ := client.DialByName("https://myaddress.com", false)
status, body, _ := client.Request(address, "acme/orders", "create", order)

TLS proves the domain, a signed reach record proves the address, and the Noise handshake confirms it. Spoof the A record or MITM the lookup and the attacker still can't forge the cert or complete the handshake as the address, and a request sealed to that address is unreadable to anyone else.

How it maps to the core

The endpoint is a hop-core node in host-a-mailbox mode, over the same C ABI every Hop SDK binds (via cgo), with zero core changes:

Endpoint libhop C ABI
server.On(svc, h) hop_subscribe + hop_poll_service_requests
reply(status, body) hop_send_service_response (status is a uint16)
client.Request(...) hop_send_service_request + hop_poll_service_responses
the Internet bearer hop_link_up / hop_bytes_received / hop_drain_outgoing

Examples

Install libhop and export the HOP_PREFIX environment shown above, then:

go test ./...               # raw ABI + in-process + TCP + reach record + WSS discovery, all pass
go run ./examples/echo      # the On / reply DX in-process
go run ./examples/tcp       # the same round trip over a real TCP bearer
go run ./examples/discovery # the full reachable-by-name chain (HTTPS + WSS)

Two-process shape (a standalone server plus a client that dials it):

go run ./examples/server                       # prints its address, listens on tcp://0.0.0.0:9944
go run ./examples/client <address> localhost 9944

(The raw C ABI round trip lives in TestRawRoundTrip, since Go's FFI layer is unexported; go test runs it.)

Status

Prototype. Built and working: On and reply, the client Request, the in-process / TCP / WSS bearers, base58 addressing, reach records (SignReach / VerifyReach) with Attach / DialByName discovery, sibling-replica clustering, and the ABI-version assert. HNS name publish/resolve and multi-tenant hosting are on the roadmap (each an SDK-level follow-up, not a core change).

The Hop family

hop-sdk-go is one of several SDKs over the same C ABI. Same surface, your language: node · python · go · ruby · crystal · elixir. The protocol core is libhop / hop-core.

License

Apache-2.0, embed it freely. Only the protocol core (hop-core) is FSL-1.1-ALv2, source-available and converting to Apache-2.0 after two years.

Documentation

Overview

Package hop is the Go server-side endpoint SDK: receive Hop messages with an net/http-shaped surface, over the libhop C ABI via cgo. This file is the thin cgo layer; endpoint.go has the ergonomics. libhop is found via -L below; build it with `cargo build -p hop`.

Index

Constants

View Source
const (
	MaxWSSMessageBytes  = MaxFrameBytes
	MaxWSSHeaderBytes   = 16 << 10
	MaxPendingHTTPSocks = 64
	MaxPendingWSSLinks  = 64
	WSSHandshakeTimeout = 5 * time.Second
	WSSReadTimeout      = 15 * time.Second
	WSSWriteTimeout     = 5 * time.Second
)
View Source
const DefaultRequestTimeout = 15 * time.Second

DefaultRequestTimeout bounds Request when no explicit timeout is given (aligns with the other SDKs, which default the timeout too).

View Source
const MaxFrameBytes = 1 << 20

Variables

View Source
var (
	// ErrHTTPServerNotAttached prevents starting a server without Hop's pre-handler admission limits.
	ErrHTTPServerNotAttached = errors.New("Hop HTTP server must be attached before serving")
	// ErrHTTPServerStarted prevents changing admission after any socket could have been accepted.
	ErrHTTPServerStarted = errors.New("Hop HTTP server has already started")
)

Functions

func ConnectInProcess

func ConnectInProcess(a, b *Endpoint)

ConnectInProcess wires two endpoints directly (in-process bearer), no sockets.

func Dial

func Dial(e *Endpoint, host string, port int) (net.Conn, error)

Dial connects to a reachable endpoint (we are the Noise initiator).

func Listen

func Listen(e *Endpoint, port int) (net.Listener, error)

Listen accepts inbound Hop connections; each accepted socket is one bearer link (we are acceptor).

func Resolve

func Resolve(client *http.Client, baseURL string) (address, wssURL string, err error)

Resolve fetches + verifies baseURL's well-known, returning the reachable address (base58) + wss URL.

Types

type Endpoint

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

Endpoint receives Hop messages with an net/http-shaped surface, over hop-core.

func New

func New(opts ...Option) (*Endpoint, error)

New starts an endpoint and its pump loop.

func (*Endpoint) AcceptServiceResponse

func (e *Endpoint) AcceptServiceResponse(requestID []byte) (bool, error)

AcceptServiceResponse durably accepts a previously-polled response by its correlation request id. Asynchronous consumers call this only after their own processing has completed.

func (*Endpoint) Address

func (e *Endpoint) Address() string

Address is this endpoint's base58 address (publish it, or its HNS name).

func (*Endpoint) Attach

func (e *Endpoint) Attach(server *HTTPServer, publicURL string) error

Attach wires the WSS bearer and discovery responder into an unstarted HTTPServer and atomically installs acceptance-time admission, TLS/header deadlines, parser caps, and worker limits.

func (*Endpoint) Close

func (e *Endpoint) Close()

Close stops the pump, shuts the bearers, and frees the node. Safe against a late bearer goroutine: once closed is set, every withNode call short-circuits, so a recvLoop firing linkDown as its socket closes cannot dereference a freed node.

func (*Endpoint) ClusterMembers

func (e *Endpoint) ClusterMembers() uint32

ClusterMembers reports the live replica count (self + peers within the membership TTL); 1 if not clustered.

func (*Endpoint) ClusterQuorum

func (e *Endpoint) ClusterQuorum(min uint32)

ClusterQuorum requires at least min live cluster members visible before this replica will process a request (CP: hold-until-coordinated); see WithQuorum. 0 or 1 disables the hold.

func (*Endpoint) DialByName

func (e *Endpoint) DialByName(baseURL string, insecureTLS bool) (string, error)

DialByName resolves a base HTTPS URL to a verified endpoint, dials its WSS, and returns the reachable address (then use Request). Set insecureTLS only for a dev/self-signed cert.

func (*Endpoint) On

func (e *Endpoint) On(service string, h Handler)

On registers a receiver for a hops:// service.

func (*Endpoint) Request

func (e *Endpoint) Request(dst, service, method string, args []byte) (uint16, []byte, error)

Request calls a service on a remote endpoint (dst is a base58 address). Blocks until the response returns (delay-tolerant) or DefaultRequestTimeout elapses. Use RequestTimeout to override.

func (*Endpoint) RequestTimeout

func (e *Endpoint) RequestTimeout(dst, service, method string, args []byte, timeout time.Duration) (uint16, []byte, error)

RequestTimeout is Request with an explicit timeout.

func (*Endpoint) SignReach

func (e *Endpoint) SignReach(endpoint string, ttlSecs uint32) []byte

SignReach signs a self-certifying reachability record for this endpoint's address bound to endpoint (e.g. "wss://myaddress.com/_hop"), valid ttlSecs from now.

func (*Endpoint) WellKnownHandler

func (e *Endpoint) WellKnownHandler(publicURL string, ttlSecs uint32) http.Handler

WellKnownHandler serves the /.well-known/hop discovery body (mount it in any mux).

type HTTPServer

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

HTTPServer owns the only supported public serve path for an attached endpoint. It cannot start until Endpoint.Attach has installed raw-connection admission and all net/http limits.

func NewHTTPServer

func NewHTTPServer(addr string, handler http.Handler) *HTTPServer

NewHTTPServer creates an unstarted server. handler receives every route except /_hop and /.well-known/hop; nil uses http.DefaultServeMux.

func (*HTTPServer) Close

func (s *HTTPServer) Close() error

Close immediately closes the listener and all accepted connections.

func (*HTTPServer) ListenAndServe

func (s *HTTPServer) ListenAndServe() error

ListenAndServe starts an attached plaintext server. Production WSS deployments should use TLS.

func (*HTTPServer) ListenAndServeTLS

func (s *HTTPServer) ListenAndServeTLS(certFile, keyFile string) error

ListenAndServeTLS starts an attached TLS server with the configured absolute handshake deadline.

func (*HTTPServer) Serve

func (s *HTTPServer) Serve(listener net.Listener) error

Serve starts an attached server on listener.

func (*HTTPServer) Shutdown

func (s *HTTPServer) Shutdown(ctx context.Context) error

Shutdown gracefully closes the server.

type Handler

type Handler func(req *Request, reply Reply)

Handler receives an inbound request. Delivery is durable store-and-forward; a reply may arrive later. Treat it like a queue consumer, not a synchronous HTTP handler.

type Option

type Option func(*config)

Option configures New.

func WithCluster

func WithCluster(passphrase string) Option

WithCluster joins the endpoint cluster keyed by a passphrase, so sibling replicas (same identity, no shared datastore) each handle a given request once. The same string interops with the standalone service's HOP_CLUSTER_SECRET. Dedup then applies transparently to inbound requests.

func WithKey

func WithKey(k []byte) Option

WithKey opens the endpoint with a saved 32-byte identity secret (a stable address).

func WithQuorum

func WithQuorum(min uint32) Option

WithQuorum sets a TTL-based visibility threshold before this replica processes a request. It is a conservative failover heuristic, not consensus or an at-most-once guarantee. 0 or 1 disables it.

func WithTickMs

func WithTickMs(ms int) Option

WithTickMs sets the pump interval (default 50ms).

type OutPacket

type OutPacket struct {
	Link  uint64
	Bytes []byte
}

OutPacket is one drained outbound frame for a link.

type ReachInfo

type ReachInfo struct {
	Address  []byte
	Endpoint string
	IssuedAt uint64
	TtlSecs  uint32
}

ReachInfo is a verified reachability record: which Address is reachable at which Endpoint.

func VerifyReach

func VerifyReach(record []byte, nowSecs uint64) (ReachInfo, bool)

VerifyReach verifies a reachability record (0 nowSecs skips the expiry check).

type Reply

type Reply func(status uint16, body []byte) bool

Reply seals a hops:// response back to the request's caller. status is a uint16 (HTTP-shaped).

type Request

type Request struct {
	From      string // base58
	FromBytes []byte
	Service   string
	Method    string
	Args      []byte
}

Request is an inbound service request. From is the cryptographically verified sender identity.

type ServiceReq

type ServiceReq struct {
	From      []byte
	RequestID []byte
	Service   string
	Method    string
	Args      []byte
}

ServiceReq is an inbound hops:// service request.

type ServiceResp

type ServiceResp struct {
	From         []byte
	ForRequestID []byte
	Status       uint16
	Body         []byte
}

ServiceResp is an inbound hops:// service response.

Directories

Path Synopsis
cmd
hop-install command
Command hop-install installs one signed libhop release outside Go's read-only module cache.
Command hop-install installs one signed libhop release outside Go's read-only module cache.
examples
client command
Calls a self-hosted Hop endpoint over TCP.
Calls a self-hosted Hop endpoint over TCP.
discovery command
Proves the full DNS-free discovery chain: a client resolves a domain by name, the TLS cert proves the domain (WebPKI), the served reach record self-certifies the address, and the WSS handshake confirms it, then a hops:// round trip runs over the WebSocket.
Proves the full DNS-free discovery chain: a client resolves a domain by name, the TLS cert proves the domain (WebPKI), the served reach record self-certifies the address, and the WSS handshake confirms it, then a hops:// round trip runs over the WebSocket.
echo command
The net/http-shaped DX, running on real hop-core over the C ABI.
The net/http-shaped DX, running on real hop-core over the C ABI.
server command
A standalone, self-hostable Hop endpoint (the two-process deployment shape).
A standalone, self-hostable Hop endpoint (the two-process deployment shape).
tcp command
Proves the Internet bearer: a server endpoint LISTENS on TCP, a client DIALS it over a real socket, and the hops:// round trip completes over TCP with real Noise.
Proves the Internet bearer: a server endpoint LISTENS on TCP, a client DIALS it over a real socket, and the hops:// round trip completes over TCP with real Noise.

Jump to

Keyboard shortcuts

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