mirror of
https://github.com/extism/extism.git
synced 2026-01-11 14:58:01 -05:00
Compare commits
7 Commits
wasi-reado
...
userdata
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d704a5068e | ||
|
|
054a29e91d | ||
|
|
d32d4a3dd7 | ||
|
|
5f62554aa1 | ||
|
|
d47af24552 | ||
|
|
8a29e5b1d4 | ||
|
|
4e0cd3b1cf |
@@ -8,7 +8,7 @@
|
||||
|
||||
[](https://extism.org/discord)
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ description = "Traits to make Rust types usable with Extism"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.75"
|
||||
base64 = "~0.21"
|
||||
base64 = "~0.22"
|
||||
bytemuck = {version = "1.14.0", optional = true }
|
||||
prost = { version = "0.12.0", optional = true }
|
||||
protobuf = { version = "3.2.0", optional = true }
|
||||
|
||||
@@ -10,7 +10,7 @@ version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
base64 = "~0.21"
|
||||
base64 = "~0.22"
|
||||
schemars = { version = "0.8", optional = true }
|
||||
serde_json = "1"
|
||||
|
||||
|
||||
@@ -38,7 +38,8 @@
|
||||
"description": "Memory options",
|
||||
"default": {
|
||||
"max_http_response_bytes": null,
|
||||
"max_pages": null
|
||||
"max_pages": null,
|
||||
"max_var_bytes": null
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
@@ -47,7 +48,7 @@
|
||||
]
|
||||
},
|
||||
"timeout_ms": {
|
||||
"description": "The plugin timeout, by default this is set to 30s",
|
||||
"description": "The plugin timeout in milliseconds",
|
||||
"default": null,
|
||||
"type": [
|
||||
"integer",
|
||||
@@ -89,6 +90,16 @@
|
||||
],
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"max_var_bytes": {
|
||||
"description": "The maximum number of bytes allowed to be used by plugin vars. Setting this to 0 will disable Extism vars. The default value is 1mb.",
|
||||
"default": 1048576,
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "uint64",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
|
||||
@@ -16,6 +16,40 @@ pub struct MemoryOptions {
|
||||
/// The maximum number of bytes allowed in an HTTP response
|
||||
#[serde(default)]
|
||||
pub max_http_response_bytes: Option<u64>,
|
||||
|
||||
/// The maximum number of bytes allowed to be used by plugin vars. Setting this to 0
|
||||
/// will disable Extism vars. The default value is 1mb.
|
||||
#[serde(default = "default_var_bytes")]
|
||||
pub max_var_bytes: Option<u64>,
|
||||
}
|
||||
|
||||
impl MemoryOptions {
|
||||
/// Create an empty `MemoryOptions` value
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Set max pages
|
||||
pub fn with_max_pages(mut self, pages: u32) -> Self {
|
||||
self.max_pages = Some(pages);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set max HTTP response size
|
||||
pub fn with_max_http_response_bytes(mut self, bytes: u64) -> Self {
|
||||
self.max_http_response_bytes = Some(bytes);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set max size of Extism vars
|
||||
pub fn with_max_var_bytes(mut self, bytes: u64) -> Self {
|
||||
self.max_var_bytes = Some(bytes);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
fn default_var_bytes() -> Option<u64> {
|
||||
Some(1024 * 1024)
|
||||
}
|
||||
|
||||
/// Generic HTTP request structure
|
||||
@@ -249,7 +283,7 @@ pub struct Manifest {
|
||||
#[serde(default)]
|
||||
pub allowed_paths: Option<BTreeMap<PathBuf, PathBuf>>,
|
||||
|
||||
/// The plugin timeout, by default this is set to 30s
|
||||
/// The plugin timeout in milliseconds
|
||||
#[serde(default)]
|
||||
pub timeout_ms: Option<u64>,
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use extism::*;
|
||||
|
||||
// pretend this is redis or something :)
|
||||
type KVStore = std::collections::BTreeMap<String, Vec<u8>>;
|
||||
type KVStore = std::sync::Arc<std::sync::Mutex<std::collections::BTreeMap<String, Vec<u8>>>>;
|
||||
|
||||
// When a first argument separated with a semicolon is provided to `host_fn` it is used as the
|
||||
// variable name and type for the `UserData` parameter
|
||||
|
||||
@@ -81,18 +81,18 @@ pub(crate) enum UserDataHandle {
|
||||
/// using `UserData::get`. The `C` data is stored as a pointer and cleanup function and isn't usable from Rust. The cleanup function
|
||||
/// will be called when the inner `CPtr` is dropped.
|
||||
#[derive(Debug)]
|
||||
pub enum UserData<T: Sized> {
|
||||
pub enum UserData<T: Sync + Clone + Sized> {
|
||||
C(Arc<CPtr>),
|
||||
Rust(Arc<std::sync::Mutex<T>>),
|
||||
Rust(T),
|
||||
}
|
||||
|
||||
impl<T: Default> Default for UserData<T> {
|
||||
impl<T: Default + Sync + Clone> Default for UserData<T> {
|
||||
fn default() -> Self {
|
||||
UserData::new(T::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for UserData<T> {
|
||||
impl<T: Sync + Clone> Clone for UserData<T> {
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
UserData::C(ptr) => UserData::C(ptr.clone()),
|
||||
@@ -101,7 +101,7 @@ impl<T> Clone for UserData<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> UserData<T> {
|
||||
impl<T: Sync + Clone> UserData<T> {
|
||||
/// Create a new `UserData` from an existing pointer and free function, this is used
|
||||
/// by the C API to wrap C pointers into user data
|
||||
pub(crate) fn new_pointer(
|
||||
@@ -126,12 +126,11 @@ impl<T> UserData<T> {
|
||||
///
|
||||
/// This will wrap the provided value in a reference-counted mutex
|
||||
pub fn new(x: T) -> Self {
|
||||
let data = Arc::new(std::sync::Mutex::new(x));
|
||||
UserData::Rust(data)
|
||||
UserData::Rust(x)
|
||||
}
|
||||
|
||||
/// Get a copy of the inner value
|
||||
pub fn get(&self) -> Result<Arc<std::sync::Mutex<T>>, Error> {
|
||||
pub fn get(&self) -> Result<T, Error> {
|
||||
match self {
|
||||
UserData::C { .. } => anyhow::bail!("C UserData should not be used from Rust"),
|
||||
UserData::Rust(data) => Ok(data.clone()),
|
||||
@@ -150,8 +149,8 @@ impl Drop for CPtr {
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T> Send for UserData<T> {}
|
||||
unsafe impl<T> Sync for UserData<T> {}
|
||||
unsafe impl<T: Sync + Clone> Send for UserData<T> {}
|
||||
unsafe impl<T: Sync + Clone> Sync for UserData<T> {}
|
||||
unsafe impl Send for CPtr {}
|
||||
unsafe impl Sync for CPtr {}
|
||||
|
||||
@@ -180,7 +179,7 @@ pub struct Function {
|
||||
|
||||
impl Function {
|
||||
/// Create a new host function
|
||||
pub fn new<T: 'static, F>(
|
||||
pub fn new<T: 'static + Sync + Clone, F>(
|
||||
name: impl Into<String>,
|
||||
args: impl IntoIterator<Item = ValType>,
|
||||
returns: impl IntoIterator<Item = ValType>,
|
||||
@@ -211,7 +210,9 @@ impl Function {
|
||||
namespace: None,
|
||||
_user_data: match &user_data {
|
||||
UserData::C(ptr) => UserDataHandle::C(ptr.clone()),
|
||||
UserData::Rust(x) => UserDataHandle::Rust(x.clone()),
|
||||
UserData::Rust(x) => {
|
||||
UserDataHandle::Rust(std::sync::Arc::new(std::sync::Mutex::new(x.clone())))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,19 +97,13 @@ pub(crate) fn var_set(
|
||||
) -> Result<(), Error> {
|
||||
let data: &mut CurrentPlugin = caller.data_mut();
|
||||
|
||||
let mut size = 0;
|
||||
for v in data.vars.values() {
|
||||
size += v.len();
|
||||
if data.manifest.memory.max_var_bytes.is_some_and(|x| x == 0) {
|
||||
anyhow::bail!("Vars are disabled by this host")
|
||||
}
|
||||
|
||||
let voffset = args!(input, 1, i64) as u64;
|
||||
|
||||
// If the store is larger than 100MB then stop adding things
|
||||
if size > 1024 * 1024 * 100 && voffset != 0 {
|
||||
return Err(Error::msg("Variable store is full"));
|
||||
}
|
||||
|
||||
let key_offs = args!(input, 0, i64) as u64;
|
||||
|
||||
let key = {
|
||||
let handle = match data.memory_handle(key_offs) {
|
||||
Some(h) => h,
|
||||
@@ -132,6 +126,22 @@ pub(crate) fn var_set(
|
||||
None => anyhow::bail!("invalid handle offset for var value: {voffset}"),
|
||||
};
|
||||
|
||||
let mut size = std::mem::size_of::<String>()
|
||||
+ std::mem::size_of::<Vec<u8>>()
|
||||
+ key.len()
|
||||
+ handle.length as usize;
|
||||
|
||||
for (k, v) in data.vars.iter() {
|
||||
size += k.len();
|
||||
size += v.len();
|
||||
size += std::mem::size_of::<String>() + std::mem::size_of::<Vec<u8>>();
|
||||
}
|
||||
|
||||
// If the store is larger than the configured size, or 1mb by default, then stop adding things
|
||||
if size > data.manifest.memory.max_var_bytes.unwrap_or(1024 * 1024) as usize && voffset != 0 {
|
||||
return Err(Error::msg("Variable store is full"));
|
||||
}
|
||||
|
||||
let value = data.memory_bytes(handle)?.to_vec();
|
||||
|
||||
// Insert the value from memory into the `vars` map
|
||||
@@ -226,11 +236,12 @@ pub(crate) fn http_request(
|
||||
Some(res.into_reader())
|
||||
}
|
||||
Err(e) => {
|
||||
let msg = e.to_string();
|
||||
if let Some(res) = e.into_response() {
|
||||
data.http_status = res.status();
|
||||
Some(res.into_reader())
|
||||
} else {
|
||||
None
|
||||
return Err(Error::msg(msg));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -60,7 +60,7 @@ impl<'a> PluginBuilder<'a> {
|
||||
}
|
||||
|
||||
/// Add a single host function
|
||||
pub fn with_function<T: 'static, F>(
|
||||
pub fn with_function<T: Sync + Clone + 'static, F>(
|
||||
mut self,
|
||||
name: impl Into<String>,
|
||||
args: impl IntoIterator<Item = ValType>,
|
||||
@@ -80,7 +80,7 @@ impl<'a> PluginBuilder<'a> {
|
||||
}
|
||||
|
||||
/// Add a single host function in a specific namespace
|
||||
pub fn with_function_in_namespace<T: 'static, F>(
|
||||
pub fn with_function_in_namespace<T: Sync + Clone + 'static, F>(
|
||||
mut self,
|
||||
namespace: impl Into<String>,
|
||||
name: impl Into<String>,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use extism_manifest::MemoryOptions;
|
||||
|
||||
use crate::*;
|
||||
use std::{io::Write, time::Instant};
|
||||
|
||||
@@ -451,7 +453,7 @@ fn hello_world_user_data(
|
||||
_plugin: &mut CurrentPlugin,
|
||||
inputs: &[Val],
|
||||
outputs: &mut [Val],
|
||||
user_data: UserData<std::fs::File>,
|
||||
user_data: UserData<std::sync::Arc<std::sync::Mutex<std::fs::File>>>,
|
||||
) -> Result<(), Error> {
|
||||
let data = user_data.get()?;
|
||||
let mut data = data.lock().unwrap();
|
||||
@@ -468,7 +470,8 @@ fn test_userdata() {
|
||||
if path.exists() {
|
||||
std::fs::remove_file(&path).unwrap();
|
||||
}
|
||||
let file = std::fs::File::create(&path).unwrap();
|
||||
let file =
|
||||
std::sync::Arc::new(std::sync::Mutex::new(std::fs::File::create(&path).unwrap()));
|
||||
let f = Function::new(
|
||||
"hello_world",
|
||||
[PTR],
|
||||
@@ -594,6 +597,29 @@ fn test_manifest_ptr_len() {
|
||||
assert_eq!(count.get("count").unwrap().as_i64().unwrap(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_no_vars() {
|
||||
let data = br#"
|
||||
(module
|
||||
(import "extism:host/env" "var_set" (func $var_set (param i64 i64)))
|
||||
(import "extism:host/env" "input_offset" (func $input_offset (result i64)))
|
||||
(func (export "test") (result i32)
|
||||
(call $input_offset)
|
||||
(call $input_offset)
|
||||
(call $var_set)
|
||||
(i32.const 0)
|
||||
)
|
||||
)
|
||||
"#;
|
||||
let manifest = Manifest::new([Wasm::data(data)])
|
||||
.with_memory_options(MemoryOptions::new().with_max_var_bytes(1));
|
||||
let mut plugin = Plugin::new(manifest, [], true).unwrap();
|
||||
let output: Result<(), Error> = plugin.call("test", b"A".repeat(1024));
|
||||
assert!(output.is_err());
|
||||
let output: Result<(), Error> = plugin.call("test", vec![]);
|
||||
assert!(output.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_linking() {
|
||||
let manifest = Manifest::new([
|
||||
|
||||
Reference in New Issue
Block a user