Compare commits

..

1 Commits

Author SHA1 Message Date
Steve Manuel
ce67aecf61 ci: add multiple version test strategy for java sdk 2023-01-10 09:14:37 -07:00
134 changed files with 4405 additions and 9930 deletions

View File

@@ -17,7 +17,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
os: [ubuntu-latest, macos-latest, windows-latest]
rust:
- stable
steps:

View File

@@ -34,6 +34,6 @@ jobs:
- name: Test Go Host SDK
run: |
go version
LD_LIBRARY_PATH=/usr/local/lib go test
cd go
LD_LIBRARY_PATH=/usr/local/lib go run main.go | grep "Hello from Go!"
LD_LIBRARY_PATH=/usr/local/lib go run main.go
LD_LIBRARY_PATH=/usr/local/lib go test

View File

@@ -18,7 +18,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest]
version: [11, 17]
version: [8, 11, 17]
rust:
- stable
steps:
@@ -33,5 +33,10 @@ jobs:
- name: Test Java
run: |
cd java
cat pom.xml | sed 's/<java.version>17/<java.version>${{ matrix.version }}/' > updated.xml
mv updated.xml pom.xml
mvn --batch-mode -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn verify
#- name: Examine logs
# if: success() || failure()
# run: |
# cat /tmp/extism.log

View File

@@ -18,7 +18,6 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
zig_version: ["master"] # eventually use multiple versions once stable
rust:
- stable
steps:
@@ -27,8 +26,6 @@ jobs:
- uses: ./.github/actions/extism
- name: Setup Zig env
uses: goto-bus-stop/setup-zig@v2
with:
version: ${{ matrix.zig_version }}
- name: Test Zig Host SDK
run: |

View File

@@ -1,7 +1,7 @@
on:
workflow_dispatch:
name: Release Haskell SDK
name: Release Rust SDK
jobs:
release-sdks:
@@ -10,6 +10,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
- uses: cachix/haskell-release-action@v1
with:
- hackage-token: "${{ secrets.HACKAGE_TOKEN }}"

5
.gitignore vendored
View File

@@ -36,8 +36,3 @@ vendor
zig/zig-*
zig/example-out/
zig/*.log
java/*.iml
java/*.log
java/.idea
java/.DS_Store

View File

@@ -18,19 +18,21 @@ else
FEATURE_FLAGS=--features $(FEATURES)
endif
build:
cargo build --release $(FEATURE_FLAGS) --manifest-path libextism/Cargo.toml
.PHONY: build
lint:
cargo clippy --release --no-deps --manifest-path runtime/Cargo.toml
build:
cargo build --release $(FEATURE_FLAGS) --manifest-path libextism/Cargo.toml
debug:
RUSTFLAGS=-g $(MAKE) build
install:
mkdir -p $(DEST)/lib $(DEST)/include
install runtime/extism.h $(DEST)/include/extism.h
install target/release/libextism.$(SOEXT) $(DEST)/lib/libextism.$(SOEXT)
install runtime/extism.h $(DEST)/include
install target/release/libextism.$(SOEXT) $(DEST)/lib
uninstall:
rm -f $(DEST)/include/extism.h $(DEST)/lib/libextism.$(SOEXT)

View File

@@ -1,8 +1,6 @@
### _Welcome!_
**Please note:** This project still under active development and APIs may change until we hit v1.0.
If you're interested in working on or building with Extism, please join our [Discord](https://discord.gg/cx3usBCWnc) and let us know - we are happy to help get you started.
**Please note:** this project still under active development. It's usable, but expect some rough edges while work is underway. If you're interested in working on or building with Extism, please join our [Discord](https://discord.gg/cx3usBCWnc) and let us know - we are happy to help get you started.
[![Discord](https://img.shields.io/discord/1011124058408112148?color=%23404eed&label=Community%20Chat&logo=Discord&logoColor=%23404eed)](https://discord.gg/cx3usBCWnc)
@@ -64,4 +62,5 @@ Extism is an open-source product from the team at:
</p>
_Reach out and tell us what you're building! We'd love to help._

View File

@@ -1,5 +1,5 @@
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'jsdom',
testEnvironment: 'node',
};

5127
browser/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -23,9 +23,7 @@
"devDependencies": {
"@types/jest": "^29.2.2",
"esbuild": "^0.15.13",
"esbuild-jest": "^0.5.0",
"jest": "^29.2.2",
"jest-environment-jsdom": "^29.3.1",
"prettier": "^2.7.1",
"ts-jest": "^29.0.3",
"tslint": "^6.1.3",

View File

@@ -8,17 +8,16 @@ function parse(bytes: Uint8Array): any {
describe('', () => {
it('can load and call a plugin', async () => {
// const data = fs.readFileSync(path.join(__dirname, '..', 'data', 'code.wasm'));
// const ctx = new ExtismContext();
// const plugin = await ctx.newPlugin({ wasm: [{ data: data }] });
// const functions = await plugin.getExports();
// expect(Object.keys(functions).filter((x) => !x.startsWith('__') && x !== 'memory')).toEqual(['count_vowels']);
// let output = await plugin.call('count_vowels', 'this is a test');
// expect(parse(output)).toEqual({ count: 4 });
// output = await plugin.call('count_vowels', 'this is a test again');
// expect(parse(output)).toEqual({ count: 7 });
// output = await plugin.call('count_vowels', 'this is a test thrice');
// expect(parse(output)).toEqual({ count: 6 });
expect(true).toEqual(true)
const data = fs.readFileSync(path.join(__dirname, '..', 'data', 'code.wasm'));
const ctx = new ExtismContext();
const plugin = await ctx.newPlugin({ wasm: [{ data: data }] });
const functions = await plugin.getExports();
expect(Object.keys(functions).filter((x) => !x.startsWith('__') && x !== 'memory')).toEqual(['count_vowels']);
let output = await plugin.call('count_vowels', 'this is a test');
expect(parse(output)).toEqual({ count: 4 });
output = await plugin.call('count_vowels', 'this is a test again');
expect(parse(output)).toEqual({ count: 7 });
output = await plugin.call('count_vowels', 'this is a test thrice');
expect(parse(output)).toEqual({ count: 6 });
});
});

View File

@@ -76,9 +76,6 @@ export default class ExtismPlugin {
env: environment
};
this.module = await WebAssembly.instantiate(this.moduleData, env);
if (this.module.instance.exports._start) {
wasi.start(this.module.instance);
}
return this.module;
}

View File

@@ -1,13 +1,13 @@
{
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"strict": true,
"skipLibCheck": true,
"allowJs": true
},
"exclude": ["node_modules", "dist", "**/*.test.ts"]
}
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"strict": true,
"skipLibCheck": true,
"allowJs": true
},
"exclude": ["node_modules", "dist", "**/*.test.ts"]
}

View File

@@ -1,2 +1,2 @@
build:
clang -g -o main main.c -lextism -L .
clang -o main main.c -lextism -L .

View File

