插件契约
优先通过脚手架携带的独立 SDK 实现 ABI,避免手动管理指针和导出。此页用于理解宿主边界及排查兼容问题。
包和 manifest
一个包包含 manifest、Wasm 和可选 schema。必填项为 id、version、abi_version:1、module、protocols、memory_mib 和 call_timeout_ms。可选 tcp_ports/udp_ports、schema、sha256、detection、capabilities、host_schema_version。
| 限制 | 范围 |
|---|---|
| 单包线性内存 | 1–64 MiB;Go 模板 16,Rust 模板 8 |
| 单次数据调用 | 1–1000 ms 墙钟;Go 模板 500,Rust 模板 100 |
| 加载集合 | 最多 32 包,协议名不可重复,模块文件最多 8 MiB |
| 编译 / 初始化 | 10 秒 / 独立 1 秒预算 |
| schema | 普通文件,最多 1 MiB,不可覆盖宿主字段 |
当前宿主支持 host_schema_version 0..6。声明更高版本会拒绝加载。总内存预算及旧代际限制见协议支持。
流回调
| ABI 导出 | 签名 |
|---|---|
nfs_abi_version | () -> i32,返回 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 |
kind 为 OPEN=1、DATA=2、GAP=3、CLOSE=4、TIMEOUT=5、DATAGRAM=6、QUIC_STREAM=7。方向相对连接发起方,不是本地进程。DATA 是有序字节,不保证消息边界;UDP 每次一个报文。句柄只在实例内有效,同实例回调串行。
输入指针仅在回调内可用,需保留时复制。宿主可以复用长度相同的输入缓冲,插件不得自行释放输入,也不能假设每次调用都重新分配。空输入 ptr/len 均为 0。非零返回值退休当前流。
记录输出
nfs.emit(handle:i64,ptr:i32,len:i32) -> i32 接受 JSON;只允许 type、fields、incomplete、truncated。宿主注入时间、连接、地址、服务和进程信息。字段必须符合 schema,原文应声明 sensitive。
{"type":"acme_tcp.message","fields":{"acme_tcp.value":"hello","acme_tcp.request":true},"incomplete":false,"truncated":false}每回调最多 512 条 / 4 MiB,每条最多 1 MiB。输出暂存到整次回调成功且全部记录合法后才提交;panic/trap/非法输出不能留下半次业务输出。
故障隔离
没有宿主文件、网络、环境或 stdio 权限。受限 WASI 运行库不授予目录或 socket。宿主提供有界纯函数用于解压、WebSocket accept 和静态 QPACK,不提供任意 I/O。
超时、trap、内存或输出越界会重建该实例,退休同模块、同 shard 的活动流并标记不完整。其他模块/shard 可继续,新连接使用重建实例;这不是每连接完全独立的执行隔离。协议自身仍必须限制关联队列和缓冲。
识别和版本切换
detection 使用有限偏移的十六进制签名,可限定方向和 mask;命中歧义会拒绝,不在错误帧后猜测另一个协议。UDP 按端口路由。
兼容 reload 在完整候选集验证后仅切换新连接;老连接固定原版本。schema/敏感性变更需要协调重启。旧 Go 项目升级 SDK 后需重新编译,并按实际负载复核旧 manifest 中的超时预算。