├── .cargo └── config.toml ├── .gitignore ├── .vscode └── settings.json ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE.txt ├── README.md ├── bins ├── ops │ ├── Cargo.toml │ └── src │ │ └── main.rs └── request │ ├── Cargo.toml │ └── src │ └── main.rs ├── crates ├── wasmrs-codec │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── error.rs │ │ ├── lib.rs │ │ └── messagepack.rs ├── wasmrs-frames │ ├── Cargo.toml │ ├── README.md │ ├── src │ │ ├── error.rs │ │ ├── frames │ │ │ ├── f_cancel.rs │ │ │ ├── f_error.rs │ │ │ ├── f_payload.rs │ │ │ ├── f_request_channel.rs │ │ │ ├── f_request_fnf.rs │ │ │ ├── f_request_n.rs │ │ │ ├── f_request_response.rs │ │ │ ├── f_request_stream.rs │ │ │ ├── header.rs │ │ │ ├── metadata.rs │ │ │ ├── mod.rs │ │ │ └── request_payload.rs │ │ ├── lib.rs │ │ └── util.rs │ └── testdata │ │ ├── frame.cancel.bin │ │ ├── frame.error.bin │ │ ├── frame.payload.bin │ │ ├── frame.request_channel.bin │ │ ├── frame.request_fnf.bin │ │ ├── frame.request_n.bin │ │ ├── frame.request_response.bin │ │ └── frame.request_stream.bin ├── wasmrs-guest │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── error.rs │ │ ├── exports.rs │ │ ├── guest.rs │ │ ├── imports.rs │ │ ├── lib.rs │ │ └── server.rs ├── wasmrs-host │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── context.rs │ │ ├── errors.rs │ │ ├── host.rs │ │ ├── lib.rs │ │ ├── protocol.rs │ │ └── wasi.rs ├── wasmrs-runtime │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── error.rs │ │ ├── lib.rs │ │ ├── runtime.rs │ │ └── runtime │ │ ├── native │ │ └── mod.rs │ │ └── wasm │ │ └── mod.rs ├── wasmrs-rx │ ├── Cargo.toml │ ├── README.md │ ├── examples │ │ └── rx.rs │ └── src │ │ ├── error.rs │ │ ├── flux.rs │ │ ├── flux │ │ ├── observable.rs │ │ ├── observer.rs │ │ ├── ops.rs │ │ ├── ops │ │ │ └── pipe.rs │ │ ├── receiver.rs │ │ └── signal.rs │ │ └── lib.rs ├── wasmrs-wasmtime │ ├── Cargo.toml │ ├── README.md │ ├── examples │ │ └── request.rs │ ├── src │ │ ├── builder.rs │ │ ├── engine_provider.rs │ │ ├── errors.rs │ │ ├── lib.rs │ │ ├── memory.rs │ │ ├── store.rs │ │ ├── wasi.rs │ │ └── wasmrs_wasmtime.rs │ └── tests │ │ └── test_baseline.rs └── wasmrs │ ├── Cargo.toml │ ├── README.md │ ├── examples │ └── decode.rs │ └── src │ ├── error.rs │ ├── handlers.rs │ ├── lib.rs │ ├── operations.rs │ ├── record.rs │ ├── socket.rs │ ├── socket │ ├── buffer.rs │ └── responder.rs │ └── util.rs ├── justfile ├── rust-toolchain.toml ├── rustfmt.toml └── wasm └── baseline ├── .vscode └── settings.json ├── Cargo.lock ├── Cargo.toml ├── justfile └── src └── lib.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.'cfg(target_arch="wasm32")'] 2 | runner = ["wasmtime"] 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /build 3 | /archive 4 | /Cargo.lock 5 | /**/target 6 | */frames/*.frame 7 | 8 | .DS_store -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "workbench.colorCustomizations": { 3 | "commandCenter.border": "#15202b99", 4 | "titleBar.activeBackground": "#af7200", 5 | "titleBar.activeForeground": "#e6e9eb", 6 | "titleBar.inactiveBackground": "#ffa50099", 7 | "titleBar.inactiveForeground": "#15202b99" 8 | }, 9 | "rust-analyzer.cargo.features": ["wasmrs/dump-frames"] 10 | } 11 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | If you would like to contribute to the project, please follow these guidelines: 4 | 5 | - Fork the repository 6 | - Create a new branch for your changes 7 | - Make your changes, including appropriate tests 8 | - Update the documentation as necessary 9 | - Run existing tests to ensure nothing has broken 10 | - Submit a pull request 11 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "crates/wasmrs-frames", 4 | "crates/wasmrs-codec", 5 | "crates/wasmrs", 6 | "crates/wasmrs-runtime", 7 | "crates/wasmrs-guest", 8 | "crates/wasmrs-rx", 9 | "crates/wasmrs-host", 10 | "crates/wasmrs-wasmtime", 11 | "bins/ops", 12 | "bins/request", 13 | ] 14 | exclude = ["wasm/reqres-component", "wasm/baseline", "wasm/grabbag"] 15 | 16 | [workspace.dependencies] 17 | # Wasmtime deps 18 | wasmtime = { version = "13.0", features = ["async"] } 19 | wasmtime-wasi = { version = "13.0", features = ["sync"] } 20 | wasi-common = { version = "13.0" } 21 | # 22 | env_logger = { version = "0.10.0", default-features = false } 23 | futures = { version = "0.3", default-features = false } 24 | futures-core = { version = "0.3", default-features = false } 25 | futures-executor = { version = "0.3", default-features = false } 26 | futures-util = { version = "0.3", default-features = false } 27 | serde = { version = "1", default-features = false } 28 | bytes = { version = "1.2", default-features = false } 29 | tokio = { version = "1", default-features = false } 30 | async-trait = { version = "0.1", default-features = false } 31 | parking_lot = { version = "0.12", default-features = false } 32 | tracing = { version = "0.1", default-features = false } 33 | thiserror = { version = "1.0", default-features = false } 34 | anyhow = { version = "1.0", default-features = false } 35 | cfg-if = { version = "1.0.0" } 36 | once_cell = { version = "1.18" } 37 | serde_json = { version = "1.0.85" } 38 | clap = "4.0.9" 39 | base64 = "0.21" 40 | test-log = "0.2.10" 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WasmRS 2 | WasmRS implements [reactive streams](https://www.reactive-streams.org) in WebAssembly modules to enable asynchronous, bidirectional communication in and out of wasm. WasmRS is a spiritual successor to waPC and dramatically expands on what you can do with WebAssembly. 3 | 4 | ## wasmRS Protocol 5 | 6 | WasmRS revolves around a handful of methods that allow the host and the guest to write [RSocket](https://rsocket.io) frames to their respective buffers in WebAssembly memory. The language-specific implementation largely handles the encoding and decoding of these frames with a light UX layer on top and metadata extensions that are relevant to WebAssembly usage. 7 | 8 | As in RSocket, wasmRS frames contain a stream ID allowing the destination to differentiate multiple frames for different transactions. 9 | 10 | For more information on the protocol, see the [wasmRS documentation](https://github.com/wasmrs/docs/blob/main/wasmrs.md) at the root of this project. 11 | 12 | ## Prerequisites 13 | 14 | - [just](github.com/casey/just) task runner. 15 | 16 | ## Building & Running tests 17 | 18 | The host tests depend on built WebAssembly modules. To build new modules, run the following: 19 | 20 | ```sh 21 | $ just wasm 22 | ``` 23 | 24 | Build debug versions of the WebAssembly modules (with `wasi`) using: 25 | 26 | ```sh 27 | $ just debug 28 | ``` 29 | 30 | Run tests with the command `just test`: 31 | 32 | ```sh 33 | $ just test 34 | ``` 35 | 36 | ## Running example host with guest wasm 37 | 38 | The `request` binary allows you to make simple requests into WebAssembly binaries, passing JSONified data as input, e.g.: 39 | 40 | ``` 41 | $ cargo run --bin request -- ./build/reqres_component.wasm suite.test reverse '{"input":"abcdefghijklmnopqrstuvwxyz"}' 42 | ``` 43 | 44 | ## Enable trace logging 45 | 46 | ``` 47 | RUST_LOG=wasmrs=trace cargo run --bin request ... 48 | ``` 49 | 50 | ## See also 51 | 52 | - [nanobus](https://github.com/nanobus/nanobus) as a way to run wasmRS modules 53 | - [apex](https://apexlang.io) to generate wasmrs boilerplate and scaffold projects using wasmrs. 54 | 55 | ## Contributing 56 | 57 | See [CONTRIBUTING.md](https://github.com/wasmrs/wasmrs-rust/blob/main/CONTRIBUTING.md) 58 | 59 | ## License 60 | 61 | See the root [LICENSE.txt](https://github.com/wasmrs/wasmrs-rust/blob/main/LICENSE.txt) 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /bins/ops/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasmrs-ops" 3 | version = "0.15.0" 4 | edition = "2021" 5 | description = "Print wasmRS operations from a .wasm file." 6 | license = "Apache-2.0" 7 | repository = "https://github.com/wasmrs/wasmrs-rust" 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | [dependencies] 11 | wasmrs = { path = "../../crates/wasmrs", version = "0.17.0" } 12 | wasmrs-codec = { path = "../../crates/wasmrs-codec", version = "0.17.0" } 13 | wasmrs-wasmtime = { path = "../../crates/wasmrs-wasmtime", version = "0.17.0" } 14 | wasmrs-host = { path = "../../crates/wasmrs-host", version = "0.17.0" } 15 | env_logger = { workspace = true } 16 | tokio = { version = "1", features = ["macros", "rt-multi-thread"] } 17 | futures = { workspace = true } 18 | anyhow = { version = "1.0" } 19 | clap = { version = "4.0.9", features = ["derive"] } 20 | -------------------------------------------------------------------------------- /bins/ops/src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use wasmrs_host::WasiParams; 3 | use wasmrs_wasmtime::WasmtimeBuilder; 4 | 5 | #[derive(Parser, Debug)] 6 | #[command(author, version)] 7 | struct Args { 8 | /// Wasm module 9 | #[arg()] 10 | module: String, 11 | } 12 | 13 | #[tokio::main] 14 | async fn main() -> anyhow::Result<()> { 15 | env_logger::init(); 16 | let args = Args::parse(); 17 | 18 | let module_bytes = std::fs::read(&args.module)?; 19 | let engine = WasmtimeBuilder::new() 20 | .with_module_bytes(&args.module, &module_bytes) 21 | .wasi_params(WasiParams::default()) 22 | .build()?; 23 | let host = wasmrs_host::Host::new(engine).await?; 24 | let context = host.new_context(64 * 1024, 64 * 1024).await?; 25 | context.dump_operations(); 26 | 27 | Ok(()) 28 | } 29 | -------------------------------------------------------------------------------- /bins/request/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasmrs-request" 3 | version = "0.15.0" 4 | edition = "2021" 5 | description = "Make a request to a wasmRS .wasm file." 6 | license = "Apache-2.0" 7 | repository = "https://github.com/wasmrs/wasmrs-rust" 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | [dependencies] 11 | wasmrs = { path = "../../crates/wasmrs", version = "0.17.0", features = [ 12 | "record-frames" 13 | ] } 14 | wasmrs-rx = { path = "../../crates/wasmrs-rx", version = "0.17.0" } 15 | wasmrs-codec = { path = "../../crates/wasmrs-codec", version = "0.17.0" } 16 | wasmrs-wasmtime = { path = "../../crates/wasmrs-wasmtime", version = "0.17.0" } 17 | wasmrs-host = { path = "../../crates/wasmrs-host", version = "0.17.0" } 18 | env_logger = { workspace = true } 19 | tokio = { version = "1", features = ["macros", "rt-multi-thread"] } 20 | futures = { workspace = true } 21 | anyhow = { version = "1.0" } 22 | clap = { version = "4.0.9", features = ["derive"] } 23 | serde_json = "1.0.85" 24 | -------------------------------------------------------------------------------- /bins/request/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::io::{BufRead, Write}; 2 | 3 | use clap::Parser; 4 | use futures::StreamExt; 5 | use wasmrs::{Metadata, RSocket, RawPayload}; 6 | use wasmrs_codec::messagepack::*; 7 | use wasmrs_host::WasiParams; 8 | use wasmrs_rx::*; 9 | use wasmrs_wasmtime::WasmtimeBuilder; 10 | 11 | #[derive(Parser, Debug)] 12 | #[command(author, version)] 13 | struct Args { 14 | /// Wasm module 15 | #[arg()] 16 | module: String, 17 | 18 | /// Namespace 19 | #[arg()] 20 | namespace: String, 21 | 22 | /// Operation 23 | #[arg()] 24 | operation: String, 25 | 26 | /// Data to send 27 | #[arg(default_value = "\"\"")] 28 | data: String, 29 | 30 | /// The file path to store the frames in a replay file 31 | #[arg(long = "replay", short = 'r')] 32 | replay: Option, 33 | 34 | /// Treat request as request_stream 35 | #[arg(long = "stream", short = 's')] 36 | stream: bool, 37 | 38 | /// Treat request as request_channel 39 | #[arg(long = "channel", short = 'c')] 40 | channel: bool, 41 | } 42 | 43 | #[tokio::main] 44 | async fn main() -> anyhow::Result<()> { 45 | env_logger::init(); 46 | let args = Args::parse(); 47 | 48 | let module_bytes = std::fs::read(&args.module)?; 49 | let engine = WasmtimeBuilder::new() 50 | .with_module_bytes(&args.module, &module_bytes) 51 | .wasi_params(WasiParams::default()) 52 | .build()?; 53 | let host = wasmrs_host::Host::new(engine).await?; 54 | let context = host.new_context(64 * 1024, 64 * 1024).await?; 55 | 56 | let op = context.get_export(&args.namespace, &args.operation).unwrap(); 57 | 58 | let mbytes = Metadata::new(op).encode(); 59 | 60 | if args.channel { 61 | let stdin = std::io::stdin(); 62 | let (tx, rx) = FluxChannel::new_parts(); 63 | 64 | let task = tokio::spawn(async move { 65 | let mut response = context.request_channel(Box::pin(rx)); 66 | while let Some(Ok(payload)) = response.next().await { 67 | let bytes = payload.data.unwrap(); 68 | let val: String = deserialize(&bytes).unwrap(); 69 | println!("{}", val); 70 | } 71 | }); 72 | for (_i, line) in stdin.lock().lines().enumerate() { 73 | let bytes = serialize(&line.unwrap()).unwrap(); 74 | let payload = RawPayload::new(mbytes.clone(), bytes.into()); 75 | let _ = tx.send(payload); 76 | } 77 | drop(tx); 78 | task.await?; 79 | } else { 80 | let val: serde_json::Value = serde_json::from_str(&args.data)?; 81 | let bytes = serialize(&val).unwrap(); 82 | 83 | let payload = RawPayload::new(mbytes, bytes.into()); 84 | if args.stream { 85 | let mut response = context.request_stream(payload.clone()); 86 | while let Some(Ok(v)) = response.next().await { 87 | let bytes = v.data.unwrap(); 88 | let val: String = deserialize(&bytes).unwrap(); 89 | println!("{}", val); 90 | } 91 | } else { 92 | let response = context.request_response(payload.clone()); 93 | match response.await { 94 | Ok(v) => { 95 | let bytes = v.data.unwrap(); 96 | let val: String = deserialize(&bytes).unwrap(); 97 | println!("{}", val); 98 | } 99 | Err(e) => { 100 | println!("Error: {}", e) 101 | } 102 | } 103 | } 104 | } 105 | 106 | if let Some(replay) = args.replay { 107 | let mut file = std::fs::File::create(replay)?; 108 | let frames = wasmrs::get_records(); 109 | for frame in frames { 110 | file.write_fmt(format_args!("{}\n", serde_json::to_string(&frame)?))?; 111 | } 112 | } 113 | 114 | Ok(()) 115 | } 116 | -------------------------------------------------------------------------------- /crates/wasmrs-codec/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasmrs-codec" 3 | version = "0.17.0" 4 | edition = "2021" 5 | description = "MessagePack Codec implementation used by wasmRS modules" 6 | license = "Apache-2.0" 7 | repository = "https://github.com/wasmrs/wasmrs-rust" 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | [features] 11 | default = [] 12 | std = ["serde/std"] 13 | 14 | [dependencies] 15 | wasmrs-frames = { path = "../wasmrs-frames", version = "0.17.1" } 16 | rmp-serde = "1.1" 17 | serde = { version = "1", features = [], default-features = false } 18 | heapless = "0.7" 19 | 20 | [dev-dependencies] 21 | bytes = { version = "1.1", features = ["serde"] } 22 | serde_json = { version = "1.0" } 23 | -------------------------------------------------------------------------------- /crates/wasmrs-codec/README.md: -------------------------------------------------------------------------------- 1 | # wasmrs-codec 2 | 3 | This crate provides the MessagePack encoding/decoding implementation for wasmRS modules. 4 | 5 | ## Usage 6 | 7 | For more information on wasmRS, see the core [wasmrs](https://github.com/wasmrs/wasmrs-rust/blob/main/crates/wasmrs/README.md) crate. 8 | 9 | ## Contributing 10 | 11 | See [CONTRIBUTING.md](https://github.com/WasmRS/wasmrs-rust/blob/main/CONTRIBUTING.md) 12 | 13 | ## License 14 | 15 | See the root [LICENSE.txt](https://github.com/WasmRS/wasmrs-rust/blob/main/LICENSE.txt) 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /crates/wasmrs-codec/src/error.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub enum Error { 3 | MsgPackDecode(rmp_serde::decode::Error), 4 | MsgPackEncode(rmp_serde::encode::Error), 5 | } 6 | 7 | #[cfg(feature = "std")] 8 | impl std::error::Error for Error {} 9 | 10 | impl core::fmt::Display for Error { 11 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 12 | match self { 13 | Error::MsgPackDecode(e) => e.fmt(f), 14 | Error::MsgPackEncode(e) => e.fmt(f), 15 | } 16 | } 17 | } 18 | 19 | impl From for wasmrs_frames::PayloadError { 20 | fn from(val: Error) -> Self { 21 | use core::fmt::Write; 22 | 23 | let mut string: heapless::String<256> = heapless::String::new(); 24 | string.write_fmt(format_args!("{:.256}", val)).unwrap(); 25 | 26 | wasmrs_frames::PayloadError::new(0, string.as_str(), None) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /crates/wasmrs-codec/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "std"), no_std)] 2 | 3 | pub mod error; 4 | pub mod messagepack; 5 | -------------------------------------------------------------------------------- /crates/wasmrs-codec/src/messagepack.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use crate::error::Error; 4 | use core::result::Result; 5 | extern crate alloc; 6 | use alloc::vec::Vec; 7 | 8 | #[doc(hidden)] 9 | pub fn mp_serialize(item: &T) -> Result, rmp_serde::encode::Error> 10 | where 11 | T: ?Sized + Serialize, 12 | { 13 | let mut buf = Vec::new(); 14 | let mut serializer = rmp_serde::encode::Serializer::new(&mut buf) 15 | .with_human_readable() 16 | .with_struct_map(); 17 | item.serialize(&mut serializer)?; 18 | Ok(buf) 19 | } 20 | 21 | /// The standard function for serializing codec structs into a format that can be. 22 | /// used for message exchange between actor and host. Use of any other function to. 23 | /// serialize could result in breaking incompatibilities. 24 | pub fn serialize(item: &T) -> Result, crate::error::Error> 25 | where 26 | T: ?Sized + Serialize, 27 | { 28 | mp_serialize(item).map_err(Error::MsgPackEncode) 29 | } 30 | 31 | #[doc(hidden)] 32 | pub fn mp_deserialize<'de, T: Deserialize<'de>>(buf: &'de [u8]) -> Result { 33 | rmp_serde::decode::from_slice(buf) 34 | } 35 | 36 | /// The standard function for de-serializing codec structs from a format suitable. 37 | /// for message exchange between actor and host. Use of any other function to. 38 | /// deserialize could result in breaking incompatibilities. 39 | pub fn deserialize<'de, T: Deserialize<'de>>(buf: &'de [u8]) -> Result { 40 | mp_deserialize(buf).map_err(Error::MsgPackDecode) 41 | } 42 | 43 | #[cfg(test)] 44 | mod test { 45 | use super::*; 46 | use bytes::Bytes; 47 | 48 | #[test] 49 | fn test_bytes() { 50 | let bytes = b"\xc4\xf2PK\x03\x04\x14\0\0\0\x08\x000t\nA~\xe7\xffi$\0\0\0$\0\0\0\x06\0\0\0README\x0b\xc9\xc8,V(\xceM\xcc\xc9QH\xcb\xccIU\0\xf22\xf3\x14\xa2<\x03\xccL\x14\xd2\xf2\x8br\x13K\xf4\xb8\0PK\x01\x02-\x03-\0\0\0\x08\x000t\nA~\xe7\xffi\xff\xff\xff\xff\xff\xff\xff\xff\x06\0\x14\0\0\0\0\0\0\0\0\0\xa4\x81\0\0\0\0README\x01\0\x10\0$\0\0\0\0\0\0\0$\0\0\0\0\0\0\0PK\x06\x06,\0\0\0\0\0\0\0-\0-\0\0\0\0\0\0\0\0\0\x01\0\0\0\0\0\0\0\x01\0\0\0\0\0\0\0H\0\0\0\0\0\0\0H\0\0\0\0\0\0\0PK\x06\x07\0\0\0\0\x90\0\0\0\0\0\0\0\x01\0\0\0PK\x05\x06\0\0\0\0\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\0\0"; 51 | let v: Bytes = deserialize(bytes).unwrap(); 52 | assert_eq!(v, Bytes::from(&bytes[2..])); 53 | } 54 | 55 | #[test] 56 | fn test_map() { 57 | let bytes = b"\x81\xa6source\xa9zip64.zip"; 58 | let actual: serde_json::Value = deserialize(bytes).unwrap(); 59 | let expected = serde_json::json!({ 60 | "source": "zip64.zip" 61 | }); 62 | assert_eq!(expected, actual); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /crates/wasmrs-frames/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasmrs-frames" 3 | version = "0.17.1" 4 | edition = "2021" 5 | description = "WasmRS RSocket frame decoding, encoding, and data structures" 6 | license = "Apache-2.0" 7 | repository = "https://github.com/wasmrs/wasmrs-rust" 8 | 9 | [features] 10 | default = [] 11 | derive_serde = ["serde/derive"] 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | [dependencies] 15 | bytes = { workspace = true } 16 | serde = { workspace = true, features = [ 17 | ], default-features = false, optional = true } 18 | 19 | [dev-dependencies] 20 | anyhow = { workspace = true, features = ["std"] } 21 | -------------------------------------------------------------------------------- /crates/wasmrs-frames/README.md: -------------------------------------------------------------------------------- 1 | # wasmrs-frames 2 | 3 | This crate provides the encoding, decoding, and data structures for wasmRS RSocket frames. 4 | 5 | ## More Information 6 | 7 | For more information on wasmRS, see the core [wasmrs](https://github.com/wasmrs/wasmrs-rust/blob/main/crates/wasmrs/README.md) crate. 8 | 9 | WasmRS makes heavy use of generated code from `apex` specs and generators to automate all of the boilerplate. See the [getting-started](https://github.com/WasmRS/docs/blob/main/wasmrs-rust-howto.md) for usage. 10 | 11 | ## Contributing 12 | 13 | See [CONTRIBUTING.md](https://github.com/WasmRS/wasmrs-rust/blob/main/CONTRIBUTING.md) 14 | 15 | ## License 16 | 17 | See the root [LICENSE.txt](https://github.com/WasmRS/wasmrs-rust/blob/main/LICENSE.txt) 18 | 19 | -------------------------------------------------------------------------------- /crates/wasmrs-frames/src/error.rs: -------------------------------------------------------------------------------- 1 | //! Library-specific error types and utility functions 2 | 3 | use bytes::Bytes; 4 | 5 | use crate::frames::ErrorCode; 6 | 7 | /// Error type for wasmRS RSocket errors. 8 | #[allow(missing_copy_implementations)] 9 | #[derive(Debug, Clone)] 10 | pub enum Error { 11 | /// An error associated with OperationList methods. 12 | OpList(String), 13 | /// A generic RSocket error. 14 | RSocket(u32), 15 | /// Used when the receiver for a [crate::WasmSocket] has already been taken. 16 | ReceiverAlreadyGone, 17 | /// Variant used when a frame is treated as the wrong type. 18 | WrongType, 19 | /// Could not convert string from passed bytes. 20 | StringConversion, 21 | /// Did not find necessary [crate::Metadata] on a payload. 22 | MetadataNotFound, 23 | /// A problem with extra metadata on [crate::Metadata]. 24 | Extra(String), 25 | } 26 | 27 | /// A utility method for creating an Error::Extra variant. 28 | pub fn ex_err(msg: impl AsRef) -> Error { 29 | Error::Extra(msg.as_ref().to_owned()) 30 | } 31 | 32 | impl std::error::Error for Error {} 33 | impl std::fmt::Display for Error { 34 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 35 | match self { 36 | Error::RSocket(code) => f.write_str((Into::::into(*code)).to_string().as_str()), 37 | Error::OpList(msg) => f.write_str(msg), 38 | Error::ReceiverAlreadyGone => f.write_str("Received already taken"), 39 | Error::WrongType => f.write_str("Tried to decode frame with wrong frame decoder"), 40 | Error::StringConversion => f.write_str("Could not read string bytes"), 41 | Error::Extra(m) => f.write_str(m), 42 | Error::MetadataNotFound => f.write_str("Metadata missing"), 43 | } 44 | } 45 | } 46 | 47 | #[derive(Debug, Clone)] 48 | #[cfg_attr(feature = "derive_serde", derive(serde::Serialize, serde::Deserialize))] 49 | #[must_use] 50 | /// The error type used for all [wasmrs_rx::Mono]/[wasmrs_rx::Flux] payloads. 51 | pub struct PayloadError { 52 | /// The error code. 53 | pub code: u32, 54 | /// Metadata associated with the error. 55 | pub metadata: Option, 56 | /// The error message. 57 | pub msg: String, 58 | } 59 | 60 | impl PayloadError { 61 | /// Create a new [PayloadError] with the passed code and message. 62 | pub fn new(code: u32, msg: impl AsRef, metadata: Option) -> Self { 63 | Self { 64 | code, 65 | metadata, 66 | msg: msg.as_ref().to_owned(), 67 | } 68 | } 69 | 70 | /// Create a new [PayloadError] with the [ErrorCode::ApplicationError] code. 71 | pub fn application_error(msg: impl AsRef, metadata: Option) -> Self { 72 | Self { 73 | code: ErrorCode::ApplicationError.into(), 74 | metadata, 75 | msg: msg.as_ref().to_owned(), 76 | } 77 | } 78 | } 79 | impl std::error::Error for PayloadError {} 80 | impl std::fmt::Display for PayloadError { 81 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 82 | f.write_str(&self.msg) 83 | } 84 | } 85 | 86 | impl From for PayloadError { 87 | fn from(e: Error) -> Self { 88 | app_err(&e) 89 | } 90 | } 91 | 92 | impl From> for PayloadError { 93 | fn from(e: Box) -> Self { 94 | app_err(e.as_ref()) 95 | } 96 | } 97 | 98 | impl From> for PayloadError { 99 | fn from(e: Box) -> Self { 100 | app_err(e.as_ref()) 101 | } 102 | } 103 | 104 | fn app_err(e: &dyn std::error::Error) -> PayloadError { 105 | PayloadError::application_error(e.to_string(), None) 106 | } 107 | -------------------------------------------------------------------------------- /crates/wasmrs-frames/src/frames/f_cancel.rs: -------------------------------------------------------------------------------- 1 | use bytes::Bytes; 2 | 3 | use super::{Error, FrameHeader, FrameType, RSocketFrame}; 4 | use crate::Frame; 5 | 6 | #[derive(Clone)] 7 | #[cfg_attr(not(target = "wasm32-unknown-unknown"), derive(Debug))] 8 | #[must_use] 9 | pub struct Cancel { 10 | /// The stream ID this frame belongs to. 11 | pub stream_id: u32, 12 | } 13 | 14 | impl RSocketFrame for Cancel { 15 | const FRAME_TYPE: FrameType = FrameType::Cancel; 16 | 17 | fn stream_id(&self) -> u32 { 18 | self.stream_id 19 | } 20 | 21 | fn decode_all(mut buffer: Bytes) -> Result { 22 | let header = FrameHeader::from_bytes(buffer.split_to(Frame::LEN_HEADER)); 23 | Self::decode_frame(&header, buffer) 24 | } 25 | 26 | fn decode_frame(header: &FrameHeader, _buffer: Bytes) -> Result { 27 | Self::check_type(header)?; 28 | Ok(Cancel { 29 | stream_id: header.stream_id(), 30 | }) 31 | } 32 | 33 | fn encode(self) -> Bytes { 34 | self.gen_header().encode() 35 | } 36 | 37 | fn gen_header(&self) -> FrameHeader { 38 | FrameHeader::new(self.stream_id, FrameType::Cancel, 0) 39 | } 40 | } 41 | 42 | #[cfg(test)] 43 | mod test { 44 | use anyhow::Result; 45 | 46 | use super::*; 47 | use crate::frames::RSocketFrame; 48 | 49 | static BYTES: &[u8] = include_bytes!("../../testdata/frame.cancel.bin"); 50 | 51 | #[test] 52 | fn test_decode() -> Result<()> { 53 | println!("RAW: {:?}", BYTES); 54 | let p = Cancel::decode_all(BYTES.into())?; 55 | assert_eq!(p.stream_id, 1234); 56 | Ok(()) 57 | } 58 | 59 | #[test] 60 | fn test_encode() -> Result<()> { 61 | let payload = Cancel { stream_id: 1234 }; 62 | let encoded = payload.encode(); 63 | assert_eq!(encoded, Bytes::from(BYTES)); 64 | Ok(()) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /crates/wasmrs-frames/src/frames/f_error.rs: -------------------------------------------------------------------------------- 1 | use bytes::{BufMut, Bytes, BytesMut}; 2 | 3 | use super::{Error, FrameHeader, FrameType, RSocketFrame}; 4 | use crate::util::{from_u24_bytes, from_u32_bytes, to_u24_bytes}; 5 | use crate::{Frame, FrameFlags}; 6 | 7 | #[derive(Clone)] 8 | #[cfg_attr(not(target = "wasm32-unknown-unknown"), derive(Debug))] 9 | #[must_use] 10 | pub struct ErrorFrame { 11 | /// The stream ID this frame belongs to. 12 | pub stream_id: u32, 13 | /// The error code. 14 | pub code: u32, 15 | /// Any metadata associated with the Error as raw bytes. 16 | pub metadata: Option, 17 | /// The error message data. 18 | pub data: String, 19 | } 20 | 21 | impl ErrorFrame {} 22 | 23 | impl RSocketFrame for ErrorFrame { 24 | const FRAME_TYPE: FrameType = FrameType::Err; 25 | 26 | fn stream_id(&self) -> u32 { 27 | self.stream_id 28 | } 29 | 30 | fn decode_all(mut buffer: Bytes) -> Result { 31 | let header = FrameHeader::from_bytes(buffer.split_to(Frame::LEN_HEADER)); 32 | Self::decode_frame(&header, buffer) 33 | } 34 | 35 | fn decode_frame(header: &FrameHeader, mut buffer: Bytes) -> Result { 36 | Self::check_type(header)?; 37 | let metadata = if header.has_metadata() { 38 | let metadata_len = from_u24_bytes(&buffer.split_to(3)) as usize; 39 | Some(buffer.split_to(metadata_len)) 40 | } else { 41 | None 42 | }; 43 | 44 | Ok(ErrorFrame { 45 | stream_id: header.stream_id(), 46 | metadata, 47 | code: from_u32_bytes(&buffer.split_to(4)), 48 | data: String::from_utf8(buffer.to_vec()).map_err(|_| crate::Error::StringConversion)?, 49 | }) 50 | } 51 | 52 | fn encode(self) -> Bytes { 53 | let header = self.gen_header().encode(); 54 | let (mlen, md) = self.metadata.map_or_else( 55 | || (Bytes::new(), Bytes::new()), 56 | |md| (to_u24_bytes(md.len() as u32), md), 57 | ); 58 | 59 | let code = self.code.to_be_bytes(); 60 | let data = self.data.into_bytes(); 61 | let mut bytes = BytesMut::with_capacity(Frame::LEN_HEADER + code.len() + data.len()); 62 | bytes.put(header); 63 | bytes.put(mlen); 64 | bytes.put(md); 65 | bytes.put(code.as_slice()); 66 | bytes.put(data.as_slice()); 67 | bytes.freeze() 68 | } 69 | 70 | fn gen_header(&self) -> FrameHeader { 71 | FrameHeader::new(self.stream_id, FrameType::Err, self.get_flag()) 72 | } 73 | 74 | fn get_flag(&self) -> FrameFlags { 75 | let mut flags = 0; 76 | if self.metadata.is_some() { 77 | flags |= Frame::FLAG_METADATA; 78 | } 79 | flags 80 | } 81 | } 82 | 83 | #[cfg(test)] 84 | mod test { 85 | use anyhow::Result; 86 | 87 | use super::*; 88 | use crate::frames::RSocketFrame; 89 | 90 | static BYTES: &[u8] = include_bytes!("../../testdata/frame.error.bin"); 91 | 92 | #[test] 93 | fn test_decode() -> Result<()> { 94 | println!("{:?}", BYTES); 95 | let p = ErrorFrame::decode_all(BYTES.into())?; 96 | assert_eq!(p.stream_id, 1234); 97 | assert_eq!(&p.data, "errstr"); 98 | assert_eq!(p.code, 11); 99 | Ok(()) 100 | } 101 | 102 | #[test] 103 | fn test_encode() -> Result<()> { 104 | let payload = ErrorFrame { 105 | stream_id: 1234, 106 | metadata: None, 107 | data: "errstr".to_owned(), 108 | code: 11, 109 | }; 110 | let encoded = payload.encode(); 111 | assert_eq!(encoded, Bytes::from(BYTES)); 112 | Ok(()) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /crates/wasmrs-frames/src/frames/f_payload.rs: -------------------------------------------------------------------------------- 1 | use bytes::{BufMut, Bytes, BytesMut}; 2 | 3 | use super::{Error, FrameFlags, FrameHeader, FrameType, RSocketFlags, RSocketFrame}; 4 | use crate::util::{from_u24_bytes, to_u24_bytes}; 5 | use crate::{Frame, RawPayload}; 6 | 7 | /// A Payload frame. 8 | #[derive(Clone)] 9 | #[cfg_attr(not(target = "wasm32-unknown-unknown"), derive(Debug))] 10 | #[must_use] 11 | pub struct PayloadFrame { 12 | /// The stream ID this frame belongs to. 13 | pub stream_id: u32, 14 | /// Any metadata associated with the Payload as raw bytes. 15 | pub metadata: Bytes, 16 | /// The actual payload data as raw bytes. 17 | pub data: Bytes, 18 | /// Whether this payload is broken up into multiple frames. 19 | pub follows: bool, 20 | /// Whether or not this frame is the last frame in a stream. 21 | pub complete: bool, 22 | /// Whether or not this frame is followed by another frame. 23 | pub next: bool, 24 | } 25 | 26 | impl PayloadFrame { 27 | pub(crate) fn from_payload(stream_id: u32, payload: RawPayload, flags: FrameFlags) -> Self { 28 | Self { 29 | stream_id, 30 | metadata: payload.metadata.unwrap_or_default(), 31 | data: payload.data.unwrap_or_default(), 32 | follows: flags.flag_follows(), 33 | complete: flags.flag_complete(), 34 | next: flags.flag_next(), 35 | } 36 | } 37 | } 38 | 39 | impl RSocketFrame for PayloadFrame { 40 | const FRAME_TYPE: FrameType = FrameType::Payload; 41 | 42 | fn stream_id(&self) -> u32 { 43 | self.stream_id 44 | } 45 | 46 | fn decode_all(mut buffer: Bytes) -> Result { 47 | let header = FrameHeader::from_bytes(buffer.split_to(Frame::LEN_HEADER)); 48 | Self::decode_frame(&header, buffer) 49 | } 50 | 51 | fn decode_frame(header: &FrameHeader, mut buffer: Bytes) -> Result { 52 | Self::check_type(header)?; 53 | 54 | let metadata = if header.has_metadata() { 55 | let metadata_len = from_u24_bytes(&buffer.split_to(3)) as usize; 56 | buffer.split_to(metadata_len) 57 | } else { 58 | Bytes::new() 59 | }; 60 | let payload: Bytes = buffer; 61 | 62 | Ok(PayloadFrame { 63 | stream_id: header.stream_id(), 64 | metadata, 65 | data: payload, 66 | follows: header.has_follows(), 67 | complete: header.has_complete(), 68 | next: header.has_next(), 69 | }) 70 | } 71 | 72 | fn encode(self) -> Bytes { 73 | let header = self.gen_header().encode(); 74 | let (mlen, md) = if self.metadata.is_empty() { 75 | (Bytes::new(), Bytes::new()) 76 | } else { 77 | (to_u24_bytes(self.metadata.len() as u32), self.metadata) 78 | }; 79 | let data = self.data; 80 | let mut bytes = BytesMut::with_capacity(Frame::LEN_HEADER + mlen.len() + md.len() + data.len()); 81 | bytes.put(header); 82 | bytes.put(mlen); 83 | bytes.put(md); 84 | bytes.put(data); 85 | bytes.freeze() 86 | } 87 | 88 | fn gen_header(&self) -> FrameHeader { 89 | FrameHeader::new(self.stream_id, FrameType::Payload, self.get_flag()) 90 | } 91 | 92 | fn get_flag(&self) -> FrameFlags { 93 | let mut flags = 0; 94 | if !self.metadata.is_empty() { 95 | flags |= Frame::FLAG_METADATA; 96 | } 97 | if self.complete { 98 | flags |= Frame::FLAG_COMPLETE; 99 | } 100 | if self.next { 101 | flags |= Frame::FLAG_NEXT; 102 | } 103 | if self.follows { 104 | flags |= Frame::FLAG_FOLLOW; 105 | } 106 | flags 107 | } 108 | } 109 | 110 | impl From for RawPayload { 111 | fn from(req: PayloadFrame) -> Self { 112 | RawPayload { 113 | metadata: Some(req.metadata), 114 | data: Some(req.data), 115 | } 116 | } 117 | } 118 | 119 | #[cfg(test)] 120 | mod test { 121 | use anyhow::Result; 122 | 123 | use super::*; 124 | use crate::frames::RSocketFrame; 125 | 126 | static BYTES: &[u8] = include_bytes!("../../testdata/frame.payload.bin"); 127 | 128 | #[test] 129 | fn test_decode() -> Result<()> { 130 | println!("RAW: {:?}", BYTES); 131 | let p = PayloadFrame::decode_all(BYTES.into())?; 132 | assert_eq!(p.stream_id, 1234); 133 | assert_eq!(p.data, Bytes::from("hello")); 134 | assert_eq!(p.metadata, Bytes::from("hello")); 135 | Ok(()) 136 | } 137 | 138 | #[test] 139 | fn test_encode() -> Result<()> { 140 | let payload = PayloadFrame { 141 | stream_id: 1234, 142 | metadata: Bytes::from("hello"), 143 | data: Bytes::from("hello"), 144 | follows: true, 145 | complete: true, 146 | next: true, 147 | }; 148 | let encoded = payload.encode(); 149 | assert_eq!(encoded, Bytes::from(BYTES)); 150 | Ok(()) 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /crates/wasmrs-frames/src/frames/f_request_channel.rs: -------------------------------------------------------------------------------- 1 | use bytes::Bytes; 2 | 3 | use super::{request_payload::RequestPayload, Error, FrameFlags, FrameHeader, FrameType, RSocketFrame}; 4 | use crate::{Frame, RawPayload}; 5 | 6 | #[cfg_attr(not(target = "wasm32-unknown-unknown"), derive(Debug))] 7 | #[must_use] 8 | #[derive(Clone)] 9 | pub struct RequestChannel(pub RequestPayload); 10 | 11 | impl RequestChannel { 12 | pub(crate) fn from_payload(stream_id: u32, payload: RawPayload, flags: FrameFlags, initial_n: u32) -> Self { 13 | Self(RequestPayload::from_payload( 14 | stream_id, 15 | payload, 16 | Self::FRAME_TYPE, 17 | flags, 18 | initial_n, 19 | )) 20 | } 21 | } 22 | 23 | impl RSocketFrame for RequestChannel { 24 | const FRAME_TYPE: FrameType = FrameType::RequestChannel; 25 | 26 | fn stream_id(&self) -> u32 { 27 | self.0.stream_id 28 | } 29 | 30 | fn decode_all(mut buffer: Bytes) -> Result { 31 | let header = FrameHeader::from_bytes(buffer.split_to(Frame::LEN_HEADER)); 32 | Self::decode_frame(&header, buffer) 33 | } 34 | 35 | fn decode_frame(header: &FrameHeader, buffer: Bytes) -> Result { 36 | Self::check_type(header)?; 37 | Ok(Self(RequestPayload::decode(header, buffer)?)) 38 | } 39 | 40 | fn encode(self) -> Bytes { 41 | self.0.encode() 42 | } 43 | 44 | fn gen_header(&self) -> FrameHeader { 45 | self.0.gen_header() 46 | } 47 | 48 | fn get_flag(&self) -> FrameFlags { 49 | self.0.get_flags() 50 | } 51 | } 52 | 53 | impl From for RawPayload { 54 | fn from(req: RequestChannel) -> Self { 55 | req.0.into() 56 | } 57 | } 58 | 59 | #[cfg(test)] 60 | mod test { 61 | use anyhow::Result; 62 | 63 | use super::*; 64 | 65 | static BYTES: &[u8] = include_bytes!("../../testdata/frame.request_channel.bin"); 66 | 67 | #[test] 68 | fn test_decode() -> Result<()> { 69 | println!("RAW: {:?}", BYTES); 70 | let p = RequestChannel::decode_all(BYTES.into())?; 71 | assert_eq!(p.0.stream_id, 1234); 72 | Ok(()) 73 | } 74 | 75 | #[test] 76 | fn test_encode() -> Result<()> { 77 | let payload = RequestPayload { 78 | frame_type: FrameType::RequestChannel, 79 | stream_id: 1234, 80 | metadata: Bytes::from("hello"), 81 | data: Bytes::from("hello"), 82 | follows: true, 83 | complete: true, 84 | initial_n: 0, 85 | }; 86 | let this = RequestChannel(payload); 87 | let encoded = this.encode(); 88 | assert_eq!(encoded, Bytes::from(BYTES)); 89 | Ok(()) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /crates/wasmrs-frames/src/frames/f_request_fnf.rs: -------------------------------------------------------------------------------- 1 | use bytes::Bytes; 2 | 3 | use super::{request_payload::RequestPayload, Error, FrameFlags, FrameHeader, FrameType, RSocketFrame}; 4 | use crate::{Frame, RawPayload}; 5 | 6 | #[cfg_attr(not(target = "wasm32-unknown-unknown"), derive(Debug))] 7 | #[must_use] 8 | #[derive(Clone)] 9 | pub struct RequestFnF(pub RequestPayload); 10 | 11 | impl RequestFnF { 12 | pub(crate) fn from_payload(stream_id: u32, payload: RawPayload, flags: FrameFlags, initial_n: u32) -> Self { 13 | Self(RequestPayload::from_payload( 14 | stream_id, 15 | payload, 16 | Self::FRAME_TYPE, 17 | flags, 18 | initial_n, 19 | )) 20 | } 21 | } 22 | 23 | impl RSocketFrame for RequestFnF { 24 | const FRAME_TYPE: FrameType = FrameType::RequestFnf; 25 | 26 | fn stream_id(&self) -> u32 { 27 | self.0.stream_id 28 | } 29 | 30 | fn decode_all(mut buffer: Bytes) -> Result { 31 | let header = FrameHeader::from_bytes(buffer.split_to(Frame::LEN_HEADER)); 32 | Self::decode_frame(&header, buffer) 33 | } 34 | 35 | fn decode_frame(header: &FrameHeader, buffer: Bytes) -> Result { 36 | Self::check_type(header)?; 37 | Ok(Self(RequestPayload::decode(header, buffer)?)) 38 | } 39 | 40 | fn encode(self) -> Bytes { 41 | self.0.encode() 42 | } 43 | 44 | fn gen_header(&self) -> FrameHeader { 45 | self.0.gen_header() 46 | } 47 | 48 | fn get_flag(&self) -> FrameFlags { 49 | self.0.get_flags() 50 | } 51 | } 52 | 53 | impl From for RawPayload { 54 | fn from(req: RequestFnF) -> Self { 55 | req.0.into() 56 | } 57 | } 58 | 59 | #[cfg(test)] 60 | mod test { 61 | use anyhow::Result; 62 | 63 | use super::*; 64 | 65 | static BYTES: &[u8] = include_bytes!("../../testdata/frame.request_fnf.bin"); 66 | 67 | #[test] 68 | fn test_decode() -> Result<()> { 69 | println!("RAW: {:?}", BYTES); 70 | let p = RequestFnF::decode_all(BYTES.into())?; 71 | assert_eq!(p.0.stream_id, 1234); 72 | Ok(()) 73 | } 74 | 75 | #[test] 76 | fn test_encode() -> Result<()> { 77 | let payload = RequestPayload { 78 | frame_type: FrameType::RequestFnf, 79 | stream_id: 1234, 80 | metadata: Bytes::from("hello"), 81 | data: Bytes::from("hello"), 82 | follows: true, 83 | complete: true, 84 | initial_n: 0, 85 | }; 86 | let this = RequestFnF(payload); 87 | let encoded = this.encode(); 88 | assert_eq!(encoded, Bytes::from(BYTES)); 89 | Ok(()) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /crates/wasmrs-frames/src/frames/f_request_n.rs: -------------------------------------------------------------------------------- 1 | use bytes::Bytes; 2 | 3 | use super::{Error, FrameHeader, FrameType, RSocketFrame}; 4 | use crate::util::from_u32_bytes; 5 | use crate::Frame; 6 | 7 | #[derive(Clone)] 8 | #[allow(missing_copy_implementations)] 9 | #[cfg_attr(not(target = "wasm32-unknown-unknown"), derive(Debug))] 10 | #[must_use] 11 | pub struct RequestN { 12 | /// The stream ID this frame belongs to. 13 | pub stream_id: u32, 14 | pub n: u32, 15 | } 16 | 17 | impl RequestN {} 18 | 19 | impl RSocketFrame for RequestN { 20 | const FRAME_TYPE: FrameType = FrameType::RequestN; 21 | 22 | fn stream_id(&self) -> u32 { 23 | self.stream_id 24 | } 25 | 26 | fn decode_all(mut buffer: Bytes) -> Result { 27 | let header = FrameHeader::from_bytes(buffer.split_to(Frame::LEN_HEADER)); 28 | Self::decode_frame(&header, buffer) 29 | } 30 | 31 | fn decode_frame(header: &FrameHeader, mut buffer: Bytes) -> Result { 32 | Self::check_type(header)?; 33 | Ok(RequestN { 34 | stream_id: header.stream_id(), 35 | n: from_u32_bytes(&buffer.split_to(4)), 36 | }) 37 | } 38 | 39 | fn encode(self) -> Bytes { 40 | [self.gen_header().encode(), self.n.to_be_bytes().to_vec().into()] 41 | .concat() 42 | .into() 43 | } 44 | 45 | fn gen_header(&self) -> FrameHeader { 46 | FrameHeader::new(self.stream_id, FrameType::RequestN, 0) 47 | } 48 | } 49 | 50 | #[cfg(test)] 51 | mod test { 52 | use anyhow::Result; 53 | 54 | use super::*; 55 | use crate::frames::RSocketFrame; 56 | 57 | static BYTES: &[u8] = include_bytes!("../../testdata/frame.request_n.bin"); 58 | 59 | #[test] 60 | fn test_decode() -> Result<()> { 61 | println!("RAW {:?}", BYTES); 62 | let p = RequestN::decode_all(BYTES.into())?; 63 | assert_eq!(p.stream_id, 1234); 64 | Ok(()) 65 | } 66 | 67 | #[test] 68 | fn test_encode() -> Result<()> { 69 | let payload = RequestN { stream_id: 1234, n: 15 }; 70 | let encoded = payload.encode(); 71 | assert_eq!(encoded, Bytes::from(BYTES)); 72 | Ok(()) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /crates/wasmrs-frames/src/frames/f_request_response.rs: -------------------------------------------------------------------------------- 1 | use bytes::Bytes; 2 | 3 | use super::{request_payload::RequestPayload, Error, FrameFlags, FrameHeader, FrameType, RSocketFrame}; 4 | use crate::{Frame, RawPayload}; 5 | 6 | #[cfg_attr(not(target = "wasm32-unknown-unknown"), derive(Debug))] 7 | #[must_use] 8 | #[derive(Clone)] 9 | pub struct RequestResponse(pub RequestPayload); 10 | 11 | impl RequestResponse { 12 | pub(crate) fn from_payload(stream_id: u32, payload: RawPayload, flags: FrameFlags, initial_n: u32) -> Self { 13 | Self(RequestPayload::from_payload( 14 | stream_id, 15 | payload, 16 | Self::FRAME_TYPE, 17 | flags, 18 | initial_n, 19 | )) 20 | } 21 | } 22 | 23 | impl RSocketFrame for RequestResponse { 24 | const FRAME_TYPE: FrameType = FrameType::RequestResponse; 25 | 26 | fn stream_id(&self) -> u32 { 27 | self.0.stream_id 28 | } 29 | 30 | fn decode_all(mut buffer: Bytes) -> Result { 31 | let header = FrameHeader::from_bytes(buffer.split_to(Frame::LEN_HEADER)); 32 | Self::decode_frame(&header, buffer) 33 | } 34 | 35 | fn decode_frame(header: &FrameHeader, buffer: Bytes) -> Result { 36 | Self::check_type(header)?; 37 | Ok(Self(RequestPayload::decode(header, buffer)?)) 38 | } 39 | 40 | fn encode(self) -> Bytes { 41 | self.0.encode() 42 | } 43 | 44 | fn gen_header(&self) -> FrameHeader { 45 | FrameHeader::new(self.0.stream_id, FrameType::RequestResponse, Frame::FLAG_METADATA) 46 | } 47 | 48 | fn get_flag(&self) -> FrameFlags { 49 | self.0.get_flags() 50 | } 51 | } 52 | 53 | impl From for RawPayload { 54 | fn from(req: RequestResponse) -> Self { 55 | req.0.into() 56 | } 57 | } 58 | 59 | #[cfg(test)] 60 | mod test { 61 | use anyhow::Result; 62 | 63 | use super::*; 64 | 65 | static BYTES: &[u8] = include_bytes!("../../testdata/frame.request_response.bin"); 66 | 67 | #[test] 68 | fn test_decode() -> Result<()> { 69 | println!("RAW: {:?}", BYTES); 70 | let p = RequestResponse::decode_all(BYTES.into())?; 71 | assert_eq!(p.0.stream_id, 1234); 72 | assert_eq!(p.0.data, Bytes::from(b"hello".as_slice())); 73 | Ok(()) 74 | } 75 | 76 | #[test] 77 | fn test_encode() -> Result<()> { 78 | let payload = RequestPayload { 79 | frame_type: FrameType::RequestResponse, 80 | stream_id: 1234, 81 | metadata: Bytes::from("hello"), 82 | data: Bytes::from("hello"), 83 | follows: true, 84 | complete: true, 85 | initial_n: 1, 86 | }; 87 | let this = RequestResponse(payload); 88 | let encoded = this.encode(); 89 | assert_eq!(encoded, Bytes::from(BYTES)); 90 | Ok(()) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /crates/wasmrs-frames/src/frames/f_request_stream.rs: -------------------------------------------------------------------------------- 1 | use bytes::Bytes; 2 | 3 | use super::{request_payload::RequestPayload, Error, FrameFlags, FrameHeader, FrameType, RSocketFrame}; 4 | use crate::{Frame, RawPayload}; 5 | 6 | #[cfg_attr(not(target = "wasm32-unknown-unknown"), derive(Debug))] 7 | #[must_use] 8 | #[derive(Clone)] 9 | pub struct RequestStream(pub RequestPayload); 10 | 11 | impl RequestStream { 12 | pub(crate) fn from_payload(stream_id: u32, payload: RawPayload, flags: FrameFlags, initial_n: u32) -> Self { 13 | Self(RequestPayload::from_payload( 14 | stream_id, 15 | payload, 16 | Self::FRAME_TYPE, 17 | flags, 18 | initial_n, 19 | )) 20 | } 21 | } 22 | 23 | impl RSocketFrame for RequestStream { 24 | const FRAME_TYPE: FrameType = FrameType::RequestStream; 25 | 26 | fn stream_id(&self) -> u32 { 27 | self.0.stream_id 28 | } 29 | 30 | fn decode_all(mut buffer: Bytes) -> Result { 31 | let header = FrameHeader::from_bytes(buffer.split_to(Frame::LEN_HEADER)); 32 | Self::decode_frame(&header, buffer) 33 | } 34 | 35 | fn decode_frame(header: &FrameHeader, buffer: Bytes) -> Result { 36 | Self::check_type(header)?; 37 | Ok(Self(RequestPayload::decode(header, buffer)?)) 38 | } 39 | 40 | fn encode(self) -> Bytes { 41 | self.0.encode() 42 | } 43 | 44 | fn gen_header(&self) -> FrameHeader { 45 | self.0.gen_header() 46 | } 47 | 48 | fn get_flag(&self) -> FrameFlags { 49 | self.0.get_flags() 50 | } 51 | } 52 | 53 | impl From for RawPayload { 54 | fn from(req: RequestStream) -> Self { 55 | req.0.into() 56 | } 57 | } 58 | 59 | #[cfg(test)] 60 | mod test { 61 | use anyhow::Result; 62 | 63 | use super::*; 64 | use crate::frames::RSocketFrame; 65 | 66 | static BYTES: &[u8] = include_bytes!("../../testdata/frame.request_stream.bin"); 67 | 68 | #[test] 69 | fn test_decode() -> Result<()> { 70 | println!("RAW: {:?}", BYTES); 71 | let p = RequestStream::decode_all(BYTES.into())?; 72 | assert_eq!(p.0.stream_id, 1234); 73 | Ok(()) 74 | } 75 | 76 | #[test] 77 | fn test_encode() -> Result<()> { 78 | let payload = RequestPayload { 79 | frame_type: FrameType::RequestStream, 80 | stream_id: 1234, 81 | metadata: Bytes::from("hello"), 82 | data: Bytes::from("hello"), 83 | follows: true, 84 | complete: true, 85 | initial_n: 0, 86 | }; 87 | let this = RequestStream(payload); 88 | let encoded = this.encode(); 89 | assert_eq!(encoded, Bytes::from(BYTES)); 90 | Ok(()) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /crates/wasmrs-frames/src/frames/header.rs: -------------------------------------------------------------------------------- 1 | use bytes::{BufMut, Bytes, BytesMut}; 2 | 3 | use super::{FrameFlags, FrameHeader, FrameType}; 4 | use crate::util::from_u16_bytes; 5 | use crate::Frame; 6 | 7 | impl FrameHeader { 8 | pub(crate) fn new(stream_id: u32, frame_type: FrameType, frame_flags: u16) -> Self { 9 | let mut header = BytesMut::with_capacity(Frame::LEN_HEADER); 10 | let frame_type: u32 = frame_type.into(); 11 | let frame_type: u16 = frame_type.try_into().unwrap(); 12 | let frame_type = (frame_type << 10) | frame_flags; 13 | 14 | header.put(stream_id.to_be_bytes().as_slice()); 15 | header.put(frame_type.to_be_bytes().as_slice()); 16 | 17 | Self { 18 | header: header.freeze(), 19 | } 20 | } 21 | 22 | pub(crate) fn from_bytes(header: Bytes) -> Self { 23 | Self { header } 24 | } 25 | 26 | #[cfg(test)] 27 | fn as_bytes(&self) -> &[u8] { 28 | &self.header 29 | } 30 | 31 | pub(crate) fn encode(self) -> Bytes { 32 | self.header 33 | } 34 | 35 | pub(crate) fn stream_id(&self) -> u32 { 36 | let bytes: [u8; 4] = [self.header[0] & 0x7f, self.header[1], self.header[2], self.header[3]]; 37 | u32::from_be_bytes(bytes) 38 | } 39 | 40 | fn n(&self) -> u16 { 41 | from_u16_bytes(&self.header.slice(4..Frame::LEN_HEADER)) 42 | } 43 | 44 | pub(crate) fn frame_type(&self) -> FrameType { 45 | let id: u8 = self.header[4] >> 2; 46 | id.try_into().unwrap() 47 | } 48 | 49 | pub(crate) fn check(&self, flag: FrameFlags) -> bool { 50 | self.n() & flag == flag 51 | } 52 | 53 | pub(crate) fn has_metadata(&self) -> bool { 54 | self.check(Frame::FLAG_METADATA) 55 | } 56 | 57 | pub(crate) fn has_follows(&self) -> bool { 58 | self.check(Frame::FLAG_FOLLOW) 59 | } 60 | 61 | pub(crate) fn has_next(&self) -> bool { 62 | self.check(Frame::FLAG_NEXT) 63 | } 64 | 65 | pub(crate) fn has_complete(&self) -> bool { 66 | self.check(Frame::FLAG_COMPLETE) 67 | } 68 | 69 | pub(crate) fn has_ignore(&self) -> bool { 70 | self.check(Frame::FLAG_IGNORE) 71 | } 72 | } 73 | 74 | impl std::fmt::Display for FrameHeader { 75 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 76 | let mut flags = Vec::new(); 77 | if self.has_next() { 78 | flags.push("N"); 79 | } 80 | if self.has_complete() { 81 | flags.push("CL"); 82 | } 83 | if self.has_follows() { 84 | flags.push("FRS"); 85 | } 86 | if self.has_metadata() { 87 | flags.push("M"); 88 | } 89 | if self.has_ignore() { 90 | flags.push("I"); 91 | } 92 | 93 | let t = self.frame_type(); 94 | f.write_str("FrameHeader{{id=")?; 95 | self.stream_id().fmt(f)?; 96 | f.write_str(",type=")?; 97 | t.fmt(f)?; 98 | f.write_str(",flag=")?; 99 | flags.join("|").fmt(f)?; 100 | f.write_str("}}") 101 | } 102 | } 103 | 104 | #[cfg(test)] 105 | mod test { 106 | use super::*; 107 | use anyhow::Result; 108 | 109 | fn print_binary(v: &[u8]) { 110 | let mut bytes = Vec::new(); 111 | for byte in v { 112 | bytes.push(format!("{:08b}", byte)); 113 | } 114 | println!("[{}]", bytes.join(" ")); 115 | } 116 | use crate::Frame; 117 | 118 | #[test] 119 | fn test_new_header() -> Result<()> { 120 | let header = FrameHeader::new(2147483647, FrameType::Payload, Frame::FLAG_COMPLETE); 121 | println!("Bytes: {:?}", header.as_bytes()); 122 | println!("Frame type: {}", header.frame_type()); 123 | print_binary(header.as_bytes()); 124 | println!("Header: {}", header); 125 | assert_eq!(header.stream_id(), 2147483647); 126 | assert_eq!(header.frame_type() as u32, FrameType::Payload.into()); 127 | assert!(header.has_complete()); 128 | assert!(!header.has_next()); 129 | assert!(!header.has_metadata()); 130 | assert!(!header.has_follows()); 131 | assert!(!header.has_ignore()); 132 | 133 | Ok(()) 134 | } 135 | 136 | #[test] 137 | fn test_payload_header() -> Result<()> { 138 | let frame = include_bytes!("../../testdata/frame.payload.bin"); 139 | let header = FrameHeader::from_bytes(frame[0..Frame::LEN_HEADER].into()); 140 | print_binary(header.as_bytes()); 141 | assert!(header.has_metadata()); 142 | Ok(()) 143 | } 144 | 145 | #[test] 146 | fn test_header() -> Result<()> { 147 | let header = FrameHeader::from_bytes(vec![0u8, 0, 4, 210, 25, 0].into()); 148 | print_binary(header.as_bytes()); 149 | println!("{}", header); 150 | println!("{:?}", header.as_bytes()); 151 | assert!(header.has_metadata()); 152 | Ok(()) 153 | } 154 | 155 | #[test] 156 | fn test_header_no_flags() -> Result<()> { 157 | let header = FrameHeader::new(0, FrameType::RequestStream, 0); 158 | print_binary(header.as_bytes()); 159 | println!("{}", header); 160 | println!("{:?}", header.as_bytes()); 161 | assert!(!header.has_metadata()); 162 | assert!(!header.has_next()); 163 | assert!(!header.has_complete()); 164 | assert!(!header.has_metadata()); 165 | assert!(!header.has_ignore()); 166 | Ok(()) 167 | } 168 | 169 | #[test] 170 | fn test_header_metadata() -> Result<()> { 171 | let header = FrameHeader::new(0, FrameType::RequestStream, Frame::FLAG_METADATA); 172 | print_binary(header.as_bytes()); 173 | println!("{}", header); 174 | println!("{:?}", header.as_bytes()); 175 | assert!(header.has_metadata()); 176 | assert!(!header.has_next()); 177 | assert!(!header.has_complete()); 178 | assert!(!header.has_follows()); 179 | assert!(!header.has_ignore()); 180 | Ok(()) 181 | } 182 | 183 | #[test] 184 | fn test_header_next() -> Result<()> { 185 | let header = FrameHeader::new(0, FrameType::RequestStream, Frame::FLAG_NEXT); 186 | print_binary(header.as_bytes()); 187 | println!("{}", header); 188 | println!("{:?}", header.as_bytes()); 189 | assert!(!header.has_metadata()); 190 | assert!(header.has_next()); 191 | assert!(!header.has_complete()); 192 | assert!(!header.has_follows()); 193 | assert!(!header.has_ignore()); 194 | Ok(()) 195 | } 196 | 197 | #[test] 198 | fn test_header_complete() -> Result<()> { 199 | let header = FrameHeader::new(0, FrameType::RequestStream, Frame::FLAG_COMPLETE); 200 | print_binary(header.as_bytes()); 201 | println!("{}", header); 202 | println!("{:?}", header.as_bytes()); 203 | assert!(!header.has_metadata()); 204 | assert!(!header.has_next()); 205 | assert!(header.has_complete()); 206 | assert!(!header.has_follows()); 207 | assert!(!header.has_ignore()); 208 | Ok(()) 209 | } 210 | 211 | #[test] 212 | fn test_header_ignore() -> Result<()> { 213 | let header = FrameHeader::new(0, FrameType::RequestStream, Frame::FLAG_IGNORE); 214 | print_binary(header.as_bytes()); 215 | println!("{}", header); 216 | println!("{:?}", header.as_bytes()); 217 | assert!(!header.has_metadata()); 218 | assert!(!header.has_next()); 219 | assert!(!header.has_complete()); 220 | assert!(!header.has_follows()); 221 | assert!(header.has_ignore()); 222 | Ok(()) 223 | } 224 | 225 | #[test] 226 | fn test_header_follows() -> Result<()> { 227 | let header = FrameHeader::new(0, FrameType::RequestStream, Frame::FLAG_FOLLOW); 228 | print_binary(header.as_bytes()); 229 | println!("{}", header); 230 | println!("{:?}", header.as_bytes()); 231 | assert!(!header.has_metadata()); 232 | assert!(!header.has_next()); 233 | assert!(!header.has_complete()); 234 | assert!(header.has_follows()); 235 | assert!(!header.has_ignore()); 236 | Ok(()) 237 | } 238 | 239 | // #[test] 240 | // fn test_flags() -> Result<()> { 241 | // let header = FrameHeader::new(0, FrameType::RequestStream, FRAME_FLAG_IGNORE); 242 | // print_binary(&FRAME_FLAG_IGNORE.to_be_bytes()); 243 | // print_binary(&FRAME_FLAG_NEXT.to_be_bytes()); 244 | // print_binary(&FRAME_FLAG_COMPLETE.to_be_bytes()); 245 | // print_binary(&FRAME_FLAG_FOLLOWS.to_be_bytes()); 246 | // print_binary(&FRAME_FLAG_METADATA.to_be_bytes()); 247 | // panic!(); 248 | // Ok(()) 249 | // } 250 | } 251 | -------------------------------------------------------------------------------- /crates/wasmrs-frames/src/frames/metadata.rs: -------------------------------------------------------------------------------- 1 | use bytes::{Buf, BufMut, Bytes, BytesMut}; 2 | 3 | use crate::util::{from_u24_bytes, to_u24_bytes}; 4 | 5 | use super::Metadata; 6 | 7 | impl Metadata { 8 | /// Create a new [Metadata] object for the specified stream_id. 9 | pub fn new(index: u32) -> Metadata { 10 | Metadata { 11 | index: Some(index), 12 | extra: None, 13 | } 14 | } 15 | 16 | /// Create a new [Metadata] object for the specified stream_id. 17 | pub fn new_extra(index: u32, extra: Bytes) -> Metadata { 18 | Metadata { 19 | index: Some(index), 20 | extra: Some(extra), 21 | } 22 | } 23 | 24 | #[must_use] 25 | /// Encode the [Metadata] object into bytes for sending in a [crate::Frame]. 26 | pub fn encode(self) -> Bytes { 27 | let custom_mime_len = 4; 28 | let our_len: u32 = self 29 | .index 30 | .map_or(0, |_| 8 + self.extra.as_ref().map(|e| e.len()).unwrap_or(0) as u32); 31 | 32 | let mut bytes = BytesMut::with_capacity(custom_mime_len + our_len as usize); 33 | bytes.fill(0); 34 | bytes.put_u8(0xca); 35 | bytes.put(to_u24_bytes(our_len)); 36 | 37 | if let Some(index) = &self.index { 38 | bytes.put((index).to_be_bytes().as_slice()); 39 | bytes.put([0u8, 0, 0, 0].as_slice()); // reserved 40 | 41 | if let Some(extra) = self.extra { 42 | bytes.put(extra); 43 | } 44 | } 45 | 46 | bytes.freeze() 47 | } 48 | 49 | /// Decode bytes into [Metadata] object 50 | pub fn decode(bytes: &mut Bytes) -> Result { 51 | if bytes.is_empty() { 52 | return Ok(Self { 53 | index: None, 54 | extra: None, 55 | }); 56 | } 57 | 58 | if bytes[0] == 0xca { 59 | // new, RSocket-aligned metadata 60 | 61 | let _mime_type = bytes.get_u8(); 62 | 63 | let _mime_len = from_u24_bytes(&bytes.split_to(3)) as usize; 64 | 65 | let index = bytes.get_u32(); 66 | let _reserved = bytes.get_u32(); 67 | 68 | let extra = if bytes.is_empty() { 69 | None 70 | } else { 71 | Some(bytes.split_to(bytes.remaining())) 72 | }; 73 | 74 | Ok(Self { 75 | index: Some(index), 76 | extra, 77 | }) 78 | } else { 79 | let index = bytes.get_u32(); 80 | 81 | let _reserved = bytes.get_u32(); 82 | let extra = if bytes.is_empty() { None } else { Some(bytes.clone()) }; 83 | let md = Metadata { 84 | index: Some(index), 85 | extra, 86 | }; 87 | Ok(md) 88 | } 89 | } 90 | } 91 | 92 | #[cfg(test)] 93 | mod test { 94 | use super::*; 95 | use anyhow::Result; 96 | 97 | #[test] 98 | fn test_rt() -> Result<()> { 99 | let md = Metadata::new(32); 100 | let bytes = md.clone().encode(); 101 | let mut bytes = bytes.clone(); 102 | let md2 = Metadata::decode(&mut bytes)?; 103 | 104 | assert_eq!(md, md2); 105 | 106 | Ok(()) 107 | } 108 | 109 | #[test] 110 | fn test_old() -> Result<()> { 111 | let md = Metadata::new(48); 112 | 113 | let mut bytes: Bytes = vec![0, 0, 0, 0x30, 0, 0, 0, 0].into(); 114 | let md2 = Metadata::decode(&mut bytes)?; 115 | 116 | assert_eq!(md, md2); 117 | 118 | Ok(()) 119 | } 120 | 121 | #[test] 122 | fn test_new() -> Result<()> { 123 | let md = Metadata::new(48); 124 | let mut bytes: Bytes = vec![0xca, 0, 0, 8, 0, 0, 0, 0x30, 0, 0, 0, 0].into(); 125 | let md2 = Metadata::decode(&mut bytes)?; 126 | 127 | assert_eq!(md, md2); 128 | 129 | Ok(()) 130 | } 131 | 132 | #[test] 133 | fn test_new_extra() -> Result<()> { 134 | let md = Metadata::new_extra(48, b"hello".to_vec().into()); 135 | let mut bytes = md.clone().encode(); 136 | 137 | let md2 = Metadata::decode(&mut bytes)?; 138 | 139 | assert_eq!(md, md2); 140 | 141 | Ok(()) 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /crates/wasmrs-frames/src/frames/request_payload.rs: -------------------------------------------------------------------------------- 1 | use bytes::{BufMut, Bytes, BytesMut}; 2 | 3 | use super::{Error, FrameFlags, FrameHeader, FrameType, RSocketFlags}; 4 | use crate::util::{from_u24_bytes, from_u32_bytes, to_u24_bytes}; 5 | use crate::{Frame, RawPayload}; 6 | 7 | #[derive(Clone)] 8 | #[cfg_attr(not(target = "wasm32-unknown-unknown"), derive(Debug))] 9 | #[must_use] 10 | pub struct RequestPayload { 11 | /// The type of Request this payload creates. 12 | pub frame_type: FrameType, 13 | /// The stream ID this frame belongs to. 14 | pub stream_id: u32, 15 | /// Any metadata associated with the Payload as raw bytes. 16 | pub metadata: Bytes, 17 | /// The actual payload data as raw bytes. 18 | pub data: Bytes, 19 | /// Whether this payload is broken up into multiple frames. 20 | pub follows: bool, 21 | /// Whether or not this frame is the last frame in a stream. 22 | pub complete: bool, 23 | pub initial_n: u32, 24 | } 25 | 26 | impl RequestPayload { 27 | pub(super) fn from_payload( 28 | stream_id: u32, 29 | payload: RawPayload, 30 | frame_type: FrameType, 31 | flags: FrameFlags, 32 | initial_n: u32, 33 | ) -> Self { 34 | Self { 35 | stream_id, 36 | metadata: payload.metadata.unwrap_or_default(), 37 | data: payload.data.unwrap_or_default(), 38 | follows: flags.flag_follows(), 39 | complete: flags.flag_complete(), 40 | frame_type, 41 | initial_n, 42 | } 43 | } 44 | 45 | pub(super) fn get_flags(&self) -> FrameFlags { 46 | let mut flags = 0; 47 | if !self.metadata.is_empty() { 48 | flags |= Frame::FLAG_METADATA; 49 | } 50 | if self.complete && self.frame_type == FrameType::RequestChannel { 51 | flags |= Frame::FLAG_COMPLETE; 52 | } 53 | flags 54 | } 55 | 56 | pub(crate) fn decode(header: &FrameHeader, mut buffer: Bytes) -> Result { 57 | let frame_type = header.frame_type(); 58 | 59 | let initial_n = if Self::is_multi(frame_type) { 60 | from_u32_bytes(&buffer.split_to(4)) 61 | } else { 62 | 0 63 | }; 64 | 65 | let metadata = if header.has_metadata() { 66 | let metadata_len = from_u24_bytes(&buffer.split_to(3)) as usize; 67 | buffer.split_to(metadata_len) 68 | } else { 69 | Bytes::new() 70 | }; 71 | 72 | let payload: Bytes = buffer; 73 | 74 | Ok(RequestPayload { 75 | frame_type, 76 | stream_id: header.stream_id(), 77 | metadata, 78 | data: payload, 79 | follows: header.has_follows(), 80 | complete: header.has_complete(), 81 | initial_n, 82 | }) 83 | } 84 | 85 | fn is_multi(frame_type: FrameType) -> bool { 86 | matches!(frame_type, FrameType::RequestChannel | FrameType::RequestStream) 87 | } 88 | 89 | pub(crate) fn gen_header(&self) -> FrameHeader { 90 | FrameHeader::new(self.stream_id, self.frame_type, self.get_flags()) 91 | } 92 | 93 | #[must_use] 94 | pub(crate) fn encode(self) -> Bytes { 95 | let header = self.gen_header().encode(); 96 | let n_bytes = if Self::is_multi(self.frame_type) { 97 | self.initial_n.to_be_bytes().to_vec() 98 | } else { 99 | Vec::new() 100 | }; 101 | 102 | let (mlen, md) = if self.metadata.is_empty() { 103 | (Bytes::new(), Bytes::new()) 104 | } else { 105 | (to_u24_bytes(self.metadata.len() as u32), self.metadata) 106 | }; 107 | let data = self.data; 108 | let frame_len = Frame::LEN_HEADER + n_bytes.len() + mlen.len() + md.len() + data.len(); 109 | let mut bytes = BytesMut::with_capacity(frame_len); 110 | bytes.put(header); 111 | bytes.put(n_bytes.as_slice()); 112 | bytes.put(mlen); 113 | bytes.put(md); 114 | bytes.put(data); 115 | bytes.freeze() 116 | } 117 | } 118 | 119 | impl From for RawPayload { 120 | fn from(req: RequestPayload) -> Self { 121 | RawPayload { 122 | metadata: Some(req.metadata), 123 | data: Some(req.data), 124 | } 125 | } 126 | } 127 | 128 | #[cfg(test)] 129 | mod test { 130 | // Tested in the request* frames 131 | } 132 | -------------------------------------------------------------------------------- /crates/wasmrs-frames/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny( 2 | clippy::expect_used, 3 | clippy::explicit_deref_methods, 4 | clippy::option_if_let_else, 5 | clippy::await_holding_lock, 6 | clippy::cloned_instead_of_copied, 7 | clippy::explicit_into_iter_loop, 8 | clippy::flat_map_option, 9 | clippy::fn_params_excessive_bools, 10 | clippy::implicit_clone, 11 | clippy::inefficient_to_string, 12 | clippy::large_types_passed_by_value, 13 | clippy::manual_ok_or, 14 | clippy::map_flatten, 15 | clippy::map_unwrap_or, 16 | clippy::must_use_candidate, 17 | clippy::needless_for_each, 18 | clippy::needless_pass_by_value, 19 | clippy::option_option, 20 | clippy::redundant_else, 21 | clippy::semicolon_if_nothing_returned, 22 | clippy::too_many_lines, 23 | clippy::trivially_copy_pass_by_ref, 24 | clippy::unnested_or_patterns, 25 | clippy::future_not_send, 26 | clippy::useless_let_if_seq, 27 | clippy::str_to_string, 28 | clippy::inherent_to_string, 29 | clippy::let_and_return, 30 | clippy::string_to_string, 31 | clippy::try_err, 32 | clippy::unused_async, 33 | clippy::missing_enforced_import_renames, 34 | clippy::nonstandard_macro_braces, 35 | clippy::rc_mutex, 36 | clippy::unwrap_or_else_default, 37 | clippy::manual_split_once, 38 | clippy::derivable_impls, 39 | clippy::needless_option_as_deref, 40 | clippy::iter_not_returning_iterator, 41 | clippy::same_name_method, 42 | clippy::manual_assert, 43 | clippy::non_send_fields_in_send_ty, 44 | clippy::equatable_if_let, 45 | bad_style, 46 | clashing_extern_declarations, 47 | dead_code, 48 | deprecated, 49 | explicit_outlives_requirements, 50 | improper_ctypes, 51 | invalid_value, 52 | missing_debug_implementations, 53 | mutable_transmutes, 54 | no_mangle_generic_items, 55 | non_shorthand_field_patterns, 56 | overflowing_literals, 57 | path_statements, 58 | patterns_in_fns_without_body, 59 | private_in_public, 60 | trivial_bounds, 61 | trivial_casts, 62 | trivial_numeric_casts, 63 | type_alias_bounds, 64 | unconditional_recursion, 65 | unreachable_pub, 66 | unsafe_code, 67 | unstable_features, 68 | unused, 69 | unused_allocation, 70 | unused_comparisons, 71 | unused_import_braces, 72 | unused_parens, 73 | unused_qualifications, 74 | while_true, 75 | missing_docs 76 | )] 77 | #![doc = include_str!("../README.md")] 78 | 79 | mod frames; 80 | pub use frames::*; 81 | 82 | mod error; 83 | pub use error::{ex_err, Error, PayloadError}; 84 | 85 | pub(crate) mod util; 86 | -------------------------------------------------------------------------------- /crates/wasmrs-frames/src/util.rs: -------------------------------------------------------------------------------- 1 | use bytes::{BufMut, Bytes, BytesMut}; 2 | 3 | #[must_use] 4 | pub(crate) fn from_u32_bytes(bytes: &[u8]) -> u32 { 5 | assert!(bytes.len() == 4, "Need 4 bytes to convert to u32"); 6 | let mut num_parts: [u8; 4] = Default::default(); 7 | 8 | num_parts[0..4].copy_from_slice(bytes); 9 | 10 | u32::from_be_bytes(num_parts) 11 | } 12 | 13 | #[must_use] 14 | pub(crate) fn from_u16_bytes(bytes: &[u8]) -> u16 { 15 | assert!(bytes.len() == 2, "Need two bytes to convert to u16"); 16 | let mut num_parts: [u8; 2] = Default::default(); 17 | 18 | num_parts[0..2].copy_from_slice(bytes); 19 | 20 | u16::from_be_bytes(num_parts) 21 | } 22 | 23 | #[must_use] 24 | pub(crate) fn from_u24_bytes(bytes: &[u8]) -> u32 { 25 | assert!(bytes.len() == 3, "Need three bytes to convert to u24"); 26 | let mut num_parts: [u8; 4] = Default::default(); 27 | 28 | num_parts[1..4].copy_from_slice(bytes); 29 | 30 | u32::from_be_bytes(num_parts) 31 | } 32 | 33 | #[must_use] 34 | /// Convert a [u32] to a `u24` represented in bytes. 35 | pub(crate) fn to_u24_bytes(num: u32) -> Bytes { 36 | let mut num_parts = BytesMut::with_capacity(3); 37 | 38 | num_parts.put(&num.to_be_bytes()[1..4]); 39 | 40 | num_parts.freeze() 41 | } 42 | -------------------------------------------------------------------------------- /crates/wasmrs-frames/testdata/frame.cancel.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WasmRS/wasmrs-rust/2edaa6eeb7c4e1abe8592eb5404e5c9e66c4ac1d/crates/wasmrs-frames/testdata/frame.cancel.bin -------------------------------------------------------------------------------- /crates/wasmrs-frames/testdata/frame.error.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WasmRS/wasmrs-rust/2edaa6eeb7c4e1abe8592eb5404e5c9e66c4ac1d/crates/wasmrs-frames/testdata/frame.error.bin -------------------------------------------------------------------------------- /crates/wasmrs-frames/testdata/frame.payload.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WasmRS/wasmrs-rust/2edaa6eeb7c4e1abe8592eb5404e5c9e66c4ac1d/crates/wasmrs-frames/testdata/frame.payload.bin -------------------------------------------------------------------------------- /crates/wasmrs-frames/testdata/frame.request_channel.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WasmRS/wasmrs-rust/2edaa6eeb7c4e1abe8592eb5404e5c9e66c4ac1d/crates/wasmrs-frames/testdata/frame.request_channel.bin -------------------------------------------------------------------------------- /crates/wasmrs-frames/testdata/frame.request_fnf.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WasmRS/wasmrs-rust/2edaa6eeb7c4e1abe8592eb5404e5c9e66c4ac1d/crates/wasmrs-frames/testdata/frame.request_fnf.bin -------------------------------------------------------------------------------- /crates/wasmrs-frames/testdata/frame.request_n.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WasmRS/wasmrs-rust/2edaa6eeb7c4e1abe8592eb5404e5c9e66c4ac1d/crates/wasmrs-frames/testdata/frame.request_n.bin -------------------------------------------------------------------------------- /crates/wasmrs-frames/testdata/frame.request_response.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WasmRS/wasmrs-rust/2edaa6eeb7c4e1abe8592eb5404e5c9e66c4ac1d/crates/wasmrs-frames/testdata/frame.request_response.bin -------------------------------------------------------------------------------- /crates/wasmrs-frames/testdata/frame.request_stream.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WasmRS/wasmrs-rust/2edaa6eeb7c4e1abe8592eb5404e5c9e66c4ac1d/crates/wasmrs-frames/testdata/frame.request_stream.bin -------------------------------------------------------------------------------- /crates/wasmrs-guest/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasmrs-guest" 3 | version = "0.17.0" 4 | edition = "2021" 5 | license = "Apache-2.0" 6 | description = "wasmRS guest implementation of the RSocket protocol for reactive streams in WebAssembly." 7 | repository = "https://github.com/wasmrs/wasmrs-rust" 8 | 9 | [features] 10 | default = [] 11 | logging = ["env_logger", "log", "wasmrs-runtime/logging"] 12 | record-frames = ["wasmrs/record-frames"] 13 | std = ["wasmrs-codec/std"] 14 | 15 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 16 | [dependencies] 17 | wasmrs-runtime = { path = "../wasmrs-runtime", version = "0.17.1" } 18 | wasmrs-rx = { path = "../wasmrs-rx", version = "0.17.0" } 19 | wasmrs-codec = { path = "../wasmrs-codec", version = "0.17.0" } 20 | wasmrs = { path = "../wasmrs", version = "0.17.0" } 21 | wasmrs-frames = { path = "../wasmrs-frames", version = "0.17.1" } 22 | bytes = { workspace = true, default-features = false, features = ["serde"] } 23 | futures-executor = { workspace = true, default-features = false, features = [ 24 | "std", 25 | ] } 26 | futures-util = { workspace = true, default-features = false, features = [ 27 | "alloc", 28 | ] } 29 | serde = { workspace = true, features = [ 30 | "derive", 31 | "alloc", 32 | ], default-features = false } 33 | tracing = { workspace = true, features = ["log"] } 34 | serde_json = "1.0" 35 | env_logger = { workspace = true, optional = true } 36 | log = { version = "0.4", optional = true } 37 | cfg-if = "1.0" 38 | 39 | [dev-dependencies] 40 | anyhow = { workspace = true } 41 | -------------------------------------------------------------------------------- /crates/wasmrs-guest/README.md: -------------------------------------------------------------------------------- 1 | # wasmrs-guest 2 | 3 | This crate provides the WebAssembly-side logic for wasmRS modules using the wasmRS RSocket protocol. 4 | 5 | 6 | ## Usage 7 | 8 | This is a basic implementation of a WebAssembly module that exports three operations: 9 | 10 | - `greeting::sayHello(input: string) -> string` - returns a greeting, e.g. `Hello World!' 11 | - `echo::chars(input: string) -> stream string` - returns a stream of `string` representing each character in the input string 12 | - `echo::reverse(input: stream string) -> stream string` - reverses each `string` from the input stream and outputs it on a stream. 13 | 14 | ```rs 15 | use guest::*; 16 | use wasmrs_guest as guest; 17 | 18 | #[no_mangle] 19 | extern "C" fn __wasmrs_init(guest_buffer_size: u32, host_buffer_size: u32, max_host_frame_len: u32) { 20 | guest::init(guest_buffer_size, host_buffer_size, max_host_frame_len); 21 | 22 | guest::register_request_response("greeting", "sayHello", request_response); 23 | guest::register_request_stream("echo", "chars", request_stream); 24 | guest::register_request_channel("echo", "reverse", request_channel); 25 | } 26 | 27 | fn request_response(input: Mono) -> Result, GenericError> { 28 | Ok(async move { 29 | let input = deserialize::(&input.await.unwrap().data).unwrap(); 30 | let output = format!("Hello, {}!", input); 31 | Ok(Payload::new_data(None, Some(serialize(&output).unwrap().into()))) 32 | }.boxed()) 33 | } 34 | 35 | fn request_stream( 36 | input: Mono, 37 | ) -> Result, GenericError> { 38 | let channel = FluxChannel::::new(); 39 | let rx = channel.take_rx().unwrap(); 40 | spawn(async move { 41 | let input = deserialize::(&input.await.unwrap().data).unwrap(); 42 | for char in input.chars() { 43 | channel 44 | .send(Payload::new_data(None, Some(serialize(&char).unwrap().into()))) 45 | .unwrap(); 46 | } 47 | }); 48 | 49 | Ok(rx) 50 | } 51 | fn request_channel( 52 | mut input: FluxReceiver, 53 | ) -> Result, GenericError> { 54 | let channel = FluxChannel::::new(); 55 | let rx = channel.take_rx().unwrap(); 56 | spawn(async move { 57 | while let Some(payload) = input.next().await { 58 | if let Err(e) = payload { 59 | println!("{}", e); 60 | continue; 61 | } 62 | let payload = payload.unwrap(); 63 | let input = deserialize::(&payload.data).unwrap(); 64 | let output: String = input.chars().rev().collect(); 65 | if let Err(e) = channel.send(Payload::new_data(None, Some(serialize(&output).unwrap().into()))) { 66 | println!("{}", e); 67 | } 68 | } 69 | }); 70 | 71 | Ok(rx) 72 | } 73 | ``` 74 | 75 | ## Apex Code generators 76 | 77 | NanoBus iota code generators use the wasmRS protocol. You can build `wasmRS` modules from those templates using the [`https://github.com/apexlang/apex`](apex) CLI. 78 | 79 | Run the following command to get started: 80 | 81 | ```sh 82 | $ apex new git@github.com:nanobus/iota.git -p templates/rust [your-project] 83 | ``` 84 | 85 | From there, edit the `apex.axdl` interface definition to match your needs and run `apex build` to generate the wasmRS module. 86 | 87 | ## More Information 88 | 89 | WasmRS makes heavy use of generated code from `apex` specs and generators to automate all of the boilerplate. See the [getting-started](https://github.com/WasmRS/docs/blob/main/wasmrs-rust-howto.md) for usage. 90 | 91 | For more information on wasmRS, see the core [wasmrs](https://github.com/wasmrs/wasmrs-rust/blob/main/crates/wasmrs/README.md) crate. 92 | 93 | ## Contributing 94 | 95 | See [CONTRIBUTING.md](https://github.com/WasmRS/wasmrs-rust/blob/main/CONTRIBUTING.md) 96 | 97 | ## License 98 | 99 | See the root [LICENSE.txt](https://github.com/WasmRS/wasmrs-rust/blob/main/LICENSE.txt) 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /crates/wasmrs-guest/src/error.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | /// The error variants used by wasmrs-guest. 3 | pub enum Error { 4 | /// No handler could be found for the passed index or namespace + operation. 5 | NoHandler, 6 | /// The handler failed. 7 | HandlerFail(String), 8 | /// Error reading frame buffer. 9 | BufferRead, 10 | /// Internal Error. 11 | Internal(String), 12 | /// Error decoding payload or metadata. 13 | Codec(String), 14 | /// Error in the asynchronous runtime. 15 | Runtime(String), 16 | /// Missing input in payload. 17 | MissingInput(String), 18 | } 19 | 20 | impl From for Error { 21 | fn from(e: wasmrs::Error) -> Self { 22 | Self::Internal(e.to_string()) 23 | } 24 | } 25 | 26 | impl From for Error { 27 | fn from(e: wasmrs_codec::error::Error) -> Self { 28 | Self::Codec(e.to_string()) 29 | } 30 | } 31 | 32 | impl From for Error { 33 | fn from(e: wasmrs_runtime::Error) -> Self { 34 | Self::Runtime(e.to_string()) 35 | } 36 | } 37 | 38 | impl std::error::Error for Error {} 39 | impl std::fmt::Display for Error { 40 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 41 | match self { 42 | Error::NoHandler => f.write_str("No handler found"), 43 | Error::HandlerFail(msg) => f.write_str(msg), 44 | Error::BufferRead => f.write_str("Error reading buffer"), 45 | Error::Internal(e) => f.write_str(&e.to_string()), 46 | Error::Codec(e) => f.write_str(e), 47 | Error::Runtime(e) => f.write_str(e), 48 | Error::MissingInput(e) => { 49 | let mut message = "Missing input: ".to_owned(); 50 | message.push_str(e); 51 | f.write_str(e) 52 | } 53 | } 54 | } 55 | } 56 | impl From for Error { 57 | fn from(_: std::io::Error) -> Self { 58 | Error::BufferRead 59 | } 60 | } 61 | 62 | impl From for Error { 63 | fn from(value: wasmrs_frames::Error) -> Self { 64 | Error::Internal(value.to_string()) 65 | } 66 | } 67 | 68 | impl From for Error { 69 | fn from(value: wasmrs_rx::Error) -> Self { 70 | Error::Internal(value.to_string()) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /crates/wasmrs-guest/src/exports.rs: -------------------------------------------------------------------------------- 1 | use crate::{op_list_request, send_frame}; 2 | 3 | #[allow(unsafe_code)] 4 | #[no_mangle] 5 | extern "C" fn __wasmrs_op_list_request() { 6 | op_list_request(); 7 | } 8 | 9 | #[allow(unsafe_code)] 10 | #[no_mangle] 11 | extern "C" fn __wasmrs_send(read_until: u32) { 12 | send_frame(read_until); 13 | } 14 | 15 | #[allow(unsafe_code)] 16 | #[no_mangle] 17 | extern "C" fn __wasmrs_v1() { 18 | /* no-op */ 19 | } 20 | -------------------------------------------------------------------------------- /crates/wasmrs-guest/src/imports.rs: -------------------------------------------------------------------------------- 1 | #[link(wasm_import_module = "wasmrs")] 2 | extern "C" { 3 | #[link_name = "__init_buffers"] 4 | pub(crate) fn _host_wasmrs_init(guest_buffer_ptr: usize, host_buffer_ptr: usize); 5 | #[link_name = "__send"] 6 | pub(crate) fn _host_wasmrs_send(size: usize); 7 | #[link_name = "__op_list"] 8 | pub(crate) fn _host_op_list(ptr: usize, len: usize); 9 | } 10 | -------------------------------------------------------------------------------- /crates/wasmrs-guest/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny( 2 | clippy::expect_used, 3 | clippy::explicit_deref_methods, 4 | clippy::option_if_let_else, 5 | clippy::await_holding_lock, 6 | clippy::cloned_instead_of_copied, 7 | clippy::explicit_into_iter_loop, 8 | clippy::flat_map_option, 9 | clippy::fn_params_excessive_bools, 10 | clippy::implicit_clone, 11 | clippy::inefficient_to_string, 12 | clippy::large_types_passed_by_value, 13 | clippy::manual_ok_or, 14 | clippy::map_flatten, 15 | clippy::map_unwrap_or, 16 | clippy::must_use_candidate, 17 | clippy::needless_for_each, 18 | clippy::needless_pass_by_value, 19 | clippy::option_option, 20 | clippy::redundant_else, 21 | clippy::semicolon_if_nothing_returned, 22 | clippy::too_many_lines, 23 | clippy::trivially_copy_pass_by_ref, 24 | clippy::unnested_or_patterns, 25 | clippy::future_not_send, 26 | clippy::useless_let_if_seq, 27 | clippy::str_to_string, 28 | clippy::inherent_to_string, 29 | clippy::let_and_return, 30 | clippy::string_to_string, 31 | clippy::try_err, 32 | clippy::unused_async, 33 | clippy::missing_enforced_import_renames, 34 | clippy::nonstandard_macro_braces, 35 | clippy::rc_mutex, 36 | clippy::unwrap_or_else_default, 37 | clippy::manual_split_once, 38 | clippy::derivable_impls, 39 | clippy::needless_option_as_deref, 40 | clippy::iter_not_returning_iterator, 41 | clippy::same_name_method, 42 | clippy::manual_assert, 43 | clippy::non_send_fields_in_send_ty, 44 | clippy::equatable_if_let, 45 | bad_style, 46 | clashing_extern_declarations, 47 | dead_code, 48 | deprecated, 49 | explicit_outlives_requirements, 50 | improper_ctypes, 51 | invalid_value, 52 | missing_copy_implementations, 53 | missing_debug_implementations, 54 | mutable_transmutes, 55 | no_mangle_generic_items, 56 | non_shorthand_field_patterns, 57 | overflowing_literals, 58 | path_statements, 59 | patterns_in_fns_without_body, 60 | private_in_public, 61 | trivial_bounds, 62 | trivial_casts, 63 | trivial_numeric_casts, 64 | type_alias_bounds, 65 | unconditional_recursion, 66 | unreachable_pub, 67 | unsafe_code, 68 | unstable_features, 69 | unused, 70 | unused_allocation, 71 | unused_comparisons, 72 | unused_import_braces, 73 | unused_parens, 74 | unused_qualifications, 75 | while_true, 76 | missing_docs 77 | )] 78 | #![doc = include_str!("../README.md")] 79 | 80 | mod guest; 81 | pub use error::Error; 82 | pub use futures_util::{FutureExt, Stream}; 83 | 84 | pub use guest::*; 85 | pub use wasmrs_runtime as runtime; 86 | pub use wasmrs_rx::{Flux, FluxChannel, FluxReceiver, Mono, Observable, Observer}; 87 | 88 | mod exports; 89 | mod imports; 90 | mod server; 91 | 92 | /// The wasmRS-guest error module. 93 | pub mod error; 94 | 95 | pub use serde_json::Value; 96 | pub use wasmrs::Payload; 97 | pub use wasmrs_rx::{BoxFlux, BoxMono}; 98 | 99 | /// Deserialize a generic [Value] from CBOR bytes. 100 | pub fn deserialize_generic(buf: &[u8]) -> Result, Error> { 101 | deserialize(buf).map_err(|e| Error::Codec(e.to_string())) 102 | } 103 | 104 | cfg_if::cfg_if!( 105 | if #[cfg(all(feature = "logging", target_os = "wasi"))] { 106 | /// Turn on logging for the guest (WASI only). 107 | pub fn init_logging() { 108 | env_logger::builder() 109 | .filter_level(log::LevelFilter::Trace) 110 | .parse_env("wasmrs") 111 | .init(); 112 | } 113 | } else { 114 | /// Turn on logging for the guest (WASI only). 115 | pub fn init_logging() {} 116 | } 117 | ); 118 | 119 | #[cfg(test)] 120 | mod test { 121 | 122 | use super::*; 123 | #[test] 124 | fn test_basic() { 125 | #[derive(serde::Deserialize, serde::Serialize, Debug, PartialEq)] 126 | struct Input { 127 | input: String, 128 | num: u32, 129 | } 130 | let input = Input { 131 | input: "HELLO WORLD".to_owned(), 132 | num: 32, 133 | }; 134 | let bytes = serialize(&input).unwrap(); 135 | let input2: Input = deserialize(&bytes).unwrap(); 136 | assert_eq!(input.input, input2.input); 137 | assert_eq!(input.num, input2.num); 138 | println!("{:?}", bytes); 139 | let map: Value = deserialize(&bytes).unwrap(); 140 | println!("{:?}", map); 141 | if let Value::Object(map) = map { 142 | assert_eq!(map.get("input"), Some(&Value::String("HELLO WORLD".to_owned()))); 143 | } else { 144 | panic!("expected map"); 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /crates/wasmrs-guest/src/server.rs: -------------------------------------------------------------------------------- 1 | use std::cell::UnsafeCell; 2 | 3 | use futures_util::{FutureExt, Stream, StreamExt}; 4 | use runtime::{ConditionallySend, RtRc}; 5 | use wasmrs::{BoxFlux, BoxMono, OperationMap, Payload, RSocket, RawPayload}; 6 | use wasmrs_frames::PayloadError; 7 | use wasmrs_runtime as runtime; 8 | use wasmrs_rx::{FluxChannel, Observer}; 9 | 10 | use crate::error::Error; 11 | 12 | #[allow(missing_debug_implementations, missing_copy_implementations)] 13 | #[derive(Clone)] 14 | pub(crate) struct WasmServer {} 15 | 16 | impl RSocket for WasmServer { 17 | fn fire_and_forget(&self, payload: RawPayload) -> BoxMono<(), PayloadError> { 18 | futures_util::future::ready(request_fnf(payload).map_err(|e| PayloadError::application_error(e.to_string(), None))) 19 | .boxed() 20 | } 21 | 22 | fn request_response(&self, payload: RawPayload) -> BoxMono { 23 | match request_response(payload) { 24 | Ok(v) => v, 25 | Err(e) => futures_util::future::ready(Err(PayloadError::application_error(e.to_string(), None))).boxed(), 26 | } 27 | } 28 | 29 | fn request_stream(&self, payload: RawPayload) -> BoxFlux { 30 | match request_stream(payload) { 31 | Ok(flux) => flux, 32 | Err(e) => futures_util::stream::iter([Err(PayloadError::application_error(e.to_string(), None))]).boxed(), 33 | } 34 | } 35 | 36 | fn request_channel> + ConditionallySend + Unpin + 'static>( 37 | &self, 38 | stream: T, 39 | ) -> BoxFlux { 40 | match request_channel(stream) { 41 | Ok(flux) => flux, 42 | Err(e) => futures_util::stream::iter([Err(PayloadError::application_error(e.to_string(), None))]).boxed(), 43 | } 44 | } 45 | } 46 | 47 | fn request_fnf(payload: RawPayload) -> Result<(), Error> { 48 | let parsed: Payload = payload.try_into()?; 49 | 50 | let handler = get_process_handler(&crate::guest::REQUEST_FNF_HANDLERS, parsed.metadata.index.unwrap() as _)?; 51 | 52 | handler(Box::pin(futures_util::future::ready(Ok(parsed)))).map_err(|e| Error::HandlerFail(e.to_string()))?; 53 | Ok(()) 54 | } 55 | 56 | fn request_response(payload: RawPayload) -> Result, Error> { 57 | let parsed: Payload = payload.try_into()?; 58 | 59 | let handler = get_process_handler( 60 | &crate::guest::REQUEST_RESPONSE_HANDLERS, 61 | parsed.metadata.index.unwrap() as _, 62 | )?; 63 | 64 | handler(Box::pin(futures_util::future::ready(Ok(parsed)))).map_err(|e| Error::HandlerFail(e.to_string())) 65 | } 66 | 67 | fn request_stream(payload: RawPayload) -> Result, Error> { 68 | let parsed: Payload = payload.try_into()?; 69 | let handler = get_process_handler( 70 | &crate::guest::REQUEST_STREAM_HANDLERS, 71 | parsed.metadata.index.unwrap() as _, 72 | )?; 73 | handler(futures_util::future::ready(Ok(parsed)).boxed()).map_err(|e| Error::HandlerFail(e.to_string())) 74 | } 75 | 76 | fn request_channel> + ConditionallySend + Unpin + 'static>( 77 | stream: T, 78 | ) -> Result, Error> { 79 | let (tx, rx) = FluxChannel::new_parts(); 80 | 81 | runtime::spawn("guest:server:request_channel", async move { 82 | match request_channel_inner(stream).await { 83 | Ok(mut res_stream) => { 84 | while let Some(r) = res_stream.next().await { 85 | let _ = tx.send_result(r); 86 | } 87 | } 88 | Err(e) => { 89 | let _ = tx.error(PayloadError::application_error(e.to_string(), None)); 90 | } 91 | } 92 | }); 93 | Ok(rx.boxed()) 94 | } 95 | 96 | async fn request_channel_inner< 97 | T: Stream> + ConditionallySend + Unpin + 'static, 98 | >( 99 | mut incoming_stream: T, 100 | ) -> Result, Error> { 101 | let (handler_input, handler_stream) = FluxChannel::new_parts(); 102 | let handler_out = if let Some(result) = incoming_stream.next().await { 103 | let payload = match result { 104 | Ok(v) => v, 105 | Err(e) => { 106 | return Ok(futures_util::stream::iter([Err(e)]).boxed()); 107 | } 108 | }; 109 | 110 | let parsed: Payload = payload.try_into()?; 111 | let handler = get_process_handler( 112 | &crate::guest::REQUEST_CHANNEL_HANDLERS, 113 | parsed.metadata.index.unwrap() as _, 114 | )?; 115 | 116 | handler_input.send(parsed).unwrap(); 117 | 118 | handler(handler_stream.boxed()).map_err(|e| Error::HandlerFail(e.to_string()))? 119 | } else { 120 | return Ok( 121 | futures_util::stream::iter([Err(PayloadError::application_error( 122 | "Can not initiate a channel with no payload", 123 | None, 124 | ))]) 125 | .boxed(), 126 | ); 127 | }; 128 | 129 | runtime::spawn("guest:server:request_channel_inner", async move { 130 | while let Some(next) = incoming_stream.next().await { 131 | let v = next.and_then(|v: RawPayload| { 132 | v.try_into() 133 | .map_err(|e: wasmrs::Error| PayloadError::application_error(e.to_string(), None)) 134 | }); 135 | let _ = handler_input.send_result(v); 136 | } 137 | }); 138 | Ok(handler_out) 139 | } 140 | 141 | fn get_process_handler( 142 | kind: &'static std::thread::LocalKey>>, 143 | index: usize, 144 | ) -> Result, Error> { 145 | kind.with(|cell| { 146 | #[allow(unsafe_code)] 147 | let buffer = unsafe { &*cell.get() }; 148 | buffer.get(index).map(|(_, _, op)| op.clone()).ok_or(Error::NoHandler) 149 | }) 150 | } 151 | -------------------------------------------------------------------------------- /crates/wasmrs-host/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasmrs-host" 3 | version = "0.17.0" 4 | edition = "2021" 5 | license = "Apache-2.0" 6 | description = "wasmRS host implementation for executing and interactin with wasmRS modules." 7 | repository = "https://github.com/wasmrs/wasmrs-rust" 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | [dependencies] 11 | wasmrs-frames = { path = "../wasmrs-frames", version = "0.17.1" } 12 | wasmrs-runtime = { path = "../wasmrs-runtime", version = "0.17.1" } 13 | wasmrs-rx = { path = "../wasmrs-rx", version = "0.17.0" } 14 | wasmrs = { path = "../wasmrs", version = "0.17.0" } 15 | parking_lot = { workspace = true } 16 | thiserror = { workspace = true } 17 | futures-util = { workspace = true } 18 | futures-core = { workspace = true } 19 | tokio = { workspace = true, features = ["sync", "rt"] } 20 | strum = { version = "0.24", features = ["derive"] } 21 | tracing = { workspace = true } 22 | bytes = { workspace = true } 23 | dashmap = "5.4" 24 | async-trait = { workspace = true } 25 | 26 | [dev-dependencies] 27 | tokio = { workspace = true, features = ["macros"] } 28 | -------------------------------------------------------------------------------- /crates/wasmrs-host/README.md: -------------------------------------------------------------------------------- 1 | # wasmrs-host 2 | 3 | This crate provides the host-side logic to run wasmRS modules. It delegates the WebAssembly interpreter implementation to engine providers like [wasmrs-wasmtime](https://github.com/wasmrs/wasmrs-rust/blob/main//crates/wasmrs-wasmtime). 4 | 5 | ## Engine providers 6 | 7 | - [wasmrs-wasmtime](https://github.com/wasmrs/wasmrs-rust/blob/main//crates/wasmrs-wasmtime) 8 | 9 | ## Usage 10 | 11 | For more information on using wasmRS-host, see examples in the [wasmrs-wasmtime](https://github.com/wasmrs/wasmrs-rust/blob/main/crates/wasmrs-wasmtime/README.md) crate. 12 | 13 | For more information on wasmRS, see the core [wasmrs](https://github.com/wasmrs/wasmrs-rust/blob/main/crates/wasmrs/README.md) crate. 14 | 15 | ## Contributing 16 | 17 | See [CONTRIBUTING.md](https://github.com/WasmRS/wasmrs-rust/blob/main/CONTRIBUTING.md) 18 | 19 | ## License 20 | 21 | See the root [LICENSE.txt](https://github.com/WasmRS/wasmrs-rust/blob/main/LICENSE.txt) 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /crates/wasmrs-host/src/context.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use bytes::Bytes; 4 | use wasmrs::{Frame, OperationList, WasmSocket}; 5 | 6 | use crate::host::HostServer; 7 | 8 | type Result = std::result::Result; 9 | 10 | #[derive(Clone)] 11 | #[allow(missing_debug_implementations)] 12 | #[allow(missing_docs)] 13 | pub struct SharedContext(Arc); 14 | 15 | impl SharedContext { 16 | /// Create a new shared context with the passed [ProviderCallContext] 17 | pub fn new(context: impl ProviderCallContext + Send + Sync + 'static) -> Self { 18 | Self(Arc::new(context)) 19 | } 20 | 21 | pub(crate) async fn init(&self, host_buffer_size: u32, guest_buffer_size: u32) -> Result<()> { 22 | self.0.init(host_buffer_size, guest_buffer_size).await 23 | } 24 | 25 | pub(crate) async fn write_frame(&self, frame: Frame) -> Result<()> { 26 | let id = frame.stream_id(); 27 | let result = self.0.write_frame(frame).await; 28 | 29 | if let Err(e) = &result { 30 | error!("failed to write frame for stream ID {}: {}", id, e); 31 | self.0.on_error(id).await?; 32 | } 33 | 34 | Ok(result?) 35 | } 36 | 37 | pub(crate) fn get_import(&self, namespace: &str, operation: &str) -> Option { 38 | self.0.get_import(namespace, operation) 39 | } 40 | 41 | pub(crate) fn get_export(&self, namespace: &str, operation: &str) -> Option { 42 | self.0.get_export(namespace, operation) 43 | } 44 | 45 | pub(crate) fn get_operation_list(&self) -> OperationList { 46 | self.0.get_operation_list() 47 | } 48 | } 49 | 50 | /// All engine providers must implement the [EngineProvider] trait. 51 | #[async_trait::async_trait] 52 | pub trait EngineProvider { 53 | /// Called to create a new [SharedContext]. 54 | async fn new_context(&self, state: Arc>) -> Result; 55 | } 56 | 57 | /// The trait implemented by a context for a call or set of calls. 58 | #[async_trait::async_trait] 59 | pub trait ProviderCallContext: wasmrs::ModuleHost { 60 | /// Initialize the call context. 61 | async fn init(&self, host_buffer_size: u32, guest_buffer_size: u32) -> Result<()>; 62 | } 63 | 64 | /// The trait that a host needs to implement to satisfy wasmrs protocol imports and to query data about the loaded module. 65 | pub trait CallbackProvider { 66 | /// The callback for `__wasmrs_send` 67 | fn do_host_send(&self, frame_bytes: Bytes) -> Result<()>; 68 | #[allow(missing_docs)] 69 | fn do_console_log(&self, msg: &str); 70 | /// Query the operation list for the module. 71 | fn do_op_list(&mut self, bytes: Bytes) -> Result<()>; 72 | /// The callback for `__wasmrs_init` 73 | fn do_host_init(&self, guest_buff_ptr: u32, host_buff_ptr: u32) -> Result<()>; 74 | } 75 | -------------------------------------------------------------------------------- /crates/wasmrs-host/src/errors.rs: -------------------------------------------------------------------------------- 1 | //! Library-specific error types and utility functions 2 | 3 | type BoxError = Box; 4 | 5 | /// Error type for wasmRS errors. 6 | #[derive(Debug, thiserror::Error)] 7 | pub enum Error { 8 | /// Initialization Failed. 9 | #[error(transparent)] 10 | InitFailed(BoxError), 11 | 12 | /// Creating a new context failed. 13 | #[error("Could not create new context: {0}")] 14 | NewContext(String), 15 | 16 | /// Sending a frame failed. 17 | #[error("Could not send frame: {0}")] 18 | SendFailed(String), 19 | 20 | /// Guest send response to a stream that doesn't exist. 21 | #[error(transparent)] 22 | RSocket(#[from] wasmrs::Error), 23 | 24 | /// Guest send response to a stream that doesn't exist. 25 | #[error(transparent)] 26 | PayloadError(#[from] wasmrs_frames::PayloadError), 27 | 28 | /// Querying Operation List failed. 29 | #[error("Failed to query or decode operation list")] 30 | OpList(String), 31 | 32 | /// Could not find specified operation. 33 | #[error("Could not find operation {0}::{1}")] 34 | OpMissing(String, String), 35 | } 36 | -------------------------------------------------------------------------------- /crates/wasmrs-host/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny( 2 | clippy::expect_used, 3 | clippy::explicit_deref_methods, 4 | clippy::option_if_let_else, 5 | clippy::await_holding_lock, 6 | clippy::cloned_instead_of_copied, 7 | clippy::explicit_into_iter_loop, 8 | clippy::flat_map_option, 9 | clippy::fn_params_excessive_bools, 10 | clippy::implicit_clone, 11 | clippy::inefficient_to_string, 12 | clippy::large_types_passed_by_value, 13 | clippy::manual_ok_or, 14 | clippy::map_flatten, 15 | clippy::map_unwrap_or, 16 | clippy::must_use_candidate, 17 | clippy::needless_for_each, 18 | clippy::needless_pass_by_value, 19 | clippy::option_option, 20 | clippy::redundant_else, 21 | clippy::semicolon_if_nothing_returned, 22 | clippy::too_many_lines, 23 | clippy::trivially_copy_pass_by_ref, 24 | clippy::unnested_or_patterns, 25 | clippy::future_not_send, 26 | clippy::useless_let_if_seq, 27 | clippy::str_to_string, 28 | clippy::inherent_to_string, 29 | clippy::let_and_return, 30 | clippy::string_to_string, 31 | clippy::try_err, 32 | clippy::unused_async, 33 | clippy::missing_enforced_import_renames, 34 | clippy::nonstandard_macro_braces, 35 | clippy::rc_mutex, 36 | clippy::unwrap_or_else_default, 37 | clippy::manual_split_once, 38 | clippy::derivable_impls, 39 | clippy::needless_option_as_deref, 40 | clippy::iter_not_returning_iterator, 41 | clippy::same_name_method, 42 | clippy::manual_assert, 43 | clippy::non_send_fields_in_send_ty, 44 | clippy::equatable_if_let, 45 | bad_style, 46 | clashing_extern_declarations, 47 | dead_code, 48 | deprecated, 49 | explicit_outlives_requirements, 50 | improper_ctypes, 51 | invalid_value, 52 | missing_copy_implementations, 53 | missing_debug_implementations, 54 | mutable_transmutes, 55 | no_mangle_generic_items, 56 | non_shorthand_field_patterns, 57 | overflowing_literals, 58 | path_statements, 59 | patterns_in_fns_without_body, 60 | private_in_public, 61 | trivial_bounds, 62 | trivial_casts, 63 | trivial_numeric_casts, 64 | type_alias_bounds, 65 | unconditional_recursion, 66 | unreachable_pub, 67 | unsafe_code, 68 | unstable_features, 69 | unused, 70 | unused_allocation, 71 | unused_comparisons, 72 | unused_import_braces, 73 | unused_parens, 74 | unused_qualifications, 75 | while_true, 76 | missing_docs 77 | )] 78 | #![doc = include_str!("../README.md")] 79 | #[macro_use] 80 | extern crate tracing; 81 | 82 | mod context; 83 | pub mod errors; 84 | 85 | pub use context::*; 86 | 87 | /// The host module name / namespace that guest modules must use for imports 88 | pub const HOST_NAMESPACE: &str = "wasmrs"; 89 | 90 | /// A list of the function names that are part of each wasmRS conversation 91 | pub mod protocol; 92 | 93 | mod host; 94 | mod wasi; 95 | 96 | pub use host::{CallContext, Host, HostServer}; 97 | pub use protocol::*; 98 | pub use wasi::WasiParams; 99 | -------------------------------------------------------------------------------- /crates/wasmrs-host/src/protocol.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use strum::EnumIter; 4 | pub use strum::IntoEnumIterator; 5 | 6 | /// Functions called by guest, exported by host 7 | pub mod host_exports { 8 | /// The wasmRS protocol function `__init_buffers` 9 | pub const INIT: &str = "__init_buffers"; 10 | /// The wasmRS protocol function `__send` 11 | pub const SEND: &str = "__send"; 12 | /// The wasmRS protocol function `__console_log` 13 | pub const LOG: &str = "__console_log"; 14 | /// The wasmRS protocol function `__op_list` 15 | pub const OP_LIST: &str = "__op_list"; 16 | } 17 | 18 | /// The exported host functions as an enum. 19 | #[derive(Debug, Copy, Clone, EnumIter)] 20 | #[allow(missing_docs)] 21 | pub enum HostExports { 22 | Init, 23 | Send, 24 | Log, 25 | OpList, 26 | } 27 | 28 | impl FromStr for HostExports { 29 | type Err = (); 30 | 31 | fn from_str(s: &str) -> Result { 32 | let result = match s { 33 | host_exports::INIT => Self::Init, 34 | host_exports::SEND => Self::Send, 35 | host_exports::LOG => Self::Log, 36 | host_exports::OP_LIST => Self::OpList, 37 | _ => return Err(()), 38 | }; 39 | Ok(result) 40 | } 41 | } 42 | 43 | impl AsRef for HostExports { 44 | fn as_ref(&self) -> &str { 45 | match self { 46 | Self::Init => host_exports::INIT, 47 | Self::Send => host_exports::SEND, 48 | Self::Log => host_exports::LOG, 49 | Self::OpList => host_exports::OP_LIST, 50 | } 51 | } 52 | } 53 | 54 | impl std::fmt::Display for HostExports { 55 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 56 | f.write_str(self.as_ref()) 57 | } 58 | } 59 | 60 | /// Functions called by host, exported by guest 61 | pub mod guest_exports { 62 | /// The wasmRS protocol function `__wasmrs_init` 63 | pub const INIT: &str = "__wasmrs_init"; 64 | 65 | /// The wasmRS protocol function `__wasmrs_send` 66 | pub const SEND: &str = "__wasmrs_send"; 67 | 68 | /// The wasmRS protocol function `__wasmrs_op_list_request` 69 | pub const OP_LIST_REQUEST: &str = "__wasmrs_op_list_request"; 70 | 71 | /// The wasmRS export that denotes wasmRS version 1 vs version 0. Version 1 aligns metadata with RSocket. 72 | pub const VERSION_1: &str = "__wasmrs_v1"; 73 | 74 | /// The wasmRS protocol function `_start` 75 | pub const TINYGO_START: &str = "_start"; 76 | 77 | /// Start functions to attempt to call - order is important 78 | pub const REQUIRED_STARTS: [&str; 2] = [TINYGO_START, INIT]; 79 | } 80 | 81 | /// The exported guest functions as an enum. 82 | #[derive(Debug, Copy, Clone, EnumIter)] 83 | #[allow(missing_docs)] 84 | pub enum GuestExports { 85 | Init, 86 | Start, 87 | OpListRequest, 88 | Send, 89 | Version1, 90 | } 91 | 92 | impl FromStr for GuestExports { 93 | type Err = (); 94 | 95 | fn from_str(s: &str) -> Result { 96 | let result = match s { 97 | guest_exports::INIT => Self::Init, 98 | guest_exports::TINYGO_START => Self::Start, 99 | guest_exports::OP_LIST_REQUEST => Self::OpListRequest, 100 | guest_exports::SEND => Self::Send, 101 | guest_exports::VERSION_1 => Self::Version1, 102 | _ => return Err(()), 103 | }; 104 | Ok(result) 105 | } 106 | } 107 | 108 | impl AsRef for GuestExports { 109 | fn as_ref(&self) -> &str { 110 | match self { 111 | GuestExports::Init => guest_exports::INIT, 112 | GuestExports::Start => guest_exports::TINYGO_START, 113 | GuestExports::Send => guest_exports::SEND, 114 | GuestExports::OpListRequest => guest_exports::OP_LIST_REQUEST, 115 | GuestExports::Version1 => guest_exports::VERSION_1, 116 | } 117 | } 118 | } 119 | 120 | impl std::fmt::Display for GuestExports { 121 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 122 | f.write_str(self.as_ref()) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /crates/wasmrs-host/src/wasi.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | /// Parameters defining the options for enabling WASI on a module (if applicable) 4 | #[derive(Debug, Default, Clone, Eq, PartialEq)] 5 | #[must_use] 6 | pub struct WasiParams { 7 | /// Command line arguments to expose to WASI. 8 | pub argv: Vec, 9 | /// A mapping of directories. 10 | pub map_dirs: Vec<(String, PathBuf)>, 11 | /// Environment variables and values to expose. 12 | pub env_vars: Vec<(String, String)>, 13 | /// Directories that WASI has access to. 14 | pub preopened_dirs: Vec, 15 | } 16 | 17 | impl WasiParams { 18 | /// Instantiate a new WasiParams struct. 19 | pub fn new( 20 | argv: Vec, 21 | map_dirs: Vec<(String, PathBuf)>, 22 | env_vars: Vec<(String, String)>, 23 | preopened_dirs: Vec, 24 | ) -> Self { 25 | WasiParams { 26 | argv, 27 | map_dirs, 28 | preopened_dirs, 29 | env_vars, 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /crates/wasmrs-runtime/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasmrs-runtime" 3 | version = "0.17.1" 4 | edition = "2021" 5 | license = "Apache-2.0" 6 | description = "Base host and client implementations of the wasmRS RSocket protocol." 7 | repository = "https://github.com/wasmrs/wasmrs-rust" 8 | 9 | [features] 10 | logging = [] 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | [dependencies] 14 | wasmrs-frames = { path = "../wasmrs-frames", version = "0.17.1" } 15 | futures = { workspace = true, default-features = false } 16 | bytes = { workspace = true, default-features = false } 17 | parking_lot = { workspace = true, default-features = false } 18 | pin-project-lite = "0.2" 19 | 20 | [target.'cfg(not(target_family = "wasm"))'.dependencies] 21 | tokio = { workspace = true, features = ["sync", "rt"] } 22 | dashmap = "5.4" 23 | tracing = "0.1" 24 | 25 | [target.'cfg(target_family = "wasm")'.dependencies] 26 | tokio = { workspace = true, default-features = false, features = ["sync"] } 27 | crossbeam-channel = { version = "0.5" } 28 | futures-executor = { workspace = true, default-features = false, features = [ 29 | "std", 30 | ] } 31 | futures-util = { workspace = true, default-features = false, features = [ 32 | "alloc", 33 | ] } 34 | 35 | [dev-dependencies] 36 | anyhow = { version = "1.0" } 37 | tokio = { workspace = true, features = ["rt", "time", "macros"] } 38 | -------------------------------------------------------------------------------- /crates/wasmrs-runtime/README.md: -------------------------------------------------------------------------------- 1 | # wasmrs-runtime 2 | 3 | wasmrs-runtime is a set of structs and functions that are normalized across multithreaded native tokio and single-threaded WebAssembly using *whatever the smallest, fastest, single threaded, async, WebAssembly-compatible runtime of the day is*. 4 | 5 | ## Notice! 6 | 7 | You're better off not relying on this crate. It's a crate that exists only for as long as it needs to. As WebAssembly matures and there are more standard solutions for the problems this crate solves, this crate will be deprecated. 8 | 9 | ## More Info 10 | 11 | For more information on wasmRS, see the core [wasmrs](https://github.com/wasmrs/wasmrs-rust/blob/main/crates/wasmrs/README.md) crate. 12 | 13 | WasmRS makes heavy use of generated code from `apex` specs and generators to automate all of the boilerplate. See the [getting-started](https://github.com/WasmRS/docs/blob/main/wasmrs-rust-howto.md) for usage. 14 | 15 | ## Contributing 16 | 17 | See [CONTRIBUTING.md](https://github.com/WasmRS/wasmrs-rust/blob/main/CONTRIBUTING.md) 18 | 19 | ## License 20 | 21 | See the root [LICENSE.txt](https://github.com/WasmRS/wasmrs-rust/blob/main/LICENSE.txt) 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /crates/wasmrs-runtime/src/error.rs: -------------------------------------------------------------------------------- 1 | //! Library-specific error types and utility functions 2 | 3 | /// Error type for wasmRS Runtime errors. 4 | #[allow(missing_copy_implementations)] 5 | #[derive(Debug, Clone)] 6 | pub enum Error { 7 | /// Sending on the channel failed. 8 | SendFailed(u8), 9 | /// Receiving from the channel failed. 10 | RecvFailed(u8), 11 | } 12 | 13 | impl std::error::Error for Error {} 14 | impl std::fmt::Display for Error { 15 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 16 | match self { 17 | Error::SendFailed(_) => f.write_str("Send failed"), 18 | Error::RecvFailed(_) => f.write_str("Receive failed"), 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /crates/wasmrs-runtime/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny( 2 | clippy::expect_used, 3 | clippy::explicit_deref_methods, 4 | clippy::option_if_let_else, 5 | clippy::await_holding_lock, 6 | clippy::cloned_instead_of_copied, 7 | clippy::explicit_into_iter_loop, 8 | clippy::flat_map_option, 9 | clippy::fn_params_excessive_bools, 10 | clippy::implicit_clone, 11 | clippy::inefficient_to_string, 12 | clippy::large_types_passed_by_value, 13 | clippy::manual_ok_or, 14 | clippy::map_flatten, 15 | clippy::map_unwrap_or, 16 | clippy::must_use_candidate, 17 | clippy::needless_for_each, 18 | clippy::needless_pass_by_value, 19 | clippy::option_option, 20 | clippy::redundant_else, 21 | clippy::semicolon_if_nothing_returned, 22 | clippy::too_many_lines, 23 | clippy::trivially_copy_pass_by_ref, 24 | clippy::unnested_or_patterns, 25 | clippy::future_not_send, 26 | clippy::useless_let_if_seq, 27 | clippy::str_to_string, 28 | clippy::inherent_to_string, 29 | clippy::let_and_return, 30 | clippy::string_to_string, 31 | clippy::try_err, 32 | clippy::unused_async, 33 | clippy::missing_enforced_import_renames, 34 | clippy::nonstandard_macro_braces, 35 | clippy::rc_mutex, 36 | clippy::unwrap_or_else_default, 37 | clippy::manual_split_once, 38 | clippy::derivable_impls, 39 | clippy::needless_option_as_deref, 40 | clippy::iter_not_returning_iterator, 41 | clippy::same_name_method, 42 | clippy::manual_assert, 43 | clippy::non_send_fields_in_send_ty, 44 | clippy::equatable_if_let, 45 | bad_style, 46 | clashing_extern_declarations, 47 | dead_code, 48 | deprecated, 49 | explicit_outlives_requirements, 50 | improper_ctypes, 51 | invalid_value, 52 | missing_copy_implementations, 53 | missing_debug_implementations, 54 | mutable_transmutes, 55 | no_mangle_generic_items, 56 | non_shorthand_field_patterns, 57 | overflowing_literals, 58 | path_statements, 59 | patterns_in_fns_without_body, 60 | private_in_public, 61 | trivial_bounds, 62 | trivial_casts, 63 | trivial_numeric_casts, 64 | type_alias_bounds, 65 | unconditional_recursion, 66 | unreachable_pub, 67 | unsafe_code, 68 | unstable_features, 69 | unused, 70 | unused_allocation, 71 | unused_comparisons, 72 | unused_import_braces, 73 | unused_parens, 74 | unused_qualifications, 75 | while_true, 76 | missing_docs 77 | )] 78 | #![doc = include_str!("../README.md")] 79 | mod error; 80 | mod runtime; 81 | 82 | pub use runtime::*; 83 | 84 | pub use error::Error; 85 | -------------------------------------------------------------------------------- /crates/wasmrs-runtime/src/runtime.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_family = "wasm")] 2 | mod wasm; 3 | use std::task::{Context, Poll}; 4 | 5 | use futures::{Future, FutureExt}; 6 | #[cfg(target_family = "wasm")] 7 | pub use wasm::*; 8 | 9 | #[cfg(not(target_family = "wasm"))] 10 | mod native; 11 | #[cfg(not(target_family = "wasm"))] 12 | pub use native::*; 13 | 14 | use crate::Error; 15 | 16 | #[must_use] 17 | /// Create an unbounded channel. 18 | pub fn unbounded_channel() -> (UnboundedSender, UnboundedReceiver) 19 | where 20 | Item: ConditionallySendSync, 21 | { 22 | let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); 23 | 24 | (UnboundedSender(tx), UnboundedReceiver(rx)) 25 | } 26 | 27 | #[allow(missing_debug_implementations)] 28 | /// A Unbounded Sender that works the same way in single-threaded WebAssembly as multi-threaded native. 29 | pub struct UnboundedSender(tokio::sync::mpsc::UnboundedSender) 30 | where 31 | Item: ConditionallySendSync; 32 | 33 | impl Clone for UnboundedSender 34 | where 35 | Item: ConditionallySendSync, 36 | { 37 | fn clone(&self) -> Self { 38 | Self(self.0.clone()) 39 | } 40 | } 41 | 42 | impl UnboundedSender 43 | where 44 | Item: ConditionallySendSync, 45 | { 46 | /// Send an `Item` to the channel. 47 | pub fn send(&self, message: Item) -> Result<(), Error> { 48 | self.0.send(message).map_err(|_| Error::SendFailed(0)) 49 | } 50 | 51 | #[must_use] 52 | /// Check if the channel is closed. 53 | pub fn is_closed(&self) -> bool { 54 | self.0.is_closed() 55 | } 56 | } 57 | 58 | #[allow(missing_debug_implementations)] 59 | /// A Unbounded Receiver that works the same way in single-threaded WebAssembly as multi-threaded native. 60 | pub struct UnboundedReceiver(tokio::sync::mpsc::UnboundedReceiver) 61 | where 62 | Item: ConditionallySendSync; 63 | 64 | impl UnboundedReceiver 65 | where 66 | Item: ConditionallySendSync, 67 | { 68 | /// Receive the next `Item` on the channel. 69 | pub async fn recv(&mut self) -> Option { 70 | self.0.recv().await 71 | } 72 | 73 | /// Poll the channel to see if an `Item` is ready. 74 | pub fn poll_recv(&mut self, cx: &mut Context) -> Poll> { 75 | self.0.poll_recv(cx) 76 | } 77 | } 78 | 79 | impl futures::Stream for UnboundedReceiver 80 | where 81 | T: ConditionallySendSync, 82 | { 83 | type Item = T; 84 | 85 | fn poll_next(mut self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 86 | self.0.poll_recv(cx) 87 | } 88 | } 89 | 90 | #[must_use] 91 | /// A oneshot channel similar to [tokio::sync::oneshot::channel] but works the same way in single-threaded WebAssembly as multi-threaded native. 92 | pub fn oneshot() -> (OneShotSender, OneShotReceiver) 93 | where 94 | Item: ConditionallySendSync, 95 | { 96 | let (tx, rx) = tokio::sync::oneshot::channel(); 97 | 98 | (OneShotSender(tx), OneShotReceiver(rx)) 99 | } 100 | 101 | #[allow(missing_debug_implementations)] 102 | /// A Unbounded Sender that works the same way in single-threaded WebAssembly as multi-threaded native. 103 | pub struct OneShotSender(tokio::sync::oneshot::Sender) 104 | where 105 | Item: ConditionallySendSync; 106 | 107 | impl OneShotSender 108 | where 109 | Item: ConditionallySendSync, 110 | { 111 | /// Send an item on the channel. 112 | pub fn send(self, message: Item) -> Result<(), Error> { 113 | self.0.send(message).map_err(|_| Error::SendFailed(0)) 114 | } 115 | 116 | #[must_use] 117 | /// Check if the channel is closed. 118 | pub fn is_closed(&self) -> bool { 119 | self.0.is_closed() 120 | } 121 | } 122 | 123 | #[allow(missing_debug_implementations)] 124 | /// A OneShort Receiver that works the same way in single-threaded WebAssembly as multi-threaded native. 125 | pub struct OneShotReceiver(tokio::sync::oneshot::Receiver) 126 | where 127 | Item: ConditionallySendSync; 128 | 129 | impl OneShotReceiver 130 | where 131 | Item: ConditionallySendSync, 132 | { 133 | /// Receive the next item on the channel. 134 | pub async fn recv(self) -> Result { 135 | self.0.await.map_err(|_e| Error::RecvFailed(80)) 136 | } 137 | } 138 | 139 | impl Future for OneShotReceiver 140 | where 141 | Item: ConditionallySendSync, 142 | { 143 | type Output = Result; 144 | 145 | fn poll(self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 146 | let poll = self.get_mut().0.poll_unpin(cx); 147 | poll.map_err(|_e| Error::RecvFailed(95)) 148 | } 149 | } 150 | 151 | impl std::fmt::Debug for MutRc 152 | where 153 | T: std::fmt::Debug, 154 | { 155 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 156 | f.debug_tuple("MutRc").field(&self.0).finish() 157 | } 158 | } 159 | 160 | impl Clone for MutRc { 161 | fn clone(&self) -> Self { 162 | Self(self.0.clone()) 163 | } 164 | } 165 | 166 | #[cfg(test)] 167 | mod test { 168 | use super::*; 169 | use anyhow::Result; 170 | 171 | #[test] 172 | fn test_rc() -> Result<()> { 173 | let one = RtRc::new("Hello World".to_owned()); 174 | let two = RtRc::new("Hello World".to_owned()); 175 | 176 | assert_eq!(one, two); 177 | Ok(()) 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /crates/wasmrs-runtime/src/runtime/native/mod.rs: -------------------------------------------------------------------------------- 1 | //! Native implementations of wasmrs-runtime functions and structs. 2 | #![allow(missing_docs)] 3 | use std::future::Future; 4 | use std::sync::Arc; 5 | 6 | use dashmap::DashMap; 7 | use parking_lot::Mutex; 8 | use tokio::task::JoinHandle; 9 | 10 | pub type TaskHandle = JoinHandle<()>; 11 | 12 | pub type BoxFuture = std::pin::Pin + Send + Sync + 'static>>; 13 | 14 | pub fn spawn(id: &'static str, task: F) -> TaskHandle 15 | where 16 | F: Future + ConditionallySend, 17 | { 18 | tracing::trace!("native:runtime:task:start:{}", id); 19 | tokio::spawn(async move { 20 | task.await; 21 | tracing::trace!("native:runtime:task:end:{}", id); 22 | }) 23 | } 24 | 25 | pub fn exhaust_pool() { 26 | unimplemented!("Not implemented in non-wasm hosts") 27 | } 28 | 29 | #[allow(missing_debug_implementations)] 30 | pub struct SafeMap(DashMap) 31 | where 32 | K: std::hash::Hash, 33 | K: Eq; 34 | 35 | impl SafeMap 36 | where 37 | K: std::hash::Hash, 38 | K: Eq, 39 | { 40 | pub fn remove(&self, key: &K) -> Option { 41 | self.0.remove(key).map(|v| v.1) 42 | } 43 | 44 | pub fn insert(&self, key: K, value: V) { 45 | self.0.insert(key, value); 46 | } 47 | 48 | #[must_use] 49 | pub fn len(&self) -> usize { 50 | self.0.len() 51 | } 52 | 53 | #[must_use] 54 | pub fn is_empty(&self) -> bool { 55 | self.0.is_empty() 56 | } 57 | 58 | pub fn cloned(&self, key: &K) -> Option 59 | where 60 | V: Clone, 61 | { 62 | self.0.get(key).map(|v| v.clone()) 63 | } 64 | 65 | pub fn entry(&self, key: K) -> Entry<'_, K, V> { 66 | match self.0.entry(key) { 67 | dashmap::mapref::entry::Entry::Occupied(v) => Entry::Occupied::(OccupiedEntry(v)), 68 | dashmap::mapref::entry::Entry::Vacant(v) => Entry::Vacant::(VacantEntry(v)), 69 | } 70 | } 71 | } 72 | 73 | #[must_use] 74 | #[allow(missing_debug_implementations)] 75 | pub enum Entry<'a, K, V> { 76 | Occupied(OccupiedEntry<'a, K, V>), 77 | Vacant(VacantEntry<'a, K, V>), 78 | } 79 | 80 | #[allow(missing_debug_implementations)] 81 | pub struct OccupiedEntry<'a, K, V>(dashmap::mapref::entry::OccupiedEntry<'a, K, V>); 82 | 83 | impl<'a, K, V> OccupiedEntry<'a, K, V> 84 | where 85 | K: Eq, 86 | K: std::hash::Hash, 87 | { 88 | pub fn get(&self) -> &V { 89 | self.0.get() 90 | } 91 | pub fn remove(self) -> V { 92 | self.0.remove() 93 | } 94 | } 95 | 96 | #[allow(missing_debug_implementations)] 97 | pub struct VacantEntry<'a, K, V>(dashmap::mapref::entry::VacantEntry<'a, K, V>); 98 | 99 | impl Default for SafeMap 100 | where 101 | K: std::hash::Hash, 102 | K: Eq, 103 | { 104 | fn default() -> Self { 105 | Self(Default::default()) 106 | } 107 | } 108 | 109 | #[allow(missing_debug_implementations)] 110 | pub struct OptionalMut(Arc>>); 111 | 112 | impl OptionalMut 113 | where 114 | T: Send, 115 | { 116 | pub fn new(item: T) -> Self { 117 | Self(Arc::new(Mutex::new(Some(item)))) 118 | } 119 | 120 | #[must_use] 121 | pub fn none() -> Self { 122 | Self(Arc::new(Mutex::new(None))) 123 | } 124 | 125 | #[must_use] 126 | pub fn take(&self) -> Option { 127 | self.0.lock().take() 128 | } 129 | 130 | pub fn insert(&self, item: T) { 131 | let _ = self.0.lock().insert(item); 132 | } 133 | 134 | #[must_use] 135 | pub fn is_some(&self) -> bool { 136 | self.0.lock().is_some() 137 | } 138 | 139 | #[must_use] 140 | pub fn is_none(&self) -> bool { 141 | self.0.lock().is_none() 142 | } 143 | } 144 | 145 | impl Clone for OptionalMut { 146 | fn clone(&self) -> Self { 147 | Self(self.0.clone()) 148 | } 149 | } 150 | 151 | #[allow(missing_debug_implementations)] 152 | pub struct MutRc(pub(super) Arc>); 153 | 154 | impl MutRc 155 | where 156 | T: ConditionallySendSync, 157 | { 158 | pub fn new(item: T) -> Self { 159 | Self(Arc::new(Mutex::new(item))) 160 | } 161 | 162 | pub fn lock(&self) -> parking_lot::lock_api::MutexGuard<'_, parking_lot::RawMutex, T> { 163 | self.0.lock() 164 | } 165 | } 166 | 167 | pub type RtRc = Arc; 168 | 169 | pub trait ConditionallySendSync: Send + Sync + 'static {} 170 | 171 | impl ConditionallySendSync for S where S: Send + Sync + 'static {} 172 | 173 | pub trait ConditionallySend: Send + 'static {} 174 | 175 | impl ConditionallySend for S where S: Send + 'static {} 176 | -------------------------------------------------------------------------------- /crates/wasmrs-runtime/src/runtime/wasm/mod.rs: -------------------------------------------------------------------------------- 1 | //! WebAssembly implementations of wasmrs-runtime functions and structs. 2 | #![allow(missing_docs)] 3 | 4 | use std::cell::{RefCell, UnsafeCell}; 5 | use std::rc::Rc; 6 | use std::sync::Arc; 7 | 8 | use futures_util::task::LocalSpawnExt; 9 | use futures_util::Future; 10 | pub type TaskHandle = (); 11 | 12 | pub type BoxFuture = std::pin::Pin + 'static>>; 13 | 14 | thread_local! { 15 | static LOCAL_POOL: UnsafeCell = UnsafeCell::new(futures_executor::LocalPool::new()); 16 | static SPAWNER: UnsafeCell> = UnsafeCell::new(None); 17 | static IS_RUNNING: AtomicBool = AtomicBool::new(false); 18 | } 19 | 20 | pub fn spawn(_id: &'static str, future: Fut) 21 | where 22 | Fut: Future + ConditionallySend + 'static, 23 | { 24 | SPAWNER.with(|spawner| { 25 | #[allow(unsafe_code)] 26 | let spawner = unsafe { &mut *spawner.get() }; 27 | #[cfg(feature = "logging")] 28 | println!("wasm:runtime:spawn:start:{}", _id); 29 | let future = Box::pin(async move { 30 | future.await; 31 | #[cfg(feature = "logging")] 32 | println!("wasm:runtime:spawn:end:{}", _id) 33 | }); 34 | match spawner { 35 | Some(spawner) => spawner 36 | .spawn_local(future) 37 | .expect("Could not spawn process in WASM runtime."), 38 | None => { 39 | LOCAL_POOL.with(|pool| { 40 | #[allow(unsafe_code)] 41 | let pool = unsafe { &mut *pool.get() }; 42 | let s = pool.spawner(); 43 | s.spawn_local(future).expect("Could not spawn process in WASM runtime."); 44 | spawner.replace(s) 45 | }); 46 | } 47 | } 48 | }); 49 | } 50 | 51 | #[allow(missing_copy_implementations, missing_debug_implementations)] 52 | pub struct PendingOnce { 53 | is_ready: bool, 54 | } 55 | 56 | impl Future for PendingOnce { 57 | type Output = (); 58 | fn poll(mut self: std::pin::Pin<&mut Self>, ctx: &mut std::task::Context<'_>) -> std::task::Poll { 59 | if self.is_ready { 60 | std::task::Poll::Ready(()) 61 | } else { 62 | self.is_ready = true; 63 | ctx.waker().wake_by_ref(); 64 | std::task::Poll::Pending 65 | } 66 | } 67 | } 68 | 69 | pub async fn yield_now() { 70 | PendingOnce { is_ready: false }.await 71 | } 72 | 73 | fn is_running() -> bool { 74 | IS_RUNNING.with(|cell| cell.load(std::sync::atomic::Ordering::SeqCst)) 75 | } 76 | 77 | fn running_state(state: bool) { 78 | IS_RUNNING.with(|cell| cell.store(state, std::sync::atomic::Ordering::SeqCst)); 79 | } 80 | 81 | pub fn exhaust_pool() { 82 | if !is_running() { 83 | running_state(true); 84 | LOCAL_POOL.with(|cell| { 85 | #[allow(unsafe_code)] 86 | let pool = unsafe { &mut *cell.get() }; 87 | pool.run_until_stalled(); 88 | }); 89 | running_state(false); 90 | } 91 | } 92 | 93 | use std::collections::HashMap; 94 | use std::sync::atomic::AtomicBool; 95 | 96 | #[allow(missing_debug_implementations)] 97 | pub struct SafeMap(UnsafeCell>) 98 | where 99 | K: std::hash::Hash, 100 | K: Eq; 101 | 102 | impl SafeMap 103 | where 104 | K: std::hash::Hash, 105 | K: Eq, 106 | { 107 | pub fn remove(&self, key: &K) -> Option { 108 | #[allow(unsafe_code)] 109 | unsafe { &mut *self.0.get() }.remove(key) 110 | } 111 | pub fn insert(&self, key: K, value: V) { 112 | #[allow(unsafe_code)] 113 | unsafe { &mut *self.0.get() }.insert(key, value); 114 | } 115 | #[must_use] 116 | pub fn len(&self) -> usize { 117 | #[allow(unsafe_code)] 118 | unsafe { &mut *self.0.get() }.len() 119 | } 120 | #[must_use] 121 | pub fn is_empty(&self) -> bool { 122 | #[allow(unsafe_code)] 123 | unsafe { &mut *self.0.get() }.is_empty() 124 | } 125 | 126 | pub fn cloned(&self, key: &K) -> Option 127 | where 128 | V: Clone, 129 | { 130 | #[allow(unsafe_code)] 131 | unsafe { &mut *self.0.get() }.get(key).map(|v| v.clone()) 132 | } 133 | 134 | pub fn entry<'a>(&'a self, key: K) -> Entry<'a, K, V> { 135 | #[allow(unsafe_code)] 136 | let map = unsafe { &mut *self.0.get() }; 137 | let entry = map.entry(key); 138 | let val = match entry { 139 | std::collections::hash_map::Entry::Occupied(v) => Entry::Occupied(OccupiedEntry(v)), 140 | std::collections::hash_map::Entry::Vacant(v) => Entry::Vacant(VacantEntry(v)), 141 | }; 142 | val 143 | } 144 | } 145 | 146 | #[must_use] 147 | #[allow(missing_debug_implementations)] 148 | pub enum Entry<'a, K, V> { 149 | Occupied(OccupiedEntry<'a, K, V>), 150 | Vacant(VacantEntry<'a, K, V>), 151 | } 152 | 153 | #[allow(missing_debug_implementations)] 154 | pub struct OccupiedEntry<'a, K, V>(std::collections::hash_map::OccupiedEntry<'a, K, V>); 155 | 156 | impl<'a, K, V> OccupiedEntry<'a, K, V> 157 | where 158 | K: Eq, 159 | K: std::hash::Hash, 160 | { 161 | pub fn get(&self) -> &V { 162 | self.0.get() 163 | } 164 | pub fn remove(self) -> V { 165 | self.0.remove() 166 | } 167 | } 168 | 169 | #[allow(missing_debug_implementations)] 170 | pub struct VacantEntry<'a, K, V>(std::collections::hash_map::VacantEntry<'a, K, V>); 171 | 172 | impl Default for SafeMap 173 | where 174 | K: std::hash::Hash, 175 | K: Eq, 176 | { 177 | fn default() -> Self { 178 | Self(Default::default()) 179 | } 180 | } 181 | 182 | #[allow(missing_debug_implementations)] 183 | pub struct OptionalMut(Arc>>); 184 | 185 | impl OptionalMut 186 | where 187 | T: ConditionallySendSync, 188 | { 189 | pub fn new(item: T) -> Self { 190 | Self(Arc::new(RefCell::new(Some(item)))) 191 | } 192 | 193 | pub fn none() -> Self { 194 | Self(Arc::new(RefCell::new(None))) 195 | } 196 | 197 | pub fn take(&self) -> Option { 198 | self.0.borrow_mut().take() 199 | } 200 | 201 | pub fn insert(&self, item: T) { 202 | let _ = self.0.borrow_mut().insert(item); 203 | } 204 | 205 | #[must_use] 206 | pub fn is_some(&self) -> bool { 207 | self.0.borrow().is_some() 208 | } 209 | 210 | #[must_use] 211 | pub fn is_none(&self) -> bool { 212 | self.0.borrow().is_none() 213 | } 214 | } 215 | impl Clone for OptionalMut { 216 | fn clone(&self) -> Self { 217 | Self(self.0.clone()) 218 | } 219 | } 220 | 221 | #[allow(missing_debug_implementations)] 222 | pub struct MutRc(pub(super) Rc>); 223 | 224 | impl MutRc 225 | where 226 | T: ConditionallySendSync, 227 | { 228 | pub fn new(item: T) -> Self { 229 | Self(Rc::new(RefCell::new(item))) 230 | } 231 | 232 | pub fn lock(&self) -> std::cell::RefMut { 233 | self.0.borrow_mut() 234 | } 235 | } 236 | impl PartialEq for MutRc 237 | where 238 | T: PartialEq, 239 | { 240 | fn eq(&self, other: &Self) -> bool { 241 | self.0.eq(&other.0) 242 | } 243 | } 244 | 245 | pub type RtRc = Rc; 246 | 247 | pub trait ConditionallySendSync: 'static {} 248 | 249 | impl ConditionallySendSync for S where S: 'static {} 250 | 251 | pub trait ConditionallySend: 'static {} 252 | 253 | impl ConditionallySend for S where S: 'static {} 254 | -------------------------------------------------------------------------------- /crates/wasmrs-rx/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasmrs-rx" 3 | version = "0.17.0" 4 | edition = "2021" 5 | license = "Apache-2.0" 6 | description = "Base host and client implementations of the wasmRS RSocket protocol." 7 | repository = "https://github.com/wasmrs/wasmrs-rust" 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | [dependencies] 11 | wasmrs-runtime = { path = "../wasmrs-runtime", version = "0.17.1" } 12 | futures = { workspace = true, default-features = false, features = [ 13 | "io-compat" 14 | ] } 15 | bytes = { workspace = true, default-features = false } 16 | parking_lot = { workspace = true, default-features = false } 17 | tracing = { workspace = true } 18 | pin-project-lite = "0.2" 19 | async-trait = { workspace = true } 20 | 21 | [target.'cfg(not(target_family = "wasm"))'.dependencies] 22 | dashmap = "5.4" 23 | 24 | [target.'cfg(target_family = "wasm")'.dependencies] 25 | crossbeam-channel = { version = "0.5" } 26 | futures-executor = { workspace = true, default-features = false, features = [ 27 | "std", 28 | ] } 29 | futures-util = { workspace = true, default-features = false, features = [ 30 | "alloc", 31 | ] } 32 | 33 | [dev-dependencies] 34 | anyhow = { version = "1.0" } 35 | tokio = { workspace = true, features = [ 36 | "rt", 37 | "rt-multi-thread", 38 | "time", 39 | "macros" 40 | ] } 41 | -------------------------------------------------------------------------------- /crates/wasmrs-rx/README.md: -------------------------------------------------------------------------------- 1 | # wasmrs-rx 2 | 3 | WasmRS-RX is a simple implementation of rx-like functionality for Rust tailored towards use in wasmrs, the WebAssembly RSocket implementation. 4 | 5 | ## Note 6 | 7 | RX & Reactive Streams revolve around concepts of Observables. This project chooses to retain Flux/Mono terminology to keep it in line with other RSocket implementations. 8 | 9 | ## Usage 10 | 11 | A `Mono` is a single value while a `Flux` is any number of values. They are analogous to Futures and Streams, respectively. In this implementation, each value is either a success or a failure which makes wasmrs-rx's `Mono` and `Flux` feel like an asynchronous `Result` or a stream of `Result`s. 12 | 13 | A `Mono` can be instantiated with a single success or failure value as so: 14 | 15 | ```rs 16 | let mono = Mono::<_, Error>::new_success(100); 17 | 18 | let result = mono.await?; 19 | 20 | println!("{}", result); 21 | ``` 22 | 23 | It can also be created from a future: 24 | 25 | ```rs 26 | let mono = Mono::<_, Error>::from_future(async move { Ok(101) }); 27 | 28 | let result = mono.await?; 29 | 30 | println!("{}", result); 31 | ``` 32 | 33 | Or a `Mono` can be created and completed later: 34 | 35 | ```rs 36 | let mut mono = Mono::::new(); 37 | 38 | mono.success(100); 39 | 40 | let result = mono.await?; 41 | 42 | println!("{}", result); 43 | ``` 44 | 45 | ## Flux 46 | 47 | A `Flux` is a stream/channel wrapped up together. You can push to it, complete it, and await it: 48 | 49 | ```rs 50 | let mut flux = FluxChannel::<_, Error>::new(); 51 | 52 | flux.send(100)?; 53 | flux.send(101)?; 54 | flux.send(102)?; 55 | flux.complete(); 56 | 57 | while let Some(payload) = flux.next().await { 58 | println!("{}", payload?); 59 | } 60 | ``` 61 | 62 | You can take the receiver portion and split the send/receive as you would other channels: 63 | 64 | ```rs 65 | let flux = FluxChannel::<_, Error>::new(); 66 | let mut rx = flux.take_rx()?; 67 | 68 | let task = tokio::spawn(async move { 69 | sleep(Duration::from_millis(500)).await; 70 | flux.send(100).unwrap(); 71 | flux.send(101).unwrap(); 72 | flux.send(102).unwrap(); 73 | flux.complete() 74 | }); 75 | 76 | while let Some(payload) = rx.next().await { 77 | println!("{}", payload?); 78 | } 79 | task.await?; 80 | ``` 81 | 82 | Since `Flux`es embed the concept of a `Result`, `.send()` pushes `Ok` values and `.error()` can be used to push error values. 83 | 84 | ```rs 85 | let mut flux = FluxChannel::<_, Error>::new(); 86 | 87 | flux.send(100)?; 88 | flux.send(101)?; 89 | flux.send(102)?; 90 | flux.error(anyhow::anyhow!("error"))?; 91 | flux.complete(); 92 | 93 | while let Some(payload) = flux.next().await { 94 | println!("{:?}", payload); 95 | } 96 | ``` 97 | 98 | ## More Info 99 | 100 | For more information on wasmRS, see the core [wasmrs](https://github.com/wasmrs/wasmrs-rust/blob/main/crates/wasmrs/README.md) crate. 101 | 102 | WasmRS makes heavy use of generated code from `apex` specs and generators to automate all of the boilerplate. See the [getting-started](https://github.com/WasmRS/docs/blob/main/wasmrs-rust-howto.md) for usage. 103 | 104 | ## Contributing 105 | 106 | See [CONTRIBUTING.md](https://github.com/WasmRS/wasmrs-rust/blob/main/CONTRIBUTING.md) 107 | 108 | ## License 109 | 110 | See the root [LICENSE.txt](https://github.com/WasmRS/wasmrs-rust/blob/main/LICENSE.txt) 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /crates/wasmrs-rx/examples/rx.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use futures::StreamExt; 4 | use tokio::time::sleep; 5 | use wasmrs_rx::*; 6 | 7 | use anyhow::{Error, Result}; 8 | 9 | #[tokio::main] 10 | async fn main() -> Result<()> { 11 | basic_mono().await?; 12 | 13 | mono_future().await?; 14 | 15 | mono_later().await?; 16 | 17 | basic_flux().await?; 18 | 19 | flux_channels().await?; 20 | 21 | errors().await?; 22 | 23 | Ok(()) 24 | } 25 | 26 | async fn basic_mono() -> Result<()> { 27 | let mono = Mono::<_, Error>::new_success(100); 28 | 29 | let result = mono.await?; 30 | 31 | println!("{}", result); 32 | 33 | Ok(()) 34 | } 35 | 36 | async fn mono_future() -> Result<()> { 37 | let mono = Mono::<_, Error>::from_future(async move { Ok(101) }); 38 | 39 | let result = mono.await?; 40 | 41 | println!("{}", result); 42 | 43 | Ok(()) 44 | } 45 | 46 | async fn mono_later() -> Result<()> { 47 | let mut mono = Mono::::new(); 48 | 49 | mono.success(100); 50 | 51 | let result = mono.await?; 52 | 53 | println!("{}", result); 54 | 55 | Ok(()) 56 | } 57 | 58 | async fn basic_flux() -> Result<()> { 59 | let mut flux = FluxChannel::<_, Error>::new(); 60 | 61 | flux.send(100)?; 62 | flux.send(101)?; 63 | flux.send(102)?; 64 | flux.complete(); 65 | 66 | while let Some(payload) = flux.next().await { 67 | println!("{}", payload?); 68 | } 69 | 70 | Ok(()) 71 | } 72 | 73 | async fn flux_channels() -> Result<()> { 74 | let flux = FluxChannel::<_, Error>::new(); 75 | let mut rx = flux.take_rx()?; 76 | 77 | let task = tokio::spawn(async move { 78 | sleep(Duration::from_millis(500)).await; 79 | flux.send(100).unwrap(); 80 | flux.send(101).unwrap(); 81 | flux.send(102).unwrap(); 82 | flux.complete() 83 | }); 84 | 85 | while let Some(payload) = rx.next().await { 86 | println!("{}", payload?); 87 | } 88 | task.await?; 89 | 90 | Ok(()) 91 | } 92 | 93 | async fn errors() -> Result<()> { 94 | let mut flux = FluxChannel::<_, Error>::new(); 95 | 96 | flux.send(100)?; 97 | flux.send(101)?; 98 | flux.send(102)?; 99 | flux.error(anyhow::anyhow!("error"))?; 100 | flux.complete(); 101 | 102 | while let Some(payload) = flux.next().await { 103 | println!("{:?}", payload); 104 | } 105 | Ok(()) 106 | } 107 | -------------------------------------------------------------------------------- /crates/wasmrs-rx/src/error.rs: -------------------------------------------------------------------------------- 1 | //! Library-specific error types and utility functions 2 | 3 | /// Error type for wasmRS-rx errors. 4 | #[allow(missing_copy_implementations)] 5 | #[derive(Debug, Clone)] 6 | pub enum Error { 7 | /// Receive on a channel failed. 8 | RecvFailed(u8), 9 | /// The sender in a [FluxChannel] has already been removed or dropped. 10 | SenderClosed, 11 | /// The receiver in a [FluxChannel] has already been removed. 12 | ReceiverAlreadyGone, 13 | /// A Runtime-related error. 14 | Runtime(String), 15 | /// Error decoding a payload. 16 | Decode(String), 17 | } 18 | 19 | impl std::error::Error for Error {} 20 | impl std::fmt::Display for Error { 21 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 22 | match self { 23 | Error::RecvFailed(_) => f.write_str("Receive failed"), 24 | Error::ReceiverAlreadyGone => f.write_str("Received already taken"), 25 | Error::Decode(e) => { 26 | let mut message = "Decode error: ".to_owned(); 27 | message.push_str(e); 28 | f.write_str(e) 29 | } 30 | Error::Runtime(msg) => f.write_str(msg), 31 | Error::SenderClosed => f.write_str("Sender closed"), 32 | } 33 | } 34 | } 35 | 36 | impl From for Error { 37 | fn from(e: wasmrs_runtime::Error) -> Self { 38 | Self::Runtime(e.to_string()) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /crates/wasmrs-rx/src/flux/observable.rs: -------------------------------------------------------------------------------- 1 | use futures::Stream; 2 | 3 | use super::{FluxChannel, FluxPipe}; 4 | use wasmrs_runtime::ConditionallySendSync; 5 | 6 | /// The wasmrs-rx implementation of an Rx Observable trait 7 | pub trait Observable: Stream> + ConditionallySendSync 8 | where 9 | Item: ConditionallySendSync, 10 | Err: ConditionallySendSync, 11 | Self: Sized, 12 | { 13 | /// Pipe one [Flux] into another. 14 | fn pipe(self, into: FluxChannel) -> FluxPipe { 15 | FluxPipe::new(self, into) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /crates/wasmrs-rx/src/flux/observer.rs: -------------------------------------------------------------------------------- 1 | use wasmrs_runtime::ConditionallySendSync; 2 | 3 | use super::Signal; 4 | use crate::Error; 5 | 6 | /// The wasmrs-rx implementation of an Rx Observer trait 7 | pub trait Observer 8 | where 9 | Item: ConditionallySendSync, 10 | Err: ConditionallySendSync, 11 | { 12 | /// Send a complete [Signal] 13 | fn send_signal(&self, signal: Signal) -> Result<(), Error>; 14 | 15 | /// Send a [Result] and have it map to an appropriate [Signal] variant. 16 | fn send_result(&self, result: Result) -> Result<(), Error> { 17 | self.send_signal(match result { 18 | Ok(ok) => Signal::Ok(ok), 19 | Err(err) => Signal::Err(err), 20 | }) 21 | } 22 | 23 | /// Send a successful value. 24 | fn send(&self, item: Item) -> Result<(), Error> { 25 | self.send_signal(Signal::Ok(item)) 26 | } 27 | 28 | /// Send an error value. 29 | fn error(&self, err: Err) -> Result<(), Error> { 30 | self.send_signal(Signal::Err(err)) 31 | } 32 | 33 | /// Mark the [Observer] as complete. 34 | fn complete(&self) { 35 | let _ = self.send_signal(Signal::Complete); 36 | } 37 | 38 | /// Returns true if the observer has been closed. 39 | fn is_complete(&self) -> bool; 40 | } 41 | 42 | impl Observer for F 43 | where 44 | Item: ConditionallySendSync, 45 | Err: ConditionallySendSync, 46 | F: Fn(Signal) -> Result<(), Error>, 47 | { 48 | fn send_signal(&self, signal: Signal) -> Result<(), Error> { 49 | self(signal) 50 | } 51 | 52 | fn is_complete(&self) -> bool { 53 | false 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /crates/wasmrs-rx/src/flux/ops.rs: -------------------------------------------------------------------------------- 1 | mod pipe; 2 | pub use pipe::*; 3 | -------------------------------------------------------------------------------- /crates/wasmrs-rx/src/flux/ops/pipe.rs: -------------------------------------------------------------------------------- 1 | use std::pin::Pin; 2 | use std::task::Poll; 3 | 4 | use futures::{Stream, TryStreamExt}; 5 | use pin_project_lite::pin_project; 6 | 7 | use crate::flux::FluxChannel; 8 | use wasmrs_runtime::ConditionallySendSync; 9 | 10 | pin_project! { 11 | /// A [FluxPipe] is the result of piping one [Flux] into another. 12 | pub struct FluxPipe 13 | where 14 | Item: ConditionallySendSync, 15 | Err: ConditionallySendSync, 16 | { 17 | #[pin] 18 | from: From, 19 | to: FluxChannel, 20 | } 21 | } 22 | 23 | impl FluxPipe 24 | where 25 | Item: ConditionallySendSync, 26 | Err: ConditionallySendSync, 27 | { 28 | /// Create a new [FluxPipe] 29 | pub fn new(from: From, to: FluxChannel) -> Self { 30 | Self { from, to } 31 | } 32 | } 33 | 34 | impl FluxPipe 35 | where 36 | Item: ConditionallySendSync, 37 | Err: ConditionallySendSync, 38 | From: Stream>, 39 | { 40 | } 41 | 42 | impl Stream for FluxPipe 43 | where 44 | Item: ConditionallySendSync, 45 | Err: ConditionallySendSync, 46 | From: Stream> + Unpin, 47 | { 48 | type Item = Result; 49 | 50 | fn poll_next(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll> { 51 | self.get_mut().from.try_poll_next_unpin(cx) 52 | } 53 | } 54 | #[cfg(all(test, not(target_family = "wasm")))] 55 | mod test { 56 | 57 | use anyhow::Result; 58 | use futures::StreamExt; 59 | 60 | use super::*; 61 | use crate::flux::Observer; 62 | use crate::Observable; 63 | 64 | #[tokio::test] 65 | async fn test_pipes() -> Result<()> { 66 | let (flux, observer) = FluxChannel::new_parts(); 67 | 68 | flux.send("First".to_owned())?; 69 | 70 | let second_flux = FluxChannel::::new(); 71 | 72 | let mut pipe = observer.pipe(second_flux); 73 | 74 | let value = pipe.next().await; 75 | assert_eq!(value, Some(Ok("First".to_owned()))); 76 | Ok(()) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /crates/wasmrs-rx/src/flux/receiver.rs: -------------------------------------------------------------------------------- 1 | use std::task::Poll; 2 | use std::{io::Write, pin::Pin}; 3 | 4 | use futures::Stream; 5 | 6 | use super::{signal_into_result, FutureResult, Signal}; 7 | use crate::{Error, FluxChannel, Observable, Observer}; 8 | use wasmrs_runtime::{ConditionallySendSync, OptionalMut, UnboundedReceiver}; 9 | 10 | #[must_use] 11 | #[allow(missing_debug_implementations)] 12 | /// The receving end-only of a [crate::Flux] 13 | pub struct FluxReceiver 14 | where 15 | Item: ConditionallySendSync, 16 | Err: ConditionallySendSync, 17 | { 18 | rx: OptionalMut>>, 19 | } 20 | 21 | impl FluxReceiver 22 | where 23 | Item: ConditionallySendSync, 24 | Err: ConditionallySendSync, 25 | { 26 | /// Create a new [FluxReceiver]. 27 | pub fn new(rx: UnboundedReceiver>) -> Self { 28 | Self { 29 | rx: OptionalMut::new(rx), 30 | } 31 | } 32 | 33 | /// Create a [Pin>] from a [FluxReceiver]. 34 | #[must_use] 35 | pub fn boxed(self) -> Pin> { 36 | Box::pin(self) 37 | } 38 | 39 | /// Create a new [FluxReceiver] that is immediately closed. 40 | pub fn none() -> Self { 41 | Self { 42 | rx: OptionalMut::none(), 43 | } 44 | } 45 | 46 | /// Create a new [FluxReceiver] that is immediately closed with the passed item. 47 | pub fn one(item: Result) -> FluxReceiver 48 | where 49 | I: ConditionallySendSync, 50 | E: ConditionallySendSync, 51 | { 52 | let (tx, rx) = FluxChannel::new_parts(); 53 | tx.send_result(item).unwrap(); 54 | rx 55 | } 56 | } 57 | 58 | impl futures::io::AsyncRead for FluxReceiver, Err> 59 | where 60 | Err: ConditionallySendSync, 61 | { 62 | fn poll_read(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, buf: &mut [u8]) -> Poll> { 63 | match Pin::new(&mut self.get_mut()).poll_next(cx) { 64 | Poll::Ready(Some(Ok(item))) => { 65 | let len = item.len(); 66 | let mut buf = std::io::Cursor::new(buf); 67 | buf.write_all(&item).unwrap(); 68 | Poll::Ready(Ok(len)) 69 | } 70 | Poll::Ready(Some(Err(_err))) => Poll::Ready(Err(std::io::Error::new( 71 | std::io::ErrorKind::Other, 72 | crate::Error::RecvFailed(99), 73 | ))), 74 | Poll::Ready(None) => Poll::Ready(Ok(0)), 75 | Poll::Pending => Poll::Pending, 76 | } 77 | } 78 | } 79 | 80 | impl futures::io::AsyncRead for FluxReceiver 81 | where 82 | Err: ConditionallySendSync, 83 | { 84 | fn poll_read(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, buf: &mut [u8]) -> Poll> { 85 | match Pin::new(&mut self.get_mut()).poll_next(cx) { 86 | Poll::Ready(Some(Ok(item))) => { 87 | let len = item.len(); 88 | let mut buf = std::io::Cursor::new(buf); 89 | buf.write_all(&item).unwrap(); 90 | Poll::Ready(Ok(len)) 91 | } 92 | Poll::Ready(Some(Err(_err))) => Poll::Ready(Err(std::io::Error::new( 93 | std::io::ErrorKind::Other, 94 | crate::Error::RecvFailed(99), 95 | ))), 96 | Poll::Ready(None) => Poll::Ready(Ok(0)), 97 | Poll::Pending => Poll::Pending, 98 | } 99 | } 100 | } 101 | 102 | impl Clone for FluxReceiver 103 | where 104 | Item: ConditionallySendSync, 105 | Err: ConditionallySendSync, 106 | { 107 | fn clone(&self) -> Self { 108 | Self { rx: self.rx.clone() } 109 | } 110 | } 111 | 112 | impl Observable for FluxReceiver 113 | where 114 | Item: ConditionallySendSync, 115 | Err: ConditionallySendSync, 116 | { 117 | } 118 | 119 | impl FluxReceiver 120 | where 121 | Item: ConditionallySendSync, 122 | Err: ConditionallySendSync, 123 | { 124 | #[must_use] 125 | /// Receive the next value from the [FluxReceiver]. 126 | pub fn recv(&self) -> FutureResult 127 | where 128 | Err: ConditionallySendSync, 129 | Item: ConditionallySendSync, 130 | { 131 | let root_rx = self.rx.clone(); 132 | let opt = root_rx.take(); 133 | Box::pin(async move { 134 | match opt { 135 | Some(mut rx) => { 136 | let signal = rx.recv().await; 137 | root_rx.insert(rx); 138 | Ok(signal_into_result(signal)) 139 | } 140 | None => Err(Error::RecvFailed(0)), 141 | } 142 | }) 143 | } 144 | 145 | /// Poll the [FluxReceiver] to see if there is a value available. 146 | pub fn poll_recv(&self, cx: &mut std::task::Context<'_>) -> Poll>> { 147 | let opt = self.rx.take(); 148 | opt.map_or(std::task::Poll::Ready(None), |mut rx| { 149 | let poll = rx.poll_recv(cx); 150 | match poll { 151 | Poll::Ready(Some(Signal::Complete)) => Poll::Ready(None), 152 | Poll::Ready(Some(Signal::Ok(v))) => { 153 | self.rx.insert(rx); 154 | Poll::Ready(Some(Ok(v))) 155 | } 156 | Poll::Ready(Some(Signal::Err(e))) => { 157 | self.rx.insert(rx); 158 | Poll::Ready(Some(Err(e))) 159 | } 160 | Poll::Ready(None) => Poll::Ready(None), 161 | Poll::Pending => { 162 | self.rx.insert(rx); 163 | Poll::Pending 164 | } 165 | } 166 | }) 167 | } 168 | 169 | #[must_use] 170 | /// Remove the inner channel from the [FluxReceiver] 171 | pub fn eject(&self) -> Option { 172 | self.rx.take().map(|inner| Self { 173 | rx: OptionalMut::new(inner), 174 | }) 175 | } 176 | } 177 | 178 | impl Stream for FluxReceiver 179 | where 180 | Item: ConditionallySendSync, 181 | Err: ConditionallySendSync, 182 | { 183 | type Item = Result; 184 | 185 | fn poll_next(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll> { 186 | self.poll_recv(cx) 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /crates/wasmrs-rx/src/flux/signal.rs: -------------------------------------------------------------------------------- 1 | use wasmrs_runtime::ConditionallySendSync; 2 | 3 | #[derive(PartialEq, Eq, Clone)] 4 | /// The [Signal] is the wrapper payload that wasmrx types pass around. 5 | pub enum Signal 6 | where 7 | Item: ConditionallySendSync, 8 | Err: ConditionallySendSync, 9 | { 10 | /// A success value. 11 | Ok(Item), 12 | /// An error value. 13 | Err(Err), 14 | /// An internal signal. 15 | Complete, 16 | } 17 | 18 | impl std::fmt::Debug for Signal 19 | where 20 | Item: ConditionallySendSync + std::fmt::Debug, 21 | Err: ConditionallySendSync + std::fmt::Debug, 22 | { 23 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 24 | match self { 25 | Self::Ok(arg0) => f.debug_tuple("Ok").field(arg0).finish(), 26 | Self::Err(arg0) => f.debug_tuple("Err").field(arg0).finish(), 27 | Self::Complete => f.write_str("Complete"), 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /crates/wasmrs-rx/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny( 2 | clippy::expect_used, 3 | clippy::explicit_deref_methods, 4 | clippy::option_if_let_else, 5 | clippy::await_holding_lock, 6 | clippy::cloned_instead_of_copied, 7 | clippy::explicit_into_iter_loop, 8 | clippy::flat_map_option, 9 | clippy::fn_params_excessive_bools, 10 | clippy::implicit_clone, 11 | clippy::inefficient_to_string, 12 | clippy::large_types_passed_by_value, 13 | clippy::manual_ok_or, 14 | clippy::map_flatten, 15 | clippy::map_unwrap_or, 16 | clippy::must_use_candidate, 17 | clippy::needless_for_each, 18 | clippy::needless_pass_by_value, 19 | clippy::option_option, 20 | clippy::redundant_else, 21 | clippy::semicolon_if_nothing_returned, 22 | clippy::too_many_lines, 23 | clippy::trivially_copy_pass_by_ref, 24 | clippy::unnested_or_patterns, 25 | clippy::future_not_send, 26 | clippy::useless_let_if_seq, 27 | clippy::str_to_string, 28 | clippy::inherent_to_string, 29 | clippy::let_and_return, 30 | clippy::string_to_string, 31 | clippy::try_err, 32 | clippy::unused_async, 33 | clippy::missing_enforced_import_renames, 34 | clippy::nonstandard_macro_braces, 35 | clippy::rc_mutex, 36 | clippy::unwrap_or_else_default, 37 | clippy::manual_split_once, 38 | clippy::derivable_impls, 39 | clippy::needless_option_as_deref, 40 | clippy::iter_not_returning_iterator, 41 | clippy::same_name_method, 42 | clippy::manual_assert, 43 | clippy::non_send_fields_in_send_ty, 44 | clippy::equatable_if_let, 45 | bad_style, 46 | clashing_extern_declarations, 47 | dead_code, 48 | deprecated, 49 | explicit_outlives_requirements, 50 | improper_ctypes, 51 | invalid_value, 52 | missing_copy_implementations, 53 | missing_debug_implementations, 54 | mutable_transmutes, 55 | no_mangle_generic_items, 56 | non_shorthand_field_patterns, 57 | overflowing_literals, 58 | path_statements, 59 | patterns_in_fns_without_body, 60 | private_in_public, 61 | trivial_bounds, 62 | trivial_casts, 63 | trivial_numeric_casts, 64 | type_alias_bounds, 65 | unconditional_recursion, 66 | unreachable_pub, 67 | unsafe_code, 68 | unstable_features, 69 | unused, 70 | unused_allocation, 71 | unused_comparisons, 72 | unused_import_braces, 73 | unused_parens, 74 | unused_qualifications, 75 | while_true, 76 | missing_docs 77 | )] 78 | #![doc = include_str!("../README.md")] 79 | 80 | mod error; 81 | mod flux; 82 | 83 | pub use flux::*; 84 | 85 | pub use error::Error; 86 | use futures::Stream; 87 | use wasmrs_runtime::ConditionallySendSync; 88 | 89 | /// A generic trait to wrap over Flux, Mono, and supporting types. 90 | pub trait Flux: Stream> + Unpin + ConditionallySendSync {} 91 | 92 | #[cfg(target_family = "wasm")] 93 | mod wasm { 94 | /// A utility type for a boxed future. 95 | pub type BoxMono = std::pin::Pin> + 'static>>; 96 | 97 | /// A utility type for a boxed stream. 98 | pub type BoxFlux = std::pin::Pin> + 'static>>; 99 | } 100 | #[cfg(target_family = "wasm")] 101 | pub use wasm::*; 102 | 103 | #[cfg(not(target_family = "wasm"))] 104 | mod native { 105 | /// A utility type for a boxed future. 106 | pub type BoxMono = std::pin::Pin> + Send + 'static>>; 107 | 108 | /// A utility type for a boxed stream. 109 | pub type BoxFlux = std::pin::Pin> + Send + 'static>>; 110 | } 111 | #[cfg(not(target_family = "wasm"))] 112 | pub use native::*; 113 | 114 | impl Flux for T 115 | where 116 | T: Stream> + Unpin, 117 | T: ConditionallySendSync, 118 | { 119 | } 120 | 121 | #[cfg(test)] 122 | mod test { 123 | use super::*; 124 | use anyhow::Result; 125 | use futures::StreamExt; 126 | 127 | #[tokio::test] 128 | async fn test_basic() -> Result<()> { 129 | async fn takes_any(mut stream: impl Flux) -> Vec { 130 | let mut acc = vec![]; 131 | while let Some(Ok(v)) = stream.next().await { 132 | acc.push(v); 133 | } 134 | acc 135 | } 136 | let flux = FluxChannel::::new(); 137 | flux.send(1)?; 138 | flux.send(2)?; 139 | flux.send(3)?; 140 | flux.send(4)?; 141 | flux.complete(); 142 | 143 | println!("waiting for flux results"); 144 | let results = takes_any(flux).await; 145 | assert_eq!(results, vec![1, 2, 3, 4]); 146 | 147 | let mono = Mono::::from_future(async move { Ok(42) }); 148 | println!("waiting for mono results"); 149 | let results = takes_any(mono).await; 150 | assert_eq!(results, vec![42]); 151 | Ok(()) 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /crates/wasmrs-wasmtime/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasmrs-wasmtime" 3 | version = "0.17.0" 4 | edition = "2021" 5 | license = "Apache-2.0" 6 | description = "Wasmtime engine for wasmRS hosts" 7 | repository = "https://github.com/wasmrs/wasmrs-rust" 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | [features] 11 | default = [] 12 | profiler = [] 13 | 14 | [dependencies] 15 | wasmrs-host = { path = "../wasmrs-host", version = "0.17.0" } 16 | wasmrs = { path = "../wasmrs", version = "0.17.0" } 17 | 18 | wasmtime = { workspace = true, features = ["async"] } 19 | wasmtime-wasi = { workspace = true, features = ["sync"] } 20 | wasi-common = { workspace = true } 21 | 22 | tracing = { workspace = true } 23 | thiserror = { workspace = true } 24 | parking_lot = { workspace = true } 25 | tokio = { workspace = true, features = ["rt", "sync"] } 26 | bytes = { workspace = true } 27 | futures = { workspace = true } 28 | cfg-if = { workspace = true } 29 | anyhow = { workspace = true } 30 | once_cell = { workspace = true } 31 | async-trait = { workspace = true } 32 | 33 | [dev-dependencies] 34 | env_logger = { workspace = true } 35 | wasmrs-rx = { path = "../wasmrs-rx", version = "0.17.0" } 36 | wasmrs-codec = { path = "../wasmrs-codec", version = "0.17.0", features = [] } 37 | tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } 38 | test-log = { workspace = true } 39 | serde = { workspace = true } 40 | clap = { workspace = true, features = ["derive"] } 41 | serde_json = { workspace = true } 42 | -------------------------------------------------------------------------------- /crates/wasmrs-wasmtime/README.md: -------------------------------------------------------------------------------- 1 | # wasmrs-wasmtime 2 | 3 | This crate provides the wasmtime implementation for wasmrs hosts. 4 | 5 | ## Usage 6 | 7 | See the [example](https://github.com/wasmrs/wasmrs-rust/blob/main/crates/wasmrs-wasmtime/examples/request.rs) for usage. 8 | 9 | For more information on wasmRS, see the core [wasmrs](https://github.com/wasmrs/wasmrs-rust/blob/main/crates/wasmrs/README.md) crate. 10 | 11 | ## Contributing 12 | 13 | See [CONTRIBUTING.md](https://github.com/WasmRS/wasmrs-rust/blob/main/CONTRIBUTING.md) 14 | 15 | ## License 16 | 17 | See the root [LICENSE.txt](https://github.com/WasmRS/wasmrs-rust/blob/main/LICENSE.txt) 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /crates/wasmrs-wasmtime/examples/request.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use wasmrs::{Metadata, RSocket, RawPayload}; 3 | use wasmrs_codec::messagepack::*; 4 | use wasmrs_host::WasiParams; 5 | use wasmrs_wasmtime::WasmtimeBuilder; 6 | 7 | #[derive(Parser, Debug)] 8 | #[command(author, version)] 9 | struct Args { 10 | /// Wasm module 11 | #[arg()] 12 | module: String, 13 | 14 | /// Namespace 15 | #[arg()] 16 | namespace: String, 17 | 18 | /// Operation 19 | #[arg()] 20 | operation: String, 21 | 22 | /// Data to send 23 | #[arg()] 24 | data: String, 25 | 26 | /// Treat request as request_stream 27 | #[arg(long = "stream", short = 's')] 28 | stream: bool, 29 | 30 | /// Treat request as request_channel 31 | #[arg(long = "channel", short = 'c')] 32 | channel: bool, 33 | } 34 | 35 | #[tokio::main] 36 | async fn main() -> anyhow::Result<()> { 37 | env_logger::init(); 38 | let args = Args::parse(); 39 | 40 | let module_bytes = std::fs::read(&args.module)?; 41 | let engine = WasmtimeBuilder::new() 42 | .with_module_bytes(&args.module, &module_bytes) 43 | .wasi_params(WasiParams::default()) 44 | .build()?; 45 | let host = wasmrs_host::Host::new(engine).await?; 46 | let context = host.new_context(64 * 1024, 64 * 1024).await?; 47 | 48 | let op = context.get_export(&args.namespace, &args.operation).unwrap(); 49 | 50 | let mbytes = Metadata::new(op).encode(); 51 | let val: serde_json::Value = serde_json::from_str(&args.data)?; 52 | let bytes = serialize(&val).unwrap(); 53 | 54 | let payload = RawPayload::new(mbytes, bytes.into()); 55 | 56 | if args.stream { 57 | unimplemented!() 58 | } else if args.channel { 59 | unimplemented!() 60 | } else { 61 | let response = context.request_response(payload.clone()); 62 | match response.await { 63 | Ok(v) => { 64 | let bytes = v.data.unwrap(); 65 | let val: String = deserialize(&bytes).unwrap(); 66 | println!("{}", val); 67 | } 68 | Err(e) => { 69 | println!("Error: {}", e) 70 | } 71 | } 72 | } 73 | 74 | Ok(()) 75 | } 76 | -------------------------------------------------------------------------------- /crates/wasmrs-wasmtime/src/builder.rs: -------------------------------------------------------------------------------- 1 | use parking_lot::Mutex; 2 | use wasi_common::WasiCtx; 3 | use wasmtime::Module; 4 | 5 | use crate::engine_provider::EpochDeadlines; 6 | use crate::errors::Error; 7 | use crate::wasi::init_wasi; 8 | use crate::WasmtimeEngineProvider; 9 | 10 | static MODULE_CACHE: once_cell::sync::Lazy>> = 11 | once_cell::sync::Lazy::new(|| Mutex::new(std::collections::HashMap::new())); 12 | 13 | /// Used to build [`WasmtimeEngineProvider`](crate::WasmtimeEngineProvider) instances. 14 | #[allow(missing_debug_implementations)] 15 | #[must_use] 16 | #[derive(Default)] 17 | pub struct WasmtimeBuilder<'a> { 18 | engine: Option, 19 | module: Option, 20 | module_bytes: Option<(String, &'a [u8])>, 21 | cache_enabled: bool, 22 | cache_path: Option, 23 | wasi_params: Option, 24 | wasi_ctx: Option, 25 | epoch_deadlines: Option, 26 | } 27 | 28 | impl<'a> WasmtimeBuilder<'a> { 29 | /// A new [WasmtimeBuilder] instance. 30 | pub fn new() -> Self { 31 | WasmtimeBuilder { ..Default::default() } 32 | } 33 | 34 | /// Query if the module cache contains a module with the given id. 35 | pub fn is_cached(id: impl AsRef) -> bool { 36 | let lock = MODULE_CACHE.lock(); 37 | lock.contains_key(id.as_ref()) 38 | } 39 | 40 | /// Initialize the builder with a preloaded module. 41 | pub fn with_cached_module(mut self, id: impl AsRef) -> Result { 42 | let lock = MODULE_CACHE.lock(); 43 | if let Some(module) = lock.get(id.as_ref()) { 44 | self.module = Some(module.clone()); 45 | return Ok(self); 46 | } 47 | Err(Error::NotFound(id.as_ref().to_owned())) 48 | } 49 | 50 | /// Initialize the builder with a module from the passed bytes, caching it with the passed ID. 51 | pub fn with_module_bytes(mut self, id: impl AsRef, bytes: &'a [u8]) -> Self { 52 | self.module_bytes = Some((id.as_ref().to_owned(), bytes)); 53 | self 54 | } 55 | 56 | /// Provide a preinitialized [`wasmtime::Engine`] 57 | /// 58 | /// **Warning:** when used, engine specific options like 59 | /// [`cache`](WasmtimeEngineProviderBuilder::enable_cache) and 60 | /// [`enable_epoch_interruptions`](WasmtimeEngineProviderBuilder::enable_epoch_interruptions) 61 | /// must be pre-configured by the user. `WasmtimeEngineProviderBuilder` won't be 62 | /// able to configure them at [`build`](WasmtimeEngineProviderBuilder::build) time. 63 | pub fn engine(mut self, engine: wasmtime::Engine) -> Self { 64 | self.engine = Some(engine); 65 | self 66 | } 67 | 68 | /// WASI params, for basic WASI support. 69 | /// 70 | /// **Warning:** this has no effect when a custom [`WasiCtx`] is provided via the 71 | /// [`WasmtimeEngineProviderBuilder::wasi_ctx`] helper. 72 | pub fn wasi_params(mut self, wasi: wasmrs_host::WasiParams) -> Self { 73 | self.wasi_params = Some(wasi); 74 | self 75 | } 76 | 77 | /// Wasmtime WASI Context, for when you need more control over the WASI environment. 78 | pub fn wasi_ctx(mut self, wasi: WasiCtx) -> Self { 79 | self.wasi_ctx = Some(wasi); 80 | self 81 | } 82 | 83 | /// Enable Wasmtime cache feature 84 | /// 85 | /// **Warning:** this has no effect when a custom [`wasmtime::Engine`] is provided via 86 | /// the [`WasmtimeEngineProviderBuilder::engine`] helper. In that case, it's up to the 87 | /// user to provide a [`wasmtime::Engine`] instance with the cache values properly configured. 88 | pub fn enable_cache(mut self, path: Option<&std::path::Path>) -> Self { 89 | self.cache_enabled = true; 90 | self.cache_path = path.map(|p| p.to_path_buf()); 91 | self 92 | } 93 | 94 | /// Enable Wasmtime [epoch-based interruptions](wasmtime::Config::epoch_interruption) and set 95 | /// the deadlines to be enforced 96 | /// 97 | /// Two kind of deadlines have to be set: 98 | /// 99 | /// * `wasmrs_init_deadline`: the number of ticks the wasmRS initialization code can take before the 100 | /// code is interrupted. This is the code usually defined inside of the `wasmrs_init`/`_start` 101 | /// functions 102 | /// * `wasmrs_func_deadline`: the number of ticks any regular wasmRS guest function can run before 103 | /// its terminated by the host 104 | /// 105 | /// Both these limits are expressed using the number of ticks that are allowed before the 106 | /// WebAssembly execution is interrupted. 107 | /// It's up to the embedder of wasmRS to define how much time a single tick is granted. This could 108 | /// be 1 second, 10 nanoseconds, or whatever the user prefers. 109 | /// 110 | /// **Warning:** when providing an instance of `wasmtime::Engine` via the 111 | /// `WasmtimeEngineProvider::engine` helper, ensure the `wasmtime::Engine` 112 | /// has been created with the `epoch_interruption` feature enabled 113 | pub fn enable_epoch_interruptions(mut self, wasmrs_init_deadline: u64, wasmrs_func_deadline: u64) -> Self { 114 | self.epoch_deadlines = Some(EpochDeadlines { 115 | wasmrs_init: wasmrs_init_deadline, 116 | wasmrs_func: wasmrs_func_deadline, 117 | }); 118 | self 119 | } 120 | 121 | /// Create a `WasmtimeEngineProvider` instance 122 | pub fn build(self) -> Result { 123 | let engine = match &self.engine { 124 | Some(e) => { 125 | // note: we have to call `.clone()` because `e` is behind 126 | // a shared reference and `Engine` does not implement `Copy`. 127 | // However, cloning an `Engine` is a cheap operation because 128 | // under the hood wasmtime does not create a new `Engine`, but 129 | // rather creates a new reference to it. 130 | // See https://docs.rs/wasmtime/latest/wasmtime/struct.Engine.html#engines-and-clone 131 | e.clone() 132 | } 133 | None => { 134 | let mut config = wasmtime::Config::default(); 135 | config.async_support(true); 136 | 137 | if self.epoch_deadlines.is_some() { 138 | config.epoch_interruption(true); 139 | } 140 | 141 | if self.cache_enabled { 142 | config.strategy(wasmtime::Strategy::Cranelift); 143 | if let Some(cache) = &self.cache_path { 144 | config.cache_config_load(cache).map_err(Error::Initialization)?; 145 | } else if let Err(e) = config.cache_config_load_default() { 146 | warn!("Wasmtime cache configuration not found ({}). Repeated loads will speed up significantly with a cache configuration. See https://docs.wasmtime.dev/cli-cache.html for more information.",e); 147 | } 148 | } 149 | 150 | #[cfg(feature = "profiler")] 151 | config.profiler(wasmtime::ProfilingStrategy::JitDump); 152 | 153 | wasmtime::Engine::new(&config).map_err(Error::Initialization)? 154 | } 155 | }; 156 | 157 | let module = match (self.module, self.module_bytes) { 158 | (Some(m), None) => m.clone(), 159 | (None, Some((id, bytes))) => { 160 | let module = Module::from_binary(&engine, bytes).map_err(Error::Initialization)?; 161 | let mut lock = MODULE_CACHE.lock(); 162 | lock.insert(id.clone(), module.clone()); 163 | module 164 | } 165 | (None, None) => return Err(Error::NoModule), 166 | _ => return Err(Error::AmbiguousModule), 167 | }; 168 | 169 | let epoch_deadlines = self.epoch_deadlines; 170 | 171 | let ctx = if self.wasi_ctx.is_some() { 172 | self.wasi_ctx 173 | } else if let Some(wasi_params) = self.wasi_params { 174 | Some(init_wasi(&wasi_params)?) 175 | } else { 176 | None 177 | }; 178 | 179 | let mut provider = WasmtimeEngineProvider::new_with_engine(module, engine, ctx)?; 180 | 181 | provider.epoch_deadlines = epoch_deadlines; 182 | 183 | Ok(provider) 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /crates/wasmrs-wasmtime/src/engine_provider.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use bytes::{BufMut, BytesMut}; 4 | use tokio::sync::Mutex; 5 | use wasi_common::WasiCtx; 6 | use wasmrs::{Frame, OperationList, WasmSocket}; 7 | use wasmrs_host::{EngineProvider, GuestExports, HostServer, ProviderCallContext, SharedContext}; 8 | use wasmtime::{AsContextMut, Engine, Linker, Memory, Module, Store, TypedFunc}; 9 | 10 | use super::Result; 11 | use crate::errors::Error; 12 | use crate::memory::write_bytes_to_memory; 13 | use crate::store::{new_store, ProviderStore}; 14 | use crate::wasmrs_wasmtime::{self}; 15 | 16 | /// A wasmRS engine provider that encapsulates the Wasmtime WebAssembly runtime 17 | #[allow(missing_debug_implementations)] 18 | pub struct WasmtimeEngineProvider { 19 | module: Module, 20 | engine: Engine, 21 | linker: Linker>, 22 | wasi_ctx: Option, 23 | pub(crate) epoch_deadlines: Option, 24 | } 25 | 26 | #[derive(Clone, Copy, Debug)] 27 | pub(crate) struct EpochDeadlines { 28 | /// Deadline for wasmRS initialization code. Expressed in number of epoch ticks 29 | #[allow(dead_code)] 30 | pub(crate) wasmrs_init: u64, 31 | 32 | /// Deadline for user-defined wasmRS function computation. Expressed in number of epoch ticks 33 | #[allow(dead_code)] 34 | pub(crate) wasmrs_func: u64, 35 | } 36 | 37 | impl WasmtimeEngineProvider { 38 | /// Creates a new instance of a [WasmtimeEngineProvider] from a separately created [wasmtime::Engine]. 39 | pub(crate) fn new_with_engine(module: Module, engine: Engine, wasi_ctx: Option) -> Result { 40 | let mut linker: Linker> = Linker::new(&engine); 41 | 42 | if wasi_ctx.is_some() { 43 | wasmtime_wasi::add_to_linker(&mut linker, |s| s.wasi_ctx.as_mut().unwrap()).unwrap(); 44 | } 45 | 46 | Ok(WasmtimeEngineProvider { 47 | module, 48 | engine, 49 | wasi_ctx, 50 | linker, 51 | epoch_deadlines: None, 52 | }) 53 | } 54 | } 55 | 56 | #[async_trait::async_trait] 57 | impl EngineProvider for WasmtimeEngineProvider { 58 | async fn new_context( 59 | &self, 60 | socket: Arc>, 61 | ) -> std::result::Result { 62 | let store = new_store(self.wasi_ctx.clone(), socket, &self.engine) 63 | .map_err(|e| wasmrs_host::errors::Error::NewContext(e.to_string()))?; 64 | 65 | let context = SharedContext::new( 66 | WasmtimeCallContext::new(self.linker.clone(), &self.module, store) 67 | .await 68 | .map_err(|e| wasmrs_host::errors::Error::InitFailed(Box::new(e)))?, 69 | ); 70 | 71 | Ok(context) 72 | } 73 | } 74 | 75 | #[derive(PartialEq, Debug)] 76 | enum Version { 77 | V0, 78 | V1, 79 | } 80 | 81 | struct Imports { 82 | start: Option>, 83 | guest_init: TypedFunc<(u32, u32, u32), ()>, 84 | op_list: Option>, 85 | guest_send: TypedFunc, 86 | version: Version, 87 | } 88 | 89 | struct WasmtimeCallContext { 90 | memory: Memory, 91 | store: Mutex>>, 92 | imports: Imports, 93 | op_list: parking_lot::Mutex, 94 | } 95 | 96 | impl WasmtimeCallContext { 97 | pub(crate) async fn new( 98 | mut linker: Linker>, 99 | module: &Module, 100 | mut store: Store>, 101 | ) -> Result { 102 | wasmrs_wasmtime::add_to_linker(&mut linker)?; 103 | let instance = linker 104 | .instantiate_async(&mut store, module) 105 | .await 106 | .map_err(Error::Linker)?; 107 | 108 | let guest_send = instance 109 | .get_typed_func::(&mut store, GuestExports::Send.as_ref()) 110 | .map_err(|_| crate::errors::Error::GuestSend)?; 111 | let memory = instance.get_memory(&mut store, "memory").unwrap(); 112 | 113 | let version = instance 114 | .get_typed_func::<(), ()>(&mut store, GuestExports::Version1.as_ref()) 115 | .map_or(Version::V0, |_| Version::V1); 116 | 117 | let imports = Imports { 118 | version, 119 | start: instance.get_typed_func(&mut store, GuestExports::Start.as_ref()).ok(), 120 | guest_init: instance 121 | .get_typed_func(&mut store, GuestExports::Init.as_ref()) 122 | .map_err(|_e| Error::GuestInit)?, 123 | op_list: instance 124 | .get_typed_func::<(), ()>(&mut store, GuestExports::OpListRequest.as_ref()) 125 | .ok(), 126 | guest_send, 127 | }; 128 | 129 | Ok(Self { 130 | memory, 131 | store: Mutex::new(store), 132 | imports, 133 | op_list: parking_lot::Mutex::new(OperationList::default()), 134 | }) 135 | } 136 | } 137 | 138 | #[async_trait::async_trait] 139 | impl wasmrs::ModuleHost for WasmtimeCallContext { 140 | /// Request-Response interaction model of RSocket. 141 | async fn write_frame(&self, mut req: Frame) -> std::result::Result<(), wasmrs::Error> { 142 | let bytes = if self.imports.version == Version::V0 { 143 | req.make_v0_metadata(); 144 | req.encode() 145 | } else { 146 | req.encode() 147 | }; 148 | trace!(?bytes, "writing frame"); 149 | 150 | let buffer_len_bytes = wasmrs::util::to_u24_bytes(bytes.len() as u32); 151 | let mut buffer = BytesMut::with_capacity(buffer_len_bytes.len() + bytes.len()); 152 | buffer.put(buffer_len_bytes); 153 | buffer.put(bytes); 154 | 155 | let mut store = self.store.lock().await; 156 | 157 | let start = store.data().guest_buffer.get_start(); 158 | let len = store.data().guest_buffer.get_size(); 159 | 160 | let written = write_bytes_to_memory(store.as_context_mut(), self.memory, &buffer, start, len); 161 | 162 | self 163 | .imports 164 | .guest_send 165 | .call_async(store.as_context_mut(), written as i32) 166 | .await 167 | .map_err(|e| wasmrs::Error::GuestCall(e.to_string()))?; 168 | 169 | Ok(()) 170 | } 171 | 172 | async fn on_error(&self, stream_id: u32) -> std::result::Result<(), wasmrs::Error> { 173 | let mut lock = self.store.lock().await; 174 | let data = lock.data_mut(); 175 | if let Err(e) = data.socket.process_once(Frame::new_cancel(stream_id)) { 176 | error!("error processing cancel for stream id {}, {}", stream_id, e); 177 | }; 178 | Ok(()) 179 | } 180 | 181 | fn get_import(&self, namespace: &str, operation: &str) -> Option { 182 | self.op_list.lock().get_import(namespace, operation) 183 | } 184 | 185 | fn get_export(&self, namespace: &str, operation: &str) -> Option { 186 | self.op_list.lock().get_export(namespace, operation) 187 | } 188 | 189 | fn get_operation_list(&self) -> OperationList { 190 | self.op_list.lock().clone() 191 | } 192 | } 193 | 194 | #[async_trait::async_trait] 195 | impl ProviderCallContext for WasmtimeCallContext { 196 | async fn init( 197 | &self, 198 | host_buffer_size: u32, 199 | guest_buffer_size: u32, 200 | ) -> std::result::Result<(), wasmrs_host::errors::Error> { 201 | let mut store = self.store.lock().await; 202 | 203 | if let Some(start) = &self.imports.start { 204 | start 205 | .call_async(store.as_context_mut(), ()) 206 | .await 207 | .map_err(|e| wasmrs_host::errors::Error::InitFailed(e.into()))?; 208 | } 209 | 210 | self 211 | .imports 212 | .guest_init 213 | .call_async(store.as_context_mut(), (host_buffer_size, guest_buffer_size, 128)) 214 | .await 215 | .map_err(|e| wasmrs_host::errors::Error::InitFailed(e.into()))?; 216 | 217 | store.data().guest_buffer.update_size(guest_buffer_size); 218 | store.data().host_buffer.update_size(host_buffer_size); 219 | 220 | if let Some(oplist) = self.imports.op_list { 221 | trace!("calling operation list"); 222 | oplist 223 | .call_async(store.as_context_mut(), ()) 224 | .await 225 | .map_err(|e| wasmrs_host::errors::Error::OpList(e.to_string()))?; 226 | 227 | *self.op_list.lock() = store.data().op_list.clone(); 228 | } 229 | 230 | Ok(()) 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /crates/wasmrs-wasmtime/src/errors.rs: -------------------------------------------------------------------------------- 1 | use wasi_common::StringArrayError; 2 | 3 | /// This crate's Error type 4 | #[derive(thiserror::Error, Debug)] 5 | pub enum Error { 6 | /// WASMTime initialization failed 7 | #[error("Initialization failed: {0}")] 8 | Initialization(anyhow::Error), 9 | 10 | /// WASMTime Linker initialization failed 11 | #[error("Linker initialization failed: {0}")] 12 | Linker(anyhow::Error), 13 | 14 | /// Setting up linked functions failed. 15 | #[error("Could not create WebAssembly function: {0}")] 16 | Func(anyhow::Error), 17 | 18 | /// WASMTime module instantiation failed 19 | #[error("Could not instantiate new WASM Module: {0}")] 20 | Module(anyhow::Error), 21 | 22 | /// WASMTime module instantiation failed 23 | #[error("Could not find module {0} in module cache")] 24 | NotFound(String), 25 | 26 | /// Error originating from [wasi_common] 27 | #[error(transparent)] 28 | WasiError(#[from] wasi_common::Error), 29 | 30 | /// Error originating from [wasi_common] 31 | #[error(transparent)] 32 | WasiStringArray(#[from] StringArrayError), 33 | 34 | /// Error originating from [std::io::Error] 35 | #[error(transparent)] 36 | IO(#[from] std::io::Error), 37 | 38 | /// Thrown if the guest's send function is not exported. 39 | #[error("Guest init function not exported by wasm module.")] 40 | GuestInit, 41 | 42 | /// Thrown if the guest's send function is not exported. 43 | #[error("Guest send function not exported by wasm module.")] 44 | GuestSend, 45 | 46 | /// Thrown if the host has a problem reading the guest's memory. 47 | #[error("Could not read guest memory")] 48 | GuestMemory, 49 | 50 | /// Thrown if the builder wasn't provide a module to instantiate with. 51 | #[error("Must provide a module to the builder")] 52 | NoModule, 53 | 54 | /// Thrown if the builder was provided too many module options. 55 | #[error("Must provide either module bytes with ID to cache or a cached ID, not both")] 56 | AmbiguousModule, 57 | } 58 | 59 | impl From for wasmrs::Error { 60 | fn from(e: Error) -> Self { 61 | let code = match e { 62 | Error::GuestMemory => wasmrs::ErrorCode::Canceled, 63 | Error::Initialization(_) => wasmrs::ErrorCode::ConnectionError, 64 | Error::Func(_) => wasmrs::ErrorCode::ConnectionError, 65 | Error::Linker(_) => wasmrs::ErrorCode::ConnectionError, 66 | Error::Module(_) => wasmrs::ErrorCode::ConnectionError, 67 | Error::WasiError(_) => wasmrs::ErrorCode::ConnectionError, 68 | _ => wasmrs::ErrorCode::ApplicationError, 69 | }; 70 | wasmrs::Error::RSocket(code.into()) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /crates/wasmrs-wasmtime/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny( 2 | clippy::expect_used, 3 | clippy::explicit_deref_methods, 4 | clippy::option_if_let_else, 5 | clippy::await_holding_lock, 6 | clippy::cloned_instead_of_copied, 7 | clippy::explicit_into_iter_loop, 8 | clippy::flat_map_option, 9 | clippy::fn_params_excessive_bools, 10 | clippy::implicit_clone, 11 | clippy::inefficient_to_string, 12 | clippy::large_types_passed_by_value, 13 | clippy::manual_ok_or, 14 | clippy::map_flatten, 15 | clippy::map_unwrap_or, 16 | clippy::must_use_candidate, 17 | clippy::needless_for_each, 18 | clippy::needless_pass_by_value, 19 | clippy::option_option, 20 | clippy::redundant_else, 21 | clippy::semicolon_if_nothing_returned, 22 | clippy::too_many_lines, 23 | clippy::trivially_copy_pass_by_ref, 24 | clippy::unnested_or_patterns, 25 | clippy::future_not_send, 26 | clippy::useless_let_if_seq, 27 | clippy::str_to_string, 28 | clippy::inherent_to_string, 29 | clippy::let_and_return, 30 | clippy::string_to_string, 31 | clippy::try_err, 32 | clippy::unused_async, 33 | clippy::missing_enforced_import_renames, 34 | clippy::nonstandard_macro_braces, 35 | clippy::rc_mutex, 36 | clippy::unwrap_or_else_default, 37 | clippy::manual_split_once, 38 | clippy::derivable_impls, 39 | clippy::needless_option_as_deref, 40 | clippy::iter_not_returning_iterator, 41 | clippy::same_name_method, 42 | clippy::manual_assert, 43 | clippy::non_send_fields_in_send_ty, 44 | clippy::equatable_if_let, 45 | bad_style, 46 | clashing_extern_declarations, 47 | dead_code, 48 | deprecated, 49 | explicit_outlives_requirements, 50 | improper_ctypes, 51 | invalid_value, 52 | missing_copy_implementations, 53 | missing_debug_implementations, 54 | mutable_transmutes, 55 | no_mangle_generic_items, 56 | non_shorthand_field_patterns, 57 | overflowing_literals, 58 | path_statements, 59 | patterns_in_fns_without_body, 60 | private_in_public, 61 | trivial_bounds, 62 | trivial_casts, 63 | trivial_numeric_casts, 64 | type_alias_bounds, 65 | unconditional_recursion, 66 | unreachable_pub, 67 | unsafe_code, 68 | unstable_features, 69 | unused, 70 | unused_allocation, 71 | unused_comparisons, 72 | unused_import_braces, 73 | unused_parens, 74 | unused_qualifications, 75 | while_true, 76 | missing_docs 77 | )] 78 | #![doc = include_str!("../README.md")] 79 | #![allow(unused_attributes)] 80 | mod builder; 81 | mod memory; 82 | mod wasi; 83 | mod wasmrs_wasmtime; 84 | 85 | mod engine_provider; 86 | /// The crate's error module 87 | pub mod errors; 88 | mod store; 89 | 90 | pub use builder::WasmtimeBuilder; 91 | pub use engine_provider::WasmtimeEngineProvider; 92 | 93 | #[macro_use] 94 | extern crate tracing; 95 | 96 | type Result = std::result::Result; 97 | -------------------------------------------------------------------------------- /crates/wasmrs-wasmtime/src/memory.rs: -------------------------------------------------------------------------------- 1 | use bytes::Bytes; 2 | use wasmtime::{AsContext, Caller, Memory, StoreContext}; 3 | 4 | use crate::errors::Error; 5 | 6 | pub(crate) fn get_vec_from_memory<'a, T: 'a>( 7 | store: impl Into>, 8 | mem: Memory, 9 | ptr: i32, 10 | len: i32, 11 | ) -> Vec { 12 | let data = mem.data(store); 13 | data[ptr as usize..(ptr + len) as usize].to_vec() 14 | } 15 | 16 | pub(crate) fn get_caller_memory(caller: &mut Caller) -> Memory { 17 | let memory = caller.get_export("memory").map(|e| e.into_memory().unwrap()); 18 | memory.unwrap() 19 | } 20 | 21 | pub(crate) fn read_frame<'a, T: 'a>( 22 | store: impl Into>, 23 | mem: Memory, 24 | ptr: usize, 25 | read_until: usize, 26 | ) -> super::Result { 27 | let data = mem.data(store); 28 | let buff = &data[ptr..(ptr + read_until)]; 29 | wasmrs::util::read_frame(buff).map_err(|_| Error::GuestMemory) 30 | } 31 | 32 | pub(crate) fn write_bytes_to_memory( 33 | store: impl AsContext, 34 | memory: Memory, 35 | buffer: &[u8], 36 | buffer_start: u32, 37 | buffer_len: u32, 38 | ) -> u32 { 39 | let len = buffer.len(); 40 | 41 | #[allow(unsafe_code)] 42 | unsafe { 43 | let guest_ptr = memory.data_ptr(&store).offset(buffer_start as isize); 44 | assert!( 45 | len <= buffer_len as usize, 46 | "Writing more data than guest buffer can store." 47 | ); 48 | guest_ptr.copy_from(buffer.as_ptr(), len); 49 | } 50 | len as u32 51 | } 52 | -------------------------------------------------------------------------------- /crates/wasmrs-wasmtime/src/store.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use bytes::Bytes; 4 | use wasmrs::{BufferState, Frame, OperationList, PayloadError, RSocket, WasmSocket}; 5 | use wasmrs_host::CallbackProvider; 6 | use wasmrs_host::{errors::Error, HostServer}; 7 | use wasmtime::{Engine, Store}; 8 | 9 | type WasiCtx = wasmtime_wasi::WasiCtx; 10 | 11 | pub(crate) struct ProviderStore { 12 | pub(crate) wasi_ctx: Option, 13 | pub(crate) socket: Arc>, 14 | pub(crate) host_buffer: BufferState, 15 | pub(crate) guest_buffer: BufferState, 16 | pub(crate) op_list: OperationList, 17 | } 18 | 19 | impl CallbackProvider for ProviderStore { 20 | fn do_host_init(&self, guest_buff_ptr: u32, host_buff_ptr: u32) -> Result<(), Error> { 21 | self.guest_buffer.update_start(guest_buff_ptr); 22 | self.host_buffer.update_start(host_buff_ptr); 23 | Ok(()) 24 | } 25 | 26 | fn do_host_send(&self, frame_bytes: Bytes) -> Result<(), Error> { 27 | match Frame::decode(frame_bytes) { 28 | Ok(frame) => self 29 | .socket 30 | .process_once(frame) 31 | .map_err(|e| Error::SendFailed(e.to_string())), 32 | Err((stream_id, err)) => { 33 | self 34 | .socket 35 | .send(Frame::new_error(stream_id, PayloadError::new(0, err.to_string(), None))); 36 | Ok(()) 37 | } 38 | } 39 | } 40 | 41 | fn do_console_log(&self, msg: &str) { 42 | println!("{}", msg); 43 | } 44 | 45 | fn do_op_list(&mut self, bytes: Bytes) -> Result<(), Error> { 46 | let op_list = match OperationList::decode(bytes) { 47 | Ok(v) => v, 48 | Err(e) => { 49 | eprintln!("Could not decode operation list: {}", e); 50 | return Err(Error::OpList(e.to_string())); 51 | } 52 | }; 53 | self.op_list = op_list; 54 | Ok(()) 55 | } 56 | } 57 | 58 | pub(crate) fn new_store( 59 | wasi_ctx: Option, 60 | socket: Arc>, 61 | engine: &Engine, 62 | ) -> super::Result>> { 63 | Ok(Store::new( 64 | engine, 65 | ProviderStore { 66 | host_buffer: Default::default(), 67 | guest_buffer: Default::default(), 68 | op_list: OperationList::default(), 69 | socket, 70 | wasi_ctx, 71 | }, 72 | )) 73 | } 74 | -------------------------------------------------------------------------------- /crates/wasmrs-wasmtime/src/wasi.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use wasi_common::WasiCtx; 4 | use wasmtime_wasi::{ambient_authority, Dir}; 5 | 6 | use crate::errors::Error; 7 | 8 | pub(crate) fn init_ctx( 9 | preopen_dirs: &[(PathBuf, Dir)], 10 | argv: &[String], 11 | env: &[(String, String)], 12 | ) -> Result { 13 | let mut ctx_builder = wasmtime_wasi::WasiCtxBuilder::new(); 14 | 15 | ctx_builder.inherit_stdio().args(argv)?.envs(env)?; 16 | 17 | for (name, file) in preopen_dirs { 18 | ctx_builder.preopened_dir(file.try_clone()?, name)?; 19 | } 20 | 21 | Ok(ctx_builder.build()) 22 | } 23 | 24 | pub(crate) fn compute_preopen_dirs<'a, T: Iterator>( 25 | dirs: &[PathBuf], 26 | map_dirs: T, 27 | ) -> Result, Error> { 28 | let ambient_authority = ambient_authority(); 29 | let mut preopen_dirs = Vec::new(); 30 | 31 | for dir in dirs.iter() { 32 | preopen_dirs.push((dir.clone(), Dir::open_ambient_dir(dir, ambient_authority)?)); 33 | } 34 | 35 | for (guest, host) in map_dirs { 36 | preopen_dirs.push((PathBuf::from(guest), Dir::open_ambient_dir(host, ambient_authority)?)); 37 | } 38 | 39 | Ok(preopen_dirs) 40 | } 41 | 42 | pub(crate) fn init_wasi(params: &wasmrs_host::WasiParams) -> Result { 43 | init_ctx( 44 | &compute_preopen_dirs(¶ms.preopened_dirs, params.map_dirs.iter()).unwrap(), 45 | ¶ms.argv, 46 | ¶ms.env_vars, 47 | ) 48 | } 49 | -------------------------------------------------------------------------------- /crates/wasmrs-wasmtime/src/wasmrs_wasmtime.rs: -------------------------------------------------------------------------------- 1 | use bytes::Bytes; 2 | use wasmrs_host::{CallbackProvider, HostExports, HostServer, IntoEnumIterator}; 3 | use wasmtime::{AsContext, Caller, FuncType, Linker, Val, ValType}; 4 | 5 | use crate::errors::Error; 6 | use crate::memory::{get_caller_memory, get_vec_from_memory, read_frame}; 7 | use crate::store::ProviderStore; 8 | 9 | pub(crate) fn add_to_linker(linker: &mut Linker>) -> super::Result<()> { 10 | let module_name = wasmrs_host::HOST_NAMESPACE; 11 | for export in HostExports::iter() { 12 | match export { 13 | HostExports::Send => { 14 | let (extern_type, extern_fn) = linker_send(); 15 | linker 16 | .func_new(module_name, export.as_ref(), extern_type, extern_fn) 17 | .map_err(Error::Func)?; 18 | } 19 | HostExports::Init => { 20 | let (extern_type, extern_fn) = linker_init(); 21 | linker 22 | .func_new(module_name, export.as_ref(), extern_type, extern_fn) 23 | .map_err(Error::Func)?; 24 | } 25 | HostExports::Log => { 26 | let (extern_type, extern_fn) = linker_console_log(); 27 | linker 28 | .func_new(module_name, export.as_ref(), extern_type, extern_fn) 29 | .map_err(Error::Func)?; 30 | } 31 | HostExports::OpList => { 32 | let (extern_type, extern_fn) = linker_op_list(); 33 | linker 34 | .func_new(module_name, export.as_ref(), extern_type, extern_fn) 35 | .map_err(Error::Func)?; 36 | } 37 | }; 38 | } 39 | Ok(()) 40 | } 41 | 42 | fn linker_send() -> ( 43 | FuncType, 44 | impl Fn(Caller<'_, ProviderStore>, &[Val], &mut [Val]) -> Result<(), anyhow::Error> + Send + Sync + 'static, 45 | ) { 46 | ( 47 | FuncType::new(vec![ValType::I32], vec![]), 48 | move |mut caller, params: &[Val], _results: &mut [Val]| { 49 | trace!( 50 | import = wasmrs_host::HostExports::Send.as_ref(), 51 | ?params, 52 | "guest calling host" 53 | ); 54 | 55 | let read_until = params[0].unwrap_i32() as usize; 56 | let memory = get_caller_memory(&mut caller); 57 | let bytes = read_frame( 58 | caller.as_context(), 59 | memory, 60 | caller.data().host_buffer.get_start() as _, 61 | read_until, 62 | )?; 63 | trace!(?bytes, "got frame"); 64 | 65 | caller.data().do_host_send(bytes)?; 66 | Ok(()) 67 | }, 68 | ) 69 | } 70 | 71 | fn linker_init() -> ( 72 | FuncType, 73 | impl Fn(Caller<'_, ProviderStore>, &[Val], &mut [Val]) -> Result<(), anyhow::Error> + Send + Sync + 'static, 74 | ) { 75 | ( 76 | FuncType::new(vec![ValType::I32, ValType::I32], vec![]), 77 | move |caller, params: &[Val], _results: &mut [Val]| { 78 | trace!( 79 | import = wasmrs_host::HostExports::Init.as_ref(), 80 | ?params, 81 | "guest calling host" 82 | ); 83 | 84 | let guest_buff_ptr = params[0].unwrap_i32(); 85 | let host_buff_ptr = params[1].unwrap_i32(); 86 | 87 | caller 88 | .data() 89 | .do_host_init(guest_buff_ptr.try_into().unwrap(), host_buff_ptr.try_into().unwrap())?; 90 | 91 | Ok(()) 92 | }, 93 | ) 94 | } 95 | 96 | fn linker_console_log() -> ( 97 | FuncType, 98 | impl Fn(Caller<'_, ProviderStore>, &[Val], &mut [Val]) -> Result<(), anyhow::Error> + Send + Sync + 'static, 99 | ) { 100 | ( 101 | FuncType::new(vec![ValType::I32, ValType::I32], vec![]), 102 | move |mut caller, params: &[Val], _results: &mut [Val]| { 103 | let ptr = params[0].i32(); 104 | let len = params[1].i32(); 105 | let memory = get_caller_memory(&mut caller); 106 | let vec = get_vec_from_memory(caller.as_context(), memory, ptr.unwrap(), len.unwrap()); 107 | 108 | let msg = std::str::from_utf8(&vec).unwrap(); 109 | 110 | caller.data().do_console_log(msg); 111 | Ok(()) 112 | }, 113 | ) 114 | } 115 | 116 | fn linker_op_list() -> ( 117 | FuncType, 118 | impl Fn(Caller<'_, ProviderStore>, &[Val], &mut [Val]) -> Result<(), anyhow::Error> + Send + Sync + 'static, 119 | ) { 120 | ( 121 | FuncType::new(vec![ValType::I32, ValType::I32], vec![]), 122 | move |mut caller, params: &[Val], _results: &mut [Val]| { 123 | trace!( 124 | import = %wasmrs_host::HostExports::OpList, 125 | ?params, 126 | "guest calling host" 127 | ); 128 | let ptr = params[0].i32(); 129 | let len = params[1].i32(); 130 | let memory = get_caller_memory(&mut caller); 131 | let vec = get_vec_from_memory(caller.as_context(), memory, ptr.unwrap(), len.unwrap()); 132 | 133 | caller.data_mut().do_op_list(Bytes::from(vec))?; 134 | Ok(()) 135 | }, 136 | ) 137 | } 138 | -------------------------------------------------------------------------------- /crates/wasmrs-wasmtime/tests/test_baseline.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | 3 | use futures::StreamExt; 4 | use wasmrs::{BoxFlux, GenericError, Metadata, Payload, PayloadError, RSocket, RawPayload}; 5 | use wasmrs_codec::messagepack::*; 6 | use wasmrs_host::WasiParams; 7 | use wasmrs_rx::*; 8 | use wasmrs_wasmtime::WasmtimeBuilder; 9 | 10 | static MODULE_BYTES: &[u8] = include_bytes!("../../../build/baseline.wasm"); 11 | 12 | fn callback(incoming: BoxFlux) -> Result, GenericError> { 13 | let (tx, rx) = FluxChannel::new_parts(); 14 | tokio::spawn(async move { 15 | let mut incoming = incoming; 16 | while let Some(payload) = incoming.next().await { 17 | let _ = tx.send_result(payload.map(|p| RawPayload::new_data(None, Some(p.data)))); 18 | } 19 | }); 20 | Ok(rx.boxed()) 21 | } 22 | 23 | #[test_log::test(tokio::test)] 24 | async fn test_req_channel_callback() -> anyhow::Result<()> { 25 | let engine = WasmtimeBuilder::new() 26 | .with_module_bytes("baseline", MODULE_BYTES) 27 | .wasi_params(WasiParams::default()) 28 | .build()?; 29 | let host = wasmrs_host::Host::new(engine).await?; 30 | 31 | host.register_request_channel("test", "callback", Box::new(callback)); 32 | let context = host.new_context(64 * 1024, 64 * 1024).await?; 33 | let op = context.get_export("test", "callback").unwrap(); 34 | 35 | let mbytes = Metadata::new(op).encode(); 36 | 37 | let input = "HELLO WORLD".to_owned(); 38 | 39 | let bytes = serialize(&input).unwrap(); 40 | 41 | let payload = RawPayload::new(mbytes, bytes.into()); 42 | 43 | let stream = FluxChannel::new(); 44 | stream.send(payload.clone())?; 45 | stream.complete(); 46 | let mut response = context.request_channel(Box::pin(stream)); 47 | let mut outputs: VecDeque = vec!["HELLO WORLD".to_owned()].into(); 48 | while let Some(response) = response.next().await { 49 | println!("response: {:?}", response); 50 | match response { 51 | Ok(v) => { 52 | let bytes = v.data.unwrap(); 53 | let val: String = deserialize(&bytes).unwrap(); 54 | println!("{}", val); 55 | let next = outputs.pop_front().unwrap(); 56 | assert_eq!(val, next); 57 | } 58 | Err(e) => { 59 | panic!("Error: {:?}", e); 60 | } 61 | } 62 | } 63 | assert!(outputs.is_empty()); 64 | 65 | Ok(()) 66 | } 67 | 68 | #[test_log::test(tokio::test)] 69 | async fn test_req_res() -> anyhow::Result<()> { 70 | let engine = WasmtimeBuilder::new() 71 | .with_module_bytes("baseline", MODULE_BYTES) 72 | .wasi_params(WasiParams::default()) 73 | .build()?; 74 | let host = wasmrs_host::Host::new(engine).await?; 75 | 76 | let buffer_size = 10 * 1024 * 1024; 77 | let context = host.new_context(buffer_size, buffer_size).await?; 78 | let op = context.get_export("greeting", "sayHello").unwrap(); 79 | 80 | let mbytes = Metadata::new(op).encode(); 81 | 82 | #[derive(serde::Serialize)] 83 | struct Input { 84 | message: Vec, 85 | } 86 | let message = "01234567"; 87 | 88 | let mut input = Input { message: vec![] }; 89 | let mb = 1024 * 1024; 90 | 91 | for _ in 0..mb { 92 | input.message.push(message.to_string()); 93 | } 94 | 95 | let bytes = serialize(&input).unwrap(); 96 | 97 | let payload = RawPayload::new(mbytes, bytes.into()); 98 | 99 | println!("making large request"); 100 | let num = input.message.len(); 101 | let response = context.request_response(payload.clone()).await; 102 | println!("finished large request"); 103 | match response { 104 | Ok(v) => { 105 | let bytes = v.data.unwrap(); 106 | let val: String = deserialize(&bytes).unwrap(); 107 | println!("{}", val); 108 | assert_eq!(val, format!("Hello! You sent me {} messages.", num)); 109 | } 110 | Err(e) => { 111 | panic!("Error: {:?}", e); 112 | } 113 | } 114 | 115 | Ok(()) 116 | } 117 | -------------------------------------------------------------------------------- /crates/wasmrs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasmrs" 3 | version = "0.17.0" 4 | edition = "2021" 5 | license = "Apache-2.0" 6 | description = "Base host and client implementations of the wasmRS RSocket protocol." 7 | repository = "https://github.com/wasmrs/wasmrs-rust" 8 | 9 | [features] 10 | default = [] 11 | record-frames = [ 12 | "serde", 13 | "wasmrs-frames/serde", 14 | "base64", 15 | "serde_json", 16 | "once_cell", 17 | ] 18 | dump-frames = ["record-frames"] 19 | print-frames = ["record-frames"] 20 | 21 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 22 | [dependencies] 23 | wasmrs-frames = { path = "../wasmrs-frames", version = "0.17.1" } 24 | wasmrs-codec = { path = "../wasmrs-codec", version = "0.17.0" } 25 | wasmrs-runtime = { path = "../wasmrs-runtime", version = "0.17.1" } 26 | wasmrs-rx = { path = "../wasmrs-rx", version = "0.17.0" } 27 | futures = { workspace = true, default-features = false } 28 | bytes = { workspace = true, default-features = false } 29 | parking_lot = { workspace = true, default-features = false } 30 | tracing = { workspace = true } 31 | pin-project-lite = "0.2" 32 | # For recording frames 33 | once_cell = { version = "1.8", optional = true } 34 | serde = { workspace = true, features = ["derive"], optional = true } 35 | base64 = { version = "0.21", optional = true } 36 | serde_json = { version = "1.0", optional = true } 37 | async-trait = { workspace = true } 38 | 39 | [target.'cfg(not(target_family = "wasm"))'.dependencies] 40 | tokio = { workspace = true, features = ["sync", "rt"] } 41 | dashmap = "5.4" 42 | 43 | [target.'cfg(target_family = "wasm")'.dependencies] 44 | tokio = { workspace = true, default-features = false, features = ["sync"] } 45 | crossbeam-channel = { version = "0.5" } 46 | futures-executor = { workspace = true, default-features = false, features = [ 47 | "std", 48 | ] } 49 | futures-util = { workspace = true, default-features = false, features = [ 50 | "alloc", 51 | ] } 52 | 53 | [dev-dependencies] 54 | env_logger = { workspace = true } 55 | anyhow = { version = "1.0" } 56 | tokio = { workspace = true, features = ["rt", "time", "macros"] } 57 | test-log = { workspace = true } 58 | -------------------------------------------------------------------------------- /crates/wasmrs/README.md: -------------------------------------------------------------------------------- 1 | # wasmrs 2 | 3 | WasmRS is an implementation of Reactive Streams for WebAssembly modules that allows hosts & guests to communicate via asynchronous, bidirectional streams. 4 | 5 | The `wasmrs` crate is the base implementation of the bidirectional WebAssembly socket. 6 | 7 | ## Usage 8 | 9 | See [wasmrs-guest](https://github.com/wasmrs/wasmrs-rust/blob/main/crates/wasmrs-guest/README.md), [wasmrs-host](https://github.com/wasmrs/wasmrs-rust/blob/main/crates/wasmrs-guest/README.md), and [wasmrs-wamtime](https://github.com/wasmrs/wasmrs-rust/blob/main/crates/wasmrs-guest/README.md) for examples on how to use wasmrs directly. 10 | 11 | ## More Information 12 | 13 | For more information on wasmRS, see the core [wasmrs](https://github.com/wasmrs/wasmrs-rust/blob/main/crates/wasmrs/README.md) crate. 14 | 15 | WasmRS makes heavy use of generated code from `apex` specs and generators to automate all of the boilerplate. See the [getting-started](https://github.com/WasmRS/docs/blob/main/wasmrs-rust-howto.md) for usage. 16 | 17 | ## Contributing 18 | 19 | See [CONTRIBUTING.md](https://github.com/WasmRS/wasmrs-rust/blob/main/CONTRIBUTING.md) 20 | 21 | ## License 22 | 23 | See the root [LICENSE.txt](https://github.com/WasmRS/wasmrs-rust/blob/main/LICENSE.txt) 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /crates/wasmrs/examples/decode.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let stringified_bytes: String = std::env::args().skip(1).take(1).collect(); 3 | println!("Decoding: {}", stringified_bytes); 4 | let bytes: bytes::Bytes = stringified_bytes 5 | .trim_start_matches('[') 6 | .trim_end_matches(']') 7 | .split(',') 8 | .map(|v| v.trim()) 9 | .map(|v| v.parse::().unwrap()) 10 | .collect(); 11 | let frame = wasmrs::Frame::decode(bytes).unwrap(); 12 | println!("{:#?}", frame); 13 | } 14 | -------------------------------------------------------------------------------- /crates/wasmrs/src/error.rs: -------------------------------------------------------------------------------- 1 | //! Library-specific error types and utility functions 2 | 3 | /// Error type for wasmRS RSocket errors. 4 | #[allow(missing_copy_implementations)] 5 | #[derive(Debug, Clone)] 6 | pub enum Error { 7 | /// An error associated with OperationList methods. 8 | OpList(String), 9 | /// A generic RSocket error. 10 | RSocket(u32), 11 | /// Used when the receiver for a [crate::WasmSocket] has already been taken. 12 | ReceiverAlreadyGone, 13 | /// Variant used when a frame is treated as the wrong type. 14 | WrongType, 15 | /// Could not convert string from passed bytes. 16 | StringConversion, 17 | /// [crate::Metadata] not found in [crate::Payload] 18 | MetadataNotFound, 19 | /// Error encoding or decoding an RSocket frame 20 | Frame(wasmrs_frames::Error), 21 | /// Error associate with recording frames during debug 22 | Record(String), 23 | /// An error calling guest methods. 24 | GuestCall(String), 25 | } 26 | 27 | impl std::error::Error for Error {} 28 | impl std::fmt::Display for Error { 29 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 30 | match self { 31 | Error::RSocket(code) => f.write_str((Into::::into(*code)).to_string().as_str()), 32 | Error::OpList(msg) => f.write_str(msg), 33 | Error::ReceiverAlreadyGone => f.write_str("Received already taken"), 34 | Error::WrongType => f.write_str("Tried to decode frame with wrong frame decoder"), 35 | Error::StringConversion => f.write_str("Could not read string bytes"), 36 | Error::GuestCall(e) => f.write_str(e.as_str()), 37 | Error::MetadataNotFound => f.write_str("No metadata found"), 38 | Error::Frame(e) => f.write_str(e.to_string().as_str()), 39 | Error::Record(e) => f.write_str(e.as_str()), 40 | } 41 | } 42 | } 43 | 44 | impl From for Error { 45 | fn from(value: wasmrs_frames::Error) -> Self { 46 | Error::Frame(value) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /crates/wasmrs/src/handlers.rs: -------------------------------------------------------------------------------- 1 | use bytes::Bytes; 2 | use wasmrs_frames::{Metadata, PayloadError, RawPayload}; 3 | use wasmrs_runtime::RtRc; 4 | 5 | use crate::operations::OperationList; 6 | use crate::{BoxFlux, BoxMono}; 7 | 8 | /// An alias to [Box] 9 | pub type GenericError = Box; 10 | /// An alias for a [Vec<(String, String, RtRc)>] 11 | pub type OperationMap = Vec<(String, String, RtRc)>; 12 | /// An alias for the function that creates the output for a task. 13 | pub type OperationHandler = Box Result + Send + Sync>; 14 | 15 | /// An alias for [Mono] 16 | pub type IncomingMono = BoxMono; 17 | /// An alias for [Mono] 18 | pub type OutgoingMono = BoxMono; 19 | /// An alias for [FluxReceiver] 20 | pub type IncomingStream = BoxFlux; 21 | /// An alias for [FluxReceiver] 22 | pub type OutgoingStream = BoxFlux; 23 | 24 | #[allow(missing_debug_implementations)] 25 | #[derive(Debug)] 26 | /// A [Payload] with pre-parsed [Metadata]. 27 | pub struct Payload { 28 | /// The parsed [Metadata]. 29 | pub metadata: Metadata, 30 | /// The raw data bytes. 31 | pub data: Bytes, 32 | } 33 | 34 | impl Payload { 35 | /// Create a new [ParsedPayload] from the given [Metadata] and [Bytes]. 36 | pub fn new(metadata: Metadata, data: Bytes) -> Self { 37 | Self { metadata, data } 38 | } 39 | } 40 | 41 | impl TryFrom for Payload { 42 | type Error = crate::Error; 43 | 44 | fn try_from(mut value: RawPayload) -> Result { 45 | Ok(Payload { 46 | metadata: value.parse_metadata()?, 47 | data: value.data.unwrap_or_default(), 48 | }) 49 | } 50 | } 51 | 52 | #[derive(Default)] 53 | /// A list of all the operations exported by a wasmrs implementer. 54 | pub struct Handlers { 55 | op_list: OperationList, 56 | request_response_handlers: OperationMap>, 57 | request_stream_handlers: OperationMap>, 58 | request_channel_handlers: OperationMap>, 59 | request_fnf_handlers: OperationMap>, 60 | } 61 | 62 | impl std::fmt::Debug for Handlers { 63 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 64 | f.debug_struct("Handlers").field("op_list", &self.op_list).finish() 65 | } 66 | } 67 | 68 | impl Handlers { 69 | /// Get the operation list. 70 | pub fn op_list(&self) -> &OperationList { 71 | &self.op_list 72 | } 73 | 74 | /// Register a Request/Response style handler on the host. 75 | pub fn register_request_response( 76 | &mut self, 77 | ns: impl AsRef, 78 | op: impl AsRef, 79 | handler: OperationHandler, 80 | ) -> usize { 81 | let list = &mut self.request_response_handlers; 82 | list.push((ns.as_ref().to_owned(), op.as_ref().to_owned(), RtRc::new(handler))); 83 | let index = list.len() - 1; 84 | self 85 | .op_list 86 | .add_export(index as _, crate::OperationType::RequestResponse, ns, op); 87 | index 88 | } 89 | 90 | /// Register a Request/Response style handler on the host. 91 | pub fn register_request_stream( 92 | &mut self, 93 | ns: impl AsRef, 94 | op: impl AsRef, 95 | handler: OperationHandler, 96 | ) -> usize { 97 | let list = &mut self.request_stream_handlers; 98 | list.push((ns.as_ref().to_owned(), op.as_ref().to_owned(), RtRc::new(handler))); 99 | let index = list.len() - 1; 100 | self 101 | .op_list 102 | .add_export(index as _, crate::OperationType::RequestStream, ns, op); 103 | index 104 | } 105 | 106 | /// Register a Request/Response style handler on the host. 107 | pub fn register_request_channel( 108 | &mut self, 109 | ns: impl AsRef, 110 | op: impl AsRef, 111 | handler: OperationHandler, 112 | ) -> usize { 113 | let list = &mut self.request_channel_handlers; 114 | list.push((ns.as_ref().to_owned(), op.as_ref().to_owned(), RtRc::new(handler))); 115 | let index = list.len() - 1; 116 | self 117 | .op_list 118 | .add_export(index as _, crate::OperationType::RequestChannel, ns, op); 119 | index 120 | } 121 | 122 | /// Register a Request/Response style handler on the host. 123 | pub fn register_fire_and_forget( 124 | &mut self, 125 | ns: impl AsRef, 126 | op: impl AsRef, 127 | handler: OperationHandler, 128 | ) -> usize { 129 | let list = &mut self.request_fnf_handlers; 130 | list.push((ns.as_ref().to_owned(), op.as_ref().to_owned(), RtRc::new(handler))); 131 | let index = list.len() - 1; 132 | self 133 | .op_list 134 | .add_export(index as _, crate::OperationType::RequestFnF, ns, op); 135 | index 136 | } 137 | 138 | #[must_use] 139 | /// Get a Request/Response handler by id. 140 | pub fn get_request_response_handler(&self, index: u32) -> Option>> { 141 | let a = self 142 | .request_response_handlers 143 | .get(index as usize) 144 | .map(|(_, _, h)| h.clone()); 145 | a 146 | } 147 | #[must_use] 148 | /// Get a Request/Response handler by id. 149 | pub fn get_request_stream_handler(&self, index: u32) -> Option>> { 150 | let a = self 151 | .request_stream_handlers 152 | .get(index as usize) 153 | .map(|(_, _, h)| h.clone()); 154 | a 155 | } 156 | #[must_use] 157 | /// Get a Request/Response handler by id. 158 | pub fn get_request_channel_handler( 159 | &self, 160 | index: u32, 161 | ) -> Option>> { 162 | let a = self 163 | .request_channel_handlers 164 | .get(index as usize) 165 | .map(|(_, _, h)| h.clone()); 166 | a 167 | } 168 | #[must_use] 169 | /// Get a Request/Response handler by id. 170 | pub fn get_fnf_handler(&self, index: u32) -> Option>> { 171 | let a = self.request_fnf_handlers.get(index as usize).map(|(_, _, h)| h.clone()); 172 | a 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /crates/wasmrs/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny( 2 | clippy::expect_used, 3 | clippy::explicit_deref_methods, 4 | clippy::option_if_let_else, 5 | clippy::await_holding_lock, 6 | clippy::cloned_instead_of_copied, 7 | clippy::explicit_into_iter_loop, 8 | clippy::flat_map_option, 9 | clippy::fn_params_excessive_bools, 10 | clippy::implicit_clone, 11 | clippy::inefficient_to_string, 12 | clippy::large_types_passed_by_value, 13 | clippy::manual_ok_or, 14 | clippy::map_flatten, 15 | clippy::map_unwrap_or, 16 | clippy::must_use_candidate, 17 | clippy::needless_for_each, 18 | clippy::needless_pass_by_value, 19 | clippy::option_option, 20 | clippy::redundant_else, 21 | clippy::semicolon_if_nothing_returned, 22 | clippy::too_many_lines, 23 | clippy::trivially_copy_pass_by_ref, 24 | clippy::unnested_or_patterns, 25 | clippy::future_not_send, 26 | clippy::useless_let_if_seq, 27 | clippy::str_to_string, 28 | clippy::inherent_to_string, 29 | clippy::let_and_return, 30 | clippy::string_to_string, 31 | clippy::try_err, 32 | clippy::unused_async, 33 | clippy::missing_enforced_import_renames, 34 | clippy::nonstandard_macro_braces, 35 | clippy::rc_mutex, 36 | clippy::unwrap_or_else_default, 37 | clippy::manual_split_once, 38 | clippy::derivable_impls, 39 | clippy::needless_option_as_deref, 40 | clippy::iter_not_returning_iterator, 41 | clippy::same_name_method, 42 | clippy::manual_assert, 43 | clippy::non_send_fields_in_send_ty, 44 | clippy::equatable_if_let, 45 | bad_style, 46 | clashing_extern_declarations, 47 | dead_code, 48 | deprecated, 49 | explicit_outlives_requirements, 50 | improper_ctypes, 51 | invalid_value, 52 | missing_copy_implementations, 53 | missing_debug_implementations, 54 | mutable_transmutes, 55 | no_mangle_generic_items, 56 | non_shorthand_field_patterns, 57 | overflowing_literals, 58 | path_statements, 59 | patterns_in_fns_without_body, 60 | private_in_public, 61 | trivial_bounds, 62 | trivial_casts, 63 | trivial_numeric_casts, 64 | type_alias_bounds, 65 | unconditional_recursion, 66 | unreachable_pub, 67 | unsafe_code, 68 | unstable_features, 69 | unused, 70 | unused_allocation, 71 | unused_comparisons, 72 | unused_import_braces, 73 | unused_parens, 74 | unused_qualifications, 75 | while_true, 76 | missing_docs 77 | )] 78 | #![doc = include_str!("../README.md")] 79 | 80 | mod error; 81 | mod handlers; 82 | mod operations; 83 | mod socket; 84 | /// Utility functions related to frames. 85 | pub mod util; 86 | use futures::Stream; 87 | pub use handlers::*; 88 | 89 | #[macro_use] 90 | extern crate tracing; 91 | 92 | pub use error::Error; 93 | pub use operations::{Operation, OperationList, OperationType}; 94 | pub use socket::{BufferState, SocketSide, WasmSocket}; 95 | pub use wasmrs_frames::{ErrorCode, Frame, Metadata, RawPayload}; 96 | 97 | #[cfg(feature = "record-frames")] 98 | mod record; 99 | #[cfg(feature = "record-frames")] 100 | pub use record::{get_records, FrameRecord, FRAME_RECORDS}; 101 | use wasmrs_runtime::{ConditionallySend, ConditionallySendSync}; 102 | pub use wasmrs_rx::{BoxFlux, BoxMono, Flux}; 103 | 104 | type Result = std::result::Result; 105 | 106 | pub use wasmrs_frames::PayloadError; 107 | 108 | /// A trait that defines the interface for a wasmRS module host. 109 | #[async_trait::async_trait] 110 | pub trait ModuleHost: Sync + Send { 111 | /// Write a frame to a wasmRS module's memory buffer. 112 | async fn write_frame(&self, frame: Frame) -> Result<()>; 113 | 114 | /// Method called when there is a critical failure writing to a stream. 115 | async fn on_error(&self, stream_id: u32) -> Result<()>; 116 | 117 | /// Get an imported operation's index. 118 | fn get_export(&self, namespace: &str, operation: &str) -> Option; 119 | 120 | /// Get an exported operation's index. 121 | fn get_import(&self, namespace: &str, operation: &str) -> Option; 122 | 123 | /// Get a cloned operation list. 124 | fn get_operation_list(&self) -> OperationList; 125 | } 126 | 127 | /// A trait for an RSocket client/server (host/guest). 128 | pub trait RSocket: ConditionallySendSync { 129 | /// Fire and Forget interaction model of RSocket. 130 | fn fire_and_forget(&self, payload: RawPayload) -> BoxMono<(), PayloadError>; 131 | /// Request-Response interaction model of RSocket. 132 | fn request_response(&self, payload: RawPayload) -> BoxMono; 133 | /// Request-Stream interaction model of RSocket. 134 | fn request_stream(&self, payload: RawPayload) -> BoxFlux; 135 | /// Request-Channel interaction model of RSocket. 136 | fn request_channel< 137 | T: Stream> + ConditionallySend + Unpin + 'static, 138 | >( 139 | &self, 140 | stream: T, 141 | ) -> BoxFlux; 142 | } 143 | -------------------------------------------------------------------------------- /crates/wasmrs/src/record.rs: -------------------------------------------------------------------------------- 1 | use wasmrs_frames::Frame; 2 | 3 | use crate::SocketSide; 4 | 5 | #[derive(Debug, Default)] 6 | /// A struct used to record frames sent & received by a [WasmSocket]. 7 | pub struct FrameRecords { 8 | pub frames: Vec, 9 | } 10 | 11 | impl FrameRecords { 12 | fn push(&mut self, record: FrameRecord) { 13 | #[cfg(feature = "print-frames")] 14 | print_record(record.clone(), self.frames.len()); 15 | self.frames.push(record.clone()); 16 | #[cfg(feature = "dump-frames")] 17 | dump_record(record, self.frames.len(), std::path::Path::new("frames")).unwrap(); 18 | } 19 | } 20 | 21 | #[derive(Debug, Clone)] 22 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 23 | #[cfg_attr(feature = "serde", serde(tag = "dir"))] 24 | /// A serialized frame sent or received by a [WasmSocket]. 25 | pub enum FrameRecord { 26 | /// An incoming frame. 27 | Incoming { 28 | /// Whether the frame was received by the host or the guest. 29 | side: SocketSide, 30 | /// The stream ID the frame belongs to. 31 | stream_id: u32, 32 | /// The frame encoded as base64. 33 | frame: String, 34 | }, 35 | /// An outgoing frame. 36 | Outgoing { 37 | /// Whether the frame was sent by the host or the guest. 38 | side: SocketSide, 39 | /// The stream ID the frame belongs to. 40 | stream_id: u32, 41 | /// The frame encoded as base64. 42 | frame: String, 43 | }, 44 | } 45 | 46 | impl FrameRecord { 47 | /// True if the frame was sent out of the socket. 48 | #[must_use] 49 | pub fn is_outgoing(&self) -> bool { 50 | matches!(self, FrameRecord::Outgoing { .. }) 51 | } 52 | 53 | /// True if the frame was received from of the socket. 54 | #[must_use] 55 | pub fn is_incoming(&self) -> bool { 56 | matches!(self, FrameRecord::Outgoing { .. }) 57 | } 58 | 59 | #[cfg(feature = "dump-frames")] 60 | fn dir(&self) -> &str { 61 | match self { 62 | FrameRecord::Incoming { .. } => "in", 63 | FrameRecord::Outgoing { .. } => "out", 64 | } 65 | } 66 | 67 | #[cfg(feature = "dump-frames")] 68 | fn stream_id(&self) -> u32 { 69 | match self { 70 | FrameRecord::Incoming { stream_id, .. } => *stream_id, 71 | FrameRecord::Outgoing { stream_id, .. } => *stream_id, 72 | } 73 | } 74 | 75 | /// Decode the frame from the base64-encoded string. 76 | pub fn frame(&self) -> Result { 77 | let frame = match self { 78 | FrameRecord::Incoming { frame, .. } => frame, 79 | FrameRecord::Outgoing { frame, .. } => frame, 80 | }; 81 | use base64::Engine; 82 | wasmrs_frames::Frame::decode( 83 | base64::engine::general_purpose::STANDARD 84 | .decode(frame) 85 | .map_err(|e| crate::Error::Record(e.to_string()))? 86 | .into(), 87 | ) 88 | .map_err(|(_id, e)| crate::Error::Record(e.to_string())) 89 | } 90 | 91 | /// The base64 representation of the frame. 92 | #[must_use] 93 | pub fn encoded(&self) -> &str { 94 | match self { 95 | FrameRecord::Incoming { frame, .. } => frame, 96 | FrameRecord::Outgoing { frame, .. } => frame, 97 | } 98 | } 99 | 100 | #[cfg(feature = "dump-frames")] 101 | fn side(&self) -> String { 102 | match self { 103 | FrameRecord::Incoming { side, .. } => side.to_string(), 104 | FrameRecord::Outgoing { side, .. } => side.to_string(), 105 | } 106 | } 107 | } 108 | 109 | impl std::fmt::Display for FrameRecord { 110 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 111 | let (sid, side) = match self { 112 | FrameRecord::Incoming { side, stream_id, .. } => (stream_id, side), 113 | FrameRecord::Outgoing { side, stream_id, .. } => (stream_id, side), 114 | }; 115 | f.write_str("s")?; 116 | sid.fmt(f)?; 117 | f.write_str("-")?; 118 | side.fmt(f)?; 119 | f.write_str("-out") 120 | } 121 | } 122 | 123 | /// A record of all frames sent & received by a [crate::WasmSocket]. 124 | pub static FRAME_RECORDS: once_cell::sync::Lazy> = 125 | once_cell::sync::Lazy::new(|| parking_lot::Mutex::new(FrameRecords::default())); 126 | 127 | pub(crate) fn write_outgoing_record(side: SocketSide, frame: Frame) { 128 | use base64::Engine; 129 | FRAME_RECORDS.lock().push(FrameRecord::Outgoing { 130 | side, 131 | stream_id: frame.stream_id(), 132 | frame: base64::engine::general_purpose::STANDARD.encode(frame.encode()), 133 | }); 134 | } 135 | 136 | pub(crate) fn write_incoming_record(side: SocketSide, frame: Frame) { 137 | use base64::Engine; 138 | FRAME_RECORDS.lock().push(FrameRecord::Incoming { 139 | side, 140 | stream_id: frame.stream_id(), 141 | frame: base64::engine::general_purpose::STANDARD.encode(frame.encode()), 142 | }); 143 | } 144 | 145 | #[cfg(feature = "dump-frames")] 146 | fn dump_record(record: FrameRecord, i: usize, dir: &std::path::Path) -> Result<(), crate::Error> { 147 | { 148 | use std::fs::File; 149 | use std::io::Write; 150 | 151 | if std::fs::read_dir(dir).is_err() { 152 | std::fs::create_dir(dir).map_err(|e| crate::Error::Record(e.to_string()))?; 153 | } 154 | 155 | let mut file = File::create(dir.join(format!( 156 | "s{}-n{:03}-{}-{}.frame", 157 | record.stream_id(), 158 | i, 159 | record.side(), 160 | record.dir() 161 | ))) 162 | .map_err(|e| crate::Error::Record(e.to_string()))?; 163 | let json = serde_json::to_string(&record).unwrap(); 164 | 165 | file 166 | .write_all(json.as_bytes()) 167 | .map_err(|e| crate::Error::Record(e.to_string()))?; 168 | } 169 | Ok(()) 170 | } 171 | 172 | /// Get the recorded frames. 173 | pub fn get_records() -> Vec { 174 | FRAME_RECORDS.lock().frames.drain(..).collect() 175 | } 176 | 177 | #[cfg(feature = "print-frames")] 178 | fn print_record(record: FrameRecord, i: usize) { 179 | { 180 | let json = serde_json::to_string(&record).unwrap(); 181 | 182 | println!("{}", json); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /crates/wasmrs/src/socket/buffer.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::{AtomicU32, Ordering}; 2 | 3 | /// The implementation of a WasmRS buffer where frames are written to and read from. 4 | pub struct BufferState { 5 | size: AtomicU32, 6 | start: AtomicU32, 7 | } 8 | 9 | impl Default for BufferState { 10 | fn default() -> Self { 11 | Self { 12 | size: AtomicU32::new(4092), 13 | start: Default::default(), 14 | } 15 | } 16 | } 17 | 18 | impl BufferState { 19 | /// Get the size of the buffer. 20 | pub fn get_size(&self) -> u32 { 21 | self.size.load(Ordering::SeqCst) 22 | } 23 | 24 | /// Change the size of the buffer. 25 | pub fn update_size(&self, size: u32) { 26 | self.size.store(size, Ordering::SeqCst); 27 | } 28 | 29 | /// Get the start location of the buffer. 30 | pub fn get_start(&self) -> u32 { 31 | self.start.load(Ordering::SeqCst) 32 | } 33 | 34 | /// Update the start position of thebuffer. 35 | pub fn update_start(&self, position: u32) { 36 | self.start.store(position, Ordering::SeqCst); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /crates/wasmrs/src/socket/responder.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use futures::{FutureExt, Stream, StreamExt}; 4 | use parking_lot::RwLock; 5 | use wasmrs_runtime::ConditionallySend; 6 | 7 | use crate::{BoxFlux, BoxMono, PayloadError, RSocket, RawPayload}; 8 | 9 | pub(crate) struct Responder { 10 | inner: Arc>, 11 | } 12 | 13 | impl Clone for Responder { 14 | fn clone(&self) -> Self { 15 | Self { 16 | inner: self.inner.clone(), 17 | } 18 | } 19 | } 20 | 21 | impl Responder { 22 | pub(crate) fn new(rsocket: T) -> Responder { 23 | Responder { 24 | inner: Arc::new(RwLock::new(rsocket)), 25 | } 26 | } 27 | } 28 | 29 | impl RSocket for Responder { 30 | fn fire_and_forget(&self, req: RawPayload) -> BoxMono<(), PayloadError> { 31 | let inner = self.inner.read(); 32 | (*inner).fire_and_forget(req) 33 | } 34 | 35 | fn request_response(&self, req: RawPayload) -> BoxMono { 36 | let inner = self.inner.read(); 37 | (*inner).request_response(req) 38 | } 39 | 40 | fn request_stream(&self, req: RawPayload) -> BoxFlux { 41 | let inner = self.inner.clone(); 42 | let r = inner.read(); 43 | (*r).request_stream(req) 44 | } 45 | 46 | fn request_channel> + ConditionallySend + Unpin + 'static>( 47 | &self, 48 | stream: S, 49 | ) -> BoxFlux { 50 | let inner = self.inner.clone(); 51 | let r = inner.read(); 52 | (*r).request_channel(stream) 53 | } 54 | } 55 | #[derive(Clone)] 56 | pub(crate) struct EmptyRSocket; 57 | 58 | impl RSocket for EmptyRSocket { 59 | fn fire_and_forget(&self, _req: RawPayload) -> BoxMono<(), PayloadError> { 60 | futures::future::ready(Err(PayloadError::application_error("Unimplemented", None))).boxed() 61 | } 62 | 63 | fn request_response(&self, _req: RawPayload) -> BoxMono { 64 | futures::future::ready(Err(PayloadError::application_error("Unimplemented", None))).boxed() 65 | } 66 | 67 | fn request_stream(&self, _req: RawPayload) -> BoxFlux { 68 | futures::stream::iter([Err(PayloadError::application_error("Unimplemented", None))]).boxed() 69 | } 70 | 71 | fn request_channel>>( 72 | &self, 73 | _reqs: T, 74 | ) -> BoxFlux { 75 | futures::stream::iter([Err(PayloadError::application_error("Unimplemented", None))]).boxed() 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /crates/wasmrs/src/util.rs: -------------------------------------------------------------------------------- 1 | use std::io::Read; 2 | 3 | use bytes::{BufMut, Bytes, BytesMut}; 4 | 5 | #[must_use] 6 | pub(crate) fn from_u32_bytes(bytes: &[u8]) -> u32 { 7 | assert!(bytes.len() == 4, "Need 4 bytes to convert to u32"); 8 | let mut num_parts: [u8; 4] = Default::default(); 9 | 10 | num_parts[0..4].copy_from_slice(bytes); 11 | 12 | u32::from_be_bytes(num_parts) 13 | } 14 | 15 | #[must_use] 16 | pub(crate) fn from_u16_bytes(bytes: &[u8]) -> u16 { 17 | assert!(bytes.len() == 2, "Need two bytes to convert to u16"); 18 | let mut num_parts: [u8; 2] = Default::default(); 19 | 20 | num_parts[0..2].copy_from_slice(bytes); 21 | 22 | u16::from_be_bytes(num_parts) 23 | } 24 | 25 | #[must_use] 26 | pub(crate) fn from_u24_bytes(bytes: &[u8]) -> u32 { 27 | assert!(bytes.len() == 3, "Need three bytes to convert to u24"); 28 | let mut num_parts: [u8; 4] = Default::default(); 29 | 30 | num_parts[1..4].copy_from_slice(bytes); 31 | 32 | u32::from_be_bytes(num_parts) 33 | } 34 | 35 | #[must_use] 36 | /// Convert a [u32] to a `u24` represented in bytes. 37 | pub fn to_u24_bytes(num: u32) -> Bytes { 38 | let mut num_parts = BytesMut::with_capacity(3); 39 | 40 | num_parts.put(&num.to_be_bytes()[1..4]); 41 | 42 | num_parts.freeze() 43 | } 44 | 45 | /// Read a frame from a buffer. 46 | pub fn read_frame(mut buf: impl Read) -> std::io::Result { 47 | let mut len_bytes = [0u8; 3]; 48 | buf.read_exact(&mut len_bytes)?; 49 | let len = from_u24_bytes(&len_bytes); 50 | 51 | let mut frame = vec![0; len as usize]; 52 | buf.read_exact(&mut frame)?; 53 | Ok(frame.into()) 54 | } 55 | 56 | #[cfg(test)] 57 | mod test { 58 | use anyhow::Result; 59 | 60 | use super::read_frame; 61 | 62 | #[test] 63 | fn test_read_frame() -> Result<()> { 64 | let mut buf: &[u8] = &[0, 0, 4, 1, 2, 3, 4]; 65 | let frame = read_frame(&mut buf)?; 66 | assert_eq!(frame, vec![1, 2, 3, 4]); 67 | 68 | Ok(()) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | # Apex-oriented projects have gone stale but can still 2 | # be used as regression tests. 3 | 4 | wasm: 5 | mkdir -p build 6 | just wasm/baseline/build 7 | 8 | debug: 9 | mkdir -p build 10 | just wasm/baseline/debug 11 | 12 | test: 13 | cargo test --workspace 14 | cargo test -p wasmrs-runtime --target=wasm32-unknown-unknown 15 | 16 | clean: 17 | cargo clean 18 | rm -rf build/* 19 | just wasm/baseline/clean -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = 'stable' 3 | targets = ['wasm32-unknown-unknown', 'wasm32-wasi'] 4 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | unstable_features = true 2 | enum_discrim_align_threshold = 10 3 | # fn_params_layout = "Compressed" 4 | # fn_call_width = 80 5 | # fn_single_line = true 6 | # indent_style = "Visual" 7 | # inline_attribute_width = 50 8 | max_width = 120 9 | # overflow_delimited_expr = true 10 | where_single_line = true 11 | 12 | tab_spaces = 2 13 | newline_style = "Unix" 14 | reorder_imports = true 15 | reorder_modules = true 16 | remove_nested_parens = true 17 | edition = "2021" 18 | imports_indent = "Block" 19 | imports_granularity = "Module" 20 | imports_layout = "HorizontalVertical" 21 | group_imports = "StdExternalCrate" 22 | trailing_comma = "Vertical" 23 | trailing_semicolon = true 24 | -------------------------------------------------------------------------------- /wasm/baseline/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.cargo.target": "wasm32-unknown-unknown" 3 | } 4 | -------------------------------------------------------------------------------- /wasm/baseline/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "baseline" 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 | [profile.release] 8 | strip = "symbols" 9 | codegen-units = 1 10 | debug = false 11 | lto = true 12 | opt-level = "z" 13 | panic = "abort" 14 | 15 | [lib] 16 | crate-type = ["cdylib"] 17 | 18 | [dependencies] 19 | wasmrs-guest = { path = "../../crates/wasmrs-guest" } 20 | serde = { version = "1", features = ["derive"] } 21 | -------------------------------------------------------------------------------- /wasm/baseline/justfile: -------------------------------------------------------------------------------- 1 | build: 2 | cargo build --release --target=wasm32-unknown-unknown 3 | cp ./target/wasm32-unknown-unknown/release/*.wasm ../../build/ 4 | debug: 5 | cargo build --target=wasm32-wasi 6 | cp ./target/wasm32-wasi/debug/*.wasm ../../build/ 7 | clean: 8 | cargo clean 9 | -------------------------------------------------------------------------------- /wasm/baseline/src/lib.rs: -------------------------------------------------------------------------------- 1 | use guest::*; 2 | use wasmrs_guest as guest; 3 | 4 | #[no_mangle] 5 | extern "C" fn __wasmrs_init(guest_buffer_size: u32, host_buffer_size: u32, max_host_frame_len: u32) { 6 | guest::init(guest_buffer_size, host_buffer_size, max_host_frame_len); 7 | 8 | guest::register_request_response("greeting", "sayHello", Box::new(request_response)); 9 | guest::register_request_stream("echo", "chars", Box::new(request_stream)); 10 | guest::register_request_channel("echo", "reverse", Box::new(request_channel)); 11 | guest::register_request_channel("test", "callback", Box::new(channel_callback)); 12 | guest::add_import(0, OperationType::RequestChannel, "test", "echo"); 13 | } 14 | 15 | fn request_response(input: BoxMono) -> Result, GenericError> { 16 | #[derive(serde::Deserialize)] 17 | struct Input { 18 | message: Vec, 19 | } 20 | Ok( 21 | Mono::from_future(async move { 22 | let input = deserialize::(&input.await.unwrap().data).unwrap(); 23 | let mut output = "Hello! You sent me ".to_owned(); 24 | output.push_str(&input.message.len().to_string()); 25 | output.push_str(" messages."); 26 | Ok(RawPayload::new_data(None, Some(serialize(&output).unwrap().into()))) 27 | }) 28 | .boxed(), 29 | ) 30 | } 31 | 32 | fn request_stream(input: BoxMono) -> Result, GenericError> { 33 | let channel = FluxChannel::::new(); 34 | let rx = channel.take_rx().unwrap(); 35 | spawn("request_stream", async move { 36 | let input = deserialize::(&input.await.unwrap().data).unwrap(); 37 | for char in input.chars() { 38 | channel 39 | .send(RawPayload::new_data(None, Some(serialize(&char).unwrap().into()))) 40 | .unwrap(); 41 | } 42 | }); 43 | 44 | Ok(rx.boxed()) 45 | } 46 | 47 | fn request_channel( 48 | mut input: BoxFlux, 49 | ) -> Result, GenericError> { 50 | let channel = FluxChannel::::new(); 51 | let rx = channel.take_rx().unwrap(); 52 | spawn("request_channel", async move { 53 | while let Some(payload) = input.next().await { 54 | if let Err(e) = payload { 55 | println!("{}", e); 56 | continue; 57 | } 58 | let payload = payload.unwrap(); 59 | let input = deserialize::(&payload.data).unwrap(); 60 | let output: String = input.chars().rev().collect(); 61 | if let Err(e) = channel.send(RawPayload::new_data(None, Some(serialize(&output).unwrap().into()))) { 62 | println!("{}", e); 63 | } 64 | } 65 | }); 66 | 67 | Ok(rx.boxed()) 68 | } 69 | 70 | fn channel_callback( 71 | mut input: BoxFlux, 72 | ) -> Result, GenericError> { 73 | let (job_tx, job_rx) = FluxChannel::::new_parts(); 74 | let (host_tx, host_rx) = FluxChannel::::new_parts(); 75 | let mut host_stream = Host::default().request_channel(Box::pin(host_rx)); 76 | spawn("channel_callback", async move { 77 | println!("waiting for input..."); 78 | while let Some(payload) = input.next().await { 79 | if let Err(e) = payload { 80 | println!("{}", e); 81 | continue; 82 | } 83 | let payload = payload.unwrap(); 84 | println!("got payload: {:?}", payload); 85 | let input = deserialize::(&payload.data).unwrap(); 86 | let md = Metadata::new(0); 87 | host_tx 88 | .send(RawPayload::new_data( 89 | Some(md.encode()), 90 | Some(serialize(&input).unwrap().into()), 91 | )) 92 | .unwrap(); 93 | } 94 | }); 95 | spawn("channel_callback", async move { 96 | println!("waiting for host output..."); 97 | while let Some(Ok(payload)) = host_stream.next().await { 98 | let output = deserialize::(&payload.data.unwrap()).unwrap(); 99 | 100 | println!("sending final output..."); 101 | job_tx 102 | .send(RawPayload::new_data(None, Some(serialize(&output).unwrap().into()))) 103 | .unwrap(); 104 | } 105 | }); 106 | 107 | Ok(job_rx.boxed()) 108 | } 109 | --------------------------------------------------------------------------------