mirror of
https://github.com/extism/extism.git
synced 2026-01-09 13:57:55 -05:00
Add ExtismContext to SDK + better errors for failed register/update (#19)
- Adds `ExtismContext` instead of global `PLUGINS` registry - Adds `extism_context_new`, `extism_context_free` and `extism_context_reset` - Requires updating nearly every SDK function to add context parameter - Renames some SDK functions to follow better naming conventions - `extism_plugin_register` -> `extism_plugin_new` - `extism_output_get` -> `extism_plugin_output_data` - `extism_output_length` -> `extism_plugin_output_length` - `extism_call` -> `extism_plugin_call` - Updates `extism_error` to return the context error when -1 issued for the plug-in ID - Adds `extism_plugin_free` to remove an existing plugin - Updates SDKs to include these functions - Updates SDK examples and comments Co-authored-by: Steve Manuel <steve@dylib.so>
This commit is contained in:
15
c/main.c
15
c/main.c
@@ -36,20 +36,25 @@ int main(int argc, char *argv[]) {
|
||||
fputs("Not enough arguments\n", stderr);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
ExtismContext *ctx = extism_context_new();
|
||||
|
||||
size_t len = 0;
|
||||
uint8_t *data = read_file("../wasm/code.wasm", &len);
|
||||
ExtismPlugin plugin = extism_plugin_register(data, len, false);
|
||||
ExtismPlugin plugin = extism_plugin_new(ctx, data, len, false);
|
||||
free(data);
|
||||
if (plugin < 0) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
assert(extism_call(plugin, "count_vowels", (uint8_t *)argv[1],
|
||||
strlen(argv[1])) == 0);
|
||||
ExtismSize out_len = extism_output_length(plugin);
|
||||
const uint8_t *output = extism_output_get(plugin);
|
||||
assert(extism_plugin_call(ctx, plugin, "count_vowels", (uint8_t *)argv[1],
|
||||
strlen(argv[1])) == 0);
|
||||
ExtismSize out_len = extism_plugin_output_length(ctx, plugin);
|
||||
const uint8_t *output = extism_plugin_output_data(ctx, plugin);
|
||||
write(STDOUT_FILENO, output, out_len);
|
||||
write(STDOUT_FILENO, "\n", 1);
|
||||
|
||||
extism_plugin_free(ctx, plugin);
|
||||
extism_context_free(ctx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -14,17 +14,14 @@ std::vector<uint8_t> read(const char *filename) {
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
auto wasm = read("../wasm/code.wasm");
|
||||
Plugin plugin(wasm);
|
||||
Context context = Context();
|
||||
|
||||
if (argc < 2) {
|
||||
std::cout << "Not enough arguments" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
Plugin plugin = context.plugin(wasm);
|
||||
|
||||
auto input = std::vector<uint8_t>((uint8_t *)argv[1],
|
||||
(uint8_t *)argv[1] + strlen(argv[1]));
|
||||
auto output = plugin.call("count_vowels", input);
|
||||
std::string str(output.begin(), output.end());
|
||||
std::cout << str << std::endl;
|
||||
const char *input = argc > 1 ? argv[1] : "this is a test";
|
||||
ExtismSize length = strlen(input);
|
||||
|
||||
extism::Buffer output = plugin.call("count_vowels", (uint8_t *)input, length);
|
||||
std::cout << (char *)output.data << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
../core/extism.h
|
||||
@@ -1,10 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
extern "C" {
|
||||
#include "extism.h"
|
||||
#include <extism.h>
|
||||
}
|
||||
|
||||
namespace extism {
|
||||
@@ -17,29 +18,54 @@ public:
|
||||
const char *what() { return message.c_str(); }
|
||||
};
|
||||
|
||||
class Buffer {
|
||||
public:
|
||||
Buffer(const uint8_t *ptr, ExtismSize len) : data(ptr), length(len) {}
|
||||
const uint8_t *data;
|
||||
ExtismSize length;
|
||||
|
||||
operator std::string() { return std::string((const char *)data, length); }
|
||||
operator std::vector<uint8_t>() {
|
||||
return std::vector<uint8_t>(data, data + length);
|
||||
}
|
||||
};
|
||||
|
||||
class Plugin {
|
||||
std::shared_ptr<ExtismContext> context;
|
||||
ExtismPlugin plugin;
|
||||
|
||||
public:
|
||||
Plugin(const uint8_t *wasm, size_t length, bool with_wasi = false) {
|
||||
this->plugin = extism_plugin_register(wasm, length, with_wasi);
|
||||
Plugin(std::shared_ptr<ExtismContext> ctx, const uint8_t *wasm,
|
||||
ExtismSize length, bool with_wasi = false) {
|
||||
this->plugin = extism_plugin_new(ctx.get(), wasm, length, with_wasi);
|
||||
if (this->plugin < 0) {
|
||||
throw Error("Unable to load plugin");
|
||||
const char *err = extism_error(ctx.get(), -1);
|
||||
throw Error(err == nullptr ? "Unable to load plugin" : err);
|
||||
}
|
||||
this->context = ctx;
|
||||
}
|
||||
|
||||
~Plugin() {
|
||||
extism_plugin_free(this->context.get(), this->plugin);
|
||||
this->plugin = -1;
|
||||
}
|
||||
|
||||
void update(const uint8_t *wasm, size_t length, bool with_wasi = false) {
|
||||
bool b = extism_plugin_update(this->context.get(), this->plugin, wasm,
|
||||
length, with_wasi);
|
||||
if (!b) {
|
||||
const char *err = extism_error(this->context.get(), -1);
|
||||
throw Error(err == nullptr ? "Unable to update plugin" : err);
|
||||
}
|
||||
}
|
||||
|
||||
Plugin(const std::string &s, bool with_wasi = false)
|
||||
: Plugin((const uint8_t *)s.c_str(), s.size(), with_wasi) {}
|
||||
Plugin(const std::vector<uint8_t> &s, bool with_wasi = false)
|
||||
: Plugin(s.data(), s.size(), with_wasi) {}
|
||||
Buffer call(const std::string &func, const uint8_t *input,
|
||||
ExtismSize input_length) {
|
||||
|
||||
std::vector<uint8_t> call(const std::string &func,
|
||||
std::vector<uint8_t> input) {
|
||||
|
||||
int32_t rc =
|
||||
extism_call(this->plugin, func.c_str(), input.data(), input.size());
|
||||
int32_t rc = extism_plugin_call(this->context.get(), this->plugin,
|
||||
func.c_str(), input, input_length);
|
||||
if (rc != 0) {
|
||||
const char *error = extism_error(this->plugin);
|
||||
const char *error = extism_error(this->context.get(), this->plugin);
|
||||
if (error == nullptr) {
|
||||
throw Error("extism_call failed");
|
||||
}
|
||||
@@ -47,10 +73,42 @@ public:
|
||||
throw Error(error);
|
||||
}
|
||||
|
||||
ExtismSize length = extism_output_length(this->plugin);
|
||||
const uint8_t *ptr = extism_output_get(this->plugin);
|
||||
std::vector<uint8_t> out = std::vector<uint8_t>(ptr, ptr + length);
|
||||
return out;
|
||||
ExtismSize length =
|
||||
extism_plugin_output_length(this->context.get(), this->plugin);
|
||||
const uint8_t *ptr =
|
||||
extism_plugin_output_data(this->context.get(), this->plugin);
|
||||
return Buffer(ptr, length);
|
||||
}
|
||||
|
||||
Buffer call(const std::string &func, const std::vector<uint8_t> &input) {
|
||||
return this->call(func, input.data(), input.size());
|
||||
}
|
||||
|
||||
Buffer call(const std::string &func, const std::string &input) {
|
||||
return this->call(func, (const uint8_t *)input.c_str(), input.size());
|
||||
}
|
||||
};
|
||||
|
||||
class Context {
|
||||
public:
|
||||
std::shared_ptr<ExtismContext> pointer;
|
||||
Context() {
|
||||
this->pointer = std::shared_ptr<ExtismContext>(extism_context_new(),
|
||||
extism_context_free);
|
||||
}
|
||||
|
||||
Plugin plugin(const uint8_t *wasm, size_t length, bool with_wasi = false) {
|
||||
return Plugin(this->pointer, wasm, length, with_wasi);
|
||||
}
|
||||
|
||||
Plugin plugin(const std::string &str, bool with_wasi = false) {
|
||||
return Plugin(this->pointer, (const uint8_t *)str.c_str(), str.size(),
|
||||
with_wasi);
|
||||
}
|
||||
|
||||
Plugin plugin(const std::vector<uint8_t> &data, bool with_wasi = false) {
|
||||
return Plugin(this->pointer, data.data(), data.size(), with_wasi);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace extism
|
||||
|
||||
120
extism.go
120
extism.go
@@ -15,8 +15,29 @@ import (
|
||||
*/
|
||||
import "C"
|
||||
|
||||
// Context is used to manage Plugins
|
||||
type Context struct {
|
||||
pointer *C.ExtismContext
|
||||
}
|
||||
|
||||
// NewContext creates a new context, it should be freed using the `Free` method
|
||||
func NewContext() Context {
|
||||
p := C.extism_context_new()
|
||||
return Context{
|
||||
pointer: p,
|
||||
}
|
||||
}
|
||||
|
||||
// Free a context
|
||||
func (ctx *Context) Free() {
|
||||
C.extism_context_free(ctx.pointer)
|
||||
ctx.pointer = nil
|
||||
}
|
||||
|
||||
// Plugin is used to call WASM functions
|
||||
type Plugin struct {
|
||||
id int32
|
||||
ctx *Context
|
||||
id int32
|
||||
}
|
||||
|
||||
type WasmData struct {
|
||||
@@ -57,6 +78,7 @@ func makePointer(data []byte) unsafe.Pointer {
|
||||
return ptr
|
||||
}
|
||||
|
||||
// SetLogFile sets the log file and level, this is a global setting
|
||||
func SetLogFile(filename string, level string) bool {
|
||||
name := C.CString(filename)
|
||||
l := C.CString(level)
|
||||
@@ -66,81 +88,120 @@ func SetLogFile(filename string, level string) bool {
|
||||
return bool(r)
|
||||
}
|
||||
|
||||
func register(data []byte, wasi bool) (Plugin, error) {
|
||||
func register(ctx *Context, data []byte, wasi bool) (Plugin, error) {
|
||||
ptr := makePointer(data)
|
||||
plugin := C.extism_plugin_register(
|
||||
plugin := C.extism_plugin_new(
|
||||
ctx.pointer,
|
||||
(*C.uchar)(ptr),
|
||||
C.uint64_t(len(data)),
|
||||
C._Bool(wasi),
|
||||
)
|
||||
|
||||
if plugin < 0 {
|
||||
return Plugin{id: -1}, errors.New("Unable to load plugin")
|
||||
err := C.extism_error(ctx.pointer, C.int32_t(-1))
|
||||
msg := "Unknown"
|
||||
if err != nil {
|
||||
msg = C.GoString(err)
|
||||
}
|
||||
|
||||
return Plugin{id: -1}, errors.New(
|
||||
fmt.Sprintf("Unable to load plugin: %s", msg),
|
||||
)
|
||||
}
|
||||
|
||||
return Plugin{id: int32(plugin)}, nil
|
||||
return Plugin{id: int32(plugin), ctx: ctx}, nil
|
||||
}
|
||||
|
||||
func update(plugin int32, data []byte, wasi bool) bool {
|
||||
func update(ctx *Context, plugin int32, data []byte, wasi bool) error {
|
||||
ptr := makePointer(data)
|
||||
return bool(C.extism_plugin_update(
|
||||
b := bool(C.extism_plugin_update(
|
||||
ctx.pointer,
|
||||
C.int32_t(plugin),
|
||||
(*C.uchar)(ptr),
|
||||
C.uint64_t(len(data)),
|
||||
C._Bool(wasi),
|
||||
))
|
||||
|
||||
if b {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := C.extism_error(ctx.pointer, C.int32_t(-1))
|
||||
msg := "Unknown"
|
||||
if err != nil {
|
||||
msg = C.GoString(err)
|
||||
}
|
||||
|
||||
return errors.New(
|
||||
fmt.Sprintf("Unable to load plugin: %s", msg),
|
||||
)
|
||||
}
|
||||
|
||||
func LoadManifest(manifest Manifest, wasi bool) (Plugin, error) {
|
||||
// PluginFromManifest creates a plugin from a `Manifest`
|
||||
func (ctx *Context) PluginFromManifest(manifest Manifest, wasi bool) (Plugin, error) {
|
||||
data, err := json.Marshal(manifest)
|
||||
if err != nil {
|
||||
return Plugin{id: -1}, err
|
||||
}
|
||||
|
||||
return register(data, wasi)
|
||||
return register(ctx, data, wasi)
|
||||
}
|
||||
|
||||
func LoadPlugin(module io.Reader, wasi bool) (Plugin, error) {
|
||||
// Plugin creates a plugin from a WASM module
|
||||
func (ctx *Context) Plugin(module io.Reader, wasi bool) (Plugin, error) {
|
||||
wasm, err := io.ReadAll(module)
|
||||
if err != nil {
|
||||
return Plugin{id: -1}, err
|
||||
}
|
||||
|
||||
return register(wasm, wasi)
|
||||
return register(ctx, wasm, wasi)
|
||||
}
|
||||
|
||||
func (p *Plugin) Update(module io.Reader, wasi bool) (bool, error) {
|
||||
// Update a plugin with a new WASM module
|
||||
func (p *Plugin) Update(module io.Reader, wasi bool) error {
|
||||
wasm, err := io.ReadAll(module)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return err
|
||||
}
|
||||
|
||||
return update(p.id, wasm, wasi), nil
|
||||
return update(p.ctx, p.id, wasm, wasi)
|
||||
}
|
||||
|
||||
func (p *Plugin) UpdateManifest(manifest Manifest, wasi bool) (bool, error) {
|
||||
// Update a plugin with a new Manifest
|
||||
func (p *Plugin) UpdateManifest(manifest Manifest, wasi bool) error {
|
||||
data, err := json.Marshal(manifest)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return err
|
||||
}
|
||||
|
||||
return update(p.id, data, wasi), nil
|
||||
return update(p.ctx, p.id, data, wasi)
|
||||
}
|
||||
|
||||
// Set configuration values
|
||||
func (plugin Plugin) SetConfig(data map[string][]byte) error {
|
||||
s, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ptr := makePointer(s)
|
||||
C.extism_plugin_config(C.int(plugin.id), (*C.uchar)(ptr), C.uint64_t(len(s)))
|
||||
C.extism_plugin_config(plugin.ctx.pointer, C.int(plugin.id), (*C.uchar)(ptr), C.uint64_t(len(s)))
|
||||
return nil
|
||||
}
|
||||
|
||||
/// FunctionExists returns true when the name function is present in the plugin
|
||||
func (plugin Plugin) FunctionExists(functionName string) bool {
|
||||
name := C.CString(functionName)
|
||||
b := C.extism_plugin_function_exists(plugin.ctx.pointer, C.int(plugin.id), name)
|
||||
C.free(unsafe.Pointer(name))
|
||||
return bool(b)
|
||||
}
|
||||
|
||||
/// Call a function by name with the given input, returning the output
|
||||
func (plugin Plugin) Call(functionName string, input []byte) ([]byte, error) {
|
||||
ptr := makePointer(input)
|
||||
name := C.CString(functionName)
|
||||
rc := C.extism_call(
|
||||
rc := C.extism_plugin_call(
|
||||
plugin.ctx.pointer,
|
||||
C.int32_t(plugin.id),
|
||||
name,
|
||||
(*C.uchar)(ptr),
|
||||
@@ -149,7 +210,7 @@ func (plugin Plugin) Call(functionName string, input []byte) ([]byte, error) {
|
||||
C.free(unsafe.Pointer(name))
|
||||
|
||||
if rc != 0 {
|
||||
err := C.extism_error(C.int32_t(plugin.id))
|
||||
err := C.extism_error(plugin.ctx.pointer, C.int32_t(plugin.id))
|
||||
msg := "<unset by plugin>"
|
||||
if err != nil {
|
||||
msg = C.GoString(err)
|
||||
@@ -160,14 +221,27 @@ func (plugin Plugin) Call(functionName string, input []byte) ([]byte, error) {
|
||||
)
|
||||
}
|
||||
|
||||
length := C.extism_output_length(C.int32_t(plugin.id))
|
||||
length := C.extism_plugin_output_length(plugin.ctx.pointer, C.int32_t(plugin.id))
|
||||
|
||||
if length > 0 {
|
||||
x := C.extism_output_get(C.int32_t(plugin.id))
|
||||
x := C.extism_plugin_output_data(plugin.ctx.pointer, C.int32_t(plugin.id))
|
||||
y := (*[]byte)(unsafe.Pointer(&x))
|
||||
return []byte((*y)[0:length]), nil
|
||||
|
||||
}
|
||||
|
||||
return []byte{}, nil
|
||||
}
|
||||
|
||||
// Free a plugin
|
||||
func (plugin *Plugin) Free() {
|
||||
if plugin.ctx.pointer == nil {
|
||||
return
|
||||
}
|
||||
C.extism_plugin_free(plugin.ctx.pointer, C.int32_t(plugin.id))
|
||||
plugin.id = -1
|
||||
}
|
||||
|
||||
// Reset removes all registered plugins in a Context
|
||||
func (ctx Context) Reset() {
|
||||
C.extism_context_reset(ctx.pointer)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,9 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx := extism.NewContext()
|
||||
defer ctx.Free() // this will free the context and all associated plugins
|
||||
|
||||
// set some input data to provide to the plugin module
|
||||
var data []byte
|
||||
if len(os.Args) > 1 {
|
||||
@@ -18,7 +21,7 @@ func main() {
|
||||
}
|
||||
|
||||
manifest := extism.Manifest{Wasm: []extism.Wasm{extism.WasmFile{Path: "../wasm/code.wasm"}}}
|
||||
plugin, err := extism.LoadManifest(manifest, false)
|
||||
plugin, err := ctx.PluginFromManifest(manifest, false)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
|
||||
@@ -5,13 +5,19 @@ import qualified Data.ByteString as B
|
||||
import Extism
|
||||
import Extism.Manifest
|
||||
|
||||
main = do
|
||||
plugin <- Extism.registerManifest (manifest [wasmFile "../wasm/code.wasm"]) False
|
||||
try f (Right x) = f x
|
||||
try f (Left (Error msg)) = do
|
||||
_ <- putStrLn msg
|
||||
exitFailure
|
||||
|
||||
handlePlugin plugin = do
|
||||
res <- Extism.call plugin "count_vowels" (Extism.toByteString "this is a test")
|
||||
case res of
|
||||
Right (Error msg) -> do
|
||||
_ <- putStrLn msg
|
||||
exitFailure
|
||||
Left bs -> do
|
||||
_ <- putStrLn (Extism.fromByteString bs)
|
||||
exitSuccess
|
||||
try (\bs -> do
|
||||
_ <- putStrLn (Extism.fromByteString bs)
|
||||
_ <- Extism.free plugin
|
||||
exitSuccess) res
|
||||
|
||||
main = do
|
||||
context <- Extism.newContext ()
|
||||
plugin <- Extism.pluginFromManifest context (manifest [wasmFile "../wasm/code.wasm"]) False
|
||||
try handlePlugin plugin
|
||||
@@ -5,6 +5,7 @@ import GHC.Int
|
||||
import GHC.Word
|
||||
import Foreign.C.Types
|
||||
import Foreign.Ptr
|
||||
import Foreign.ForeignPtr
|
||||
import Foreign.C.String
|
||||
import Control.Monad (void)
|
||||
import Data.ByteString as B
|
||||
@@ -13,58 +14,105 @@ import Data.ByteString.Unsafe (unsafeUseAsCString)
|
||||
import Text.JSON (JSON, toJSObject, encode)
|
||||
import Extism.Manifest (Manifest, toString)
|
||||
|
||||
foreign import ccall unsafe "extism.h extism_plugin_register" extism_plugin_register :: Ptr Word8 -> Word64 -> CBool -> IO Int32
|
||||
foreign import ccall unsafe "extism.h extism_plugin_update" extism_plugin_update :: Int32 -> Ptr Word8 -> Word64 -> CBool -> IO CBool
|
||||
foreign import ccall unsafe "extism.h extism_call" extism_call :: Int32 -> CString -> Ptr Word8 -> Word64 -> IO Int32
|
||||
foreign import ccall unsafe "extism.h extism_function_exists" extism_function_exists :: Int32 -> CString -> IO CBool
|
||||
foreign import ccall unsafe "extism.h extism_error" extism_error :: Int32 -> IO CString
|
||||
foreign import ccall unsafe "extism.h extism_output_length" extism_output_length :: Int32 -> IO Word64
|
||||
foreign import ccall unsafe "extism.h extism_output_get" extism_output_get :: Int32 -> IO (Ptr Word8)
|
||||
foreign import ccall unsafe "extism.h extism_log_file" extism_log_file :: CString -> CString -> IO CBool
|
||||
foreign import ccall unsafe "extism.h extism_plugin_config" extism_plugin_config :: Int32 -> Ptr Word8 -> Int64 -> IO CBool
|
||||
newtype ExtismContext = ExtismContext () deriving Show
|
||||
|
||||
newtype Plugin = Plugin Int32 deriving Show
|
||||
foreign import ccall unsafe "extism.h extism_context_new" extism_context_new :: IO (Ptr ExtismContext)
|
||||
foreign import ccall unsafe "extism.h &extism_context_free" extism_context_free :: FunPtr (Ptr ExtismContext -> IO ())
|
||||
foreign import ccall unsafe "extism.h extism_plugin_new" extism_plugin_new :: Ptr ExtismContext -> Ptr Word8 -> Word64 -> CBool -> IO Int32
|
||||
foreign import ccall unsafe "extism.h extism_plugin_update" extism_plugin_update :: Ptr ExtismContext -> Int32 -> Ptr Word8 -> Word64 -> CBool -> IO CBool
|
||||
foreign import ccall unsafe "extism.h extism_plugin_call" extism_plugin_call :: Ptr ExtismContext -> Int32 -> CString -> Ptr Word8 -> Word64 -> IO Int32
|
||||
foreign import ccall unsafe "extism.h extism_plugin_function_exists" extism_plugin_function_exists :: Ptr ExtismContext -> Int32 -> CString -> IO CBool
|
||||
foreign import ccall unsafe "extism.h extism_error" extism_error :: Ptr ExtismContext -> Int32 -> IO CString
|
||||
foreign import ccall unsafe "extism.h extism_plugin_output_length" extism_plugin_output_length :: Ptr ExtismContext -> Int32 -> IO Word64
|
||||
foreign import ccall unsafe "extism.h extism_plugin_output_data" extism_plugin_output_data :: Ptr ExtismContext -> Int32 -> IO (Ptr Word8)
|
||||
foreign import ccall unsafe "extism.h extism_log_file" extism_log_file :: CString -> CString -> IO CBool
|
||||
foreign import ccall unsafe "extism.h extism_plugin_config" extism_plugin_config :: Ptr ExtismContext -> Int32 -> Ptr Word8 -> Int64 -> IO CBool
|
||||
foreign import ccall unsafe "extism.h extism_plugin_free" extism_plugin_free :: Ptr ExtismContext -> Int32 -> IO ()
|
||||
foreign import ccall unsafe "extism.h extism_context_reset" extism_context_reset :: Ptr ExtismContext -> IO ()
|
||||
|
||||
-- Context manages plugins
|
||||
newtype Context = Context (ForeignPtr ExtismContext)
|
||||
|
||||
-- Plugins can be used to call WASM function
|
||||
data Plugin = Plugin Context Int32
|
||||
|
||||
-- Extism error
|
||||
newtype Error = Error String deriving Show
|
||||
|
||||
-- Helper function to convert a string to a bytestring
|
||||
toByteString :: String -> ByteString
|
||||
toByteString x = B.pack (Prelude.map c2w x)
|
||||
|
||||
-- Helper function to convert a bytestring to a string
|
||||
fromByteString :: ByteString -> String
|
||||
fromByteString bs = Prelude.map w2c $ B.unpack bs
|
||||
|
||||
register :: B.ByteString -> Bool -> IO Plugin
|
||||
register wasm useWasi =
|
||||
-- Remove all registered plugins in a Context
|
||||
reset :: Context -> IO ()
|
||||
reset (Context ctx) =
|
||||
withForeignPtr ctx (\ctx ->
|
||||
extism_context_reset ctx)
|
||||
|
||||
-- Create a new context
|
||||
newContext :: () -> IO Context
|
||||
newContext () = do
|
||||
ptr <- extism_context_new
|
||||
fptr <- newForeignPtr extism_context_free ptr
|
||||
return (Context fptr)
|
||||
|
||||
-- Create a plugin from a WASM module, `useWasi` determines if WASI should
|
||||
-- be linked
|
||||
plugin :: Context -> B.ByteString -> Bool -> IO (Either Error Plugin)
|
||||
plugin c wasm useWasi =
|
||||
let length = fromIntegral (B.length wasm) in
|
||||
let wasi = fromInteger (if useWasi then 1 else 0) in
|
||||
let Context ctx = c in
|
||||
do
|
||||
p <- unsafeUseAsCString wasm (\s ->
|
||||
extism_plugin_register (castPtr s) length wasi)
|
||||
return $ Plugin p
|
||||
withForeignPtr ctx (\ctx -> do
|
||||
p <- unsafeUseAsCString wasm (\s ->
|
||||
extism_plugin_new ctx (castPtr s) length wasi)
|
||||
if p < 0 then do
|
||||
err <- extism_error ctx (-1)
|
||||
e <- peekCString err
|
||||
return $ Left (Error e)
|
||||
else
|
||||
return $ Right (Plugin c p))
|
||||
|
||||
registerManifest :: Manifest -> Bool -> IO Plugin
|
||||
registerManifest manifest useWasi =
|
||||
-- Create a plugin from a Manifest
|
||||
pluginFromManifest :: Context -> Manifest -> Bool -> IO (Either Error Plugin)
|
||||
pluginFromManifest ctx manifest useWasi =
|
||||
let wasm = toByteString $ toString manifest in
|
||||
register wasm useWasi
|
||||
plugin ctx wasm useWasi
|
||||
|
||||
update :: Plugin -> B.ByteString -> Bool -> IO Bool
|
||||
update (Plugin id) wasm useWasi =
|
||||
-- Update a plugin with a new WASM module
|
||||
update :: Plugin -> B.ByteString -> Bool -> IO (Either Error ())
|
||||
update (Plugin (Context ctx) id) wasm useWasi =
|
||||
let length = fromIntegral (B.length wasm) in
|
||||
let wasi = fromInteger (if useWasi then 1 else 0) in
|
||||
do
|
||||
b <- unsafeUseAsCString wasm (\s ->
|
||||
extism_plugin_update id (castPtr s) length wasi)
|
||||
return (b > 0)
|
||||
withForeignPtr ctx (\ctx -> do
|
||||
b <- unsafeUseAsCString wasm (\s ->
|
||||
extism_plugin_update ctx id (castPtr s) length wasi)
|
||||
if b <= 0 then do
|
||||
err <- extism_error ctx (-1)
|
||||
e <- peekCString err
|
||||
return $ Left (Error e)
|
||||
else
|
||||
return (Right ()))
|
||||
|
||||
updateManifest :: Plugin -> Manifest -> Bool -> IO Bool
|
||||
-- Update a plugin with a new Manifest
|
||||
updateManifest :: Plugin -> Manifest -> Bool -> IO (Either Error ())
|
||||
updateManifest plugin manifest useWasi =
|
||||
let wasm = toByteString $ toString manifest in
|
||||
update plugin wasm useWasi
|
||||
|
||||
-- Check if a plugin is value
|
||||
isValid :: Plugin -> Bool
|
||||
isValid (Plugin p) = p >= 0
|
||||
isValid (Plugin _ p) = p >= 0
|
||||
|
||||
setConfig :: Plugin -> [(String, String)] -> IO ()
|
||||
setConfig (Plugin plugin) x =
|
||||
-- Set configuration values for a plugin
|
||||
setConfig :: Plugin -> [(String, Maybe String)] -> IO ()
|
||||
setConfig (Plugin (Context ctx) plugin) x =
|
||||
if plugin < 0
|
||||
then return ()
|
||||
else
|
||||
@@ -72,34 +120,46 @@ setConfig (Plugin plugin) x =
|
||||
let bs = toByteString (encode obj) in
|
||||
let length = fromIntegral (B.length bs) in
|
||||
unsafeUseAsCString bs (\s -> do
|
||||
void $ extism_plugin_config plugin (castPtr s) length)
|
||||
withForeignPtr ctx (\ctx ->
|
||||
void $ extism_plugin_config ctx plugin (castPtr s) length))
|
||||
|
||||
-- Set the log file and level, this is a global configuration
|
||||
setLogFile :: String -> String -> IO ()
|
||||
setLogFile filename level =
|
||||
withCString filename (\f ->
|
||||
withCString level (\l -> do
|
||||
void $ extism_log_file f l))
|
||||
|
||||
-- Check if a function exists in the given plugin
|
||||
functionExists :: Plugin -> String -> IO Bool
|
||||
functionExists (Plugin plugin) name = do
|
||||
b <- withCString name (extism_function_exists plugin)
|
||||
if b == 1 then return True else return False
|
||||
functionExists (Plugin (Context ctx) plugin) name = do
|
||||
withForeignPtr ctx (\ctx -> do
|
||||
b <- withCString name (extism_plugin_function_exists ctx plugin)
|
||||
if b == 1 then return True else return False)
|
||||
|
||||
call :: Plugin -> String -> B.ByteString -> IO (Either B.ByteString Error)
|
||||
call (Plugin plugin) name input =
|
||||
--- Call a function provided by the given plugin
|
||||
call :: Plugin -> String -> B.ByteString -> IO (Either Error B.ByteString)
|
||||
call (Plugin (Context ctx) plugin) name input =
|
||||
let length = fromIntegral (B.length input) in
|
||||
do
|
||||
rc <- withCString name (\name ->
|
||||
unsafeUseAsCString input (\input ->
|
||||
extism_call plugin name (castPtr input) length))
|
||||
err <- extism_error plugin
|
||||
if err /= nullPtr
|
||||
then do e <- peekCString err
|
||||
return $ Right (Error e)
|
||||
else if rc == 0
|
||||
then do
|
||||
length <- extism_output_length plugin
|
||||
ptr <- extism_output_get plugin
|
||||
buf <- packCStringLen (castPtr ptr, fromIntegral length)
|
||||
return $ Left buf
|
||||
else return $ Right (Error "Call failed")
|
||||
withForeignPtr ctx (\ctx -> do
|
||||
rc <- withCString name (\name ->
|
||||
unsafeUseAsCString input (\input ->
|
||||
extism_plugin_call ctx plugin name (castPtr input) length))
|
||||
err <- extism_error ctx plugin
|
||||
if err /= nullPtr
|
||||
then do e <- peekCString err
|
||||
return $ Left (Error e)
|
||||
else if rc == 0
|
||||
then do
|
||||
length <- extism_plugin_output_length ctx plugin
|
||||
ptr <- extism_plugin_output_data ctx plugin
|
||||
buf <- packCStringLen (castPtr ptr, fromIntegral length)
|
||||
return $ Right buf
|
||||
else return $ Left (Error "Call failed"))
|
||||
|
||||
-- Free a plugin
|
||||
free :: Plugin -> IO ()
|
||||
free (Plugin (Context ctx) plugin) =
|
||||
withForeignPtr ctx (\ctx ->
|
||||
extism_plugin_free ctx plugin)
|
||||
|
||||
@@ -89,6 +89,6 @@ mod base64 {
|
||||
|
||||
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<u8>, D::Error> {
|
||||
let base64 = String::deserialize(d)?;
|
||||
base64::decode(base64.as_bytes()).map_err(|e| serde::de::Error::custom(e))
|
||||
base64::decode(base64.as_bytes()).map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
import { Plugin } from './index.js';
|
||||
import { withContext, Context } from './index.js';
|
||||
import { readFileSync } from 'fs';
|
||||
|
||||
withContext(async function (context) {
|
||||
let wasm = readFileSync('../wasm/code.wasm');
|
||||
let p = context.plugin(wasm);
|
||||
|
||||
if (!p.functionExists('count_vowels')) {
|
||||
console.log("no function 'count_vowels' in wasm");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let buf = await p.call('count_vowels', process.argv[2] || 'this is a test');
|
||||
console.log(JSON.parse(buf.toString())['count']);
|
||||
p.free();
|
||||
});
|
||||
|
||||
// or, use a context like this:
|
||||
let ctx = new Context();
|
||||
let wasm = readFileSync('../wasm/code.wasm');
|
||||
let p = new Plugin(wasm);
|
||||
|
||||
if (!p.function_exists('count_vowels')) {
|
||||
console.log("no function 'count_vowels' in wasm");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let buf = await p.call('count_vowels', process.argv[2] || 'this is a test');
|
||||
console.log(JSON.parse(buf.toString())['count']);
|
||||
let p = ctx.plugin(wasm);
|
||||
// ... where the context can be passed around to various functions etc.
|
||||
129
node/index.js
129
node/index.js
@@ -3,17 +3,21 @@ import path from 'path';
|
||||
import url from 'url';
|
||||
|
||||
const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
|
||||
|
||||
let context = 'void*';
|
||||
let _functions = {
|
||||
extism_plugin_register: ['int32', ['string', 'uint64', 'bool']],
|
||||
extism_plugin_update: ['bool', ['int32', 'string', 'uint64', 'bool']],
|
||||
extism_error: ['char*', ['int32']],
|
||||
extism_call: ['int32', ['int32', 'string', 'string', 'uint64']],
|
||||
extism_output_length: ['uint64', ['int32']],
|
||||
extism_output_get: ['uint8*', ['int32']],
|
||||
extism_context_new: [context, []],
|
||||
extism_context_free: ['void', [context]],
|
||||
extism_plugin_new: ['int32', [context, 'string', 'uint64', 'bool']],
|
||||
extism_plugin_update: ['bool', [context, 'int32', 'string', 'uint64', 'bool']],
|
||||
extism_error: ['char*', [context, 'int32']],
|
||||
extism_plugin_call: ['int32', [context, 'int32', 'string', 'string', 'uint64']],
|
||||
extism_plugin_output_length: ['uint64', [context, 'int32']],
|
||||
extism_plugin_output_data: ['uint8*', [context, 'int32']],
|
||||
extism_log_file: ['bool', ['string', 'char*']],
|
||||
extism_function_exists: ['bool', ['int32', 'string']],
|
||||
extism_plugin_config: ['void', ['int32', 'char*', 'uint64']],
|
||||
extism_plugin_function_exists: ['bool', [context, 'int32', 'string']],
|
||||
extism_plugin_config: ['void', [context, 'int32', 'char*', 'uint64']],
|
||||
extism_plugin_free: ['void', [context, 'int32']],
|
||||
extism_context_reset: ['void', [context]],
|
||||
};
|
||||
|
||||
function locate(paths) {
|
||||
@@ -41,62 +45,135 @@ if (process.env.EXTISM_PATH) {
|
||||
|
||||
var lib = locate(searchPath);
|
||||
|
||||
export function set_log_file(filename, level = null) {
|
||||
// Set the log file and level
|
||||
export function setLogFile(filename, level = null) {
|
||||
lib.extism_log_file(filename, level);
|
||||
}
|
||||
|
||||
const pluginRegistry = new FinalizationRegistry(({ id, pointer }) => {
|
||||
lib.extism_plugin_free(pointer, id);
|
||||
});
|
||||
|
||||
|
||||
const contextRegistry = new FinalizationRegistry((pointer) => {
|
||||
lib.extism_context_free(pointer);
|
||||
});
|
||||
|
||||
// Context manages plugins
|
||||
export class Context {
|
||||
constructor() {
|
||||
this.pointer = lib.extism_context_new();
|
||||
|
||||
contextRegistry.register(this, this.pointer, this);
|
||||
}
|
||||
|
||||
// Create a new plugin, optionally enabling WASI
|
||||
plugin(data, wasi = false, config = null) {
|
||||
return new Plugin(this, data, wasi, config);
|
||||
}
|
||||
|
||||
// Free a context, this should be called when it is
|
||||
// no longer needed
|
||||
free() {
|
||||
contextRegistry.unregister(this);
|
||||
|
||||
lib.extism_context_free(this.pointer);
|
||||
this.pointer = null;
|
||||
}
|
||||
|
||||
// Remove all registered plugins
|
||||
reset() {
|
||||
lib.extism_context_reset(this.pointer);
|
||||
}
|
||||
}
|
||||
|
||||
export async function withContext(f) {
|
||||
let ctx = new Context();
|
||||
|
||||
try {
|
||||
let x = await f(ctx);
|
||||
ctx.free();
|
||||
return x;
|
||||
} catch (err) {
|
||||
ctx.free();
|
||||
throw err;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Plugin provides an interface for calling WASM functions
|
||||
export class Plugin {
|
||||
constructor(data, wasi = false, config = null) {
|
||||
constructor(ctx, data, wasi = false, config = null) {
|
||||
if (typeof data === 'object' && data.wasm) {
|
||||
data = JSON.stringify(data);
|
||||
}
|
||||
let plugin = lib.extism_plugin_register(data, data.length, wasi);
|
||||
let plugin = lib.extism_plugin_new(ctx.pointer, data, data.length, wasi);
|
||||
if (plugin < 0) {
|
||||
throw 'Unable to load plugin';
|
||||
var err = lib.extism_error(ctx.pointer, -1);
|
||||
if (err.length == 0) {
|
||||
throw "extism_context_plugin failed";
|
||||
}
|
||||
throw `Unable to load plugin: ${err.toString()}`;
|
||||
}
|
||||
this.id = plugin;
|
||||
this.ctx = ctx;
|
||||
pluginRegistry.register(this, { id: this.id, pointer: this.ctx.pointer }, this);
|
||||
|
||||
if (config != null) {
|
||||
let s = JSON.stringify(config);
|
||||
lib.extism_plugin_config(this.id, s, s.length);
|
||||
lib.extism_plugin_config(this.ctx.pointer, this.id, s, s.length);
|
||||
}
|
||||
}
|
||||
|
||||
// Update an existing plugin with new WASM or manifest
|
||||
update(data, wasi = false, config = null) {
|
||||
if (typeof data === 'object' && data.wasm) {
|
||||
data = JSON.stringify(data);
|
||||
}
|
||||
const ok = lib.extism_plugin_update(this.id, data, data.length, wasi);
|
||||
const ok = lib.extism_plugin_update(this.ctx.pointer, this.id, data, data.length, wasi);
|
||||
if (!ok) {
|
||||
return false;
|
||||
var err = lib.extism_error(this.ctx.pointer, -1);
|
||||
if (err.length == 0) {
|
||||
throw "extism_plugin_update failed";
|
||||
}
|
||||
throw `Unable to update plugin: ${err.toString()}`;
|
||||
}
|
||||
|
||||
if (config != null) {
|
||||
let s = JSON.stringify(config);
|
||||
lib.extism_plugin_config(this.id, s, s.length);
|
||||
lib.extism_plugin_config(this.ctx.pointer, this.id, s, s.length);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function_exists(name) {
|
||||
return lib.extism_function_exists(this.id, name);
|
||||
// Check if a function exists
|
||||
functionExists(name) {
|
||||
return lib.extism_plugin_function_exists(this.ctx.pointer, this.id, name);
|
||||
}
|
||||
|
||||
// Call a function by name with the given input
|
||||
async call(name, input) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var rc = lib.extism_call(this.id, name, input, input.length);
|
||||
var rc = lib.extism_plugin_call(this.ctx.pointer, this.id, name, input, input.length);
|
||||
if (rc != 0) {
|
||||
var err = lib.extism_error(this.id);
|
||||
var err = lib.extism_error(this.ctx.pointer, this.id);
|
||||
if (err.length == 0) {
|
||||
reject(`extism_call: "${name}" failed`);
|
||||
reject(`extism_plugin_call: "${name}" failed`);
|
||||
}
|
||||
reject(`Plugin error: ${err.toString()}, code: ${rc}`);
|
||||
}
|
||||
|
||||
var out_len = lib.extism_output_length(this.id);
|
||||
var buf = Buffer.from(lib.extism_output_get(this.id).buffer, 0, out_len);
|
||||
var out_len = lib.extism_plugin_output_length(this.ctx.pointer, this.id);
|
||||
var buf = Buffer.from(lib.extism_plugin_output_data(this.ctx.pointer, this.id).buffer, 0, out_len);
|
||||
resolve(buf);
|
||||
});
|
||||
}
|
||||
|
||||
// Free a plugin, this should be called when the plugin is no longer needed
|
||||
free() {
|
||||
pluginRegistry.unregister(this);
|
||||
lib.extism_plugin_free(this.ctx.pointer, this.id);
|
||||
this.id = -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,9 @@ let () =
|
||||
let input =
|
||||
if Array.length Sys.argv > 1 then Sys.argv.(1) else "this is a test"
|
||||
in
|
||||
let ctx = Context.create () in
|
||||
let manifest = Manifest.v [ Manifest.file "../wasm/code.wasm" ] in
|
||||
let plugin = Extism.register_manifest manifest in
|
||||
let plugin = Extism.of_manifest ctx manifest |> Result.get_ok in
|
||||
let res = Extism.call plugin ~name:"count_vowels" input |> Result.get_ok in
|
||||
print_endline res
|
||||
print_endline res;
|
||||
Context.free ctx
|
||||
|
||||
@@ -39,41 +39,56 @@ module Bindings = struct
|
||||
open Ctypes
|
||||
|
||||
let fn = Foreign.foreign ~from ~release_runtime_lock:true
|
||||
let context = ptr void
|
||||
let extism_context_new = fn "extism_context_new" (void @-> returning context)
|
||||
let extism_context_free = fn "extism_context_free" (context @-> returning void)
|
||||
|
||||
let extism_plugin_register =
|
||||
fn "extism_plugin_register"
|
||||
(string @-> uint64_t @-> bool @-> returning int32_t)
|
||||
let extism_plugin_new =
|
||||
fn "extism_plugin_new"
|
||||
(context @-> string @-> uint64_t @-> bool @-> returning int32_t)
|
||||
|
||||
let extism_plugin_update =
|
||||
fn "extism_plugin_update"
|
||||
(int32_t @-> string @-> uint64_t @-> bool @-> returning bool)
|
||||
(context @-> int32_t @-> string @-> uint64_t @-> bool @-> returning bool)
|
||||
|
||||
let extism_plugin_config =
|
||||
fn "extism_plugin_config"
|
||||
(int32_t @-> string @-> uint64_t @-> returning bool)
|
||||
(context @-> int32_t @-> string @-> uint64_t @-> returning bool)
|
||||
|
||||
let extism_call =
|
||||
fn "extism_call"
|
||||
(int32_t @-> string @-> ptr char @-> uint64_t @-> returning int32_t)
|
||||
let extism_plugin_call =
|
||||
fn "extism_plugin_call"
|
||||
(context @-> int32_t @-> string @-> ptr char @-> uint64_t
|
||||
@-> returning int32_t)
|
||||
|
||||
let extism_call_s =
|
||||
fn "extism_call"
|
||||
(int32_t @-> string @-> string @-> uint64_t @-> returning int32_t)
|
||||
let extism_plugin_call_s =
|
||||
fn "extism_plugin_call"
|
||||
(context @-> int32_t @-> string @-> string @-> uint64_t
|
||||
@-> returning int32_t)
|
||||
|
||||
let extism_error = fn "extism_error" (int32_t @-> returning string_opt)
|
||||
let extism_error =
|
||||
fn "extism_error" (context @-> int32_t @-> returning string_opt)
|
||||
|
||||
let extism_output_length =
|
||||
fn "extism_output_length" (int32_t @-> returning uint64_t)
|
||||
let extism_plugin_output_length =
|
||||
fn "extism_plugin_output_length" (context @-> int32_t @-> returning uint64_t)
|
||||
|
||||
let extism_output_get =
|
||||
fn "extism_output_get" (int32_t @-> returning (ptr char))
|
||||
let extism_plugin_output_data =
|
||||
fn "extism_plugin_output_data" (context @-> int32_t @-> returning (ptr char))
|
||||
|
||||
let extism_log_file =
|
||||
fn "extism_log_file" (string @-> string_opt @-> returning bool)
|
||||
|
||||
let extism_plugin_free =
|
||||
fn "extism_plugin_free" (context @-> int32_t @-> returning void)
|
||||
|
||||
let extism_context_reset =
|
||||
fn "extism_context_reset" (context @-> returning void)
|
||||
|
||||
let extism_plugin_function_exists =
|
||||
fn "extism_plugin_function_exists"
|
||||
(context @-> int32_t @-> string @-> returning bool)
|
||||
end
|
||||
|
||||
type error = [ `Msg of string ]
|
||||
type t = { id : int32 }
|
||||
|
||||
module Manifest = struct
|
||||
type memory = { max : int option [@yojson.option] } [@@deriving yojson]
|
||||
@@ -139,59 +154,98 @@ module Manifest = struct
|
||||
let json t = yojson_of_t t |> Yojson.Safe.to_string
|
||||
end
|
||||
|
||||
exception Failed_to_load_plugin
|
||||
module Context = struct
|
||||
type t = { mutable pointer : unit Ctypes.ptr }
|
||||
|
||||
let set_log_file ?level filename = Bindings.extism_log_file filename level
|
||||
let create () =
|
||||
let ptr = Bindings.extism_context_new () in
|
||||
let t = { pointer = ptr } in
|
||||
Gc.finalise (fun { pointer } -> Bindings.extism_context_free pointer) t;
|
||||
t
|
||||
|
||||
let free ctx =
|
||||
let () = Bindings.extism_context_free ctx.pointer in
|
||||
ctx.pointer <- Ctypes.null
|
||||
|
||||
let reset ctx = Bindings.extism_context_reset ctx.pointer
|
||||
end
|
||||
|
||||
type t = { id : int32; ctx : Context.t }
|
||||
|
||||
let with_context f =
|
||||
let ctx = Context.create () in
|
||||
let x =
|
||||
try f ctx
|
||||
with exc ->
|
||||
Context.free ctx;
|
||||
raise exc
|
||||
in
|
||||
Context.free ctx;
|
||||
x
|
||||
|
||||
let set_config plugin config =
|
||||
match config with
|
||||
| Some config ->
|
||||
let config = Manifest.yojson_of_config config |> Yojson.Safe.to_string in
|
||||
let _ =
|
||||
Bindings.extism_plugin_config plugin config
|
||||
Bindings.extism_plugin_config plugin.ctx.pointer plugin.id config
|
||||
(Unsigned.UInt64.of_int (String.length config))
|
||||
in
|
||||
()
|
||||
| None -> ()
|
||||
|
||||
let register ?config ?(wasi = false) wasm =
|
||||
let free t =
|
||||
if not (Ctypes.is_null t.ctx.pointer) then
|
||||
Bindings.extism_plugin_free t.ctx.pointer t.id
|
||||
|
||||
let plugin ?config ?(wasi = false) ctx wasm =
|
||||
let id =
|
||||
Bindings.extism_plugin_register wasm
|
||||
Bindings.extism_plugin_new ctx.Context.pointer wasm
|
||||
(Unsigned.UInt64.of_int (String.length wasm))
|
||||
wasi
|
||||
in
|
||||
if id < 0l then raise Failed_to_load_plugin;
|
||||
set_config id config;
|
||||
{ id }
|
||||
if id < 0l then
|
||||
match Bindings.extism_error ctx.pointer (-1l) with
|
||||
| None -> Error (`Msg "extism_plugin_call failed")
|
||||
| Some msg -> Error (`Msg msg)
|
||||
else
|
||||
let t = { id; ctx } in
|
||||
let () = set_config t config in
|
||||
let () = Gc.finalise free t in
|
||||
Ok t
|
||||
|
||||
let register_manifest ?config ?wasi manifest =
|
||||
let of_manifest ?config ?wasi ctx manifest =
|
||||
let data = Manifest.json manifest in
|
||||
register ?config ?wasi data
|
||||
plugin ctx ?config ?wasi data
|
||||
|
||||
let update { id } ?config ?(wasi = false) wasm =
|
||||
let update plugin ?config ?(wasi = false) wasm =
|
||||
let { id; ctx } = plugin in
|
||||
let ok =
|
||||
Bindings.extism_plugin_update id wasm
|
||||
Bindings.extism_plugin_update ctx.pointer id wasm
|
||||
(Unsigned.UInt64.of_int (String.length wasm))
|
||||
wasi
|
||||
in
|
||||
if ok then
|
||||
let () = set_config id config in
|
||||
true
|
||||
else false
|
||||
if not ok then
|
||||
match Bindings.extism_error ctx.pointer (-1l) with
|
||||
| None -> Error (`Msg "extism_plugin_update failed")
|
||||
| Some msg -> Error (`Msg msg)
|
||||
else
|
||||
let () = set_config plugin config in
|
||||
Ok ()
|
||||
|
||||
let update_manifest plugin ?config ?wasi manifest =
|
||||
let data = Manifest.json manifest in
|
||||
update plugin ?config ?wasi data
|
||||
|
||||
let call' f { id } ~name input len =
|
||||
let rc = f id name input len in
|
||||
let call' f { id; ctx } ~name input len =
|
||||
let rc = f ctx.pointer id name input len in
|
||||
if rc <> 0l then
|
||||
match Bindings.extism_error id with
|
||||
| None -> Error (`Msg "extism_call failed")
|
||||
match Bindings.extism_error ctx.pointer id with
|
||||
| None -> Error (`Msg "extism_plugin_call failed")
|
||||
| Some msg -> Error (`Msg msg)
|
||||
else
|
||||
let out_len = Bindings.extism_output_length id in
|
||||
let ptr = Bindings.extism_output_get id in
|
||||
let out_len = Bindings.extism_plugin_output_length ctx.pointer id in
|
||||
let ptr = Bindings.extism_plugin_output_data ctx.pointer id in
|
||||
let buf =
|
||||
Ctypes.bigarray_of_ptr Ctypes.array1
|
||||
(Unsigned.UInt64.to_int out_len)
|
||||
@@ -199,12 +253,17 @@ let call' f { id } ~name input len =
|
||||
in
|
||||
Ok buf
|
||||
|
||||
let call_bigstring t ~name input =
|
||||
let call_bigstring (t : t) ~name input =
|
||||
let len = Unsigned.UInt64.of_int (Bigstringaf.length input) in
|
||||
let ptr = Ctypes.bigarray_start Ctypes.array1 input in
|
||||
call' Bindings.extism_call t ~name ptr len
|
||||
call' Bindings.extism_plugin_call t ~name ptr len
|
||||
|
||||
let call t ~name input =
|
||||
let call (t : t) ~name input =
|
||||
let len = String.length input in
|
||||
call' Bindings.extism_call_s t ~name input (Unsigned.UInt64.of_int len)
|
||||
call' Bindings.extism_plugin_call_s t ~name input (Unsigned.UInt64.of_int len)
|
||||
|> Result.map Bigstringaf.to_string
|
||||
|
||||
let function_exists { id; ctx } name =
|
||||
Bindings.extism_plugin_function_exists ctx.pointer id name
|
||||
|
||||
let set_log_file ?level filename = Bindings.extism_log_file filename level
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
type t
|
||||
type error = [`Msg of string]
|
||||
|
||||
exception Failed_to_load_plugin
|
||||
|
||||
module Manifest : sig
|
||||
type memory = { max : int option } [@@deriving yojson]
|
||||
type wasm_file = {
|
||||
@@ -42,10 +40,21 @@ module Manifest : sig
|
||||
val json: t -> string
|
||||
end
|
||||
|
||||
module Context : sig
|
||||
type t
|
||||
|
||||
val create: unit -> t
|
||||
val free: t -> unit
|
||||
val reset: t -> unit
|
||||
end
|
||||
|
||||
val with_context : (Context.t -> 'a) -> 'a
|
||||
val set_log_file: ?level:string -> string -> bool
|
||||
val register: ?config:(string * string) list -> ?wasi:bool -> string -> t
|
||||
val register_manifest: ?config:(string * string) list -> ?wasi:bool -> Manifest.t -> t
|
||||
val update: t -> ?config:(string * string) list -> ?wasi:bool -> string -> bool
|
||||
val update_manifest: t -> ?config:(string * string) list -> ?wasi:bool -> Manifest.t -> bool
|
||||
val plugin: ?config:(string * string) list -> ?wasi:bool -> Context.t -> string -> (t, [`Msg of string]) result
|
||||
val of_manifest: ?config:(string * string) list -> ?wasi:bool -> Context.t -> Manifest.t -> (t, [`Msg of string]) result
|
||||
val update: t -> ?config:(string * string) list -> ?wasi:bool -> string -> (unit, [`Msg of string]) result
|
||||
val update_manifest: t -> ?config:(string * string) list -> ?wasi:bool -> Manifest.t -> (unit, [`Msg of string]) result
|
||||
val call_bigstring: t -> name:string -> Bigstringaf.t -> (Bigstringaf.t, error) result
|
||||
val call: t -> name:string -> string -> (string, error) result
|
||||
val free: t -> unit
|
||||
val function_exists: t -> string -> bool
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
$ctx = new \Extism\Context();
|
||||
$wasm = file_get_contents("../../wasm/code.wasm");
|
||||
$plugin = new \Extism\Plugin($wasm);
|
||||
$plugin = new \Extism\Plugin($ctx, $wasm);
|
||||
|
||||
$output = $plugin->call("count_vowels", "this is an example");
|
||||
$json = json_decode(pack('C*', ...$output));
|
||||
|
||||
53
php/src/Context.php
Normal file
53
php/src/Context.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace Extism;
|
||||
|
||||
require_once "vendor/autoload.php";
|
||||
require_once "generate.php";
|
||||
require_once "ExtismLib.php";
|
||||
|
||||
$lib = new \ExtismLib(\ExtismLib::SOFILE);
|
||||
if ($lib == null) {
|
||||
throw new Exception("Extism: failed to create new runtime instance");
|
||||
}
|
||||
|
||||
class Context
|
||||
{
|
||||
public $pointer;
|
||||
public $lib;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
global $lib;
|
||||
|
||||
if ($lib == null) {
|
||||
$lib = new \ExtismLib(\ExtismLib::SOFILE);
|
||||
}
|
||||
|
||||
$this->pointer = $lib->extism_context_new();
|
||||
$this->lib = $lib;
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
global $lib;
|
||||
|
||||
$lib->extism_context_free($this->pointer);
|
||||
}
|
||||
|
||||
|
||||
public function reset()
|
||||
{
|
||||
global $lib;
|
||||
|
||||
$lib->extism_context_reset($this->pointer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function set_log_file($filename, $level)
|
||||
{
|
||||
global $lib;
|
||||
|
||||
$lib->extism_log_file($filename, $level);
|
||||
}
|
||||
@@ -6,28 +6,19 @@ require_once "vendor/autoload.php";
|
||||
require_once "generate.php";
|
||||
require_once "ExtismLib.php";
|
||||
|
||||
$lib = new \ExtismLib(\ExtismLib::SOFILE);
|
||||
if ($lib == null) {
|
||||
throw new Exception("Extism: failed to create new runtime instance");
|
||||
}
|
||||
|
||||
class Plugin
|
||||
{
|
||||
private $lib;
|
||||
private $context;
|
||||
|
||||
private $wasi;
|
||||
private $config;
|
||||
|
||||
private $id;
|
||||
|
||||
public function __construct($data, $wasi = false, $config = null)
|
||||
public function __construct($ctx, $data, $wasi = false, $config = null)
|
||||
{
|
||||
global $lib;
|
||||
|
||||
if ($lib == null) {
|
||||
$lib = new \ExtismLib(\ExtismLib::SOFILE);
|
||||
}
|
||||
$this->lib = $lib;
|
||||
$this->lib = $ctx->lib;
|
||||
|
||||
$this->wasi = $wasi;
|
||||
$this->config = $config;
|
||||
@@ -40,21 +31,34 @@ class Plugin
|
||||
$data = string_to_bytes($data);
|
||||
}
|
||||
|
||||
$id = $this->lib->extism_plugin_register($data, count($data), (int)$wasi);
|
||||
$id = $this->lib->extism_plugin_new($ctx->pointer, $data, count($data), (int)$wasi);
|
||||
if ($id < 0) {
|
||||
throw new Exception("Extism: unable to load plugin");
|
||||
$err = $this->lib->extism_error($ctx->pointer, -1);
|
||||
throw new Exception("Extism: unable to load plugin: " . $err);
|
||||
}
|
||||
$this->id = $id;
|
||||
$this->context = $ctx;
|
||||
|
||||
if ($config != null) {
|
||||
$cfg = string_to_bytes(json_encode(config));
|
||||
$this->lib->extism_plugin_config($this->id, $cfg, count($cfg));
|
||||
$this->lib->extism_plugin_config($ctx->pointer, $this->id, $cfg, count($cfg));
|
||||
}
|
||||
}
|
||||
|
||||
public function __destruct() {
|
||||
$this->lib->extism_plugin_free($this->context->pointer, $this->id);
|
||||
$this->id = -1;
|
||||
}
|
||||
|
||||
public function getId() {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
|
||||
public function functionExists($name)
|
||||
{
|
||||
return $this->lib->extism_plugin_function_exists($this->context->pointer, $this->id, $name);
|
||||
}
|
||||
|
||||
public function call($name, $input = null)
|
||||
{
|
||||
@@ -62,19 +66,19 @@ class Plugin
|
||||
$input = string_to_bytes($input);
|
||||
}
|
||||
|
||||
$rc = $this->lib->extism_call($this->id, $name, $input, count($input));
|
||||
$rc = $this->lib->extism_plugin_call($this->context->pointer, $this->id, $name, $input, count($input));
|
||||
if ($rc != 0) {
|
||||
$msg = "code = " . $rc;
|
||||
$err = $this->lib->extism_error($this->id);
|
||||
$err = $this->lib->extism_error($this->context->pointer, $this->id);
|
||||
if ($err) {
|
||||
$msg = $msg . ", error = " . $err;
|
||||
}
|
||||
throw new Execption("Extism: call to '".$name."' failed with " . $msg);
|
||||
}
|
||||
|
||||
$length = $this->lib->extism_output_length($this->id);
|
||||
$length = $this->lib->extism_plugin_output_length($this->context->pointer, $this->id);
|
||||
|
||||
$buf = $this->lib->extism_output_get($this->id);
|
||||
$buf = $this->lib->extism_plugin_output_data($this->context->pointer, $this->id);
|
||||
|
||||
$ouput = [];
|
||||
$data = $buf->getData();
|
||||
@@ -94,17 +98,16 @@ class Plugin
|
||||
$data = string_to_bytes($data);
|
||||
}
|
||||
|
||||
$ok = $this->lib->extism_plugin_update($this->id, $data, count($data), (int)$wasi);
|
||||
$ok = $this->lib->extism_plugin_update($this->context->pointer, $this->id, $data, count($data), (int)$wasi);
|
||||
if (!$ok) {
|
||||
return false;
|
||||
$err = $this->lib->extism_error($this->context->pointer, -1);
|
||||
throw new Exception("Extism: unable to update plugin: " . $err);
|
||||
}
|
||||
|
||||
if ($config != null) {
|
||||
$config = json_encode($config);
|
||||
$this->lib->extism_plugin_config($this->id, $config, strlen($config));
|
||||
$this->lib->extism_plugin_config($this->context->pointer, $this->id, $config, strlen($config));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
<?php
|
||||
require_once "vendor/autoload.php";
|
||||
|
||||
$search_path = array(__DIR__, "/usr/local/lib", "/usr/lib", getenv("HOME")."/.local");
|
||||
|
||||
function generate($paths) {
|
||||
for ($i = 0; $i < count($paths); $i++) {
|
||||
try {
|
||||
$ffi = (new FFIMe\FFIMe("libextism.".soext()))
|
||||
->include("extism.h")
|
||||
->showWarnings(false)
|
||||
->codeGen('ExtismLib', __DIR__.'/ExtismLib.php');
|
||||
} catch (Exception $e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
function generate() {
|
||||
return (new FFIMe\FFIMe("libextism.".soext()))
|
||||
->include("extism.h")
|
||||
->showWarnings(false)
|
||||
->codeGen('ExtismLib', __DIR__.'/ExtismLib.php');
|
||||
}
|
||||
|
||||
function soext() {
|
||||
@@ -31,6 +24,6 @@ function soext() {
|
||||
}
|
||||
|
||||
if (!file_exists(__DIR__."/ExtismLib.php")) {
|
||||
generate($search_path);
|
||||
generate();
|
||||
}
|
||||
|
||||
|
||||
@@ -4,22 +4,24 @@ import json
|
||||
import hashlib
|
||||
|
||||
sys.path.append(".")
|
||||
from extism import Plugin
|
||||
from extism import Plugin, Context
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
data = sys.argv[1].encode()
|
||||
else:
|
||||
data = b"some data from python!"
|
||||
|
||||
wasm = open("../wasm/code.wasm", 'rb').read()
|
||||
hash = hashlib.sha256(wasm).hexdigest()
|
||||
config = {"wasm": [{"data": wasm, "hash": hash}], "memory": {"max": 5}}
|
||||
# a Context provides a scope for plugins to be managed within. creating multiple contexts
|
||||
# is expected and groups plugins based on source/tenant/lifetime etc.
|
||||
with Context() as context:
|
||||
wasm = open("../wasm/code.wasm", 'rb').read()
|
||||
hash = hashlib.sha256(wasm).hexdigest()
|
||||
config = {"wasm": [{"data": wasm, "hash": hash}], "memory": {"max": 5}}
|
||||
|
||||
plugin = Plugin(config)
|
||||
|
||||
# Call `count_vowels`
|
||||
j = json.loads(plugin.call("count_vowels", data))
|
||||
print("Number of vowels:", j["count"])
|
||||
plugin = context.plugin(config)
|
||||
# Call `count_vowels`
|
||||
j = json.loads(plugin.call("count_vowels", data))
|
||||
print("Number of vowels:", j["count"])
|
||||
|
||||
|
||||
# Compare against Python implementation
|
||||
|
||||
@@ -1 +1 @@
|
||||
from .extism import Error, Plugin, set_log_file
|
||||
from .extism import Error, Plugin, set_log_file, Context
|
||||
|
||||
@@ -9,6 +9,7 @@ from typing import Union
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
'''Extism error type'''
|
||||
pass
|
||||
|
||||
|
||||
@@ -87,6 +88,7 @@ class Base64Encoder(json.JSONEncoder):
|
||||
|
||||
|
||||
def set_log_file(file, level=ffi.NULL):
|
||||
'''Sets the log file and level, this is a global configuration'''
|
||||
if isinstance(level, str):
|
||||
level = level.encode()
|
||||
lib.extism_log_file(file.encode(), level)
|
||||
@@ -105,47 +107,115 @@ def _wasm(plugin):
|
||||
return wasm
|
||||
|
||||
|
||||
class Context:
|
||||
'''Context is used to store and manage plugins'''
|
||||
|
||||
def __init__(self):
|
||||
self.pointer = lib.extism_context_new()
|
||||
|
||||
def __del__(self):
|
||||
lib.extism_context_free(self.pointer)
|
||||
self.pointer = ffi.NULL
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, type, exc, traceback):
|
||||
self.__del__()
|
||||
|
||||
def reset(self):
|
||||
'''Remove all registered plugins'''
|
||||
lib.extism_context_reset(self.pointer)
|
||||
|
||||
def plugin(self, plugin: Union[str, bytes, dict], wasi=False, config=None):
|
||||
'''Register a new plugin from a WASM module or JSON encoded manifest'''
|
||||
return Plugin(self, plugin, wasi, config)
|
||||
|
||||
|
||||
class Plugin:
|
||||
'''Plugin is used to call WASM functions'''
|
||||
|
||||
def __init__(self,
|
||||
context: Context,
|
||||
plugin: Union[str, bytes, dict],
|
||||
wasi=False,
|
||||
config=None):
|
||||
wasm = _wasm(plugin)
|
||||
|
||||
# Register plugin
|
||||
self.plugin = lib.extism_plugin_register(wasm, len(wasm), wasi)
|
||||
self.plugin = lib.extism_plugin_new(context.pointer, wasm, len(wasm),
|
||||
wasi)
|
||||
|
||||
self.ctx = context
|
||||
|
||||
if self.plugin < 0:
|
||||
error = lib.extism_error(-1)
|
||||
if error != ffi.NULL:
|
||||
raise Error(ffi.string(error).decode())
|
||||
raise Error("Unable to register plugin")
|
||||
|
||||
if config is not None:
|
||||
s = json.dumps(config).encode()
|
||||
lib.extism_plugin_config(self.plugin, s, len(s))
|
||||
|
||||
def update(self, plugin: Union[str, bytes, dict], wasi=False, config=None):
|
||||
'''Update a plugin with a new WASM module or manifest'''
|
||||
wasm = _wasm(plugin)
|
||||
ok = lib.extism_plugin_update(self.plugin, wasm, len(wasm), wasi)
|
||||
ok = lib.extism_plugin_update(self.ctx.pointer, self.plugin, wasm,
|
||||
len(wasm), wasi)
|
||||
if not ok:
|
||||
return False
|
||||
error = lib.extism_error(self.ctx.pointer, -1)
|
||||
if error != ffi.NULL:
|
||||
raise Error(ffi.string(error).decode())
|
||||
raise Error("Unable to update plugin")
|
||||
|
||||
if config is not None:
|
||||
s = json.dumps(config).encode()
|
||||
lib.extism_plugin_config(self.plugin, s, len(s))
|
||||
return True
|
||||
lib.extism_plugin_config(self.ctx.pointer, self.plugin, s, len(s))
|
||||
|
||||
def _check_error(self, rc):
|
||||
if rc != 0:
|
||||
error = lib.extism_error(self.plugin)
|
||||
error = lib.extism_error(self.ctx.pointer, self.plugin)
|
||||
if error != ffi.NULL:
|
||||
raise Error(ffi.string(error).decode())
|
||||
raise Error(f"Error code: {rc}")
|
||||
|
||||
def call(self, name: str, data: Union[str, bytes], parse=bytes) -> bytes:
|
||||
def function_exists(self, name: str) -> bool:
|
||||
'''Returns true if the given function exists'''
|
||||
return lib.extism_plugin_function_exists(self.ctx.pointer, self.plugin,
|
||||
name.encode())
|
||||
|
||||
def call(self, name: str, data: Union[str, bytes], parse=bytes):
|
||||
'''
|
||||
Call a function by name with the provided input data
|
||||
|
||||
The `parse` argument can be used to transform the output buffer into
|
||||
your expected type. It expects a function that takes a buffer as the
|
||||
only argument
|
||||
'''
|
||||
if isinstance(data, str):
|
||||
data = data.encode()
|
||||
self._check_error(
|
||||
lib.extism_call(self.plugin, name.encode(), data, len(data)))
|
||||
out_len = lib.extism_output_length(self.plugin)
|
||||
out_buf = lib.extism_output_get(self.plugin)
|
||||
lib.extism_plugin_call(self.ctx.pointer, self.plugin,
|
||||
name.encode(), data, len(data)))
|
||||
out_len = lib.extism_plugin_output_length(self.ctx.pointer,
|
||||
self.plugin)
|
||||
out_buf = lib.extism_plugin_output_data(self.ctx.pointer, self.plugin)
|
||||
buf = ffi.buffer(out_buf, out_len)
|
||||
if parse is None:
|
||||
return buf
|
||||
return parse(buf)
|
||||
|
||||
def __del__(self):
|
||||
if not hasattr(self, 'ctx'):
|
||||
return
|
||||
if self.ctx.pointer == ffi.NULL:
|
||||
return
|
||||
lib.extism_plugin_free(self.ctx.pointer, self.plugin)
|
||||
self.plugin = -1
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, type, exc, traceback):
|
||||
self.__del__()
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
require './lib/extism'
|
||||
require 'json'
|
||||
|
||||
manifest = {
|
||||
:wasm => [{:path => "../wasm/code.wasm"}]
|
||||
# a Context provides a scope for plugins to be managed within. creating multiple contexts
|
||||
# is expected and groups plugins based on source/tenant/lifetime etc.
|
||||
ctx = Extism::Context.new
|
||||
Extism::with_context {|ctx|
|
||||
manifest = {
|
||||
:wasm => [{:path => "../wasm/code.wasm"}]
|
||||
}
|
||||
|
||||
plugin = ctx.plugin(manifest)
|
||||
res = JSON.parse(plugin.call("count_vowels", ARGV[0] || "this is a test"))
|
||||
|
||||
puts res['count']
|
||||
}
|
||||
plugin = Extism::Plugin.new(manifest)
|
||||
res = JSON.parse(plugin.call("count_vowels", ARGV[0] || "this is a test"))
|
||||
puts res['count']
|
||||
|
||||
@@ -5,19 +5,25 @@ module Extism
|
||||
module C
|
||||
extend FFI::Library
|
||||
ffi_lib "extism"
|
||||
attach_function :extism_plugin_register, [:pointer, :uint64, :bool], :int32
|
||||
attach_function :extism_plugin_update, [:int32, :pointer, :uint64, :bool], :bool
|
||||
attach_function :extism_error, [:int32], :string
|
||||
attach_function :extism_call, [:int32, :string, :pointer, :uint64], :int32
|
||||
attach_function :extism_output_length, [:int32], :uint64
|
||||
attach_function :extism_output_get, [:int32], :pointer
|
||||
attach_function :extism_context_new, [], :pointer
|
||||
attach_function :extism_context_free, [:pointer], :void
|
||||
attach_function :extism_plugin_new, [:pointer, :pointer, :uint64, :bool], :int32
|
||||
attach_function :extism_plugin_update, [:pointer, :int32, :pointer, :uint64, :bool], :bool
|
||||
attach_function :extism_error, [:pointer, :int32], :string
|
||||
attach_function :extism_plugin_call, [:pointer, :int32, :string, :pointer, :uint64], :int32
|
||||
attach_function :extism_plugin_function_exists, [:pointer, :int32, :string], :bool
|
||||
attach_function :extism_plugin_output_length, [:pointer, :int32], :uint64
|
||||
attach_function :extism_plugin_output_data, [:pointer, :int32], :pointer
|
||||
attach_function :extism_log_file, [:string, :pointer], :void
|
||||
attach_function :extism_plugin_free, [:pointer, :int32], :void
|
||||
attach_function :extism_context_reset, [:pointer], :void
|
||||
end
|
||||
|
||||
|
||||
class Error < StandardError
|
||||
end
|
||||
|
||||
# Set log file and level, this is a global configuration
|
||||
def self.set_log_file(name, level=nil)
|
||||
if level then
|
||||
level = FFI::MemoryPointer::from_string(level)
|
||||
@@ -25,54 +31,147 @@ module Extism
|
||||
C.extism_log_file(name, level)
|
||||
end
|
||||
|
||||
$PLUGINS = {}
|
||||
$FREE_PLUGIN = proc { |id|
|
||||
x = $PLUGINS[id]
|
||||
if !x.nil? then
|
||||
C.extism_plugin_free(x[:context].pointer, x[:plugin])
|
||||
$PLUGINS.delete(id)
|
||||
end
|
||||
}
|
||||
|
||||
$CONTEXTS = {}
|
||||
$FREE_CONTEXT = proc { |id|
|
||||
x = $CONTEXTS[id]
|
||||
if !x.nil? then
|
||||
C.extism_context_free($CONTEXTS[id])
|
||||
$CONTEXTS.delete(id)
|
||||
end
|
||||
}
|
||||
|
||||
# Context is used to manage plugins
|
||||
class Context
|
||||
attr_accessor :pointer
|
||||
|
||||
def initialize
|
||||
@pointer = C.extism_context_new()
|
||||
$CONTEXTS[self.object_id] = @pointer
|
||||
ObjectSpace.define_finalizer(self, $FREE_CONTEXT)
|
||||
end
|
||||
|
||||
# Remove all registered plugins
|
||||
def reset
|
||||
C.extism_context_reset(@pointer)
|
||||
end
|
||||
|
||||
# Free the context, this should be called when it is no longer needed
|
||||
def free
|
||||
if @pointer.nil? then
|
||||
return
|
||||
end
|
||||
$CONTEXTS.delete(self.object_id)
|
||||
C.extism_context_free(@pointer)
|
||||
@pointer = nil
|
||||
end
|
||||
|
||||
# Create a new plugin from a WASM module or JSON encoded manifest
|
||||
def plugin(wasm, wasi=false, config=nil)
|
||||
return Plugin.new(self, wasm, wasi, config)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def self.with_context(&block)
|
||||
ctx = Context.new
|
||||
begin
|
||||
x = block.call(ctx)
|
||||
return x
|
||||
ensure
|
||||
ctx.free
|
||||
end
|
||||
end
|
||||
|
||||
class Plugin
|
||||
def initialize(wasm, wasi=false, config=nil)
|
||||
def initialize(context, wasm, wasi=false, config=nil)
|
||||
if wasm.class == Hash then
|
||||
wasm = JSON.generate(wasm)
|
||||
end
|
||||
code = FFI::MemoryPointer.new(:char, wasm.bytesize)
|
||||
code.put_bytes(0, wasm)
|
||||
@plugin = C.extism_plugin_register(code, wasm.bytesize, wasi)
|
||||
|
||||
@plugin = C.extism_plugin_new(context.pointer, code, wasm.bytesize, wasi)
|
||||
if @plugin < 0 then
|
||||
err = C.extism_error(-1)
|
||||
if err&.empty? then
|
||||
raise Error.new "extism_plugin_new failed"
|
||||
else raise Error.new err
|
||||
end
|
||||
end
|
||||
@context = context
|
||||
$PLUGINS[self.object_id] = {:plugin => @plugin, :context => context}
|
||||
ObjectSpace.define_finalizer(self, $FREE_PLUGIN)
|
||||
if config != nil and @plugin >= 0 then
|
||||
s = JSON.generate(config)
|
||||
ptr = FFI::MemoryPointer::from_string(s)
|
||||
C.extism_plugin_config(@plugin, ptr, s.bytesize)
|
||||
C.extism_plugin_config(@context.pointer, @plugin, ptr, s.bytesize)
|
||||
end
|
||||
end
|
||||
|
||||
# Update a plugin with new WASM module or manifest
|
||||
def update(wasm, wasi=false, config=nil)
|
||||
if wasm.class == Hash then
|
||||
wasm = JSON.generate(wasm)
|
||||
end
|
||||
code = FFI::MemoryPointer.new(:char, wasm.bytesize)
|
||||
code.put_bytes(0, wasm)
|
||||
ok = C.extism_plugin_update(@plugin, code, wasm.bytesize, wasi)
|
||||
if ok then
|
||||
if config != nil then
|
||||
s = JSON.generate(config)
|
||||
ptr = FFI::MemoryPointer::from_string(s)
|
||||
C.extism_plugin_config(@plugin, ptr, s.bytesize)
|
||||
ok = C.extism_plugin_update(@context.pointer, @plugin, code, wasm.bytesize, wasi)
|
||||
if !ok then
|
||||
err = C.extism_error(-1)
|
||||
if err&.empty? then
|
||||
raise Error.new "extism_plugin_update failed"
|
||||
else raise Error.new err
|
||||
end
|
||||
end
|
||||
return ok
|
||||
|
||||
if config != nil then
|
||||
s = JSON.generate(config)
|
||||
ptr = FFI::MemoryPointer::from_string(s)
|
||||
C.extism_plugin_config(@context.pointer, @plugin, ptr, s.bytesize)
|
||||
end
|
||||
end
|
||||
|
||||
# Check if a function exists
|
||||
def function_exists(name)
|
||||
return C.extism_function_exists(@context.pointer, @plugin, name)
|
||||
end
|
||||
|
||||
# Call a function by name
|
||||
def call(name, data, &block)
|
||||
# If no block was passed then use Pointer::read_string
|
||||
block ||= ->(buf, len){ buf.read_string(len) }
|
||||
input = FFI::MemoryPointer::from_string(data)
|
||||
rc = C.extism_call(@plugin, name, input, data.bytesize)
|
||||
rc = C.extism_plugin_call(@context.pointer, @plugin, name, input, data.bytesize)
|
||||
if rc != 0 then
|
||||
err = C.extism_error(@plugin)
|
||||
err = C.extism_error(@context.pointer, @plugin)
|
||||
if err&.empty? then
|
||||
raise Error.new "extism_call failed"
|
||||
else raise Error.new err
|
||||
end
|
||||
end
|
||||
out_len = C.extism_output_length(@plugin)
|
||||
buf = C.extism_output_get(@plugin)
|
||||
out_len = C.extism_plugin_output_length(@context.pointer, @plugin)
|
||||
buf = C.extism_plugin_output_data(@context.pointer, @plugin)
|
||||
return block.call(buf, out_len)
|
||||
end
|
||||
|
||||
# Free a plugin, this should be called when the plugin is no longer needed
|
||||
def free
|
||||
if @context.pointer.nil? then
|
||||
return
|
||||
end
|
||||
|
||||
$PLUGINS.delete(self.object_id)
|
||||
C.extism_plugin_free(@context.pointer, @plugin)
|
||||
@plugin = -1
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -12,6 +12,7 @@ fn main() {
|
||||
.with_pragma_once(true)
|
||||
.rename_item("Size", "ExtismSize")
|
||||
.rename_item("PluginIndex", "ExtismPlugin")
|
||||
.rename_item("Context", "ExtismContext")
|
||||
.generate()
|
||||
{
|
||||
bindings.write_to_file("extism.h");
|
||||
|
||||
@@ -3,30 +3,106 @@
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/**
|
||||
* A `Context` is used to store and manage plugins
|
||||
*/
|
||||
typedef struct ExtismContext ExtismContext;
|
||||
|
||||
typedef int32_t ExtismPlugin;
|
||||
|
||||
typedef uint64_t ExtismSize;
|
||||
|
||||
ExtismPlugin extism_plugin_register(const uint8_t *wasm, ExtismSize wasm_size, bool with_wasi);
|
||||
/**
|
||||
* Create a new context
|
||||
*/
|
||||
struct ExtismContext *extism_context_new(void);
|
||||
|
||||
bool extism_plugin_update(ExtismPlugin index,
|
||||
/**
|
||||
* Free a context
|
||||
*/
|
||||
void extism_context_free(struct ExtismContext *ctx);
|
||||
|
||||
/**
|
||||
* Create a new plugin
|
||||
*
|
||||
* `wasm`: is a WASM module (wat or wasm) or a JSON encoded manifest
|
||||
* `wasm_size`: the length of the `wasm` parameter
|
||||
* `with_wasi`: enables/disables WASI
|
||||
*/
|
||||
ExtismPlugin extism_plugin_new(struct ExtismContext *ctx,
|
||||
const uint8_t *wasm,
|
||||
ExtismSize wasm_size,
|
||||
bool with_wasi);
|
||||
|
||||
/**
|
||||
* Update a plugin, keeping the existing ID
|
||||
*
|
||||
* Similar to `extism_plugin_new` but takes an `index` argument to specify
|
||||
* which plugin to update
|
||||
*
|
||||
* Memory for this plugin will be reset upon update
|
||||
*/
|
||||
bool extism_plugin_update(struct ExtismContext *ctx,
|
||||
ExtismPlugin index,
|
||||
const uint8_t *wasm,
|
||||
ExtismSize wasm_size,
|
||||
bool with_wasi);
|
||||
|
||||
bool extism_plugin_config(ExtismPlugin plugin, const uint8_t *json, ExtismSize json_size);
|
||||
/**
|
||||
* Remove a plugin from the registry and free associated memory
|
||||
*/
|
||||
void extism_plugin_free(struct ExtismContext *ctx, ExtismPlugin plugin);
|
||||
|
||||
bool extism_function_exists(ExtismPlugin plugin, const char *func_name);
|
||||
/**
|
||||
* Remove all plugins from the registry
|
||||
*/
|
||||
void extism_context_reset(struct ExtismContext *ctx);
|
||||
|
||||
int32_t extism_call(ExtismPlugin plugin_id,
|
||||
const char *func_name,
|
||||
const uint8_t *data,
|
||||
ExtismSize data_len);
|
||||
/**
|
||||
* Update plugin config values, this will merge with the existing values
|
||||
*/
|
||||
bool extism_plugin_config(struct ExtismContext *ctx,
|
||||
ExtismPlugin plugin,
|
||||
const uint8_t *json,
|
||||
ExtismSize json_size);
|
||||
|
||||
const char *extism_error(ExtismPlugin plugin);
|
||||
/**
|
||||
* Returns true if `func_name` exists
|
||||
*/
|
||||
bool extism_plugin_function_exists(struct ExtismContext *ctx,
|
||||
ExtismPlugin plugin,
|
||||
const char *func_name);
|
||||
|
||||
ExtismSize extism_output_length(ExtismPlugin plugin);
|
||||
/**
|
||||
* Call a function
|
||||
*
|
||||
* `func_name`: is the function to call
|
||||
* `data`: is the input data
|
||||
* `data_len`: is the length of `data`
|
||||
*/
|
||||
int32_t extism_plugin_call(struct ExtismContext *ctx,
|
||||
ExtismPlugin plugin_id,
|
||||
const char *func_name,
|
||||
const uint8_t *data,
|
||||
ExtismSize data_len);
|
||||
|
||||
const uint8_t *extism_output_get(ExtismPlugin plugin);
|
||||
/**
|
||||
* Get the error associated with a `Context` or `Plugin`, if `plugin` is `-1` then the context
|
||||
* error will be returned
|
||||
*/
|
||||
const char *extism_error(struct ExtismContext *ctx, ExtismPlugin plugin);
|
||||
|
||||
/**
|
||||
* Get the length of a plugin's output data
|
||||
*/
|
||||
ExtismSize extism_plugin_output_length(struct ExtismContext *ctx, ExtismPlugin plugin);
|
||||
|
||||
/**
|
||||
* Get the length of a plugin's output data
|
||||
*/
|
||||
const uint8_t *extism_plugin_output_data(struct ExtismContext *ctx, ExtismPlugin plugin);
|
||||
|
||||
/**
|
||||
* Set log file and level
|
||||
*/
|
||||
bool extism_log_file(const char *filename, const char *log_level);
|
||||
|
||||
74
runtime/src/context.rs
Normal file
74
runtime/src/context.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::*;
|
||||
|
||||
/// A `Context` is used to store and manage plugins
|
||||
#[derive(Default)]
|
||||
pub struct Context {
|
||||
/// Plugin registry
|
||||
pub plugins: BTreeMap<PluginIndex, Plugin>,
|
||||
|
||||
/// Error message
|
||||
pub error: Option<std::ffi::CString>,
|
||||
next_id: std::sync::atomic::AtomicI32,
|
||||
reclaimed_ids: Vec<PluginIndex>,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
/// Create a new context
|
||||
pub fn new() -> Context {
|
||||
Context {
|
||||
plugins: BTreeMap::new(),
|
||||
error: None,
|
||||
next_id: std::sync::atomic::AtomicI32::new(0),
|
||||
reclaimed_ids: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the next valid plugin ID
|
||||
pub fn next_id(&mut self) -> Result<PluginIndex, Error> {
|
||||
// Make sure we haven't exhausted all plugin IDs, it reach this it would require the machine
|
||||
// running this code to have a lot of memory - no computer I tested on was able to allocate
|
||||
// this many plugins.
|
||||
if self.next_id.load(std::sync::atomic::Ordering::SeqCst) == PluginIndex::MAX {
|
||||
// Since `Context::remove` collects IDs that have been removed we will
|
||||
// try to use one of those before returning an error
|
||||
match self.reclaimed_ids.pop() {
|
||||
None => {
|
||||
return Err(anyhow::format_err!(
|
||||
"All plugin descriptors are in use, unable to allocate a new plugin"
|
||||
))
|
||||
}
|
||||
Some(x) => return Ok(x),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(self
|
||||
.next_id
|
||||
.fetch_add(1, std::sync::atomic::Ordering::SeqCst))
|
||||
}
|
||||
|
||||
/// Set the context error
|
||||
pub fn set_error(&mut self, e: impl std::fmt::Debug) {
|
||||
self.error = Some(error_string(e));
|
||||
}
|
||||
|
||||
/// Convenience function to set error and return the value passed as the final parameter
|
||||
pub fn error<T>(&mut self, e: impl std::fmt::Debug, x: T) -> T {
|
||||
self.set_error(e);
|
||||
x
|
||||
}
|
||||
|
||||
/// Get a plugin from the context
|
||||
pub fn plugin(&mut self, id: PluginIndex) -> Option<&mut Plugin> {
|
||||
self.plugins.get_mut(&id)
|
||||
}
|
||||
|
||||
/// Remove a plugin from the context
|
||||
pub fn remove(&mut self, id: PluginIndex) {
|
||||
self.plugins.remove(&id);
|
||||
|
||||
// Collect old IDs in case we need to re-use them
|
||||
self.reclaimed_ids.push(id);
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,9 @@
|
||||
/// All the functions in the file are exposed from inside WASM plugins
|
||||
use crate::*;
|
||||
|
||||
macro_rules! plugin {
|
||||
(mut $a:expr) => {
|
||||
unsafe { (&mut *$a.plugin) }
|
||||
};
|
||||
|
||||
($a:expr) => {
|
||||
unsafe { (&*$a.plugin) }
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! memory {
|
||||
(mut $a:expr) => {
|
||||
&mut plugin!(mut $a).memory
|
||||
};
|
||||
|
||||
($a:expr) => {
|
||||
&plugin!($a).memory
|
||||
};
|
||||
}
|
||||
|
||||
/// Get the input length
|
||||
/// Params: none
|
||||
/// Returns: i64 (length)
|
||||
pub(crate) fn input_length(
|
||||
caller: Caller<Internal>,
|
||||
_input: &[Val],
|
||||
@@ -27,9 +11,12 @@ pub(crate) fn input_length(
|
||||
) -> Result<(), Trap> {
|
||||
let data: &Internal = caller.data();
|
||||
output[0] = Val::I64(data.input_length as i64);
|
||||
return Ok(());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Load a byte from input
|
||||
/// Params: i64 (offset)
|
||||
/// Returns: i32 (byte)
|
||||
pub(crate) fn input_load_u8(
|
||||
caller: Caller<Internal>,
|
||||
input: &[Val],
|
||||
@@ -43,6 +30,9 @@ pub(crate) fn input_load_u8(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Load an unsigned 64 bit integer from input
|
||||
/// Params: i64 (offset)
|
||||
/// Returns: i64 (int)
|
||||
pub(crate) fn input_load_u64(
|
||||
caller: Caller<Internal>,
|
||||
input: &[Val],
|
||||
@@ -59,6 +49,108 @@ pub(crate) fn input_load_u64(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Store a byte in memory
|
||||
/// Params: i64 (offset), i32 (byte)
|
||||
/// Returns: none
|
||||
pub(crate) fn store_u8(
|
||||
mut caller: Caller<Internal>,
|
||||
input: &[Val],
|
||||
_output: &mut [Val],
|
||||
) -> Result<(), Trap> {
|
||||
let data: &mut Internal = caller.data_mut();
|
||||
let byte = input[1].unwrap_i32() as u8;
|
||||
data.memory_mut()
|
||||
.store_u8(input[0].unwrap_i64() as usize, byte)
|
||||
.map_err(|_| Trap::new("Write error"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Load a byte from memory
|
||||
/// Params: i64 (offset)
|
||||
/// Returns: i32 (byte)
|
||||
pub(crate) fn load_u8(
|
||||
caller: Caller<Internal>,
|
||||
input: &[Val],
|
||||
output: &mut [Val],
|
||||
) -> Result<(), Trap> {
|
||||
let data: &Internal = caller.data();
|
||||
let byte = data
|
||||
.memory()
|
||||
.load_u8(input[0].unwrap_i64() as usize)
|
||||
.map_err(|_| Trap::new("Read error"))?;
|
||||
output[0] = Val::I32(byte as i32);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Store an unsigned 32 bit integer in memory
|
||||
/// Params: i64 (offset), i32 (int)
|
||||
/// Returns: none
|
||||
pub(crate) fn store_u32(
|
||||
mut caller: Caller<Internal>,
|
||||
input: &[Val],
|
||||
_output: &mut [Val],
|
||||
) -> Result<(), Trap> {
|
||||
let data: &mut Internal = caller.data_mut();
|
||||
let b = input[1].unwrap_i32() as u32;
|
||||
data.memory_mut()
|
||||
.store_u32(input[0].unwrap_i64() as usize, b)
|
||||
.map_err(|_| Trap::new("Write error"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Load an unsigned 32 bit integer from memory
|
||||
/// Params: i64 (offset)
|
||||
/// Returns: i32 (int)
|
||||
pub(crate) fn load_u32(
|
||||
caller: Caller<Internal>,
|
||||
input: &[Val],
|
||||
output: &mut [Val],
|
||||
) -> Result<(), Trap> {
|
||||
let data: &Internal = caller.data();
|
||||
let b = data
|
||||
.memory()
|
||||
.load_u32(input[0].unwrap_i64() as usize)
|
||||
.map_err(|_| Trap::new("Read error"))?;
|
||||
output[0] = Val::I32(b as i32);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Store an unsigned 64 bit integer in memory
|
||||
/// Params: i64 (offset), i64 (int)
|
||||
/// Returns: none
|
||||
pub(crate) fn store_u64(
|
||||
mut caller: Caller<Internal>,
|
||||
input: &[Val],
|
||||
_output: &mut [Val],
|
||||
) -> Result<(), Trap> {
|
||||
let data: &mut Internal = caller.data_mut();
|
||||
let b = input[1].unwrap_i64() as u64;
|
||||
data.memory_mut()
|
||||
.store_u64(input[0].unwrap_i64() as usize, b)
|
||||
.map_err(|_| Trap::new("Write error"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Load an unsigned 64 bit integer from memory
|
||||
/// Params: i64 (offset)
|
||||
/// Returns: i64 (int)
|
||||
pub(crate) fn load_u64(
|
||||
caller: Caller<Internal>,
|
||||
input: &[Val],
|
||||
output: &mut [Val],
|
||||
) -> Result<(), Trap> {
|
||||
let data: &Internal = caller.data();
|
||||
let byte = data
|
||||
.memory()
|
||||
.load_u64(input[0].unwrap_i64() as usize)
|
||||
.map_err(|_| Trap::new("Read error"))?;
|
||||
output[0] = Val::I64(byte as i64);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set output offset and length
|
||||
/// Params: i64 (offset), i64 (length)
|
||||
/// Returns: none
|
||||
pub(crate) fn output_set(
|
||||
mut caller: Caller<Internal>,
|
||||
input: &[Val],
|
||||
@@ -70,18 +162,24 @@ pub(crate) fn output_set(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Allocate bytes
|
||||
/// Params: i64 (length)
|
||||
/// Returns: i64 (offset)
|
||||
pub(crate) fn alloc(
|
||||
mut caller: Caller<Internal>,
|
||||
input: &[Val],
|
||||
output: &mut [Val],
|
||||
) -> Result<(), Trap> {
|
||||
let data: &mut Internal = caller.data_mut();
|
||||
let offs = memory!(mut data).alloc(input[0].unwrap_i64() as _)?;
|
||||
let offs = data.memory_mut().alloc(input[0].unwrap_i64() as _)?;
|
||||
output[0] = Val::I64(offs.offset as i64);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Free memory
|
||||
/// Params: i64 (offset)
|
||||
/// Returns: none
|
||||
pub(crate) fn free(
|
||||
mut caller: Caller<Internal>,
|
||||
input: &[Val],
|
||||
@@ -89,88 +187,13 @@ pub(crate) fn free(
|
||||
) -> Result<(), Trap> {
|
||||
let data: &mut Internal = caller.data_mut();
|
||||
let offset = input[0].unwrap_i64() as usize;
|
||||
memory!(mut data).free(offset);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn store_u8(
|
||||
mut caller: Caller<Internal>,
|
||||
input: &[Val],
|
||||
_output: &mut [Val],
|
||||
) -> Result<(), Trap> {
|
||||
let data: &mut Internal = caller.data_mut();
|
||||
let byte = input[1].unwrap_i32() as u8;
|
||||
memory!(mut data)
|
||||
.store_u8(input[0].unwrap_i64() as usize, byte)
|
||||
.map_err(|_| Trap::new("Write error"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn load_u8(
|
||||
mut caller: Caller<Internal>,
|
||||
input: &[Val],
|
||||
output: &mut [Val],
|
||||
) -> Result<(), Trap> {
|
||||
let data: &mut Internal = caller.data_mut();
|
||||
let byte = memory!(data)
|
||||
.load_u8(input[0].unwrap_i64() as usize)
|
||||
.map_err(|_| Trap::new("Read error"))?;
|
||||
output[0] = Val::I32(byte as i32);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn store_u32(
|
||||
mut caller: Caller<Internal>,
|
||||
input: &[Val],
|
||||
_output: &mut [Val],
|
||||
) -> Result<(), Trap> {
|
||||
let data: &mut Internal = caller.data_mut();
|
||||
let b = input[1].unwrap_i32() as u32;
|
||||
memory!(mut data)
|
||||
.store_u32(input[0].unwrap_i64() as usize, b)
|
||||
.map_err(|_| Trap::new("Write error"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn load_u32(
|
||||
mut caller: Caller<Internal>,
|
||||
input: &[Val],
|
||||
output: &mut [Val],
|
||||
) -> Result<(), Trap> {
|
||||
let data: &mut Internal = caller.data_mut();
|
||||
let b = memory!(data)
|
||||
.load_u32(input[0].unwrap_i64() as usize)
|
||||
.map_err(|_| Trap::new("Read error"))?;
|
||||
output[0] = Val::I32(b as i32);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn store_u64(
|
||||
mut caller: Caller<Internal>,
|
||||
input: &[Val],
|
||||
_output: &mut [Val],
|
||||
) -> Result<(), Trap> {
|
||||
let data: &mut Internal = caller.data_mut();
|
||||
let b = input[1].unwrap_i64() as u64;
|
||||
memory!(mut data)
|
||||
.store_u64(input[0].unwrap_i64() as usize, b)
|
||||
.map_err(|_| Trap::new("Write error"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn load_u64(
|
||||
mut caller: Caller<Internal>,
|
||||
input: &[Val],
|
||||
output: &mut [Val],
|
||||
) -> Result<(), Trap> {
|
||||
let data: &mut Internal = caller.data_mut();
|
||||
let byte = memory!(data)
|
||||
.load_u64(input[0].unwrap_i64() as usize)
|
||||
.map_err(|_| Trap::new("Read error"))?;
|
||||
output[0] = Val::I64(byte as i64);
|
||||
data.memory_mut().free(offset);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the error message, this can be checked by the host program
|
||||
/// Params: i64 (offset)
|
||||
/// Returns: none
|
||||
pub(crate) fn error_set(
|
||||
mut caller: Caller<Internal>,
|
||||
input: &[Val],
|
||||
@@ -178,23 +201,27 @@ pub(crate) fn error_set(
|
||||
) -> Result<(), Trap> {
|
||||
let data: &mut Internal = caller.data_mut();
|
||||
let offset = input[0].unwrap_i64() as usize;
|
||||
let length = match memory!(data).block_length(offset) {
|
||||
let length = match data.memory().block_length(offset) {
|
||||
Some(x) => x,
|
||||
None => return Err(Trap::new("Invalid offset in call to error_set")),
|
||||
};
|
||||
|
||||
let handle = MemoryBlock { offset, length };
|
||||
if handle.offset == 0 {
|
||||
plugin!(mut data).clear_error();
|
||||
data.plugin_mut().clear_error();
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let buf = memory!(data).get(handle);
|
||||
let buf = data.memory().ptr(handle);
|
||||
let buf = unsafe { std::slice::from_raw_parts(buf, length) };
|
||||
let s = unsafe { std::str::from_utf8_unchecked(buf) };
|
||||
plugin!(mut data).set_error(s);
|
||||
data.plugin_mut().set_error(s);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get a configuration value
|
||||
/// Params: i64 (offset)
|
||||
/// Returns: i64 (offset)
|
||||
pub(crate) fn config_get(
|
||||
mut caller: Caller<Internal>,
|
||||
input: &[Val],
|
||||
@@ -202,16 +229,24 @@ pub(crate) fn config_get(
|
||||
) -> Result<(), Trap> {
|
||||
let data: &mut Internal = caller.data_mut();
|
||||
let offset = input[0].unwrap_i64() as usize;
|
||||
let length = match memory!(data).block_length(offset) {
|
||||
let length = match data.memory().block_length(offset) {
|
||||
Some(x) => x,
|
||||
None => return Err(Trap::new("Invalid offset in call to config_get")),
|
||||
};
|
||||
|
||||
let buf = memory!(data).get((offset, length));
|
||||
let buf = data.memory().get((offset, length));
|
||||
let str = unsafe { std::str::from_utf8_unchecked(buf) };
|
||||
let val = plugin!(data).manifest.as_ref().config.get(str);
|
||||
let val = data
|
||||
.plugin()
|
||||
.manifest
|
||||
.as_ref()
|
||||
.config
|
||||
.get(str)
|
||||
.map(|x| x.as_ptr());
|
||||
let mem = match val {
|
||||
Some(f) => memory!(mut data).alloc_bytes(f.as_bytes())?,
|
||||
Some(f) => data
|
||||
.memory_mut()
|
||||
.alloc_bytes(unsafe { std::slice::from_raw_parts(f, length) })?,
|
||||
None => {
|
||||
output[0] = Val::I64(0);
|
||||
return Ok(());
|
||||
@@ -222,6 +257,9 @@ pub(crate) fn config_get(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get a variable
|
||||
/// Params: i64 (offset)
|
||||
/// Returns: i64 (offset)
|
||||
pub(crate) fn var_get(
|
||||
mut caller: Caller<Internal>,
|
||||
input: &[Val],
|
||||
@@ -229,16 +267,21 @@ pub(crate) fn var_get(
|
||||
) -> Result<(), Trap> {
|
||||
let data: &mut Internal = caller.data_mut();
|
||||
let offset = input[0].unwrap_i64() as usize;
|
||||
let length = match memory!(data).block_length(offset) {
|
||||
let length = match data.memory().block_length(offset) {
|
||||
Some(x) => x,
|
||||
None => return Err(Trap::new("Invalid offset in call to var_get")),
|
||||
};
|
||||
|
||||
let buf = memory!(data).get((offset, length));
|
||||
let str = unsafe { std::str::from_utf8_unchecked(buf) };
|
||||
let val = data.vars.get(str);
|
||||
let val = {
|
||||
let buf = data.memory().ptr((offset, length));
|
||||
let buf = unsafe { std::slice::from_raw_parts(buf, length) };
|
||||
let str = unsafe { std::str::from_utf8_unchecked(buf) };
|
||||
data.vars.get(str).map(|x| x.as_ptr())
|
||||
};
|
||||
let mem = match val {
|
||||
Some(f) => memory!(mut data).alloc_bytes(&f)?,
|
||||
Some(f) => data
|
||||
.memory_mut()
|
||||
.alloc_bytes(unsafe { std::slice::from_raw_parts(f, length) })?,
|
||||
None => {
|
||||
output[0] = Val::I64(0);
|
||||
return Ok(());
|
||||
@@ -249,6 +292,9 @@ pub(crate) fn var_get(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set a variable, if the value offset is 0 then the provided key will be removed
|
||||
/// Params: i64 (key offset), i64 (value offset)
|
||||
/// Returns: none
|
||||
pub(crate) fn var_set(
|
||||
mut caller: Caller<Internal>,
|
||||
input: &[Val],
|
||||
@@ -269,30 +315,35 @@ pub(crate) fn var_set(
|
||||
}
|
||||
|
||||
let koffset = input[0].unwrap_i64() as usize;
|
||||
let klength = match memory!(data).block_length(koffset) {
|
||||
let klength = match data.memory().block_length(koffset) {
|
||||
Some(x) => x,
|
||||
None => return Err(Trap::new("Invalid offset for key in call to var_set")),
|
||||
};
|
||||
|
||||
let kbuf = memory!(data).get((koffset, klength));
|
||||
let kbuf = data.memory().ptr((koffset, klength));
|
||||
let kbuf = unsafe { std::slice::from_raw_parts(kbuf, klength) };
|
||||
let kstr = unsafe { std::str::from_utf8_unchecked(kbuf) };
|
||||
|
||||
// Remove if the value offset is 0
|
||||
if voffset == 0 {
|
||||
data.vars.remove(kstr);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let vlength = match memory!(data).block_length(voffset) {
|
||||
let vlength = match data.memory().block_length(voffset) {
|
||||
Some(x) => x,
|
||||
None => return Err(Trap::new("Invalid offset for value in call to var_set")),
|
||||
};
|
||||
|
||||
let vbuf = memory!(data).get((voffset, vlength));
|
||||
let vbuf = data.memory().get((voffset, vlength));
|
||||
|
||||
data.vars.insert(kstr.to_string(), vbuf.to_vec());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Make an HTTP request
|
||||
/// Params: i64 (offset to JSON encoded HttpRequest), i64 (offset to body or 0)
|
||||
/// Returns: i64 (offset)
|
||||
pub(crate) fn http_request(
|
||||
#[allow(unused_mut)] mut caller: Caller<Internal>,
|
||||
input: &[Val],
|
||||
@@ -313,11 +364,11 @@ pub(crate) fn http_request(
|
||||
let data: &mut Internal = caller.data_mut();
|
||||
let offset = input[0].unwrap_i64() as usize;
|
||||
|
||||
let length = match memory!(data).block_length(offset) {
|
||||
let length = match data.memory().block_length(offset) {
|
||||
Some(x) => x,
|
||||
None => return Err(Trap::new("Invalid offset in call to http_request")),
|
||||
};
|
||||
let buf = memory!(data).get((offset, length));
|
||||
let buf = data.memory().get((offset, length));
|
||||
let req: extism_manifest::HttpRequest =
|
||||
serde_json::from_slice(buf).map_err(|_| Trap::new("Invalid http request"))?;
|
||||
|
||||
@@ -330,11 +381,11 @@ pub(crate) fn http_request(
|
||||
}
|
||||
|
||||
let mut res = if body_offset > 0 {
|
||||
let length = match memory!(data).block_length(body_offset) {
|
||||
let length = match data.memory().block_length(body_offset) {
|
||||
Some(x) => x,
|
||||
None => return Err(Trap::new("Invalid offset in call to http_request")),
|
||||
};
|
||||
let buf = memory!(data).get((offset, length));
|
||||
let buf = data.memory().get((offset, length));
|
||||
r.send_bytes(buf)
|
||||
.map_err(|e| Trap::new(&format!("Request error: {e:?}")))?
|
||||
.into_reader()
|
||||
@@ -348,13 +399,16 @@ pub(crate) fn http_request(
|
||||
res.read_to_end(&mut buf)
|
||||
.map_err(|e| Trap::new(format!("{:?}", e)))?;
|
||||
|
||||
let mem = memory!(mut data).alloc_bytes(buf)?;
|
||||
let mem = data.memory_mut().alloc_bytes(buf)?;
|
||||
|
||||
output[0] = Val::I64(mem.offset as i64);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the length of an allocated block given the offset
|
||||
/// Params: i64 (offset)
|
||||
/// Returns: i64 (length or 0)
|
||||
pub(crate) fn length(
|
||||
mut caller: Caller<Internal>,
|
||||
input: &[Val],
|
||||
@@ -366,7 +420,7 @@ pub(crate) fn length(
|
||||
output[0] = Val::I64(0);
|
||||
return Ok(());
|
||||
}
|
||||
let length = match memory!(data).block_length(offset) {
|
||||
let length = match data.memory().block_length(offset) {
|
||||
Some(x) => x,
|
||||
None => return Err(Trap::new("Unable to find length for offset")),
|
||||
};
|
||||
@@ -374,7 +428,7 @@ pub(crate) fn length(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn log(
|
||||
pub fn log(
|
||||
level: log::Level,
|
||||
caller: Caller<Internal>,
|
||||
input: &[Val],
|
||||
@@ -383,11 +437,11 @@ pub(crate) fn log(
|
||||
let data: &Internal = caller.data();
|
||||
let offset = input[0].unwrap_i64() as usize;
|
||||
|
||||
let length = match memory!(data).block_length(offset) {
|
||||
let length = match data.memory().block_length(offset) {
|
||||
Some(x) => x,
|
||||
None => return Err(Trap::new("Invalid offset in call to http_request")),
|
||||
};
|
||||
let buf = memory!(data).get((offset, length));
|
||||
let buf = data.memory().get((offset, length));
|
||||
|
||||
match std::str::from_utf8(buf) {
|
||||
Ok(buf) => log::log!(level, "{}", buf),
|
||||
@@ -396,6 +450,9 @@ pub(crate) fn log(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write to logs (warning)
|
||||
/// Params: i64 (offset)
|
||||
/// Returns: none
|
||||
pub(crate) fn log_warn(
|
||||
caller: Caller<Internal>,
|
||||
input: &[Val],
|
||||
@@ -404,6 +461,9 @@ pub(crate) fn log_warn(
|
||||
log(log::Level::Warn, caller, input, _output)
|
||||
}
|
||||
|
||||
/// Write to logs (info)
|
||||
/// Params: i64 (offset)
|
||||
/// Returns: none
|
||||
pub(crate) fn log_info(
|
||||
caller: Caller<Internal>,
|
||||
input: &[Val],
|
||||
@@ -412,6 +472,9 @@ pub(crate) fn log_info(
|
||||
log(log::Level::Info, caller, input, _output)
|
||||
}
|
||||
|
||||
/// Write to logs (debug)
|
||||
/// Params: i64 (offset)
|
||||
/// Returns: none
|
||||
pub(crate) fn log_debug(
|
||||
caller: Caller<Internal>,
|
||||
input: &[Val],
|
||||
@@ -420,6 +483,9 @@ pub(crate) fn log_debug(
|
||||
log(log::Level::Debug, caller, input, _output)
|
||||
}
|
||||
|
||||
/// Write to logs (error)
|
||||
/// Params: i64 (offset)
|
||||
/// Returns: none
|
||||
pub(crate) fn log_error(
|
||||
caller: Caller<Internal>,
|
||||
input: &[Val],
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
pub use anyhow::Error;
|
||||
pub(crate) use wasmtime::*;
|
||||
|
||||
mod context;
|
||||
pub(crate) mod export;
|
||||
pub mod manifest;
|
||||
mod memory;
|
||||
@@ -8,12 +9,25 @@ mod plugin;
|
||||
mod plugin_ref;
|
||||
pub mod sdk;
|
||||
|
||||
pub use context::Context;
|
||||
pub use manifest::Manifest;
|
||||
pub use memory::{MemoryBlock, PluginMemory};
|
||||
pub use plugin::{Internal, Plugin, PLUGINS};
|
||||
pub use plugin::{Internal, Plugin};
|
||||
pub use plugin_ref::PluginRef;
|
||||
|
||||
pub type Size = u64;
|
||||
pub type PluginIndex = i32;
|
||||
|
||||
pub(crate) use log::{debug, error, info, trace};
|
||||
|
||||
/// Converts any type implementing `std::fmt::Debug` into a suitable CString to use
|
||||
/// as an error message
|
||||
pub(crate) fn error_string(e: impl std::fmt::Debug) -> std::ffi::CString {
|
||||
let x = format!("{:?}", e).into_bytes();
|
||||
let x = if x[0] == b'"' && x[x.len() - 1] == b'"' {
|
||||
x[1..x.len() - 1].to_vec()
|
||||
} else {
|
||||
x
|
||||
};
|
||||
unsafe { std::ffi::CString::from_vec_unchecked(x) }
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use sha2::Digest;
|
||||
|
||||
use crate::*;
|
||||
|
||||
/// Manifest wraps the manifest exported by `extism_manifest`
|
||||
#[derive(Default, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct Manifest(extism_manifest::Manifest);
|
||||
@@ -59,11 +60,7 @@ fn check_hash(hash: &Option<String>, data: &[u8]) -> Result<(), Error> {
|
||||
}
|
||||
}
|
||||
|
||||
fn hash_url(url: &str) -> String {
|
||||
let digest = sha2::Sha256::digest(url.as_bytes());
|
||||
hex(&digest)
|
||||
}
|
||||
|
||||
/// Convert from manifest to a wasmtime Module
|
||||
fn to_module(
|
||||
engine: &Engine,
|
||||
wasm: &extism_manifest::ManifestWasm,
|
||||
@@ -74,6 +71,7 @@ fn to_module(
|
||||
return Err(anyhow::format_err!("File-based registration is disabled"));
|
||||
}
|
||||
|
||||
// Figure out a good name for the file
|
||||
let name = match name {
|
||||
None => {
|
||||
let name = path.with_extension("");
|
||||
@@ -82,6 +80,7 @@ fn to_module(
|
||||
Some(n) => n.clone(),
|
||||
};
|
||||
|
||||
// Load file
|
||||
let mut buf = Vec::new();
|
||||
let mut file = std::fs::File::open(path)?;
|
||||
file.read_to_end(&mut buf)?;
|
||||
@@ -108,6 +107,7 @@ fn to_module(
|
||||
},
|
||||
hash,
|
||||
} => {
|
||||
// Get the file name
|
||||
let file_name = url.split('/').last().unwrap();
|
||||
let name = match name {
|
||||
Some(name) => name.as_str(),
|
||||
@@ -124,7 +124,6 @@ fn to_module(
|
||||
}
|
||||
};
|
||||
|
||||
let url_hash = hash_url(url);
|
||||
if let Some(h) = hash {
|
||||
if let Ok(Some(data)) = cache_get_file(h) {
|
||||
check_hash(hash, &data)?;
|
||||
@@ -140,7 +139,7 @@ fn to_module(
|
||||
|
||||
#[cfg(feature = "register-http")]
|
||||
{
|
||||
let url_hash = hash_url(url);
|
||||
// Setup request
|
||||
let mut req = ureq::request(method.as_deref().unwrap_or("GET"), url);
|
||||
|
||||
for (k, v) in header.iter() {
|
||||
@@ -152,6 +151,7 @@ fn to_module(
|
||||
let mut data = Vec::new();
|
||||
r.read_to_end(&mut data)?;
|
||||
|
||||
// Try to cache file
|
||||
if let Some(hash) = hash {
|
||||
cache_add_file(hash, &data);
|
||||
}
|
||||
@@ -169,6 +169,7 @@ fn to_module(
|
||||
const WASM_MAGIC: [u8; 4] = [0x00, 0x61, 0x73, 0x6d];
|
||||
|
||||
impl Manifest {
|
||||
/// Create a new Manifest, returns the manifest and a map of modules
|
||||
pub fn new(engine: &Engine, data: &[u8]) -> Result<(Self, BTreeMap<String, Module>), Error> {
|
||||
let has_magic = data.len() >= 4 && data[0..4] == WASM_MAGIC;
|
||||
let is_wast = data.starts_with(b"(module") || data.starts_with(b";;");
|
||||
@@ -196,6 +197,8 @@ impl Manifest {
|
||||
}
|
||||
|
||||
let mut modules = BTreeMap::new();
|
||||
|
||||
// If there's only one module, it should be called `main`
|
||||
if self.0.wasm.len() == 1 {
|
||||
let (_, m) = to_module(engine, &self.0.wasm[0])?;
|
||||
modules.insert("main".to_string(), m);
|
||||
|
||||
@@ -20,6 +20,7 @@ const PAGE_SIZE: u32 = 65536;
|
||||
const BLOCK_SIZE_THRESHOLD: usize = 32;
|
||||
|
||||
impl PluginMemory {
|
||||
/// Create memory for a plugin
|
||||
pub fn new(store: Store<Internal>, memory: Memory) -> Self {
|
||||
PluginMemory {
|
||||
free: Vec::new(),
|
||||
@@ -30,6 +31,7 @@ impl PluginMemory {
|
||||
}
|
||||
}
|
||||
|
||||
/// Write byte to memory
|
||||
pub(crate) fn store_u8(&mut self, offs: usize, data: u8) -> Result<(), MemoryAccessError> {
|
||||
trace!("store_u8: {data:x} at offset {offs}");
|
||||
if offs >= self.size() {
|
||||
@@ -42,7 +44,7 @@ impl PluginMemory {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read from memory
|
||||
/// Read byte from memory
|
||||
pub(crate) fn load_u8(&self, offs: usize) -> Result<u8, MemoryAccessError> {
|
||||
trace!("load_u8: offset {offs}");
|
||||
if offs >= self.size() {
|
||||
@@ -54,17 +56,18 @@ impl PluginMemory {
|
||||
Ok(self.memory.data(&self.store)[offs])
|
||||
}
|
||||
|
||||
/// Write u32 to memory
|
||||
pub(crate) fn store_u32(&mut self, offs: usize, data: u32) -> Result<(), MemoryAccessError> {
|
||||
trace!("store_u32: {data:x} at offset {offs}");
|
||||
let handle = MemoryBlock {
|
||||
offset: offs,
|
||||
length: 4,
|
||||
};
|
||||
self.write(handle, &data.to_ne_bytes())?;
|
||||
self.write(handle, data.to_ne_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read from memory
|
||||
/// Read u32 from memory
|
||||
pub(crate) fn load_u32(&self, offs: usize) -> Result<u32, MemoryAccessError> {
|
||||
trace!("load_u32: offset {offs}");
|
||||
let mut buf = [0; 4];
|
||||
@@ -77,16 +80,18 @@ impl PluginMemory {
|
||||
Ok(u32::from_ne_bytes(buf))
|
||||
}
|
||||
|
||||
/// Write u64 to memory
|
||||
pub(crate) fn store_u64(&mut self, offs: usize, data: u64) -> Result<(), MemoryAccessError> {
|
||||
trace!("store_u64: {data:x} at offset {offs}");
|
||||
let handle = MemoryBlock {
|
||||
offset: offs,
|
||||
length: 8,
|
||||
};
|
||||
self.write(handle, &data.to_ne_bytes())?;
|
||||
self.write(handle, data.to_ne_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read u64 from memory
|
||||
pub(crate) fn load_u64(&self, offs: usize) -> Result<u64, MemoryAccessError> {
|
||||
trace!("load_u64: offset {offs}");
|
||||
let mut buf = [0; 8];
|
||||
@@ -98,7 +103,7 @@ impl PluginMemory {
|
||||
Ok(u64::from_ne_bytes(buf))
|
||||
}
|
||||
|
||||
/// Write to memory
|
||||
/// Write slice to memory
|
||||
pub fn write(
|
||||
&mut self,
|
||||
pos: impl Into<MemoryBlock>,
|
||||
@@ -110,7 +115,7 @@ impl PluginMemory {
|
||||
.write(&mut self.store, pos.offset, data.as_ref())
|
||||
}
|
||||
|
||||
/// Read from memory
|
||||
/// Read slice from memory
|
||||
pub fn read(
|
||||
&self,
|
||||
pos: impl Into<MemoryBlock>,
|
||||
@@ -232,13 +237,14 @@ impl PluginMemory {
|
||||
}
|
||||
}
|
||||
|
||||
/// Log entire memory as hexdump using the `trace` log level
|
||||
pub fn dump(&self) {
|
||||
let data = self.memory.data(&self.store);
|
||||
|
||||
trace!("{:?}", data[..self.position].hex_dump());
|
||||
}
|
||||
|
||||
/// Reset memory
|
||||
/// Reset memory - clears free-list and live blocks and resets position
|
||||
pub fn reset(&mut self) {
|
||||
self.free.clear();
|
||||
self.live_blocks.clear();
|
||||
@@ -267,6 +273,12 @@ impl PluginMemory {
|
||||
&mut self.memory.data_mut(&mut self.store)[handle.offset..handle.offset + handle.length]
|
||||
}
|
||||
|
||||
/// Pointer to the provided memory handle
|
||||
pub fn ptr(&self, handle: impl Into<MemoryBlock>) -> *mut u8 {
|
||||
let handle = handle.into();
|
||||
unsafe { self.memory.data_ptr(&self.store).add(handle.offset) }
|
||||
}
|
||||
|
||||
/// Get the length of the block starting at `offs`
|
||||
pub fn block_length(&self, offs: usize) -> Option<usize> {
|
||||
self.live_blocks.get(&offs).cloned()
|
||||
|
||||
@@ -38,6 +38,22 @@ impl Internal {
|
||||
plugin: std::ptr::null_mut(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn plugin(&self) -> &Plugin {
|
||||
unsafe { &*self.plugin }
|
||||
}
|
||||
|
||||
pub fn plugin_mut(&mut self) -> &mut Plugin {
|
||||
unsafe { &mut *self.plugin }
|
||||
}
|
||||
|
||||
pub fn memory(&self) -> &PluginMemory {
|
||||
&self.plugin().memory
|
||||
}
|
||||
|
||||
pub fn memory_mut(&mut self) -> &mut PluginMemory {
|
||||
&mut self.plugin_mut().memory
|
||||
}
|
||||
}
|
||||
|
||||
const EXPORT_MODULE_NAME: &str = "env";
|
||||
@@ -145,14 +161,7 @@ impl Plugin {
|
||||
/// Set `last_error` field
|
||||
pub fn set_error(&mut self, e: impl std::fmt::Debug) {
|
||||
debug!("Set error: {:?}", e);
|
||||
let x = format!("{:?}", e).into_bytes();
|
||||
let x = if x[0] == b'"' && x[x.len() - 1] == b'"' {
|
||||
x[1..x.len() - 1].to_vec()
|
||||
} else {
|
||||
x
|
||||
};
|
||||
let e = unsafe { std::ffi::CString::from_vec_unchecked(x) };
|
||||
self.last_error = Some(e);
|
||||
self.last_error = Some(error_string(e));
|
||||
}
|
||||
|
||||
pub fn error<E>(&mut self, e: impl std::fmt::Debug, x: E) -> E {
|
||||
@@ -181,6 +190,3 @@ impl Plugin {
|
||||
self.memory.dump();
|
||||
}
|
||||
}
|
||||
|
||||
/// A registry for plugins
|
||||
pub static mut PLUGINS: std::sync::Mutex<Vec<Plugin>> = std::sync::Mutex::new(Vec::new());
|
||||
|
||||
@@ -1,48 +1,50 @@
|
||||
use crate::*;
|
||||
|
||||
// PluginRef is used to access a plugin from the global plugin registry
|
||||
// PluginRef is used to access a plugin from a context-scoped plugin registry
|
||||
pub struct PluginRef<'a> {
|
||||
pub id: PluginIndex,
|
||||
pub plugins: std::sync::MutexGuard<'a, Vec<Plugin>>,
|
||||
plugin: *mut Plugin,
|
||||
plugin: &'a mut Plugin,
|
||||
}
|
||||
|
||||
impl<'a> PluginRef<'a> {
|
||||
pub fn init(mut self) -> Self {
|
||||
trace!(
|
||||
"Resetting memory and clearing error message for plugin {}",
|
||||
self.id,
|
||||
);
|
||||
// Initialize
|
||||
self.as_mut().clear_error();
|
||||
/// Initialize the plugin for a new call
|
||||
///
|
||||
/// - Resets memory offsets
|
||||
/// - Updates `input` pointer
|
||||
pub fn init(mut self, data: *const u8, data_len: usize) -> Self {
|
||||
trace!("PluginRef::init: {}", self.id,);
|
||||
self.as_mut().memory.reset();
|
||||
let internal = self.as_mut().memory.store.data_mut();
|
||||
internal.input = std::ptr::null();
|
||||
internal.input_length = 0;
|
||||
self.plugin.set_input(data, data_len);
|
||||
self
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// This function is used to access the static `PLUGINS` registry
|
||||
pub unsafe fn new(plugin_id: PluginIndex) -> Self {
|
||||
let mut plugins = match PLUGINS.lock() {
|
||||
Ok(p) => p,
|
||||
Err(e) => e.into_inner(),
|
||||
};
|
||||
|
||||
/// Create a `PluginRef` from a context
|
||||
pub fn new(ctx: &'a mut Context, plugin_id: PluginIndex, clear_error: bool) -> Self {
|
||||
trace!("Loading plugin {plugin_id}");
|
||||
|
||||
if plugin_id < 0 || plugin_id as usize >= plugins.len() {
|
||||
drop(plugins);
|
||||
panic!("Invalid PluginIndex {plugin_id}");
|
||||
if plugin_id < 0 {
|
||||
panic!("Invalid PluginIndex in PluginRef::new: {plugin_id}");
|
||||
}
|
||||
|
||||
let plugin = plugins.get_unchecked_mut(plugin_id as usize) as *mut _;
|
||||
if clear_error {
|
||||
ctx.error = None;
|
||||
}
|
||||
|
||||
let plugin = ctx.plugin(plugin_id);
|
||||
|
||||
let plugin = match plugin {
|
||||
None => {
|
||||
panic!("Plugin does not exist: {plugin_id}");
|
||||
}
|
||||
Some(p) => p,
|
||||
};
|
||||
|
||||
if clear_error {
|
||||
plugin.clear_error();
|
||||
}
|
||||
|
||||
PluginRef {
|
||||
id: plugin_id,
|
||||
plugins,
|
||||
plugin,
|
||||
}
|
||||
}
|
||||
@@ -50,13 +52,13 @@ impl<'a> PluginRef<'a> {
|
||||
|
||||
impl<'a> AsRef<Plugin> for PluginRef<'a> {
|
||||
fn as_ref(&self) -> &Plugin {
|
||||
unsafe { &*self.plugin }
|
||||
self.plugin
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AsMut<Plugin> for PluginRef<'a> {
|
||||
fn as_mut(&mut self) -> &mut Plugin {
|
||||
unsafe { &mut *self.plugin }
|
||||
self.plugin
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,74 +5,135 @@ use std::str::FromStr;
|
||||
|
||||
use crate::*;
|
||||
|
||||
/// Create a new context
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn extism_plugin_register(
|
||||
pub unsafe extern "C" fn extism_context_new() -> *mut Context {
|
||||
trace!("Creating new Context");
|
||||
Box::into_raw(Box::new(Context::new()))
|
||||
}
|
||||
|
||||
/// Free a context
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn extism_context_free(ctx: *mut Context) {
|
||||
trace!("Freeing context");
|
||||
if ctx.is_null() {
|
||||
return;
|
||||
}
|
||||
drop(Box::from_raw(ctx))
|
||||
}
|
||||
|
||||
/// Create a new plugin
|
||||
///
|
||||
/// `wasm`: is a WASM module (wat or wasm) or a JSON encoded manifest
|
||||
/// `wasm_size`: the length of the `wasm` parameter
|
||||
/// `with_wasi`: enables/disables WASI
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn extism_plugin_new(
|
||||
ctx: *mut Context,
|
||||
wasm: *const u8,
|
||||
wasm_size: Size,
|
||||
with_wasi: bool,
|
||||
) -> PluginIndex {
|
||||
trace!(
|
||||
"Call to extism_plugin_register with wasm pointer {:?}",
|
||||
wasm
|
||||
);
|
||||
trace!("Call to extism_plugin_new with wasm pointer {:?}", wasm);
|
||||
let ctx = &mut *ctx;
|
||||
|
||||
let data = std::slice::from_raw_parts(wasm, wasm_size as usize);
|
||||
let plugin = match Plugin::new(data, with_wasi) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
error!("Error creating Plugin: {:?}", e);
|
||||
ctx.set_error(e);
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
||||
let mut plugins = match PLUGINS.lock() {
|
||||
Ok(p) => p,
|
||||
Err(e) => e.into_inner(),
|
||||
// Allocate a new plugin ID
|
||||
let id: i32 = match ctx.next_id() {
|
||||
Ok(id) => id,
|
||||
Err(e) => {
|
||||
error!("Error creating Plugin: {:?}", e);
|
||||
ctx.set_error(e);
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
||||
plugins.push(plugin);
|
||||
let id = (plugins.len() - 1) as PluginIndex;
|
||||
ctx.plugins.insert(id, plugin);
|
||||
info!("New plugin added: {id}");
|
||||
id
|
||||
}
|
||||
|
||||
/// Update a plugin, keeping the existing ID
|
||||
///
|
||||
/// Similar to `extism_plugin_new` but takes an `index` argument to specify
|
||||
/// which plugin to update
|
||||
///
|
||||
/// Memory for this plugin will be reset upon update
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn extism_plugin_update(
|
||||
ctx: *mut Context,
|
||||
index: PluginIndex,
|
||||
wasm: *const u8,
|
||||
wasm_size: Size,
|
||||
with_wasi: bool,
|
||||
) -> bool {
|
||||
let index = index as usize;
|
||||
trace!("Call to extism_plugin_update with wasm pointer {:?}", wasm);
|
||||
let ctx = &mut *ctx;
|
||||
let data = std::slice::from_raw_parts(wasm, wasm_size as usize);
|
||||
let plugin = match Plugin::new(data, with_wasi) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
error!("Error creating Plugin: {:?}", e);
|
||||
ctx.set_error(e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
let mut plugins = match PLUGINS.lock() {
|
||||
Ok(p) => p,
|
||||
Err(e) => e.into_inner(),
|
||||
};
|
||||
|
||||
if index < plugins.len() {
|
||||
plugins[index] = plugin;
|
||||
if !ctx.plugins.contains_key(&index) {
|
||||
ctx.set_error("Plugin index does not exist");
|
||||
return false;
|
||||
}
|
||||
|
||||
ctx.plugins.insert(index, plugin);
|
||||
|
||||
info!("Plugin updated: {index}");
|
||||
true
|
||||
}
|
||||
|
||||
/// Remove a plugin from the registry and free associated memory
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn extism_plugin_free(ctx: *mut Context, plugin: PluginIndex) {
|
||||
if plugin < 0 || ctx.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
trace!("Freeing plugin {plugin}");
|
||||
|
||||
let ctx = &mut *ctx;
|
||||
ctx.remove(plugin);
|
||||
}
|
||||
|
||||
/// Remove all plugins from the registry
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn extism_context_reset(ctx: *mut Context) {
|
||||
let ctx = &mut *ctx;
|
||||
|
||||
trace!(
|
||||
"Resetting context, plugins cleared: {:?}",
|
||||
ctx.plugins.keys().collect::<Vec<&i32>>()
|
||||
);
|
||||
|
||||
ctx.plugins.clear();
|
||||
}
|
||||
|
||||
/// Update plugin config values, this will merge with the existing values
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn extism_plugin_config(
|
||||
ctx: *mut Context,
|
||||
plugin: PluginIndex,
|
||||
json: *const u8,
|
||||
json_size: Size,
|
||||
) -> bool {
|
||||
let mut plugin = PluginRef::new(plugin);
|
||||
let ctx = &mut *ctx;
|
||||
let mut plugin = PluginRef::new(ctx, plugin, true);
|
||||
|
||||
trace!(
|
||||
"Call to extism_plugin_config for {} with json pointer {:?}",
|
||||
@@ -81,50 +142,74 @@ pub unsafe extern "C" fn extism_plugin_config(
|
||||
);
|
||||
|
||||
let data = std::slice::from_raw_parts(json, json_size as usize);
|
||||
let json: std::collections::BTreeMap<String, String> = match serde_json::from_slice(data) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
plugin.as_mut().set_error(e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
let json: std::collections::BTreeMap<String, Option<String>> =
|
||||
match serde_json::from_slice(data) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
return plugin.as_mut().error(e, false);
|
||||
}
|
||||
};
|
||||
|
||||
let plugin = plugin.as_mut();
|
||||
let wasi = &mut plugin.memory.store.data_mut().wasi;
|
||||
let config = &mut plugin.manifest.as_mut().config;
|
||||
for (k, v) in json.into_iter() {
|
||||
trace!("Config, adding {k}");
|
||||
let _ = wasi.push_env(&k, &v);
|
||||
config.insert(k, v);
|
||||
match v {
|
||||
Some(v) => {
|
||||
trace!("Config, adding {k}");
|
||||
let _ = wasi.push_env(&k, &v);
|
||||
config.insert(k, v);
|
||||
}
|
||||
None => {
|
||||
config.remove(&k);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Returns true if `func_name` exists
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn extism_function_exists(
|
||||
pub unsafe extern "C" fn extism_plugin_function_exists(
|
||||
ctx: *mut Context,
|
||||
plugin: PluginIndex,
|
||||
func_name: *const c_char,
|
||||
) -> bool {
|
||||
let mut plugin = PluginRef::new(plugin);
|
||||
let ctx = &mut *ctx;
|
||||
let mut plugin = PluginRef::new(ctx, plugin, true);
|
||||
|
||||
let name = std::ffi::CStr::from_ptr(func_name);
|
||||
trace!("Call to extism_plugin_function_exists for: {:?}", name);
|
||||
|
||||
let name = match name.to_str() {
|
||||
Ok(x) => x,
|
||||
Err(_) => return false,
|
||||
Err(e) => {
|
||||
return plugin.as_mut().error(e, false);
|
||||
}
|
||||
};
|
||||
|
||||
plugin.as_mut().get_func(name).is_some()
|
||||
}
|
||||
|
||||
/// Call a function
|
||||
///
|
||||
/// `func_name`: is the function to call
|
||||
/// `data`: is the input data
|
||||
/// `data_len`: is the length of `data`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn extism_call(
|
||||
pub unsafe extern "C" fn extism_plugin_call(
|
||||
ctx: *mut Context,
|
||||
plugin_id: PluginIndex,
|
||||
func_name: *const c_char,
|
||||
data: *const u8,
|
||||
data_len: Size,
|
||||
) -> i32 {
|
||||
let mut plugin = PluginRef::new(plugin_id).init();
|
||||
let ctx = &mut *ctx;
|
||||
|
||||
// Get a `PluginRef` and call `init` to set up the plugin input and memory, this is only
|
||||
// needed before a new call
|
||||
let mut plugin = PluginRef::new(ctx, plugin_id, true).init(data, data_len as usize);
|
||||
let plugin = plugin.as_mut();
|
||||
|
||||
// Find function
|
||||
@@ -141,9 +226,6 @@ pub unsafe extern "C" fn extism_call(
|
||||
None => return plugin.error(format!("Function not found: {name}"), -1),
|
||||
};
|
||||
|
||||
// Always needs to be called before `func.call()`
|
||||
plugin.set_input(data, data_len as usize);
|
||||
|
||||
// Call function with offset+length pointing to input data.
|
||||
let mut results = vec![Val::I32(0)];
|
||||
match func.call(&mut plugin.memory.store, &[], results.as_mut_slice()) {
|
||||
@@ -161,10 +243,25 @@ pub unsafe extern "C" fn extism_call(
|
||||
results[0].unwrap_i32()
|
||||
}
|
||||
|
||||
/// Get the error associated with a `Context` or `Plugin`, if `plugin` is `-1` then the context
|
||||
/// error will be returned
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn extism_error(plugin: PluginIndex) -> *const c_char {
|
||||
pub unsafe extern "C" fn extism_error(ctx: *mut Context, plugin: PluginIndex) -> *const c_char {
|
||||
trace!("Call to extism_error for plugin {plugin}");
|
||||
let plugin = PluginRef::new(plugin);
|
||||
|
||||
let ctx = &mut *ctx;
|
||||
|
||||
if plugin < 0 {
|
||||
match ctx.error.as_ref() {
|
||||
Some(e) => return e.as_ptr() as *const _,
|
||||
None => {
|
||||
trace!("Error is NULL");
|
||||
return std::ptr::null();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let plugin = PluginRef::new(ctx, plugin, false);
|
||||
match &plugin.as_ref().last_error {
|
||||
Some(e) => e.as_ptr() as *const _,
|
||||
None => {
|
||||
@@ -174,21 +271,32 @@ pub unsafe extern "C" fn extism_error(plugin: PluginIndex) -> *const c_char {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the length of a plugin's output data
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn extism_output_length(plugin: PluginIndex) -> Size {
|
||||
trace!("Call to extism_output_length for plugin {plugin}");
|
||||
let plugin = PluginRef::new(plugin);
|
||||
pub unsafe extern "C" fn extism_plugin_output_length(
|
||||
ctx: *mut Context,
|
||||
plugin: PluginIndex,
|
||||
) -> Size {
|
||||
trace!("Call to extism_plugin_output_length for plugin {plugin}");
|
||||
|
||||
let ctx = &mut *ctx;
|
||||
let plugin = PluginRef::new(ctx, plugin, true);
|
||||
|
||||
let len = plugin.as_ref().memory.store.data().output_length as Size;
|
||||
trace!("Output length: {len}");
|
||||
len
|
||||
}
|
||||
|
||||
/// Get the length of a plugin's output data
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn extism_output_get(plugin: PluginIndex) -> *const u8 {
|
||||
trace!("Call to extism_output_get for plugin {plugin}");
|
||||
pub unsafe extern "C" fn extism_plugin_output_data(
|
||||
ctx: *mut Context,
|
||||
plugin: PluginIndex,
|
||||
) -> *const u8 {
|
||||
trace!("Call to extism_plugin_output_data for plugin {plugin}");
|
||||
|
||||
let plugin = PluginRef::new(plugin);
|
||||
let ctx = &mut *ctx;
|
||||
let plugin = PluginRef::new(ctx, plugin, true);
|
||||
let data = plugin.as_ref().memory.store.data();
|
||||
|
||||
plugin
|
||||
@@ -198,6 +306,7 @@ pub unsafe extern "C" fn extism_output_get(plugin: PluginIndex) -> *const u8 {
|
||||
.as_ptr()
|
||||
}
|
||||
|
||||
/// Set log file and level
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn extism_log_file(
|
||||
filename: *const c_char,
|
||||
@@ -254,8 +363,7 @@ pub unsafe extern "C" fn extism_log_file(
|
||||
} else {
|
||||
match FileAppender::builder().encoder(encoder).build(file) {
|
||||
Ok(x) => Box::new(x),
|
||||
Err(e) => {
|
||||
error!("Unable to set up log encoder: {e:?}");
|
||||
Err(_) => {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,10 @@
|
||||
fn main() {
|
||||
let out_dir = std::env::var("OUT_DIR").unwrap();
|
||||
if std::path::PathBuf::from("libextism.so").exists() {
|
||||
std::process::Command::new("cp")
|
||||
.arg("libextism.so")
|
||||
.arg(&out_dir)
|
||||
.status()
|
||||
.unwrap();
|
||||
} else {
|
||||
std::process::Command::new("cp")
|
||||
.arg("libextism.dylib")
|
||||
.arg(&out_dir)
|
||||
.status()
|
||||
.unwrap();
|
||||
println!("cargo:rustc-link-search=/usr/local/lib");
|
||||
|
||||
if let Ok(home) = std::env::var("HOME") {
|
||||
let path = std::path::PathBuf::from(home).join(".local").join("lib");
|
||||
println!("cargo:rustc-link-search={}", path.display());
|
||||
}
|
||||
println!("cargo:rustc-link-search={}", out_dir);
|
||||
|
||||
println!("cargo:rustc-link-lib=extism");
|
||||
println!("cargo:rerun-if-changed=libextism.so");
|
||||
println!("cargo:rerun-if-changed=libextism.dylib");
|
||||
}
|
||||
|
||||
@@ -3,10 +3,22 @@
|
||||
pub type __uint8_t = ::std::os::raw::c_uchar;
|
||||
pub type __int32_t = ::std::os::raw::c_int;
|
||||
pub type __uint64_t = ::std::os::raw::c_ulong;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct ExtismContext {
|
||||
_unused: [u8; 0],
|
||||
}
|
||||
pub type ExtismPlugin = i32;
|
||||
pub type ExtismSize = u64;
|
||||
extern "C" {
|
||||
pub fn extism_plugin_register(
|
||||
pub fn extism_context_new() -> *mut ExtismContext;
|
||||
}
|
||||
extern "C" {
|
||||
pub fn extism_context_free(ctx: *mut ExtismContext);
|
||||
}
|
||||
extern "C" {
|
||||
pub fn extism_plugin_new(
|
||||
ctx: *mut ExtismContext,
|
||||
wasm: *const u8,
|
||||
wasm_size: ExtismSize,
|
||||
with_wasi: bool,
|
||||
@@ -14,27 +26,37 @@ extern "C" {
|
||||
}
|
||||
extern "C" {
|
||||
pub fn extism_plugin_update(
|
||||
ctx: *mut ExtismContext,
|
||||
index: ExtismPlugin,
|
||||
wasm: *const u8,
|
||||
wasm_size: ExtismSize,
|
||||
with_wasi: bool,
|
||||
) -> bool;
|
||||
}
|
||||
extern "C" {
|
||||
pub fn extism_plugin_free(ctx: *mut ExtismContext, plugin: ExtismPlugin);
|
||||
}
|
||||
extern "C" {
|
||||
pub fn extism_context_reset(ctx: *mut ExtismContext);
|
||||
}
|
||||
extern "C" {
|
||||
pub fn extism_plugin_config(
|
||||
ctx: *mut ExtismContext,
|
||||
plugin: ExtismPlugin,
|
||||
json: *const u8,
|
||||
json_size: ExtismSize,
|
||||
) -> bool;
|
||||
}
|
||||
extern "C" {
|
||||
pub fn extism_function_exists(
|
||||
pub fn extism_plugin_function_exists(
|
||||
ctx: *mut ExtismContext,
|
||||
plugin: ExtismPlugin,
|
||||
func_name: *const ::std::os::raw::c_char,
|
||||
) -> bool;
|
||||
}
|
||||
extern "C" {
|
||||
pub fn extism_call(
|
||||
pub fn extism_plugin_call(
|
||||
ctx: *mut ExtismContext,
|
||||
plugin_id: ExtismPlugin,
|
||||
func_name: *const ::std::os::raw::c_char,
|
||||
data: *const u8,
|
||||
@@ -42,13 +64,17 @@ extern "C" {
|
||||
) -> i32;
|
||||
}
|
||||
extern "C" {
|
||||
pub fn extism_error(plugin: ExtismPlugin) -> *const ::std::os::raw::c_char;
|
||||
pub fn extism_error(
|
||||
ctx: *mut ExtismContext,
|
||||
plugin: ExtismPlugin,
|
||||
) -> *const ::std::os::raw::c_char;
|
||||
}
|
||||
extern "C" {
|
||||
pub fn extism_output_length(plugin: ExtismPlugin) -> ExtismSize;
|
||||
pub fn extism_plugin_output_length(ctx: *mut ExtismContext, plugin: ExtismPlugin)
|
||||
-> ExtismSize;
|
||||
}
|
||||
extern "C" {
|
||||
pub fn extism_output_get(plugin: ExtismPlugin) -> *const u8;
|
||||
pub fn extism_plugin_output_data(ctx: *mut ExtismContext, plugin: ExtismPlugin) -> *const u8;
|
||||
}
|
||||
extern "C" {
|
||||
pub fn extism_log_file(
|
||||
|
||||
34
rust/src/context.rs
Normal file
34
rust/src/context.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use crate::*;
|
||||
|
||||
pub struct Context {
|
||||
pub(crate) pointer: *mut bindings::ExtismContext,
|
||||
}
|
||||
|
||||
impl Default for Context {
|
||||
fn default() -> Context {
|
||||
Context::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Context {
|
||||
/// Create a new context
|
||||
pub fn new() -> Context {
|
||||
let pointer = unsafe { bindings::extism_context_new() };
|
||||
Context { pointer }
|
||||
}
|
||||
|
||||
/// Remove all registered plugins
|
||||
pub fn reset(&mut self) {
|
||||
unsafe { bindings::extism_context_reset(self.pointer) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Context {
|
||||
fn drop(&mut self) {
|
||||
if self.pointer.is_null() {
|
||||
return;
|
||||
}
|
||||
unsafe { bindings::extism_context_free(self.pointer) }
|
||||
self.pointer = std::ptr::null_mut();
|
||||
}
|
||||
}
|
||||
160
rust/src/lib.rs
160
rust/src/lib.rs
@@ -3,14 +3,17 @@ use std::collections::BTreeMap;
|
||||
use extism_manifest::Manifest;
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
mod bindings;
|
||||
pub(crate) mod bindings;
|
||||
|
||||
#[repr(transparent)]
|
||||
pub struct Plugin(isize);
|
||||
mod context;
|
||||
mod plugin;
|
||||
|
||||
pub use context::Context;
|
||||
pub use plugin::Plugin;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
UnableToLoadPlugin,
|
||||
UnableToLoadPlugin(String),
|
||||
Message(String),
|
||||
Json(serde_json::Error),
|
||||
}
|
||||
@@ -21,115 +24,14 @@ impl From<serde_json::Error> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl Plugin {
|
||||
pub fn new_with_manifest(manifest: &Manifest, wasi: bool) -> Result<Plugin, Error> {
|
||||
let data = serde_json::to_vec(manifest)?;
|
||||
Self::new(data, wasi)
|
||||
}
|
||||
|
||||
pub fn new(data: impl AsRef<[u8]>, wasi: bool) -> Result<Plugin, Error> {
|
||||
let plugin = unsafe {
|
||||
bindings::extism_plugin_register(
|
||||
data.as_ref().as_ptr(),
|
||||
data.as_ref().len() as u64,
|
||||
wasi,
|
||||
)
|
||||
};
|
||||
|
||||
if plugin < 0 {
|
||||
return Err(Error::UnableToLoadPlugin);
|
||||
}
|
||||
|
||||
Ok(Plugin(plugin as isize))
|
||||
}
|
||||
|
||||
pub fn update(&mut self, data: impl AsRef<[u8]>, wasi: bool) -> bool {
|
||||
unsafe {
|
||||
bindings::extism_plugin_update(
|
||||
self.0 as i32,
|
||||
data.as_ref().as_ptr(),
|
||||
data.as_ref().len() as u64,
|
||||
wasi,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_manifest(&mut self, manifest: &Manifest, wasi: bool) -> Result<bool, Error> {
|
||||
let data = serde_json::to_vec(manifest)?;
|
||||
Ok(self.update(data, wasi))
|
||||
}
|
||||
|
||||
pub fn set_config(&self, config: &BTreeMap<String, String>) -> Result<(), Error> {
|
||||
let encoded = serde_json::to_vec(config)?;
|
||||
unsafe {
|
||||
bindings::extism_plugin_config(
|
||||
self.0 as i32,
|
||||
encoded.as_ptr() as *const _,
|
||||
encoded.len() as u64,
|
||||
)
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn with_config(self, config: &BTreeMap<String, String>) -> Result<Self, Error> {
|
||||
self.set_config(config)?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn set_log_file(
|
||||
&self,
|
||||
filename: impl AsRef<std::path::Path>,
|
||||
log_level: Option<log::LevelFilter>,
|
||||
) {
|
||||
let log_level = log_level.map(|x| x.as_str());
|
||||
unsafe {
|
||||
bindings::extism_log_file(
|
||||
filename.as_ref().as_os_str().to_string_lossy().as_ptr() as *const _,
|
||||
log_level.map(|x| x.as_ptr()).unwrap_or(std::ptr::null()) as *const _,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_log_file(
|
||||
self,
|
||||
filename: impl AsRef<std::path::Path>,
|
||||
log_level: Option<log::LevelFilter>,
|
||||
) -> Self {
|
||||
self.set_log_file(filename, log_level);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn has_function(&self, name: impl AsRef<str>) -> bool {
|
||||
let name = std::ffi::CString::new(name.as_ref()).expect("Invalid function name");
|
||||
unsafe { bindings::extism_function_exists(self.0 as i32, name.as_ptr() as *const _) }
|
||||
}
|
||||
|
||||
pub fn call(&self, name: impl AsRef<str>, input: impl AsRef<[u8]>) -> Result<&[u8], Error> {
|
||||
let name = std::ffi::CString::new(name.as_ref()).expect("Invalid function name");
|
||||
let rc = unsafe {
|
||||
bindings::extism_call(
|
||||
self.0 as i32,
|
||||
name.as_ptr() as *const _,
|
||||
input.as_ref().as_ptr() as *const _,
|
||||
input.as_ref().len() as u64,
|
||||
)
|
||||
};
|
||||
|
||||
if rc != 0 {
|
||||
let err = unsafe { bindings::extism_error(self.0 as i32) };
|
||||
if !err.is_null() {
|
||||
let s = unsafe { std::ffi::CStr::from_ptr(err) };
|
||||
return Err(Error::Message(s.to_str().unwrap().to_string()));
|
||||
}
|
||||
|
||||
return Err(Error::Message("extism_call failed".to_string()));
|
||||
}
|
||||
|
||||
let out_len = unsafe { bindings::extism_output_length(self.0 as i32) };
|
||||
unsafe {
|
||||
let ptr = bindings::extism_output_get(self.0 as i32);
|
||||
Ok(std::slice::from_raw_parts(ptr, out_len as usize))
|
||||
}
|
||||
/// Set the log file and level, this is a global setting
|
||||
pub fn set_log_file(filename: impl AsRef<std::path::Path>, log_level: Option<log::Level>) {
|
||||
let log_level = log_level.map(|x| x.as_str());
|
||||
unsafe {
|
||||
bindings::extism_log_file(
|
||||
filename.as_ref().as_os_str().to_string_lossy().as_ptr() as *const _,
|
||||
log_level.map(|x| x.as_ptr()).unwrap_or(std::ptr::null()) as *const _,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,13 +40,14 @@ mod tests {
|
||||
use super::*;
|
||||
use std::time::Instant;
|
||||
|
||||
const WASM: &[u8] = include_bytes!("../../wasm/code.wasm");
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let wasm = include_bytes!("../../wasm/code.wasm");
|
||||
let wasm_start = Instant::now();
|
||||
let plugin = Plugin::new(wasm, false)
|
||||
.unwrap()
|
||||
.with_log_file("test.log", Some(log::LevelFilter::Info));
|
||||
set_log_file("test.log", Some(log::Level::Info));
|
||||
let context = Context::new();
|
||||
let plugin = Plugin::new(&context, WASM, false).unwrap();
|
||||
println!("register loaded plugin: {:?}", wasm_start.elapsed());
|
||||
|
||||
let repeat = 1182;
|
||||
@@ -230,4 +133,27 @@ mod tests {
|
||||
|
||||
println!("wasm function call (avg, N = {}): {:?}", num_tests, avg);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_threads() {
|
||||
use std::io::Write;
|
||||
std::thread::spawn(|| {
|
||||
let context = Context::new();
|
||||
let plugin = Plugin::new(&context, WASM, false).unwrap();
|
||||
let output = plugin.call("count_vowels", "this is a test").unwrap();
|
||||
std::io::stdout().write_all(output).unwrap();
|
||||
});
|
||||
|
||||
std::thread::spawn(|| {
|
||||
let context = Context::new();
|
||||
let plugin = Plugin::new(&context, WASM, false).unwrap();
|
||||
let output = plugin.call("count_vowels", "this is a test aaa").unwrap();
|
||||
std::io::stdout().write_all(output).unwrap();
|
||||
});
|
||||
|
||||
let context = Context::new();
|
||||
let plugin = Plugin::new(&context, WASM, false).unwrap();
|
||||
let output = plugin.call("count_vowels", "abc123").unwrap();
|
||||
std::io::stdout().write_all(output).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
144
rust/src/plugin.rs
Normal file
144
rust/src/plugin.rs
Normal file
@@ -0,0 +1,144 @@
|
||||
use crate::*;
|
||||
|
||||
pub struct Plugin<'a> {
|
||||
id: bindings::ExtismPlugin,
|
||||
context: &'a Context,
|
||||
}
|
||||
|
||||
impl<'a> Plugin<'a> {
|
||||
/// Create a new plugin from the given manifest
|
||||
pub fn new_with_manifest(
|
||||
ctx: &'a Context,
|
||||
manifest: &Manifest,
|
||||
wasi: bool,
|
||||
) -> Result<Plugin<'a>, Error> {
|
||||
let data = serde_json::to_vec(manifest)?;
|
||||
Self::new(ctx, data, wasi)
|
||||
}
|
||||
|
||||
/// Create a new plugin from a WASM module
|
||||
pub fn new(ctx: &'a Context, data: impl AsRef<[u8]>, wasi: bool) -> Result<Plugin, Error> {
|
||||
let plugin = unsafe {
|
||||
bindings::extism_plugin_new(
|
||||
ctx.pointer,
|
||||
data.as_ref().as_ptr(),
|
||||
data.as_ref().len() as u64,
|
||||
wasi,
|
||||
)
|
||||
};
|
||||
|
||||
if plugin < 0 {
|
||||
let err = unsafe { bindings::extism_error(ctx.pointer, -1) };
|
||||
let buf = unsafe { std::ffi::CStr::from_ptr(err) };
|
||||
let buf = buf.to_str().unwrap().to_string();
|
||||
return Err(Error::UnableToLoadPlugin(buf));
|
||||
}
|
||||
|
||||
Ok(Plugin {
|
||||
id: plugin,
|
||||
context: ctx,
|
||||
})
|
||||
}
|
||||
|
||||
/// Update a plugin with the given WASM module
|
||||
pub fn update(&mut self, data: impl AsRef<[u8]>, wasi: bool) -> Result<(), Error> {
|
||||
let b = unsafe {
|
||||
bindings::extism_plugin_update(
|
||||
self.context.pointer,
|
||||
self.id,
|
||||
data.as_ref().as_ptr(),
|
||||
data.as_ref().len() as u64,
|
||||
wasi,
|
||||
)
|
||||
};
|
||||
if b {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let err = unsafe { bindings::extism_error(self.context.pointer, -1) };
|
||||
if !err.is_null() {
|
||||
let s = unsafe { std::ffi::CStr::from_ptr(err) };
|
||||
return Err(Error::Message(s.to_str().unwrap().to_string()));
|
||||
}
|
||||
|
||||
Err(Error::Message("extism_plugin_update failed".to_string()))
|
||||
}
|
||||
|
||||
/// Update a plugin with the given manifest
|
||||
pub fn update_manifest(&mut self, manifest: &Manifest, wasi: bool) -> Result<(), Error> {
|
||||
let data = serde_json::to_vec(manifest)?;
|
||||
self.update(data, wasi)
|
||||
}
|
||||
|
||||
/// Set configuration values
|
||||
pub fn set_config(&self, config: &BTreeMap<String, Option<String>>) -> Result<(), Error> {
|
||||
let encoded = serde_json::to_vec(config)?;
|
||||
unsafe {
|
||||
bindings::extism_plugin_config(
|
||||
self.context.pointer,
|
||||
self.id,
|
||||
encoded.as_ptr() as *const _,
|
||||
encoded.len() as u64,
|
||||
)
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set configuration values, builder-style
|
||||
pub fn with_config(self, config: &BTreeMap<String, Option<String>>) -> Result<Self, Error> {
|
||||
self.set_config(config)?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Returns true if the plugin has a function matching `name`
|
||||
pub fn has_function(&self, name: impl AsRef<str>) -> bool {
|
||||
let name = std::ffi::CString::new(name.as_ref()).expect("Invalid function name");
|
||||
unsafe {
|
||||
bindings::extism_plugin_function_exists(
|
||||
self.context.pointer,
|
||||
self.id,
|
||||
name.as_ptr() as *const _,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Call a function with the given input
|
||||
pub fn call(&self, name: impl AsRef<str>, input: impl AsRef<[u8]>) -> Result<&[u8], Error> {
|
||||
let name = std::ffi::CString::new(name.as_ref()).expect("Invalid function name");
|
||||
let rc = unsafe {
|
||||
bindings::extism_plugin_call(
|
||||
self.context.pointer,
|
||||
self.id,
|
||||
name.as_ptr() as *const _,
|
||||
input.as_ref().as_ptr() as *const _,
|
||||
input.as_ref().len() as u64,
|
||||
)
|
||||
};
|
||||
|
||||
if rc != 0 {
|
||||
let err = unsafe { bindings::extism_error(self.context.pointer, self.id) };
|
||||
if !err.is_null() {
|
||||
let s = unsafe { std::ffi::CStr::from_ptr(err) };
|
||||
return Err(Error::Message(s.to_str().unwrap().to_string()));
|
||||
}
|
||||
|
||||
return Err(Error::Message("extism_call failed".to_string()));
|
||||
}
|
||||
|
||||
let out_len =
|
||||
unsafe { bindings::extism_plugin_output_length(self.context.pointer, self.id) };
|
||||
unsafe {
|
||||
let ptr = bindings::extism_plugin_output_data(self.context.pointer, self.id);
|
||||
Ok(std::slice::from_raw_parts(ptr, out_len as usize))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Drop for Plugin<'a> {
|
||||
fn drop(&mut self) {
|
||||
if self.context.pointer.is_null() {
|
||||
return;
|
||||
}
|
||||
unsafe { bindings::extism_plugin_free(self.context.pointer, self.id) }
|
||||
}
|
||||
}
|
||||
@@ -1,38 +1,46 @@
|
||||
from pycparser import c_ast, parse_file
|
||||
|
||||
|
||||
class Function:
|
||||
|
||||
def __init__(self, name, return_type, args):
|
||||
self.name = name
|
||||
self.return_type = return_type
|
||||
self.args = args
|
||||
|
||||
|
||||
|
||||
|
||||
typemap = {
|
||||
"_Bool": "bool",
|
||||
"ExtismPlugin": "int32_t",
|
||||
"ExtismSize": "uint64_t",
|
||||
}
|
||||
|
||||
|
||||
|
||||
class Type:
|
||||
|
||||
def __init__(self, name, const=False, pointer=False):
|
||||
self.name = typemap.get(name) or name
|
||||
self.const = const
|
||||
self.pointer = pointer
|
||||
|
||||
|
||||
|
||||
class Arg:
|
||||
|
||||
def __init__(self, name, type):
|
||||
self.name = name
|
||||
self.type = type
|
||||
self.type = type
|
||||
|
||||
|
||||
class Visitor(c_ast.NodeVisitor):
|
||||
|
||||
def __init__(self, header):
|
||||
self.header = header
|
||||
|
||||
|
||||
def args(self, args):
|
||||
dest = []
|
||||
for arg in args:
|
||||
name = arg.name
|
||||
|
||||
|
||||
if isinstance(arg.type, c_ast.PtrDecl):
|
||||
t = arg.type.type
|
||||
is_ptr = True
|
||||
@@ -40,12 +48,15 @@ class Visitor(c_ast.NodeVisitor):
|
||||
t = arg.type
|
||||
is_ptr = False
|
||||
|
||||
type_name = t.type.names[0]
|
||||
if hasattr(t.type, 'name'):
|
||||
type_name = t.type.name
|
||||
else:
|
||||
type_name = t.type.names[0]
|
||||
const = hasattr(t.type, 'quals') and 'const' in t.type.quals
|
||||
t = Type(type_name, const=const, pointer=is_ptr)
|
||||
dest.append(Arg(name, t))
|
||||
return dest
|
||||
|
||||
|
||||
def visit_FuncDecl(self, node: c_ast.FuncDecl):
|
||||
if isinstance(node.type, c_ast.PtrDecl):
|
||||
t = node.type.type
|
||||
@@ -56,21 +67,26 @@ class Visitor(c_ast.NodeVisitor):
|
||||
|
||||
name = t.declname
|
||||
args = self.args(node.args)
|
||||
return_type_name = t.type.names[0]
|
||||
if hasattr(t.type, 'name'):
|
||||
return_type_name = t.type.name
|
||||
else:
|
||||
return_type_name = t.type.names[0]
|
||||
const = hasattr(t.type, 'quals') and 'const' in t.type.quals
|
||||
return_type = Type(return_type_name, const=const, pointer=is_ptr)
|
||||
self.header.functions.append(Function(name, return_type, args))
|
||||
|
||||
|
||||
|
||||
class Header:
|
||||
|
||||
def __init__(self, filename='runtime/extism.h'):
|
||||
self.functions = []
|
||||
self.header = parse_file(filename, use_cpp=True, cpp_args='-w')
|
||||
self.visitor = Visitor(self)
|
||||
self.visitor.visit(self.header)
|
||||
|
||||
|
||||
def __iter__(self):
|
||||
return self.functions.__iter__()
|
||||
|
||||
|
||||
def __getitem__(self, func):
|
||||
for f in self.functions:
|
||||
if f.name == func:
|
||||
|
||||
Reference in New Issue
Block a user