debuglet

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Oct 19, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

Documentation

Overview

Package debuglet is the Go client library for writing debuglets (WASM measurement programs) that run on the Debuglet executor.

A debuglet is a standard WASI command module: write a normal `func main()`, build it with `GOOS=wasip1 GOARCH=wasm`, and the executor streams whatever you print to stdout/stderr back to the user. The executor passes the user's command-line arguments verbatim as WASI argv, so `flag.CommandLine.Parse(os.Args)` reads them directly (note: there is no program name at os.Args[0]).

This package wraps the raw host imports exported by the executor engine (internal/executor/debuglet/wasm/host_functions.go) so that debuglet authors do not have to deal with //go:wasmimport declarations or unsafe pointer math. Instead of juggling 32-bit memory offsets, you work with a small Conn type:

conn, err := debuglet.ConnectTCP("example.com:80")
if err != nil { ... }
defer conn.Close()
if err := conn.Write([]byte("GET / HTTP/1.0\r\n\r\n")); err != nil { ... }
body, err := io.ReadAll(conn) // Read returns io.EOF when the peer closes
if err != nil { ... }
fmt.Println(string(body))

Conn.Read follows the io.Reader contract: short reads are normal, and a TCP or TLS stream (including connections from AcceptTCP) reports the peer's clean close as (0, io.EOF). UDP and ICMP reads return (0, nil) for an empty datagram, never io.EOF. Reads of an empty buffer return (0, nil) without a host call. Conn.Write keeps its Write([]byte) error signature; it is not an io.Writer.

One host call transfers at most MaxIOBytes bytes. Read therefore fills a larger buffer only up to that bound, which is an ordinary short read. Write splits a longer stream payload into consecutive calls, and rejects a longer datagram rather than sending a truncated one.

In the legacy Conn API, only a clean TCP/TLS end of stream surfaces as a Go error. Other host-side failures reach the caller as neither a value nor an error: a refused or policy-denied destination, a socket error other than EOF, an invalid handle and an invalid buffer all abort the guest as WASM traps, so the job ends with whatever the guest printed before the call.

Dial and DialTimeout use the optional debuglet_io_v1 extension and return Socket instead. Its Read and Write preserve byte counts alongside typed transport errors, so a diagnostic can handle a timeout or reset and continue. Invalid memory and never-issued handles still trap.

On non-wasip1 platforms the package compiles against panic stubs (debuglet_stub.go) so the rest of the module builds and tests on the host; every SDK call panics off-target. The real implementation is in debuglet_wasip1.go.

This stub lets the package (and therefore the whole module) build and test on the host. The host imports it shadows are only available inside a wasip1 guest, so calling these off-target panics. Build a debuglet with `GOOS=wasip1 GOARCH=wasm` to get the real implementation.

Index

Constants

View Source
const MaxExperimentMetadata = experimentwire.MaxExperimentMetadata

MaxExperimentMetadata is the opaque metadata allowance for one participant.

View Source
const MaxIOBytes = 8192

MaxIOBytes is the number of bytes one host call transfers. It belongs to the guest ABI rather than to this package: the host reads or writes at most this many bytes of the buffer it is given, whatever the buffer's length is.

Variables

View Source
var ErrConnect = errors.New("debuglet: connect failed")

ErrConnect reports a connection the SDK or the host rejected as a value: an empty address, or a negative handle. A destination that is refused, outside the job's policy, or otherwise unreachable aborts the guest inside the host call instead of returning this error.

View Source
var ErrExperimentLate = errors.New("debuglet: experiment start already passed")

ErrExperimentLate means the agreed start passed before the guest could wait.

View Source
var ErrExperimentUnavailable = errors.New("debuglet: experiment readiness unavailable or refused")
View Source
var ErrTooLarge = errors.New("debuglet: payload exceeds the host transfer bound")

ErrTooLarge reports a datagram longer than MaxIOBytes. Splitting it would change one datagram into several, so Write rejects it instead.

Functions

func ListenAddr

func ListenAddr() (string, error)

ListenAddr returns the public "host:port" address of the debuglet's TCP listener, i.e. where clients should connect. It returns an error when no listener has been started (the executor has no public host configured).

func ListenUDPAddr

func ListenUDPAddr() (string, error)

ListenUDPAddr returns the public "host:port" address of the debuglet's UDP listener, i.e. where clients should send datagrams. It returns an error when no UDP listener has been started (the executor has no public host configured).

func ReadFromUDP

func ReadFromUDP(buf []byte) (n int, from string, err error)

ReadFromUDP blocks until a single datagram arrives on the debuglet's UDP listener and returns the number of bytes read into buf, the sender's "host:port" address, and any error. It mirrors Go's PacketConn.ReadFrom: the call blocks until a datagram is available or the listener is closed.

func Ready added in v0.3.0

func Ready(ctx context.Context, metadata []byte) (experimentwire.Experiment, error)

Ready publishes immutable metadata for this run and waits for every run in its submitted batch. Listeners and application setup should be ready first. The host supplies run identity and authorization; metadata grants no network access. Waiting is limited to 30 seconds, the caller's deadline and the run's lifetime. Cancellation is checked between bounded host calls (at most one second each).

