Plugin contract
Use the independent SDK generated by the scaffold rather than implementing exports and pointer ownership manually. This reference explains the host boundary and compatibility rules.
Package and manifest
A package contains a manifest, Wasm module, and optional schema. Required fields are id, version, abi_version:1, module, protocols, memory_mib, and call_timeout_ms. Optional fields include tcp_ports/udp_ports, schema, sha256, detection, capabilities, and host_schema_version.
| Limit | Range |
|---|---|
| Linear memory per package instance | 1–64 MiB; Go template 16, Rust template 8 |
| Data call wall time | 1–1000 ms; Go template 500, Rust template 100 |
| Loaded set | At most 32 packages; unique protocols; module size at most 8 MiB |
| Compilation / initialization | 10 seconds / separate 1-second budget |
| Schema | Regular file, at most 1 MiB, no host-field overrides |
The host currently supports host_schema_version 0..6. Higher declarations reject loading. See protocol support for aggregate generation budgets.
Stream callbacks
| ABI export | Signature |
|---|---|
nfs_abi_version | () -> i32, returns 1 |
nfs_alloc | (size:i32) -> ptr:i32 |
nfs_free | (ptr:i32,size:i32) |
nfs_process | (kind:i32,handle:i64,originator:i32,timestamp_ns:i64,ptr:i32,len:i32) -> i32 |
Kinds are OPEN=1, DATA=2, GAP=3, CLOSE=4, TIMEOUT=5, DATAGRAM=6, QUIC_STREAM=7. Direction is relative to the connection originator, not the local process. DATA contains ordered bytes without message boundaries; UDP delivers one datagram. Handles are instance-local and callbacks are serial within an instance.
Input pointers are valid only during a callback; copy retained data. The host may reuse a buffer for equal-length input. Plugins must not free it or assume each callback allocates again. Empty input has zero ptr/len. Nonzero return values retire the current stream.
Record output
nfs.emit(handle:i64,ptr:i32,len:i32) -> i32 accepts JSON containing only type, fields, incomplete, and truncated. The host injects time, connection, addresses, service, and process metadata. Fields must match schema; declare raw content sensitive.
{"type":"acme_tcp.message","fields":{"acme_tcp.value":"hello","acme_tcp.request":true},"incomplete":false,"truncated":false}Each callback allows at most 512 records / 4 MiB, with at most 1 MiB per record. Output is staged until the whole callback and every record succeed. Panic, trap, and invalid output cannot leave a partially accepted callback.
Fault isolation
No host filesystem, network, environment, or stdio access is granted. Restricted WASI imports grant neither directories nor sockets. Bounded pure functions support decompression, WebSocket accept, and static QPACK, without arbitrary I/O.
Timeout, trap, memory violation, or invalid output rebuilds the instance and retires active streams of the same module/shard as incomplete. Other modules/shards continue; new connections can use the replacement. Isolation is not fully per connection. Parsers must still bound buffers and correlation queues.
Detection and version changes
Detection uses bounded-offset hex signatures with optional direction and masks. Ambiguity rejects detection; explicit-parser errors do not trigger guessing another protocol. UDP uses port routing.
A compatible reload validates the complete candidate set before switching new connections. Existing connections retain their generation. Schema/sensitivity changes need coordinated restarts. Recompile older Go projects after SDK updates, and reassess timeout budgets retained in older manifests.