mirror of
https://github.com/extism/extism.git
synced 2026-01-09 13:57:55 -05:00
refactor: port over missing changes
This commit is contained in:
@@ -3,4 +3,5 @@ members = [
|
||||
"manifest",
|
||||
"runtime",
|
||||
"wasm/rust-pdk",
|
||||
"rust",
|
||||
]
|
||||
|
||||
23
extism.go
23
extism.go
@@ -48,6 +48,15 @@ type Manifest struct {
|
||||
Config map[string]string `json:"config,omitempty"`
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func register(data []byte, wasi bool) (Plugin, error) {
|
||||
plugin := C.extism_plugin_register(
|
||||
(*C.uchar)(unsafe.Pointer(&data[0])),
|
||||
@@ -80,13 +89,25 @@ func LoadPlugin(module io.Reader, wasi bool) (Plugin, error) {
|
||||
return register(wasm, wasi)
|
||||
}
|
||||
|
||||
func (plugin Plugin) SetConfig(data map[string][]byte) error {
|
||||
s, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
C.extism_plugin_config(C.int(plugin.id), (*C.uchar)(unsafe.Pointer(&s[0])), C.uint64_t(len(s)))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (plugin Plugin) Call(functionName string, input []byte) ([]byte, error) {
|
||||
name := C.CString(functionName)
|
||||
rc := C.extism_call(
|
||||
C.int32_t(plugin.id),
|
||||
C.CString(functionName),
|
||||
name,
|
||||
(*C.uchar)(unsafe.Pointer(&input[0])),
|
||||
C.uint64_t(len(input)),
|
||||
)
|
||||
C.free(unsafe.Pointer(name))
|
||||
|
||||
if rc != 0 {
|
||||
error := C.extism_error(C.int32_t(plugin.id))
|
||||
if error != nil {
|
||||
|
||||
5
manifest/README.md
Normal file
5
manifest/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# extsim-manifest
|
||||
|
||||
This crate defines the manifest type for [Extism](https://github.com/extism/extism).
|
||||
|
||||
The JSON Schema definition can be found in [schema.json](https://github.com/extism/extism/blob/main/manifest/schema.json)
|
||||
7
manifest/examples/json_schema.rs
Normal file
7
manifest/examples/json_schema.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
use extism_manifest::Manifest;
|
||||
use schemars::schema_for;
|
||||
|
||||
fn main() {
|
||||
let schema = schema_for!(Manifest);
|
||||
println!("{}", serde_json::to_string_pretty(&schema).unwrap());
|
||||
}
|
||||
3
manifest/json_schema.sh
Executable file
3
manifest/json_schema.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
cargo run --example json_schema --features=json_schema > schema.json
|
||||
133
manifest/schema.json
Normal file
133
manifest/schema.json
Normal file
@@ -0,0 +1,133 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Manifest",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"config": {
|
||||
"default": {},
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"memory": {
|
||||
"default": {
|
||||
"max": null
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ManifestMemory"
|
||||
}
|
||||
]
|
||||
},
|
||||
"wasm": {
|
||||
"default": [],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/ManifestWasm"
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"ManifestMemory": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"max": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
}
|
||||
}
|
||||
},
|
||||
"ManifestWasm": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"path"
|
||||
],
|
||||
"properties": {
|
||||
"hash": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"name": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
},
|
||||
"hash": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"name": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"url"
|
||||
],
|
||||
"properties": {
|
||||
"hash": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"header": {
|
||||
"default": {},
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"method": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"name": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,22 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[derive(Default, serde::Serialize, serde::Deserialize)]
|
||||
#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))]
|
||||
pub struct ManifestMemory {
|
||||
pub max: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))]
|
||||
pub struct HttpRequest {
|
||||
pub url: String,
|
||||
#[serde(default)]
|
||||
pub header: std::collections::BTreeMap<String, String>,
|
||||
pub method: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))]
|
||||
#[serde(untagged)]
|
||||
pub enum ManifestWasm {
|
||||
File {
|
||||
@@ -15,23 +26,31 @@ pub enum ManifestWasm {
|
||||
},
|
||||
Data {
|
||||
#[serde(with = "base64")]
|
||||
#[cfg_attr(feature = "json_schema", schemars(schema_with = "base64_schema"))]
|
||||
data: Vec<u8>,
|
||||
name: Option<String>,
|
||||
hash: Option<String>,
|
||||
},
|
||||
Url {
|
||||
url: String,
|
||||
#[serde(default)]
|
||||
header: BTreeMap<String, String>,
|
||||
#[serde(flatten)]
|
||||
req: HttpRequest,
|
||||
name: Option<String>,
|
||||
method: Option<String>,
|
||||
hash: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
#[cfg(feature = "json_schema")]
|
||||
fn base64_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
|
||||
use schemars::{schema::SchemaObject, JsonSchema};
|
||||
let mut schema: SchemaObject = <String>::json_schema(gen).into();
|
||||
schema.format = Some("string".to_owned());
|
||||
schema.into()
|
||||
}
|
||||
|
||||
#[derive(Default, serde::Serialize, serde::Deserialize)]
|
||||
#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))]
|
||||
pub struct Manifest {
|
||||
#[serde(default = "Vec::new")]
|
||||
#[serde(default)]
|
||||
pub wasm: Vec<ManifestWasm>,
|
||||
#[serde(default)]
|
||||
pub memory: ManifestMemory,
|
||||
|
||||
@@ -11,12 +11,18 @@ var lib = ffi.Library(
|
||||
extism_error: ['char*', ['int32']],
|
||||
extism_call: ['int32', ['int32', 'string', 'string', 'uint64']],
|
||||
extism_output_length: ['uint64', ['int32']],
|
||||
extism_output_get: ['void', ['int32', 'char*', 'uint64']]
|
||||
extism_output_get: ['void', ['int32', 'char*', 'uint64']],
|
||||
extism_log_file: ['bool', ['string', 'char*']],
|
||||
extism_plugin_config: ['void', ['int32', 'char*', 'uint64']],
|
||||
}
|
||||
)
|
||||
|
||||
export function set_log_file(filename, level = null) {
|
||||
lib.extism_log_file(filename, level)
|
||||
}
|
||||
|
||||
export class Plugin {
|
||||
constructor(data, wasi = false) {
|
||||
constructor(data, wasi = false, config = null) {
|
||||
if (typeof data === "object" && data.wasm) {
|
||||
data = JSON.stringify(data);
|
||||
}
|
||||
@@ -25,6 +31,11 @@ export class Plugin {
|
||||
throw "Unable to load plugin";
|
||||
}
|
||||
this.id = plugin;
|
||||
|
||||
if (config != null) {
|
||||
let s = JSON.stringify(config);
|
||||
lib.extism_plugin_config(this.id, s, s.length);
|
||||
}
|
||||
}
|
||||
|
||||
call(name, input) {
|
||||
|
||||
@@ -59,6 +59,9 @@ module Bindings = struct
|
||||
|
||||
let extism_output_get =
|
||||
fn "extism_output_get" (int32_t @-> ptr char @-> uint64_t @-> returning void)
|
||||
|
||||
let extism_log_file =
|
||||
fn "extism_log_file" (string @-> string_opt @-> returning bool)
|
||||
end
|
||||
|
||||
type error = [ `Msg of string ]
|
||||
@@ -125,6 +128,9 @@ end
|
||||
|
||||
exception Failed_to_load_plugin
|
||||
|
||||
let set_log_file ?level filename =
|
||||
Bindings.extism_log_file filename level
|
||||
|
||||
let register ?(wasi = false) wasm =
|
||||
let id =
|
||||
Bindings.extism_plugin_register wasm
|
||||
|
||||
@@ -42,6 +42,7 @@ module Manifest : sig
|
||||
val json: t -> string
|
||||
end
|
||||
|
||||
val set_log_file: ?level:string -> string -> bool
|
||||
val register: ?wasi:bool -> string -> t
|
||||
val register_manifest: ?wasi:bool -> Manifest.t -> t
|
||||
val call_bigstring: t -> name:string -> Bigstringaf.t -> (Bigstringaf.t, error) result
|
||||
|
||||
@@ -86,9 +86,18 @@ class Base64Encoder(json.JSONEncoder):
|
||||
return json.JSONEncoder.default(self, o)
|
||||
|
||||
|
||||
def set_log_file(file, level=ffi.NULL):
|
||||
if isinstance(level, str):
|
||||
level = level.encode()
|
||||
lib.extism_log_file(file.encode(), level)
|
||||
|
||||
|
||||
class Plugin:
|
||||
|
||||
def __init__(self, plugin: Union[str, bytes, dict], wasi=False):
|
||||
def __init__(self,
|
||||
plugin: Union[str, bytes, dict],
|
||||
wasi=False,
|
||||
config=None):
|
||||
if isinstance(plugin, str) and os.path.exists(plugin):
|
||||
with open(plugin, 'rb') as f:
|
||||
wasm = f.read()
|
||||
@@ -102,6 +111,10 @@ class Plugin:
|
||||
# Register plugin
|
||||
self.plugin = lib.extism_plugin_register(wasm, len(wasm), wasi)
|
||||
|
||||
if config is not None:
|
||||
s = json.dumps(config).encode()
|
||||
lib.extism_plugin_config(s, len(s))
|
||||
|
||||
def _check_error(self, rc):
|
||||
if rc != 0:
|
||||
error = lib.extism_error(self.plugin)
|
||||
|
||||
@@ -9,26 +9,40 @@ module C
|
||||
attach_function :extism_call, [:int32, :string, :pointer, :uint64], :int32
|
||||
attach_function :extism_output_length, [:int32], :uint64
|
||||
attach_function :extism_output_get, [:int32, :pointer, :uint64], :void
|
||||
attack_function :extism_log_file, [:string, :pointer], :void
|
||||
end
|
||||
|
||||
class Error < StandardError
|
||||
end
|
||||
|
||||
def set_log_file(name, level=FFI::NIL)
|
||||
if level != FFI::NIL then
|
||||
level = FFI::MemoryPointer::from_string(level)
|
||||
end
|
||||
C.extism_log_file(name, level)
|
||||
end
|
||||
|
||||
|
||||
class Plugin
|
||||
def initialize(wasm, wasi=false)
|
||||
def initialize(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)
|
||||
code.put_bytes(0, wasm)
|
||||
@plugin = C.extism_plugin_register(code, wasm.bytesize, wasi)
|
||||
|
||||
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)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def call(name, data)
|
||||
input = FFI::MemoryPointer::from_string(data)
|
||||
rc = C.extism_call(@plugin, name, input, data.bytesize)
|
||||
if rc != 0 then
|
||||
if rc != 0 then
|
||||
err = C.extism_error(@plugin)
|
||||
if err.empty? then
|
||||
raise Error.new "extism_call failed"
|
||||
|
||||
@@ -22,6 +22,8 @@ serde = { version = "1", features=["derive"] }
|
||||
toml = "0.5"
|
||||
serde_json = "1"
|
||||
sha2 = "0.10"
|
||||
log = "0.4"
|
||||
log4rs = "1.1"
|
||||
ureq = {version = "2.5", optional=true}
|
||||
extism-manifest = { version = "0.0.1-alpha", path = "../manifest" }
|
||||
pretty-hex = { version = "0.3", optional = true }
|
||||
|
||||
@@ -15,7 +15,7 @@ bool extism_plugin_config(ExtismPlugin plugin, const uint8_t *json, ExtismSize j
|
||||
|
||||
bool extism_function_exists(ExtismPlugin plugin, const char *func_name);
|
||||
|
||||
int32_t extism_call(ExtismPlugin plugin,
|
||||
int32_t extism_call(ExtismPlugin plugin_id,
|
||||
const char *func_name,
|
||||
const uint8_t *data,
|
||||
ExtismSize data_len);
|
||||
@@ -25,3 +25,5 @@ const char *extism_error(ExtismPlugin plugin);
|
||||
ExtismSize extism_output_length(ExtismPlugin plugin);
|
||||
|
||||
void extism_output_get(ExtismPlugin plugin, uint8_t *buf, ExtismSize len);
|
||||
|
||||
bool extism_log_file(const char *filename, const char *log_level);
|
||||
|
||||
@@ -261,14 +261,6 @@ pub(crate) fn var_set(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
struct HttpRequest {
|
||||
url: String,
|
||||
#[serde(default)]
|
||||
header: std::collections::BTreeMap<String, String>,
|
||||
method: Option<String>,
|
||||
}
|
||||
|
||||
pub(crate) fn http_request(
|
||||
#[allow(unused_mut)] mut caller: Caller<Internal>,
|
||||
input: &[Val],
|
||||
@@ -277,7 +269,10 @@ pub(crate) fn http_request(
|
||||
#[cfg(not(feature = "http"))]
|
||||
{
|
||||
let _ = (caller, input, output);
|
||||
panic!("HTTP requests have been disabled");
|
||||
|
||||
output[0] = Val::I64(0 as i64);
|
||||
error!("http_request is not enabled");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
#[cfg(feature = "http")]
|
||||
@@ -288,25 +283,37 @@ pub(crate) fn http_request(
|
||||
|
||||
let length = match memory!(data).block_length(offset) {
|
||||
Some(x) => x,
|
||||
None => return Err(Trap::new("Invalid offset in call to config_get")),
|
||||
None => return Err(Trap::new("Invalid offset in call to http_request")),
|
||||
};
|
||||
let buf = memory!(data).get((offset, length));
|
||||
let req: HttpRequest =
|
||||
let req: extism_manifest::HttpRequest =
|
||||
serde_json::from_slice(buf).map_err(|_| Trap::new("Invalid http request"))?;
|
||||
|
||||
let body_offset = input[1].unwrap_i64() as usize;
|
||||
|
||||
let mut r = ureq::request(req.method.as_deref().unwrap_or("GET"), &req.url);
|
||||
|
||||
for (k, v) in req.header.iter() {
|
||||
r = r.set(k, v);
|
||||
}
|
||||
|
||||
let mut r = r
|
||||
.call()
|
||||
.map_err(|e| Trap::new(format!("{:?}", e)))?
|
||||
.into_reader();
|
||||
let mut res = if body_offset > 0 {
|
||||
let length = match memory!(data).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));
|
||||
r.send_bytes(buf)
|
||||
.map_err(|e| Trap::new(&format!("Request error: {e:?}")))?
|
||||
.into_reader()
|
||||
} else {
|
||||
r.call()
|
||||
.map_err(|e| Trap::new(format!("{:?}", e)))?
|
||||
.into_reader()
|
||||
};
|
||||
|
||||
let mut buf = Vec::new();
|
||||
r.read_to_end(&mut buf)
|
||||
res.read_to_end(&mut buf)
|
||||
.map_err(|e| Trap::new(format!("{:?}", e)))?;
|
||||
|
||||
let mem = memory!(mut data).alloc_bytes(buf)?;
|
||||
|
||||
@@ -15,3 +15,5 @@ pub use plugin_ref::PluginRef;
|
||||
|
||||
pub type Size = u64;
|
||||
pub type PluginIndex = i32;
|
||||
|
||||
pub(crate) use log::{debug, error, info};
|
||||
|
||||
@@ -100,9 +100,12 @@ fn to_module(
|
||||
#[allow(unused)]
|
||||
extism_manifest::ManifestWasm::Url {
|
||||
name,
|
||||
url,
|
||||
header,
|
||||
method,
|
||||
req:
|
||||
extism_manifest::HttpRequest {
|
||||
url,
|
||||
header,
|
||||
method,
|
||||
},
|
||||
hash,
|
||||
} => {
|
||||
let file_name = url.split('/').last().unwrap();
|
||||
|
||||
@@ -128,16 +128,24 @@ impl PluginMemory {
|
||||
|
||||
/// Reserve `n` bytes of memory
|
||||
pub fn alloc(&mut self, n: usize) -> Result<MemoryBlock, Error> {
|
||||
debug!("Allocating {n} bytes");
|
||||
|
||||
for (i, block) in self.free.iter_mut().enumerate() {
|
||||
if block.length == n {
|
||||
let block = self.free.swap_remove(i);
|
||||
self.live_blocks.insert(block.offset, block.length);
|
||||
debug!("Found block with exact size at offset {}", block.offset);
|
||||
return Ok(block);
|
||||
} else if block.length - n >= BLOCK_SIZE_THRESHOLD {
|
||||
let handle = MemoryBlock {
|
||||
offset: block.offset,
|
||||
length: n,
|
||||
};
|
||||
debug!(
|
||||
"Using block with size {} at offset {}",
|
||||
block.length, block.offset
|
||||
);
|
||||
|
||||
block.offset += n;
|
||||
block.length -= n;
|
||||
self.live_blocks.insert(handle.offset, handle.length);
|
||||
@@ -147,6 +155,8 @@ impl PluginMemory {
|
||||
|
||||
// If there aren't enough bytes, try to grow the memory size
|
||||
if self.position + n >= self.size() {
|
||||
debug!("Need more memory");
|
||||
|
||||
let bytes_needed = (self.position as f64 + n as f64
|
||||
- self.memory.data_size(&self.store) as f64)
|
||||
/ PAGE_SIZE as f64;
|
||||
@@ -155,6 +165,7 @@ impl PluginMemory {
|
||||
pages_needed = 1
|
||||
}
|
||||
|
||||
info!("Requesting {pages_needed} more pages");
|
||||
// This will fail if we've already allocated the maximum amount of memory allowed
|
||||
self.memory.grow(&mut self.store, pages_needed)?;
|
||||
}
|
||||
@@ -178,6 +189,7 @@ impl PluginMemory {
|
||||
|
||||
/// Free the block allocated at `offset`
|
||||
pub fn free(&mut self, offset: usize) {
|
||||
info!("Freeing block at {offset}");
|
||||
if let Some(length) = self.live_blocks.remove(&offset) {
|
||||
self.free.push(MemoryBlock { offset, length });
|
||||
} else {
|
||||
|
||||
@@ -91,12 +91,12 @@ impl Plugin {
|
||||
// Add builtins
|
||||
for (_name, module) in modules.iter() {
|
||||
for import in module.imports() {
|
||||
let m = import.module();
|
||||
let n = import.name();
|
||||
let module_name = import.module();
|
||||
let name = import.name();
|
||||
use ValType::*;
|
||||
|
||||
if m == EXPORT_MODULE_NAME {
|
||||
define_funcs!(n, {
|
||||
if module_name == EXPORT_MODULE_NAME {
|
||||
define_funcs!(name, {
|
||||
alloc(I64) -> I64;
|
||||
free(I64);
|
||||
load_u8(I64) -> I32;
|
||||
@@ -111,23 +111,15 @@ impl Plugin {
|
||||
config_get(I64) -> I64;
|
||||
var_get(I64) -> I64;
|
||||
var_set(I64, I64);
|
||||
http_request(I64) -> I64;
|
||||
http_request(I64, I64) -> I64;
|
||||
length(I64) -> I64;
|
||||
});
|
||||
}
|
||||
|
||||
// Define memory or check to ensure the symbol is exported by another module
|
||||
// since it doesn't match one of our known exports
|
||||
match import.ty() {
|
||||
ExternType::Memory(t) => {
|
||||
linker.define(m, n, Extern::Memory(Memory::new(&mut memory.store, t)?))?;
|
||||
}
|
||||
ExternType::Func(_f) => {
|
||||
if !m.starts_with("wasi") && !exports.contains_key(n) {
|
||||
panic!("Invalid export: {m}::{n}")
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
if !module_name.starts_with("wasi") && !exports.contains_key(name) {
|
||||
return Err(anyhow::format_err!("Invalid export: {module_name}::{name}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,12 +18,14 @@ impl<'a> PluginRef<'a> {
|
||||
///
|
||||
/// This function is used to access the static `PLUGINS` registry
|
||||
pub unsafe fn new(plugin: PluginIndex) -> Self {
|
||||
let mut plugins = PLUGINS
|
||||
.lock()
|
||||
.expect("Unable to acquire lock on plugin registry");
|
||||
let mut plugins = match PLUGINS.lock() {
|
||||
Ok(p) => p,
|
||||
Err(e) => e.into_inner(),
|
||||
};
|
||||
|
||||
if plugin < 0 || plugin as usize >= plugins.len() {
|
||||
panic!("Invalid PluginIndex {plugin}")
|
||||
drop(plugins);
|
||||
panic!("Invalid PluginIndex {plugin}");
|
||||
}
|
||||
|
||||
let plugin = plugins.get_unchecked_mut(plugin as usize) as *mut _;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#![allow(clippy::missing_safety_doc)]
|
||||
|
||||
use std::os::raw::c_char;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::*;
|
||||
|
||||
@@ -14,18 +15,21 @@ pub unsafe extern "C" fn extism_plugin_register(
|
||||
let plugin = match Plugin::new(data, with_wasi) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
eprintln!("Error creating Plugin: {:?}", e);
|
||||
error!("Error creating Plugin: {:?}", e);
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
||||
// Acquire lock and add plugin to registry
|
||||
if let Ok(mut plugins) = PLUGINS.lock() {
|
||||
plugins.push(plugin);
|
||||
return (plugins.len() - 1) as PluginIndex;
|
||||
}
|
||||
let mut plugins = match PLUGINS.lock() {
|
||||
Ok(p) => p,
|
||||
Err(e) => e.into_inner(),
|
||||
};
|
||||
|
||||
-1
|
||||
// Acquire lock and add plugin to registry
|
||||
plugins.push(plugin);
|
||||
let id = (plugins.len() - 1) as PluginIndex;
|
||||
info!("New plugin added: {id}");
|
||||
return id;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -64,26 +68,37 @@ pub unsafe extern "C" fn extism_function_exists(
|
||||
let mut plugin = PluginRef::new(plugin);
|
||||
|
||||
let name = std::ffi::CStr::from_ptr(func_name);
|
||||
let name = name.to_str().expect("Invalid function name");
|
||||
let name = match name.to_str() {
|
||||
Ok(x) => x,
|
||||
Err(_) => return false,
|
||||
};
|
||||
|
||||
plugin.as_mut().get_func(name).is_some()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn extism_call(
|
||||
plugin: PluginIndex,
|
||||
plugin_id: PluginIndex,
|
||||
func_name: *const c_char,
|
||||
data: *const u8,
|
||||
data_len: Size,
|
||||
) -> i32 {
|
||||
let mut plugin = PluginRef::new(plugin).init();
|
||||
let mut plugin = PluginRef::new(plugin_id).init();
|
||||
let plugin = plugin.as_mut();
|
||||
|
||||
// Find function
|
||||
let name = std::ffi::CStr::from_ptr(func_name);
|
||||
let name = name.to_str().expect("Invalid function name");
|
||||
let func = plugin
|
||||
.get_func(name)
|
||||
.unwrap_or_else(|| panic!("Function not found {name}"));
|
||||
let name = match name.to_str() {
|
||||
Ok(name) => name,
|
||||
Err(e) => return plugin.error(e, -1),
|
||||
};
|
||||
|
||||
debug!("Calling function: {name} in plugin {plugin_id}");
|
||||
|
||||
let func = match plugin.get_func(name) {
|
||||
Some(x) => x,
|
||||
None => return plugin.error(format!("Function not found: {name}"), -1),
|
||||
};
|
||||
|
||||
// Write input to memory
|
||||
let data = std::slice::from_raw_parts(data, data_len as usize);
|
||||
@@ -99,14 +114,14 @@ pub unsafe extern "C" fn extism_call(
|
||||
plugin.set_input(handle);
|
||||
|
||||
// Call function with offset+length pointing to input data.
|
||||
// TODO: In the future this could be a JSON or Protobuf payload.
|
||||
let mut results = vec![Val::I32(0)];
|
||||
match func.call(&mut plugin.memory.store, &[], results.as_mut_slice()) {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
#[cfg(feature = "debug")]
|
||||
plugin.dump_memory();
|
||||
return plugin.error(e.context("Invalid write"), -1);
|
||||
error!("Call: {e:?}");
|
||||
return plugin.error(e.context("Call failed"), -1);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -148,3 +163,66 @@ pub unsafe extern "C" fn extism_output_get(plugin: PluginIndex, buf: *mut u8, le
|
||||
)
|
||||
.expect("Out of bounds read in extism_output_get");
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn extism_log_file(
|
||||
filename: *const c_char,
|
||||
log_level: *const c_char,
|
||||
) -> bool {
|
||||
use log::LevelFilter;
|
||||
use log4rs::append::file::FileAppender;
|
||||
use log4rs::config::{Appender, Config, Root};
|
||||
use log4rs::encode::pattern::PatternEncoder;
|
||||
|
||||
let file = std::ffi::CStr::from_ptr(filename);
|
||||
let file = match file.to_str() {
|
||||
Ok(x) => x,
|
||||
Err(_) => {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
let level = if log_level.is_null() {
|
||||
"error"
|
||||
} else {
|
||||
let level = std::ffi::CStr::from_ptr(log_level);
|
||||
match level.to_str() {
|
||||
Ok(x) => x,
|
||||
Err(_) => {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let level = match LevelFilter::from_str(level) {
|
||||
Ok(x) => x,
|
||||
Err(_) => {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
let logfile = match FileAppender::builder()
|
||||
.encoder(Box::new(PatternEncoder::new("{d}: {l} - {m}\n")))
|
||||
.build(file)
|
||||
{
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
error!("Unable to set up log encoder: {e:?}");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
let config = match Config::builder()
|
||||
.appender(Appender::builder().build("logfile", Box::new(logfile)))
|
||||
.build(Root::builder().appender("logfile").build(level))
|
||||
{
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
error!("Unable to configure log file: {e:?}");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
log4rs::init_config(config).expect("Log initialization failed");
|
||||
true
|
||||
}
|
||||
|
||||
@@ -11,4 +11,5 @@ description = "Extism Host SDK for Rust"
|
||||
|
||||
[dependencies]
|
||||
extism-manifest = { version = "0.0.1-alpha", path = "../manifest" }
|
||||
serde_json = "1"
|
||||
serde_json = "1"
|
||||
log = "0.4"
|
||||
@@ -42,3 +42,9 @@ extern "C" {
|
||||
extern "C" {
|
||||
pub fn extism_output_get(plugin: ExtismPlugin, buf: *mut u8, len: ExtismSize);
|
||||
}
|
||||
extern "C" {
|
||||
pub fn extism_log_file(
|
||||
filename: *const ::std::os::raw::c_char,
|
||||
log_level: *const ::std::os::raw::c_char,
|
||||
) -> bool;
|
||||
}
|
||||
|
||||
@@ -55,6 +55,34 @@ impl Plugin {
|
||||
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 _) }
|
||||
@@ -100,7 +128,9 @@ mod tests {
|
||||
fn it_works() {
|
||||
let wasm = include_bytes!("../../wasm/code.wasm");
|
||||
let wasm_start = Instant::now();
|
||||
let plugin = Plugin::new(wasm, false).unwrap();
|
||||
let plugin = Plugin::new(wasm, false)
|
||||
.unwrap()
|
||||
.with_log_file("test.log", Some(log::LevelFilter::Info));
|
||||
println!("register loaded plugin: {:?}", wasm_start.elapsed());
|
||||
|
||||
let repeat = 1182;
|
||||
@@ -143,7 +173,7 @@ mod tests {
|
||||
// .collect::<Vec<_>>()
|
||||
// .len();
|
||||
|
||||
let mut _native_vowel_count = 0;
|
||||
let mut native_vowel_count2 = 0;
|
||||
let input: &[u8] = input.as_ref();
|
||||
for i in 0..input.len() {
|
||||
if input[i] == b'A'
|
||||
@@ -157,7 +187,7 @@ mod tests {
|
||||
|| input[i] == b'o'
|
||||
|| input[i] == b'u'
|
||||
{
|
||||
_native_vowel_count += 1;
|
||||
native_vowel_count2 += 1;
|
||||
}
|
||||
}
|
||||
native_start.elapsed()
|
||||
|
||||
@@ -8,6 +8,7 @@ homepage = "https://extism.org"
|
||||
repository = "https://github.com/extism/extism"
|
||||
description = "Extism Plug-in Development Kit (PDK) for Rust"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
extism-manifest = {path = "../../manifest"}
|
||||
serde_json = "1"
|
||||
|
||||
@@ -16,6 +16,7 @@ extern "C" {
|
||||
pub fn extism_config_get(offs: u64) -> u64;
|
||||
pub fn extism_kv_get(offs: u64) -> u64;
|
||||
pub fn extism_kv_set(offs: u64, offs1: u64);
|
||||
pub fn extism_http_request(req: u64, body: u64) -> u64;
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
|
||||
@@ -103,6 +103,24 @@ impl Host {
|
||||
unsafe { std::str::from_utf8_unchecked(self.input.as_slice()) }
|
||||
}
|
||||
|
||||
pub fn http_request(
|
||||
&self,
|
||||
req: &extism_manifest::HttpRequest,
|
||||
body: Option<&[u8]>,
|
||||
) -> Result<Vec<u8>, serde_json::Error> {
|
||||
let enc = serde_json::to_vec(req)?;
|
||||
let req = self.alloc_bytes(&enc);
|
||||
let body = match body {
|
||||
Some(b) => self.alloc_bytes(b).offset,
|
||||
None => 0,
|
||||
};
|
||||
let res = unsafe { extism_http_request(req.offset, body) };
|
||||
let len = unsafe { extism_length(res) };
|
||||
let mut dest = vec![0; len as usize];
|
||||
unsafe { bindings::extism_load(res, &mut dest) };
|
||||
Ok(dest)
|
||||
}
|
||||
|
||||
pub fn output(&self, data: impl AsRef<[u8]>) {
|
||||
let len = data.as_ref().len();
|
||||
unsafe {
|
||||
|
||||
Reference in New Issue
Block a user