func WaitStart added in v0.3.0

func WaitStart(ctx context.Context, experiment experimentwire.Experiment) error

WaitStart waits for the agreed wall-clock time. It rejects a start already in the past; callers should record their observed start themselves. This helper cannot guarantee synchronized clocks or simultaneous execution across hosts.

Types

type Conn

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

Conn is a connection handle returned by the Connect* and AcceptTCP helpers. It wraps the integer socket handle owned by the host and routes Write/Read/Close to the correct host functions for its transport. TLS and accepted TCP connections are plain TCP streams to the SDK.

func AcceptTCP

func AcceptTCP() (*Conn, error)

AcceptTCP blocks until the host's TCP listener accepts one inbound connection and returns it. Used by server-style debuglets (e.g. an echo or throughput sink). Requires the executor's TCP listener to be enabled.

func ConnectICMP4

func ConnectICMP4(addr string) (*Conn, error)

ConnectICMP4 opens a raw ICMPv4 socket to addr (an IPv4 address, no port). Use it to send/receive ICMP echo packets, e.g. for a ping probe.

func ConnectTCP

func ConnectTCP(addr string) (*Conn, error)

ConnectTCP dials a plaintext TCP connection to addr ("host:port").

func ConnectTLS

func ConnectTLS(addr string) (*Conn, error)

ConnectTLS dials a TLS-over-TCP connection to addr ("host:port"). The host performs the TLS handshake; reads and writes carry plaintext application data.

func ConnectUDP

func ConnectUDP(addr string) (*Conn, error)

ConnectUDP dials a UDP connection to addr ("host:port"). The returned Conn sends and receives datagrams on the connected socket; no UDP listener is required to use it.

func (*Conn) Close

func (c *Conn) Close() error

func (*Conn) Drain

func (c *Conn) Drain() error

func (*Conn) Handle

func (c *Conn) Handle() int32

Handle returns the raw host socket handle. Most debuglets do not need this; it is exposed for diagnostics and advanced use.

func (*Conn) Read

func (c *Conn) Read(b []byte) (int, error)

func (*Conn) RemoteAddr

func (c *Conn) RemoteAddr() (string, error)

func (*Conn) Write

func (c *Conn) Write(b []byte) error

type IOError added in v0.3.0

type IOError uint32

IOError is a stable transport outcome from the debuglet_io_v1 extension. Use errors.Is(err, ErrTimeout), for example, to handle a wrapped outcome.

const (
	ErrTimeout     IOError = guestio.Timeout
	ErrReset       IOError = guestio.Reset
	ErrClosed      IOError = guestio.Closed
	ErrRefused     IOError = guestio.Refused
	ErrDenied      IOError = guestio.Denied
	ErrIO          IOError = guestio.Failure
	ErrCanceled    IOError = guestio.Canceled
	ErrUnsupported IOError = guestio.Unsupported
)

func (IOError) Error added in v0.3.0

func (e IOError) Error() string

func (IOError) Temporary added in v0.3.0

func (e IOError) Temporary() bool

func (IOError) Timeout added in v0.3.0

func (e IOError) Timeout() bool

type Socket added in v0.3.0

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

Socket is an io.ReadWriteCloser with recoverable transport errors. Unlike Conn, its Write method returns a byte count. It requires debuglet_io_v1; existing Connect* functions and compiled guests keep their legacy behavior.

func AcceptTCPTimeout added in v0.3.0

func AcceptTCPTimeout(timeout time.Duration) (*Socket, error)

AcceptTCPTimeout accepts an admitted connection on the run's TCP listener. The timeout bounds the whole accept, including refused peers. Zero uses the run's remaining lifetime; a negative timeout expires immediately. ErrTimeout leaves the listener usable for another call. The returned Socket has its own read/write deadlines and remains open until Close or the run ends.

This requires the accept_tcp export in debuglet_io_v1 and a run with TCP listening enabled. Older hosts without that export fail to link the guest. Use short timeouts in event loops: guest goroutines cannot make progress while the host is blocked accepting a connection.

func Dial added in v0.3.0

func Dial(network, address string) (*Socket, error)

Dial connects using "tcp", "tls", "udp" or "ip4:icmp". Policy and resource admission are identical to Connect*. The run's lifetime bounds the call.

func DialTimeout added in v0.3.0

func DialTimeout(network, address string, timeout time.Duration) (*Socket, error)

DialTimeout also bounds resolution, connection and TLS handshake. Zero uses the run's remaining lifetime; a negative timeout expires immediately.

func (*Socket) Close added in v0.3.0

func (*Socket) Close() error

func (*Socket) Read added in v0.3.0

func (*Socket) Read([]byte) (int, error)

func (*Socket) SetReadDeadline added in v0.3.0

func (*Socket) SetReadDeadline(time.Time) error

func (*Socket) SetWriteDeadline added in v0.3.0

func (*Socket) SetWriteDeadline(time.Time) error

func (*Socket) Write added in v0.3.0

func (*Socket) Write([]byte) (int, error)

Jump to

Keyboard shortcuts

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