diff --git a/go.mod b/go.mod index db9b789..8de151a 100644 --- a/go.mod +++ b/go.mod @@ -4,14 +4,10 @@ go 1.21 toolchain go1.21.0 -require code.icb4dc0.de/buildr/api v0.0.0-20230817151157-dbc0adad8f8b - require ( - github.com/golang/protobuf v1.5.3 // indirect - golang.org/x/net v0.14.0 // indirect - golang.org/x/sys v0.11.0 // indirect - golang.org/x/text v0.12.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230815205213-6bfd019c3878 // indirect - google.golang.org/grpc v1.57.0 // indirect - google.golang.org/protobuf v1.31.0 // indirect + code.icb4dc0.de/buildr/api v0.0.0-20230905195458-e4d230e5c7fd + connectrpc.com/connect v1.11.1 + github.com/tetratelabs/wazero v1.5.0 ) + +require google.golang.org/protobuf v1.31.0 // indirect diff --git a/go.sum b/go.sum index d7a6aa6..1bd3c61 100644 --- a/go.sum +++ b/go.sum @@ -1,23 +1,14 @@ -code.icb4dc0.de/buildr/api v0.0.0-20230817151157-dbc0adad8f8b h1:pZDHPJCMRw3UM1Jd0y7XnPZaDOzZ5du17EvW09mgb4g= -code.icb4dc0.de/buildr/api v0.0.0-20230817151157-dbc0adad8f8b/go.mod h1:Bxt+fw/9hH7/WESz3asYBIWPr81UlUMEleXJGTqX6ys= +code.icb4dc0.de/buildr/api v0.0.0-20230905195458-e4d230e5c7fd h1:N4WkVoqphjFXfFwfKpLcEtS5lBCfd9NVytUutiszQ90= +code.icb4dc0.de/buildr/api v0.0.0-20230905195458-e4d230e5c7fd/go.mod h1:eWSjeX25XbbNGKlxVoOf2a8V6xOCIaQh5W65T7nNcL8= +connectrpc.com/connect v1.11.1 h1:dqRwblixqkVh+OFBOOL1yIf1jS/yP0MSJLijRj29bFg= +connectrpc.com/connect v1.11.1/go.mod h1:3AGaO6RRGMx5IKFfqbe3hvK1NqLosFNP2BxDYTPmNPo= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= -golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +github.com/tetratelabs/wazero v1.5.0 h1:Yz3fZHivfDiZFUXnWMPUoiW7s8tC1sjdBtlJn08qYa0= +github.com/tetratelabs/wazero v1.5.0/go.mod h1:0U0G41+ochRKoPKCJlh0jMg1CHkyfK8kDqiirMmKY8A= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230815205213-6bfd019c3878 h1:lv6/DhyiFFGsmzxbsUUTOkN29II+zeWHxvT8Lpdxsv0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230815205213-6bfd019c3878/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= -google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw= -google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= diff --git a/wasirpc/host2module.go b/wasirpc/host2module.go new file mode 100644 index 0000000..9a4bc6d --- /dev/null +++ b/wasirpc/host2module.go @@ -0,0 +1,122 @@ +//go:build !wasi + +package wasirpc + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "net/http" + "sync" + + "connectrpc.com/connect" + "github.com/tetratelabs/wazero/api" +) + +var ( + ErrMissingMandatoryFunction = errors.New("missing mandatory function") + ErrUnmappedFunction = errors.New("unmapped function") + _ connect.HTTPClient = (*hostToModuleClient)(nil) +) + +const ( + allocateFuncName string = "allocate" + defaultIntegerSize = 32 +) + +type hostToModuleClient struct { + lock sync.Mutex + mod api.Module + alloc api.Function + functions map[string]api.Function +} + +func NewModuleClient(mod api.Module) (connect.HTTPClient, error) { + client := &hostToModuleClient{ + mod: mod, + } + + definitions := mod.ExportedFunctionDefinitions() + client.functions = make(map[string]api.Function, len(definitions)) + + for funcName, _ := range definitions { + client.functions[funcName] = mod.ExportedFunction(funcName) + } + + var ok bool + if client.alloc, ok = client.functions[allocateFuncName]; !ok { + return nil, fmt.Errorf("%w: %s", ErrMissingMandatoryFunction, allocateFuncName) + } + + return client, nil +} + +func (h *hostToModuleClient) Do(request *http.Request) (resp *http.Response, err error) { + h.lock.Lock() + defer h.lock.Unlock() + + exportedFunc, ok := h.functions[request.URL.Path] + if !ok { + return nil, fmt.Errorf("%w: %s", ErrUnmappedFunction, request.URL.Path) + } + + reqData, err := io.ReadAll(request.Body) + if err != nil { + return nil, err + } + + reqDataPtr, err := h.allocate(request.Context(), uint64(len(reqData))) + if err != nil { + return nil, err + } + + if !h.mod.Memory().Write(uint32(reqDataPtr), reqData) { + return nil, errors.New("failed to write to memory") + } + + results, err := exportedFunc.Call(request.Context(), reqDataPtr, uint64(len(reqData))) + if err != nil { + return nil, err + } + + resp = &http.Response{ + Status: "200 OK", + StatusCode: http.StatusOK, + Request: request, + } + + if len(results) == 0 { + return resp, nil + } + + if len(results) > 1 { + return nil, fmt.Errorf("unexpected number of results: %d", len(results)) + } + + respDataPtr, size := uint32(results[0]>>defaultIntegerSize), uint32(results[0]) + if respDataPtr == 0 { + resp.Body = io.NopCloser(bytes.NewReader(make([]byte, 0))) + return resp, nil + } + + respData, ok := h.mod.Memory().Read(respDataPtr, size) + if !ok { + return nil, errors.New("failed to read result data") + } + + resp.Body = io.NopCloser(bytes.NewReader(respData)) + resp.ContentLength = int64(len(respData)) + + return resp, nil +} + +func (h *hostToModuleClient) allocate(ctx context.Context, size uint64) (ptr uint64, err error) { + results, err := h.alloc.Call(ctx, size) + if err != nil { + return 0, err + } + + return results[0], nil +}