Compare commits

...

28 Commits

Author SHA1 Message Date
zach
e7127759a6 fix: private fields 2024-05-31 10:50:07 -07:00
zach
ed5bad922a cleanup: add ways to get output from WasiOutput 2024-05-31 10:43:48 -07:00
zach
5af226219e cleanup: require store reset on error 2024-05-31 10:23:28 -07:00
zach
b0e6387eef cleanup: enable wasi whenever run_wasi_command is used 2024-05-31 10:23:28 -07:00
zach
6a8e65eeb3 chore: clippy 2024-05-31 10:23:27 -07:00
zach
e8041dc5be doc: add comment 2024-05-31 10:22:41 -07:00
zach
433276274b cleanup: run_command 2024-05-31 10:22:40 -07:00
zach
a531e337fe feat: add Plugin::run_command 2024-05-31 10:20:44 -07:00
zach
ea29e080a6 cleanup: allow blocking thread 2024-05-31 10:17:55 -07:00
zach
c4312fe324 chore: updates for ExternRef changes upstream 2024-05-31 10:17:55 -07:00
zach
3ab6137ff1 chore: allow wastime 21 2024-05-31 10:17:55 -07:00
zach
fde222a6a0 cleanup: enable wasm gc 2024-05-31 10:17:55 -07:00
zach
ec4d45fb88 chore: use wasmtime 20 2024-05-31 10:17:55 -07:00
zach
345d2d9f3b fix: don't silently ignore non-directory paths 2024-05-31 10:17:55 -07:00
zach
d8460d6aa2 chore: clippy 2024-05-31 10:17:55 -07:00
zach
ecc6463e6c chore: update to wasmtime 19 2024-05-31 10:17:53 -07:00
zach
ecf18a2d81 fix: re-use linking code from Plugin::new in Plugin::reset_store (#722) 2024-05-31 08:25:06 -07:00
zach
9bc1fc73f2 chore: rename kernel/.cargo/config to kernel/.cargo/config.toml (#721) 2024-05-28 18:26:19 -07:00
zach
2bf391f236 doc: update API.md for latest changes (#720) 2024-05-28 09:27:17 -07:00
Muhammad Azeez
5da0eb38ec refactor: remove HttpRequest.header alias (#718)
https://github.com/extism/dotnet-pdk/pull/84 brought to my attention
that the Rust SDK supports both `headers` and `header` while the go sdk
only supports `headers`. After talking to Zach, we decided to remove the
`header` alias from the Rust SDK too, since it's obsolete and we want
people to use `headers`
2024-05-22 20:34:35 +03:00
zach
7cb6c53910 v1.3.0 2024-05-22 10:07:06 -07:00
zach
0882f35300 fix: respect overall timeout when using http requests (#717)
Currently HTTP requests can extend beyond the configured timeout for a
plugin - this PR sets the timeout of the HTTP request to the remaining
time left if a timeout is set in the manifest.
2024-05-21 20:11:00 -07:00
Chris Dickinson
5d9c8c5d05 feat: per call context (#711)
Add `plugin.call_with_host_context` and `current_plugin.host_context`
methods, enabling per-call context to be looped from the guest invocation
to any host functions it calls. In an HTTP server environment, this enables
re-using a plugin across multiple requests while switching out backing
connections and user information in host functions. (Imagine a host
function, `update_user` -- previously the plugin would have to have been
aware of the user to pass to the host function. Now that information is
ambient.)

This is a backwards-compatible change and requires no changes to
existing plugins.

Implement by adding a global, mutable externref to the extism kernel.
Since most programming languages, including Rust, don't let you define
these natively, we accomplish this by using `wasm-merge` to combine the
kernel Wasm with Wasm generated by a WAT file containing only the
global.

(This pattern might be useful for other Wasm constructs we can't use
directly from Rust, like `v128` in argument parameters.)

Wasmtime requires extern refs to be `Any + Send + Sync + 'static`; we
additionally add `Clone`. I haven't tried this with an `Arc` directly,
but it should work at least for container structs that hold `Arc`'s
themselves.
2024-05-21 11:53:43 -07:00
Gavin Hayes
75e92c40a0 fix(libextism): namespace ExtismValType (#715)
Some of the old constants conflicted with Perl headers used for build
Perl extensions such the
[perl-sdk](002e4437ac/Extism/lib/Extism/XS.xs).
This fix would allow removing the copy of extism.h (inlined in that file
linked) out of the perl-sdk.

These constants especially `I32` likely have conflicts with many other
large C codebases.

A new extism release with this fix should not be made until the affected
sdks (cpp-sdk) have an update ready.
2024-05-20 11:58:04 -04:00
Benjamin Eckel
5373f7d88d docs: Add more context to linking example (#707)
Just adding more context in response to an inquiry about how this works.
2024-04-28 15:11:39 -05:00
Roland Fredenhagen
c5a23a31d8 feat: add id() to CurrentPlugin (#705)
fixes  #704
2024-04-15 09:03:48 -07:00
Steve Manuel
7206e2b362 chore: add perl-sdk to README list 2024-04-10 22:43:27 -06:00
Steve Manuel
20f551f019 chore: fix broken links in crate readme (#701) 2024-04-04 10:23:46 -06:00
22 changed files with 645 additions and 175 deletions

View File

@@ -21,6 +21,9 @@ jobs:
target: wasm32-unknown-unknown
- uses: Swatinem/rust-cache@v2
- name: install wasm-tools
uses: bytecodealliance/actions/wasm-tools/setup@v1
- name: Install deps
run: |
sudo apt install wabt --yes

View File

@@ -51,6 +51,7 @@ started:
| Java SDK | <img alt="Java SDK" src="https://extism.org/img/sdk-languages/java-android.svg" width="50px"/> | https://github.com/extism/java-sdk | [Sonatype](https://central.sonatype.com/artifact/org.extism.sdk/extism) |
| .NET SDK | <img alt=".NET SDK" src="https://extism.org/img/sdk-languages/dotnet.svg" width="50px"/> | https://github.com/extism/dotnet-sdk <br/>(supports C# & F#!) | [Nuget](https://www.nuget.org/packages/Extism.Sdk) |
| OCaml SDK | <img alt="OCaml SDK" src="https://extism.org/img/sdk-languages/ocaml.svg" width="50px"/> | https://github.com/extism/ocaml-sdk | [opam](https://opam.ocaml.org/packages/extism/) |
| Perl SDK | <img alt="Perl SDK" src="https://extism.org/img/sdk-languages/perl.svg" width="50px"/> | https://github.com/extism/perl-sdk | N/A |
| PHP SDK | <img alt="PHP SDK" src="https://extism.org/img/sdk-languages/php.svg" width="50px"/> | https://github.com/extism/php-sdk | [Packagist](https://packagist.org/packages/extism/extism) |
| Python SDK | <img alt="Python SDK" src="https://extism.org/img/sdk-languages/python.svg" width="50px"/> | https://github.com/extism/python-sdk | [PyPi](https://pypi.org/project/extism/) |
| Ruby SDK | <img alt="Ruby SDK" src="https://extism.org/img/sdk-languages/ruby.svg" width="50px"/> | https://github.com/extism/ruby-sdk | [RubyGems](https://rubygems.org/gems/extism) |

View File

@@ -59,6 +59,10 @@ pub trait ToBytes<'a> {
/// `to_bytes` converts a value into `Self::Bytes`
fn to_bytes(&self) -> Result<Self::Bytes, Error>;
fn to_vec(&self) -> Result<Vec<u8>, Error> {
self.to_bytes().map(|x| x.as_ref().to_vec())
}
}
impl<'a> ToBytes<'a> for () {
@@ -73,6 +77,10 @@ impl<'a> ToBytes<'a> for Vec<u8> {
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
Ok(self.clone())
}
fn to_vec(&self) -> Result<Vec<u8>, Error> {
Ok(self.clone())
}
}
impl<'a> ToBytes<'a> for String {
@@ -80,6 +88,10 @@ impl<'a> ToBytes<'a> for String {
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
Ok(self.clone())
}
fn to_vec(&self) -> Result<Vec<u8>, Error> {
self.to_bytes().map(|x| x.into_bytes())
}
}
impl<'a> ToBytes<'a> for &'a [u8] {
@@ -150,6 +162,10 @@ impl<'a, T: ToBytes<'a>> ToBytes<'a> for &'a T {
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
<T as ToBytes>::to_bytes(self)
}
fn to_vec(&self) -> Result<Vec<u8>, Error> {
<T as ToBytes>::to_vec(self)
}
}
impl<'a, T: ToBytes<'a>> ToBytes<'a> for Option<T> {
@@ -161,6 +177,13 @@ impl<'a, T: ToBytes<'a>> ToBytes<'a> for Option<T> {
None => Ok(vec![]),
}
}
fn to_vec(&self) -> Result<Vec<u8>, Error> {
match self {
Some(x) => <T as ToBytes>::to_vec(x),
None => Ok(vec![]),
}
}
}
#[test]

View File

@@ -17,6 +17,9 @@ done
cargo build --package extism-runtime-kernel --bin extism-runtime --release --target wasm32-unknown-unknown $CARGO_FLAGS
cp target/wasm32-unknown-unknown/release/extism-runtime.wasm .
wasm-strip extism-runtime.wasm
mv extism-runtime.wasm ../runtime/src/extism-runtime.wasm
wasm-tools parse extism-context.wat -o extism-context.wasm
wasm-merge --enable-reference-types ./extism-runtime.wasm runtime extism-context.wasm context -o ../runtime/src/extism-runtime.wasm
rm extism-context.wasm
rm extism-runtime.wasm
wasm-strip ../runtime/src/extism-runtime.wasm

View File

@@ -0,0 +1,3 @@
(module
(global (export "extism_context") (mut externref) (ref.null extern))
)

View File

@@ -14,7 +14,9 @@ Create a new plugin.
- `functions`: is an array of `ExtismFunction*`
- `n_functions`: is the number of functions
- `with_wasi`: enables/disables WASI
- `errmsg`: error message during plugin creation
- `errmsg`: error message during plugin creation, this should be freed with
`extism_plugin_new_error_free`
```c
ExtismPlugin extism_plugin_new(const uint8_t *wasm,
@@ -24,6 +26,17 @@ ExtismPlugin extism_plugin_new(const uint8_t *wasm,
bool with_wasi,
char **errmsg);
```
---
### `extism_plugin_new_error_free`
Frees the error message returned when creating a plugin
```c
void extism_plugin_new_error_free(char *err);
```
---
### `extism_plugin_free`
@@ -33,6 +46,8 @@ Remove a plugin from the registry and free associated memory.
void extism_plugin_free(ExtismPlugin *plugin);
```
---
### `extism_plugin_config`
Update plugin config values, this will merge with the existing values.
@@ -43,6 +58,8 @@ bool extism_plugin_config(ExtismPlugin *plugin,
ExtismSize json_size);
```
---
### `extism_plugin_function_exists`
Returns true if `func_name` exists.
@@ -52,6 +69,8 @@ bool extism_plugin_function_exists(ExtismPlugin *plugin,
const char *func_name);
```
---
### `extism_plugin_call`
Call a function.
@@ -59,6 +78,8 @@ Call a function.
- `data`: is the input data
- `data_len`: is the length of `data`
Returns `0` when the call is successful.
```c
int32_t extism_plugin_call(ExtismPlugin *plugin,
const char *func_name,
@@ -66,6 +87,28 @@ int32_t extism_plugin_call(ExtismPlugin *plugin,
ExtismSize data_len);
```
---
### `extism_plugin_call_with_host_context`
Call a function with additional host context that can be accessed from inside host functions.
- `func_name`: is the function to call
- `data`: is the input data
- `data_len`: is the length of `data`
- `host_ctx`: an opaque pointer that can be accessed in host functions
Returns `0` when the call is successful.
```c
int32_t extism_plugin_call_with_host_context(ExtismPlugin *plugin,
const char *func_name,
const uint8_t *data,
ExtismSize data_len,
void *host_ctx);
```
---
### `extism_plugin_error`
Get the error associated with a `Plugin`
@@ -74,6 +117,8 @@ Get the error associated with a `Plugin`
const char *extism_plugin_error(ExtismPlugin *plugin);
```
---
### `extism_plugin_output_length`
Get the length of a plugin's output data.
@@ -82,6 +127,8 @@ Get the length of a plugin's output data.
ExtismSize extism_plugin_output_length(ExtismPlugin *plugin);
```
---
### `extism_plugin_output_data`
Get the plugin's output data.
@@ -90,6 +137,8 @@ Get the plugin's output data.
const uint8_t *extism_plugin_output_data(ExtismPlugin *plugin);
```
---
### `extism_plugin_reset`
Reset the Extism runtime, this will invalidate all allocated memory.
@@ -98,6 +147,8 @@ Reset the Extism runtime, this will invalidate all allocated memory.
bool extism_plugin_reset(ExtismPlugin *plugin);
```
---
### `extism_log_file`
Set log file and level.
@@ -106,6 +157,8 @@ Set log file and level.
bool extism_log_file(const char *filename, const char *log_level);
```
---
### `extism_log_custom`
Enable a custom log handler, this will buffer logs until `extism_log_drain`
@@ -115,6 +168,8 @@ is called Log level should be one of: info, error, trace, debug, warn
bool extism_log_custom(const char *log_level);
```
---
### `extism_log_drain`
Calls the provided callback function for each buffered log line.
@@ -124,6 +179,8 @@ This is only needed when `extism_log_custom` is used.
void extism_log_drain(void (*handler)(const char *, uintptr_t));
```
---
### `extism_version`
Get the Extism version string.
@@ -132,6 +189,8 @@ Get the Extism version string.
const char *extism_version(void);
```
---
### `extism_current_plugin_memory`
Returns a pointer to the memory of the currently running plugin
@@ -140,6 +199,19 @@ Returns a pointer to the memory of the currently running plugin
uint8_t *extism_current_plugin_memory(ExtismCurrentPlugin *plugin);
```
---
### `extism_current_plugin_host_context`
Get access to the host context, passed in using `extism_plugin_call_with_host_context`
```c
void *extism_current_plugin_host_context(ExtismCurrentPlugin *plugin);
```
---
### `extism_current_plugin_memory_alloc`
Allocate a memory block in the currently running plugin
@@ -148,6 +220,8 @@ Allocate a memory block in the currently running plugin
uint64_t extism_current_plugin_memory_alloc(ExtismCurrentPlugin *plugin, ExtismSize n);
```
---
### `extism_current_plugin_memory_length`
Get the length of an allocated block
@@ -156,6 +230,8 @@ Get the length of an allocated block
ExtismSize extism_current_plugin_memory_length(ExtismCurrentPlugin *plugin, ExtismSize n);
```
---
### `extism_current_plugin_memory_free`
Free an allocated memory block
@@ -164,6 +240,8 @@ Free an allocated memory block
void extism_current_plugin_memory_free(ExtismCurrentPlugin *plugin, uint64_t ptr);
```
---
### `extism_function_new`
Create a new host function
- `name`: function name, this should be valid UTF-8
@@ -190,6 +268,8 @@ ExtismFunction *extism_function_new(const char *name,
void (*free_user_data)(void *_));
```
---
### `extism_function_set_namespace`
Set the namespace of an `ExtismFunction`
@@ -198,6 +278,8 @@ Set the namespace of an `ExtismFunction`
void extism_function_set_namespace(ExtismFunction *ptr, const char *namespace_);
```
---
### `extism_function_free`
Free an `ExtismFunction`
@@ -206,6 +288,8 @@ Free an `ExtismFunction`
void extism_function_free(ExtismFunction *ptr);
```
---
### `extism_plugin_cancel_handle`
Get handle for plugin cancellation
@@ -214,6 +298,8 @@ Get handle for plugin cancellation
const ExtismCancelHandle *extism_plugin_cancel_handle(const ExtismPlugin *plugin);
```
---
### `extism_plugin_cancel`
Cancel a running plugin from another thread
@@ -222,12 +308,14 @@ Cancel a running plugin from another thread
bool extism_plugin_cancel(const ExtismCancelHandle *handle);
```
---
## Type definitions:
### `ExtismPlugin`
```c
typedef int32_t ExtismPlugin;
typedef struct ExtismPlugin ExtismPlugin;
```
### `ExtismSize`
@@ -259,3 +347,5 @@ typedef struct ExtismCurrentPlugin ExtismCurrentPlugin;
```c
typedef struct ExtismCancelHandle ExtismCancelHandle;
```

View File

@@ -10,7 +10,6 @@ pub type ManifestMemory = MemoryOptions;
#[serde(deny_unknown_fields)]
pub struct MemoryOptions {
/// The max number of WebAssembly pages that should be allocated
#[serde(alias = "max")]
pub max_pages: Option<u32>,
/// The maximum number of bytes allowed in an HTTP response
@@ -62,7 +61,6 @@ pub struct HttpRequest {
/// Request headers
#[serde(default)]
#[serde(alias = "header")]
pub headers: std::collections::BTreeMap<String, String>,
/// Request method

View File

@@ -9,8 +9,8 @@ repository.workspace = true
version.workspace = true
[dependencies]
wasmtime = ">= 14.0.0, < 18.0.0"
wasmtime-wasi = ">= 14.0.0, < 18.0.0"
wasmtime = ">= 20.0.0, < 22.0.0"
wasmtime-wasi = ">= 20.0.0, < 22.0.0"
anyhow = "1"
serde = {version = "1", features = ["derive"]}
serde_json = "1"

View File

@@ -12,7 +12,7 @@ To use the `extism` crate, you can add it to your Cargo file:
```toml
[dependencies]
extism = "1.0.0"
extism = "1.2.0"
```
## Environment variables
@@ -201,7 +201,7 @@ fn main() {
}
```
> *Note*: In order to write host functions you should get familiar with the methods on the [Extism::CurrentPlugin](https://docs.rs/extism/latest/extism/struct.CurrentPlugin.html) and [Extism::CurrentPlugin](https://docs.rs/extism/latest/extism/struct.UserData.html) types.
> *Note*: In order to write host functions you should get familiar with the methods on the [CurrentPlugin](https://docs.rs/extism/latest/extism/struct.CurrentPlugin.html) and [UserData](https://docs.rs/extism/latest/extism/enum.UserData.html) types.
Now we can invoke the event:

View File

@@ -7,7 +7,7 @@ fn main() {
#define EXTISM_SUCCESS 0
/** An alias for I64 to signify an Extism pointer */
#define PTR I64
#define EXTISM_PTR ExtismValType_I64
";
if let Ok(x) = cbindgen::Builder::new()
.with_crate(".")

View File

@@ -2,6 +2,8 @@ use extism::*;
fn main() {
let manifest = Manifest::new([
// upper.wat provides an export called `host_reflect` that takes a string
// and returns the same string uppercased
Wasm::File {
// See https://github.com/extism/plugins/blob/main/upper.wat
path: "../wasm/upper.wasm".into(),
@@ -10,6 +12,9 @@ fn main() {
hash: None,
},
},
// reflect expects host_reflect to be imported: https://github.com/extism/plugins/blob/e5578bbbdd87f9936a0a8d36df629768b2eff6bb/reflect/src/lib.rs#L5
// Extism will link the export from upper.wat to the import of reflect.rs at runtime so it
// can call it
Wasm::File {
// See https://github.com/extism/plugins/tree/main/reflect
path: "../wasm/reflect.wasm".into(),

View File

@@ -10,7 +10,7 @@
#define EXTISM_SUCCESS 0
/** An alias for I64 to signify an Extism pointer */
#define PTR I64
#define EXTISM_PTR ExtismValType_I64
/**
@@ -20,31 +20,31 @@ typedef enum {
/**
* Signed 32 bit integer.
*/
I32,
ExtismValType_I32,
/**
* Signed 64 bit integer.
*/
I64,
ExtismValType_I64,
/**
* Floating point 32 bit integer.
*/
F32,
ExtismValType_F32,
/**
* Floating point 64 bit integer.
*/
F64,
ExtismValType_F64,
/**
* A 128 bit number.
*/
V128,
ExtismValType_V128,
/**
* A reference to a Wasm function.
*/
FuncRef,
ExtismValType_FuncRef,
/**
* A reference to opaque data in the Wasm instance.
*/
ExternRef,
ExtismValType_ExternRef,
} ExtismValType;
/**
@@ -113,6 +113,12 @@ extern "C" {
*/
const uint8_t *extism_plugin_id(ExtismPlugin *plugin);
/**
* Get the current plugin's associated host context data. Returns null if call was made without
* host context.
*/
void *extism_current_plugin_host_context(ExtismCurrentPlugin *plugin);
/**
* Returns a pointer to the memory of the currently running plugin
* NOTE: this should only be called from host functions.
@@ -231,6 +237,20 @@ int32_t extism_plugin_call(ExtismPlugin *plugin,
const uint8_t *data,
ExtismSize data_len);
/**
* Call a function with host context.
*
* `func_name`: is the function to call
* `data`: is the input data
* `data_len`: is the length of `data`
* `host_context`: a pointer to context data that will be available in host functions
*/
int32_t extism_plugin_call_with_host_context(ExtismPlugin *plugin,
const char *func_name,
const uint8_t *data,
ExtismSize data_len,
void *host_context);
/**
* Get the error associated with a `Plugin`
*/

View File

@@ -1,5 +1,88 @@
use wasmtime_wasi::pipe::{MemoryInputPipe, MemoryOutputPipe};
use crate::*;
#[derive(Default, Clone)]
pub struct WasiConfig {
pub args: Vec<String>,
pub stdin: Option<wasmtime_wasi::pipe::MemoryInputPipe>,
pub stdout: Option<wasmtime_wasi::pipe::MemoryOutputPipe>,
pub stderr: Option<wasmtime_wasi::pipe::MemoryOutputPipe>,
}
impl WasiConfig {
pub fn new() -> WasiConfig {
WasiConfig::default()
}
pub fn with_arg(mut self, x: impl Into<String>) -> Self {
self.args.push(x.into());
self
}
pub fn with_args(mut self, x: impl IntoIterator<Item = impl Into<String>>) -> Self {
let x: Vec<String> = x.into_iter().map(|x| x.into()).collect();
self.args.extend(x);
self
}
pub fn with_stdin<'a, T: ToBytes<'a>>(mut self, x: T) -> Result<Self, Error> {
self.stdin = Some(MemoryInputPipe::new(x.to_vec()?));
Ok(self)
}
pub fn with_stdout(mut self, size: usize) -> Self {
self.stdout = Some(MemoryOutputPipe::new(size));
self
}
pub fn with_stderr(mut self, size: usize) -> Self {
self.stderr = Some(MemoryOutputPipe::new(size));
self
}
}
#[derive(Clone)]
pub struct WasiOutput {
pub(crate) return_code: i32,
pub(crate) stdout: Option<wasmtime_wasi::pipe::MemoryOutputPipe>,
pub(crate) stderr: Option<wasmtime_wasi::pipe::MemoryOutputPipe>,
}
impl WasiOutput {
pub fn return_code(&self) -> i32 {
self.return_code
}
pub fn stdout(&self) -> Vec<u8> {
match &self.stdout {
Some(x) => x.contents().to_vec(),
None => vec![],
}
}
pub fn stderr(&self) -> Vec<u8> {
match &self.stderr {
Some(x) => x.contents().to_vec(),
None => vec![],
}
}
pub fn convert_stdout<T: FromBytesOwned>(&self) -> Result<T, Error> {
match &self.stdout {
Some(x) => T::from_bytes(x.contents().as_ref()),
None => anyhow::bail!("stdout not enabled"),
}
}
pub fn convert_stderr<T: FromBytesOwned>(&self) -> Result<T, Error> {
match &self.stderr {
Some(x) => T::from_bytes(x.contents().as_ref()),
None => anyhow::bail!("stdout not enabled"),
}
}
}
/// CurrentPlugin stores data that is available to the caller in PDK functions, this should
/// only be accessed from inside a host function
pub struct CurrentPlugin {
@@ -15,6 +98,7 @@ pub struct CurrentPlugin {
pub(crate) available_pages: Option<u32>,
pub(crate) memory_limiter: Option<MemoryLimiter>,
pub(crate) id: uuid::Uuid,
pub(crate) start_time: std::time::Instant,
}
unsafe impl Send for CurrentPlugin {}
@@ -62,6 +146,11 @@ impl wasmtime::ResourceLimiter for MemoryLimiter {
}
impl CurrentPlugin {
/// Gets `Plugin`'s ID
pub fn id(&self) -> uuid::Uuid {
self.id
}
/// Get a `MemoryHandle` from a memory offset
pub fn memory_handle(&mut self, offs: u64) -> Option<MemoryHandle> {
if offs == 0 {
@@ -181,6 +270,23 @@ impl CurrentPlugin {
anyhow::bail!("{} unable to locate extism memory", self.id)
}
pub fn host_context<T: Clone + 'static>(&mut self) -> Result<T, Error> {
let (linker, mut store) = self.linker_and_store();
let Some(Extern::Global(xs)) = linker.get(&mut store, EXTISM_ENV_MODULE, "extism_context")
else {
anyhow::bail!("unable to locate an extism kernel global: extism_context",)
};
let Val::ExternRef(Some(xs)) = xs.get(&mut store) else {
anyhow::bail!("expected extism_context to be an externref value",)
};
match xs.data(&mut store)?.downcast_ref::<T>().cloned() {
Some(xs) => Ok(xs.clone()),
None => anyhow::bail!("could not downcast extism_context",),
}
}
pub fn memory_alloc(&mut self, n: u64) -> Result<MemoryHandle, Error> {
if n == 0 {
return Ok(MemoryHandle {
@@ -284,39 +390,73 @@ impl CurrentPlugin {
pub(crate) fn new(
manifest: extism_manifest::Manifest,
wasi: bool,
wasi_args: Option<WasiConfig>,
available_pages: Option<u32>,
id: uuid::Uuid,
) -> Result<Self, Error> {
let wasi = if wasi {
let auth = wasmtime_wasi::ambient_authority();
let mut ctx = wasmtime_wasi::WasiCtxBuilder::new();
for (k, v) in manifest.config.iter() {
ctx.env(k, v)?;
ctx.allow_ip_name_lookup(true)
.allow_tcp(true)
.allow_udp(true)
.allow_blocking_current_thread(true);
if let Some(wasi_args) = wasi_args {
ctx.args(&wasi_args.args);
if let Some(stdin) = wasi_args.stdin {
ctx.stdin(stdin);
}
if let Some(stdout) = wasi_args.stdout {
ctx.stdout(stdout);
}
if let Some(stderr) = wasi_args.stderr {
ctx.stderr(stderr);
}
}
if let Some(a) = &manifest.allowed_paths {
for (k, v) in a.iter() {
let d = wasmtime_wasi::Dir::open_ambient_dir(k, auth).map_err(|err| {
Error::msg(format!(
"Unable to preopen directory \"{}\": {}",
k.display(),
err.kind()
))
})?;
ctx.preopened_dir(d, v)?;
ctx.preopened_dir(
k,
v.to_string_lossy(),
wasmtime_wasi::DirPerms::READ | wasmtime_wasi::DirPerms::MUTATE,
wasmtime_wasi::FilePerms::READ | wasmtime_wasi::FilePerms::WRITE,
)?;
}
}
if let Some(h) = &manifest.allowed_hosts {
let h = h.clone();
ctx.socket_addr_check(move |addr, _kind| {
for host in h.iter() {
let addrs = std::net::ToSocketAddrs::to_socket_addrs(&host);
if let Ok(addrs) = addrs {
for a in addrs.into_iter() {
if addr == &a {
return true;
}
}
}
}
false
});
}
// Enable WASI output, typically used for debugging purposes
if std::env::var("EXTISM_ENABLE_WASI_OUTPUT").is_ok() {
ctx.inherit_stdout().inherit_stderr();
}
Some(Wasi { ctx: ctx.build() })
Some(Wasi {
ctx: ctx.build_p1(),
})
} else {
None
};
let memory_limiter = if let Some(pgs) = available_pages {
let n = pgs as usize * 65536;
Some(crate::current_plugin::MemoryLimiter {
@@ -337,6 +477,7 @@ impl CurrentPlugin {
available_pages,
memory_limiter,
id,
start_time: std::time::Instant::now(),
})
}
@@ -447,6 +588,18 @@ impl CurrentPlugin {
let length = self.memory_length(offs).unwrap_or_default();
(offs, length)
}
/// Returns the remaining time before a plugin will timeout, or
/// `None` if no timeout is configured in the manifest
pub fn time_remaining(&self) -> Option<std::time::Duration> {
if let Some(x) = &self.manifest.timeout_ms {
let elapsed = &self.start_time.elapsed().as_millis();
let ms_left = x.saturating_sub(*elapsed as u64);
return Some(std::time::Duration::from_millis(ms_left));
}
None
}
}
impl Internal for CurrentPlugin {
@@ -458,14 +611,6 @@ impl Internal for CurrentPlugin {
unsafe { &mut *self.store }
}
fn linker(&self) -> &Linker<CurrentPlugin> {
unsafe { &*self.linker }
}
fn linker_mut(&mut self) -> &mut Linker<CurrentPlugin> {
unsafe { &mut *self.linker }
}
fn linker_and_store(&mut self) -> (&mut Linker<CurrentPlugin>, &mut Store<CurrentPlugin>) {
unsafe { (&mut *self.linker, &mut *self.store) }
}

Binary file not shown.

View File

@@ -4,6 +4,7 @@ use wasmtime::Caller;
use crate::{error, trace, CurrentPlugin, Error};
/// An enumeration of all possible value types in WebAssembly.
/// cbindgen:prefix-with-name
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
#[repr(C)]
pub enum ValType {
@@ -37,8 +38,13 @@ impl From<wasmtime::ValType> for ValType {
F32 => ValType::F32,
F64 => ValType::F64,
V128 => ValType::V128,
FuncRef => ValType::FuncRef,
ExternRef => ValType::ExternRef,
Ref(t) => {
if t.heap_type().is_func() {
ValType::FuncRef
} else {
ValType::ExternRef
}
}
}
}
}
@@ -52,8 +58,8 @@ impl From<ValType> for wasmtime::ValType {
F32 => wasmtime::ValType::F32,
F64 => wasmtime::ValType::F64,
V128 => wasmtime::ValType::V128,
FuncRef => wasmtime::ValType::FuncRef,
ExternRef => wasmtime::ValType::ExternRef,
FuncRef => wasmtime::ValType::FUNCREF,
ExternRef => wasmtime::ValType::EXTERNREF,
}
}
}
@@ -170,8 +176,8 @@ pub struct Function {
/// Module name
pub(crate) namespace: Option<String>,
/// Function type
pub(crate) ty: wasmtime::FuncType,
pub(crate) params: Vec<wasmtime::ValType>,
pub(crate) results: Vec<wasmtime::ValType>,
/// Function handle
pub(crate) f: Arc<FunctionInner>,
@@ -184,8 +190,8 @@ impl Function {
/// Create a new host function
pub fn new<T: 'static, F>(
name: impl Into<String>,
args: impl IntoIterator<Item = ValType>,
returns: impl IntoIterator<Item = ValType>,
params: impl IntoIterator<Item = ValType>,
results: impl IntoIterator<Item = ValType>,
user_data: UserData<T>,
f: F,
) -> Function
@@ -197,13 +203,13 @@ impl Function {
{
let data = user_data.clone();
let name = name.into();
let args = args.into_iter().map(wasmtime::ValType::from);
let returns = returns.into_iter().map(wasmtime::ValType::from);
let ty = wasmtime::FuncType::new(args, returns);
trace!("Creating function {name}: type={ty:?}");
let params = params.into_iter().map(wasmtime::ValType::from).collect();
let results = results.into_iter().map(wasmtime::ValType::from).collect();
trace!("Creating function {name}: params={params:?}, results={results:?}");
Function {
name,
ty,
params,
results,
f: Arc::new(
move |mut caller: Caller<_>, inp: &[Val], outp: &mut [Val]| {
let x = data.clone();
@@ -218,6 +224,10 @@ impl Function {
}
}
pub(crate) fn ty(&self, engine: &wasmtime::Engine) -> wasmtime::FuncType {
wasmtime::FuncType::new(engine, self.params.clone(), self.results.clone())
}
/// Host function name
pub fn name(&self) -> &str {
&self.name
@@ -240,11 +250,6 @@ impl Function {
self.set_namespace(namespace);
self
}
/// Get function type
pub fn ty(&self) -> &wasmtime::FuncType {
&self.ty
}
}
/// The `host_fn` macro is used to define typed host functions

View File

@@ -3,7 +3,7 @@ use crate::*;
/// WASI context
pub struct Wasi {
/// wasi
pub ctx: wasmtime_wasi::WasiCtx,
pub ctx: wasmtime_wasi::preview1::WasiP1Ctx,
}
/// InternalExt provides a unified way of acessing `memory`, `store` and `internal` values
@@ -12,10 +12,6 @@ pub(crate) trait Internal {
fn store_mut(&mut self) -> &mut Store<CurrentPlugin>;
fn linker(&self) -> &Linker<CurrentPlugin>;
fn linker_mut(&mut self) -> &mut Linker<CurrentPlugin>;
fn linker_and_store(&mut self) -> (&mut Linker<CurrentPlugin>, &mut Store<CurrentPlugin>);
fn current_plugin(&self) -> &CurrentPlugin {

View File

@@ -23,7 +23,7 @@ mod timer;
/// Extism C API
pub mod sdk;
pub use current_plugin::CurrentPlugin;
pub use current_plugin::{CurrentPlugin, WasiConfig, WasiOutput};
pub use extism_convert::{FromBytes, FromBytesOwned, ToBytes};
pub use extism_manifest::{Manifest, Wasm, WasmMetadata};
pub use function::{Function, UserData, Val, ValType, PTR};

View File

@@ -217,6 +217,11 @@ pub(crate) fn http_request(
r = r.set(k, v);
}
// Set HTTP timeout to respect the manifest timeout
if let Some(remaining) = data.time_remaining() {
r = r.timeout(remaining);
}
let res = if body_offset > 0 {
let handle = match data.memory_handle(body_offset) {
Some(h) => h,
@@ -236,6 +241,12 @@ pub(crate) fn http_request(
Some(res.into_reader())
}
Err(e) => {
// Catch timeout and return
if let Some(d) = data.time_remaining() {
if e.kind() == ureq::ErrorKind::Io && d.as_nanos() == 0 {
anyhow::bail!("timeout");
}
}
let msg = e.to_string();
if let Some(res) = e.into_response() {
data.http_status = res.status();

View File

@@ -1,4 +1,5 @@
use std::{
any::Any,
collections::{BTreeMap, BTreeSet},
path::PathBuf,
};
@@ -100,14 +101,6 @@ impl Internal for Plugin {
&mut self.store
}
fn linker(&self) -> &Linker<CurrentPlugin> {
&self.linker
}
fn linker_mut(&mut self) -> &mut Linker<CurrentPlugin> {
&mut self.linker
}
fn linker_and_store(&mut self) -> (&mut Linker<CurrentPlugin>, &mut Store<CurrentPlugin>) {
(&mut self.linker, &mut self.store)
}
@@ -193,6 +186,12 @@ fn add_module<T: 'static>(
}
for import in module.imports() {
let module = import.module();
if module == EXTISM_ENV_MODULE && !matches!(import.ty(), ExternType::Func(_)) {
anyhow::bail!("linked modules cannot access non-function exports of extism kernel");
}
if !linked.contains(import.module()) {
if let Some(m) = modules.get(import.module()) {
add_module(
@@ -213,6 +212,73 @@ fn add_module<T: 'static>(
Ok(())
}
fn relink(
engine: &Engine,
mut store: &mut Store<CurrentPlugin>,
imports: &[Function],
modules: &BTreeMap<String, Module>,
with_wasi: bool,
) -> Result<(InstancePre<CurrentPlugin>, Linker<CurrentPlugin>), Error> {
let mut linker = Linker::new(engine);
// Define PDK functions
macro_rules! add_funcs {
($($name:ident($($args:expr),*) $(-> $($r:expr),*)?);* $(;)?) => {
$(
let t = FuncType::new(&engine, [$($args),*], [$($($r),*)?]);
linker.func_new(EXTISM_ENV_MODULE, stringify!($name), t, pdk::$name)?;
)*
};
}
// Add builtins
use wasmtime::ValType::*;
add_funcs!(
config_get(I64) -> I64;
var_get(I64) -> I64;
var_set(I64, I64);
http_request(I64, I64) -> I64;
http_status_code() -> I32;
log_warn(I64);
log_info(I64);
log_debug(I64);
log_error(I64);
);
let mut linked = BTreeSet::new();
linker.module(&mut store, EXTISM_ENV_MODULE, &modules[EXTISM_ENV_MODULE])?;
linked.insert(EXTISM_ENV_MODULE.to_string());
// If wasi is enabled then add it to the linker
if with_wasi {
wasmtime_wasi::preview1::add_to_linker_sync(&mut linker, |x: &mut CurrentPlugin| {
&mut x.wasi.as_mut().unwrap().ctx
})?;
}
for f in imports.iter() {
let name = f.name();
let ns = f.namespace().unwrap_or(EXTISM_USER_MODULE);
unsafe {
linker.func_new(ns, name, f.ty(engine).clone(), &*(f.f.as_ref() as *const _))?;
}
}
for (name, module) in modules.iter() {
add_module(
store,
&mut linker,
&mut linked,
modules,
name.clone(),
module,
)?;
}
let main = &modules[MAIN_KEY];
let instance_pre = linker.instantiate_pre(main)?;
Ok((instance_pre, linker))
}
impl Plugin {
/// Create a new plugin from a Manifest or WebAssembly module, and host functions. The `with_wasi`
/// parameter determines whether or not the module should be executed with WASI enabled.
@@ -239,7 +305,8 @@ impl Plugin {
.coredump_on_trap(debug_options.coredump.is_some())
.profiler(debug_options.profiling_strategy)
.wasm_tail_call(true)
.wasm_function_references(true);
.wasm_function_references(true)
.wasm_gc(true);
match cache_dir {
Some(None) => (),
@@ -271,68 +338,12 @@ impl Plugin {
let id = uuid::Uuid::new_v4();
let mut store = Store::new(
&engine,
CurrentPlugin::new(manifest, with_wasi, available_pages, id)?,
CurrentPlugin::new(manifest, with_wasi, None, available_pages, id)?,
);
store.set_epoch_deadline(1);
let mut linker = Linker::new(&engine);
let mut imports: Vec<_> = imports.into_iter().collect();
// Define PDK functions
macro_rules! add_funcs {
($($name:ident($($args:expr),*) $(-> $($r:expr),*)?);* $(;)?) => {
$(
let t = FuncType::new([$($args),*], [$($($r),*)?]);
linker.func_new(EXTISM_ENV_MODULE, stringify!($name), t, pdk::$name)?;
)*
};
}
// Add builtins
use wasmtime::ValType::*;
add_funcs!(
config_get(I64) -> I64;
var_get(I64) -> I64;
var_set(I64, I64);
http_request(I64, I64) -> I64;
http_status_code() -> I32;
log_warn(I64);
log_info(I64);
log_debug(I64);
log_error(I64);
);
let mut linked = BTreeSet::new();
linker.module(&mut store, EXTISM_ENV_MODULE, &modules[EXTISM_ENV_MODULE])?;
linked.insert(EXTISM_ENV_MODULE.to_string());
// If wasi is enabled then add it to the linker
if with_wasi {
wasmtime_wasi::add_to_linker(&mut linker, |x: &mut CurrentPlugin| {
&mut x.wasi.as_mut().unwrap().ctx
})?;
}
for f in &mut imports {
let name = f.name();
let ns = f.namespace().unwrap_or(EXTISM_USER_MODULE);
unsafe {
linker.func_new(ns, name, f.ty().clone(), &*(f.f.as_ref() as *const _))?;
}
}
for (name, module) in modules.iter() {
add_module(
&mut store,
&mut linker,
&mut linked,
&modules,
name.clone(),
module,
)?;
}
let main = &modules[MAIN_KEY];
let instance_pre = linker.instantiate_pre(main)?;
let imports: Vec<Function> = imports.into_iter().collect();
let (instance_pre, linker) = relink(&engine, &mut store, &imports, &modules, with_wasi)?;
let timer_tx = Timer::tx();
let mut plugin = Plugin {
modules,
@@ -366,20 +377,34 @@ impl Plugin {
pub(crate) fn reset_store(
&mut self,
instance_lock: &mut std::sync::MutexGuard<Option<Instance>>,
wasi_args: Option<WasiConfig>,
) -> Result<(), Error> {
if self.store_needs_reset {
if self.store_needs_reset || wasi_args.is_some() {
let engine = self.store.engine().clone();
let internal = self.current_plugin_mut();
let with_wasi = internal.wasi.is_some() || wasi_args.is_some();
self.store = Store::new(
&engine,
CurrentPlugin::new(
internal.manifest.clone(),
internal.wasi.is_some(),
wasi_args,
internal.available_pages,
self.id,
)?,
);
self.linker = Linker::new(&engine);
self.store.set_epoch_deadline(1);
let (instance_pre, linker) = relink(
&engine,
&mut self.store,
&self._functions,
&self.modules,
with_wasi,
)?;
self.linker = linker;
self.instance_pre = instance_pre;
let store = &mut self.store as *mut _;
let linker = &mut self.linker as *mut _;
let current_plugin = self.current_plugin_mut();
@@ -390,14 +415,7 @@ impl Plugin {
.limiter(|internal| internal.memory_limiter.as_mut().unwrap());
}
let main = &self.modules[MAIN_KEY];
for (name, module) in self.modules.iter() {
if name != MAIN_KEY {
self.linker.module(&mut self.store, name, module)?;
}
}
self.instantiations = 0;
self.instance_pre = self.linker.instantiate_pre(main)?;
**instance_lock = None;
self.store_needs_reset = false;
}
@@ -450,7 +468,7 @@ impl Plugin {
if let Some(f) = x.func() {
let (params, mut results) = (f.params(), f.results());
match (params.len(), results.len()) {
(0, 1) => results.next() == Some(wasmtime::ValType::I32),
(0, 1) => matches!(results.next(), Some(wasmtime::ValType::I32)),
(0, 0) => true,
_ => false,
}
@@ -462,7 +480,12 @@ impl Plugin {
}
// Store input in memory and re-initialize `Internal` pointer
pub(crate) fn set_input(&mut self, input: *const u8, mut len: usize) -> Result<(), Error> {
pub(crate) fn set_input(
&mut self,
input: *const u8,
mut len: usize,
host_context: Option<Rooted<ExternRef>>,
) -> Result<(), Error> {
self.output = Output::default();
self.clear_error()?;
let id = self.id.to_string();
@@ -496,6 +519,13 @@ impl Plugin {
)?;
}
if let Some(Extern::Global(ctxt)) =
self.linker
.get(&mut self.store, EXTISM_ENV_MODULE, "extism_context")
{
ctxt.set(&mut self.store, Val::ExternRef(host_context))?;
}
Ok(())
}
@@ -588,7 +618,7 @@ impl Plugin {
if let Some(reactor_init) = reactor_init {
reactor_init.call(&mut store, &[], &mut [])?;
}
let mut results = vec![Val::null(); init.ty(&store).results().len()];
let mut results = vec![Val::I32(0); init.ty(&store).results().len()];
init.call(
&mut store,
&[Val::I32(0), Val::I32(0)],
@@ -673,20 +703,23 @@ impl Plugin {
lock: &mut std::sync::MutexGuard<Option<Instance>>,
name: impl AsRef<str>,
input: impl AsRef<[u8]>,
host_context: Option<Rooted<ExternRef>>,
wasi_args: Option<WasiConfig>,
) -> Result<i32, (Error, i32)> {
let name = name.as_ref();
let input = input.as_ref();
if let Err(e) = self.reset_store(lock) {
if let Err(e) = self.reset_store(lock, wasi_args) {
error!(
plugin = self.id.to_string(),
"call to Plugin::reset_store failed: {e:?}"
);
}
self.store_needs_reset = name == "_start";
self.instantiate(lock).map_err(|e| (e, -1))?;
self.set_input(input.as_ptr(), input.len())
self.set_input(input.as_ptr(), input.len(), host_context)
.map_err(|x| (x, -1))?;
let func = match self.get_func(lock, name) {
@@ -717,16 +750,16 @@ impl Plugin {
.expect("Timer should start");
self.store.epoch_deadline_trap();
self.store.set_epoch_deadline(1);
self.current_plugin_mut().start_time = std::time::Instant::now();
// Call the function
let mut results = vec![wasmtime::Val::null(); n_results];
let mut results = vec![wasmtime::Val::I32(0); n_results];
let mut res = func.call(self.store_mut(), &[], results.as_mut_slice());
// Stop timer
self.store
.epoch_deadline_callback(|_| Ok(UpdateDeadline::Continue(1)));
let _ = self.timer_tx.send(TimerAction::Stop { id: self.id });
self.store_needs_reset = name == "_start";
// Get extism error
self.get_output_after_call().map_err(|x| (x, -1))?;
@@ -804,13 +837,7 @@ impl Plugin {
}
}
let wasi_exit_code = e
.downcast_ref::<wasmtime_wasi::I32Exit>()
.map(|e| e.0)
.or_else(|| {
e.downcast_ref::<wasmtime_wasi::preview2::I32Exit>()
.map(|e| e.0)
});
let wasi_exit_code = e.downcast_ref::<wasmtime_wasi::I32Exit>().map(|e| e.0);
if let Some(exit_code) = wasi_exit_code {
debug!(
plugin = self.id.to_string(),
@@ -873,11 +900,51 @@ impl Plugin {
let lock = self.instance.clone();
let mut lock = lock.lock().unwrap();
let data = input.to_bytes()?;
self.raw_call(&mut lock, name, data)
self.raw_call(&mut lock, name, data, None, None)
.map_err(|e| e.0)
.and_then(move |_| self.output())
}
pub fn call_with_host_context<'a, 'b, T, U, C>(
&'b mut self,
name: impl AsRef<str>,
input: T,
host_context: C,
) -> Result<U, Error>
where
T: ToBytes<'a>,
U: FromBytes<'b>,
C: Any + Send + Sync + 'static,
{
let lock = self.instance.clone();
let mut lock = lock.lock().unwrap();
let data = input.to_bytes()?;
let r = ExternRef::new(&mut self.store, host_context)?;
self.raw_call(&mut lock, name, data, Some(r), None)
.map_err(|e| e.0)
.and_then(move |_| self.output())
}
/// Execute the `_start` function of a WASI command module, providing input/output and command-line arguments
/// via `WasiConfig`
pub fn run_wasi_command(&mut self, wasi_args: WasiConfig) -> Result<WasiOutput, Error> {
let mut output = WasiOutput {
return_code: 0,
stdout: wasi_args.stdout.clone(),
stderr: wasi_args.stderr.clone(),
};
let lock = self.instance.clone();
let mut lock = lock.lock().unwrap();
let res = self.raw_call(&mut lock, "_start", b"", None, Some(wasi_args));
output.return_code = match res {
Ok(rc) => rc,
Err((_, rc)) => rc,
};
Ok(output)
}
/// Similar to `Plugin::call`, but returns the Extism error code along with the
/// `Error`. It is assumed if `Ok(_)` is returned that the error code was `0`.
///
@@ -892,7 +959,7 @@ impl Plugin {
let lock = self.instance.clone();
let mut lock = lock.lock().unwrap();
let data = input.to_bytes().map_err(|e| (e, -1))?;
self.raw_call(&mut lock, name, data)
self.raw_call(&mut lock, name, data, None, None)
.and_then(move |_| self.output().map_err(|e| (e, -1)))
}

View File

@@ -41,9 +41,9 @@ pub type ExtismFunctionType = extern "C" fn(
/// Log drain callback
pub type ExtismLogDrainFunctionType = extern "C" fn(data: *const std::ffi::c_char, size: Size);
impl From<&wasmtime::Val> for ExtismVal {
fn from(value: &wasmtime::Val) -> Self {
match value.ty() {
impl ExtismVal {
fn from_val(value: &wasmtime::Val, ctx: impl AsContext) -> Self {
match value.ty(ctx) {
wasmtime::ValType::I32 => ExtismVal {
t: ValType::I32,
v: ValUnion {
@@ -84,6 +84,24 @@ pub unsafe extern "C" fn extism_plugin_id(plugin: *mut Plugin) -> *const u8 {
plugin.id.as_bytes().as_ptr()
}
/// Get the current plugin's associated host context data. Returns null if call was made without
/// host context.
#[no_mangle]
pub unsafe extern "C" fn extism_current_plugin_host_context(
plugin: *mut CurrentPlugin,
) -> *mut std::ffi::c_void {
if plugin.is_null() {
return std::ptr::null_mut();
}
let plugin = &mut *plugin;
if let Ok(CVoidContainer(ptr)) = plugin.host_context::<CVoidContainer>() {
ptr
} else {
std::ptr::null_mut()
}
}
/// Returns a pointer to the memory of the currently running plugin
/// NOTE: this should only be called from host functions.
#[no_mangle]
@@ -200,7 +218,10 @@ pub unsafe extern "C" fn extism_function_new(
output_types.clone(),
user_data,
move |plugin, inputs, outputs, user_data| {
let inputs: Vec<_> = inputs.iter().map(ExtismVal::from).collect();
let inputs: Vec<_> = inputs
.iter()
.map(|x| ExtismVal::from_val(x, unsafe { &*plugin.store }))
.collect();
let mut output_tmp: Vec<_> = output_types
.iter()
.map(|t| ExtismVal {
@@ -389,20 +410,6 @@ pub unsafe extern "C" fn extism_plugin_config(
}
};
let wasi = &mut plugin.current_plugin_mut().wasi;
if let Some(Wasi { ctx, .. }) = wasi {
for (k, v) in json.iter() {
match v {
Some(v) => {
let _ = ctx.push_env(k, v);
}
None => {
let _ = ctx.push_env(k, "");
}
}
}
}
let id = plugin.id;
let config = &mut plugin.current_plugin_mut().manifest.config;
for (k, v) in json.into_iter() {
@@ -464,6 +471,31 @@ pub unsafe extern "C" fn extism_plugin_call(
func_name: *const c_char,
data: *const u8,
data_len: Size,
) -> i32 {
extism_plugin_call_with_host_context(plugin, func_name, data, data_len, std::ptr::null_mut())
}
#[derive(Clone)]
#[repr(transparent)]
struct CVoidContainer(*mut std::ffi::c_void);
// "You break it, you buy it."
unsafe impl Send for CVoidContainer {}
unsafe impl Sync for CVoidContainer {}
/// Call a function with host context.
///
/// `func_name`: is the function to call
/// `data`: is the input data
/// `data_len`: is the length of `data`
/// `host_context`: a pointer to context data that will be available in host functions
#[no_mangle]
pub unsafe extern "C" fn extism_plugin_call_with_host_context(
plugin: *mut Plugin,
func_name: *const c_char,
data: *const u8,
data_len: Size,
host_context: *mut std::ffi::c_void,
) -> i32 {
if plugin.is_null() {
return -1;
@@ -485,7 +517,11 @@ pub unsafe extern "C" fn extism_plugin_call(
name
);
let input = std::slice::from_raw_parts(data, data_len as usize);
let res = plugin.raw_call(&mut lock, name, input);
let r = match ExternRef::new(&mut plugin.store, CVoidContainer(host_context)) {
Err(e) => return plugin.return_error(&mut lock, e, -1),
Ok(x) => x,
};
let res = plugin.raw_call(&mut lock, name, input, Some(r), None);
match res {
Err((e, rc)) => plugin.return_error(&mut lock, e, rc),

View File

@@ -240,6 +240,34 @@ fn test_timeout() {
assert!(err == "timeout");
}
#[test]
fn test_http_timeout() {
let f = Function::new(
"hello_world",
[PTR],
[PTR],
UserData::default(),
hello_world,
);
let manifest = Manifest::new([extism_manifest::Wasm::data(WASM_HTTP)])
.with_timeout(std::time::Duration::from_millis(1))
.with_allowed_host("www.extism.org");
let mut plugin = Plugin::new(manifest, [f], true).unwrap();
let start = std::time::Instant::now();
let output: Result<&[u8], Error> =
plugin.call("http_request", r#"{"url": "https://www.extism.org"}"#);
let end = std::time::Instant::now();
let time = end - start;
let err = output.unwrap_err().root_cause().to_string();
println!(
"Timed out plugin ran for {:?}, with error: {:?}",
time, &err
);
assert!(err == "timeout");
}
typed_plugin!(pub TestTypedPluginGenerics {
count_vowels<T: FromBytes<'a>>(&str) -> T
});
@@ -307,6 +335,42 @@ fn test_toml_manifest() {
assert_eq!(count.get("count").unwrap().as_i64().unwrap(), 1);
}
#[test]
fn test_call_with_host_context() {
#[derive(Clone)]
struct Foo {
message: String,
}
let f = Function::new(
"host_reflect",
[PTR],
[PTR],
UserData::default(),
|current_plugin, _val, ret, _user_data: UserData<()>| {
let foo = current_plugin.host_context::<Foo>()?;
let hnd = current_plugin.memory_new(foo.message)?;
ret[0] = current_plugin.memory_to_val(hnd);
Ok(())
},
);
let mut plugin = Plugin::new(WASM_REFLECT, [f], true).unwrap();
let message = "hello world";
let output: String = plugin
.call_with_host_context(
"reflect",
"anything, really",
Foo {
message: message.to_string(),
},
)
.unwrap();
assert_eq!(output, message);
}
#[test]
fn test_fuzz_reflect_plugin() {
// assert!(set_log_file("stdout", Some(log::Level::Trace)));