diff --git a/src/main/java/io/roastedroot/proxywasm/impl/Imports.java b/src/main/java/io/roastedroot/proxywasm/impl/Imports.java index 1c41873..61c6027 100644 --- a/src/main/java/io/roastedroot/proxywasm/impl/Imports.java +++ b/src/main/java/io/roastedroot/proxywasm/impl/Imports.java @@ -862,4 +862,31 @@ int proxyDispatchHttpCall( return e.result().getValue(); } } + + @WasmExport + int proxyCallForeignFunction( + int nameDataPtr, + int nameSize, + int argumentDataPtr, + int argumentSize, + int returnResultsPtr, + int returnResultsSizePtr) { + try { + var name = string(readMemory(nameDataPtr, nameSize)); + var argument = readMemory(argumentDataPtr, argumentSize); + var result = handler.callForeignFunction(name, argument); + + // Allocate memory in the WebAssembly instance + int addr = malloc(result.length); + putMemory(addr, result); + // Write the address to the return pointer + putUint32(returnResultsPtr, addr); + // Write the length to the return size pointer + putUint32(returnResultsSizePtr, result.length); + return WasmResult.OK.getValue(); + + } catch (WasmException e) { + return e.result().getValue(); + } + } } diff --git a/src/main/java/io/roastedroot/proxywasm/v1/ChainedHandler.java b/src/main/java/io/roastedroot/proxywasm/v1/ChainedHandler.java index 76c8f38..98aaf7a 100644 --- a/src/main/java/io/roastedroot/proxywasm/v1/ChainedHandler.java +++ b/src/main/java/io/roastedroot/proxywasm/v1/ChainedHandler.java @@ -265,4 +265,20 @@ public int httpCall( throws WasmException { return next().httpCall(uri, headers, body, trailers, timeout); } + + @Override + public int dispatchHttpCall( + String upstreamName, + HashMap headers, + byte[] body, + HashMap trailers, + int timeoutMilliseconds) + throws WasmException { + return next().dispatchHttpCall(upstreamName, headers, body, trailers, timeoutMilliseconds); + } + + @Override + public byte[] callForeignFunction(String name, byte[] bytes) throws WasmException { + return next().callForeignFunction(name, bytes); + } } diff --git a/src/main/java/io/roastedroot/proxywasm/v1/ForeignFunction.java b/src/main/java/io/roastedroot/proxywasm/v1/ForeignFunction.java new file mode 100644 index 0000000..fd75763 --- /dev/null +++ b/src/main/java/io/roastedroot/proxywasm/v1/ForeignFunction.java @@ -0,0 +1,5 @@ +package io.roastedroot.proxywasm.v1; + +public interface ForeignFunction { + byte[] apply(byte[] data); +} diff --git a/src/main/java/io/roastedroot/proxywasm/v1/Handler.java b/src/main/java/io/roastedroot/proxywasm/v1/Handler.java index 3258ccb..429b5ef 100644 --- a/src/main/java/io/roastedroot/proxywasm/v1/Handler.java +++ b/src/main/java/io/roastedroot/proxywasm/v1/Handler.java @@ -406,4 +406,8 @@ default int dispatchHttpCall( throws WasmException { throw new WasmException(WasmResult.UNIMPLEMENTED); } + + default byte[] callForeignFunction(String name, byte[] bytes) throws WasmException { + throw new WasmException(WasmResult.NOT_FOUND); + } } diff --git a/src/main/java/io/roastedroot/proxywasm/v1/ProxyWasm.java b/src/main/java/io/roastedroot/proxywasm/v1/ProxyWasm.java index 53b3f90..16c3db7 100644 --- a/src/main/java/io/roastedroot/proxywasm/v1/ProxyWasm.java +++ b/src/main/java/io/roastedroot/proxywasm/v1/ProxyWasm.java @@ -43,6 +43,7 @@ public final class ProxyWasm implements Closeable { private Map httpCallResponseHeaders; private Map httpCallResponseTrailers; private byte[] httpCallResponseBody; + private HashMap foreignFunctions = new HashMap<>(); private ProxyWasm(Builder other) throws StartException { this.vmConfig = other.vmConfig; @@ -169,6 +170,15 @@ public Map getHttpCallResponseTrailers() { public byte[] getHttpCallResponseBody() { return httpCallResponseBody; } + + @Override + public byte[] callForeignFunction(String name, byte[] bytes) throws WasmException { + ForeignFunction func = foreignFunctions.get(name); + if (func == null) { + throw new WasmException(WasmResult.NOT_FOUND); + } + return func.apply(bytes); + } }; } @@ -266,6 +276,10 @@ public int contextId() { return pluginContext.id(); } + public void registerForeignFunction(String name, ForeignFunction func) { + foreignFunctions.put(name, func); + } + public static class Builder implements Cloneable { private Exports exports; diff --git a/src/test/go-examples/foreign_call_on_tick/README.md b/src/test/go-examples/foreign_call_on_tick/README.md new file mode 100644 index 0000000..3c76613 --- /dev/null +++ b/src/test/go-examples/foreign_call_on_tick/README.md @@ -0,0 +1,4 @@ +## Attribution + +This example originally came from: +https://github.com/proxy-wasm/proxy-wasm-go-sdk/tree/main/examples/foreign_call_on_tick diff --git a/src/test/go-examples/foreign_call_on_tick/go.mod b/src/test/go-examples/foreign_call_on_tick/go.mod new file mode 100644 index 0000000..747ebce --- /dev/null +++ b/src/test/go-examples/foreign_call_on_tick/go.mod @@ -0,0 +1,5 @@ +module github.com/proxy-wasm/proxy-wasm-go-sdk/examples/foreign_call_on_tick + +go 1.24 + +require github.com/proxy-wasm/proxy-wasm-go-sdk v0.0.0-20250212164326-ab4161dcf924 diff --git a/src/test/go-examples/foreign_call_on_tick/go.sum b/src/test/go-examples/foreign_call_on_tick/go.sum new file mode 100644 index 0000000..3ddb896 --- /dev/null +++ b/src/test/go-examples/foreign_call_on_tick/go.sum @@ -0,0 +1,10 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/proxy-wasm/proxy-wasm-go-sdk v0.0.0-20250212164326-ab4161dcf924 h1:wTcK6gcyTKJMeDka69AMjZYvisdI8CBXzTEfZ+2pOxI= +github.com/proxy-wasm/proxy-wasm-go-sdk v0.0.0-20250212164326-ab4161dcf924/go.mod h1:9mBRvh8I6Td6sg3CwEY+zGFE4DKaIoieCaca1kQnDBE= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/src/test/go-examples/foreign_call_on_tick/main.go b/src/test/go-examples/foreign_call_on_tick/main.go new file mode 100644 index 0000000..65d33de --- /dev/null +++ b/src/test/go-examples/foreign_call_on_tick/main.go @@ -0,0 +1,69 @@ +// Copyright 2020-2024 Tetrate +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "encoding/hex" + + "github.com/proxy-wasm/proxy-wasm-go-sdk/proxywasm" + "github.com/proxy-wasm/proxy-wasm-go-sdk/proxywasm/types" +) + +const tickMilliseconds uint32 = 1 + +func main() {} +func init() { + proxywasm.SetVMContext(&vmContext{}) +} + +// vmContext implements types.VMContext. +type vmContext struct { + // Embed the default VM context here, + // so that we don't need to reimplement all the methods. + types.DefaultVMContext +} + +// NewPluginContext implements types.VMContext. +func (*vmContext) NewPluginContext(contextID uint32) types.PluginContext { + return &pluginContext{} +} + +// pluginContext implements types.PluginContext. +type pluginContext struct { + // Embed the default plugin context here, + // so that we don't need to reimplement all the methods. + types.DefaultPluginContext + callNum uint32 +} + +// OnPluginStart implements types.PluginContext. +func (ctx *pluginContext) OnPluginStart(pluginConfigurationSize int) types.OnPluginStartStatus { + if err := proxywasm.SetTickPeriodMilliSeconds(tickMilliseconds); err != nil { + proxywasm.LogCriticalf("failed to set tick period: %v", err) + return types.OnPluginStartStatusFailed + } + proxywasm.LogInfof("set tick period milliseconds: %d", tickMilliseconds) + return types.OnPluginStartStatusOK +} + +// OnTick implements types.PluginContext. +func (ctx *pluginContext) OnTick() { + ctx.callNum++ + ret, err := proxywasm.CallForeignFunction("compress", []byte("hello world!")) + if err != nil { + proxywasm.LogCriticalf("foreign function (compress) failed: %v", err) + } + proxywasm.LogInfof("foreign function (compress) called: %d, result: %s", ctx.callNum, hex.EncodeToString(ret)) +} diff --git a/src/test/go-examples/foreign_call_on_tick/main.wasm b/src/test/go-examples/foreign_call_on_tick/main.wasm new file mode 100644 index 0000000..5f45bbc Binary files /dev/null and b/src/test/go-examples/foreign_call_on_tick/main.wasm differ diff --git a/src/test/java/io/roastedroot/proxywasm/ForeignCallOnTickTest.java b/src/test/java/io/roastedroot/proxywasm/ForeignCallOnTickTest.java new file mode 100644 index 0000000..2830a45 --- /dev/null +++ b/src/test/java/io/roastedroot/proxywasm/ForeignCallOnTickTest.java @@ -0,0 +1,37 @@ +package io.roastedroot.proxywasm; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.dylibso.chicory.wasm.Parser; +import io.roastedroot.proxywasm.v1.ProxyWasm; +import io.roastedroot.proxywasm.v1.StartException; +import java.nio.file.Path; +import org.junit.jupiter.api.Test; + +/** + */ +public class ForeignCallOnTickTest { + + static int tickMilliseconds = 1; + + @Test + public void testOnTick() throws StartException { + + var handler = new MockHandler(); + var module = Parser.parse(Path.of("./src/test/go-examples/foreign_call_on_tick/main.wasm")); + ProxyWasm.Builder builder = ProxyWasm.builder().withPluginHandler(handler); + try (var host = builder.build(module)) { + assertEquals(tickMilliseconds, handler.getTickPeriodMilliseconds()); + + host.registerForeignFunction("compress", data -> data); + + for (int i = 1; i <= 10; i++) { + host.tick(); // call OnTick + handler.assertLogsContain( + String.format( + "foreign function (compress) called: %d, result: %s", + i, "68656c6c6f20776f726c6421")); + } + } + } +}