refactor: port over missing changes

This commit is contained in:
zach
2022-08-25 20:26:36 -07:00
parent d62d08c070
commit 64856207d0
27 changed files with 464 additions and 73 deletions

View File

@@ -3,4 +3,5 @@ members = [
"manifest",
"runtime",
"wasm/rust-pdk",
"rust",
]

View File

@@ -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
View 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)

View 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
View 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
View 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"
}
}
}
]
}
}
}

View File

@@ -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,

View File

@@ -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) {

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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"

View File

@@ -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 }

View File

@@ -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);

View File

@@ -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)?;

View File

@@ -15,3 +15,5 @@ pub use plugin_ref::PluginRef;
pub type Size = u64;
pub type PluginIndex = i32;
pub(crate) use log::{debug, error, info};

View File

@@ -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();

View File

@@ -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 {

View File

@@ -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}"));
}
}
}

View File

@@ -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 _;

View File

@@ -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
}

View File

@@ -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"

View File

@@ -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;
}

View File

@@ -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()

View File

@@ -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"

View File

@@ -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

View File

@@ -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 {