Skip to content

Commit 73bb4da

Browse files
authored
Merge pull request #28 from n0-computer/extism
WIP: extism integration example
2 parents 26253d9 + 5d1f575 commit 73bb4da

File tree

8 files changed

+225
-0
lines changed

8 files changed

+225
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ target/
55
# Added by cargo
66

77
/target
8+
Cargo.lock

extism/host/Cargo.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[package]
2+
name = "extism-host"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
anyhow = "1.0.79"
8+
extism = "1.0.0"
9+
tokio = { version = "1", features = ["full"] }
10+
iroh-extism-host-functions = { path = "../iroh-extism-host-functions" }

extism/host/src/main.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
use extism::*;
2+
3+
fn main() -> anyhow::Result<()> {
4+
let tokio_rt = tokio::runtime::Builder::new_multi_thread()
5+
.thread_name("main-runtime")
6+
.worker_threads(2)
7+
.enable_all()
8+
.build()?;
9+
let rt = tokio_rt.handle().clone();
10+
11+
let ticket = std::env::args().nth(1).expect("missing ticket");
12+
13+
let iroh = rt.block_on(async {
14+
let iroh_path = iroh_extism_host_functions::default_iroh_extism_data_root().await?;
15+
iroh_extism_host_functions::create_iroh(iroh_path).await
16+
})?;
17+
println!("iroh node id: {:?}", iroh.node_id());
18+
19+
let file = Wasm::file("../plugin/target/wasm32-unknown-unknown/debug/plugin.wasm");
20+
let manifest = Manifest::new([file]);
21+
22+
let plugin = PluginBuilder::new(manifest).with_wasi(true);
23+
24+
let mut plugin =
25+
iroh_extism_host_functions::add_all_host_functions(rt, plugin, iroh).build()?;
26+
27+
let res = plugin
28+
.call::<&str, &str>("print_hai_and_get_ticket", &ticket)
29+
.unwrap();
30+
31+
println!("Received iroh data:\n\n{}", res);
32+
33+
Ok(())
34+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[package]
2+
name = "iroh-extism-host-functions"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
8+
[dependencies]
9+
anyhow = "1.0.79"
10+
dirs-next = "2.0.0"
11+
extism = "1.0.0"
12+
futures = "0.3.29"
13+
iroh = { version = "0.12.0", default-features = false }
14+
tokio = { version = "1", features = ["full"] }
15+
tokio-util = { version = "0.7", features = ["codec", "io-util", "io", "time"] }
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
use std::path::PathBuf;
2+
use std::str::FromStr;
3+
4+
use anyhow::{anyhow, Result};
5+
use extism::*;
6+
use futures::stream::StreamExt;
7+
use iroh::net::key::SecretKey;
8+
use iroh::node::Node;
9+
use iroh::rpc_protocol::{DownloadLocation, SetTagOption};
10+
use iroh::util::path::IrohPaths;
11+
12+
type IrohNode = Node<iroh::bytes::store::flat::Store>;
13+
14+
const IROH_EXTISM_DATA_DIR: &str = "iroh-extism";
15+
16+
pub async fn default_iroh_extism_data_root() -> Result<PathBuf> {
17+
if let Some(val) = std::env::var_os("IROH_EXTISM_DATA_DIR") {
18+
return Ok(PathBuf::from(val));
19+
}
20+
let path = dirs_next::data_dir().ok_or_else(|| {
21+
anyhow!("operating environment provides no directory for application data")
22+
})?;
23+
24+
Ok(path.join(IROH_EXTISM_DATA_DIR))
25+
}
26+
27+
pub async fn create_iroh(path: PathBuf) -> Result<IrohNode> {
28+
tokio::fs::create_dir_all(&path).await?;
29+
30+
let blob_dir = path.join(IrohPaths::BaoFlatStoreDir);
31+
tokio::fs::create_dir_all(&blob_dir).await?;
32+
33+
let db = iroh::bytes::store::flat::Store::load(blob_dir).await?;
34+
let doc_store = iroh::sync::store::fs::Store::new(path.join(IrohPaths::DocsDatabase))?;
35+
let node = iroh::node::Node::builder(db, doc_store)
36+
.secret_key(SecretKey::generate())
37+
.spawn()
38+
.await?;
39+
40+
Ok(node)
41+
}
42+
43+
// host_fn!(iroh_blob_get_node_id(user_data: Node<iroh::bytes::store::flat::Store>) -> String {
44+
// Ok(user_data.node_id().to_string())
45+
// });
46+
47+
struct Context {
48+
rt: tokio::runtime::Handle,
49+
iroh: IrohNode,
50+
}
51+
52+
host_fn!(iroh_blob_get_ticket(user_data: Context; ticket: &str) -> Vec<u8> {
53+
let ctx = user_data.get()?;
54+
let ctx = ctx.lock().unwrap();
55+
56+
let (node_addr, hash, format) = iroh::ticket::BlobTicket::from_str(ticket).map_err(|_| anyhow!("invalid ticket"))?.into_parts();
57+
58+
if format != iroh::rpc_protocol::BlobFormat::Raw {
59+
return Err(anyhow!("can only get raw bytes for now, not HashSequences (collections)"));
60+
}
61+
let client = ctx.iroh.client();
62+
let buf = ctx.rt.block_on(async move {
63+
let mut stream = client.blobs.download(iroh::rpc_protocol::BlobDownloadRequest {
64+
hash,
65+
format,
66+
peer: node_addr,
67+
out: DownloadLocation::Internal,
68+
tag: SetTagOption::Auto,
69+
}).await?;
70+
while stream.next().await.is_some() {}
71+
72+
let buffer = client.blobs.read(hash).await?.read_to_bytes().await?;
73+
anyhow::Ok(buffer.to_vec())
74+
})?;
75+
76+
Ok(buf)
77+
});
78+
79+
pub fn add_all_host_functions(
80+
rt: tokio::runtime::Handle,
81+
b: PluginBuilder,
82+
iroh: IrohNode,
83+
) -> PluginBuilder {
84+
let ctx = UserData::new(Context {
85+
rt,
86+
iroh,
87+
});
88+
89+
b.with_function(
90+
"iroh_blob_get_ticket",
91+
[PTR],
92+
[PTR],
93+
ctx,
94+
iroh_blob_get_ticket,
95+
)
96+
}

extism/plugin/Cargo.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[package]
2+
name = "plugin"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[lib]
7+
crate_type = ["cdylib"]
8+
9+
[dependencies]
10+
extism-pdk = "1.0.1"

extism/plugin/src/lib.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
use extism_pdk::*;
2+
3+
#[host_fn]
4+
extern "ExtismHost" {
5+
fn iroh_blob_get_ticket(ticket: String) -> Vec<u8>;
6+
}
7+
8+
#[plugin_fn]
9+
pub fn print_hai_and_get_ticket(ticket: String) -> FnResult<Vec<u8>> {
10+
println!("Hai from a wasm plugin!");
11+
let v = unsafe { iroh_blob_get_ticket(ticket.into()) }?;
12+
Ok(v)
13+
}

extism/readme.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# iroh + exsitm
2+
3+
This is an example of running iroh thorugh [exitsm](https://extism.org/) as a set of [host functions](https://extism.org/docs/concepts/host-functions). It's written entirely in rust, but the purpose is to show how one would build an API that any plugin could use to gain access to a plugin-host-provided iroh instance.
4+
5+
## Requirements
6+
7+
You'll need the latest version of the rust toolchain, and the `wasm32-unknown-unknown` target. Install it with:
8+
9+
```
10+
rustup target add wasm32-unknown-unknown
11+
```
12+
13+
## Running the Example
14+
15+
There are three
16+
17+
1. Build the WASM plugin
18+
2. Get a blob ticket
19+
3. run the host
20+
21+
### 1. Build the WASM plugin
22+
23+
`cd` to `extism/plugin` and run `cargo build --target wasm32-unknown-unknown`. If you get an error, check the requirements section above. This will generate a WASM plugin that actually calls the blob download host function.
24+
25+
### 2. Get a blob ticket
26+
27+
This example needs a `Raw` blob ticket, which you can get from `iroh blob add` on a raw file, or just use this one, which should be hosted on iroh.network:
28+
```
29+
blobabk62aofuwwwu5zb5ocvzj5v3rtqt6siglyuhoxhqtu4fxravvoteajcnb2hi4dthixs65ltmuys2mjomrsxe4bonfzg62bonzsxi53pojvs4lydaac2cyt22erablaraaa5ciqbfiaqj7ya6cbpuaaaaaaaaaaaahjceagucztuhgez4qucv2733xphmgpc2nkgj54od2vuygn6sz4zzxo6ce
30+
```
31+
(it's a text file that says hello)
32+
33+
### 3. Run the host
34+
35+
The host code will actually call the example. `cd` to `./extism/host` and run
36+
37+
```
38+
cargo run <TICKET>
39+
```
40+
41+
pasting in the ticket you'd like to fetch. It'll fetch the bytes & print it out, proving this host can provide iroh to extism plugins!
42+
43+
44+
## Future work
45+
46+
What would be truly neat would be to reverse the direction, and write a plugin that brings iroh to a host. For that we'd need to be able to compile some/all of iroh to WASM, which we're [working on](https://github.com/n0-computer/iroh/issues/1803).

0 commit comments

Comments
 (0)