mirror of
https://github.com/zap-proto/go.git
synced 2026-10-30 15:05:00 +00:00
zapgen — interface (RPC) support: - Parse `interface` decls (brace + whitespace forms, one parser) with 1-based auto-assigned method ordinals; methods take ≤1 struct request and (after `returns`) ≤1 struct response, matching the spec README grammar. - desugar: `interface` opens a whitespace block; method lines pass through verbatim (no @N). Block headers now honored only at file scope, so a struct field NAMED `interface`/`struct` stays a field (H4 preserved). - emit: per interface a 1-based ordinal table, a typed Client over a Channel seam, and an abstract Handler + Dispatch<Iface> ordinal router, all over the new runtime call envelope. Method names exported. - New `rpc` package: the ZAP call envelope (BuildRequest/ParseRequest, BuildResponse/ParseResponse, Call, Response, status codes), byte-compatible with the other runtimes' transport envelopes (Version2 header + router msgType flags). Adds zap.NewBuilderV2. - examples/echo: generated service + end-to-end client↔dispatch↔handler test covering request/response, void, no-request, bare, handler-error, and unknown-ordinal paths. Golden fixtures for the interface codegen. cap — reconcile to SPEC v1.1: - B3 signature scope: sign SPEC §3 canonical bytes (Capability[0..164) || canonical(Caveats), Kind:u32||len:u32||Value), via Cap.CanonicalBytes — one definition shared by signer and verifier; excludes Sig and heap indirection so caps verify cross-language. CapID = Hash32(Canonical||Sig). - B2 delegation gate: define cross-cutting Permission bits (PermAttenuate 1<<32, PermAudit, PermRoot); enforce SPEC §2.3 step 3d in VerifyChain and at mint time in Attenuate (refuse a cap the verifier would reject). - B4/B6: VerifyRevocation is scheme-aware (dispatches on the tag byte); verifySig fails closed on SchemeReserved/unknown instead of falling back to ed25519. Verifier.VerifyRevocation added for PQ revocations. - spec_test.go covers all four. Rebrand finish + docs: - Zero capnp/Cap'n Proto and zero hanzo/avalabs/capnproto.org tokens in zap-native source (zap.go doc neutralized; cap doc-comment brand refs generalized). LLM.md refreshed: zapgen shipped (not "forthcoming"), rpc package, interface DSL, cap signature scope. Ignore the agents example binary. go build ./... and go test ./... -count=1 pass; race-clean on changed pkgs.
147 lines
4.5 KiB
Go
147 lines
4.5 KiB
Go
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
// Package rpc is the ZAP call envelope: the msgType + method + capability
|
|
// framing that carries an interface method call and its response over a
|
|
// ZAP transport.
|
|
//
|
|
// This is the canonical wire contract; the TypeScript runtime's
|
|
// envelope.ts mirrors these exact offsets and sizes byte-for-byte, so a
|
|
// request built here is decoded by the TS ParseRequest and a response
|
|
// built by the TS BuildResponse is decoded by ParseResponse here (and
|
|
// vice versa). The generated typed Client and abstract Server (zapgen
|
|
// `interface` emission) are thin wrappers over BuildRequest /
|
|
// ParseRequest / BuildResponse / ParseResponse.
|
|
//
|
|
// Request object (fixed size 28):
|
|
//
|
|
// Method u32 @0 which interface method (the .zap ordinal, 1-based)
|
|
// PromiseID u32 @4 caller-assigned id this call's answer resolves to
|
|
// Target u32 @8 promise this call pipelines off (0 = root)
|
|
// Cap bytes @12 opaque capability buffer (cap.Cap bytes; may be empty)
|
|
// Payload bytes @20 ZAP-encoded method params
|
|
//
|
|
// Response object (fixed size 20):
|
|
//
|
|
// Status u32 @0 200 ok, else an error code
|
|
// PromiseID u32 @4 echoes the request's PromiseID
|
|
// Body bytes @12 ZAP-encoded results (empty for a void method)
|
|
//
|
|
// Both envelopes are finished with header flags = MsgTypeRouterBase << 8
|
|
// so a ZAP transport routes them to this service's handler
|
|
// (msgType = flags >> 8).
|
|
package rpc
|
|
|
|
import zap "github.com/zap-proto/go"
|
|
|
|
// MsgTypeRouterBase is this service's ZAP message-type slot, carried in
|
|
// the high byte of the header flags word.
|
|
const MsgTypeRouterBase uint16 = 200
|
|
|
|
// NoTarget is the Target value for a call that does not pipeline off an
|
|
// earlier promise (the call targets the bootstrap object).
|
|
const NoTarget uint32 = 0
|
|
|
|
// Status codes carried in a Response.
|
|
const (
|
|
StatusOK uint32 = 200
|
|
StatusBadRequest uint32 = 400
|
|
StatusUnauthorized uint32 = 401
|
|
StatusForbidden uint32 = 403
|
|
StatusNotFound uint32 = 404
|
|
StatusInternal uint32 = 500
|
|
)
|
|
|
|
// Request field offsets.
|
|
const (
|
|
reqMethodOff = 0
|
|
reqPromiseIDOff = 4
|
|
reqTargetOff = 8
|
|
reqCapOff = 12
|
|
reqPayloadOff = 20
|
|
reqFixedSize = 28
|
|
)
|
|
|
|
// Response field offsets.
|
|
const (
|
|
respStatusOff = 0
|
|
respPromiseIDOff = 4
|
|
respBodyOff = 12
|
|
respFixedSize = 20
|
|
)
|
|
|
|
// Call is one outbound request's fields.
|
|
type Call struct {
|
|
Method uint32
|
|
PromiseID uint32
|
|
Target uint32
|
|
Cap []byte
|
|
Payload []byte
|
|
}
|
|
|
|
// BuildRequest encodes a Call into a router-tagged ZAP message. The
|
|
// header is v2 to match the transport default; the flags carry the
|
|
// router msgType so the transport dispatches it to this service.
|
|
func BuildRequest(c Call) []byte {
|
|
b := zap.NewBuilderV2(len(c.Cap) + len(c.Payload) + reqFixedSize + 64)
|
|
ob := b.StartObject(reqFixedSize)
|
|
ob.SetUint32(reqMethodOff, c.Method)
|
|
ob.SetUint32(reqPromiseIDOff, c.PromiseID)
|
|
ob.SetUint32(reqTargetOff, c.Target)
|
|
ob.SetBytes(reqCapOff, c.Cap)
|
|
ob.SetBytes(reqPayloadOff, c.Payload)
|
|
ob.FinishAsRoot()
|
|
return b.FinishWithFlags(MsgTypeRouterBase << 8)
|
|
}
|
|
|
|
// ParseRequest decodes a router-tagged request message into a Call. The
|
|
// returned Cap and Payload slices alias the input buffer; copy them if
|
|
// you retain them past the buffer's lifetime.
|
|
func ParseRequest(msg []byte) (Call, error) {
|
|
m, err := zap.Parse(msg)
|
|
if err != nil {
|
|
return Call{}, err
|
|
}
|
|
r := m.Root()
|
|
return Call{
|
|
Method: r.Uint32(reqMethodOff),
|
|
PromiseID: r.Uint32(reqPromiseIDOff),
|
|
Target: r.Uint32(reqTargetOff),
|
|
Cap: r.Bytes(reqCapOff),
|
|
Payload: r.Bytes(reqPayloadOff),
|
|
}, nil
|
|
}
|
|
|
|
// Response is a decoded response envelope.
|
|
type Response struct {
|
|
Status uint32
|
|
PromiseID uint32
|
|
Body []byte
|
|
}
|
|
|
|
// BuildResponse encodes a status + body into a router-tagged response.
|
|
func BuildResponse(status, promiseID uint32, body []byte) []byte {
|
|
b := zap.NewBuilderV2(len(body) + respFixedSize + 64)
|
|
ob := b.StartObject(respFixedSize)
|
|
ob.SetUint32(respStatusOff, status)
|
|
ob.SetUint32(respPromiseIDOff, promiseID)
|
|
ob.SetBytes(respBodyOff, body)
|
|
ob.FinishAsRoot()
|
|
return b.FinishWithFlags(MsgTypeRouterBase << 8)
|
|
}
|
|
|
|
// ParseResponse decodes a router-tagged response message. The returned
|
|
// Body slice aliases the input buffer.
|
|
func ParseResponse(msg []byte) (Response, error) {
|
|
m, err := zap.Parse(msg)
|
|
if err != nil {
|
|
return Response{}, err
|
|
}
|
|
r := m.Root()
|
|
return Response{
|
|
Status: r.Uint32(respStatusOff),
|
|
PromiseID: r.Uint32(respPromiseIDOff),
|
|
Body: r.Bytes(respBodyOff),
|
|
}, nil
|
|
}
|