mirror of
https://github.com/extism/extism.git
synced 2026-04-23 03:00:11 -04:00
524 lines
13 KiB
Go
524 lines
13 KiB
Go
package extism
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"runtime/cgo"
|
|
"unsafe"
|
|
)
|
|
|
|
/*
|
|
#cgo CFLAGS: -I/usr/local/include
|
|
#cgo LDFLAGS: -L/usr/local/lib -lextism
|
|
#include <extism.h>
|
|
#include <stdlib.h>
|
|
|
|
int64_t extism_val_i64(ExtismValUnion* x){
|
|
return x->i64;
|
|
}
|
|
|
|
int32_t extism_val_i32(ExtismValUnion* x){
|
|
return x->i32;
|
|
}
|
|
|
|
float extism_val_f32(ExtismValUnion* x){
|
|
return x->f32;
|
|
}
|
|
|
|
double extism_val_f64(ExtismValUnion* x){
|
|
return x->f64;
|
|
}
|
|
|
|
|
|
void extism_val_set_i64(ExtismValUnion* x, int64_t i){
|
|
x->i64 = i;
|
|
}
|
|
|
|
|
|
void extism_val_set_i32(ExtismValUnion* x, int32_t i){
|
|
x->i32 = i;
|
|
}
|
|
|
|
void extism_val_set_f32(ExtismValUnion* x, float f){
|
|
x->f32 = f;
|
|
}
|
|
|
|
void extism_val_set_f64(ExtismValUnion* x, double f){
|
|
x->f64 = f;
|
|
}
|
|
|
|
*/
|
|
import "C"
|
|
|
|
// Context is used to manage Plugins
|
|
type Context struct {
|
|
pointer *C.ExtismContext
|
|
}
|
|
|
|
type ValType = C.ExtismValType
|
|
|
|
type Val = C.ExtismVal
|
|
|
|
type Size = C.ExtismSize
|
|
|
|
var (
|
|
I32 ValType = C.I32
|
|
I64 ValType = C.I64
|
|
F32 ValType = C.F32
|
|
F64 ValType = C.F64
|
|
V128 ValType = C.V128
|
|
FuncRef ValType = C.FuncRef
|
|
ExternRef ValType = C.ExternRef
|
|
)
|
|
|
|
// Function is used to define host functions
|
|
type Function struct {
|
|
pointer *C.ExtismFunction
|
|
userData cgo.Handle
|
|
}
|
|
|
|
// Free a function
|
|
func (f *Function) Free() {
|
|
C.extism_function_free(f.pointer)
|
|
f.pointer = nil
|
|
f.userData.Delete()
|
|
}
|
|
|
|
// NewFunction creates a new host function with the given name, input/outputs and optional user data, which can be an
|
|
// arbitrary `interface{}`
|
|
func NewFunction(name string, inputs []ValType, outputs []ValType, f unsafe.Pointer, userData interface{}) Function {
|
|
var function Function
|
|
function.userData = cgo.NewHandle(userData)
|
|
cname := C.CString(name)
|
|
ptr := unsafe.Pointer(function.userData)
|
|
var inputsPtr *C.ExtismValType = nil
|
|
if len(inputs) > 0 {
|
|
inputsPtr = (*C.ExtismValType)(&inputs[0])
|
|
}
|
|
var outputsPtr *C.ExtismValType = nil
|
|
if len(outputs) > 0 {
|
|
outputsPtr = (*C.ExtismValType)(&outputs[0])
|
|
}
|
|
function.pointer = C.extism_function_new(
|
|
cname,
|
|
inputsPtr,
|
|
C.uint64_t(len(inputs)),
|
|
outputsPtr,
|
|
C.uint64_t(len(outputs)),
|
|
(*[0]byte)(f),
|
|
ptr,
|
|
nil,
|
|
)
|
|
C.free(unsafe.Pointer(cname))
|
|
return function
|
|
}
|
|
|
|
func (f *Function) SetNamespace(s string) {
|
|
cstr := C.CString(s)
|
|
defer C.free(unsafe.Pointer(cstr))
|
|
C.extism_function_set_namespace(f.pointer, cstr)
|
|
}
|
|
|
|
func (f Function) WithNamespace(s string) Function {
|
|
f.SetNamespace(s)
|
|
return f
|
|
}
|
|
|
|
type CurrentPlugin struct {
|
|
pointer *C.ExtismCurrentPlugin
|
|
}
|
|
|
|
func GetCurrentPlugin(ptr unsafe.Pointer) CurrentPlugin {
|
|
return CurrentPlugin{
|
|
pointer: (*C.ExtismCurrentPlugin)(ptr),
|
|
}
|
|
}
|
|
|
|
func (p *CurrentPlugin) Memory(offs uint) []byte {
|
|
length := C.extism_current_plugin_memory_length(p.pointer, C.uint64_t(offs))
|
|
data := unsafe.Pointer(C.extism_current_plugin_memory(p.pointer))
|
|
return unsafe.Slice((*byte)(unsafe.Add(data, offs)), C.int(length))
|
|
}
|
|
|
|
// Alloc a new memory block of the given length, returning its offset
|
|
func (p *CurrentPlugin) Alloc(n uint) uint {
|
|
return uint(C.extism_current_plugin_memory_alloc(p.pointer, C.uint64_t(n)))
|
|
}
|
|
|
|
// Free the memory block specified by the given offset
|
|
func (p *CurrentPlugin) Free(offs uint) {
|
|
C.extism_current_plugin_memory_free(p.pointer, C.uint64_t(offs))
|
|
}
|
|
|
|
// Length returns the number of bytes allocated at the specified offset
|
|
func (p *CurrentPlugin) Length(offs uint) uint {
|
|
return uint(C.extism_current_plugin_memory_length(p.pointer, C.uint64_t(offs)))
|
|
}
|
|
|
|
// 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 {
|
|
ctx *Context
|
|
id int32
|
|
functions []Function
|
|
}
|
|
|
|
type WasmData struct {
|
|
Data []byte `json:"data"`
|
|
Hash string `json:"hash,omitempty"`
|
|
Name string `json:"name,omitempty"`
|
|
}
|
|
|
|
type WasmFile struct {
|
|
Path string `json:"path"`
|
|
Hash string `json:"hash,omitempty"`
|
|
Name string `json:"name,omitempty"`
|
|
}
|
|
|
|
type WasmUrl struct {
|
|
Url string `json:"url"`
|
|
Hash string `json:"hash,omitempty"`
|
|
Headers map[string]string `json:"headers,omitempty"`
|
|
Name string `json:"name,omitempty"`
|
|
Method string `json:"method,omitempty"`
|
|
}
|
|
|
|
type Wasm interface{}
|
|
|
|
type Manifest struct {
|
|
Wasm []Wasm `json:"wasm"`
|
|
Memory struct {
|
|
MaxPages uint32 `json:"max_pages,omitempty"`
|
|
} `json:"memory,omitempty"`
|
|
Config map[string]string `json:"config,omitempty"`
|
|
AllowedHosts []string `json:"allowed_hosts,omitempty"`
|
|
AllowedPaths map[string]string `json:"allowed_paths,omitempty"`
|
|
Timeout uint `json:"timeout_ms,omitempty"`
|
|
}
|
|
|
|
func makePointer(data []byte) unsafe.Pointer {
|
|
var ptr unsafe.Pointer = nil
|
|
if len(data) > 0 {
|
|
ptr = unsafe.Pointer(&data[0])
|
|
}
|
|
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)
|
|
r := C.extism_log_file(name, l)
|
|
C.free(unsafe.Pointer(name))
|
|
C.free(unsafe.Pointer(l))
|
|
return bool(r)
|
|
}
|
|
|
|
// ExtismVersion gets the Extism version string
|
|
func ExtismVersion() string {
|
|
return C.GoString(C.extism_version())
|
|
}
|
|
|
|
func register(ctx *Context, data []byte, functions []Function, wasi bool) (Plugin, error) {
|
|
ptr := makePointer(data)
|
|
functionPointers := []*C.ExtismFunction{}
|
|
for _, f := range functions {
|
|
functionPointers = append(functionPointers, f.pointer)
|
|
}
|
|
plugin := C.int32_t(-1)
|
|
|
|
if len(functions) == 0 {
|
|
plugin = C.extism_plugin_new(
|
|
ctx.pointer,
|
|
(*C.uchar)(ptr),
|
|
C.uint64_t(len(data)),
|
|
nil,
|
|
0,
|
|
C._Bool(wasi))
|
|
} else {
|
|
plugin = C.extism_plugin_new(
|
|
ctx.pointer,
|
|
(*C.uchar)(ptr),
|
|
C.uint64_t(len(data)),
|
|
&functionPointers[0],
|
|
C.uint64_t(len(functions)),
|
|
C._Bool(wasi),
|
|
)
|
|
}
|
|
|
|
if plugin < 0 {
|
|
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), ctx: ctx, functions: functions}, nil
|
|
}
|
|
|
|
func update(ctx *Context, plugin int32, data []byte, functions []Function, wasi bool) error {
|
|
ptr := makePointer(data)
|
|
functionPointers := []*C.ExtismFunction{}
|
|
for _, f := range functions {
|
|
functionPointers = append(functionPointers, f.pointer)
|
|
}
|
|
|
|
if len(functions) == 0 {
|
|
b := bool(C.extism_plugin_update(
|
|
ctx.pointer,
|
|
C.int32_t(plugin),
|
|
(*C.uchar)(ptr),
|
|
C.uint64_t(len(data)),
|
|
nil,
|
|
0,
|
|
C._Bool(wasi),
|
|
))
|
|
|
|
if b {
|
|
return nil
|
|
}
|
|
} else {
|
|
b := bool(C.extism_plugin_update(
|
|
ctx.pointer,
|
|
C.int32_t(plugin),
|
|
(*C.uchar)(ptr),
|
|
C.uint64_t(len(data)),
|
|
&functionPointers[0],
|
|
C.uint64_t(len(functions)),
|
|
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),
|
|
)
|
|
}
|
|
|
|
// NewPlugin creates a plugin in its own context
|
|
func NewPlugin(module io.Reader, functions []Function, wasi bool) (Plugin, error) {
|
|
ctx := NewContext()
|
|
return ctx.Plugin(module, functions, wasi)
|
|
}
|
|
|
|
// NewPlugin creates a plugin in its own context from a manifest
|
|
func NewPluginFromManifest(manifest Manifest, functions []Function, wasi bool) (Plugin, error) {
|
|
ctx := NewContext()
|
|
return ctx.PluginFromManifest(manifest, functions, wasi)
|
|
}
|
|
|
|
// PluginFromManifest creates a plugin from a `Manifest`
|
|
func (ctx *Context) PluginFromManifest(manifest Manifest, functions []Function, wasi bool) (Plugin, error) {
|
|
data, err := json.Marshal(manifest)
|
|
if err != nil {
|
|
return Plugin{id: -1}, err
|
|
}
|
|
|
|
return register(ctx, data, functions, wasi)
|
|
}
|
|
|
|
// Plugin creates a plugin from a WASM module
|
|
func (ctx *Context) Plugin(module io.Reader, functions []Function, wasi bool) (Plugin, error) {
|
|
wasm, err := io.ReadAll(module)
|
|
if err != nil {
|
|
return Plugin{id: -1}, err
|
|
}
|
|
|
|
return register(ctx, wasm, functions, wasi)
|
|
}
|
|
|
|
// Update a plugin with a new WASM module
|
|
func (p *Plugin) Update(module io.Reader, functions []Function, wasi bool) error {
|
|
wasm, err := io.ReadAll(module)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
p.functions = functions
|
|
return update(p.ctx, p.id, wasm, functions, wasi)
|
|
}
|
|
|
|
// Update a plugin with a new Manifest
|
|
func (p *Plugin) UpdateManifest(manifest Manifest, functions []Function, wasi bool) error {
|
|
data, err := json.Marshal(manifest)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
p.functions = functions
|
|
return update(p.ctx, p.id, data, functions, 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(plugin.ctx.pointer, C.int(plugin.id), (*C.uchar)(ptr), C.uint64_t(len(s)))
|
|
return nil
|
|
}
|
|
|
|
// FunctionExists returns true when the named 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_plugin_call(
|
|
plugin.ctx.pointer,
|
|
C.int32_t(plugin.id),
|
|
name,
|
|
(*C.uchar)(ptr),
|
|
C.uint64_t(len(input)),
|
|
)
|
|
C.free(unsafe.Pointer(name))
|
|
|
|
if rc != 0 {
|
|
err := C.extism_error(plugin.ctx.pointer, C.int32_t(plugin.id))
|
|
msg := "<unset by plugin>"
|
|
if err != nil {
|
|
msg = C.GoString(err)
|
|
}
|
|
|
|
return nil, errors.New(
|
|
fmt.Sprintf("Plugin error: %s, code: %d", msg, rc),
|
|
)
|
|
}
|
|
|
|
length := C.extism_plugin_output_length(plugin.ctx.pointer, C.int32_t(plugin.id))
|
|
|
|
if length > 0 {
|
|
x := C.extism_plugin_output_data(plugin.ctx.pointer, C.int32_t(plugin.id))
|
|
return unsafe.Slice((*byte)(x), C.int(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)
|
|
}
|
|
|
|
// ValGetI64 returns an I64 from an ExtismVal, it accepts a pointer to a C.ExtismVal
|
|
func ValGetI64(v unsafe.Pointer) int64 {
|
|
return int64(C.extism_val_i64(&(*Val)(v).v))
|
|
}
|
|
|
|
// ValGetUInt returns a uint from an ExtismVal, it accepts a pointer to a C.ExtismVal
|
|
func ValGetUInt(v unsafe.Pointer) uint {
|
|
return uint(C.extism_val_i64(&(*Val)(v).v))
|
|
}
|
|
|
|
// ValGetI32 returns an int32 from an ExtismVal, it accepts a pointer to a C.ExtismVal
|
|
func ValGetI32(v unsafe.Pointer) int32 {
|
|
return int32(C.extism_val_i32(&(*Val)(v).v))
|
|
}
|
|
|
|
// ValGetF32 returns a float32 from an ExtismVal, it accepts a pointer to a C.ExtismVal
|
|
func ValGetF32(v unsafe.Pointer) float32 {
|
|
return float32(C.extism_val_f32(&(*Val)(v).v))
|
|
}
|
|
|
|
// ValGetF32 returns a float64 from an ExtismVal, it accepts a pointer to a C.ExtismVal
|
|
func ValGetF64(v unsafe.Pointer) float64 {
|
|
return float64(C.extism_val_i64(&(*Val)(v).v))
|
|
}
|
|
|
|
// ValSetI64 stores an int64 in an ExtismVal, it accepts a pointer to a C.ExtismVal and the new value
|
|
func ValSetI64(v unsafe.Pointer, i int64) {
|
|
C.extism_val_set_i64(&(*Val)(v).v, C.int64_t(i))
|
|
}
|
|
|
|
// ValSetI32 stores an int32 in an ExtismVal, it accepts a pointer to a C.ExtismVal and the new value
|
|
func ValSetI32(v unsafe.Pointer, i int32) {
|
|
C.extism_val_set_i32(&(*Val)(v).v, C.int32_t(i))
|
|
}
|
|
|
|
// ValSetF32 stores a float32 in an ExtismVal, it accepts a pointer to a C.ExtismVal and the new value
|
|
func ValSetF32(v unsafe.Pointer, i float32) {
|
|
C.extism_val_set_f32(&(*Val)(v).v, C.float(i))
|
|
}
|
|
|
|
// ValSetF64 stores a float64 in an ExtismVal, it accepts a pointer to a C.ExtismVal and the new value
|
|
func ValSetF64(v unsafe.Pointer, f float64) {
|
|
C.extism_val_set_f64(&(*Val)(v).v, C.double(f))
|
|
}
|
|
|
|
func (p *CurrentPlugin) ReturnBytes(v unsafe.Pointer, b []byte) {
|
|
mem := p.Alloc(uint(len(b)))
|
|
ptr := p.Memory(mem)
|
|
copy(ptr, b)
|
|
ValSetI64(v, int64(mem))
|
|
}
|
|
|
|
func (p *CurrentPlugin) ReturnString(v unsafe.Pointer, s string) {
|
|
p.ReturnBytes(v, []byte(s))
|
|
}
|
|
|
|
func (p *CurrentPlugin) InputBytes(v unsafe.Pointer) []byte {
|
|
return p.Memory(ValGetUInt(v))
|
|
}
|
|
|
|
func (p *CurrentPlugin) InputString(v unsafe.Pointer) string {
|
|
return string(p.InputBytes(v))
|
|
}
|
|
|
|
type CancelHandle struct {
|
|
pointer *C.ExtismCancelHandle
|
|
}
|
|
|
|
func (p *Plugin) CancelHandle() CancelHandle {
|
|
pointer := C.extism_plugin_cancel_handle(p.ctx.pointer, C.int(p.id))
|
|
return CancelHandle{pointer}
|
|
}
|
|
|
|
func (c *CancelHandle) Cancel() bool {
|
|
return bool(C.extism_plugin_cancel(c.pointer))
|
|
}
|