mirror of
https://github.com/zap-proto/go.git
synced 2026-10-30 15:05:00 +00:00
zap-proto/go had the wire codec + rpc envelope/pipeline but no network transport — this fills that gap so hanzo AND lux consume one identical RPC plumbing instead of forking luxfi/zap's CGO-tainted node layer. Thin by design: the codegen already emits typed clients against a one-method Channel (Call(envelope) (rpc.Response, error)) and server-side DispatchX functions. This is the drop-in NETWORK realisation of those exact contracts — *Conn implements Channel, Listen serves a Dispatch — so generated code is byte-for-byte unchanged; only the bytes now cross a socket. - transport.go: Conn (symmetric: calls + serves on one socket), length+dir framed [len][dir][envelope], PromiseID correlation from the envelope itself (no transport-level id). TCP writev fast path; 64MiB frame cap. - listen.go: Server accept loop; unix stale-socket reclaim. - Pure Go, zero CGO. QUIC + PQ X-Wing (X25519MLKEM768) land next behind the same Conn/Dispatch contract. Proven: TCP + Unix round-trips, 64-way concurrent PromiseID correlation, clean close (ErrClosed), AND the real generated EchoClient + DispatchEcho running unchanged over a live socket. No protobuf, no gRPC in the path.
148 lines
3.7 KiB
Go
148 lines
3.7 KiB
Go
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package transport
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/zap-proto/go/rpc"
|
|
)
|
|
|
|
// echoDispatch echoes the request payload back as the response body. It is
|
|
// the network-side equivalent of the generated DispatchX(handler, env).
|
|
func echoDispatch(envelope []byte) ([]byte, error) {
|
|
call, err := rpc.ParseRequest(envelope)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return rpc.BuildResponse(rpc.StatusOK, call.PromiseID, call.Payload), nil
|
|
}
|
|
|
|
// shortSock returns a short Unix-socket path (macOS caps sun_path at 104
|
|
// bytes, so t.TempDir() under /var/folders is too long).
|
|
func shortSock(t *testing.T) string {
|
|
t.Helper()
|
|
dir, err := os.MkdirTemp("/tmp", "zt")
|
|
if err != nil {
|
|
t.Fatalf("mkdtemp: %v", err)
|
|
}
|
|
t.Cleanup(func() { _ = os.RemoveAll(dir) })
|
|
return filepath.Join(dir, "s")
|
|
}
|
|
|
|
func roundTrip(t *testing.T, network, addr string) {
|
|
t.Helper()
|
|
conn, err := Dial(network, addr)
|
|
if err != nil {
|
|
t.Fatalf("Dial: %v", err)
|
|
}
|
|
defer conn.Close()
|
|
|
|
req := rpc.BuildRequest(rpc.Call{
|
|
Method: 1,
|
|
PromiseID: conn.NextPromiseID(),
|
|
Payload: []byte("hello-zap-no-protobuf"),
|
|
})
|
|
resp, err := conn.Call(req)
|
|
if err != nil {
|
|
t.Fatalf("Call: %v", err)
|
|
}
|
|
if resp.Status != rpc.StatusOK {
|
|
t.Fatalf("status = %d, want OK", resp.Status)
|
|
}
|
|
if string(resp.Body) != "hello-zap-no-protobuf" {
|
|
t.Fatalf("body = %q, want %q", resp.Body, "hello-zap-no-protobuf")
|
|
}
|
|
}
|
|
|
|
// TestRoundTrip_TCP proves a request/response over a real TCP socket.
|
|
func TestRoundTrip_TCP(t *testing.T) {
|
|
srv, err := Listen("tcp", "127.0.0.1:0", echoDispatch)
|
|
if err != nil {
|
|
t.Fatalf("Listen: %v", err)
|
|
}
|
|
defer srv.Close()
|
|
roundTrip(t, "tcp", srv.Addr().String())
|
|
}
|
|
|
|
// TestRoundTrip_Unix proves the same over a Unix-domain socket — the
|
|
// transport mount and other local-IPC services need.
|
|
func TestRoundTrip_Unix(t *testing.T) {
|
|
sock := shortSock(t)
|
|
srv, err := Listen("unix", sock, echoDispatch)
|
|
if err != nil {
|
|
t.Fatalf("Listen(unix): %v", err)
|
|
}
|
|
defer srv.Close()
|
|
roundTrip(t, "unix", sock)
|
|
}
|
|
|
|
// TestConcurrentCalls proves PromiseID correlation under load: 64 calls in
|
|
// flight at once, each must get back exactly its own distinct payload.
|
|
func TestConcurrentCalls(t *testing.T) {
|
|
srv, err := Listen("tcp", "127.0.0.1:0", echoDispatch)
|
|
if err != nil {
|
|
t.Fatalf("Listen: %v", err)
|
|
}
|
|
defer srv.Close()
|
|
|
|
conn, err := Dial("tcp", srv.Addr().String())
|
|
if err != nil {
|
|
t.Fatalf("Dial: %v", err)
|
|
}
|
|
defer conn.Close()
|
|
|
|
const n = 64
|
|
var wg sync.WaitGroup
|
|
errs := make(chan error, n)
|
|
for i := 0; i < n; i++ {
|
|
wg.Add(1)
|
|
go func(i int) {
|
|
defer wg.Done()
|
|
want := fmt.Sprintf("payload-%d", i)
|
|
req := rpc.BuildRequest(rpc.Call{
|
|
Method: 1,
|
|
PromiseID: conn.NextPromiseID(),
|
|
Payload: []byte(want),
|
|
})
|
|
resp, err := conn.Call(req)
|
|
if err != nil {
|
|
errs <- fmt.Errorf("call %d: %w", i, err)
|
|
return
|
|
}
|
|
if string(resp.Body) != want {
|
|
errs <- fmt.Errorf("call %d: body = %q, want %q", i, resp.Body, want)
|
|
}
|
|
}(i)
|
|
}
|
|
wg.Wait()
|
|
close(errs)
|
|
for err := range errs {
|
|
t.Error(err)
|
|
}
|
|
}
|
|
|
|
// TestCallAfterClose proves an in-flight Call fails cleanly (not hangs) when
|
|
// the connection is torn down.
|
|
func TestCallAfterClose(t *testing.T) {
|
|
srv, err := Listen("tcp", "127.0.0.1:0", echoDispatch)
|
|
if err != nil {
|
|
t.Fatalf("Listen: %v", err)
|
|
}
|
|
defer srv.Close()
|
|
conn, err := Dial("tcp", srv.Addr().String())
|
|
if err != nil {
|
|
t.Fatalf("Dial: %v", err)
|
|
}
|
|
conn.Close()
|
|
req := rpc.BuildRequest(rpc.Call{Method: 1, PromiseID: conn.NextPromiseID()})
|
|
if _, err := conn.Call(req); err != ErrClosed {
|
|
t.Fatalf("Call after Close = %v, want ErrClosed", err)
|
|
}
|
|
}
|