mirror of
https://github.com/extism/extism.git
synced 2026-04-23 03:00:11 -04:00
Compare commits
2 Commits
userdata
...
1.0-readme
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d08c1e2e8 | ||
|
|
65464e5e13 |
1
.github/workflows/kernel.yml
vendored
1
.github/workflows/kernel.yml
vendored
@@ -39,7 +39,6 @@ jobs:
|
|||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
if: ${{ env.GIT_EXIT_CODE }} != 0
|
if: ${{ env.GIT_EXIT_CODE }} != 0
|
||||||
with:
|
with:
|
||||||
author: "zshipko <zshipko@users.noreply.github.com>"
|
|
||||||
title: "update(kernel): extism-runtime.wasm in ${{ github.event.pull_request.head.ref }}"
|
title: "update(kernel): extism-runtime.wasm in ${{ github.event.pull_request.head.ref }}"
|
||||||
body: "Automated PR to update `runtime/src/extism-runtime.wasm` in PR #${{ github.event.number }}"
|
body: "Automated PR to update `runtime/src/extism-runtime.wasm` in PR #${{ github.event.number }}"
|
||||||
base: "${{ github.event.pull_request.head.ref }}"
|
base: "${{ github.event.pull_request.head.ref }}"
|
||||||
|
|||||||
12
.github/workflows/release-rust.yaml
vendored
12
.github/workflows/release-rust.yaml
vendored
@@ -36,18 +36,6 @@ jobs:
|
|||||||
override: true
|
override: true
|
||||||
target: ${{ matrix.target }}
|
target: ${{ matrix.target }}
|
||||||
|
|
||||||
- name: Release Rust convert-macros Crate
|
|
||||||
env:
|
|
||||||
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_API_TOKEN }}
|
|
||||||
run: |
|
|
||||||
version=$(cargo metadata --format-version=1 | jq -r '.packages[] | select(.name == "extism") | .version')
|
|
||||||
|
|
||||||
if ! &>/dev/null curl -sLIf https://crates.io/api/v1/crates/extism-convert-macros/${version}/download; then
|
|
||||||
cargo publish --manifest-path convert-macros/Cargo.toml --allow-dirty
|
|
||||||
else
|
|
||||||
echo "already published ${version}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Release Rust Convert Crate
|
- name: Release Rust Convert Crate
|
||||||
env:
|
env:
|
||||||
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_API_TOKEN }}
|
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_API_TOKEN }}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = ["extism-maturin", "manifest", "runtime", "libextism", "convert", "convert-macros"]
|
members = ["extism-maturin", "manifest", "runtime", "libextism", "convert"]
|
||||||
exclude = ["kernel"]
|
exclude = ["kernel"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
@@ -14,5 +14,4 @@ version = "0.0.0+replaced-by-ci"
|
|||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
extism = { path = "./runtime", version = "0.0.0+replaced-by-ci" }
|
extism = { path = "./runtime", version = "0.0.0+replaced-by-ci" }
|
||||||
extism-convert = { path = "./convert", version = "0.0.0+replaced-by-ci" }
|
extism-convert = { path = "./convert", version = "0.0.0+replaced-by-ci" }
|
||||||
extism-convert-macros = { path = "./convert-macros", version = "0.0.0+replaced-by-ci" }
|
|
||||||
extism-manifest = { path = "./manifest", version = "0.0.0+replaced-by-ci" }
|
extism-manifest = { path = "./manifest", version = "0.0.0+replaced-by-ci" }
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
[](https://extism.org/discord)
|
[](https://extism.org/discord)
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
@@ -76,9 +76,9 @@ get started:
|
|||||||
| Go PDK | <img alt="Go PDK" src="https://extism.org/img/sdk-languages/go.svg" width="50px"/> | https://github.com/extism/go-pdk | [Go mod](https://pkg.go.dev/github.com/extism/go-pdk) |
|
| Go PDK | <img alt="Go PDK" src="https://extism.org/img/sdk-languages/go.svg" width="50px"/> | https://github.com/extism/go-pdk | [Go mod](https://pkg.go.dev/github.com/extism/go-pdk) |
|
||||||
| Haskell PDK | <img alt="Haskell PDK" src="https://extism.org/img/sdk-languages/haskell.svg" width="50px"/> | https://github.com/extism/haskell-pdk | [Hackage](https://hackage.haskell.org/package/extism-pdk) |
|
| Haskell PDK | <img alt="Haskell PDK" src="https://extism.org/img/sdk-languages/haskell.svg" width="50px"/> | https://github.com/extism/haskell-pdk | [Hackage](https://hackage.haskell.org/package/extism-pdk) |
|
||||||
| AssemblyScript PDK | <img alt="AssemblyScript PDK" src="https://extism.org/img/sdk-languages/assemblyscript.svg" width="50px"/> | https://github.com/extism/assemblyscript-pdk | [NPM](https://www.npmjs.com/package/@extism/as-pdk) |
|
| AssemblyScript PDK | <img alt="AssemblyScript PDK" src="https://extism.org/img/sdk-languages/assemblyscript.svg" width="50px"/> | https://github.com/extism/assemblyscript-pdk | [NPM](https://www.npmjs.com/package/@extism/as-pdk) |
|
||||||
| .NET PDK | <img alt=".NET PDK" src="https://extism.org/img/sdk-languages/dotnet.svg" width="50px"/> | https://github.com/extism/dotnet-pdk <br/>(supports C# & F#!) | https://www.nuget.org/packages/Extism.Pdk |
|
|
||||||
| C PDK | <img alt="C PDK" src="https://extism.org/img/sdk-languages/c.svg" width="50px"/> | https://github.com/extism/c-pdk | N/A |
|
| C PDK | <img alt="C PDK" src="https://extism.org/img/sdk-languages/c.svg" width="50px"/> | https://github.com/extism/c-pdk | N/A |
|
||||||
| Zig PDK | <img alt="Zig PDK" src="https://extism.org/img/sdk-languages/zig.svg" width="50px"/> | https://github.com/extism/zig-pdk | N/A |
|
| Zig PDK | <img alt="Zig PDK" src="https://extism.org/img/sdk-languages/zig.svg" width="50px"/> | https://github.com/extism/zig-pdk | N/A |
|
||||||
|
| .NET PDK | <img alt=".NET PDK" src="https://extism.org/img/sdk-languages/dotnet.svg" width="50px"/> | https://github.com/extism/dotnet-pdk <br/>(supports C# & F#!) | N/A |
|
||||||
|
|
||||||
# Support
|
# Support
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "extism-convert-macros"
|
|
||||||
edition.workspace = true
|
|
||||||
authors.workspace = true
|
|
||||||
license.workspace = true
|
|
||||||
homepage.workspace = true
|
|
||||||
repository.workspace = true
|
|
||||||
version.workspace = true
|
|
||||||
description = "Macros to remove boilerplate with Extism"
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
proc-macro = true
|
|
||||||
|
|
||||||
[features]
|
|
||||||
extism-path = []
|
|
||||||
extism-pdk-path = []
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
manyhow.version = "0.11.0"
|
|
||||||
proc-macro-crate = "3.1.0"
|
|
||||||
proc-macro2 = "1.0.78"
|
|
||||||
quote = "1.0.35"
|
|
||||||
syn = { version = "2.0.48", features = ["derive"] }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
trybuild = "1.0.89"
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
use std::iter;
|
|
||||||
|
|
||||||
use manyhow::{ensure, error_message, manyhow, Result};
|
|
||||||
use proc_macro_crate::{crate_name, FoundCrate};
|
|
||||||
use quote::{format_ident, quote, ToTokens};
|
|
||||||
use syn::{parse_quote, Attribute, DeriveInput, Path};
|
|
||||||
|
|
||||||
/// Tries to resolve the path to `extism_convert` dynamically, falling back to feature flags when unsuccessful.
|
|
||||||
fn convert_path() -> Path {
|
|
||||||
match (
|
|
||||||
crate_name("extism"),
|
|
||||||
crate_name("extism-convert"),
|
|
||||||
crate_name("extism-pdk"),
|
|
||||||
) {
|
|
||||||
(Ok(FoundCrate::Name(name)), ..) => {
|
|
||||||
let ident = format_ident!("{name}");
|
|
||||||
parse_quote!(::#ident::convert)
|
|
||||||
}
|
|
||||||
(_, Ok(FoundCrate::Name(name)), ..) | (.., Ok(FoundCrate::Name(name))) => {
|
|
||||||
let ident = format_ident!("{name}");
|
|
||||||
parse_quote!(::#ident)
|
|
||||||
}
|
|
||||||
(Ok(FoundCrate::Itself), ..) => parse_quote!(::extism::convert),
|
|
||||||
(_, Ok(FoundCrate::Itself), ..) => parse_quote!(::extism_convert),
|
|
||||||
(.., Ok(FoundCrate::Itself)) => parse_quote!(::extism_pdk),
|
|
||||||
_ if cfg!(feature = "extism-path") => parse_quote!(::extism::convert),
|
|
||||||
_ if cfg!(feature = "extism-pdk-path") => parse_quote!(::extism_pdk),
|
|
||||||
_ => parse_quote!(::extism_convert),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extract_encoding(attrs: &[Attribute]) -> Result<Path> {
|
|
||||||
let encodings: Vec<_> = attrs
|
|
||||||
.iter()
|
|
||||||
.filter(|attr| attr.path().is_ident("encoding"))
|
|
||||||
.collect();
|
|
||||||
ensure!(!encodings.is_empty(), "encoding needs to be specified"; try = "`#[encoding(ToJson)]`");
|
|
||||||
ensure!(encodings.len() < 2, encodings[1], "only one encoding can be specified"; try = "remove `{}`", encodings[1].to_token_stream());
|
|
||||||
|
|
||||||
Ok(encodings[0].parse_args().map_err(
|
|
||||||
|e| error_message!(e.span(), "{e}"; note= "expects a path"; try = "`#[encoding(ToJson)]`"),
|
|
||||||
)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[manyhow]
|
|
||||||
#[proc_macro_derive(ToBytes, attributes(encoding))]
|
|
||||||
pub fn to_bytes(
|
|
||||||
DeriveInput {
|
|
||||||
attrs,
|
|
||||||
ident,
|
|
||||||
generics,
|
|
||||||
..
|
|
||||||
}: DeriveInput,
|
|
||||||
) -> Result {
|
|
||||||
let encoding = extract_encoding(&attrs)?;
|
|
||||||
let convert = convert_path();
|
|
||||||
|
|
||||||
let (_, type_generics, _) = generics.split_for_impl();
|
|
||||||
|
|
||||||
let mut generics = generics.clone();
|
|
||||||
generics.make_where_clause().predicates.push(
|
|
||||||
parse_quote!(for<'__to_bytes_b> #encoding<&'__to_bytes_b Self>: #convert::ToBytes<'__to_bytes_b>)
|
|
||||||
);
|
|
||||||
generics.params = iter::once(parse_quote!('__to_bytes_a))
|
|
||||||
.chain(generics.params)
|
|
||||||
.collect();
|
|
||||||
let (impl_generics, _, where_clause) = generics.split_for_impl();
|
|
||||||
|
|
||||||
Ok(quote! {
|
|
||||||
impl #impl_generics #convert::ToBytes<'__to_bytes_a> for #ident #type_generics #where_clause
|
|
||||||
{
|
|
||||||
type Bytes = ::std::vec::Vec<u8>;
|
|
||||||
|
|
||||||
fn to_bytes(&self) -> Result<Self::Bytes, #convert::Error> {
|
|
||||||
#convert::ToBytes::to_bytes(&#encoding(self)).map(|__bytes| __bytes.as_ref().to_vec())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[manyhow]
|
|
||||||
#[proc_macro_derive(FromBytes, attributes(encoding))]
|
|
||||||
pub fn from_bytes(
|
|
||||||
DeriveInput {
|
|
||||||
attrs,
|
|
||||||
ident,
|
|
||||||
mut generics,
|
|
||||||
..
|
|
||||||
}: DeriveInput,
|
|
||||||
) -> Result {
|
|
||||||
let encoding = extract_encoding(&attrs)?;
|
|
||||||
let convert = convert_path();
|
|
||||||
generics
|
|
||||||
.make_where_clause()
|
|
||||||
.predicates
|
|
||||||
.push(parse_quote!(#encoding<Self>: #convert::FromBytesOwned));
|
|
||||||
let (impl_generics, type_generics, where_clause) = generics.split_for_impl();
|
|
||||||
Ok(quote! {
|
|
||||||
impl #impl_generics #convert::FromBytesOwned for #ident #type_generics #where_clause
|
|
||||||
{
|
|
||||||
fn from_bytes_owned(__data: &[u8]) -> Result<Self, #convert::Error> {
|
|
||||||
<#encoding<Self> as #convert::FromBytesOwned>::from_bytes_owned(__data).map(|__encoding| __encoding.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
#[test]
|
|
||||||
fn ui() {
|
|
||||||
let t = trybuild::TestCases::new();
|
|
||||||
t.compile_fail("tests/ui/*.rs");
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
use extism_convert_macros::ToBytes;
|
|
||||||
|
|
||||||
#[derive(ToBytes)]
|
|
||||||
struct MissingEncoding;
|
|
||||||
|
|
||||||
#[derive(ToBytes)]
|
|
||||||
#[encoding]
|
|
||||||
struct EmptyAttr;
|
|
||||||
|
|
||||||
#[derive(ToBytes)]
|
|
||||||
#[encoding = "string"]
|
|
||||||
struct EqNoParen;
|
|
||||||
|
|
||||||
#[derive(ToBytes)]
|
|
||||||
#[encoding(something, else)]
|
|
||||||
struct NotAPath;
|
|
||||||
|
|
||||||
#[derive(ToBytes)]
|
|
||||||
#[encoding(Multiple)]
|
|
||||||
#[encoding(Encodings)]
|
|
||||||
struct MultipleEncodings;
|
|
||||||
|
|
||||||
fn main() {}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
error: encoding needs to be specified
|
|
||||||
|
|
||||||
= try: `#[encoding(ToJson)]`
|
|
||||||
--> tests/ui/invalid-encoding.rs:3:10
|
|
||||||
|
|
|
||||||
3 | #[derive(ToBytes)]
|
|
||||||
| ^^^^^^^
|
|
||||||
|
|
|
||||||
= note: this error originates in the derive macro `ToBytes` (in Nightly builds, run with -Z macro-backtrace for more info)
|
|
||||||
|
|
||||||
error: expected attribute arguments in parentheses: #[encoding(...)]
|
|
||||||
|
|
||||||
= note: expects a path
|
|
||||||
= try: `#[encoding(ToJson)]`
|
|
||||||
--> tests/ui/invalid-encoding.rs:7:3
|
|
||||||
|
|
|
||||||
7 | #[encoding]
|
|
||||||
| ^^^^^^^^
|
|
||||||
|
|
||||||
error: expected parentheses: #[encoding(...)]
|
|
||||||
|
|
||||||
= note: expects a path
|
|
||||||
= try: `#[encoding(ToJson)]`
|
|
||||||
--> tests/ui/invalid-encoding.rs:11:12
|
|
||||||
|
|
|
||||||
11 | #[encoding = "string"]
|
|
||||||
| ^
|
|
||||||
|
|
||||||
error: unexpected token
|
|
||||||
|
|
||||||
= note: expects a path
|
|
||||||
= try: `#[encoding(ToJson)]`
|
|
||||||
--> tests/ui/invalid-encoding.rs:15:21
|
|
||||||
|
|
|
||||||
15 | #[encoding(something, else)]
|
|
||||||
| ^
|
|
||||||
|
|
||||||
error: only one encoding can be specified
|
|
||||||
|
|
||||||
= try: remove `#[encoding(Encodings)]`
|
|
||||||
--> tests/ui/invalid-encoding.rs:20:1
|
|
||||||
|
|
|
||||||
20 | #[encoding(Encodings)]
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
@@ -11,14 +11,13 @@ description = "Traits to make Rust types usable with Extism"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.75"
|
anyhow = "1.0.75"
|
||||||
base64 = "~0.22"
|
base64 = "~0.21"
|
||||||
bytemuck = {version = "1.14.0", optional = true }
|
bytemuck = {version = "1.14.0", optional = true }
|
||||||
prost = { version = "0.12.0", optional = true }
|
prost = { version = "0.12.0", optional = true }
|
||||||
protobuf = { version = "3.2.0", optional = true }
|
protobuf = { version = "3.2.0", optional = true }
|
||||||
rmp-serde = { version = "1.1.2", optional = true }
|
rmp-serde = { version = "1.1.2", optional = true }
|
||||||
serde = "1.0.186"
|
serde = "1.0.186"
|
||||||
serde_json = "1.0.105"
|
serde_json = "1.0.105"
|
||||||
extism-convert-macros.workspace = true
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
serde = { version = "1.0.186", features = ["derive"] }
|
serde = { version = "1.0.186", features = ["derive"] }
|
||||||
@@ -27,5 +26,3 @@ serde = { version = "1.0.186", features = ["derive"] }
|
|||||||
default = ["msgpack", "prost", "raw"]
|
default = ["msgpack", "prost", "raw"]
|
||||||
msgpack = ["rmp-serde"]
|
msgpack = ["rmp-serde"]
|
||||||
raw = ["bytemuck"]
|
raw = ["bytemuck"]
|
||||||
extism-path = ["extism-convert-macros/extism-path"]
|
|
||||||
extism-pdk-path = ["extism-convert-macros/extism-pdk-path"]
|
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ use base64::Engine;
|
|||||||
/// extism_convert::encoding!(MyJson, serde_json::to_vec, serde_json::from_slice);
|
/// extism_convert::encoding!(MyJson, serde_json::to_vec, serde_json::from_slice);
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// This will create a struct `struct MyJson<T>(pub T)` and implement [`ToBytes`] using [`serde_json::to_vec`]
|
/// This will create a struct `struct MyJson<T>(pub T)` and implement `ToBytes` using `serde_json::to_vec`
|
||||||
/// and [`FromBytesOwned`] using [`serde_json::from_slice`]
|
/// and `FromBytesOwned` using `serde_json::from_vec`
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! encoding {
|
macro_rules! encoding {
|
||||||
($pub:vis $name:ident, $to_vec:expr, $from_slice:expr) => {
|
($pub:vis $name:ident, $to_vec:expr, $from_slice:expr) => {
|
||||||
@@ -183,5 +183,30 @@ impl<'a, T: bytemuck::Pod> FromBytes<'a> for Raw<'a, T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(feature = "raw", target_endian = "big"))]
|
#[cfg(all(test, feature = "raw", target_endian = "little"))]
|
||||||
compile_error!("The raw feature is only supported on little endian targets");
|
mod tests {
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_raw() {
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
struct TestRaw {
|
||||||
|
a: i32,
|
||||||
|
b: f64,
|
||||||
|
c: bool,
|
||||||
|
}
|
||||||
|
unsafe impl bytemuck::Pod for TestRaw {}
|
||||||
|
unsafe impl bytemuck::Zeroable for TestRaw {}
|
||||||
|
let x = TestRaw {
|
||||||
|
a: 123,
|
||||||
|
b: 45678.91011,
|
||||||
|
c: true,
|
||||||
|
};
|
||||||
|
let raw = Raw(&x).to_bytes().unwrap();
|
||||||
|
let y = Raw::from_bytes(&raw).unwrap();
|
||||||
|
assert_eq!(&x, y.0);
|
||||||
|
|
||||||
|
let y: Result<Raw<[u8; std::mem::size_of::<TestRaw>()]>, Error> = Raw::from_bytes(&raw);
|
||||||
|
assert!(y.is_ok());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,68 +1,14 @@
|
|||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
pub use extism_convert_macros::FromBytes;
|
|
||||||
|
|
||||||
/// `FromBytes` is used to define how a type should be decoded when working with
|
/// `FromBytes` is used to define how a type should be decoded when working with
|
||||||
/// Extism memory. It is used for plugin output and host function input.
|
/// Extism memory. It is used for plugin output and host function input.
|
||||||
///
|
|
||||||
/// `FromBytes` can be derived by delegating encoding to generic type implementing
|
|
||||||
/// `FromBytes`, e.g., [`Json`], [`Msgpack`].
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use extism_convert::{Json, FromBytes};
|
|
||||||
/// use serde::Deserialize;
|
|
||||||
///
|
|
||||||
/// #[derive(FromBytes, Deserialize, PartialEq, Debug)]
|
|
||||||
/// #[encoding(Json)]
|
|
||||||
/// struct Struct {
|
|
||||||
/// hello: String,
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// assert_eq!(Struct::from_bytes(br#"{"hello":"hi"}"#)?, Struct { hello: "hi".into() });
|
|
||||||
/// # Ok::<(), extism_convert::Error>(())
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// Custom encodings can also be used, through new-types with a single generic
|
|
||||||
/// argument, i.e., `Type<T>(T)`, that implement `FromBytesOwned` for the struct.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use std::str::{self, FromStr};
|
|
||||||
/// use std::convert::Infallible;
|
|
||||||
/// use extism_convert::{Error, FromBytes, FromBytesOwned};
|
|
||||||
///
|
|
||||||
/// // Custom serialization using `FromStr`
|
|
||||||
/// struct StringEnc<T>(T);
|
|
||||||
/// impl<T: FromStr> FromBytesOwned for StringEnc<T> where Error: From<<T as FromStr>::Err> {
|
|
||||||
/// fn from_bytes_owned(data: &[u8]) -> Result<Self, Error> {
|
|
||||||
/// Ok(Self(str::from_utf8(data)?.parse()?))
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// #[derive(FromBytes, PartialEq, Debug)]
|
|
||||||
/// #[encoding(StringEnc)]
|
|
||||||
/// struct Struct {
|
|
||||||
/// hello: String,
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// impl FromStr for Struct {
|
|
||||||
/// type Err = Infallible;
|
|
||||||
/// fn from_str(s: &str) -> Result<Self, Infallible> {
|
|
||||||
/// Ok(Self { hello: s.to_owned() })
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// assert_eq!(Struct::from_bytes(b"hi")?, Struct { hello: "hi".into() });
|
|
||||||
/// # Ok::<(), extism_convert::Error>(())
|
|
||||||
/// ```
|
|
||||||
pub trait FromBytes<'a>: Sized {
|
pub trait FromBytes<'a>: Sized {
|
||||||
/// Decode a value from a slice of bytes
|
/// Decode a value from a slice of bytes
|
||||||
fn from_bytes(data: &'a [u8]) -> Result<Self, Error>;
|
fn from_bytes(data: &'a [u8]) -> Result<Self, Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `FromBytesOwned` is similar to [`FromBytes`] but it doesn't borrow from the input slice.
|
/// `FromBytesOwned` is similar to `FromBytes` but it doesn't borrow from the input slice.
|
||||||
/// [`FromBytes`] is automatically implemented for all types that implement `FromBytesOwned`.
|
/// `FromBytes` is automatically implemented for all types that implement `FromBytesOwned`
|
||||||
///
|
|
||||||
/// `FromBytesOwned` can be derived through [`#[derive(FromBytes)]`](FromBytes).
|
|
||||||
pub trait FromBytesOwned: Sized {
|
pub trait FromBytesOwned: Sized {
|
||||||
/// Decode a value from a slice of bytes, the resulting value should not borrow the input
|
/// Decode a value from a slice of bytes, the resulting value should not borrow the input
|
||||||
/// data.
|
/// data.
|
||||||
@@ -152,13 +98,3 @@ impl<'a, T: FromBytes<'a>> FromBytes<'a> for std::io::Cursor<T> {
|
|||||||
Ok(std::io::Cursor::new(T::from_bytes(data)?))
|
Ok(std::io::Cursor::new(T::from_bytes(data)?))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T: FromBytes<'a>> FromBytes<'a> for Option<T> {
|
|
||||||
fn from_bytes(data: &'a [u8]) -> Result<Self, Error> {
|
|
||||||
if data.is_empty() {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
T::from_bytes(data).map(Some)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,9 +5,6 @@
|
|||||||
//! similar to [axum extractors](https://docs.rs/axum/latest/axum/extract/index.html#intro) - they are
|
//! similar to [axum extractors](https://docs.rs/axum/latest/axum/extract/index.html#intro) - they are
|
||||||
//! implemented as a tuple struct with a single field that is meant to be extracted using pattern matching.
|
//! implemented as a tuple struct with a single field that is meant to be extracted using pattern matching.
|
||||||
|
|
||||||
// Makes proc-macros able to resolve `::extism_convert` correctly
|
|
||||||
extern crate self as extism_convert;
|
|
||||||
|
|
||||||
pub use anyhow::Error;
|
pub use anyhow::Error;
|
||||||
|
|
||||||
mod encoding;
|
mod encoding;
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ fn roundtrip_json() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "msgpack")]
|
|
||||||
fn roundtrip_msgpack() {
|
fn roundtrip_msgpack() {
|
||||||
let x = Testing {
|
let x = Testing {
|
||||||
a: "foobar".to_string(),
|
a: "foobar".to_string(),
|
||||||
@@ -38,53 +37,3 @@ fn roundtrip_base64() {
|
|||||||
let Base64(s): Base64<String> = FromBytes::from_bytes(bytes.as_bytes()).unwrap();
|
let Base64(s): Base64<String> = FromBytes::from_bytes(bytes.as_bytes()).unwrap();
|
||||||
assert_eq!(s, "this is a test");
|
assert_eq!(s, "this is a test");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn rountrip_option() {
|
|
||||||
// `None` case
|
|
||||||
let e0: Option<Json<Testing>> = FromBytes::from_bytes(&[]).unwrap();
|
|
||||||
let b = e0.to_bytes().unwrap();
|
|
||||||
let e1: Option<Json<Testing>> = FromBytes::from_bytes(&b).unwrap();
|
|
||||||
assert!(e0.is_none());
|
|
||||||
assert_eq!(e0.is_none(), e1.is_none());
|
|
||||||
|
|
||||||
// `Some` case
|
|
||||||
let x = Testing {
|
|
||||||
a: "foobar".to_string(),
|
|
||||||
b: 123,
|
|
||||||
c: 456.7,
|
|
||||||
};
|
|
||||||
let bytes = Json(&x).to_bytes().unwrap();
|
|
||||||
let y: Option<Json<Testing>> = FromBytes::from_bytes(&bytes).unwrap();
|
|
||||||
let b = ToBytes::to_bytes(&y).unwrap();
|
|
||||||
let z: Option<Json<Testing>> = FromBytes::from_bytes(&b).unwrap();
|
|
||||||
assert_eq!(y.unwrap().0, z.unwrap().0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(all(feature = "raw", target_endian = "little"))]
|
|
||||||
mod tests {
|
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_raw() {
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
||||||
struct TestRaw {
|
|
||||||
a: i32,
|
|
||||||
b: f64,
|
|
||||||
c: bool,
|
|
||||||
}
|
|
||||||
unsafe impl bytemuck::Pod for TestRaw {}
|
|
||||||
unsafe impl bytemuck::Zeroable for TestRaw {}
|
|
||||||
let x = TestRaw {
|
|
||||||
a: 123,
|
|
||||||
b: 45678.91011,
|
|
||||||
c: true,
|
|
||||||
};
|
|
||||||
let raw = Raw(&x).to_bytes().unwrap();
|
|
||||||
let y = Raw::from_bytes(&raw).unwrap();
|
|
||||||
assert_eq!(&x, y.0);
|
|
||||||
|
|
||||||
let y: Result<Raw<[u8; std::mem::size_of::<TestRaw>()]>, Error> = Raw::from_bytes(&raw);
|
|
||||||
assert!(y.is_ok());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,58 +1,7 @@
|
|||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
pub use extism_convert_macros::ToBytes;
|
|
||||||
|
|
||||||
/// `ToBytes` is used to define how a type should be encoded when working with
|
/// `ToBytes` is used to define how a type should be encoded when working with
|
||||||
/// Extism memory. It is used for plugin input and host function output.
|
/// Extism memory. It is used for plugin input and host function output.
|
||||||
///
|
|
||||||
/// `ToBytes` can be derived by delegating encoding to generic type implementing
|
|
||||||
/// `ToBytes`, e.g., [`Json`], [`Msgpack`].
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use extism_convert::{Json, ToBytes};
|
|
||||||
/// use serde::Serialize;
|
|
||||||
///
|
|
||||||
/// #[derive(ToBytes, Serialize)]
|
|
||||||
/// #[encoding(Json)]
|
|
||||||
/// struct Struct {
|
|
||||||
/// hello: String,
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// assert_eq!(Struct { hello: "hi".into() }.to_bytes()?, br#"{"hello":"hi"}"#);
|
|
||||||
/// # Ok::<(), extism_convert::Error>(())
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// But custom types can also be used, as long as they are new-types with a single
|
|
||||||
/// generic argument, i.e., `Type<T>(T)`, that implement `ToBytes` for the struct.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use extism_convert::{Error, ToBytes};
|
|
||||||
///
|
|
||||||
/// // Custom serialization using `ToString`
|
|
||||||
/// struct StringEnc<T>(T);
|
|
||||||
/// impl<T: ToString> ToBytes<'_> for StringEnc<&T> {
|
|
||||||
/// type Bytes = String;
|
|
||||||
///
|
|
||||||
/// fn to_bytes(&self) -> Result<String, Error> {
|
|
||||||
/// Ok(self.0.to_string())
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// #[derive(ToBytes)]
|
|
||||||
/// #[encoding(StringEnc)]
|
|
||||||
/// struct Struct {
|
|
||||||
/// hello: String,
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// impl ToString for Struct {
|
|
||||||
/// fn to_string(&self) -> String {
|
|
||||||
/// self.hello.clone()
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// assert_eq!(Struct { hello: "hi".into() }.to_bytes()?, b"hi");
|
|
||||||
/// # Ok::<(), Error>(())
|
|
||||||
/// ```
|
|
||||||
pub trait ToBytes<'a> {
|
pub trait ToBytes<'a> {
|
||||||
/// A configurable byte slice representation, allows any type that implements `AsRef<[u8]>`
|
/// A configurable byte slice representation, allows any type that implements `AsRef<[u8]>`
|
||||||
type Bytes: AsRef<[u8]>;
|
type Bytes: AsRef<[u8]>;
|
||||||
@@ -151,26 +100,3 @@ impl<'a, T: ToBytes<'a>> ToBytes<'a> for &'a T {
|
|||||||
<T as ToBytes>::to_bytes(self)
|
<T as ToBytes>::to_bytes(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T: ToBytes<'a>> ToBytes<'a> for Option<T> {
|
|
||||||
type Bytes = Vec<u8>;
|
|
||||||
|
|
||||||
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
|
|
||||||
match self {
|
|
||||||
Some(x) => x.to_bytes().map(|x| x.as_ref().to_vec()),
|
|
||||||
None => Ok(vec![]),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test() {
|
|
||||||
use extism_convert::{Json, ToBytes};
|
|
||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
#[derive(ToBytes, Serialize)]
|
|
||||||
#[encoding(Json)]
|
|
||||||
struct Struct {
|
|
||||||
hello: String,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,9 +5,6 @@ edition = "2021"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
wasm-bindgen-test = "0.3.39"
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["bounds-checking"]
|
default = ["bounds-checking"]
|
||||||
bounds-checking = []
|
bounds-checking = []
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
pub use extism_runtime_kernel::*;
|
pub use extism_runtime_kernel::*;
|
||||||
|
|
||||||
#[cfg(all(target_arch = "wasm32", not(test)))]
|
#[cfg(target_arch = "wasm32")]
|
||||||
#[panic_handler]
|
#[panic_handler]
|
||||||
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
||||||
core::arch::wasm32::unreachable()
|
core::arch::wasm32::unreachable()
|
||||||
|
|||||||
@@ -198,8 +198,8 @@ impl MemoryRoot {
|
|||||||
fn pointer_in_bounds_fast(p: Pointer) -> bool {
|
fn pointer_in_bounds_fast(p: Pointer) -> bool {
|
||||||
// Similar to `pointer_in_bounds` but less accurate on the upper bound. This uses the total memory size,
|
// Similar to `pointer_in_bounds` but less accurate on the upper bound. This uses the total memory size,
|
||||||
// instead of checking `MemoryRoot::length`
|
// instead of checking `MemoryRoot::length`
|
||||||
let end = (core::arch::wasm32::memory_size(0) as u64) << 16;
|
let end = core::arch::wasm32::memory_size(0) << 16;
|
||||||
p >= core::mem::size_of::<Self>() as Pointer && p <= end as u64
|
p >= core::mem::size_of::<Self>() as Pointer && p <= end as Pointer
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find a block that is free to use, this can be a new block or an existing freed block. The `self_position` argument
|
// Find a block that is free to use, this can be a new block or an existing freed block. The `self_position` argument
|
||||||
@@ -228,7 +228,7 @@ impl MemoryRoot {
|
|||||||
if status == MemoryStatus::Free as u8 && b.size >= length as usize {
|
if status == MemoryStatus::Free as u8 && b.size >= length as usize {
|
||||||
// Split block if there is too much excess
|
// Split block if there is too much excess
|
||||||
if b.size - length as usize >= 128 {
|
if b.size - length as usize >= 128 {
|
||||||
b.size -= length as usize + core::mem::size_of::<MemoryBlock>();
|
b.size -= length as usize;
|
||||||
b.used = 0;
|
b.used = 0;
|
||||||
|
|
||||||
let block1 = b.data.as_mut_ptr().add(b.size) as *mut MemoryBlock;
|
let block1 = b.data.as_mut_ptr().add(b.size) as *mut MemoryBlock;
|
||||||
@@ -270,13 +270,12 @@ impl MemoryRoot {
|
|||||||
|
|
||||||
// Get the number of bytes available
|
// Get the number of bytes available
|
||||||
let mem_left = self_length - self_position - core::mem::size_of::<MemoryRoot>() as u64;
|
let mem_left = self_length - self_position - core::mem::size_of::<MemoryRoot>() as u64;
|
||||||
let length_with_block = length + core::mem::size_of::<MemoryBlock>() as u64;
|
|
||||||
|
|
||||||
// When the allocation is larger than the number of bytes available
|
// When the allocation is larger than the number of bytes available
|
||||||
// we will need to try to grow the memory
|
// we will need to try to grow the memory
|
||||||
if length_with_block >= mem_left {
|
if length >= mem_left {
|
||||||
// Calculate the number of pages needed to cover the remaining bytes
|
// Calculate the number of pages needed to cover the remaining bytes
|
||||||
let npages = num_pages(length_with_block - mem_left);
|
let npages = num_pages(length - mem_left);
|
||||||
let x = core::arch::wasm32::memory_grow(0, npages);
|
let x = core::arch::wasm32::memory_grow(0, npages);
|
||||||
if x == usize::MAX {
|
if x == usize::MAX {
|
||||||
return None;
|
return None;
|
||||||
@@ -575,67 +574,3 @@ pub unsafe fn error_get() -> Handle {
|
|||||||
pub unsafe fn memory_bytes() -> u64 {
|
pub unsafe fn memory_bytes() -> u64 {
|
||||||
MemoryRoot::new().length.load(Ordering::Acquire)
|
MemoryRoot::new().length.load(Ordering::Acquire)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use crate::*;
|
|
||||||
use wasm_bindgen_test::*;
|
|
||||||
|
|
||||||
// See https://github.com/extism/extism/pull/659
|
|
||||||
#[wasm_bindgen_test]
|
|
||||||
fn test_659() {
|
|
||||||
unsafe {
|
|
||||||
// Warning: These offsets will need to change if we adjust the kernel memory layout at all
|
|
||||||
reset();
|
|
||||||
assert_eq!(alloc(1065), 77);
|
|
||||||
assert_eq!(alloc(288), 1154);
|
|
||||||
assert_eq!(alloc(128), 1454);
|
|
||||||
assert_eq!(length(1154), 288);
|
|
||||||
assert_eq!(length(1454), 128);
|
|
||||||
free(1454);
|
|
||||||
assert_eq!(alloc(213), 1594);
|
|
||||||
length_unsafe(1594);
|
|
||||||
assert_eq!(alloc(511), 1819);
|
|
||||||
assert_eq!(alloc(4), 1454);
|
|
||||||
assert_eq!(length(1454), 4);
|
|
||||||
assert_eq!(length(1819), 511);
|
|
||||||
assert_eq!(alloc(13), 2342);
|
|
||||||
assert_eq!(length(2342), 13);
|
|
||||||
assert_eq!(alloc(336), 2367);
|
|
||||||
assert_eq!(alloc(1077), 2715);
|
|
||||||
assert_eq!(length(2367), 336);
|
|
||||||
assert_eq!(length(2715), 1077);
|
|
||||||
free(2715);
|
|
||||||
assert_eq!(alloc(1094), 3804);
|
|
||||||
length_unsafe(3804);
|
|
||||||
|
|
||||||
// Allocate 4 bytes, expect to receive address 3788
|
|
||||||
assert_eq!(alloc(4), 3788);
|
|
||||||
|
|
||||||
assert_eq!(alloc(4), 3772);
|
|
||||||
assert_eq!(length(3772), 4);
|
|
||||||
|
|
||||||
// Address 3788 has not been freed yet, so expect it to have 4 bytes allocated
|
|
||||||
assert_eq!(length(3788), 4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen_test]
|
|
||||||
fn test_oom() {
|
|
||||||
let size = 1024 * 1024 * 5;
|
|
||||||
|
|
||||||
let mut last = 0;
|
|
||||||
for _ in 0..1024 {
|
|
||||||
unsafe {
|
|
||||||
let ptr = alloc(size);
|
|
||||||
last = ptr;
|
|
||||||
if ptr == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
assert_eq!(length(ptr), size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_eq!(last, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
# install wasm-bindgen-cli to get wasm-bindgen-runner if it is not installed yet
|
|
||||||
which wasm-bindgen-test-runner 1>/dev/null || cargo install -f wasm-bindgen-cli
|
|
||||||
|
|
||||||
# run tests with the wasm-bindgen-runner
|
|
||||||
CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_RUNNER=wasm-bindgen-test-runner cargo test --release --target=wasm32-unknown-unknown
|
|
||||||
@@ -10,7 +10,7 @@ version.workspace = true
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
base64 = "~0.22"
|
base64 = "~0.21"
|
||||||
schemars = { version = "0.8", optional = true }
|
schemars = { version = "0.8", optional = true }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
{
|
{
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
"title": "Manifest",
|
"title": "Manifest",
|
||||||
"description": "The `Manifest` type is used to configure the runtime and specify how to load modules.",
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"allowed_hosts": {
|
"allowed_hosts": {
|
||||||
"description": "Specifies which hosts may be accessed via HTTP, if this is empty then no hosts may be accessed. Wildcards may be used.",
|
|
||||||
"default": null,
|
"default": null,
|
||||||
"type": [
|
"type": [
|
||||||
"array",
|
"array",
|
||||||
@@ -16,7 +14,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"allowed_paths": {
|
"allowed_paths": {
|
||||||
"description": "Specifies which paths should be made available on disk when using WASI. This is a mapping from the path on disk to the path it should be available inside the plugin. For example, `\".\": \"/tmp\"` would mount the current directory as `/tmp` inside the module",
|
|
||||||
"default": null,
|
"default": null,
|
||||||
"type": [
|
"type": [
|
||||||
"object",
|
"object",
|
||||||
@@ -27,7 +24,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"description": "Config values are made accessible using the PDK `extism_config_get` function",
|
|
||||||
"default": {},
|
"default": {},
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": {
|
"additionalProperties": {
|
||||||
@@ -35,11 +31,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"memory": {
|
"memory": {
|
||||||
"description": "Memory options",
|
|
||||||
"default": {
|
"default": {
|
||||||
"max_http_response_bytes": null,
|
"max_pages": null
|
||||||
"max_pages": null,
|
|
||||||
"max_var_bytes": null
|
|
||||||
},
|
},
|
||||||
"allOf": [
|
"allOf": [
|
||||||
{
|
{
|
||||||
@@ -48,8 +41,6 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"timeout_ms": {
|
"timeout_ms": {
|
||||||
"description": "The plugin timeout in milliseconds",
|
|
||||||
"default": null,
|
|
||||||
"type": [
|
"type": [
|
||||||
"integer",
|
"integer",
|
||||||
"null"
|
"null"
|
||||||
@@ -58,7 +49,6 @@
|
|||||||
"minimum": 0.0
|
"minimum": 0.0
|
||||||
},
|
},
|
||||||
"wasm": {
|
"wasm": {
|
||||||
"description": "WebAssembly modules, the `main` module should be named `main` or listed last",
|
|
||||||
"default": [],
|
"default": [],
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
@@ -66,63 +56,35 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false,
|
|
||||||
"definitions": {
|
"definitions": {
|
||||||
"MemoryOptions": {
|
"MemoryOptions": {
|
||||||
"description": "Configure memory settings",
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"max_http_response_bytes": {
|
|
||||||
"description": "The maximum number of bytes allowed in an HTTP response",
|
|
||||||
"default": null,
|
|
||||||
"type": [
|
|
||||||
"integer",
|
|
||||||
"null"
|
|
||||||
],
|
|
||||||
"format": "uint64",
|
|
||||||
"minimum": 0.0
|
|
||||||
},
|
|
||||||
"max_pages": {
|
"max_pages": {
|
||||||
"description": "The max number of WebAssembly pages that should be allocated",
|
|
||||||
"type": [
|
"type": [
|
||||||
"integer",
|
"integer",
|
||||||
"null"
|
"null"
|
||||||
],
|
],
|
||||||
"format": "uint32",
|
"format": "uint32",
|
||||||
"minimum": 0.0
|
"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
|
|
||||||
},
|
},
|
||||||
"Wasm": {
|
"Wasm": {
|
||||||
"description": "The `Wasm` type specifies how to access a WebAssembly module",
|
|
||||||
"anyOf": [
|
"anyOf": [
|
||||||
{
|
{
|
||||||
"description": "From disk",
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"path"
|
"path"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"description": "Module hash, if the data loaded from disk or via HTTP doesn't match an error will be raised",
|
|
||||||
"type": [
|
"type": [
|
||||||
"string",
|
"string",
|
||||||
"null"
|
"null"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"description": "Module name, this is used by Extism to determine which is the `main` module",
|
|
||||||
"type": [
|
"type": [
|
||||||
"string",
|
"string",
|
||||||
"null"
|
"null"
|
||||||
@@ -131,72 +93,45 @@
|
|||||||
"path": {
|
"path": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"additionalProperties": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "From memory",
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"data"
|
"data"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"data": {
|
"data": {
|
||||||
"type": [
|
"type": "string",
|
||||||
"string",
|
"format": "string"
|
||||||
"object"
|
|
||||||
],
|
|
||||||
"required": [
|
|
||||||
"len",
|
|
||||||
"ptr"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"len": {
|
|
||||||
"type": "integer",
|
|
||||||
"format": "uint64",
|
|
||||||
"minimum": 0.0
|
|
||||||
},
|
|
||||||
"ptr": {
|
|
||||||
"type": "integer",
|
|
||||||
"format": "uint64",
|
|
||||||
"minimum": 0.0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
},
|
},
|
||||||
"hash": {
|
"hash": {
|
||||||
"description": "Module hash, if the data loaded from disk or via HTTP doesn't match an error will be raised",
|
|
||||||
"type": [
|
"type": [
|
||||||
"string",
|
"string",
|
||||||
"null"
|
"null"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"description": "Module name, this is used by Extism to determine which is the `main` module",
|
|
||||||
"type": [
|
"type": [
|
||||||
"string",
|
"string",
|
||||||
"null"
|
"null"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"additionalProperties": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Via HTTP",
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"url"
|
"url"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"description": "Module hash, if the data loaded from disk or via HTTP doesn't match an error will be raised",
|
|
||||||
"type": [
|
"type": [
|
||||||
"string",
|
"string",
|
||||||
"null"
|
"null"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"headers": {
|
"headers": {
|
||||||
"description": "Request headers",
|
|
||||||
"default": {},
|
"default": {},
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": {
|
"additionalProperties": {
|
||||||
@@ -204,25 +139,21 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"method": {
|
"method": {
|
||||||
"description": "Request method",
|
|
||||||
"type": [
|
"type": [
|
||||||
"string",
|
"string",
|
||||||
"null"
|
"null"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"description": "Module name, this is used by Extism to determine which is the `main` module",
|
|
||||||
"type": [
|
"type": [
|
||||||
"string",
|
"string",
|
||||||
"null"
|
"null"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"url": {
|
"url": {
|
||||||
"description": "The request URL",
|
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"additionalProperties": false
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,44 +12,6 @@ pub struct MemoryOptions {
|
|||||||
/// The max number of WebAssembly pages that should be allocated
|
/// The max number of WebAssembly pages that should be allocated
|
||||||
#[serde(alias = "max")]
|
#[serde(alias = "max")]
|
||||||
pub max_pages: Option<u32>,
|
pub max_pages: Option<u32>,
|
||||||
|
|
||||||
/// 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
|
/// Generic HTTP request structure
|
||||||
@@ -149,8 +111,8 @@ pub enum Wasm {
|
|||||||
|
|
||||||
/// From memory
|
/// From memory
|
||||||
Data {
|
Data {
|
||||||
#[serde(with = "wasmdata")]
|
#[serde(with = "base64")]
|
||||||
#[cfg_attr(feature = "json_schema", schemars(schema_with = "wasmdata_schema"))]
|
#[cfg_attr(feature = "json_schema", schemars(schema_with = "base64_schema"))]
|
||||||
data: Vec<u8>,
|
data: Vec<u8>,
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
meta: WasmMetadata,
|
meta: WasmMetadata,
|
||||||
@@ -233,25 +195,11 @@ impl Wasm {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
|
||||||
#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))]
|
|
||||||
#[serde(deny_unknown_fields)]
|
|
||||||
struct DataPtrLength {
|
|
||||||
ptr: u64,
|
|
||||||
len: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "json_schema")]
|
#[cfg(feature = "json_schema")]
|
||||||
fn wasmdata_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
|
fn base64_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
|
||||||
use schemars::{schema::SchemaObject, JsonSchema};
|
use schemars::{schema::SchemaObject, JsonSchema};
|
||||||
let mut schema: SchemaObject = <String>::json_schema(gen).into();
|
let mut schema: SchemaObject = <String>::json_schema(gen).into();
|
||||||
let objschema: SchemaObject = <DataPtrLength>::json_schema(gen).into();
|
schema.format = Some("string".to_owned());
|
||||||
let types = schemars::schema::SingleOrVec::<schemars::schema::InstanceType>::Vec(vec![
|
|
||||||
schemars::schema::InstanceType::String,
|
|
||||||
schemars::schema::InstanceType::Object,
|
|
||||||
]);
|
|
||||||
schema.instance_type = Some(types);
|
|
||||||
schema.object = objschema.object;
|
|
||||||
schema.into()
|
schema.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,7 +211,6 @@ pub struct Manifest {
|
|||||||
/// WebAssembly modules, the `main` module should be named `main` or listed last
|
/// WebAssembly modules, the `main` module should be named `main` or listed last
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub wasm: Vec<Wasm>,
|
pub wasm: Vec<Wasm>,
|
||||||
|
|
||||||
/// Memory options
|
/// Memory options
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub memory: MemoryOptions,
|
pub memory: MemoryOptions,
|
||||||
@@ -283,7 +230,7 @@ pub struct Manifest {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub allowed_paths: Option<BTreeMap<PathBuf, PathBuf>>,
|
pub allowed_paths: Option<BTreeMap<PathBuf, PathBuf>>,
|
||||||
|
|
||||||
/// The plugin timeout in milliseconds
|
/// The plugin timeout, by default this is set to 30s
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub timeout_ms: Option<u64>,
|
pub timeout_ms: Option<u64>,
|
||||||
}
|
}
|
||||||
@@ -388,12 +335,10 @@ impl Manifest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod wasmdata {
|
mod base64 {
|
||||||
use crate::DataPtrLength;
|
|
||||||
use base64::{engine::general_purpose, Engine as _};
|
use base64::{engine::general_purpose, Engine as _};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde::{Deserializer, Serializer};
|
use serde::{Deserializer, Serializer};
|
||||||
use std::slice;
|
|
||||||
|
|
||||||
pub fn serialize<S: Serializer>(v: &Vec<u8>, s: S) -> Result<S::Ok, S::Error> {
|
pub fn serialize<S: Serializer>(v: &Vec<u8>, s: S) -> Result<S::Ok, S::Error> {
|
||||||
let base64 = general_purpose::STANDARD.encode(v.as_slice());
|
let base64 = general_purpose::STANDARD.encode(v.as_slice());
|
||||||
@@ -401,22 +346,10 @@ mod wasmdata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<u8>, D::Error> {
|
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<u8>, D::Error> {
|
||||||
#[derive(Deserialize)]
|
let base64 = String::deserialize(d)?;
|
||||||
#[serde(untagged)]
|
general_purpose::STANDARD
|
||||||
enum WasmDataTypes {
|
.decode(base64.as_bytes())
|
||||||
String(String),
|
.map_err(serde::de::Error::custom)
|
||||||
DataPtrLength(DataPtrLength),
|
|
||||||
}
|
|
||||||
Ok(match WasmDataTypes::deserialize(d)? {
|
|
||||||
WasmDataTypes::String(string) => general_purpose::STANDARD
|
|
||||||
.decode(string.as_bytes())
|
|
||||||
.map_err(serde::de::Error::custom)?,
|
|
||||||
WasmDataTypes::DataPtrLength(ptrlen) => {
|
|
||||||
let slice =
|
|
||||||
unsafe { slice::from_raw_parts(ptrlen.ptr as *const u8, ptrlen.len as usize) };
|
|
||||||
slice.to_vec()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ repository.workspace = true
|
|||||||
version.workspace = true
|
version.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
wasmtime = ">= 14.0.0, < 18.0.0"
|
wasmtime = ">= 14.0.0, < 17.0.0"
|
||||||
wasmtime-wasi = ">= 14.0.0, < 18.0.0"
|
wasmtime-wasi = ">= 14.0.0, < 17.0.0"
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
serde = {version = "1", features = ["derive"]}
|
serde = {version = "1", features = ["derive"]}
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
@@ -22,7 +22,7 @@ url = "2"
|
|||||||
glob = "0.3"
|
glob = "0.3"
|
||||||
ureq = {version = "2.5", optional=true}
|
ureq = {version = "2.5", optional=true}
|
||||||
extism-manifest = { workspace = true }
|
extism-manifest = { workspace = true }
|
||||||
extism-convert = { workspace = true, features = ["extism-path"] }
|
extism-convert = { workspace = true }
|
||||||
uuid = { version = "1", features = ["v4"] }
|
uuid = { version = "1", features = ["v4"] }
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
|
|
||||||
@@ -37,8 +37,6 @@ cbindgen = { version = "0.26", default-features = false }
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = "0.5.1"
|
criterion = "0.5.1"
|
||||||
quickcheck = "1"
|
|
||||||
rand = "0.8.5"
|
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "bench"
|
name = "bench"
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ To use the `extism` crate, you can add it to your Cargo file:
|
|||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
extism = "1.0.0"
|
extism = "^1.0.0-rc3"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Environment variables
|
## Environment variables
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use extism::*;
|
use extism::*;
|
||||||
|
|
||||||
// pretend this is redis or something :)
|
// pretend this is redis or something :)
|
||||||
type KVStore = std::sync::Arc<std::sync::Mutex<std::collections::BTreeMap<String, Vec<u8>>>>;
|
type KVStore = std::collections::BTreeMap<String, Vec<u8>>;
|
||||||
|
|
||||||
// When a first argument separated with a semicolon is provided to `host_fn` it is used as the
|
// 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
|
// variable name and type for the `UserData` parameter
|
||||||
|
|||||||
Binary file not shown.
@@ -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
|
/// 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.
|
/// will be called when the inner `CPtr` is dropped.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum UserData<T: Sync + Clone + Sized> {
|
pub enum UserData<T: Sized> {
|
||||||
C(Arc<CPtr>),
|
C(Arc<CPtr>),
|
||||||
Rust(T),
|
Rust(Arc<std::sync::Mutex<T>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Default + Sync + Clone> Default for UserData<T> {
|
impl<T: Default> Default for UserData<T> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
UserData::new(T::default())
|
UserData::new(T::default())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Sync + Clone> Clone for UserData<T> {
|
impl<T> Clone for UserData<T> {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
match self {
|
match self {
|
||||||
UserData::C(ptr) => UserData::C(ptr.clone()),
|
UserData::C(ptr) => UserData::C(ptr.clone()),
|
||||||
@@ -101,7 +101,7 @@ impl<T: Sync + Clone> Clone for UserData<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Sync + Clone> UserData<T> {
|
impl<T> UserData<T> {
|
||||||
/// Create a new `UserData` from an existing pointer and free function, this is used
|
/// 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
|
/// by the C API to wrap C pointers into user data
|
||||||
pub(crate) fn new_pointer(
|
pub(crate) fn new_pointer(
|
||||||
@@ -126,11 +126,12 @@ impl<T: Sync + Clone> UserData<T> {
|
|||||||
///
|
///
|
||||||
/// This will wrap the provided value in a reference-counted mutex
|
/// This will wrap the provided value in a reference-counted mutex
|
||||||
pub fn new(x: T) -> Self {
|
pub fn new(x: T) -> Self {
|
||||||
UserData::Rust(x)
|
let data = Arc::new(std::sync::Mutex::new(x));
|
||||||
|
UserData::Rust(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a copy of the inner value
|
/// Get a copy of the inner value
|
||||||
pub fn get(&self) -> Result<T, Error> {
|
pub fn get(&self) -> Result<Arc<std::sync::Mutex<T>>, Error> {
|
||||||
match self {
|
match self {
|
||||||
UserData::C { .. } => anyhow::bail!("C UserData should not be used from Rust"),
|
UserData::C { .. } => anyhow::bail!("C UserData should not be used from Rust"),
|
||||||
UserData::Rust(data) => Ok(data.clone()),
|
UserData::Rust(data) => Ok(data.clone()),
|
||||||
@@ -149,8 +150,8 @@ impl Drop for CPtr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl<T: Sync + Clone> Send for UserData<T> {}
|
unsafe impl<T> Send for UserData<T> {}
|
||||||
unsafe impl<T: Sync + Clone> Sync for UserData<T> {}
|
unsafe impl<T> Sync for UserData<T> {}
|
||||||
unsafe impl Send for CPtr {}
|
unsafe impl Send for CPtr {}
|
||||||
unsafe impl Sync for CPtr {}
|
unsafe impl Sync for CPtr {}
|
||||||
|
|
||||||
@@ -179,7 +180,7 @@ pub struct Function {
|
|||||||
|
|
||||||
impl Function {
|
impl Function {
|
||||||
/// Create a new host function
|
/// Create a new host function
|
||||||
pub fn new<T: 'static + Sync + Clone, F>(
|
pub fn new<T: 'static, F>(
|
||||||
name: impl Into<String>,
|
name: impl Into<String>,
|
||||||
args: impl IntoIterator<Item = ValType>,
|
args: impl IntoIterator<Item = ValType>,
|
||||||
returns: impl IntoIterator<Item = ValType>,
|
returns: impl IntoIterator<Item = ValType>,
|
||||||
@@ -210,9 +211,7 @@ impl Function {
|
|||||||
namespace: None,
|
namespace: None,
|
||||||
_user_data: match &user_data {
|
_user_data: match &user_data {
|
||||||
UserData::C(ptr) => UserDataHandle::C(ptr.clone()),
|
UserData::C(ptr) => UserDataHandle::C(ptr.clone()),
|
||||||
UserData::Rust(x) => {
|
UserData::Rust(x) => UserDataHandle::Rust(x.clone()),
|
||||||
UserDataHandle::Rust(std::sync::Arc::new(std::sync::Mutex::new(x.clone())))
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
// Makes proc-macros able to resolve `::extism` correctly
|
|
||||||
extern crate self as extism;
|
|
||||||
|
|
||||||
pub(crate) use extism_convert::*;
|
pub(crate) use extism_convert::*;
|
||||||
pub(crate) use std::collections::BTreeMap;
|
pub(crate) use std::collections::BTreeMap;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
pub(crate) use wasmtime::*;
|
pub(crate) use wasmtime::*;
|
||||||
|
|
||||||
#[doc(inline)]
|
|
||||||
pub use extism_convert as convert;
|
pub use extism_convert as convert;
|
||||||
|
|
||||||
pub use anyhow::Error;
|
pub use anyhow::Error;
|
||||||
|
|||||||
@@ -97,13 +97,19 @@ pub(crate) fn var_set(
|
|||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let data: &mut CurrentPlugin = caller.data_mut();
|
let data: &mut CurrentPlugin = caller.data_mut();
|
||||||
|
|
||||||
if data.manifest.memory.max_var_bytes.is_some_and(|x| x == 0) {
|
let mut size = 0;
|
||||||
anyhow::bail!("Vars are disabled by this host")
|
for v in data.vars.values() {
|
||||||
|
size += v.len();
|
||||||
}
|
}
|
||||||
|
|
||||||
let voffset = args!(input, 1, i64) as u64;
|
let voffset = args!(input, 1, i64) as u64;
|
||||||
let key_offs = args!(input, 0, 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 key = {
|
||||||
let handle = match data.memory_handle(key_offs) {
|
let handle = match data.memory_handle(key_offs) {
|
||||||
Some(h) => h,
|
Some(h) => h,
|
||||||
@@ -126,22 +132,6 @@ pub(crate) fn var_set(
|
|||||||
None => anyhow::bail!("invalid handle offset for var value: {voffset}"),
|
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();
|
let value = data.memory_bytes(handle)?.to_vec();
|
||||||
|
|
||||||
// Insert the value from memory into the `vars` map
|
// Insert the value from memory into the `vars` map
|
||||||
@@ -236,29 +226,20 @@ pub(crate) fn http_request(
|
|||||||
Some(res.into_reader())
|
Some(res.into_reader())
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let msg = e.to_string();
|
|
||||||
if let Some(res) = e.into_response() {
|
if let Some(res) = e.into_response() {
|
||||||
data.http_status = res.status();
|
data.http_status = res.status();
|
||||||
Some(res.into_reader())
|
Some(res.into_reader())
|
||||||
} else {
|
} else {
|
||||||
return Err(Error::msg(msg));
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(reader) = reader {
|
if let Some(reader) = reader {
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
let max = if let Some(max) = &data.manifest.memory.max_http_response_bytes {
|
reader
|
||||||
reader.take(*max + 1).read_to_end(&mut buf)?;
|
.take(1024 * 1024 * 50) // TODO: make this limit configurable
|
||||||
*max
|
.read_to_end(&mut buf)?;
|
||||||
} else {
|
|
||||||
reader.take(1024 * 1024 * 50 + 1).read_to_end(&mut buf)?;
|
|
||||||
1024 * 1024 * 50
|
|
||||||
};
|
|
||||||
|
|
||||||
if buf.len() > max as usize {
|
|
||||||
anyhow::bail!("HTTP response exceeds the configured maximum number of bytes: {max}")
|
|
||||||
}
|
|
||||||
|
|
||||||
let mem = data.memory_new(&buf)?;
|
let mem = data.memory_new(&buf)?;
|
||||||
output[0] = Val::I64(mem.offset() as i64);
|
output[0] = Val::I64(mem.offset() as i64);
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
use std::{
|
use std::{collections::BTreeMap, path::PathBuf};
|
||||||
collections::{BTreeMap, BTreeSet},
|
|
||||||
path::PathBuf,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
@@ -180,39 +177,6 @@ impl<'a> From<&'a Vec<u8>> for WasmInput<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_module<T: 'static>(
|
|
||||||
store: &mut Store<T>,
|
|
||||||
linker: &mut Linker<T>,
|
|
||||||
linked: &mut BTreeSet<String>,
|
|
||||||
modules: &BTreeMap<String, Module>,
|
|
||||||
name: String,
|
|
||||||
module: &Module,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
if linked.contains(&name) {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
for import in module.imports() {
|
|
||||||
if !linked.contains(import.module()) {
|
|
||||||
if let Some(m) = modules.get(import.module()) {
|
|
||||||
add_module(
|
|
||||||
store,
|
|
||||||
linker,
|
|
||||||
linked,
|
|
||||||
modules,
|
|
||||||
import.module().to_string(),
|
|
||||||
m,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
linker.module(store, name.as_str(), module)?;
|
|
||||||
linked.insert(name);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Plugin {
|
impl Plugin {
|
||||||
/// Create a new plugin from a Manifest or WebAssembly module, and host functions. The `with_wasi`
|
/// 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.
|
/// parameter determines whether or not the module should be executed with WASI enabled.
|
||||||
@@ -276,6 +240,22 @@ impl Plugin {
|
|||||||
store.set_epoch_deadline(1);
|
store.set_epoch_deadline(1);
|
||||||
|
|
||||||
let mut linker = Linker::new(&engine);
|
let mut linker = Linker::new(&engine);
|
||||||
|
linker.allow_shadowing(true);
|
||||||
|
|
||||||
|
// 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
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let main = &modules[MAIN_KEY];
|
||||||
|
for (name, module) in modules.iter() {
|
||||||
|
if name != MAIN_KEY {
|
||||||
|
linker.module(&mut store, name, module)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut imports: Vec<_> = imports.into_iter().collect();
|
let mut imports: Vec<_> = imports.into_iter().collect();
|
||||||
// Define PDK functions
|
// Define PDK functions
|
||||||
macro_rules! add_funcs {
|
macro_rules! add_funcs {
|
||||||
@@ -301,37 +281,14 @@ impl Plugin {
|
|||||||
log_error(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 {
|
for f in &mut imports {
|
||||||
let name = f.name();
|
let name = f.name().to_string();
|
||||||
let ns = f.namespace().unwrap_or(EXTISM_USER_MODULE);
|
let ns = f.namespace().unwrap_or(EXTISM_USER_MODULE);
|
||||||
unsafe {
|
unsafe {
|
||||||
linker.func_new(ns, name, f.ty().clone(), &*(f.f.as_ref() as *const _))?;
|
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 instance_pre = linker.instantiate_pre(main)?;
|
||||||
let timer_tx = Timer::tx();
|
let timer_tx = Timer::tx();
|
||||||
let mut plugin = Plugin {
|
let mut plugin = Plugin {
|
||||||
@@ -446,18 +403,7 @@ impl Plugin {
|
|||||||
pub fn function_exists(&mut self, function: impl AsRef<str>) -> bool {
|
pub fn function_exists(&mut self, function: impl AsRef<str>) -> bool {
|
||||||
self.modules[MAIN_KEY]
|
self.modules[MAIN_KEY]
|
||||||
.get_export(function.as_ref())
|
.get_export(function.as_ref())
|
||||||
.map(|x| {
|
.map(|x| x.func().is_some())
|
||||||
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, 0) => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ impl<'a> PluginBuilder<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Add a single host function
|
/// Add a single host function
|
||||||
pub fn with_function<T: Sync + Clone + 'static, F>(
|
pub fn with_function<T: 'static, F>(
|
||||||
mut self,
|
mut self,
|
||||||
name: impl Into<String>,
|
name: impl Into<String>,
|
||||||
args: impl IntoIterator<Item = ValType>,
|
args: impl IntoIterator<Item = ValType>,
|
||||||
@@ -80,7 +80,7 @@ impl<'a> PluginBuilder<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Add a single host function in a specific namespace
|
/// Add a single host function in a specific namespace
|
||||||
pub fn with_function_in_namespace<T: Sync + Clone + 'static, F>(
|
pub fn with_function_in_namespace<T: 'static, F>(
|
||||||
mut self,
|
mut self,
|
||||||
namespace: impl Into<String>,
|
namespace: impl Into<String>,
|
||||||
name: impl Into<String>,
|
name: impl Into<String>,
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
use crate::*;
|
use crate::*;
|
||||||
use quickcheck::*;
|
|
||||||
|
|
||||||
const KERNEL: &[u8] = include_bytes!("../extism-runtime.wasm");
|
const KERNEL: &[u8] = include_bytes!("../extism-runtime.wasm");
|
||||||
|
|
||||||
@@ -320,158 +319,3 @@ fn test_load_input() {
|
|||||||
// Out of bounds should return 0
|
// Out of bounds should return 0
|
||||||
assert_eq!(extism_input_load_u64(&mut store, instance, 123457), 0);
|
assert_eq!(extism_input_load_u64(&mut store, instance, 123457), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_failed_quickcheck1() {
|
|
||||||
let (mut store, mut instance) = init_kernel_test();
|
|
||||||
let allocs = [
|
|
||||||
20622, 23162, 58594, 32421, 25928, 44611, 26318, 24455, 5798, 60202, 42126, 64928, 57832,
|
|
||||||
50888, 63256, 37562, 46334, 47985, 60836, 28132, 65535, 37800, 33150, 48768, 38457, 57249,
|
|
||||||
5734, 58587, 26294, 26653, 24519, 1,
|
|
||||||
];
|
|
||||||
|
|
||||||
extism_reset(&mut store, &mut instance);
|
|
||||||
for a in allocs {
|
|
||||||
println!("Alloc: {a}");
|
|
||||||
let n = extism_alloc(&mut store, &mut instance, a);
|
|
||||||
if n == 0 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
assert_eq!(a, extism_length(&mut store, &mut instance, n));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_failed_quickcheck2() {
|
|
||||||
let (mut store, mut instance) = init_kernel_test();
|
|
||||||
let allocs = [352054710, 1248853976, 2678441931, 14567928];
|
|
||||||
|
|
||||||
extism_reset(&mut store, &mut instance);
|
|
||||||
for a in allocs {
|
|
||||||
println!("Alloc: {a}");
|
|
||||||
let n = extism_alloc(&mut store, &mut instance, a);
|
|
||||||
if n == 0 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
assert_eq!(a, extism_length(&mut store, &mut instance, n));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
quickcheck! {
|
|
||||||
fn check_alloc(amounts: Vec<u16>) -> bool {
|
|
||||||
let (mut store, mut instance) = init_kernel_test();
|
|
||||||
let instance = &mut instance;
|
|
||||||
for a in amounts {
|
|
||||||
let ptr = extism_alloc(&mut store, instance, a as u64);
|
|
||||||
if ptr == 0 || ptr == u64::MAX {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if extism_length(&mut store, instance, ptr) != a as u64 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
quickcheck! {
|
|
||||||
fn check_large_alloc(amounts: Vec<u32>) -> bool {
|
|
||||||
let (mut store, mut instance) = init_kernel_test();
|
|
||||||
let instance = &mut instance;
|
|
||||||
for a in amounts {
|
|
||||||
let ptr = extism_alloc(&mut store, instance, a as u64);
|
|
||||||
if ptr == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
let len = extism_length_unsafe(&mut store, instance, ptr);
|
|
||||||
if len != a as u64 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
quickcheck! {
|
|
||||||
fn check_alloc_with_frees(amounts: Vec<u16>) -> bool {
|
|
||||||
let (mut store, mut instance) = init_kernel_test();
|
|
||||||
let instance = &mut instance;
|
|
||||||
let mut prev = 0;
|
|
||||||
for a in amounts {
|
|
||||||
let ptr = extism_alloc(&mut store, instance, a as u64);
|
|
||||||
if ptr == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if extism_length(&mut store, instance, ptr) != a as u64 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if a % 2 == 0 {
|
|
||||||
extism_free(&mut store, instance, ptr);
|
|
||||||
} else if a % 3 == 0 {
|
|
||||||
extism_free(&mut store, instance, prev);
|
|
||||||
}
|
|
||||||
|
|
||||||
prev = ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
quickcheck! {
|
|
||||||
fn check_large_alloc_with_frees(amounts: Vec<u32>) -> bool {
|
|
||||||
let (mut store, mut instance) = init_kernel_test();
|
|
||||||
let instance = &mut instance;
|
|
||||||
let mut prev = 0;
|
|
||||||
for a in amounts {
|
|
||||||
let ptr = extism_alloc(&mut store, instance, a as u64);
|
|
||||||
if ptr == 0 || ptr == u64::MAX {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if extism_length(&mut store, instance, ptr) != a as u64 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if a % 2 == 0 {
|
|
||||||
extism_free(&mut store, instance, ptr);
|
|
||||||
} else if a % 3 == 0 {
|
|
||||||
extism_free(&mut store, instance, prev);
|
|
||||||
}
|
|
||||||
|
|
||||||
prev = ptr;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
quickcheck! {
|
|
||||||
fn check_alloc_with_load_and_store(amounts: Vec<u16>) -> bool {
|
|
||||||
use rand::Rng;
|
|
||||||
let mut rng = rand::thread_rng();
|
|
||||||
let (mut store, mut instance) = init_kernel_test();
|
|
||||||
let instance = &mut instance;
|
|
||||||
for a in amounts {
|
|
||||||
let ptr = extism_alloc(&mut store, instance, a as u64);
|
|
||||||
if ptr == 0 || ptr == u64::MAX {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if extism_length(&mut store, instance, ptr) != a as u64 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for _ in 0..16 {
|
|
||||||
let i = rng.gen_range(ptr..ptr+a as u64);
|
|
||||||
extism_store_u8(&mut store, instance, i, i as u8);
|
|
||||||
if extism_load_u8(&mut store, instance, i as u64) != i as u8 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
use extism_manifest::MemoryOptions;
|
|
||||||
|
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use std::{io::Write, time::Instant};
|
use std::{io::Write, time::Instant};
|
||||||
|
|
||||||
@@ -41,12 +39,11 @@ pub struct Count {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_works() {
|
fn it_works() {
|
||||||
let log = tracing_subscriber::fmt()
|
tracing_subscriber::fmt()
|
||||||
.with_ansi(false)
|
.with_ansi(false)
|
||||||
.with_env_filter("extism=debug")
|
.with_env_filter("extism=debug")
|
||||||
.with_writer(std::fs::File::create("test.log").unwrap())
|
.with_writer(std::fs::File::create("test.log").unwrap())
|
||||||
.try_init()
|
.init();
|
||||||
.is_ok();
|
|
||||||
|
|
||||||
let wasm_start = Instant::now();
|
let wasm_start = Instant::now();
|
||||||
|
|
||||||
@@ -146,10 +143,8 @@ fn it_works() {
|
|||||||
println!("wasm function call (avg, N = {}): {:?}", num_tests, avg);
|
println!("wasm function call (avg, N = {}): {:?}", num_tests, avg);
|
||||||
|
|
||||||
// Check that log file was written to
|
// Check that log file was written to
|
||||||
if log {
|
let meta = std::fs::metadata("test.log").unwrap();
|
||||||
let meta = std::fs::metadata("test.log").unwrap();
|
assert!(meta.len() > 0);
|
||||||
assert!(meta.len() > 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -453,7 +448,7 @@ fn hello_world_user_data(
|
|||||||
_plugin: &mut CurrentPlugin,
|
_plugin: &mut CurrentPlugin,
|
||||||
inputs: &[Val],
|
inputs: &[Val],
|
||||||
outputs: &mut [Val],
|
outputs: &mut [Val],
|
||||||
user_data: UserData<std::sync::Arc<std::sync::Mutex<std::fs::File>>>,
|
user_data: UserData<std::fs::File>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let data = user_data.get()?;
|
let data = user_data.get()?;
|
||||||
let mut data = data.lock().unwrap();
|
let mut data = data.lock().unwrap();
|
||||||
@@ -470,8 +465,7 @@ fn test_userdata() {
|
|||||||
if path.exists() {
|
if path.exists() {
|
||||||
std::fs::remove_file(&path).unwrap();
|
std::fs::remove_file(&path).unwrap();
|
||||||
}
|
}
|
||||||
let file =
|
let file = std::fs::File::create(&path).unwrap();
|
||||||
std::sync::Arc::new(std::sync::Mutex::new(std::fs::File::create(&path).unwrap()));
|
|
||||||
let f = Function::new(
|
let f = Function::new(
|
||||||
"hello_world",
|
"hello_world",
|
||||||
[PTR],
|
[PTR],
|
||||||
@@ -578,112 +572,3 @@ fn test_disable_cache() {
|
|||||||
|
|
||||||
assert!(t < t1);
|
assert!(t < t1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_manifest_ptr_len() {
|
|
||||||
let manifest = serde_json::json!({
|
|
||||||
"wasm" : [
|
|
||||||
{
|
|
||||||
"data" : {
|
|
||||||
"ptr" : WASM_NO_FUNCTIONS.as_ptr() as u64,
|
|
||||||
"len" : WASM_NO_FUNCTIONS.len()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
let mut plugin = Plugin::new(manifest.to_string().as_bytes(), [], true).unwrap();
|
|
||||||
let output = plugin.call("count_vowels", "abc123").unwrap();
|
|
||||||
let count: serde_json::Value = serde_json::from_slice(output).unwrap();
|
|
||||||
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([
|
|
||||||
Wasm::Data {
|
|
||||||
data: br#"
|
|
||||||
(module
|
|
||||||
(import "wasi_snapshot_preview1" "random_get" (func $random (param i32 i32) (result i32)))
|
|
||||||
(import "extism:host/env" "alloc" (func $alloc (param i64) (result i64)))
|
|
||||||
(import "extism:host/user" "hello" (func $hello))
|
|
||||||
(global $counter (mut i32) (i32.const 0))
|
|
||||||
(func $start (export "_start")
|
|
||||||
(global.set $counter (i32.add (global.get $counter) (i32.const 1)))
|
|
||||||
)
|
|
||||||
(func (export "read_counter") (result i32)
|
|
||||||
(global.get $counter)
|
|
||||||
)
|
|
||||||
(start $start)
|
|
||||||
)
|
|
||||||
"#.to_vec(),
|
|
||||||
meta: WasmMetadata {
|
|
||||||
name: Some("commander".to_string()),
|
|
||||||
hash: None,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Wasm::Data {
|
|
||||||
data: br#"
|
|
||||||
(module
|
|
||||||
(import "commander" "_start" (func $commander_start))
|
|
||||||
(import "commander" "read_counter" (func $commander_read_counter (result i32)))
|
|
||||||
(import "extism:host/env" "store_u64" (func $store_u64 (param i64 i64)))
|
|
||||||
(import "extism:host/env" "alloc" (func $alloc (param i64) (result i64)))
|
|
||||||
(import "extism:host/user" "hello" (func $hello))
|
|
||||||
(import "extism:host/env" "output_set" (func $output_set (param i64 i64)))
|
|
||||||
(func (export "run") (result i32)
|
|
||||||
(local $output i64)
|
|
||||||
(local.set $output (call $alloc (i64.const 8)))
|
|
||||||
|
|
||||||
(call $commander_start)
|
|
||||||
(call $commander_start)
|
|
||||||
(call $commander_start)
|
|
||||||
(call $commander_start)
|
|
||||||
(call $hello)
|
|
||||||
(call $store_u64 (local.get $output) (i64.extend_i32_u (call $commander_read_counter)))
|
|
||||||
(call $output_set (local.get $output) (i64.const 8))
|
|
||||||
i32.const 0
|
|
||||||
)
|
|
||||||
)
|
|
||||||
"#.to_vec(),
|
|
||||||
meta: WasmMetadata {
|
|
||||||
name: Some("main".to_string()),
|
|
||||||
hash: None,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
let mut plugin = PluginBuilder::new(manifest)
|
|
||||||
.with_wasi(true)
|
|
||||||
.with_function("hello", [], [], UserData::new(()), |_, _, _, _| {
|
|
||||||
eprintln!("hello!");
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
for _ in 0..5 {
|
|
||||||
assert_eq!(plugin.call::<&str, i64>("run", "Hello, world!").unwrap(), 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user