@@ -9,21 +9,6 @@
#include <sys/stat.h>
#include <unistd.h>
void hello_world(ExtismCurrentPlugin *plugin, const ExtismVal *inputs,
uint64_t n_inputs, ExtismVal *outputs, uint64_t n_outputs,
void *data) {
puts("Hello from C!");
puts(data);
ExtismSize ptr_offs = inputs[0].v.i64;
uint8_t *buf = extism_current_plugin_memory(plugin) + ptr_offs;
uint64_t length = extism_current_plugin_memory_length(plugin, ptr_offs);
fwrite(buf, length, 1, stdout);
fputc('\n', stdout);
outputs[0].v.i64 = inputs[0].v.i64;
}
uint8_t *read_file(const char *filename, size_t *len) {
FILE *fp = fopen(filename, "rb");
@@ -56,18 +41,13 @@ int main(int argc, char *argv[]) {
ExtismContext *ctx = extism_context_new();
size_t len = 0;
uint8_t *data = read_file("../wasm/code-functions.wasm", &len);
ExtismValType inputs[] = {I64};
ExtismValType outputs[] = {I64};
ExtismFunction *f = extism_function_new("hello_world", inputs, 1, outputs, 1,
hello_world, "Hello, again!", NULL);
ExtismPlugin plugin =
extism_plugin_new(ctx, data, len, (const ExtismFunction **)&f, 1, true);
uint8_t *data = read_file("../wasm/code.wasm", &len);
ExtismPlugin plugin = extism_plugin_new(ctx, data, len, false);
free(data);
if (plugin < 0) {
puts(extism_error(ctx, -1));
exit(1);
}
assert(extism_plugin_call(ctx, plugin, "count_vowels", (uint8_t *)argv[1],
strlen(argv[1])) == 0);
ExtismSize out_len = extism_plugin_output_length(ctx, plugin);
@@ -76,7 +56,6 @@ int main(int argc, char *argv[]) {
write(STDOUT_FILENO, "\n", 1);
extism_plugin_free(ctx, plugin);
extism_function_free(f);
extism_context_free(ctx);
return 0;
}

View File

@@ -1,16 +1,15 @@
FLAGS=`pkg-config --cflags --libs jsoncpp gtest` -lextism -lpthread
build-example:
$(CXX) -std=c++14 -o example -I. example.cpp $(FLAGS)
$(CXX) -std=c++11 -o example -I. example.cpp $(FLAGS)
.PHONY: example
example: build-example
./example
build-test:
$(CXX) -std=c++14 -o test/test -I. test/test.cpp $(FLAGS)
$(CXX) -std=c++11 -o test/test -I. test/test.cpp $(FLAGS)
.PHONY: test
test: build-test
cd test && ./test

View File

@@ -14,27 +14,10 @@ std::vector<uint8_t> read(const char *filename) {
}
int main(int argc, char *argv[]) {
auto wasm = read("../wasm/code-functions.wasm");
auto wasm = read("../wasm/code.wasm");
Context context = Context();
std::string tmp = "Testing";
// A lambda can be used as a host function
auto hello_world = [&tmp](CurrentPlugin plugin,
const std::vector<Val> &inputs,
std::vector<Val> &outputs, void *user_data) {
std::cout << "Hello from C++" << std::endl;
std::cout << (const char *)user_data << std::endl;
std::cout << tmp << std::endl;
outputs[0].v = inputs[0].v;
};
std::vector<Function> functions = {
Function("hello_world", {ValType::I64}, {ValType::I64}, hello_world,
(void *)"Hello again!",
[](void *x) { std::cout << "Free user data" << std::endl; }),
};
Plugin plugin = context.plugin(wasm, true, functions);
Plugin plugin = context.plugin(wasm);
const char *input = argc > 1 ? argv[1] : "this is a test";
ExtismSize length = strlen(input);

View File

@@ -1,10 +1,7 @@
#pragma once
#include <cstring>
#include <functional>
#include <map>
#include <memory>
#include <stdexcept>
#include <string>
#include <vector>
@@ -23,62 +20,27 @@ extern "C" {
namespace extism {
typedef std::map<std::string, std::string> Config;
template <typename T> class ManifestKey {
bool is_set = false;
public:
T value;
ManifestKey(T x, bool is_set = false) : is_set(is_set) { value = x; }
void set(T x) {
value = x;
is_set = true;
}
bool empty() const { return is_set == false; }
};
class Wasm {
std::string _path;
std::string _url;
// TODO: add base64 encoded raw data
ManifestKey<std::string> _hash =
ManifestKey<std::string>(std::string(), false);
public:
// Create Wasm pointing to a path
static Wasm path(std::string s, std::string hash = std::string()) {
Wasm w;
w._path = s;
if (!hash.empty()) {
w._hash.set(hash);
}
return w;
}
// Create Wasm pointing to a URL
static Wasm url(std::string s, std::string hash = std::string()) {
Wasm w;
w._url = s;
if (!hash.empty()) {
w._hash.set(hash);
}
return w;
}
std::string path;
std::string url;
// TODO: add base64 encoded raw data
std::string hash;
#ifndef EXTISM_NO_JSON
Json::Value json() const {
Json::Value doc;
if (!this->_path.empty()) {
doc["path"] = this->_path;
} else if (!this->_url.empty()) {
doc["url"] = this->_url;
if (!this->path.empty()) {
doc["path"] = this->path;
}
if (!this->_hash.empty()) {
doc["hash"] = this->_hash.value;
if (!this->url.empty()) {
doc["url"] = this->url;
}
if (!this->hash.empty()) {
doc["hash"] = this->hash;
}
return doc;
@@ -90,23 +52,18 @@ class Manifest {
public:
Config config;
std::vector<Wasm> wasm;
ManifestKey<std::vector<std::string>> allowed_hosts;
ManifestKey<std::map<std::string, std::string>> allowed_paths;
ManifestKey<uint64_t> timeout_ms;
std::vector<std::string> allowed_hosts;
std::map<std::string, std::string> allowed_paths;
uint64_t timeout_ms;
// Empty manifest
Manifest()
: timeout_ms(0, false), allowed_hosts(std::vector<std::string>(), false),
allowed_paths(std::map<std::string, std::string>(), false) {}
Manifest() : timeout_ms(30000) {}
// Create manifest with a single Wasm from a path
static Manifest path(std::string s, std::string hash = std::string()) {
Manifest m;
m.add_wasm_path(s, hash);
return m;
}
// Create manifest with a single Wasm from a URL
static Manifest url(std::string s, std::string hash = std::string()) {
Manifest m;
m.add_wasm_url(s, hash);
@@ -135,7 +92,7 @@ public:
if (!this->allowed_hosts.empty()) {
Json::Value h;
for (auto s : this->allowed_hosts.value) {
for (auto s : this->allowed_hosts) {
h.append(s);
}
doc["allowed_hosts"] = h;
@@ -143,63 +100,54 @@ public:
if (!this->allowed_paths.empty()) {
Json::Value h;
for (auto k : this->allowed_paths.value) {
for (auto k : this->allowed_paths) {
h[k.first] = k.second;
}
doc["allowed_paths"] = h;
}
if (!this->timeout_ms.empty()) {
doc["timeout_ms"] = Json::Value(this->timeout_ms.value);
}
doc["timeout_ms"] = Json::Value(this->timeout_ms);
Json::FastWriter writer;
return writer.write(doc);
}
#endif
// Add Wasm from path
void add_wasm_path(std::string s, std::string hash = std::string()) {
Wasm w = Wasm::path(s, hash);
Wasm w;
w.path = s;
w.hash = hash;
this->wasm.push_back(w);
}
// Add Wasm from URL
void add_wasm_url(std::string u, std::string hash = std::string()) {
Wasm w = Wasm::url(u, hash);
Wasm w;
w.url = u;
w.hash = hash;
this->wasm.push_back(w);
}
// Add host to allowed hosts
void allow_host(std::string host) {
if (this->allowed_hosts.empty()) {
this->allowed_hosts.set(std::vector<std::string>{});
}
this->allowed_hosts.value.push_back(host);
}
void allow_host(std::string host) { this->allowed_hosts.push_back(host); }
// Add path to allowed paths
void allow_path(std::string src, std::string dest = std::string()) {
if (this->allowed_paths.empty()) {
this->allowed_paths.set(std::map<std::string, std::string>{});
}
if (dest.empty()) {
dest = src;
}
this->allowed_paths.value[src] = dest;
this->allowed_paths[src] = dest;
}
// Set timeout
void set_timeout_ms(uint64_t ms) { this->timeout_ms = ms; }
// Set config key/value
void set_config(std::string k, std::string v) { this->config[k] = v; }
};
class Error : public std::runtime_error {
class Error : public std::exception {
private:
std::string message;
public:
Error(std::string msg) : std::runtime_error(msg) {}
Error(std::string msg) : message(msg) {}
const char *what() { return message.c_str(); }
};
class Buffer {
@@ -218,140 +166,14 @@ public:
}
};
typedef ExtismValType ValType;
typedef ExtismValUnion ValUnion;
typedef ExtismVal Val;
class CurrentPlugin {
ExtismCurrentPlugin *pointer;
public:
CurrentPlugin(ExtismCurrentPlugin *p) : pointer(p) {}
uint8_t *memory() { return extism_current_plugin_memory(this->pointer); }
ExtismSize memory_length(uint64_t offs) {
return extism_current_plugin_memory_length(this->pointer, offs);
}
uint64_t alloc(ExtismSize size) {
return extism_current_plugin_memory_alloc(this->pointer, size);
}
void free(uint64_t offs) {
extism_current_plugin_memory_free(this->pointer, offs);
}
void returnString(Val &output, const std::string &s) {
this->returnBytes(output, (const uint8_t *)s.c_str(), s.size());
}
void returnBytes(Val &output, const uint8_t *bytes, size_t len) {
auto offs = this->alloc(len);
memcpy(this->memory() + offs, bytes, len);
output.v.i64 = offs;
}
uint8_t *inputBytes(Val &inp, size_t *length = nullptr) {
if (inp.t != ValType::I64) {
return nullptr;
}
if (length != nullptr) {
*length = this->memory_length(inp.v.i64);
}
return this->memory() + inp.v.i64;
}
std::string inputString(Val &inp) {
size_t length = 0;
char *buf = (char *)this->inputBytes(inp, &length);
return std::string(buf, length);
}
};
typedef std::function<void(CurrentPlugin, const std::vector<Val> &,
std::vector<Val> &, void *user_data)>
FunctionType;
struct UserData {
FunctionType func;
void *user_data = NULL;
std::function<void(void *)> free_user_data;
};
static void function_callback(ExtismCurrentPlugin *plugin,
const ExtismVal *inputs, ExtismSize n_inputs,
ExtismVal *outputs, ExtismSize n_outputs,
void *user_data) {
UserData *data = (UserData *)user_data;
const std::vector<Val> inp(inputs, inputs + n_inputs);
std::vector<Val> outp(outputs, outputs + n_outputs);
data->func(CurrentPlugin(plugin), inp, outp, data->user_data);
for (ExtismSize i = 0; i < n_outputs; i++) {
outputs[i] = outp[i];
}
}
static void free_user_data(void *user_data) {
UserData *data = (UserData *)user_data;
if (data->user_data != NULL && data->free_user_data != NULL) {
data->free_user_data(data->user_data);
}
}
class Function {
std::shared_ptr<ExtismFunction> func;
std::string name;
UserData user_data;
public:
Function(std::string name, const std::vector<ValType> inputs,
const std::vector<ValType> outputs, FunctionType f,
void *user_data = NULL, std::function<void(void *)> free = nullptr)
: name(name) {
this->user_data.func = f;
this->user_data.user_data = user_data;
this->user_data.free_user_data = free;
auto ptr = extism_function_new(
this->name.c_str(), inputs.data(), inputs.size(), outputs.data(),
outputs.size(), function_callback, &this->user_data, free_user_data);
this->func = std::shared_ptr<ExtismFunction>(ptr, extism_function_free);
}
void set_namespace(std::string s) {
extism_function_set_namespace(this->func.get(), s.c_str());
}
Function(const Function &f) { this->func = f.func; }
ExtismFunction *get() { return this->func.get(); }
};
class CancelHandle {
const ExtismCancelHandle *handle;
public:
CancelHandle(const ExtismCancelHandle *x) : handle(x){};
bool cancel() { return extism_plugin_cancel(this->handle); }
};
class Plugin {
std::shared_ptr<ExtismContext> context;
ExtismPlugin plugin;
std::vector<Function> functions;
public:
// Create a new plugin
Plugin(std::shared_ptr<ExtismContext> ctx, const uint8_t *wasm,
ExtismSize length, bool with_wasi = false,
std::vector<Function> functions = std::vector<Function>())
: functions(functions) {
std::vector<const ExtismFunction *> ptrs;
for (auto i : this->functions) {
ptrs.push_back(i.get());
}
this->plugin = extism_plugin_new(ctx.get(), wasm, length, ptrs.data(),
ptrs.size(), with_wasi);
ExtismSize length, bool with_wasi = false) {
this->plugin = extism_plugin_new(ctx.get(), wasm, length, with_wasi);
if (this->plugin < 0) {
const char *err = extism_error(ctx.get(), -1);
throw Error(err == nullptr ? "Unable to load plugin" : err);
@@ -359,24 +181,12 @@ public:
this->context = ctx;
}
CancelHandle cancel_handle() {
return CancelHandle(
extism_plugin_cancel_handle(this->context.get(), this->id()));
}
#ifndef EXTISM_NO_JSON
// Create a new plugin from Manifest
Plugin(std::shared_ptr<ExtismContext> ctx, const Manifest &manifest,
bool with_wasi = false, std::vector<Function> functions = {}) {
std::vector<const ExtismFunction *> ptrs;
for (auto i : this->functions) {
ptrs.push_back(i.get());
}
bool with_wasi = false) {
auto buffer = manifest.json();
this->plugin =
extism_plugin_new(ctx.get(), (const uint8_t *)buffer.c_str(),
buffer.size(), ptrs.data(), ptrs.size(), with_wasi);
this->plugin = extism_plugin_new(ctx.get(), (const uint8_t *)buffer.c_str(),
buffer.size(), with_wasi);
if (this->plugin < 0) {
const char *err = extism_error(ctx.get(), -1);
throw Error(err == nullptr ? "Unable to load plugin from manifest" : err);
@@ -394,15 +204,9 @@ public:
ExtismContext *get_context() const { return this->context.get(); }
void update(const uint8_t *wasm, size_t length, bool with_wasi = false,
std::vector<Function> functions = {}) {
this->functions = functions;
std::vector<const ExtismFunction *> ptrs;
for (auto i : this->functions) {
ptrs.push_back(i.get());
}
void update(const uint8_t *wasm, size_t length, bool with_wasi = false) {
bool b = extism_plugin_update(this->context.get(), this->plugin, wasm,
length, ptrs.data(), ptrs.size(), with_wasi);
length, with_wasi);
if (!b) {
const char *err = extism_error(this->context.get(), -1);
throw Error(err == nullptr ? "Unable to update plugin" : err);
@@ -410,17 +214,11 @@ public:
}
#ifndef EXTISM_NO_JSON
void update(const Manifest &manifest, bool with_wasi = false,
std::vector<Function> functions = {}) {
this->functions = functions;
std::vector<const ExtismFunction *> ptrs;
for (auto i : this->functions) {
ptrs.push_back(i.get());
}
void update(const Manifest &manifest, bool with_wasi = false) {
auto buffer = manifest.json();
bool b = extism_plugin_update(
this->context.get(), this->plugin, (const uint8_t *)buffer.c_str(),
buffer.size(), ptrs.data(), ptrs.size(), with_wasi);
bool b = extism_plugin_update(this->context.get(), this->plugin,
(const uint8_t *)buffer.c_str(),
buffer.size(), with_wasi);
if (!b) {
const char *err = extism_error(this->context.get(), -1);
throw Error(err == nullptr ? "Unable to update plugin" : err);
@@ -453,7 +251,6 @@ public:
this->config(json.c_str(), json.size());
}
// Call a plugin
Buffer call(const std::string &func, const uint8_t *input,
ExtismSize input_length) const {
int32_t rc = extism_plugin_call(this->context.get(), this->plugin,
@@ -474,19 +271,15 @@ public:
return Buffer(ptr, length);
}
// Call a plugin function with std::vector<uint8_t> input
Buffer call(const std::string &func,
const std::vector<uint8_t> &input) const {
return this->call(func, input.data(), input.size());
}
// Call a plugin function with string input
Buffer call(const std::string &func,
const std::string &input = std::string()) const {
Buffer call(const std::string &func, const std::string &input) const {
return this->call(func, (const uint8_t *)input.c_str(), input.size());
}
// Returns true if the specified function exists
bool function_exists(const std::string &func) const {
return extism_plugin_function_exists(this->context.get(), this->plugin,
func.c_str());
@@ -497,49 +290,38 @@ class Context {
public:
std::shared_ptr<ExtismContext> pointer;
// Create a new context;
Context() {
this->pointer = std::shared_ptr<ExtismContext>(extism_context_new(),
extism_context_free);
}
// Create plugin from uint8_t*
Plugin plugin(const uint8_t *wasm, size_t length, bool with_wasi = false,
std::vector<Function> functions = {}) const {
return Plugin(this->pointer, wasm, length, with_wasi, functions);
Plugin plugin(const uint8_t *wasm, size_t length,
bool with_wasi = false) const {
return Plugin(this->pointer, wasm, length, with_wasi);
}
// Create plugin from std::string
Plugin plugin(const std::string &str, bool with_wasi = false,
std::vector<Function> functions = {}) const {
Plugin plugin(const std::string &str, bool with_wasi = false) const {
return Plugin(this->pointer, (const uint8_t *)str.c_str(), str.size(),
with_wasi, functions);
with_wasi);
}
// Create plugin from uint8_t vector
Plugin plugin(const std::vector<uint8_t> &data, bool with_wasi = false,
std::vector<Function> functions = {}) const {
return Plugin(this->pointer, data.data(), data.size(), with_wasi,
functions);
Plugin plugin(const std::vector<uint8_t> &data,
bool with_wasi = false) const {
return Plugin(this->pointer, data.data(), data.size(), with_wasi);
}
#ifndef EXTISM_NO_JSON
// Create plugin from Manifest
Plugin plugin(const Manifest &manifest, bool with_wasi = false,
std::vector<Function> functions = {}) const {
return Plugin(this->pointer, manifest, with_wasi, functions);
Plugin plugin(const Manifest &manifest, bool with_wasi = false) const {
return Plugin(this->pointer, manifest, with_wasi);
}
#endif
// Remove all plugins
void reset() { extism_context_reset(this->pointer.get()); }
};
// Set global log file for plugins
inline bool set_log_file(const char *filename, const char *level) {
return extism_log_file(filename, level);
}
// Get libextism version
inline std::string version() { return std::string(extism_version()); }
} // namespace extism

View File

@@ -10,8 +10,6 @@ std::vector<uint8_t> read(const char *filename) {
std::istreambuf_iterator<char>());
}
const std::string code = "../../wasm/code.wasm";
namespace {
using namespace extism;
@@ -22,7 +20,7 @@ TEST(Context, Basic) {
TEST(Plugin, Manifest) {
Context context;
Manifest manifest = Manifest::path(code);
Manifest manifest = Manifest::path("code.wasm");
manifest.set_config("a", "1");
ASSERT_NO_THROW(Plugin plugin = context.plugin(manifest));
@@ -40,7 +38,7 @@ TEST(Plugin, BadManifest) {
TEST(Plugin, Bytes) {
Context context;
auto wasm = read(code.c_str());
auto wasm = read("code.wasm");
ASSERT_NO_THROW(Plugin plugin = context.plugin(wasm));
Plugin plugin = context.plugin(wasm);
@@ -50,7 +48,7 @@ TEST(Plugin, Bytes) {
TEST(Plugin, UpdateConfig) {
Context context;
auto wasm = read(code.c_str());
auto wasm = read("code.wasm");
Plugin plugin = context.plugin(wasm);
Config config;
@@ -60,34 +58,13 @@ TEST(Plugin, UpdateConfig) {
TEST(Plugin, FunctionExists) {
Context context;
auto wasm = read(code.c_str());
auto wasm = read("code.wasm");
Plugin plugin = context.plugin(wasm);
ASSERT_FALSE(plugin.function_exists("bad_function"));
ASSERT_TRUE(plugin.function_exists("count_vowels"));
}
TEST(Plugin, HostFunction) {
Context context;
auto wasm = read("../../wasm/code-functions.wasm");
auto t = std::vector<ValType>{ValType::I64};
Function hello_world =
Function("hello_world", t, t,
[](CurrentPlugin plugin, const std::vector<Val> &params,
std::vector<Val> &results, void *user_data) {
auto offs = plugin.alloc(4);
memcpy(plugin.memory() + offs, "test", 4);
results[0].v.i64 = (int64_t)offs;
});
auto functions = std::vector<Function>{
hello_world,
};
Plugin plugin = context.plugin(wasm, true, functions);
auto buf = plugin.call("count_vowels", "aaa");
ASSERT_EQ(buf.length, 4);
ASSERT_EQ((std::string)buf, "test");
}
}; // namespace
int main(int argc, char **argv) {

View File

@@ -8,7 +8,7 @@
<PropertyGroup>
<PackageId>Extism.runtime.win-x64</PackageId>
<Version>0.4.0</Version>
<Version>0.2.0</Version>
<Authors>Extism Contributors</Authors>
<Description>Internal implementation package for Extism to work on Windows x64</Description>
<Tags>extism, wasm, plugin</Tags>

View File

@@ -38,7 +38,7 @@ public class Context : IDisposable
{
fixed (byte* wasmPtr = wasm)
{
var plugin = LibExtism.extism_plugin_new(NativeHandle, wasmPtr, wasm.Length, null, 0, withWasi);
var plugin = LibExtism.extism_plugin_new(NativeHandle, wasmPtr, wasm.Length, withWasi);
return new Plugin(this, plugin);
}
}

View File

@@ -10,7 +10,7 @@
<PropertyGroup>
<PackageId>Extism.Sdk</PackageId>
<Version>0.3.0</Version>
<Version>0.2.0</Version>
<Authors>Extism Contributors</Authors>
<Description>Extism SDK that allows hosting Extism plugins in .NET apps.</Description>
<Tags>extism, wasm, plugin</Tags>

View File

@@ -27,12 +27,10 @@ internal static class LibExtism
/// <param name="context">Pointer to the context the plugin will be associated with.</param>
/// <param name="wasm">A WASM module (wat or wasm) or a JSON encoded manifest.</param>
/// <param name="wasmSize">The length of the `wasm` parameter.</param>
/// <param name="functions">Array of host function pointers.</param>
/// <param name="nFunctions">Number of host functions.</param>
/// <param name="withWasi">Enables/disables WASI.</param>
/// <returns></returns>
[DllImport("extism")]
unsafe public static extern IntPtr extism_plugin_new(IntPtr context, byte* wasm, int wasmSize, IntPtr *functions, int nFunctions, bool withWasi);
unsafe public static extern IntPtr extism_plugin_new(IntPtr context, byte* wasm, int wasmSize, bool withWasi);
/// <summary>
/// Update a plugin, keeping the existing ID.
@@ -43,12 +41,10 @@ internal static class LibExtism
/// <param name="plugin">Pointer to the plugin you want to update.</param>
/// <param name="wasm">A WASM module (wat or wasm) or a JSON encoded manifest.</param>
/// <param name="wasmLength">The length of the `wasm` parameter.</param>
/// <param name="functions">Array of host function pointers.</param>
/// <param name="nFunctions">Number of host functions.</param>
/// <param name="withWasi">Enables/disables WASI.</param>
/// <returns></returns>
[DllImport("extism")]
unsafe public static extern bool extism_plugin_update(IntPtr context, IntPtr plugin, byte* wasm, int wasmLength, IntPtr *functions, int nFunctions, bool withWasi);
unsafe public static extern bool extism_plugin_update(IntPtr context, IntPtr plugin, byte* wasm, int wasmLength, bool withWasi);
/// <summary>
/// Remove a plugin from the registry and free associated memory.

View File

@@ -35,7 +35,7 @@ public class Plugin : IDisposable
fixed (byte* wasmPtr = wasm)
{
return LibExtism.extism_plugin_update(_context.NativeHandle, NativeHandle, wasmPtr, wasm.Length, null, 0, withWasi);
return LibExtism.extism_plugin_update(_context.NativeHandle, NativeHandle, wasmPtr, wasm.Length, withWasi);
}
}

View File

@@ -21,13 +21,12 @@
(description "Bindings to Extism, the universal plugin system")
(depends
(ocaml (>= 4.14.1))
dune
(ctypes (>= 0.18.0))
(dune (>= 3.2))
(ctypes-foreign (>= 0.18.0))
(bigstringaf (>= 0.9.0))
(ppx_yojson_conv (>= v0.15.0))
(ppx_yojson_conv (>= 0.15.0))
extism-manifest
(ppx_inline_test (>= v0.15.0))
(ppx_inline_test (>= 0.15.0))
(cmdliner (>= 1.1.1))
)
(tags
@@ -36,12 +35,11 @@
(package
(name extism-manifest)
(synopsis "Extism manifest bindings")
(description "Bindings to the Extism manifest format")
(description "Bindings to Extism, the universal plugin system")
(depends
(ocaml (>= 4.14.1))
dune
(ppx_yojson_conv (>= v0.15.0))
(ppx_inline_test (>= v0.15.0))
(dune (>= 3.2))
(ppx_yojson_conv (>= 0.15.0))
(base64 (>= 3.5.0))
)
(tags

View File

@@ -1,31 +0,0 @@
defmodule Extism.CancelHandle do
@moduledoc """
A CancelHandle is a handle generated by a plugin that allows it to be cancelled from another
thread while running.
"""
defstruct [
# The actual NIF Resource. PluginIndex and the context
handle: nil,
]
def wrap_resource(handle) do
%__MODULE__{
handle: handle
}
end
@doc """
Cancel plugin execution
"""
def cancel(handle) do
Extism.Native.plugin_cancel(handle.handle)
end
end
defimpl Inspect, for: Extim.CancelHandle do
import Inspect.Algebra
def inspect(dict, opts) do
concat(["#Extism.CancelHandle<", to_doc(dict.handle, opts), ">"])
end
end

View File

@@ -16,8 +16,6 @@ defmodule Extism.Native do
def plugin_has_function(_ctx, _plugin_id, _function_name), do: error()
def plugin_free(_ctx, _plugin_id), do: error()
def set_log_file(_filename, _level), do: error()
def plugin_cancel_handle(_ctx, _plugin_id), do: error()
def plugin_cancel(_handle), do: error()
defp error, do: :erlang.nif_error(:nif_not_loaded)
end

View File

@@ -4,7 +4,7 @@ defmodule Extism.MixProject do
def project do
[
app: :extism,
version: "0.3.0",
version: "0.1.0",
elixir: "~> 1.12",
start_permanent: Mix.env() == :prod,
deps: deps(),
@@ -23,7 +23,7 @@ defmodule Extism.MixProject do
defp deps do
[
{:rustler, "~> 0.27.0"},
{:rustler, "~> 0.26.0"},
{:json, "~> 1.4"},
{:ex_doc, "~> 0.21", only: :dev, runtime: false}
]

View File

@@ -1,12 +1,12 @@
%{
"earmark_parser": {:hex, :earmark_parser, "1.4.31", "a93921cdc6b9b869f519213d5bc79d9e218ba768d7270d46fdcf1c01bacff9e2", [:mix], [], "hexpm", "317d367ee0335ef037a87e46c91a2269fef6306413f731e8ec11fc45a7efd059"},
"ex_doc": {:hex, :ex_doc, "0.29.2", "dfa97532ba66910b2a3016a4bbd796f41a86fc71dd5227e96f4c8581fdf0fdf0", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "6b5d7139eda18a753e3250e27e4a929f8d2c880dd0d460cb9986305dea3e03af"},
"earmark_parser": {:hex, :earmark_parser, "1.4.29", "149d50dcb3a93d9f3d6f3ecf18c918fb5a2d3c001b5d3305c926cddfbd33355b", [:mix], [], "hexpm", "4902af1b3eb139016aed210888748db8070b8125c2342ce3dcae4f38dcc63503"},
"ex_doc": {:hex, :ex_doc, "0.29.1", "b1c652fa5f92ee9cf15c75271168027f92039b3877094290a75abcaac82a9f77", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "b7745fa6374a36daf484e2a2012274950e084815b936b1319aeebcf7809574f6"},
"jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"},
"json": {:hex, :json, "1.4.1", "8648f04a9439765ad449bc56a3ff7d8b11dd44ff08ffcdefc4329f7c93843dfa", [:mix], [], "hexpm", "9abf218dbe4ea4fcb875e087d5f904ef263d012ee5ed21d46e9dbca63f053d16"},
"makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"},
"makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
"nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"},
"rustler": {:hex, :rustler, "0.27.0", "53ffe86586fd1a2ea60ad07f1506962914eb669dba26c23010cf672662ec8d64", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:toml, "~> 0.6", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "d7f5ccaec6e7a96f700330898ff2e9d48818e40789fd2951ba41ecf457986e92"},
"toml": {:hex, :toml, "0.7.0", "fbcd773caa937d0c7a02c301a1feea25612720ac3fa1ccb8bfd9d30d822911de", [:mix], [], "hexpm", "0690246a2478c1defd100b0c9b89b4ea280a22be9a7b313a8a058a2408a2fa70"},
"rustler": {:hex, :rustler, "0.26.0", "06a2773d453ee3e9109efda643cf2ae633dedea709e2455ac42b83637c9249bf", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:toml, "~> 0.6", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "42961e9d2083d004d5a53e111ad1f0c347efd9a05cb2eb2ffa1d037cdc74db91"},
"toml": {:hex, :toml, "0.6.2", "38f445df384a17e5d382befe30e3489112a48d3ba4c459e543f748c2f25dd4d1", [:mix], [], "hexpm", "d013e45126d74c0c26a38d31f5e8e9b83ea19fc752470feb9a86071ca5a672fa"},
}

View File

@@ -1,6 +1,6 @@
[package]
name = "extism_nif"
version = "0.2.0"
version = "0.1.0"
edition = "2021"
authors = ["Benjamin Eckel <bhelx@simst.im>"]
@@ -10,6 +10,6 @@ path = "src/lib.rs"
crate-type = ["cdylib"]
[dependencies]
rustler = "0.27.0"
extism = { version = "0.3.0", path = "../../../rust", package = "extism" }
rustler = "0.26.0"
extism = { version = "0.1.0", path = "../../../rust" }
log = "0.4"

View File

@@ -17,24 +17,22 @@ mod atoms {
struct ExtismContext {
ctx: RwLock<Context>,
}
unsafe impl Sync for ExtismContext {}
unsafe impl Send for ExtismContext {}
struct ExtismCancelHandle {
handle: RwLock<extism::CancelHandle>,
}
unsafe impl Sync for ExtismCancelHandle {}
unsafe impl Send for ExtismCancelHandle {}
fn load(env: Env, _: Term) -> bool {
rustler::resource!(ExtismContext, env);
rustler::resource!(ExtismCancelHandle, env);
true
}
fn to_rustler_error(extism_error: extism::Error) -> rustler::Error {
rustler::Error::Term(Box::new(extism_error.to_string()))
match extism_error {
extism::Error::UnableToLoadPlugin(msg) => rustler::Error::Term(Box::new(msg)),
extism::Error::Message(msg) => rustler::Error::Term(Box::new(msg)),
extism::Error::Json(json_err) => rustler::Error::Term(Box::new(json_err.to_string())),
extism::Error::Runtime(e) => rustler::Error::Term(Box::new(e.to_string())),
}
}
#[rustler::nif]
@@ -52,7 +50,7 @@ fn context_reset(ctx: ResourceArc<ExtismContext>) {
#[rustler::nif]
fn context_free(ctx: ResourceArc<ExtismContext>) {
let context = ctx.ctx.read().unwrap();
let context = &ctx.ctx.read().unwrap();
std::mem::drop(context)
}
@@ -63,7 +61,7 @@ fn plugin_new_with_manifest(
wasi: bool,
) -> Result<i32, rustler::Error> {
let context = &ctx.ctx.write().unwrap();
let result = match Plugin::new(context, manifest_payload, [], wasi) {
let result = match Plugin::new(context, manifest_payload, wasi) {
Err(e) => Err(to_rustler_error(e)),
Ok(plugin) => {
let plugin_id = plugin.as_i32();
@@ -87,7 +85,7 @@ fn plugin_call(
let mut plugin = unsafe { Plugin::from_id(plugin_id, context) };
let result = match plugin.call(name, input) {
Err(e) => Err(to_rustler_error(e)),
Ok(result) => match str::from_utf8(result) {
Ok(result) => match str::from_utf8(&result) {
Ok(output) => Ok(output.to_string()),
Err(_e) => Err(rustler::Error::Term(Box::new(
"Could not read output from plugin",
@@ -109,7 +107,7 @@ fn plugin_update_manifest(
) -> Result<(), rustler::Error> {
let context = &ctx.ctx.read().unwrap();
let mut plugin = unsafe { Plugin::from_id(plugin_id, context) };
let result = match plugin.update(manifest_payload, [], wasi) {
let result = match plugin.update(manifest_payload, wasi) {
Ok(()) => Ok(()),
Err(e) => Err(to_rustler_error(e)),
};
@@ -119,24 +117,6 @@ fn plugin_update_manifest(
result
}
#[rustler::nif]
fn plugin_cancel_handle(
ctx: ResourceArc<ExtismContext>,
plugin_id: i32,
) -> Result<ResourceArc<ExtismCancelHandle>, rustler::Error> {
let context = &ctx.ctx.read().unwrap();
let plugin = unsafe { Plugin::from_id(plugin_id, context) };
let handle = plugin.cancel_handle();
Ok(ResourceArc::new(ExtismCancelHandle {
handle: RwLock::new(handle),
}))
}
#[rustler::nif]
fn plugin_cancel(handle: ResourceArc<ExtismCancelHandle>) -> bool {
handle.handle.read().unwrap().cancel()
}
#[rustler::nif]
fn plugin_free(ctx: ResourceArc<ExtismContext>, plugin_id: i32) -> Result<(), rustler::Error> {
let context = &ctx.ctx.read().unwrap();
@@ -190,8 +170,6 @@ rustler::init!(
plugin_call,
plugin_update_manifest,
plugin_has_function,
plugin_cancel_handle,
plugin_cancel,
plugin_free,
set_log_file,
],

View File

@@ -1,7 +1,7 @@
# This file is generated by dune, edit dune-project instead
opam-version: "2.0"
synopsis: "Extism manifest bindings"
description: "Bindings to the Extism manifest format"
description: "Bindings to Extism, the universal plugin system"
maintainer: ["Extism Authors <oss@extism.org>"]
authors: ["Extism Authors <oss@extism.org>"]
license: "BSD-3-Clause"
@@ -11,9 +11,8 @@ doc: "https://github.com/extism/extism"
bug-reports: "https://github.com/extism/extism/issues"
depends: [
"ocaml" {>= "4.14.1"}
"dune" {>= "3.2"}
"ppx_yojson_conv" {>= "v0.15.0"}
"ppx_inline_test" {>= "v0.15.0"}
"dune" {>= "3.2" & >= "3.2"}
"ppx_yojson_conv" {>= "0.15.0"}
"base64" {>= "3.5.0"}
"odoc" {with-doc}
]

310
extism.go
View File

@@ -5,50 +5,13 @@ import (
"errors"
"fmt"
"io"
"runtime/cgo"
"unsafe"
)
/*
#cgo CFLAGS: -I/usr/local/include
#cgo LDFLAGS: -L/usr/local/lib -lextism
#cgo pkg-config: libextism.pc
#include <extism.h>
#include <stdlib.h>
int64_t extism_val_i64(ExtismValUnion* x){
return x->i64;
}
int32_t extism_val_i32(ExtismValUnion* x){
return x->i32;
}
float extism_val_f32(ExtismValUnion* x){
return x->f32;
}
double extism_val_f64(ExtismValUnion* x){
return x->f64;
}
void extism_val_set_i64(ExtismValUnion* x, int64_t i){
x->i64 = i;
}
void extism_val_set_i32(ExtismValUnion* x, int32_t i){
x->i32 = i;
}
void extism_val_set_f32(ExtismValUnion* x, float f){
x->f32 = f;
}
void extism_val_set_f64(ExtismValUnion* x, double f){
x->f64 = f;
}
*/
import "C"
@@ -57,106 +20,6 @@ type Context struct {
pointer *C.ExtismContext
}
type ValType = C.ExtismValType
type Val = C.ExtismVal
type Size = C.ExtismSize
var (
I32 ValType = C.I32
I64 ValType = C.I64
F32 ValType = C.F32
F64 ValType = C.F64
V128 ValType = C.V128
FuncRef ValType = C.FuncRef
ExternRef ValType = C.ExternRef
)
// Function is used to define host functions
type Function struct {
pointer *C.ExtismFunction
userData cgo.Handle
}
// Free a function
func (f *Function) Free() {
C.extism_function_free(f.pointer)
f.pointer = nil
f.userData.Delete()
}
// NewFunction creates a new host function with the given name, input/outputs and optional user data, which can be an
// arbitrary `interface{}`
func NewFunction(name string, inputs []ValType, outputs []ValType, f unsafe.Pointer, userData interface{}) Function {
var function Function
function.userData = cgo.NewHandle(userData)
cname := C.CString(name)
ptr := unsafe.Pointer(function.userData)
var inputsPtr *C.ExtismValType = nil
if len(inputs) > 0 {
inputsPtr = (*C.ExtismValType)(&inputs[0])
}
var outputsPtr *C.ExtismValType = nil
if len(outputs) > 0 {
outputsPtr = (*C.ExtismValType)(&outputs[0])
}
function.pointer = C.extism_function_new(
cname,
inputsPtr,
C.uint64_t(len(inputs)),
outputsPtr,
C.uint64_t(len(outputs)),
(*[0]byte)(f),
ptr,
nil,
)
C.free(unsafe.Pointer(cname))
return function
}
func (f *Function) SetNamespace(s string) {
cstr := C.CString(s)
defer C.free(unsafe.Pointer(cstr))
C.extism_function_set_namespace(f.pointer, cstr)
}
func (f Function) WithNamespace(s string) Function {
f.SetNamespace(s)
return f
}
type CurrentPlugin struct {
pointer *C.ExtismCurrentPlugin
}
func GetCurrentPlugin(ptr unsafe.Pointer) CurrentPlugin {
return CurrentPlugin{
pointer: (*C.ExtismCurrentPlugin)(ptr),
}
}
func (p *CurrentPlugin) Memory(offs uint) []byte {
length := C.extism_current_plugin_memory_length(p.pointer, C.uint64_t(offs))
data := unsafe.Pointer(C.extism_current_plugin_memory(p.pointer))
return unsafe.Slice((*byte)(unsafe.Add(data, offs)), C.int(length))
}
// Alloc a new memory block of the given length, returning its offset
func (p *CurrentPlugin) Alloc(n uint) uint {
return uint(C.extism_current_plugin_memory_alloc(p.pointer, C.uint64_t(n)))
}
// Free the memory block specified by the given offset
func (p *CurrentPlugin) Free(offs uint) {
C.extism_current_plugin_memory_free(p.pointer, C.uint64_t(offs))
}
// Length returns the number of bytes allocated at the specified offset
func (p *CurrentPlugin) Length(offs uint) uint {
return uint(C.extism_current_plugin_memory_length(p.pointer, C.uint64_t(offs)))
}
// NewContext creates a new context, it should be freed using the `Free` method
func NewContext() Context {
p := C.extism_context_new()
@@ -233,32 +96,14 @@ func ExtismVersion() string {
return C.GoString(C.extism_version())
}
func register(ctx *Context, data []byte, functions []Function, wasi bool) (Plugin, error) {
func register(ctx *Context, data []byte, wasi bool) (Plugin, error) {
ptr := makePointer(data)
functionPointers := []*C.ExtismFunction{}
for _, f := range functions {
functionPointers = append(functionPointers, f.pointer)
}
plugin := C.int32_t(-1)
if len(functions) == 0 {
plugin = C.extism_plugin_new(
ctx.pointer,
(*C.uchar)(ptr),
C.uint64_t(len(data)),
nil,
0,
C._Bool(wasi))
} else {
plugin = C.extism_plugin_new(
ctx.pointer,
(*C.uchar)(ptr),
C.uint64_t(len(data)),
&functionPointers[0],
C.uint64_t(len(functions)),
C._Bool(wasi),
)
}
plugin := C.extism_plugin_new(
ctx.pointer,
(*C.uchar)(ptr),
C.uint64_t(len(data)),
C._Bool(wasi),
)
if plugin < 0 {
err := C.extism_error(ctx.pointer, C.int32_t(-1))
@@ -275,41 +120,18 @@ func register(ctx *Context, data []byte, functions []Function, wasi bool) (Plugi
return Plugin{id: int32(plugin), ctx: ctx}, nil
}
func update(ctx *Context, plugin int32, data []byte, functions []Function, wasi bool) error {
func update(ctx *Context, plugin int32, data []byte, wasi bool) error {
ptr := makePointer(data)
functionPointers := []*C.ExtismFunction{}
for _, f := range functions {
functionPointers = append(functionPointers, f.pointer)
}
b := bool(C.extism_plugin_update(
ctx.pointer,
C.int32_t(plugin),
(*C.uchar)(ptr),
C.uint64_t(len(data)),
C._Bool(wasi),
))
if len(functions) == 0 {
b := bool(C.extism_plugin_update(
ctx.pointer,
C.int32_t(plugin),
(*C.uchar)(ptr),
C.uint64_t(len(data)),
nil,
0,
C._Bool(wasi),
))
if b {
return nil
}
} else {
b := bool(C.extism_plugin_update(
ctx.pointer,
C.int32_t(plugin),
(*C.uchar)(ptr),
C.uint64_t(len(data)),
&functionPointers[0],
C.uint64_t(len(functions)),
C._Bool(wasi),
))
if b {
return nil
}
if b {
return nil
}
err := C.extism_error(ctx.pointer, C.int32_t(-1))
@@ -324,43 +146,43 @@ func update(ctx *Context, plugin int32, data []byte, functions []Function, wasi
}
// PluginFromManifest creates a plugin from a `Manifest`
func (ctx *Context) PluginFromManifest(manifest Manifest, functions []Function, wasi bool) (Plugin, error) {
func (ctx *Context) PluginFromManifest(manifest Manifest, wasi bool) (Plugin, error) {
data, err := json.Marshal(manifest)
if err != nil {
return Plugin{id: -1}, err
}
return register(ctx, data, functions, wasi)
return register(ctx, data, wasi)
}
// Plugin creates a plugin from a WASM module
func (ctx *Context) Plugin(module io.Reader, functions []Function, wasi bool) (Plugin, error) {
func (ctx *Context) Plugin(module io.Reader, wasi bool) (Plugin, error) {
wasm, err := io.ReadAll(module)
if err != nil {
return Plugin{id: -1}, err
}
return register(ctx, wasm, functions, wasi)
return register(ctx, wasm, wasi)
}
// Update a plugin with a new WASM module
func (p *Plugin) Update(module io.Reader, functions []Function, wasi bool) error {
func (p *Plugin) Update(module io.Reader, wasi bool) error {
wasm, err := io.ReadAll(module)
if err != nil {
return err
}
return update(p.ctx, p.id, wasm, functions, wasi)
return update(p.ctx, p.id, wasm, wasi)
}
// Update a plugin with a new Manifest
func (p *Plugin) UpdateManifest(manifest Manifest, functions []Function, wasi bool) error {
func (p *Plugin) UpdateManifest(manifest Manifest, wasi bool) error {
data, err := json.Marshal(manifest)
if err != nil {
return err
}
return update(p.ctx, p.id, data, functions, wasi)
return update(p.ctx, p.id, data, wasi)
}
// Set configuration values
@@ -411,7 +233,8 @@ func (plugin Plugin) Call(functionName string, input []byte) ([]byte, error) {
if length > 0 {
x := C.extism_plugin_output_data(plugin.ctx.pointer, C.int32_t(plugin.id))
return unsafe.Slice((*byte)(x), C.int(length)), nil
y := (*[]byte)(unsafe.Pointer(&x))
return []byte((*y)[0:length]), nil
}
return []byte{}, nil
@@ -430,80 +253,3 @@ func (plugin *Plugin) Free() {
func (ctx Context) Reset() {
C.extism_context_reset(ctx.pointer)
}
// ValGetI64 returns an I64 from an ExtismVal, it accepts a pointer to a C.ExtismVal
func ValGetI64(v unsafe.Pointer) int64 {
return int64(C.extism_val_i64(&(*Val)(v).v))
}
// ValGetUInt returns a uint from an ExtismVal, it accepts a pointer to a C.ExtismVal
func ValGetUInt(v unsafe.Pointer) uint {
return uint(C.extism_val_i64(&(*Val)(v).v))
}
// ValGetI32 returns an int32 from an ExtismVal, it accepts a pointer to a C.ExtismVal
func ValGetI32(v unsafe.Pointer) int32 {
return int32(C.extism_val_i32(&(*Val)(v).v))
}
// ValGetF32 returns a float32 from an ExtismVal, it accepts a pointer to a C.ExtismVal
func ValGetF32(v unsafe.Pointer) float32 {
return float32(C.extism_val_f32(&(*Val)(v).v))
}
// ValGetF32 returns a float64 from an ExtismVal, it accepts a pointer to a C.ExtismVal
func ValGetF64(v unsafe.Pointer) float64 {
return float64(C.extism_val_i64(&(*Val)(v).v))
}
// ValSetI64 stores an int64 in an ExtismVal, it accepts a pointer to a C.ExtismVal and the new value
func ValSetI64(v unsafe.Pointer, i int64) {
C.extism_val_set_i64(&(*Val)(v).v, C.int64_t(i))
}
// ValSetI32 stores an int32 in an ExtismVal, it accepts a pointer to a C.ExtismVal and the new value
func ValSetI32(v unsafe.Pointer, i int32) {
C.extism_val_set_i32(&(*Val)(v).v, C.int32_t(i))
}
// ValSetF32 stores a float32 in an ExtismVal, it accepts a pointer to a C.ExtismVal and the new value
func ValSetF32(v unsafe.Pointer, i float32) {
C.extism_val_set_f32(&(*Val)(v).v, C.float(i))
}
// ValSetF64 stores a float64 in an ExtismVal, it accepts a pointer to a C.ExtismVal and the new value
func ValSetF64(v unsafe.Pointer, f float64) {
C.extism_val_set_f64(&(*Val)(v).v, C.double(f))
}
func (p *CurrentPlugin) ReturnBytes(v unsafe.Pointer, b []byte) {
mem := p.Alloc(uint(len(b)))
ptr := p.Memory(mem)
copy(ptr, b)
ValSetI64(v, int64(mem))
}
func (p *CurrentPlugin) ReturnString(v unsafe.Pointer, s string) {
p.ReturnBytes(v, []byte(s))
}
func (p *CurrentPlugin) InputBytes(v unsafe.Pointer) []byte {
return p.Memory(ValGetUInt(v))
}
func (p *CurrentPlugin) InputString(v unsafe.Pointer) string {
return string(p.InputBytes(v))
}
type CancelHandle struct {
pointer *C.ExtismCancelHandle
}
func (p *Plugin) CancelHandle() CancelHandle {
pointer := C.extism_plugin_cancel_handle(p.ctx.pointer, C.int(p.id))
return CancelHandle{pointer}
}
func (c *CancelHandle) Cancel() bool {
return bool(C.extism_plugin_cancel(c.pointer))
}

View File

@@ -11,13 +11,12 @@ doc: "https://github.com/extism/extism"
bug-reports: "https://github.com/extism/extism/issues"
depends: [
"ocaml" {>= "4.14.1"}
"dune" {>= "3.2"}
"ctypes" {>= "0.18.0"}
"dune" {>= "3.2" & >= "3.2"}
"ctypes-foreign" {>= "0.18.0"}
"bigstringaf" {>= "0.9.0"}
"ppx_yojson_conv" {>= "v0.15.0"}
"ppx_yojson_conv" {>= "0.15.0"}
"extism-manifest"
"ppx_inline_test" {>= "v0.15.0"}
"ppx_inline_test" {>= "0.15.0"}
"cmdliner" {>= "1.1.1"}
"odoc" {with-doc}
]
@@ -36,5 +35,3 @@ build: [
]
]
dev-repo: "git+https://github.com/extism/extism.git"
build-env: [EXTISM_TEST_NO_LIB = ""]
post-messages: ["See https://extism.org/docs/install/ for information about installing libextism"]

View File

@@ -1,2 +0,0 @@
build-env: [EXTISM_TEST_NO_LIB = ""]
post-messages: ["See https://extism.org/docs/install/ for information about installing libextism"]

View File

@@ -4,19 +4,13 @@ import (
"encoding/json"
"fmt"
"testing"
"time"
)
func manifest(functions bool) Manifest {
path := "./wasm/code.wasm"
if functions {
path = "./wasm/code-functions.wasm"
}
func manifest() Manifest {
return Manifest{
Wasm: []Wasm{
WasmFile{
Path: path,
Path: "./wasm/code.wasm",
},
},
}
@@ -44,7 +38,7 @@ func TestCallPlugin(t *testing.T) {
ctx := NewContext()
defer ctx.Free()
plugin, err := ctx.PluginFromManifest(manifest(false), []Function{}, false)
plugin, err := ctx.PluginFromManifest(manifest(), false)
if err != nil {
t.Error(err)
}
@@ -64,7 +58,7 @@ func TestFreePlugin(t *testing.T) {
ctx := NewContext()
defer ctx.Free()
plugin, err := ctx.PluginFromManifest(manifest(false), []Function{}, false)
plugin, err := ctx.PluginFromManifest(manifest(), false)
if err != nil {
t.Error(err)
}
@@ -84,7 +78,7 @@ func TestContextReset(t *testing.T) {
ctx := NewContext()
defer ctx.Free()
plugin, err := ctx.PluginFromManifest(manifest(false), []Function{}, false)
plugin, err := ctx.PluginFromManifest(manifest(), false)
if err != nil {
t.Error(err)
}
@@ -104,7 +98,7 @@ func TestCanUpdateAManifest(t *testing.T) {
ctx := NewContext()
defer ctx.Free()
plugin, err := ctx.PluginFromManifest(manifest(false), []Function{}, false)
plugin, err := ctx.PluginFromManifest(manifest(), false)
if err != nil {
t.Error(err)
}
@@ -113,7 +107,7 @@ func TestCanUpdateAManifest(t *testing.T) {
t.Error(err)
}
plugin.UpdateManifest(manifest(false), []Function{}, false)
plugin.UpdateManifest(manifest(), false)
// can still call the plugin
if err := expectVowelCount(plugin, "this is a test", 4); err != nil {
@@ -125,7 +119,7 @@ func TestFunctionExists(t *testing.T) {
ctx := NewContext()
defer ctx.Free()
plugin, err := ctx.PluginFromManifest(manifest(false), []Function{}, false)
plugin, err := ctx.PluginFromManifest(manifest(), false)
if err != nil {
t.Error(err)
}
@@ -142,7 +136,7 @@ func TestErrorsOnUnknownFunction(t *testing.T) {
ctx := NewContext()
defer ctx.Free()
plugin, err := ctx.PluginFromManifest(manifest(false), []Function{}, false)
plugin, err := ctx.PluginFromManifest(manifest(), false)
if err != nil {
t.Error(err)
}
@@ -152,33 +146,3 @@ func TestErrorsOnUnknownFunction(t *testing.T) {
t.Fatal("Was expecting call to unknown function to fail")
}
}
func TestCancel(t *testing.T) {
manifest := Manifest{
Wasm: []Wasm{
WasmFile{
Path: "./wasm/loop.wasm",
},
},
}
ctx := NewContext()
defer ctx.Free()
plugin, err := ctx.PluginFromManifest(manifest, []Function{}, false)
if err != nil {
t.Error(err)
}
cancelHandle := plugin.CancelHandle()
go func(handle CancelHandle) {
time.Sleep(time.Second * 1)
handle.Cancel()
}(cancelHandle)
_, err = plugin.Call("infinite_loop", []byte(""))
if err == nil {
t.Fail()
}
}

View File

@@ -4,34 +4,10 @@ import (
"encoding/json"
"fmt"
"os"
"runtime/cgo"
"unsafe"
"github.com/extism/extism"
)
/*
#include <extism.h>
EXTISM_GO_FUNCTION(hello_world);
*/
import "C"
//export hello_world
func hello_world(plugin unsafe.Pointer, inputs *C.ExtismVal, nInputs C.ExtismSize, outputs *C.ExtismVal, nOutputs C.ExtismSize, userData uintptr) {
fmt.Println("Hello from Go!")
s := cgo.Handle(userData)
fmt.Println(s.Value().(string))
inputSlice := unsafe.Slice(inputs, nInputs)
outputSlice := unsafe.Slice(outputs, nOutputs)
// Get memory pointed to by first element of input slice
p := extism.GetCurrentPlugin(plugin)
str := p.InputString(unsafe.Pointer(&inputSlice[0]))
fmt.Println(str)
outputSlice[0] = inputSlice[0]
}
func main() {
version := extism.ExtismVersion()
fmt.Println("Extism Version: ", version)
@@ -46,10 +22,9 @@ func main() {
} else {
data = []byte("testing from go -> wasm shared memory...")
}
manifest := extism.Manifest{Wasm: []extism.Wasm{extism.WasmFile{Path: "../wasm/code-functions.wasm"}}}
f := extism.NewFunction("hello_world", []extism.ValType{extism.I64}, []extism.ValType{extism.I64}, C.hello_world, "Hello again!")
defer f.Free()
plugin, err := ctx.PluginFromManifest(manifest, []extism.Function{f}, true)
manifest := extism.Manifest{Wasm: []extism.Wasm{extism.WasmFile{Path: "../wasm/code.wasm"}}}
plugin, err := ctx.PluginFromManifest(manifest, false)
if err != nil {
fmt.Println(err)
os.Exit(1)

View File

@@ -1,5 +1,5 @@
# Revision history for extism
## 0.2.0.0 -- 2023-01-16
## 0.1.0.0 -- YYYY-mm-dd
* First version. Released on an unsuspecting world.

View File

@@ -13,9 +13,6 @@ clean:
cabal clean
publish: clean prepare
cabal v2-haddock --haddock-for-hackage ./manifest/extism-manifest.cabal
cabal v2-haddock --haddock-for-hackage
cabal sdist ./manifest/extism-manifest.cabal
cabal sdist
# TODO: upload

View File

@@ -1,6 +1,6 @@
cabal-version: 3.0
name: extism
version: 0.2.0
version: 0.0.1
license: BSD-3-Clause
maintainer: oss@extism.org
author: Extism authors
@@ -19,10 +19,10 @@ library
extra-libraries: extism
extra-lib-dirs: /usr/local/lib
build-depends:
base >= 4.16.1 && < 4.19.0,
base >= 4.16.1 && < 4.18.0,
bytestring >= 0.11.3 && < 0.12,
json >= 0.10 && < 0.11,
extism-manifest >= 0.0.0 && < 0.3.0
extism-manifest >= 0.0.0 && < 0.1.0
test-suite extism-example
type: exitcode-stdio-1.0

View File

@@ -24,8 +24,6 @@ toNullable (Just x) = NotNull x
toNullable Nothing = Null
fromNullable (NotNull x) = Just x
fromNullable Null = Nothing
fromNotNull (NotNull x) = x
fromNotNull Null = error "Value is Null"
mapNullable f Null = Null
mapNullable f (NotNull x) = NotNull (f x)

View File

@@ -1,6 +1,6 @@
cabal-version: 3.0
name: extism-manifest
version: 0.2.0
version: 0.0.1
license: BSD-3-Clause
maintainer: oss@extism.org
author: Extism authors
@@ -15,7 +15,7 @@ library
hs-source-dirs: .
default-language: Haskell2010
build-depends:
base >= 4.16.1 && < 4.19.0,
base >= 4.16.1 && < 4.18.0,
bytestring >= 0.11.3 && < 0.12,
json >= 0.10 && < 0.11,
base64-bytestring >= 1.2.1 && < 1.3,

View File

@@ -19,8 +19,6 @@ newtype Context = Context (ForeignPtr ExtismContext)
-- | Plugins can be used to call WASM function
data Plugin = Plugin Context Int32
data CancelHandle = CancelHandle (Ptr ExtismCancelHandle)
-- | Log level
data LogLevel = Error | Warn | Info | Debug | Trace deriving (Show)
@@ -72,7 +70,7 @@ plugin c wasm useWasi =
do
withForeignPtr ctx (\ctx -> do
p <- unsafeUseAsCString wasm (\s ->
extism_plugin_new ctx (castPtr s) length nullPtr 0 wasi )
extism_plugin_new ctx (castPtr s) length wasi)
if p < 0 then do
err <- extism_error ctx (-1)
e <- peekCString err
@@ -94,7 +92,7 @@ update (Plugin (Context ctx) id) wasm useWasi =
do
withForeignPtr ctx (\ctx -> do
b <- unsafeUseAsCString wasm (\s ->
extism_plugin_update ctx id (castPtr s) length nullPtr 0 wasi)
extism_plugin_update ctx id (castPtr s) length wasi)
if b <= 0 then do
err <- extism_error ctx (-1)
e <- peekCString err
@@ -174,13 +172,3 @@ call (Plugin (Context ctx) plugin) name input =
free :: Plugin -> IO ()
free (Plugin (Context ctx) plugin) =
withForeignPtr ctx (`extism_plugin_free` plugin)
cancelHandle :: Plugin -> IO CancelHandle
cancelHandle (Plugin (Context ctx) plugin) = do
handle <- withForeignPtr ctx (\ctx -> extism_plugin_cancel_handle ctx plugin)
return (CancelHandle handle)
cancel :: CancelHandle -> IO Bool
cancel (CancelHandle handle) =
extism_plugin_cancel handle

View File

@@ -9,22 +9,18 @@ import Data.Int
import Data.Word
newtype ExtismContext = ExtismContext () deriving Show
newtype ExtismFunction = ExtismFunction () deriving Show
newtype ExtismCancelHandle = ExtismCancelHandle () deriving Show
foreign import ccall safe "extism.h extism_context_new" extism_context_new :: IO (Ptr ExtismContext)
foreign import ccall safe "extism.h &extism_context_free" extism_context_free :: FunPtr (Ptr ExtismContext -> IO ())
foreign import ccall safe "extism.h extism_plugin_new" extism_plugin_new :: Ptr ExtismContext -> Ptr Word8 -> Word64 -> Ptr (Ptr ExtismFunction) -> Word64 -> CBool -> IO Int32
foreign import ccall safe "extism.h extism_plugin_update" extism_plugin_update :: Ptr ExtismContext -> Int32 -> Ptr Word8 -> Word64 -> Ptr (Ptr ExtismFunction) -> Word64 -> CBool -> IO CBool
foreign import ccall safe "extism.h extism_plugin_call" extism_plugin_call :: Ptr ExtismContext -> Int32 -> CString -> Ptr Word8 -> Word64 -> IO Int32
foreign import ccall safe "extism.h extism_plugin_function_exists" extism_plugin_function_exists :: Ptr ExtismContext -> Int32 -> CString -> IO CBool
foreign import ccall safe "extism.h extism_error" extism_error :: Ptr ExtismContext -> Int32 -> IO CString
foreign import ccall safe "extism.h extism_plugin_output_length" extism_plugin_output_length :: Ptr ExtismContext -> Int32 -> IO Word64
foreign import ccall safe "extism.h extism_plugin_output_data" extism_plugin_output_data :: Ptr ExtismContext -> Int32 -> IO (Ptr Word8)
foreign import ccall safe "extism.h extism_log_file" extism_log_file :: CString -> CString -> IO CBool
foreign import ccall safe "extism.h extism_plugin_config" extism_plugin_config :: Ptr ExtismContext -> Int32 -> Ptr Word8 -> Int64 -> IO CBool
foreign import ccall safe "extism.h extism_plugin_free" extism_plugin_free :: Ptr ExtismContext -> Int32 -> IO ()
foreign import ccall safe "extism.h extism_context_reset" extism_context_reset :: Ptr ExtismContext -> IO ()
foreign import ccall safe "extism.h extism_version" extism_version :: IO CString
foreign import ccall safe "extism.h extism_plugin_cancel_handle" extism_plugin_cancel_handle :: Ptr ExtismContext -> Int32 -> IO (Ptr ExtismCancelHandle)
foreign import ccall safe "extism.h extism_plugin_cancel" extism_plugin_cancel :: Ptr ExtismCancelHandle -> IO Bool
foreign import ccall unsafe "extism.h extism_context_new" extism_context_new :: IO (Ptr ExtismContext)
foreign import ccall unsafe "extism.h &extism_context_free" extism_context_free :: FunPtr (Ptr ExtismContext -> IO ())
foreign import ccall unsafe "extism.h extism_plugin_new" extism_plugin_new :: Ptr ExtismContext -> Ptr Word8 -> Word64 -> CBool -> IO Int32
foreign import ccall unsafe "extism.h extism_plugin_update" extism_plugin_update :: Ptr ExtismContext -> Int32 -> Ptr Word8 -> Word64 -> CBool -> IO CBool
foreign import ccall unsafe "extism.h extism_plugin_call" extism_plugin_call :: Ptr ExtismContext -> Int32 -> CString -> Ptr Word8 -> Word64 -> IO Int32
foreign import ccall unsafe "extism.h extism_plugin_function_exists" extism_plugin_function_exists :: Ptr ExtismContext -> Int32 -> CString -> IO CBool
foreign import ccall unsafe "extism.h extism_error" extism_error :: Ptr ExtismContext -> Int32 -> IO CString
foreign import ccall unsafe "extism.h extism_plugin_output_length" extism_plugin_output_length :: Ptr ExtismContext -> Int32 -> IO Word64
foreign import ccall unsafe "extism.h extism_plugin_output_data" extism_plugin_output_data :: Ptr ExtismContext -> Int32 -> IO (Ptr Word8)
foreign import ccall unsafe "extism.h extism_log_file" extism_log_file :: CString -> CString -> IO CBool
foreign import ccall unsafe "extism.h extism_plugin_config" extism_plugin_config :: Ptr ExtismContext -> Int32 -> Ptr Word8 -> Int64 -> IO CBool
foreign import ccall unsafe "extism.h extism_plugin_free" extism_plugin_free :: Ptr ExtismContext -> Int32 -> IO ()
foreign import ccall unsafe "extism.h extism_context_reset" extism_context_reset :: Ptr ExtismContext -> IO ()
foreign import ccall unsafe "extism.h extism_version" extism_version :: IO CString

View File

@@ -7,7 +7,7 @@ unwrap (Right x) = return x
unwrap (Left (ExtismError msg)) =
assertFailure msg
defaultManifest = manifest [wasmFile "../../wasm/code.wasm"]
defaultManifest = manifest [wasmFile "test/code.wasm"]
initPlugin :: Context -> IO Plugin
initPlugin context =

BIN
haskell/test/code.wasm Executable file

Binary file not shown.

View File

@@ -4,7 +4,7 @@
<groupId>org.extism.sdk</groupId>
<artifactId>extism</artifactId>
<packaging>jar</packaging>
<version>0.3.0</version>
<version>0.1.0</version>
<name>extism</name>
<url>https://github.com/extism/extism</url>
<description>Java-SDK for Extism to use webassembly from Java</description>
@@ -36,7 +36,7 @@
<scm>
<connection>scm:git:git://github.com/extism/extism.git</connection>
<developerConnection>scm:git:ssh://git@github.com/extism/extism.git</developerConnection>
<url>https://github.com/extism/extism/tree/main/java</url>
<url>https://github.com/extism/extism/java</url>
<tag>main</tag>
</scm>
@@ -46,7 +46,7 @@
</issueManagement>
<properties>
<java.version>11</java.version>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- dependencies -->
@@ -74,9 +74,6 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.4.1</version>
<configuration>
<additionalJOption>-Xdoclint:none</additionalJOption>
</configuration>
<executions>
<execution>
<id>attach-javadoc</id>

View File

@@ -1,21 +0,0 @@
package org.extism.sdk;
import com.sun.jna.Pointer;
/**
* CancelHandle is used to cancel a running Plugin
*/
public class CancelHandle {
private Pointer handle;
public CancelHandle(Pointer handle) {
this.handle = handle;
}
/**
* Cancel execution of the Plugin associated with the CancelHandle
*/
boolean cancel() {
return LibExtism.INSTANCE.extism_plugin_cancel(this.handle);
}
}

View File

@@ -28,11 +28,10 @@ public class Context implements AutoCloseable {
*
* @param manifest The manifest for the plugin
* @param withWASI Set to true to enable WASI
* @param functions List of Host functions
* @return the plugin instance
*/
public Plugin newPlugin(Manifest manifest, boolean withWASI, HostFunction[] functions) {
return new Plugin(this, manifest, withWASI, functions);
public Plugin newPlugin(Manifest manifest, boolean withWASI) {
return new Plugin(this, manifest, withWASI);
}
/**

View File

@@ -42,7 +42,7 @@ public class Extism {
*/
public static String invokeFunction(Manifest manifest, String function, String input) throws ExtismException {
try (var ctx = new Context()) {
try (var plugin = ctx.newPlugin(manifest, false, null)) {
try (var plugin = ctx.newPlugin(manifest, false)) {
return plugin.call(function, input);
}
}

View File

@@ -1,78 +0,0 @@
package org.extism.sdk;
import com.sun.jna.Pointer;
import java.nio.charset.StandardCharsets;
public class ExtismCurrentPlugin {
public Pointer pointer;
public ExtismCurrentPlugin(Pointer pointer) {
this.pointer = pointer;
}
public Pointer memory() {
return LibExtism.INSTANCE.extism_current_plugin_memory(this.pointer);
}
public int alloc(int n) {
return LibExtism.INSTANCE.extism_current_plugin_memory_alloc(this.pointer, n);
}
public void free(long offset) {
LibExtism.INSTANCE.extism_current_plugin_memory_free(this.pointer, offset);
}
public long memoryLength(long offset) {
return LibExtism.INSTANCE.extism_current_plugin_memory_length(this.pointer, offset);
}
/**
* Return a string from a host function
* @param output - The output to set
* @param s - The string to return
*/
public void returnString(LibExtism.ExtismVal output, String s) {
returnBytes(output, s.getBytes(StandardCharsets.UTF_8));
}
/**
* Return bytes from a host function
* @param output - The output to set
* @param b - The buffer to return
*/
public void returnBytes(LibExtism.ExtismVal output, byte[] b) {
int offs = this.alloc(b.length);
Pointer ptr = this.memory();
ptr.write(offs, b, 0, b.length);
output.v.i64 = offs;
}
/**
* Get bytes from host function parameter
* @param input - The input to read
*/
public byte[] inputBytes(LibExtism.ExtismVal input) {
switch (input.t) {
case 0:
return this.memory()
.getByteArray(input.v.i32,
LibExtism.INSTANCE.extism_current_plugin_memory_length(this.pointer, input.v.i32));
case 1:
return this.memory()
.getByteArray(input.v.i64,
LibExtism.INSTANCE.extism_current_plugin_memory_length(this.pointer, input.v.i64));
default:
throw new ExtismException("inputBytes error: ExtismValType " + LibExtism.ExtismValType.values()[input.t] + " not implemtented");
}
}
/**
* Get string from host function parameter
* @param input - The input to read
*/
public String inputString(LibExtism.ExtismVal input) {
return new String(this.inputBytes(input));
}
}

View File

@@ -1,12 +0,0 @@
package org.extism.sdk;
import java.util.Optional;
public interface ExtismFunction<T extends HostUserData> {
void invoke(
ExtismCurrentPlugin plugin,
LibExtism.ExtismVal[] params,
LibExtism.ExtismVal[] returns,
Optional<T> data
);
}

View File

@@ -1,92 +0,0 @@
package org.extism.sdk;
import com.sun.jna.Pointer;
import com.sun.jna.PointerType;
import java.util.Arrays;
import java.util.Optional;
public class HostFunction<T extends HostUserData> {
private final LibExtism.InternalExtismFunction callback;
public final Pointer pointer;
public final String name;
public final LibExtism.ExtismValType[] params;
public final LibExtism.ExtismValType[] returns;
public final Optional<T> userData;
public HostFunction(String name, LibExtism.ExtismValType[] params, LibExtism.ExtismValType[] returns, ExtismFunction f, Optional<T> userData) {
this.name = name;
this.params = params;
this.returns = returns;
this.userData = userData;
this.callback = (Pointer currentPlugin,
LibExtism.ExtismVal inputs,
int nInputs,
LibExtism.ExtismVal outs,
int nOutputs,
Pointer data) -> {
LibExtism.ExtismVal[] outputs = (LibExtism.ExtismVal []) outs.toArray(nOutputs);
f.invoke(
new ExtismCurrentPlugin(currentPlugin),
(LibExtism.ExtismVal []) inputs.toArray(nInputs),
outputs,
userData
);
for (LibExtism.ExtismVal output : outputs) {
convertOutput(output, output);
}
};
this.pointer = LibExtism.INSTANCE.extism_function_new(
this.name,
Arrays.stream(this.params).mapToInt(r -> r.v).toArray(),
this.params.length,
Arrays.stream(this.returns).mapToInt(r -> r.v).toArray(),
this.returns.length,
this.callback,
userData.map(PointerType::getPointer).orElse(null),
null
);
}
void convertOutput(LibExtism.ExtismVal original, LibExtism.ExtismVal fromHostFunction) {
if (fromHostFunction.t != original.t)
throw new ExtismException(String.format("Output type mismatch, got %d but expected %d", fromHostFunction.t, original.t));
if (fromHostFunction.t == LibExtism.ExtismValType.I32.v) {
original.v.setType(Integer.TYPE);
original.v.i32 = fromHostFunction.v.i32;
} else if (fromHostFunction.t == LibExtism.ExtismValType.I64.v) {
original.v.setType(Long.TYPE);
original.v.i64 = fromHostFunction.v.i64;
} else if (fromHostFunction.t == LibExtism.ExtismValType.F32.v) {
original.v.setType(Float.TYPE);
original.v.f32 = fromHostFunction.v.f32;
} else if (fromHostFunction.t == LibExtism.ExtismValType.F64.v) {
original.v.setType(Double.TYPE);
original.v.f64 = fromHostFunction.v.f64;
} else
throw new ExtismException(String.format("Unsupported return type: %s", original.t));
}
public void setNamespace(String name) {
if (this.pointer != null) {
LibExtism.INSTANCE.extism_function_set_namespace(this.pointer, name);
}
}
HostFunction withNamespace(String name) {
this.setNamespace(name);
return this;
}
}

View File

@@ -1,7 +0,0 @@
package org.extism.sdk;
import com.sun.jna.PointerType;
public class HostUserData extends PointerType {
}

View File

@@ -1,6 +1,8 @@
package org.extism.sdk;
import com.sun.jna.*;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
/**
* Wrapper around the Extism library.
@@ -13,80 +15,7 @@ public interface LibExtism extends Library {
*/
LibExtism INSTANCE = Native.load("extism", LibExtism.class);
interface InternalExtismFunction extends Callback {
void invoke(
Pointer currentPlugin,
ExtismVal inputs,
int nInputs,
ExtismVal outputs,
int nOutputs,
Pointer data
);
}
@Structure.FieldOrder({"t", "v"})
class ExtismVal extends Structure {
public int t;
public ExtismValUnion v;
}
class ExtismValUnion extends Union {
public int i32;
public long i64;
public float f32;
public double f64;
}
enum ExtismValType {
I32(0),
I64(1),
F32(2),
F64(3),
V128(4),
FuncRef(5),
ExternRef(6);
public final int v;
ExtismValType(int value) {
this.v = value;
}
}
Pointer extism_function_new(String name,
int[] inputs,
int nInputs,
int[] outputs,
int nOutputs,
InternalExtismFunction func,
Pointer userData,
Pointer freeUserData);
/**
* Get the length of an allocated block
* NOTE: this should only be called from host functions.
*/
int extism_current_plugin_memory_length(Pointer plugin, long n);
/**
* Returns a pointer to the memory of the currently running plugin
* NOTE: this should only be called from host functions.
*/
Pointer extism_current_plugin_memory(Pointer plugin);
/**
* Allocate a memory block in the currently running plugin
* NOTE: this should only be called from host functions.
*/
int extism_current_plugin_memory_alloc(Pointer plugin, long n);
/**
* Free an allocated memory block
* NOTE: this should only be called from host functions.
*/
void extism_current_plugin_memory_free(Pointer plugin, long ptr);
/**
* Create a new context
*/
Pointer extism_context_new();
@@ -127,18 +56,27 @@ public interface LibExtism extends Library {
* @param contextPointer pointer to the {@link Context}.
* @param wasm is a WASM module (wat or wasm) or a JSON encoded manifest
* @param wasmSize the length of the `wasm` parameter
* @param functions host functions
* @param nFunctions the number of host functions
* @param withWASI enables/disables WASI
* @return id of the plugin or {@literal -1} in case of error
*/
int extism_plugin_new(Pointer contextPointer, byte[] wasm, long wasmSize, Pointer[] functions, int nFunctions, boolean withWASI);
int extism_plugin_new(long contextPointer, byte[] wasm, long wasmSize, boolean withWASI);
/**
* Returns the Extism version string
*/
String extism_version();
/**
* Create a new plugin.
*
* @param contextPointer pointer to the {@link Context}.
* @param wasm is a WASM module (wat or wasm) or a JSON encoded manifest
* @param length the length of the `wasm` parameter
* @param withWASI enables/disables WASI
* @return id of the plugin or {@literal -1} in case of error
* @see #extism_plugin_new(long, byte[], long, boolean)
*/
int extism_plugin_new(Pointer contextPointer, byte[] wasm, int length, boolean withWASI);
/**
* Calls a function from the @{@link Plugin} at the given {@code pluginIndex}.
@@ -172,19 +110,17 @@ public interface LibExtism extends Library {
/**
* Update a plugin, keeping the existing ID.
* Similar to {@link #extism_plugin_new(Pointer, byte[], long, Pointer[], int, boolean)} but takes an {@code pluginIndex} argument to specify which plugin to update.
* Similar to {@link #extism_plugin_new(long, byte[], long, boolean)} but takes an {@code pluginIndex} argument to specify which plugin to update.
* Note: Memory for this plugin will be reset upon update.
*
* @param contextPointer
* @param pluginIndex
* @param wasm
* @param length
* @param functions host functions
* @param nFunctions the number of host functions
* @param withWASI
* @return {@literal true} if update was successful
*/
boolean extism_plugin_update(Pointer contextPointer, int pluginIndex, byte[] wasm, int length, Pointer[] functions, int nFunctions, boolean withWASI);
boolean extism_plugin_update(Pointer contextPointer, int pluginIndex, byte[] wasm, int length, boolean withWASI);
/**
* Remove a plugin from the registry and free associated memory.
@@ -204,7 +140,4 @@ public interface LibExtism extends Library {
* @return {@literal true} if update was successful
*/
boolean extism_plugin_config(Pointer contextPointer, int pluginIndex, byte[] json, int jsonLength);
Pointer extism_plugin_cancel_handle(Pointer contextPointer, int n);
boolean extism_plugin_cancel(Pointer contextPointer);
void extism_function_set_namespace(Pointer p, String name);
}

View File

@@ -28,27 +28,15 @@ public class Plugin implements AutoCloseable {
*
* @param context The context to manage the plugin
* @param manifestBytes The manifest for the plugin
* @param functions The Host functions for th eplugin
* @param withWASI Set to true to enable WASI
*/
public Plugin(Context context, byte[] manifestBytes, boolean withWASI, HostFunction[] functions) {
public Plugin(Context context, byte[] manifestBytes, boolean withWASI) {
Objects.requireNonNull(context, "context");
Objects.requireNonNull(manifestBytes, "manifestBytes");
Pointer[] ptrArr = new Pointer[functions == null ? 0 : functions.length];
if (functions != null)
for (int i = 0; i < functions.length; i++) {
ptrArr[i] = functions[i].pointer;
}
Pointer contextPointer = context.getPointer();
int index = LibExtism.INSTANCE.extism_plugin_new(contextPointer, manifestBytes, manifestBytes.length,
ptrArr,
functions == null ? 0 : functions.length,
withWASI);
int index = LibExtism.INSTANCE.extism_plugin_new(contextPointer, manifestBytes, manifestBytes.length, withWASI);
if (index == -1) {
String error = context.error(this);
throw new ExtismException(error);
@@ -58,8 +46,8 @@ public class Plugin implements AutoCloseable {
this.context = context;
}
public Plugin(Context context, Manifest manifest, boolean withWASI, HostFunction[] functions) {
this(context, serialize(manifest), withWASI, functions);
public Plugin(Context context, Manifest manifest, boolean withWASI) {
this(context, serialize(manifest), withWASI);
}
private static byte[] serialize(Manifest manifest) {
@@ -124,8 +112,8 @@ public class Plugin implements AutoCloseable {
* @param withWASI Set to true to enable WASI
* @return {@literal true} if update was successful
*/
public boolean update(Manifest manifest, boolean withWASI, HostFunction[] functions) {
return update(serialize(manifest), withWASI, functions);
public boolean update(Manifest manifest, boolean withWASI) {
return update(serialize(manifest), withWASI);
}
/**
@@ -135,19 +123,9 @@ public class Plugin implements AutoCloseable {
* @param withWASI Set to true to enable WASI
* @return {@literal true} if update was successful
*/
public boolean update(byte[] manifestBytes, boolean withWASI, HostFunction[] functions) {
public boolean update(byte[] manifestBytes, boolean withWASI) {
Objects.requireNonNull(manifestBytes, "manifestBytes");
Pointer[] ptrArr = new Pointer[functions == null ? 0 : functions.length];
if (functions != null)
for (int i = 0; i < functions.length; i++) {
ptrArr[i] = functions[i].pointer;
}
return LibExtism.INSTANCE.extism_plugin_update(context.getPointer(), index, manifestBytes, manifestBytes.length,
ptrArr,
functions == null ? 0 : functions.length,
withWASI);
return LibExtism.INSTANCE.extism_plugin_update(context.getPointer(), index, manifestBytes, manifestBytes.length, withWASI);
}
/**
@@ -187,15 +165,4 @@ public class Plugin implements AutoCloseable {
public void close() {
free();
}
/**
* Return a new `CancelHandle`, which can be used to cancel a running Plugin
*/
public CancelHandle cancelHandle() {
if (this.context.getPointer() == null) {
throw new ExtismException("No Context set");
}
Pointer handle = LibExtism.INSTANCE.extism_plugin_cancel_handle(this.context.getPointer(), this.index);
return new CancelHandle(handle);
}
}

View File

@@ -20,14 +20,11 @@ public class Manifest {
@SerializedName("allowed_hosts")
private final List<String> allowedHosts;
@SerializedName("allowed_paths")
private final Map<String, String> allowedPaths;
@SerializedName("config")
private final Map<String, String> config;
public Manifest() {
this(new ArrayList<>(), null, null, null, null);
this(new ArrayList<>(), null, null, null);
}
public Manifest(WasmSource source) {
@@ -35,27 +32,22 @@ public class Manifest {
}
public Manifest(List<WasmSource> sources) {
this(sources, null, null, null, null);
this(sources, null, null, null);
}
public Manifest(List<WasmSource> sources, MemoryOptions memoryOptions) {
this(sources, memoryOptions, null, null, null);
this(sources, memoryOptions, null, null);
}
public Manifest(List<WasmSource> sources, MemoryOptions memoryOptions, Map<String, String> config) {
this(sources, memoryOptions, config, null, null);
this(sources, memoryOptions, config, null);
}
public Manifest(List<WasmSource> sources, MemoryOptions memoryOptions, Map<String, String> config, List<String> allowedHosts) {
this(sources, memoryOptions, config, allowedHosts, null);
}
public Manifest(List<WasmSource> sources, MemoryOptions memoryOptions, Map<String, String> config, List<String> allowedHosts, Map<String, String> allowedPaths) {
this.sources = sources;
this.memoryOptions = memoryOptions;
this.config = config;
this.allowedHosts = allowedHosts;
this.allowedPaths = allowedPaths;
}
public void addSource(WasmSource source) {
@@ -83,11 +75,4 @@ public class Manifest {
}
return Collections.unmodifiableList(allowedHosts);
}
public Map<String, String> getAllowedPaths() {
if (allowedPaths == null || allowedPaths.isEmpty()) {
return Collections.emptyMap();
}
return Collections.unmodifiableMap(allowedPaths);
}
}

View File

@@ -3,27 +3,6 @@ package org.extism.sdk.manifest;
import java.util.Map;
// FIXME remove this and related stuff if not supported in java-sdk
public class ManifestHttpRequest {
public record ManifestHttpRequest(String url, Map<String, String> header, String method) {
}
private final String url;
private final Map<String, String> header;
private final String method;
public ManifestHttpRequest(String url, Map<String, String> header, String method) {
this.url = url;
this.header = header;
this.method = method;
}
public String url() {
return url;
}
public Map<String, String> header() {
return header;
}
public String method() {
return method;
}
}

View File

@@ -8,11 +8,5 @@ import com.google.gson.annotations.SerializedName;
*
* @param max Max number of pages.
*/
public class MemoryOptions {
@SerializedName("max")
private final Integer max;
public MemoryOptions(Integer max) {
this.max = max;
}
public record MemoryOptions(@SerializedName("max") Integer max) {
}

View File

@@ -1,14 +1,18 @@
package org.extism.sdk.support;
import com.google.gson.*;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import org.extism.sdk.manifest.Manifest;
import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class JsonSerde {
@@ -19,7 +23,7 @@ public class JsonSerde {
GSON = new GsonBuilder() //
.disableHtmlEscaping() //
// needed to convert the byte[] to a base64 encoded String
.registerTypeHierarchyAdapter(byte[].class, new ByteArrayAdapter()) //
.registerTypeHierarchyAdapter(byte[].class, new ByteArrayToBase64TypeAdapter()) //
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) //
.setPrettyPrinting() //
.create();
@@ -29,28 +33,14 @@ public class JsonSerde {
return GSON.toJson(manifest);
}
private static class ByteArrayAdapter extends TypeAdapter<byte[]> {
private static class ByteArrayToBase64TypeAdapter implements JsonSerializer<byte[]>, JsonDeserializer<byte[]> {
@Override
public void write(JsonWriter out, byte[] byteValue) throws IOException {
out.value(new String(Base64.getEncoder().encode(byteValue)));
public byte[] deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
return Base64.getDecoder().decode(json.getAsString());
}
@Override
public byte[] read(JsonReader in) {
try {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return new byte[]{};
}
String byteValue = in.nextString();
if (byteValue != null) {
return Base64.getDecoder().decode(byteValue);
}
return new byte[]{};
} catch (Exception e) {
throw new JsonParseException(e);
}
public JsonElement serialize(byte[] src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(Base64.getEncoder().withoutPadding().encodeToString(src));
}
}
}

View File

@@ -2,37 +2,10 @@ package org.extism.sdk.wasm;
/**
* WASM Source represented by raw bytes.
*
* @param name
* @param data the byte array representing the WASM code
* @param hash
*/
public class ByteArrayWasmSource implements WasmSource {
private final String name;
private final byte[] data;
private final String hash;
/**
* Constructor
* @param name
* @param data the byte array representing the WASM code
* @param hash
*/
public ByteArrayWasmSource(String name, byte[] data, String hash) {
this.name = name;
this.data = data;
this.hash = hash;
}
@Override
public String name() {
return name;
}
@Override
public String hash() {
return hash;
}
public byte[] data() {
return data;
}
public record ByteArrayWasmSource(String name, byte[] data, String hash) implements WasmSource {
}

View File

@@ -2,39 +2,11 @@ package org.extism.sdk.wasm;
/**
* WASM Source represented by a file referenced by a path.
*
* @param name
* @param path
* @param hash
*/
public class PathWasmSource implements WasmSource {
private final String name;
private final String path;
private final String hash;
/**
* Constructor
* @param name
* @param path
* @param hash
*/
public PathWasmSource(String name, String path, String hash) {
this.name = name;
this.path = path;
this.hash = hash;
}
@Override
public String name() {
return name;
}
@Override
public String hash() {
return hash;
}
public String path() {
return path;
}
public record PathWasmSource(String name, String path, String hash) implements WasmSource {
}

View File

@@ -6,7 +6,6 @@ import org.extism.sdk.support.JsonSerde;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.HashMap;
import static org.assertj.core.api.Assertions.assertThat;
import static org.extism.sdk.TestWasmSources.CODE;
@@ -17,16 +16,13 @@ public class ManifestTests {
@Test
public void shouldSerializeManifestWithWasmSourceToJson() {
var paths = new HashMap<String, String>();
paths.put("/tmp/foo", "/tmp/extism-plugins/foo");
var manifest = new Manifest(List.of(CODE.pathWasmSource()), null, null, null, paths);
var manifest = new Manifest(CODE.pathWasmSource());
var json = JsonSerde.toJson(manifest);
assertNotNull(json);
assertJson(json).at("/wasm").isArray();
assertJson(json).at("/wasm").hasSize(1);
assertJson(json).at("/allowed_paths").isObject();
assertJson(json).at("/allowed_paths").hasSize(1);
}
@Test

View File

@@ -1,12 +1,12 @@
package org.extism.sdk;
import com.sun.jna.Pointer;
import org.extism.sdk.manifest.Manifest;
import org.extism.sdk.manifest.MemoryOptions;
import org.extism.sdk.wasm.WasmSourceResolver;
import org.junit.jupiter.api.Test;
import java.util.*;
import java.util.List;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.extism.sdk.TestWasmSources.CODE;
@@ -42,12 +42,14 @@ public class PluginTests {
assertThat(output).isEqualTo("{\"count\": 3}");
}
@Test
public void shouldInvokeFunctionFromByteArrayWasmSource() {
var manifest = new Manifest(CODE.byteArrayWasmSource());
var output = Extism.invokeFunction(manifest, "count_vowels", "Hello World");
assertThat(output).isEqualTo("{\"count\": 3}");
}
// TODO This test breaks on CI with error:
// data did not match any variant of untagged enum Wasm at line 8 column 3
// @Test
// public void shouldInvokeFunctionFromByteArrayWasmSource() {
// var manifest = new Manifest(CODE.byteArrayWasmSource());
// var output = Extism.invokeFunction(manifest, "count_vowels", "Hello World");
// assertThat(output).isEqualTo("{\"count\": 3}");
// }
@Test
public void shouldFailToInvokeUnknownFunction() {
@@ -78,7 +80,7 @@ public class PluginTests {
var input = "Hello World";
try (var ctx = new Context()) {
try (var plugin = ctx.newPlugin(manifest, false, null)) {
try (var plugin = ctx.newPlugin(manifest, false)) {
var output = plugin.call(functionName, input);
assertThat(output).isEqualTo("{\"count\": 3}");
}
@@ -92,7 +94,7 @@ public class PluginTests {
var input = "Hello World";
try (var ctx = new Context()) {
try (var plugin = ctx.newPlugin(manifest, false, null)) {
try (var plugin = ctx.newPlugin(manifest, false)) {
var output = plugin.call(functionName, input);
assertThat(output).isEqualTo("{\"count\": 3}");
@@ -102,118 +104,4 @@ public class PluginTests {
}
}
@Test
public void shouldAllowInvokeHostFunctionFromPDK() {
var parametersTypes = new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64};
var resultsTypes = new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64};
class MyUserData extends HostUserData {
private String data1;
private int data2;
public MyUserData(String data1, int data2) {
super();
this.data1 = data1;
this.data2 = data2;
}
}
ExtismFunction helloWorldFunction = (ExtismFunction<MyUserData>) (plugin, params, returns, data) -> {
System.out.println("Hello from Java Host Function!");
System.out.println(String.format("Input string received from plugin, %s", plugin.inputString(params[0])));
int offs = plugin.alloc(4);
Pointer mem = plugin.memory();
mem.write(offs, "test".getBytes(), 0, 4);
returns[0].v.i64 = offs;
data.ifPresent(d -> System.out.println(String.format("Host user data, %s, %d", d.data1, d.data2)));
};
HostFunction helloWorld = new HostFunction<>(
"hello_world",
parametersTypes,
resultsTypes,
helloWorldFunction,
Optional.of(new MyUserData("test", 2))
);
HostFunction[] functions = {helloWorld};
try (var ctx = new Context()) {
Manifest manifest = new Manifest(Arrays.asList(CODE.pathWasmFunctionsSource()));
String functionName = "count_vowels";
try (var plugin = ctx.newPlugin(manifest, true, functions)) {
var output = plugin.call(functionName, "this is a test");
assertThat(output).isEqualTo("test");
}
}
}
@Test
public void shouldAllowInvokeHostFunctionWithoutUserData() {
var parametersTypes = new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64};
var resultsTypes = new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64};
ExtismFunction helloWorldFunction = (plugin, params, returns, data) -> {
System.out.println("Hello from Java Host Function!");
System.out.println(String.format("Input string received from plugin, %s", plugin.inputString(params[0])));
int offs = plugin.alloc(4);
Pointer mem = plugin.memory();
mem.write(offs, "test".getBytes(), 0, 4);
returns[0].v.i64 = offs;
assertThat(data.isEmpty());
};
HostFunction f = new HostFunction<>(
"hello_world",
parametersTypes,
resultsTypes,
helloWorldFunction,
Optional.empty()
)
.withNamespace("env");
HostFunction g = new HostFunction<>(
"hello_world",
parametersTypes,
resultsTypes,
helloWorldFunction,
Optional.empty()
)
.withNamespace("test");
HostFunction[] functions = {f,g};
try (var ctx = new Context()) {
Manifest manifest = new Manifest(Arrays.asList(CODE.pathWasmFunctionsSource()));
String functionName = "count_vowels";
try (var plugin = ctx.newPlugin(manifest, true, functions)) {
var output = plugin.call(functionName, "this is a test");
assertThat(output).isEqualTo("test");
}
}
}
@Test
public void shouldFailToInvokeUnknownHostFunction() {
try (var ctx = new Context()) {
Manifest manifest = new Manifest(Arrays.asList(CODE.pathWasmFunctionsSource()));
String functionName = "count_vowels";
try {
var plugin = ctx.newPlugin(manifest, true, null);
plugin.call(functionName, "this is a test");
} catch (ExtismException e) {
assertThat(e.getMessage()).contains("unknown import: `env::hello_world` has not been defined");
}
}
}
}

View File

@@ -16,28 +16,19 @@ public enum TestWasmSources {
public Path getWasmFilePath() {
return Paths.get(WASM_LOCATION, "code.wasm");
}
public Path getWasmFunctionsFilePath() {
return Paths.get(WASM_LOCATION, "code-functions.wasm");
}
};
public static final String WASM_LOCATION = "src/test/resources";
public abstract Path getWasmFilePath();
public abstract Path getWasmFunctionsFilePath();
public PathWasmSource pathWasmSource() {
return resolvePathWasmSource(getWasmFilePath());
}
public PathWasmSource pathWasmFunctionsSource() {
return resolvePathWasmSource(getWasmFunctionsFilePath());
}
public ByteArrayWasmSource byteArrayWasmSource() {
try {
byte[] wasmBytes = Files.readAllBytes(getWasmFilePath());
var wasmBytes = Files.readAllBytes(getWasmFilePath());
return new WasmSourceResolver().resolve("wasm@" + Arrays.hashCode(wasmBytes), wasmBytes);
} catch (IOException ioe) {
throw new RuntimeException(ioe);

10
libextism.pc Normal file
View File

@@ -0,0 +1,10 @@
prefix=/usr/local
exec_prefix=${prefix}
includedir=${prefix}/include
libdir=${exec_prefix}/lib
Name: extism
Description: The Extism universal plug-in system.
Version: 0.1.0
Cflags: -I${includedir}
Libs: -L${libdir} -lextism

View File

@@ -1,6 +1,6 @@
[package]
name = "libextism"
version = "0.3.0"
version = "0.1.0"
edition = "2021"
authors = ["The Extism Authors", "oss@extism.org"]
license = "BSD-3-Clause"
@@ -20,4 +20,4 @@ default = ["http", "register-http", "register-filesystem"]
nn = ["extism-runtime/nn"]
register-http = ["extism-runtime/register-http"] # enables wasm to be downloaded using http
register-filesystem = ["extism-runtime/register-filesystem"] # enables wasm to be loaded from disk
http = ["extism-runtime/http"] # enables extism_http_request
http = ["extism-runtime/http"] # enables extism_http_request

View File

@@ -1,6 +1,6 @@
[package]
name = "extism-manifest"
version = "0.3.0"
version = "0.1.0"
edition = "2021"
authors = ["The Extism Authors", "oss@extism.org"]
license = "BSD-3-Clause"

View File

@@ -6,7 +6,6 @@ pub type ManifestMemory = MemoryOptions;
#[derive(Default, serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct MemoryOptions {
#[serde(alias = "max")]
pub max_pages: Option<u32>,
@@ -14,7 +13,6 @@ pub struct MemoryOptions {
#[derive(serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct HttpRequest {
pub url: String,
#[serde(default)]
@@ -45,7 +43,6 @@ impl HttpRequest {
#[derive(Default, serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct WasmMetadata {
pub name: Option<String>,
pub hash: Option<String>,
@@ -84,7 +81,6 @@ pub type ManifestWasm = Wasm;
#[derive(serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))]
#[serde(untagged)]
#[serde(deny_unknown_fields)]
pub enum Wasm {
File {
path: PathBuf,
@@ -155,7 +151,6 @@ fn base64_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::
#[derive(Default, serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct Manifest {
#[serde(default)]
pub wasm: Vec<Wasm>,
@@ -255,19 +250,16 @@ impl Manifest {
}
mod base64 {
use base64::{engine::general_purpose, Engine as _};
use serde::{Deserialize, Serialize};
use serde::{Deserializer, Serializer};
pub fn serialize<S: Serializer>(v: &Vec<u8>, s: S) -> Result<S::Ok, S::Error> {
let base64 = general_purpose::STANDARD.encode(v);
let base64 = base64::encode(v);
String::serialize(&base64, s)
}
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<u8>, D::Error> {
let base64 = String::deserialize(d)?;
general_purpose::STANDARD
.decode(base64.as_bytes())
.map_err(serde::de::Error::custom)
base64::decode(base64.as_bytes()).map_err(serde::de::Error::custom)
}
}

View File

@@ -1,31 +1,9 @@
const {
withContext,
Context,
HostFunction,
ValType,
} = require("./dist/index.js");
const { readFileSync } = require("fs");
function f(currentPlugin, inputs, outputs, userData) {
console.log(currentPlugin.inputString(inputs[0]));
console.log("Hello from Javascript!");
console.log(userData);
outputs[0] = inputs[0];
}
let hello_world = new HostFunction(
"hello_world",
[ValType.I64],
[ValType.I64],
f,
"Hello again!"
);
let functions = [hello_world];
const { withContext, Context } = require('./dist/index.js');
const { readFileSync } = require('fs');
withContext(async function (context) {
let wasm = readFileSync("../wasm/code-functions.wasm");
let p = context.plugin(wasm, true, functions);
let wasm = readFileSync("../wasm/code.wasm");
let p = context.plugin(wasm);
if (!p.functionExists("count_vowels")) {
console.log("no function 'count_vowels' in wasm");
@@ -38,7 +16,7 @@ withContext(async function (context) {
});
// or, use a context like this:
// let ctx = new Context();
// let wasm = readFileSync("../wasm/code.wasm");
// let p = ctx.plugin(wasm);
let ctx = new Context();
let wasm = readFileSync("../wasm/code.wasm");
let p = ctx.plugin(wasm);
// ... where the context can be passed around to various functions etc.

3850
node/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@extism/extism",
"version": "0.3.0",
"version": "0.1.0",
"description": "Extism Host SDK for Node",
"keywords": [
"extism",
@@ -21,17 +21,12 @@
},
"scripts": {
"prepare": "npm run build",
"example": "npm run build && node example.js",
"example": "node example.js",
"build": "tsc",
"test": "jest --coverage"
},
"dependencies": {
"@types/ref-array-di": "^1.2.5",
"ffi-napi": "^4.0.3",
"ref-array-di": "^1.2.2",
"ref-napi": "^3.0.3",
"ref-struct-di": "^1.1.1",
"ref-union-di": "^1.0.1"
"ffi-napi": "^4.0.3"
},
"publishConfig": {
"access": "public"
@@ -41,7 +36,7 @@
"@types/jest": "^29.2.0",
"@types/node": "^18.11.4",
"jest": "^29.2.2",
"prettier": "2.8.4",
"prettier": "2.8.2",
"ts-jest": "^29.0.3",
"ts-node": "^10.9.1",
"typedoc": "^0.23.18",

View File

@@ -1,100 +1,30 @@
import ffi from "ffi-napi";
import ref from "ref-napi";
import path from "path";
var ArrayType = require("ref-array-di")(ref);
var StructType = require("ref-struct-di")(ref);
var UnionType = require("ref-union-di")(ref);
const opaque = ref.types.void;
const context = ref.refType(opaque);
const function_t = ref.refType(opaque);
const pluginIndex = ref.types.int32;
let ValTypeArray = ArrayType(ref.types.int);
let PtrArray = new ArrayType("void*");
let ValUnion = new UnionType({
i32: ref.types.uint32,
i64: ref.types.uint64,
f32: ref.types.float,
f64: ref.types.double,
});
/**
* Val struct, low-level WebAssembly values
*/
let Val = new StructType({
t: ref.types.int,
v: ValUnion,
});
/**
* Array of `Val`
*/
let ValArray = ArrayType(Val);
const context = "void*";
const _functions = {
extism_context_new: [context, []],
extism_context_free: ["void", [context]],
extism_plugin_new: [
pluginIndex,
[context, "string", "uint64", PtrArray, "uint64", "bool"],
],
extism_plugin_new: ["int32", [context, "string", "uint64", "bool"]],
extism_plugin_update: [
"bool",
[context, pluginIndex, "string", "uint64", PtrArray, "uint64", "bool"],
[context, "int32", "string", "uint64", "bool"],
],
extism_error: ["string", [context, pluginIndex]],
extism_error: ["char*", [context, "int32"]],
extism_plugin_call: [
"int32",
[context, pluginIndex, "string", "string", "uint64"],
[context, "int32", "string", "string", "uint64"],
],
extism_plugin_output_length: ["uint64", [context, pluginIndex]],
extism_plugin_output_data: ["uint8*", [context, pluginIndex]],
extism_plugin_output_length: ["uint64", [context, "int32"]],
extism_plugin_output_data: ["uint8*", [context, "int32"]],
extism_log_file: ["bool", ["string", "char*"]],
extism_plugin_function_exists: ["bool", [context, pluginIndex, "string"]],
extism_plugin_config: ["void", [context, pluginIndex, "char*", "uint64"]],
extism_plugin_free: ["void", [context, pluginIndex]],
extism_plugin_function_exists: ["bool", [context, "int32", "string"]],
extism_plugin_config: ["void", [context, "int32", "char*", "uint64"]],
extism_plugin_free: ["void", [context, "int32"]],
extism_context_reset: ["void", [context]],
extism_version: ["string", []],
extism_function_new: [
function_t,
[
"string",
ValTypeArray,
"uint64",
ValTypeArray,
"uint64",
"void*",
"void*",
"void*",
],
],
extism_function_free: ["void", [function_t]],
extism_function_set_namespace: ["void", [function_t, "string"]],
extism_current_plugin_memory: ["uint8*", ["void*"]],
extism_current_plugin_memory_alloc: ["uint64", ["void*", "uint64"]],
extism_current_plugin_memory_length: ["uint64", ["void*", "uint64"]],
extism_current_plugin_memory_free: ["void", ["void*", "uint64"]],
extism_plugin_cancel_handle: ["void*", [context, pluginIndex]],
extism_plugin_cancel: ["bool", ["void*"]],
extism_version: ["char*", []],
};
/**
* An enumeration of all possible `Val` types
*/
export enum ValType {
I32 = 0,
I64,
F32,
F64,
V128,
FuncRef,
ExternRef,
}
interface LibExtism {
extism_context_new: () => Buffer;
extism_context_free: (ctx: Buffer) => void;
@@ -102,8 +32,6 @@ interface LibExtism {
ctx: Buffer,
data: string | Buffer,
data_len: number,
functions: Buffer,
nfunctions: number,
wasi: boolean
) => number;
extism_plugin_update: (
@@ -111,11 +39,9 @@ interface LibExtism {
plugin_id: number,
data: string | Buffer,
data_len: number,
functions: Buffer,
nfunctions: number,
wasi: boolean
) => boolean;
extism_error: (ctx: Buffer, plugin_id: number) => string;
extism_error: (ctx: Buffer, plugin_id: number) => Buffer;
extism_plugin_call: (
ctx: Buffer,
plugin_id: number,
@@ -124,7 +50,7 @@ interface LibExtism {
input_len: number
) => number;
extism_plugin_output_length: (ctx: Buffer, plugin_id: number) => number;
extism_plugin_output_data: (ctx: Buffer, plugin_id: number) => Uint8Array;
extism_plugin_output_data: (ctx: Buffer, plugin_id: Number) => Uint8Array;
extism_log_file: (file: string, level: string) => boolean;
extism_plugin_function_exists: (
ctx: Buffer,
@@ -139,25 +65,7 @@ interface LibExtism {
) => void;
extism_plugin_free: (ctx: Buffer, plugin_id: number) => void;
extism_context_reset: (ctx: Buffer) => void;
extism_version: () => string;
extism_function_new: (
name: string,
inputs: Buffer,
nInputs: number,
outputs: Buffer,
nOutputs: number,
f: Buffer,
user_data: Buffer | null,
free: Buffer | null
) => Buffer;
extism_function_set_namespace: (f: Buffer, s: string) => void;
extism_function_free: (f: Buffer) => void;
extism_current_plugin_memory: (p: Buffer) => Buffer;
extism_current_plugin_memory_alloc: (p: Buffer, n: number) => number;
extism_current_plugin_memory_length: (p: Buffer, n: number) => number;
extism_current_plugin_memory_free: (p: Buffer, n: number) => void;
extism_plugin_cancel_handle: (p: Buffer, n: number) => Buffer;
extism_plugin_cancel: (p: Buffer) => boolean;
extism_version: () => Buffer;
}
function locate(paths: string[]): LibExtism {
@@ -202,19 +110,19 @@ export function setLogFile(filename: string, level?: string) {
* @returns The version string of the Extism runtime
*/
export function extismVersion(): string {
return lib.extism_version();
return lib.extism_version().toString();
}
// @ts-ignore
const pluginRegistry = new FinalizationRegistry(({ id, pointer }) => {
if (id && pointer) lib.extism_plugin_free(pointer, id);
});
// @ts-ignore
const contextRegistry = new FinalizationRegistry((pointer) => {
if (pointer) lib.extism_context_free(pointer);
});
// @ts-ignore
const functionRegistry = new FinalizationRegistry((pointer) => {
if (pointer) lib.extism_function_free(pointer);
});
/**
* Represents a path or url to a WASM module
*/
@@ -253,7 +161,7 @@ export type ManifestWasm = ManifestWasmFile | ManifestWasmData;
/**
* The manifest which describes the {@link Plugin} code and
* runtime constraints.
*
*
* @see [Extism > Concepts > Manifest](https://extism.org/docs/concepts/manifest)
*/
export type Manifest = {
@@ -306,7 +214,7 @@ export class Context {
*/
constructor() {
this.pointer = lib.extism_context_new();
contextRegistry.register(this, this.pointer, this.pointer);
contextRegistry.register(this, this.pointer, this);
}
/**
@@ -317,24 +225,19 @@ export class Context {
* @param config - Config details for the plugin
* @returns A new Plugin scoped to this Context
*/
plugin(
manifest: ManifestData,
wasi: boolean = false,
functions: HostFunction[] = [],
config?: PluginConfig
) {
return new Plugin(this, manifest, wasi, functions, config);
plugin(manifest: ManifestData, wasi: boolean = false, config?: PluginConfig) {
return new Plugin(this, manifest, wasi, config);
}
/**
* Frees the context. Should be called after the context is not needed to reclaim the memory.
*/
free() {
contextRegistry.unregister(this.pointer);
if (this.pointer) {
contextRegistry.unregister(this);
lib.extism_context_free(this.pointer);
this.pointer = null;
}
this.pointer = null;
}
/**
@@ -365,226 +268,12 @@ export async function withContext(f: (ctx: Context) => Promise<any>) {
}
}
/**
* Provides access to the plugin that is currently running from inside a {@link HostFunction}
*/
export class CurrentPlugin {
pointer: Buffer;
constructor(pointer: Buffer) {
this.pointer = pointer;
}
/**
* Access plugin's memory
* @param offset - The offset in memory
* @returns a pointer to the provided offset
*/
memory(offset: number): Buffer {
let length = lib.extism_current_plugin_memory_length(this.pointer, offset);
return Buffer.from(
lib.extism_current_plugin_memory(this.pointer).buffer,
offset,
length
);
}
/**
* Allocate a new memory block
* @param n - The number of bytes to allocate
* @returns the offset to the newly allocated block
*/
memoryAlloc(n: number): number {
return lib.extism_current_plugin_memory_alloc(this.pointer, n);
}
/**
* Free a memory block
* @param offset - The offset of the block to free
*/
memoryFree(offset: number) {
return lib.extism_current_plugin_memory_free(this.pointer, offset);
}
/**
* Get the length of a memory block
* @param offset - The offset of the block
* @returns the length of the block specified by `offset`
*/
memoryLength(offset: number): number {
return lib.extism_current_plugin_memory_length(this.pointer, offset);
}
/**
* Return a string from a host function
* @param output - The output to set
* @param s - The string to return
*/
returnString(output: typeof Val, s: string) {
var offs = this.memoryAlloc(Buffer.byteLength(s));
this.memory(offs).write(s);
output.v.i64 = offs;
}
/**
* Return bytes from a host function
* @param output - The output to set
* @param b - The buffer to return
*/
returnBytes(output: typeof Val, b: Buffer) {
var offs = this.memoryAlloc(b.length);
this.memory(offs).fill(b);
output.v.i64 = offs;
}
/**
* Get bytes from host function parameter
* @param input - The input to read
*/
inputBytes(input: typeof Val): Buffer {
return this.memory(input.v.i64)
}
/**
* Get string from host function parameter
* @param input - The input to read
*/
inputString(input: typeof Val): string {
return this.memory(input.v.i64).toString()
}
}
/**
* Allows for the host to define functions that can be called from WebAseembly
*/
export class HostFunction {
callback: any;
pointer: Buffer | null;
name: string;
userData: any[];
inputs: typeof ValTypeArray;
outputs: typeof ValTypeArray;
constructor(
name: string,
inputs: ValType[],
outputs: ValType[],
f: any,
...userData: any
) {
this.userData = userData;
this.callback = ffi.Callback(
"void",
[
"void*",
ref.refType(Val),
"uint64",
ref.refType(Val),
"uint64",
"void*",
],
(
currentPlugin: Buffer,
inputs: Buffer,
nInputs: number,
outputs: Buffer,
nOutputs: number,
user_data
) => {
let inputArr = [];
let outputArr = [];
for (var i = 0; i < nInputs; i++) {
inputArr.push(Val.get(inputs, i));
}
for (var i = 0; i < nOutputs; i++) {
outputArr.push(Val.get(outputs, i));
}
f(
new CurrentPlugin(currentPlugin),
inputArr,
outputArr,
...this.userData
);
for (var i = 0; i < nOutputs; i++) {
Val.set(outputs, i, outputArr[i]);
}
}
);
this.name = name;
this.inputs = new ValTypeArray(inputs);
this.outputs = new ValTypeArray(outputs);
this.pointer = lib.extism_function_new(
this.name,
this.inputs,
this.inputs.length,
this.outputs,
this.outputs.length,
this.callback,
null,
null
);
this.userData = userData;
functionRegistry.register(this, this.pointer, this.pointer);
}
/**
* Set function namespace
*/
setNamespace(name: string) {
if (this.pointer !== null) {
lib.extism_function_set_namespace(this.pointer, name)
}
}
withNamespace(name: string) : HostFunction {
this.setNamespace(name)
return this;
}
/**
* Free a host function - this should be called to cleanup the associated resources
*/
free() {
functionRegistry.unregister(this.pointer);
if (this.pointer === null) {
return;
}
lib.extism_function_free(this.pointer);
this.pointer = null;
}
}
/**
* CancelHandle is used to cancel a running Plugin
*/
export class CancelHandle {
handle: Buffer
constructor(handle: Buffer) {
this.handle = handle;
}
/**
* Cancel execution of the Plugin associated with the CancelHandle
*/
cancel(): boolean {
return lib.extism_plugin_cancel(this.handle);
}
}
/**
* A Plugin represents an instance of your WASM program from the given manifest.
*/
export class Plugin {
id: number;
ctx: Context;
functions: typeof PtrArray;
token: { id: number; pointer: Buffer };
/**
* Constructor for a plugin. @see {@link Context#plugin}.
@@ -592,14 +281,12 @@ export class Plugin {
* @param ctx - The context to manage this plugin
* @param manifest - The {@link Manifest}
* @param wasi - Set to true to enable WASI support
* @param functions - An array of {@link HostFunction}
* @param config - The plugin config
*/
constructor(
ctx: Context,
manifest: ManifestData,
wasi: boolean = false,
functions: HostFunction[] = [],
config?: PluginConfig
) {
let dataRaw: string | Buffer;
@@ -611,16 +298,10 @@ export class Plugin {
throw Error(`Unknown manifest type ${typeof manifest}`);
}
if (!ctx.pointer) throw Error("No Context set");
this.functions = new PtrArray(functions.length);
for (var i = 0; i < functions.length; i++) {
this.functions[i] = functions[i].pointer;
}
let plugin = lib.extism_plugin_new(
ctx.pointer,
dataRaw,
Buffer.byteLength(dataRaw, "utf-8"),
this.functions,
functions.length,
Buffer.byteLength(dataRaw, 'utf-8'),
wasi
);
if (plugin < 0) {
@@ -631,43 +312,27 @@ export class Plugin {
throw `Unable to load plugin: ${err.toString()}`;
}
this.id = plugin;
this.token = { id: this.id, pointer: ctx.pointer };
this.ctx = ctx;
pluginRegistry.register(
this,
{ id: this.id, pointer: this.ctx.pointer },
this
);
if (config != null) {
let s = JSON.stringify(config);
lib.extism_plugin_config(
ctx.pointer,
this.id,
s,
Buffer.byteLength(s, "utf-8")
);
lib.extism_plugin_config(ctx.pointer, this.id, s, Buffer.byteLength(s, 'utf-8'),);
}
}
/**
* Return a new `CancelHandle`, which can be used to cancel a running Plugin
*/
cancelHandle(): CancelHandle {
if (!this.ctx.pointer) throw Error("No Context set");
let handle = lib.extism_plugin_cancel_handle(this.ctx.pointer, this.id);
return new CancelHandle(handle);
}
/**
* Update an existing plugin with new WASM or manifest
*
* @param manifest - The new {@link Manifest} data
* @param wasi - Set to true to enable WASI support
* @param functions - An array of {@link HostFunction}
* @param config - The new plugin config
*/
update(
manifest: ManifestData,
wasi: boolean = false,
functions: HostFunction[] = [],
config?: PluginConfig
) {
update(manifest: ManifestData, wasi: boolean = false, config?: PluginConfig) {
let dataRaw: string | Buffer;
if (Buffer.isBuffer(manifest) || typeof manifest === "string") {
dataRaw = manifest;
@@ -677,17 +342,11 @@ export class Plugin {
throw Error("Unknown manifest type type");
}
if (!this.ctx.pointer) throw Error("No Context set");
this.functions = new PtrArray(functions.length);
for (var i = 0; i < functions.length; i++) {
this.functions[i] = functions[i].pointer;
}
const ok = lib.extism_plugin_update(
this.ctx.pointer,
this.id,
dataRaw,
Buffer.byteLength(dataRaw, "utf-8"),
this.functions,
functions.length,
Buffer.byteLength(dataRaw, 'utf-8'),
wasi
);
if (!ok) {
@@ -700,12 +359,7 @@ export class Plugin {
if (config != null) {
let s = JSON.stringify(config);
lib.extism_plugin_config(
this.ctx.pointer,
this.id,
s,
Buffer.byteLength(s, "utf-8")
);
lib.extism_plugin_config(this.ctx.pointer, this.id, s, Buffer.byteLength(s, 'utf-8'),);
}
}
@@ -739,7 +393,7 @@ export class Plugin {
*
* @param functionName - The name of the function
* @param input - The input data
*@returns A Buffer repreesentation of the output
* @returns A Buffer repreesentation of the output
*/
async call(functionName: string, input: string | Buffer): Promise<Buffer> {
return new Promise<Buffer>((resolve, reject) => {
@@ -749,7 +403,7 @@ export class Plugin {
this.id,
functionName,
input.toString(),
Buffer.byteLength(input, "utf-8")
Buffer.byteLength(input, 'utf-8'),
);
if (rc !== 0) {
var err = lib.extism_error(this.ctx.pointer, this.id);
@@ -773,7 +427,8 @@ export class Plugin {
* Free a plugin, this should be called when the plugin is no longer needed
*/
free() {
if (this.ctx.pointer && this.id >= 0) {
if (this.ctx.pointer && this.id !== -1) {
pluginRegistry.unregister(this);
lib.extism_plugin_free(this.ctx.pointer, this.id);
this.id = -1;
}

BIN
node/tests/code.wasm Executable file

Binary file not shown.

View File

@@ -2,23 +2,14 @@ import * as extism from "../src/index";
import { readFileSync } from "fs";
import { join } from "path";
function manifest(functions: boolean = false): extism.Manifest {
function manifest(): extism.Manifest {
return {
wasm: [
{
path: join(
__dirname,
functions
? "/../../wasm/code-functions.wasm"
: "/../../wasm/code.wasm"
),
},
],
wasm: [{ path: join(__dirname, "/code.wasm") }],
};
}
function wasmBuffer(): Buffer {
return readFileSync(join(__dirname, "/../../wasm/code.wasm"));
return readFileSync(join(__dirname, "/code.wasm"));
}
describe("test extism", () => {
@@ -111,27 +102,4 @@ describe("test extism", () => {
).rejects.toMatch(/Plugin error/);
});
});
test("host functions work", async () => {
await extism.withContext(async (ctx: extism.Context) => {
const plugin = ctx.plugin(manifest(true), true, [
new extism.HostFunction(
"hello_world",
[extism.ValType.I64],
[extism.ValType.I64],
(plugin: any, params: any, results: any, user_data: string) => {
const offs = plugin.memoryAlloc(user_data.length);
const mem = plugin.memory(offs);
mem.write(user_data);
results[0].v.i64 = offs;
},
"test"
),
]);
const res = await plugin.call("count_vowels", "aaa");
expect(res.toString()).toBe("test");
});
});
});

View File

@@ -1,15 +0,0 @@
VERSION?=0.2.0
build:
dune build
test:
dune test
format:
dune build @fmt --auto-promote
prepare:
opam install .. --deps-only
publish:
opam publish -v $(VERSION) -t $(VERSION) ..

View File

@@ -7,7 +7,7 @@ let main file func_name input =
with_context @@ fun ctx ->
let input = if String.equal input "-" then read_stdin () else input in
let file = In_channel.with_open_bin file In_channel.input_all in
let plugin = Plugin.create ctx file ~wasi:true |> Result.get_ok in
let plugin = Plugin.make ctx file ~wasi:true |> Result.get_ok in
let res = Plugin.call plugin ~name:func_name input |> Result.get_ok in
print_endline res

View File

@@ -27,16 +27,7 @@ let locate () =
init paths
|> function
| Some x -> x
| None -> (
let fail n =
Printf.fprintf stderr
"Unable to find Extism installation, see \
https://extism.org/docs/install/ for installation instructions\n";
exit n
in
match Sys.getenv_opt "EXTISM_TEST_NO_LIB" with
| None -> fail 1
| Some _ -> fail 0)
| None -> raise Not_found
let from =
let filename = locate () in
@@ -49,62 +40,13 @@ let context = ptr void
let extism_context_new = fn "extism_context_new" (void @-> returning context)
let extism_context_free = fn "extism_context_free" (context @-> returning void)
module Extism_val_type = struct
type t = I32 | I64 | F32 | F64 | V128 | FuncRef | ExternRef
let to_int = function
| I32 -> 0
| I64 -> 1
| F32 -> 2
| F64 -> 3
| V128 -> 4
| FuncRef -> 5
| ExternRef -> 6
let of_int = function
| 0 -> I32
| 1 -> I64
| 2 -> F32
| 3 -> F64
| 4 -> V128
| 5 -> FuncRef
| 6 -> ExternRef
| n -> invalid_arg ("Extism_val_type.of_int: " ^ string_of_int n)
let t : t typ = view ~read:of_int ~write:to_int int
end
module Extism_val_union = struct
type t
let t : t union typ = union "ExtismValUnion"
let i32 = field t "i32" int32_t
let i64 = field t "i64" int64_t
let f32 = field t "f32" float
let f64 = field t "f64" double
let () = seal t
end
module Extism_val = struct
type t
let t : t structure typ = structure "ExtismVal"
let ty = field t "t" Extism_val_type.t
let v = field t "v" Extism_val_union.t
let () = seal t
end
let extism_plugin_new =
fn "extism_plugin_new"
(context @-> string @-> uint64_t
@-> ptr (ptr void)
@-> uint64_t @-> bool @-> returning int32_t)
(context @-> string @-> uint64_t @-> bool @-> returning int32_t)
let extism_plugin_update =
fn "extism_plugin_update"
(context @-> int32_t @-> string @-> uint64_t
@-> ptr (ptr void)
@-> uint64_t @-> bool @-> returning bool)
(context @-> int32_t @-> string @-> uint64_t @-> bool @-> returning bool)
let extism_plugin_config =
fn "extism_plugin_config"
@@ -142,44 +84,3 @@ let extism_context_reset = fn "extism_context_reset" (context @-> returning void
let extism_plugin_function_exists =
fn "extism_plugin_function_exists"
(context @-> int32_t @-> string @-> returning bool)
let extism_function_type =
Foreign.funptr ~runtime_lock:true
(ptr void @-> ptr Extism_val.t @-> uint64_t @-> ptr Extism_val.t
@-> uint64_t @-> ptr void @-> returning void)
let extism_free_user_data =
Foreign.funptr_opt ~runtime_lock:true (ptr void @-> returning void)
let extism_function_new =
fn "extism_function_new"
(string @-> ptr Extism_val_type.t @-> uint64_t @-> ptr Extism_val_type.t
@-> uint64_t @-> extism_function_type @-> ptr void @-> extism_free_user_data
@-> returning (ptr void))
let extism_function_set_namespace =
fn "extism_function_set_namespace" (ptr void @-> string @-> returning void)
let extism_function_free =
fn "extism_function_free" (ptr void @-> returning void)
let extism_current_plugin_memory =
fn "extism_current_plugin_memory" (ptr void @-> returning (ptr uint8_t))
let extism_current_plugin_memory_length =
fn "extism_current_plugin_memory_length"
(ptr void @-> uint64_t @-> returning uint64_t)
let extism_current_plugin_memory_alloc =
fn "extism_current_plugin_memory_alloc"
(ptr void @-> uint64_t @-> returning uint64_t)
let extism_current_plugin_memory_free =
fn "extism_current_plugin_memory_free"
(ptr void @-> uint64_t @-> returning void)
let extism_plugin_cancel_handle =
fn "extism_plugin_cancel_handle" (context @-> int32_t @-> returning (ptr void))
let extism_plugin_cancel =
fn "extism_plugin_cancel" (ptr void @-> returning bool)

View File

@@ -1,84 +0,0 @@
open Ctypes
type t = unit ptr
type memory_block = { offs : Unsigned.UInt64.t; len : Unsigned.UInt64.t }
let memory ?(offs = Unsigned.UInt64.zero) t =
Bindings.extism_current_plugin_memory t +@ Unsigned.UInt64.to_int offs
let find t offs =
let len = Bindings.extism_current_plugin_memory_length t offs in
if Unsigned.UInt64.(equal zero len) then None else Some { offs; len }
let alloc t len =
let len = Unsigned.UInt64.of_int len in
let offs = Bindings.extism_current_plugin_memory_alloc t len in
{ offs; len }
let free t { offs; _ } = Bindings.extism_current_plugin_memory_free t offs
module Memory_block = struct
let of_val t v =
match Types.Val.to_i64 v with
| None -> None
| Some v ->
let offs = Unsigned.UInt64.of_int64 v in
find t offs
let of_val_exn t v =
match of_val t v with
| None -> invalid_arg "Memory_block.of_val_exn"
| Some v -> v
let to_val { offs; len = _ } =
Types.Val.of_i64 (Unsigned.UInt64.to_int64 offs)
let get_bigstring t { offs; len } : Bigstringaf.t =
let p = memory t ~offs in
bigarray_of_ptr array1
(Unsigned.UInt64.to_int len)
Bigarray.Char
(coerce (ptr uint8_t) (ptr char) p)
let get_string t { offs; len } =
let p = memory t ~offs in
Ctypes.string_from_ptr
(coerce (ptr uint8_t) (ptr char) p)
~length:(Unsigned.UInt64.to_int len)
let set_bigstring t { offs; len } bs =
let length = min (Unsigned.UInt64.to_int @@ len) (Bigstringaf.length bs) in
let p = coerce (ptr uint8_t) (ptr char) @@ memory t ~offs in
for i = 0 to length - 1 do
p +@ i <-@ Bigstringaf.unsafe_get bs i
done
let set_string t { offs; len } s =
let length = min (Unsigned.UInt64.to_int @@ len) (String.length s) in
let p = coerce (ptr uint8_t) (ptr char) @@ memory t ~offs in
for i = 0 to length - 1 do
p +@ i <-@ String.unsafe_get s i
done
end
let return_string t (outputs : Types.Val_array.t) index s =
let mem = alloc t (String.length s) in
Memory_block.set_string t mem s;
Types.Val_array.(
outputs.$[index] <- Types.Val.of_i64 (Unsigned.UInt64.to_int64 mem.offs))
let return_bigstring t (outputs : Types.Val_array.t) index s =
let mem = alloc t (Bigstringaf.length s) in
Memory_block.set_bigstring t mem s;
Types.Val_array.(
outputs.$[index] <- Types.Val.of_i64 (Unsigned.UInt64.to_int64 mem.offs))
let input_string t inputs index =
let inp = Types.Val_array.(inputs.$[index]) in
let mem = Memory_block.of_val_exn t inp in
Memory_block.get_string t mem
let input_bigstring t inputs index =
let inp = Types.Val_array.(inputs.$[index]) in
let mem = Memory_block.of_val_exn t inp in
Memory_block.get_bigstring t mem

View File

@@ -2,7 +2,7 @@
(name extism)
(public_name extism)
(inline_tests
(deps test/code.wasm test/code-functions.wasm))
(deps test/code.wasm))
(libraries ctypes.foreign bigstringaf extism-manifest)
(preprocess
(pps ppx_yojson_conv ppx_inline_test)))

View File

@@ -8,4 +8,3 @@ let () =
| _ -> None)
let unwrap = function Ok x -> x | Error t -> raise (Error t)
let throw e = raise (Error e)

View File

@@ -2,9 +2,6 @@ module Manifest = Extism_manifest
module Error = Error
module Context = Context
module Plugin = Plugin
module Function = Function
module Current_plugin = Current_plugin
include Types
let with_context = Plugin.with_context
let extism_version = Bindings.extism_version

View File

@@ -1,201 +1,36 @@
(** Extism bindings for OCaml *)
val extism_version : unit -> string
(** Returns the libextism version, not the version of the OCaml library *)
val extism_version : unit -> string
module Manifest = Extism_manifest
module Error : sig
type t = [ `Msg of string ]
type t = [`Msg of string]
exception Error of t
val unwrap : ('a, t) result -> 'a
val throw : t -> 'a
end
(** [Val_type] enumerates every possible argument/result type *)
module Val_type : sig
type t =
| I32
| I64
| F32
| F64
| V128
| FuncRef
| ExternRef (** Value type *)
val of_int : int -> t
val to_int : t -> int
end
(** [Val] represents low-level WebAssembly values *)
module Val : sig
type t
(** Val *)
val ty : t -> Val_type.t
(** [ty v] returns the [Val_type.t] for the value [v] *)
val of_i32 : int32 -> t
(** Create an i32 [Val] *)
val of_i64 : int64 -> t
(** Create an i64 [Val] *)
val of_f32 : float -> t
(** Create an f32 [Val] *)
val of_f64 : float -> t
(** Create an f64 [Val] *)
val to_i32 : t -> int32 option
(** Get an int32 from [Val] if the type matches *)
val to_i64 : t -> int64 option
(** Get an int64 from [Val] if the type matches *)
val to_f32 : t -> float option
(** Get a f32 from [Val] if the type matches *)
val to_f64 : t -> float option
(** Get an f64 from [Val] if the type matches *)
val to_i32_exn : t -> int32
(** Same as [to_i32] but raises an exception if the types don't match*)
val to_i64_exn : t -> int64
(** Same as [to_i64] but raises an exception if the types don't match*)
val to_f32_exn : t -> float
(** Same as [to_f32] but raises an exception if the types don't match*)
val to_f64_exn : t -> float
(** Same as [to_f64] but raises an exception if the types don't match*)
end
(** [Val_array] is used for input/output parameters for host functions *)
module Val_array : sig
type t = Val.t Ctypes.CArray.t
(** [Val_array] type *)
val get : t -> int -> Val.t
(** Get an index *)
val set : t -> int -> Val.t -> unit
(** Set an index *)
val length : t -> int
(** Get the number of items in a [Val_array]*)
val ( .$[] ) : t -> int -> Val.t
(** Syntax for [get] *)
val ( .$[]<- ) : t -> int -> Val.t -> unit
(** Syntax for [set] *)
end
(** [Current_plugin] represents the plugin that is currently running, it should
it should only be used from a host function *)
module Current_plugin : sig
type t
(** Opaque type, wraps [ExtismCurrentPlugin] *)
type memory_block = { offs : Unsigned.UInt64.t; len : Unsigned.UInt64.t }
(** Represents a block of guest memory *)
val memory : ?offs:Unsigned.UInt64.t -> t -> Unsigned.uint8 Ctypes.ptr
(** Get pointer to entire plugin memory *)
val find : t -> Unsigned.UInt64.t -> memory_block option
(** Find memory block *)
val alloc : t -> int -> memory_block
(** Allocate a new block of memory *)
val free : t -> memory_block -> unit
(** Free an allocated block of memory *)
val return_string : t -> Val_array.t -> int -> string -> unit
val return_bigstring : t -> Val_array.t -> int -> Bigstringaf.t -> unit
val input_string : t -> Val_array.t -> int -> string
val input_bigstring : t -> Val_array.t -> int -> Bigstringaf.t
(** Some helpter functions for reading/writing memory *)
module Memory_block : sig
val to_val : memory_block -> Val.t
(** Convert memory block to [Val] *)
val of_val : t -> Val.t -> memory_block option
(** Convert [Val] to memory block *)
val of_val_exn : t -> Val.t -> memory_block
(** Convert [Val] to memory block, raises [Invalid_argument] if the value is not a pointer
to a valid memory block *)
val get_string : t -> memory_block -> string
(** Get a string from memory stored at the provided offset *)
val get_bigstring : t -> memory_block -> Bigstringaf.t
(** Get a bigstring from memory stored at the provided offset *)
val set_string : t -> memory_block -> string -> unit
(** Store a string into memory at the provided offset *)
val set_bigstring : t -> memory_block -> Bigstringaf.t -> unit
(** Store a bigstring into memory at the provided offset *)
end
end
(** [Function] is used to create new a new function, which can be called
from a WebAssembly plugin *)
module Function : sig
type t
(** Function type *)
val create :
string ->
?namespace:string ->
params:Val_type.t list ->
results:Val_type.t list ->
user_data:'a ->
(Current_plugin.t -> Val_array.t -> Val_array.t -> 'a -> unit) ->
t
(** Create a new function, [Function.v name ~params ~results ~user_data f] creates
a new [Function] with the given [name], [params] specifies the argument types,
[results] specifies the return types, [user_data] is used to pass arbitrary
OCaml values into the function and [f] is the OCaml function that will be
called.
*)
val with_namespace : t -> string -> t
(** Update a function's namespace *)
val free : t -> unit
(** Free a function *)
val free_all : t list -> unit
(** Free a list of functions *)
val unwrap: ('a, t) result -> 'a
end
(** [Context] is used to group plugins *)
module Context : sig
type t
(** Context type *)
type t
val create : unit -> t
(** Create a new context *)
val create : unit -> t
val free : t -> unit
(** Free a context. All plugins will be removed and the value should not be
accessed after this call *)
val free : t -> unit
val reset : t -> unit
(** Reset a context. All plugins will be removed *)
val reset : t -> unit
end
val with_context : (Context.t -> 'a) -> 'a
(** Execute a function with a fresh context and free it after *)
val with_context : (Context.t -> 'a) -> 'a
val set_log_file :
?level:[ `Error | `Warn | `Info | `Debug | `Trace ] -> string -> bool
@@ -204,53 +39,40 @@ val set_log_file :
module Plugin : sig
type t
val create :
(** Make a new plugin from raw WebAssembly or JSON encoded manifest *)
val make :
?config:Manifest.config ->
?wasi:bool ->
?functions:Function.t list ->
Context.t ->
string ->
(t, Error.t) result
(** Make a new plugin from raw WebAssembly or JSON encoded manifest *)
val of_manifest :
?wasi:bool ->
?functions:Function.t list ->
Context.t ->
Manifest.t ->
(t, Error.t) result
(** Make a new plugin from a [Manifest] *)
val of_manifest :
?wasi:bool -> Context.t -> Manifest.t -> (t, Error.t) result
(** Update a plugin from raw WebAssembly or JSON encoded manifest *)
val update :
t ->
?config:(string * string option) list ->
?wasi:bool ->
?functions:Function.t list ->
string ->
(unit, [ `Msg of string ]) result
(** Update a plugin from raw WebAssembly or JSON encoded manifest *)
val update_manifest : t -> ?wasi:bool -> Manifest.t -> (unit, Error.t) result
(** Update a plugin from a [Manifest] *)
val update_manifest :
t -> ?wasi:bool -> Manifest.t -> (unit, Error.t) result
(** Call a function, uses [Bigstringaf.t] for input/output *)
val call_bigstring :
t -> name:string -> Bigstringaf.t -> (Bigstringaf.t, Error.t) result
(** Call a function, uses [Bigstringaf.t] for input/output *)
val call : t -> name:string -> string -> (string, Error.t) result
(** Call a function, uses [string] for input/output *)
val call : t -> name:string -> string -> (string, Error.t) result
val free : t -> unit
(** Drop a plugin *)
val free : t -> unit
val function_exists : t -> string -> bool
(** Check if a function is exported by a plugin *)
module Cancel_handle: sig
type t
val cancel: t -> bool
end
val cancel_handle: t -> Cancel_handle.t
val function_exists : t -> string -> bool
end

View File

@@ -1,47 +0,0 @@
open Ctypes
type t = {
mutable pointer : unit ptr;
mutable user_data : unit ptr;
name : string;
}
let free t =
let () =
if not (is_null t.user_data) then
let () = Root.release t.user_data in
t.user_data <- null
in
if not (is_null t.pointer) then
let () = Bindings.extism_function_free t.pointer in
t.pointer <- null
let free_all l = List.iter free l
let create name ?namespace ~params ~results ~user_data f =
let inputs = CArray.of_list Bindings.Extism_val_type.t params in
let n_inputs = Unsigned.UInt64.of_int (CArray.length inputs) in
let outputs = CArray.of_list Bindings.Extism_val_type.t results in
let n_outputs = Unsigned.UInt64.of_int (CArray.length outputs) in
let free' = Some Root.release in
let user_data = Root.create user_data in
let f current inputs n_inputs outputs n_outputs user_data =
let user_data = Root.get user_data in
let inputs = CArray.from_ptr inputs (Unsigned.UInt64.to_int n_inputs) in
let outputs = CArray.from_ptr outputs (Unsigned.UInt64.to_int n_outputs) in
f current inputs outputs user_data
in
let pointer =
Bindings.extism_function_new name (CArray.start inputs) n_inputs
(CArray.start outputs) n_outputs f user_data free'
in
let () =
Option.iter (Bindings.extism_function_set_namespace pointer) namespace
in
let t = { pointer; user_data; name } in
Gc.finalise free t;
t
let with_namespace f ns =
Bindings.extism_function_set_namespace f.pointer ns;
f

View File

@@ -1,6 +1,6 @@
module Manifest = Extism_manifest
type t = { id : int32; ctx : Context.t; mutable functions : Function.t list }
type t = { id : int32; ctx : Context.t }
let with_context f =
let ctx = Context.create () in
@@ -26,15 +26,10 @@ let free t =
if not (Ctypes.is_null t.ctx.pointer) then
Bindings.extism_plugin_free t.ctx.pointer t.id
let create ?config ?(wasi = false) ?(functions = []) ctx wasm =
let func_ptrs = List.map (fun x -> x.Function.pointer) functions in
let arr = Ctypes.CArray.of_list Ctypes.(ptr void) func_ptrs in
let n_funcs = Ctypes.CArray.length arr in
let make ?config ?(wasi = false) ctx wasm =
let id =
Bindings.extism_plugin_new ctx.Context.pointer wasm
(Unsigned.UInt64.of_int (String.length wasm))
(Ctypes.CArray.start arr)
(Unsigned.UInt64.of_int n_funcs)
wasi
in
if id < 0l then
@@ -42,33 +37,28 @@ let create ?config ?(wasi = false) ?(functions = []) ctx wasm =
| None -> Error (`Msg "extism_plugin_call failed")
| Some msg -> Error (`Msg msg)
else
let t = { id; ctx; functions } in
let t = { id; ctx } in
if not (set_config t config) then Error (`Msg "call to set_config failed")
else
let () = Gc.finalise free t in
Ok t
let of_manifest ?wasi ?functions ctx manifest =
let data = Manifest.to_json manifest in
create ctx ?wasi ?functions data
let of_manifest ?wasi ctx manifest =
let data = Manifest.json manifest in
make ctx ?wasi data
let%test "free plugin" =
let manifest = Manifest.(create [ Wasm.file "test/code.wasm" ]) in
let manifest = Manifest.v [ Manifest.file "test/code.wasm" ] in
with_context (fun ctx ->
let plugin = of_manifest ctx manifest |> Error.unwrap in
let plugin = of_manifest ctx manifest |> Result.get_ok in
free plugin;
true)
let update plugin ?config ?(wasi = false) ?(functions = []) wasm =
let { id; ctx; _ } = plugin in
let func_ptrs = List.map (fun x -> x.Function.pointer) functions in
let arr = Ctypes.CArray.of_list Ctypes.(ptr void) func_ptrs in
let n_funcs = Ctypes.CArray.length arr in
let update plugin ?config ?(wasi = false) wasm =
let { id; ctx } = plugin in
let ok =
Bindings.extism_plugin_update ctx.pointer id wasm
(Unsigned.UInt64.of_int (String.length wasm))
(Ctypes.CArray.start arr)
(Unsigned.UInt64.of_int n_funcs)
wasi
in
if not ok then
@@ -80,18 +70,18 @@ let update plugin ?config ?(wasi = false) ?(functions = []) wasm =
else Ok ()
let update_manifest plugin ?wasi manifest =
let data = Manifest.to_json manifest in
let data = Manifest.json manifest in
update plugin ?wasi data
let%test "update plugin manifest and config" =
let manifest = Manifest.(create [ Wasm.file "test/code.wasm" ]) in
let manifest = Manifest.v [ Manifest.file "test/code.wasm" ] in
with_context (fun ctx ->
let config = [ ("a", Some "1") ] in
let plugin = of_manifest ctx manifest |> Error.unwrap in
let plugin = of_manifest ctx manifest |> Result.get_ok in
let manifest = Manifest.with_config manifest config in
update_manifest plugin manifest |> Result.is_ok)
let call' f { id; ctx; _ } ~name input len =
let call' f { id; ctx } ~name input len =
let rc = f ctx.pointer id name input len in
if rc <> 0l then
match Bindings.extism_error ctx.pointer id with
@@ -113,12 +103,12 @@ let call_bigstring (t : t) ~name input =
call' Bindings.extism_plugin_call t ~name ptr len
let%test "call_bigstring" =
let manifest = Manifest.(create [ Wasm.file "test/code.wasm" ]) in
let manifest = Manifest.v [ Manifest.file "test/code.wasm" ] in
with_context (fun ctx ->
let plugin = of_manifest ctx manifest |> Error.unwrap in
let plugin = of_manifest ctx manifest |> Result.get_ok in
call_bigstring plugin ~name:"count_vowels"
(Bigstringaf.of_string ~off:0 ~len:14 "this is a test")
|> Error.unwrap |> Bigstringaf.to_string = "{\"count\": 4}")
|> Result.get_ok |> Bigstringaf.to_string = "{\"count\": 4}")
let call (t : t) ~name input =
let len = String.length input in
@@ -126,49 +116,18 @@ let call (t : t) ~name input =
|> Result.map Bigstringaf.to_string
let%test "call" =
let manifest = Manifest.(create [ Wasm.file "test/code.wasm" ]) in
let manifest = Manifest.v [ Manifest.file "test/code.wasm" ] in
with_context (fun ctx ->
let plugin = of_manifest ctx manifest |> Error.unwrap in
let plugin = of_manifest ctx manifest |> Result.get_ok in
call plugin ~name:"count_vowels" "this is a test"
|> Error.unwrap = "{\"count\": 4}")
|> Result.get_ok = "{\"count\": 4}")
let%test "call_functions" =
let open Types.Val_type in
let hello_world =
Function.create "hello_world" ~params:[ I64 ] ~results:[ I64 ]
~user_data:"Hello again!"
@@ fun plugin params results user_data ->
let open Types.Val_array in
let s = Current_plugin.input_string plugin params 0 in
let () = print_endline "Hello from OCaml!" in
let () = print_endline user_data in
let () = print_endline s in
results.$[0] <- params.$[0]
in
let functions = [ hello_world ] in
let manifest = Manifest.(create [ Wasm.file "test/code-functions.wasm" ]) in
with_context (fun ctx ->
let plugin =
of_manifest ctx manifest ~functions ~wasi:true |> Error.unwrap
in
call plugin ~name:"count_vowels" "this is a test"
|> Error.unwrap = "{\"count\": 4}")
let function_exists { id; ctx; _ } name =
let function_exists { id; ctx } name =
Bindings.extism_plugin_function_exists ctx.pointer id name
let%test "function exists" =
let manifest = Manifest.(create [ Wasm.file "test/code.wasm" ]) in
let manifest = Manifest.v [ Manifest.file "test/code.wasm" ] in
with_context (fun ctx ->
let plugin = of_manifest ctx manifest |> Error.unwrap in
let plugin = of_manifest ctx manifest |> Result.get_ok in
function_exists plugin "count_vowels"
&& not (function_exists plugin "function_does_not_exist"))
module Cancel_handle = struct
type t = { inner : unit Ctypes.ptr }
let cancel { inner } = Bindings.extism_plugin_cancel inner
end
let cancel_handle { id; ctx; _ } =
Cancel_handle.{ inner = Bindings.extism_plugin_cancel_handle ctx.pointer id }

View File

@@ -1,92 +0,0 @@
open Ctypes
module Val_type = Bindings.Extism_val_type
module Val = struct
type t = (Bindings.Extism_val.t, [ `Struct ]) Ctypes.structured
let t = Bindings.Extism_val.t
let of_i32 (x : int32) : t =
let u = Ctypes.make Bindings.Extism_val_union.t in
u @. Bindings.Extism_val_union.i32 <-@ x;
let t = Ctypes.make Bindings.Extism_val.t in
t @. Bindings.Extism_val.ty <-@ Val_type.I32;
t @. Bindings.Extism_val.v <-@ u;
t
let of_i64 (x : int64) : t =
let u = Ctypes.make Bindings.Extism_val_union.t in
u @. Bindings.Extism_val_union.i64 <-@ x;
let t = Ctypes.make Bindings.Extism_val.t in
t @. Bindings.Extism_val.ty <-@ Val_type.I64;
t @. Bindings.Extism_val.v <-@ u;
t
let of_f32 (x : float) : t =
let u = Ctypes.make Bindings.Extism_val_union.t in
u @. Bindings.Extism_val_union.f32 <-@ x;
let t = Ctypes.make Bindings.Extism_val.t in
t @. Bindings.Extism_val.ty <-@ Val_type.F32;
t @. Bindings.Extism_val.v <-@ u;
t
let of_f64 (x : float) : t =
let u = Ctypes.make Bindings.Extism_val_union.t in
u @. Bindings.Extism_val_union.f64 <-@ x;
let t = Ctypes.make Bindings.Extism_val.t in
t @. Bindings.Extism_val.ty <-@ Val_type.F64;
t @. Bindings.Extism_val.v <-@ u;
t
let to_i32 t : int32 option =
let ty = t @. Bindings.Extism_val.ty in
let v = t @. Bindings.Extism_val.v in
match !@ty with
| Bindings.Extism_val_type.I32 ->
Some !@(!@v @. Bindings.Extism_val_union.i32)
| _ -> None
let to_i64 t : int64 option =
let ty = t @. Bindings.Extism_val.ty in
let v = t @. Bindings.Extism_val.v in
match !@ty with
| Bindings.Extism_val_type.I64 ->
Some !@(!@v @. Bindings.Extism_val_union.i64)
| _ -> None
let to_f32 t : float option =
let ty = t @. Bindings.Extism_val.ty in
let v = t @. Bindings.Extism_val.v in
match !@ty with
| Bindings.Extism_val_type.F32 ->
Some !@(!@v @. Bindings.Extism_val_union.f32)
| _ -> None
let to_f64 t : float option =
let ty = t @. Bindings.Extism_val.ty in
let v = t @. Bindings.Extism_val.v in
match !@ty with
| Bindings.Extism_val_type.F64 ->
Some !@(!@v @. Bindings.Extism_val_union.f64)
| _ -> None
let ty t = !@(t @. Bindings.Extism_val.ty)
let make_exn f x =
match f x with Some x -> x | None -> Error.throw (`Msg "invalid type")
let to_i32_exn = make_exn to_i32
let to_i64_exn = make_exn to_i64
let to_f32_exn = make_exn to_f32
let to_f64_exn = make_exn to_f64
end
module Val_array = struct
type t = Val.t Ctypes.CArray.t
let get t i = Ctypes.CArray.get t i
let set t i x = Ctypes.CArray.set t i x
let length t = Ctypes.CArray.length t
let ( .$[] ) = get
let ( .$[]<- ) = set
end

View File

@@ -1,7 +1,6 @@
(library
(name extism_manifest)
(public_name extism-manifest)
(inline_tests)
(libraries base64)
(preprocess
(pps ppx_yojson_conv ppx_inline_test)))
(pps ppx_yojson_conv)))

View File

@@ -1,9 +1,22 @@
type memory = { max_pages : int option [@yojson.option] } [@@deriving yojson]
type wasm_file = {
path : string;
name : string option; [@yojson.option]
hash : string option; [@yojson.option]
}
[@@deriving yojson]
type base64 = string
let yojson_of_base64 x = `String (Base64.encode_exn x)
let base64_of_yojson j = Yojson.Safe.Util.to_string j
type memory_options = { max_pages : int option [@yojson.option] }
type wasm_data = {
data : base64;
name : string option; [@yojson.option]
hash : string option; [@yojson.option]
}
[@@deriving yojson]
type dict = (string * string) list
@@ -30,51 +43,30 @@ let yojson_of_config c =
(fun (k, v) -> (k, match v with None -> `Null | Some v -> `String v))
c)
module Wasm = struct
type file = {
path : string;
name : string option; [@yojson.option]
hash : string option; [@yojson.option]
}
[@@deriving yojson]
type wasm_url = {
url : string;
headers : dict option; [@yojson.option]
name : string option; [@yojson.option]
meth : string option; [@yojson.option] [@key "method"]
hash : string option; [@yojson.option]
}
[@@deriving yojson]
type data = {
data : base64;
name : string option; [@yojson.option]
hash : string option; [@yojson.option]
}
[@@deriving yojson]
type wasm = File of wasm_file | Data of wasm_data | Url of wasm_url
type url = {
url : string;
headers : dict option; [@yojson.option]
name : string option; [@yojson.option]
meth : string option; [@yojson.option] [@key "method"]
hash : string option; [@yojson.option]
}
[@@deriving yojson]
let yojson_of_wasm = function
| File f -> yojson_of_wasm_file f
| Data d -> yojson_of_wasm_data d
| Url u -> yojson_of_wasm_url u
type t = File of file | Data of data | Url of url
let yojson_of_t = function
| File f -> yojson_of_file f
| Data d -> yojson_of_data d
| Url u -> yojson_of_url u
let t_of_yojson x =
try File (file_of_yojson x)
with _ -> ( try Data (data_of_yojson x) with _ -> Url (url_of_yojson x))
let file ?name ?hash path = File { path; name; hash }
let data ?name ?hash data = Data { data; name; hash }
let url ?headers ?name ?meth ?hash url =
Url { headers; name; meth; hash; url }
end
let wasm_of_yojson x =
try File (wasm_file_of_yojson x)
with _ -> (
try Data (wasm_data_of_yojson x) with _ -> Url (wasm_url_of_yojson x))
type t = {
wasm : Wasm.t list;
memory : memory_options option; [@yojson.option]
wasm : wasm list;
memory : memory option; [@yojson.option]
config : config option; [@yojson.option]
allowed_hosts : string list option; [@yojson.option]
allowed_paths : dict option; [@yojson.option]
@@ -82,30 +74,12 @@ type t = {
}
[@@deriving yojson]
let create ?config ?memory ?allowed_hosts ?allowed_paths ?timeout_ms wasm =
let file ?name ?hash path = File { path; name; hash }
let data ?name ?hash data = Data { data; name; hash }
let url ?headers ?name ?meth ?hash url = Url { headers; name; meth; hash; url }
let v ?config ?memory ?allowed_hosts ?allowed_paths ?timeout_ms wasm =
{ config; wasm; memory; allowed_hosts; allowed_paths; timeout_ms }
let to_json t = yojson_of_t t |> Yojson.Safe.to_string
let of_json s =
let j = Yojson.Safe.from_string s in
t_of_yojson j
let of_file filename =
let j = Yojson.Safe.from_file filename in
t_of_yojson j
let json t = yojson_of_t t |> Yojson.Safe.to_string
let with_config t config = { t with config = Some config }
let%test "rountrip" =
let config = [ ("a", Some "b"); ("b", Some "c") ] in
let memory = { max_pages = Some 5 } in
let t =
create ~config ~memory ~allowed_hosts:[ "example.com" ]
~allowed_paths:[ ("a", "b") ]
~timeout_ms:1000 []
in
let a = to_json t in
let b = of_json a in
let c = to_json b in
String.equal a c

View File

@@ -1,87 +1,75 @@
type memory_options = { max_pages : int option } [@@deriving yojson]
(** Memory options *)
type memory = { max_pages : int option } [@@deriving yojson]
type dict = (string * string) list [@@deriving yojson]
(** Key/value dictionary *)
type dict = (string * string) list [@@deriving yojson]
type config = (string * string option) list [@@deriving yojson]
(** Key/value dictionary with optional values *)
type config = (string * string option) list [@@deriving yojson]
module Wasm : sig
type file = {
path : string;
name : string option; [@yojson.option]
hash : string option; [@yojson.option]
}
[@@deriving yojson]
(** WebAssembly file *)
(** WebAssembly file *)
type wasm_file = {
path : string;
name : string option; [@yojson.option]
hash : string option; [@yojson.option]
} [@@deriving yojson]
type data = {
data : string;
name : string option; [@yojson.option]
hash : string option; [@yojson.option]
}
[@@deriving yojson]
(** WebAssembly module data *)
(** WebAssembly module data *)
type wasm_data = {
data : string;
name : string option; [@yojson.option]
hash : string option; [@yojson.option]
} [@@deriving yojson]
type url = {
url : string;
headers : dict option; [@yojson.option]
name : string option; [@yojson.option]
meth : string option; [@yojson.option] [@key "method"]
hash : string option; [@yojson.option]
}
[@@deriving yojson]
(** WebAssembly URL *)
(** WebAssembly URL *)
type wasm_url = {
url : string;
headers : dict option; [@yojson.option]
name : string option; [@yojson.option]
meth : string option; [@yojson.option] [@key "method"]
hash : string option; [@yojson.option]
} [@@deriving yojson]
(** WebAssembly from a file, module data or URL *)
type t = File of file | Data of data | Url of url [@@deriving yojson]
val file : ?name:string -> ?hash:string -> string -> t
(** Create [wasm] from filename *)
val data : ?name:string -> ?hash:string -> string -> t
(** Create [wasm] from WebAssembly module data *)
val url :
?headers:(string * string) list ->
?name:string ->
?meth:string ->
?hash:string ->
string ->
t
(** Create [wasm] from URL *)
end
(** WebAssembly from a file, module data or URL *)
type wasm = File of wasm_file | Data of wasm_data | Url of wasm_url [@@deriving yojson]
(** Manifest type *)
type t = {
wasm : Wasm.t list;
memory : memory_options option;
wasm : wasm list;
memory : memory option;
config : config option;
allowed_hosts : string list option;
allowed_paths : dict option;
timeout_ms : int option;
}
[@@deriving yojson]
(** Manifest type *)
} [@@deriving yojson]
val create :
(** Create [wasm] from filename *)
val file : ?name:string -> ?hash:string -> string -> wasm
(** Create [wasm] from WebAssembly module data *)
val data : ?name:string -> ?hash:string -> string -> wasm
(** Create [wasm] from URL *)
val url :
?headers:(string * string) list ->
?name:string ->
?meth:string ->
?hash:string ->
string ->
wasm
(** Create new manifest *)
val v :
?config:config ->
?memory:memory_options ->
?memory:memory ->
?allowed_hosts:string list ->
?allowed_paths:dict ->
?timeout_ms:int ->
Wasm.t list ->
wasm list ->
t
(** Create new manifest *)
val to_json : t -> string
(** Convert manifest to JSON *)
val json : t -> string
val of_json : string -> t
(** Read manifest from JSON string *)
val of_file : string -> t
(** Read manifest from JSON file *)
val with_config : t -> config -> t
(** Updates a manifest config *)
val with_config : t -> config -> t

View File

@@ -4,23 +4,6 @@ namespace Extism;
require_once "ExtismLib.php";
class CancelHandle
{
private $lib;
private $handle;
function __construct($lib, $handle)
{
$this->lib = $lib;
$this->handle = $handle;
}
public function cancel()
{
return $this->lib->extism_plugin_cancel($this->handle);
}
}
class Plugin
{
private $lib;
@@ -46,7 +29,7 @@ class Plugin
$data = string_to_bytes($data);
}
$id = $this->lib->extism_plugin_new($ctx->pointer, $data, count($data), null, 0, (int)$wasi);
$id = $this->lib->extism_plugin_new($ctx->pointer, $data, count($data), (int)$wasi);
if ($id < 0) {
$err = $this->lib->extism_error($ctx->pointer, -1);
throw new \Exception("Extism: unable to load plugin: " . $err);
@@ -75,11 +58,6 @@ class Plugin
return $this->lib->extism_plugin_function_exists($this->context->pointer, $this->id, $name);
}
public function cancelHandle()
{
return new \CancelHandle($this->lib, $this->lib->extism_plugin_cancel_handle($this->context->pointer, $this->id));
}
public function call($name, $input = null)
{
if (gettype($input) == "string") {
@@ -118,7 +96,7 @@ class Plugin
$data = string_to_bytes($data);
}
$ok = $this->lib->extism_plugin_update($this->context->pointer, $this->id, $data, count($data), null, 0, (int)$wasi);
$ok = $this->lib->extism_plugin_update($this->context->pointer, $this->id, $data, count($data), (int)$wasi);
if (!$ok) {
$err = $this->lib->extism_error($this->context->pointer, -1);
throw new \Exception("Extism: unable to update plugin: " . $err);

36
php/src/extism.h Normal file
View File

@@ -0,0 +1,36 @@
#pragma once
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
typedef int32_t ExtismPlugin;
typedef uint64_t ExtismSize;
ExtismPlugin extism_plugin_register(const uint8_t *wasm, ExtismSize wasm_size, bool with_wasi);
bool extism_plugin_update(ExtismPlugin index,
const uint8_t *wasm,
ExtismSize wasm_size,
bool with_wasi);
bool extism_plugin_config(ExtismPlugin plugin, const uint8_t *json, ExtismSize json_size);
bool extism_function_exists(ExtismPlugin plugin, const char *func_name);
int32_t extism_call(ExtismPlugin plugin_id,
const char *func_name,
const uint8_t *data,
ExtismSize data_len);
const char *extism_error(ExtismPlugin plugin);
ExtismSize extism_output_length(ExtismPlugin plugin);
void extism_output_get(ExtismPlugin plugin, uint8_t *buf, ExtismSize len);
bool extism_log_file(const char *filename, const char *log_level);
const char *extism_version();

View File

@@ -1,61 +1,35 @@
import sys
import json
import hashlib
import pathlib
sys.path.append(".")
from extism import Context, Function, host_fn, ValType
from extism import Context
if len(sys.argv) > 1:
data = sys.argv[1].encode()
else:
data = b"some data from python!"
@host_fn
def hello_world(plugin, input_, output, context, a_string):
print("Hello from Python!")
print(a_string)
print(input_)
print(plugin.input_string(input_[0]))
output[0] = input_[0]
# Compare against Python implementation.
def count_vowels(data):
return sum(letter in b"AaEeIiOoUu" for letter in data)
def main(args):
if len(args) > 1:
data = args[1].encode()
else:
data = b"some data from python!"
wasm_file_path = (
pathlib.Path(__file__).parent.parent / "wasm" / "code-functions.wasm"
)
wasm = wasm_file_path.read_bytes()
# a Context provides a scope for plugins to be managed within. creating multiple contexts
# is expected and groups plugins based on source/tenant/lifetime etc.
with Context() as context:
wasm = open("../wasm/code.wasm", "rb").read()
hash = hashlib.sha256(wasm).hexdigest()
config = {"wasm": [{"data": wasm, "hash": hash}], "memory": {"max": 5}}
# a Context provides a scope for plugins to be managed within. creating multiple contexts
# is expected and groups plugins based on source/tenant/lifetime etc.
with Context() as context:
functions = [
Function(
"hello_world",
[ValType.I64],
[ValType.I64],
hello_world,
context,
"Hello again!",
)
]
plugin = context.plugin(config, wasi=True, functions=functions)
# Call `count_vowels`
wasm_vowel_count = json.loads(plugin.call("count_vowels", data))
print("Number of vowels:", wasm_vowel_count["count"])
assert wasm_vowel_count["count"] == count_vowels(data)
plugin = context.plugin(config)
# Call `count_vowels`
j = json.loads(plugin.call("count_vowels", data))
print("Number of vowels:", j["count"])
if __name__ == "__main__":
main(sys.argv)
# Compare against Python implementation
def count_vowels(data):
count = 0
for c in data:
if c in b"AaEeIiOoUu":
count += 1
return count
assert j["count"] == count_vowels(data)

View File

@@ -1,12 +1 @@
from .extism import (
Error,
Plugin,
set_log_file,
Context,
extism_version,
host_fn,
Function,
ValType,
Val,
CurrentPlugin,
)
from .extism import Error, Plugin, set_log_file, Context, extism_version

View File

@@ -3,7 +3,6 @@ import os
from base64 import b64encode
from cffi import FFI
from typing import Union
from enum import Enum
class Error(Exception):
@@ -125,15 +124,6 @@ def _wasm(plugin):
return wasm
class Memory:
def __init__(self, offs, length):
self.offset = offs
self.length = length
def __len__(self):
return self.length
class Context:
"""
Context is used to store and manage plugins. You need a context to create
@@ -171,9 +161,7 @@ class Context:
"""Remove all registered plugins"""
_lib.extism_context_reset(self.pointer)
def plugin(
self, manifest: Union[str, bytes, dict], wasi=False, config=None, functions=None
):
def plugin(self, manifest: Union[str, bytes, dict], wasi=False, config=None):
"""
Register a new plugin from a WASM module or JSON encoded manifest
@@ -185,57 +173,13 @@ class Context:
Set to `True` to enable WASI support
config : dict
The plugin config dictionary
functions: list
Additional host functions
Returns
-------
Plugin
The created plugin
"""
return Plugin(self, manifest, wasi, config, functions)
class Function:
def __init__(self, name: str, args, returns, f, *user_data):
self.pointer = None
args = [a.value for a in args]
returns = [r.value for r in returns]
if len(user_data) > 0:
self.user_data = _ffi.new_handle(user_data)
else:
self.user_data = _ffi.NULL
self.pointer = _lib.extism_function_new(
name.encode(),
args,
len(args),
returns,
len(returns),
f,
self.user_data,
_ffi.NULL,
)
def set_namespace(self, name: str):
_lib.extism_function_set_namespace(self.pointer, name.encode())
def with_namespace(self, name: str):
self.set_namespace(name)
return self
def __del__(self):
if not hasattr(self, "pointer"):
return
if self.pointer is not None:
_lib.extism_function_free(self.pointer)
class CancelHandle:
def __init__(self, ptr):
self.pointer = ptr
def cancel(self) -> bool:
return _lib.extism_plugin_cancel(self.pointer)
return Plugin(self, manifest, wasi, config)
class Plugin:
@@ -246,12 +190,7 @@ class Plugin:
"""
def __init__(
self,
context: Context,
plugin: Union[str, bytes, dict],
wasi=False,
config=None,
functions=None,
self, context: Context, plugin: Union[str, bytes, dict], wasi=False, config=None
):
"""
Construct a Plugin. Please use Context#plugin instead.
@@ -260,16 +199,7 @@ class Plugin:
wasm = _wasm(plugin)
# Register plugin
if functions is not None:
functions = [f.pointer for f in functions]
ptr = _ffi.new("ExtismFunction*[]", functions)
self.plugin = _lib.extism_plugin_new(
context.pointer, wasm, len(wasm), ptr, len(functions), wasi
)
else:
self.plugin = _lib.extism_plugin_new(
context.pointer, wasm, len(wasm), _ffi.NULL, 0, wasi
)
self.plugin = _lib.extism_plugin_new(context.pointer, wasm, len(wasm), wasi)
self.ctx = context
@@ -283,14 +213,7 @@ class Plugin:
s = json.dumps(config).encode()
_lib.extism_plugin_config(self.ctx.pointer, self.plugin, s, len(s))
def cancel_handle(self):
return CancelHandle(
_lib.extism_plugin_cancel_handle(self.ctx.pointer, self.plugin)
)
def update(
self, manifest: Union[str, bytes, dict], wasi=False, config=None, functions=None
):
def update(self, manifest: Union[str, bytes, dict], wasi=False, config=None):
"""
Update a plugin with a new WASM module or manifest
@@ -304,22 +227,9 @@ class Plugin:
The plugin config dictionary
"""
wasm = _wasm(manifest)
if functions is not None:
functions = [f.pointer for f in functions]
ptr = _ffi.new("ExtismFunction*[]", functions)
ok = _lib.extism_plugin_update(
self.ctx.pointer,
self.plugin,
wasm,
len(wasm),
ptr,
len(functions),
wasi,
)
else:
ok = _lib.extism_plugin_update(
self.ctx.pointer, self.plugin, wasm, len(wasm), _ffi.NULL, 0, wasi
)
ok = _lib.extism_plugin_update(
self.ctx.pointer, self.plugin, wasm, len(wasm), wasi
)
if not ok:
error = _lib.extism_error(self.ctx.pointer, -1)
if error != _ffi.NULL:
@@ -400,139 +310,3 @@ class Plugin:
def __exit__(self, type, exc, traceback):
self.__del__()
def _convert_value(x):
if x.t == 0:
return Val(ValType.I32, x.v.i32)
elif x.t == 1:
return Val(ValType.I64, x.v.i64)
elif x.t == 2:
return Val(ValType.F32, x.v.f32)
elif x.y == 3:
return Val(ValType.F64, x.v.f64)
return None
def _convert_output(x, v):
if v.t.value != x.t:
raise Error(f"Output type mismatch, got {v.t} but expected {x.t}")
if v.t == ValType.I32:
x.v.i32 = int(v.value)
elif v.t == ValType.I64:
x.v.i64 = int(v.value)
elif x.t == ValType.F32:
x.v.f32 = float(v.value)
elif x.t == ValType.F64:
x.v.f64 = float(v.value)
else:
raise Error("Unsupported return type: " + str(x.t))
class ValType(Enum):
I32 = 0
I64 = 1
F32 = 2
F64 = 3
V128 = 4
FUNC_REF = 5
EXTERN_REF = 6
class Val:
"""
Low-level WebAssembly value
"""
def __init__(self, t: ValType, v):
self.t = t
self.value = v
def __repr__(self):
return f"Val({self.t}, {self.value})"
class CurrentPlugin:
"""
Wraps the current plugin from inside a host function
"""
def __init__(self, p):
self.pointer = p
def memory(self, mem: Memory) -> _ffi.buffer:
"""Access a block of memory"""
p = _lib.extism_current_plugin_memory(self.pointer)
if p == 0:
return None
return _ffi.buffer(p + mem.offset, mem.length)
def alloc(self, n: int) -> Memory:
"""Allocate a new block of memory of [n] bytes"""
offs = _lib.extism_current_plugin_memory_alloc(self.pointer, n)
return Memory(offs, n)
def free(self, mem: Memory):
"""Free a block of memory"""
return _lib.extism_current_plugin_memory_free(self.pointer, mem.offset)
def memory_at_offset(self, offs: int) -> Memory:
"""Get a block of memory at the specified offset"""
if isinstance(offs, Val):
offs = offs.value
len = _lib.extism_current_plugin_memory_length(self.pointer, offs)
return Memory(offs, len)
def return_bytes(self, output: Val, b: bytes):
mem = self.alloc(len(b))
self.memory(mem)[:] = b
output.value = mem.offset
def return_string(self, output: Val, s: str):
self.return_bytes(output, s.encode())
def input_buffer(self, input: Val) -> _ffi.buffer:
mem = self.memory_at_offset(input)
return self.memory(mem)
def input_bytes(self, input: Val) -> bytes:
return self.input_buffer(input)[:]
def input_string(self, input: Val) -> str:
return self.input_bytes(input).decode()
def host_fn(func):
"""
A decorator for creating host functions, this decorator wraps a function that takes the following parameters:
- current plugin: `CurrentPlugin`
- inputs: `List[Val]`
- user_data: any number of values passed as user data
The function should return a list of `Val`
"""
@_ffi.callback(
"void(ExtismCurrentPlugin*, const ExtismVal*, ExtismSize, ExtismVal*, ExtismSize, void*)"
)
def handle_args(current, inputs, n_inputs, outputs, n_outputs, user_data):
inp = []
outp = []
for i in range(n_inputs):
inp.append(_convert_value(inputs[i]))
for i in range(n_outputs):
outp.append(_convert_value(outputs[i]))
if user_data == _ffi.NULL:
output = func(CurrentPlugin(current), inp, outp)
else:
udata = _ffi.from_handle(user_data)
func(CurrentPlugin(current), inp, outp, *udata)
for i in range(n_outputs):
_convert_output(outputs[i], outp[i])
return handle_args

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "extism"
version = "0.3.0"
version = "0.1.0"
description = "Extism Host SDK for python"
authors = ["The Extism Authors <oss@extism.org>"]
license = "BSD-3-Clause"
@@ -11,7 +11,7 @@ python = "^3.7"
cffi = "^1.10.0"
[tool.poetry.dev-dependencies]
black = "^23.1.0"
black = "^22.10.0"
pdoc3 = "^0.10.0"
[build-system]

BIN
python/tests/code.wasm Executable file

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More