mirror of
https://github.com/hanzoai/zap.git
synced 2026-10-30 18:17:49 +00:00
The Rust side was server-only and could not read a single Go RPC envelope: `Header::decode` and `Message::parse` accepted wire version 1, and every `zap-proto/go` envelope is version 2 (`NewBuilderV2`). It also declared the crate name `hanzo-zap`, which a DIFFERENT codebase (hanzo/net/hanzo-zap) already publishes at 0.6.75 with real consumers. Crate identity hanzo-zap -> zap-proto, imported as `zap` — the same split the Go module has (github.com/zap-proto/go, package zap). crates.io zap-proto was ours at 1.0.0 (a reservation), so 1.0.1 is latest+1 patch. `hanzo-zap` stays with hanzo/net, which published its max version and has the pins. Wire wire: accept version 1 AND 2 (VERSION2); drop write_frame_async. codec: the data segment — a line-for-line port of Go's zap.go + builder.go, including its adversarial bounds rules. rpc: the 28-byte call envelope, the 20-byte answer, and promise pipelining. Pipeliner is now a Dispatch that PARKS a dependent whose target has not resolved and answers it when it does — no drain() side channel, which was a second way to get a response out. transport: Conn::connect / Server::listen over TCP or a unix socket (the address shape picks the socket, as in zip), [u32 len][dir] [envelope] framing identical to Go's, PromiseID correlation, bounded in-flight dispatch. The connection is symmetric, so a server holding its accepted Conn can CALL the client — the push path that a strict request/response handler cannot have. Deleted server.rs: it framed raw ZAP messages with no direction tag, a second incompatible wire no Go peer speaks. Deleted the echo daemon binary with it. Proof tests/crosswire.rs — golden hex regenerated from zap-proto/[email protected]'s own BuildRequest/BuildResponse; Rust must parse AND emit those bytes. tests/interop.rs — builds interop/main.go (a real transport.Listen server) and drives it from Conn over a unix socket. Reverting the one version condition turns both red with InvalidVersion(2). make test is the gate and .github/workflows/test.yml runs it, with Go installed so the interop test can never silently skip. Session crypto is still absent: local-over-unix-socket is correct, remote stays on HTTPS. README documents what closing it takes. Co-authored-by: Hanzo Dev <[email protected]>
57 lines
1.3 KiB
Go
57 lines
1.3 KiB
Go
// A live Go ZAP peer, so the Rust runtime is proved against the REAL
|
|
// reference implementation rather than a hand-written fixture.
|
|
//
|
|
// go run ./interop /path/to/zap.sock
|
|
//
|
|
// It serves one method — ordinal 1, "upper" — over the canonical
|
|
// transport framing, prints "ready" once bound, and runs until killed.
|
|
// rust/tests/interop.rs drives it.
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/signal"
|
|
"strings"
|
|
"syscall"
|
|
|
|
"github.com/zap-proto/go/rpc"
|
|
"github.com/zap-proto/go/transport"
|
|
)
|
|
|
|
func main() {
|
|
if len(os.Args) < 2 {
|
|
fmt.Fprintln(os.Stderr, "usage: interop <addr>")
|
|
os.Exit(2)
|
|
}
|
|
addr := os.Args[1]
|
|
network := "tcp"
|
|
if strings.HasPrefix(addr, "/") || strings.HasPrefix(addr, "./") {
|
|
network = "unix"
|
|
}
|
|
|
|
srv, err := transport.Listen(network, addr, func(env []byte) ([]byte, error) {
|
|
call, err := rpc.ParseRequest(env)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if call.Method != 1 {
|
|
return rpc.BuildResponse(rpc.StatusNotFound, call.PromiseID, nil), nil
|
|
}
|
|
up := strings.ToUpper(string(call.Payload))
|
|
return rpc.BuildResponse(rpc.StatusOK, call.PromiseID, []byte(up)), nil
|
|
})
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, "listen:", err)
|
|
os.Exit(1)
|
|
}
|
|
defer srv.Close()
|
|
|
|
fmt.Println("ready", srv.Addr().String())
|
|
os.Stdout.Sync()
|
|
|
|
sig := make(chan os.Signal, 1)
|
|
signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
|
|
<-sig
|
|
}
|