kobako 0.19.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +127 -0
- data/Cargo.lock +15 -5
- data/README.md +96 -44
- data/ROADMAP.md +1 -1
- data/crates/kobako-runtime/CHANGELOG.md +44 -0
- data/crates/kobako-runtime/Cargo.toml +8 -1
- data/crates/kobako-runtime/README.md +1 -1
- data/crates/kobako-runtime/src/dispatch.rs +17 -11
- data/crates/kobako-runtime/src/error.rs +38 -6
- data/crates/kobako-runtime/src/lib.rs +13 -2
- data/crates/kobako-runtime/src/profile.rs +8 -0
- data/crates/kobako-runtime/src/runtime.rs +7 -2
- data/crates/kobako-runtime/src/snapshot.rs +13 -4
- data/crates/kobako-runtime/src/yielder.rs +4 -4
- data/crates/kobako-transport/CHANGELOG.md +31 -0
- data/crates/kobako-transport/Cargo.toml +27 -0
- data/crates/kobako-transport/README.md +43 -0
- data/crates/kobako-transport/src/abi.rs +89 -0
- data/crates/kobako-transport/src/envelope/bytes.rs +234 -0
- data/crates/kobako-transport/src/envelope/call.rs +222 -0
- data/crates/kobako-transport/src/envelope/error_record.rs +101 -0
- data/crates/kobako-transport/src/envelope/fault.rs +171 -0
- data/crates/kobako-transport/src/envelope/invocation_frames.rs +232 -0
- data/crates/kobako-transport/src/envelope/outcome.rs +261 -0
- data/crates/kobako-transport/src/envelope/reply.rs +240 -0
- data/crates/kobako-transport/src/envelope/run.rs +85 -0
- data/crates/kobako-transport/src/envelope.rs +73 -0
- data/crates/kobako-transport/src/lib.rs +21 -0
- data/crates/kobako-wasmtime/CHANGELOG.md +30 -0
- data/crates/kobako-wasmtime/Cargo.toml +5 -2
- data/crates/kobako-wasmtime/README.md +1 -1
- data/crates/kobako-wasmtime/src/config.rs +19 -12
- data/crates/kobako-wasmtime/src/dispatch.rs +15 -8
- data/crates/kobako-wasmtime/src/driver.rs +8 -27
- data/crates/kobako-wasmtime/src/frames.rs +11 -9
- data/crates/kobako-wasmtime/src/guest_mem.rs +7 -15
- data/crates/kobako-wasmtime/src/instance_pre.rs +2 -2
- data/crates/kobako-wasmtime/src/invocation.rs +1 -1
- data/crates/kobako-wasmtime/src/lib.rs +1 -1
- data/data/kobako.wasm +0 -0
- data/ext/kobako/Cargo.toml +7 -2
- data/ext/kobako/src/runtime/bridge.rs +103 -40
- data/ext/kobako/src/runtime/errors.rs +13 -5
- data/ext/kobako/src/runtime/gvl.rs +114 -0
- data/ext/kobako/src/runtime.rs +307 -207
- data/lib/kobako/catalog/extensions.rb +50 -43
- data/lib/kobako/catalog/handles.rb +7 -16
- data/lib/kobako/catalog/services.rb +24 -46
- data/lib/kobako/catalog/snippets.rb +16 -33
- data/lib/kobako/codec/decoder.rb +10 -13
- data/lib/kobako/codec/encoder.rb +7 -7
- data/lib/kobako/codec/error.rb +6 -6
- data/lib/kobako/codec/ext_types.rb +12 -72
- data/lib/kobako/codec/handle_walk.rb +6 -6
- data/lib/kobako/codec/state.rb +4 -56
- data/lib/kobako/codec/utils.rb +4 -4
- data/lib/kobako/codec.rb +11 -21
- data/lib/kobako/context.rb +208 -0
- data/lib/kobako/errors.rb +59 -16
- data/lib/kobako/execution.rb +52 -0
- data/lib/kobako/extension.rb +24 -10
- data/lib/kobako/outcome.rb +71 -128
- data/lib/kobako/payload/arguments.rb +59 -0
- data/lib/kobako/payload.rb +19 -0
- data/lib/kobako/pool.rb +1 -7
- data/lib/kobako/sandbox.rb +51 -191
- data/lib/kobako/sandbox_options.rb +24 -3
- data/lib/kobako/snippet/binary.rb +3 -4
- data/lib/kobako/snippet/source.rb +5 -6
- data/lib/kobako/snippet.rb +2 -2
- data/lib/kobako/transport/call.rb +21 -0
- data/lib/kobako/transport/dispatcher.rb +94 -142
- data/lib/kobako/transport/error.rb +21 -5
- data/lib/kobako/transport/reflection.rb +82 -0
- data/lib/kobako/transport/run.rb +25 -33
- data/lib/kobako/transport/yielder.rb +27 -36
- data/lib/kobako/transport.rb +11 -8
- data/lib/kobako/unresolved.rb +17 -0
- data/lib/kobako/usage.rb +7 -7
- data/lib/kobako/version.rb +1 -1
- data/lib/kobako.rb +1 -0
- data/release-please-config.json +47 -2
- data/sig/kobako/catalog/extensions.rbs +4 -6
- data/sig/kobako/catalog/handles.rbs +0 -2
- data/sig/kobako/catalog/services.rbs +4 -4
- data/sig/kobako/catalog/snippets.rbs +4 -2
- data/sig/kobako/codec/error.rbs +4 -4
- data/sig/kobako/codec/ext_types.rbs +0 -7
- data/sig/kobako/codec/state.rbs +0 -4
- data/sig/kobako/codec.rbs +4 -5
- data/sig/kobako/context.rbs +36 -0
- data/sig/kobako/errors.rbs +27 -6
- data/sig/kobako/execution.rbs +24 -0
- data/sig/kobako/extension.rbs +4 -2
- data/sig/kobako/outcome.rbs +12 -13
- data/sig/kobako/payload/arguments.rbs +26 -0
- data/sig/kobako/payload.rbs +4 -0
- data/sig/kobako/pool.rbs +0 -2
- data/sig/kobako/runtime.rbs +44 -14
- data/sig/kobako/sandbox.rbs +5 -24
- data/sig/kobako/sandbox_options.rbs +9 -2
- data/sig/kobako/snippet/binary.rbs +1 -1
- data/sig/kobako/snippet/source.rbs +1 -1
- data/sig/kobako/transport/call.rbs +21 -0
- data/sig/kobako/transport/dispatcher.rbs +9 -11
- data/sig/kobako/transport/error.rbs +11 -0
- data/sig/kobako/transport/reflection.rbs +15 -0
- data/sig/kobako/transport/run.rbs +1 -1
- data/sig/kobako/transport/yielder.rbs +2 -2
- data/sig/kobako/transport.rbs +8 -2
- data/sig/kobako/unresolved.rbs +4 -0
- metadata +30 -11
- data/lib/kobako/fault.rb +0 -38
- data/lib/kobako/outcome/panic.rb +0 -42
- data/lib/kobako/transport/request.rb +0 -76
- data/lib/kobako/transport/response.rb +0 -83
- data/lib/kobako/transport/yield.rb +0 -94
- data/sig/kobako/fault.rbs +0 -19
- data/sig/kobako/outcome/panic.rbs +0 -34
- data/sig/kobako/transport/request.rbs +0 -29
- data/sig/kobako/transport/response.rbs +0 -33
- data/sig/kobako/transport/yield.rbs +0 -34
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
[package]
|
|
15
15
|
name = "kobako-runtime"
|
|
16
|
-
version = "0.
|
|
16
|
+
version = "0.13.0"
|
|
17
17
|
edition = "2021"
|
|
18
18
|
description = "Engine-neutral host runtime contract for embedding kobako Wasm guests."
|
|
19
19
|
license = "Apache-2.0"
|
|
@@ -21,3 +21,10 @@ repository = "https://github.com/elct9620/kobako"
|
|
|
21
21
|
readme = "README.md"
|
|
22
22
|
keywords = ["wasm", "mruby", "sandbox", "wasi"]
|
|
23
23
|
categories = ["wasm", "virtualization"]
|
|
24
|
+
|
|
25
|
+
# The fixed tier: the core envelope this contract routes and attributes
|
|
26
|
+
# through. The version pin rides the linked release group; the path keeps
|
|
27
|
+
# in-tree builds (and the Ruby gem, which ships both crates) resolving
|
|
28
|
+
# locally.
|
|
29
|
+
[dependencies]
|
|
30
|
+
kobako-transport = { version = "0.13.0", path = "../kobako-transport" }
|
|
@@ -1,22 +1,28 @@
|
|
|
1
1
|
//! Engine-neutral guest→host dispatch contract, free of any `magnus`
|
|
2
2
|
//! dependency.
|
|
3
3
|
//!
|
|
4
|
-
//! The
|
|
5
|
-
//!
|
|
6
|
-
//!
|
|
7
|
-
//!
|
|
4
|
+
//! The runtime decodes the core envelope and hands a handler the routed
|
|
5
|
+
//! `Call` — target, method, block flag, and the payload bytes it never
|
|
6
|
+
//! reads — then expects a `Reply` back. What the handler *is* — a Ruby
|
|
7
|
+
//! Proc, a Rust closure — is the frontend's concern; the runtime only sees
|
|
8
|
+
//! this trait. The Ruby ext conforms by bridging its dispatch Proc behind
|
|
9
|
+
//! it.
|
|
8
10
|
|
|
9
11
|
use crate::yielder::Yielder;
|
|
12
|
+
use kobako_transport::envelope::{Call, Reply};
|
|
10
13
|
|
|
11
14
|
/// Host-side handler for a guest→host dispatch.
|
|
12
15
|
///
|
|
13
|
-
/// `dispatch` receives the
|
|
16
|
+
/// `dispatch` receives the decoded Call plus a `Yielder` for re-entering
|
|
14
17
|
/// the in-flight guest when a Service method yields to a block, and returns
|
|
15
|
-
/// the
|
|
16
|
-
///
|
|
17
|
-
///
|
|
18
|
-
///
|
|
19
|
-
///
|
|
18
|
+
/// the Reply — or `None` when the handler itself failed, in which case the
|
|
19
|
+
/// runtime walks its 0-return wire-fault path. The bound handler is
|
|
20
|
+
/// contracted to fold application failures into the Reply's fault arm, so
|
|
21
|
+
/// `None` signals a contract violation (the handler raised) rather than a
|
|
22
|
+
/// normal dispatch outcome.
|
|
23
|
+
///
|
|
24
|
+
/// The `Call` borrows the buffer the runtime decoded, so a handler passes
|
|
25
|
+
/// the payload onward without copying it first.
|
|
20
26
|
pub trait DispatchHandler: Send + Sync {
|
|
21
|
-
fn dispatch(&self,
|
|
27
|
+
fn dispatch(&self, call: Call<'_>, yielder: &mut dyn Yielder) -> Option<Reply>;
|
|
22
28
|
}
|
|
@@ -13,7 +13,12 @@ use std::fmt;
|
|
|
13
13
|
/// kinds a frontend surfaces distinctly: the wall-clock cap (`Timeout`),
|
|
14
14
|
/// the linear-memory cap (`MemoryLimit`), and every other engine fault
|
|
15
15
|
/// (`Other`).
|
|
16
|
+
///
|
|
17
|
+
/// Non-exhaustive: a frontend matches this to reach its own error classes,
|
|
18
|
+
/// and a later kind must not break that match. `Other` is where an
|
|
19
|
+
/// unrecognised one belongs.
|
|
16
20
|
#[derive(Debug)]
|
|
21
|
+
#[non_exhaustive]
|
|
17
22
|
pub enum Trap {
|
|
18
23
|
Timeout(String),
|
|
19
24
|
MemoryLimit(String),
|
|
@@ -26,7 +31,11 @@ pub enum Trap {
|
|
|
26
31
|
/// could not be constructed), and `Intact` (the runtime is live but a
|
|
27
32
|
/// host-side pre-call step failed, so no discard-and-recreate recovery is
|
|
28
33
|
/// owed).
|
|
29
|
-
|
|
34
|
+
///
|
|
35
|
+
/// Non-exhaustive, as `Trap`: a frontend maps each state onto its own
|
|
36
|
+
/// class, and a later state must not break that match.
|
|
37
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
38
|
+
#[non_exhaustive]
|
|
30
39
|
pub enum SetupError {
|
|
31
40
|
ModuleNotBuilt(String),
|
|
32
41
|
Dead(String),
|
|
@@ -38,21 +47,37 @@ pub enum SetupError {
|
|
|
38
47
|
/// the run mechanics can propagate both with `?`; a frontend destructures
|
|
39
48
|
/// it back into the two channels. Faults after the guest export starts
|
|
40
49
|
/// ride in `Completion::Trap` instead, so captures and usage survive them.
|
|
50
|
+
///
|
|
51
|
+
/// Named for where it comes from — the `Err` of `Runtime::invoke` — so a
|
|
52
|
+
/// host that also has its own `Error` in scope can hold both.
|
|
53
|
+
///
|
|
54
|
+
/// Non-exhaustive, as its two channels are: a later way an invocation can
|
|
55
|
+
/// fail to start must not break the frontends already destructuring it.
|
|
41
56
|
#[derive(Debug)]
|
|
42
|
-
|
|
57
|
+
#[non_exhaustive]
|
|
58
|
+
pub enum InvokeError {
|
|
43
59
|
Trap(Trap),
|
|
44
60
|
Setup(SetupError),
|
|
45
61
|
}
|
|
46
62
|
|
|
47
|
-
impl From<Trap> for
|
|
63
|
+
impl From<Trap> for InvokeError {
|
|
48
64
|
fn from(trap: Trap) -> Self {
|
|
49
|
-
|
|
65
|
+
InvokeError::Trap(trap)
|
|
50
66
|
}
|
|
51
67
|
}
|
|
52
68
|
|
|
53
|
-
impl From<SetupError> for
|
|
69
|
+
impl From<SetupError> for InvokeError {
|
|
54
70
|
fn from(err: SetupError) -> Self {
|
|
55
|
-
|
|
71
|
+
InvokeError::Setup(err)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
impl fmt::Display for InvokeError {
|
|
76
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
77
|
+
match self {
|
|
78
|
+
InvokeError::Trap(trap) => trap.fmt(f),
|
|
79
|
+
InvokeError::Setup(err) => err.fmt(f),
|
|
80
|
+
}
|
|
56
81
|
}
|
|
57
82
|
}
|
|
58
83
|
|
|
@@ -70,3 +95,10 @@ impl fmt::Display for SetupError {
|
|
|
70
95
|
f.write_str(msg)
|
|
71
96
|
}
|
|
72
97
|
}
|
|
98
|
+
|
|
99
|
+
// Both channels sit on the seam a third-party engine implements against,
|
|
100
|
+
// where a caller wants to lift them into `Box<dyn Error>` or `anyhow` with
|
|
101
|
+
// `?`. `Trap` carries no source of its own, so neither reports one.
|
|
102
|
+
impl std::error::Error for SetupError {}
|
|
103
|
+
|
|
104
|
+
impl std::error::Error for InvokeError {}
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
//!
|
|
3
3
|
//! The surface where a wasm engine implementation and a host frontend
|
|
4
4
|
//! meet: the `Runtime` trait, the isolation `Profile` a runtime
|
|
5
|
-
//! declares, the neutral per-invocation value types, and the
|
|
6
|
-
//!
|
|
5
|
+
//! declares, the neutral per-invocation value types, and the dispatch /
|
|
6
|
+
//! yield re-entry traits a frontend supplies. The messages these carry
|
|
7
|
+
//! are `kobako-transport`'s, which this crate takes as given.
|
|
7
8
|
//! Nothing here depends on an engine or a frontend type — each engine
|
|
8
9
|
//! hides its own machinery behind `Runtime`, and each frontend maps
|
|
9
10
|
//! these shapes onto its own host-language surface at its boundary
|
|
@@ -16,3 +17,13 @@ pub mod profile;
|
|
|
16
17
|
pub mod runtime;
|
|
17
18
|
pub mod snapshot;
|
|
18
19
|
pub mod yielder;
|
|
20
|
+
|
|
21
|
+
// Every name an engine implementation writes into its own signatures, at
|
|
22
|
+
// the crate root: the module path repeats the crate name and adds nothing
|
|
23
|
+
// a reader of `kobako_runtime::Runtime` was missing.
|
|
24
|
+
pub use dispatch::DispatchHandler;
|
|
25
|
+
pub use error::{InvokeError, SetupError, Trap};
|
|
26
|
+
pub use profile::Profile;
|
|
27
|
+
pub use runtime::{Entry, Frames, Runtime};
|
|
28
|
+
pub use snapshot::{Capture, Completion, Snapshot, Usage};
|
|
29
|
+
pub use yielder::Yielder;
|
|
@@ -16,6 +16,14 @@
|
|
|
16
16
|
/// live ambient time and entropy at the WASI boundary. Ordering
|
|
17
17
|
/// follows strength (`Permissive < Hermetic`), so a floor check is a
|
|
18
18
|
/// plain comparison.
|
|
19
|
+
///
|
|
20
|
+
/// Deliberately not `#[non_exhaustive]`, unlike this crate's error
|
|
21
|
+
/// types. A wildcard arm here would let a runtime silently keep building
|
|
22
|
+
/// the posture it already knows while declaring it satisfied a rung it
|
|
23
|
+
/// never implemented; the compile error a new rung causes is what forces
|
|
24
|
+
/// each runtime to answer for it. The ordering is the same reason — a
|
|
25
|
+
/// rung inserted mid-ladder changes what every existing `>=` means, which
|
|
26
|
+
/// no wildcard could have absorbed either.
|
|
19
27
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
|
20
28
|
pub enum Profile {
|
|
21
29
|
Permissive,
|
|
@@ -10,13 +10,18 @@
|
|
|
10
10
|
use std::sync::Arc;
|
|
11
11
|
|
|
12
12
|
use crate::dispatch::DispatchHandler;
|
|
13
|
-
use crate::error::
|
|
13
|
+
use crate::error::InvokeError;
|
|
14
14
|
use crate::profile::Profile;
|
|
15
15
|
use crate::snapshot::Snapshot;
|
|
16
16
|
|
|
17
17
|
/// The per-invocation entry: a one-shot mruby source (`Eval`) or an
|
|
18
18
|
/// entrypoint-dispatch envelope (`Run`). Both ride alongside the stdin
|
|
19
19
|
/// `Frames`; `Run` additionally copies its envelope into guest memory.
|
|
20
|
+
///
|
|
21
|
+
/// Closed rather than `#[non_exhaustive]`: the arms are the ABI's
|
|
22
|
+
/// invocation exports, so the set only grows with an ABI version, and a
|
|
23
|
+
/// runtime that has not implemented the new export should fail to
|
|
24
|
+
/// compile rather than fall into a wildcard.
|
|
20
25
|
pub enum Entry<'a> {
|
|
21
26
|
Eval { source: &'a [u8] },
|
|
22
27
|
Run { envelope: &'a [u8] },
|
|
@@ -47,7 +52,7 @@ pub trait Runtime {
|
|
|
47
52
|
entry: Entry<'_>,
|
|
48
53
|
frames: Frames<'_>,
|
|
49
54
|
handler: Option<Arc<dyn DispatchHandler>>,
|
|
50
|
-
) -> Result<Snapshot,
|
|
55
|
+
) -> Result<Snapshot, InvokeError>;
|
|
51
56
|
|
|
52
57
|
/// The isolation profile this runtime provides. Deliberately
|
|
53
58
|
/// without a default: every engine states its posture explicitly,
|
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
//! completed, the two captured output channels, and the resource usage.
|
|
6
6
|
//! A `Snapshot` exists iff the guest export ran — a failure to even start
|
|
7
7
|
//! the invocation travels on the `invoke` `Err` channel instead.
|
|
8
|
+
//!
|
|
9
|
+
//! `Usage` and `Capture` are shared with the tier above rather than
|
|
10
|
+
//! translated at its boundary: what they carry is already frontend-free,
|
|
11
|
+
//! so the Rust host SDK re-exports these types themselves.
|
|
8
12
|
|
|
9
13
|
use crate::error::Trap;
|
|
10
14
|
|
|
@@ -18,10 +22,15 @@ pub struct Capture {
|
|
|
18
22
|
pub truncated: bool,
|
|
19
23
|
}
|
|
20
24
|
|
|
21
|
-
/// How the guest invocation completed: `Outcome` carries the
|
|
22
|
-
///
|
|
23
|
-
///
|
|
24
|
-
///
|
|
25
|
+
/// How the guest invocation completed: `Outcome` carries the encoded
|
|
26
|
+
/// Outcome envelope, still undecoded; `Trap` is an engine fault after the
|
|
27
|
+
/// export call started, kept as a value so the rest of the `Snapshot`
|
|
28
|
+
/// survives it.
|
|
29
|
+
///
|
|
30
|
+
/// Closed rather than `#[non_exhaustive]`: an invocation either produced
|
|
31
|
+
/// its final message or the engine stopped it, and there is no third way
|
|
32
|
+
/// for one to end. A wildcard would only hide a frontend that had not
|
|
33
|
+
/// been told otherwise.
|
|
25
34
|
pub enum Completion {
|
|
26
35
|
Outcome(Vec<u8>),
|
|
27
36
|
Trap(Trap),
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
//! During a guest→host dispatch, a Service method may yield to a guest
|
|
5
5
|
//! block. The host drives that re-entry through a `Yielder`: it ships the
|
|
6
6
|
//! yield-arguments payload into the in-flight guest and returns the
|
|
7
|
-
//!
|
|
7
|
+
//! Yield Reply bytes. What backs the re-entry — a wasmtime `Caller`, some
|
|
8
8
|
//! other engine handle — is the implementer's concern; the dispatch
|
|
9
9
|
//! contract sees only this trait.
|
|
10
10
|
|
|
@@ -13,10 +13,10 @@ use crate::error::Trap;
|
|
|
13
13
|
/// Host-initiated re-entry into the in-flight guest instance to run a
|
|
14
14
|
/// yielded block.
|
|
15
15
|
///
|
|
16
|
-
/// `
|
|
17
|
-
/// raw
|
|
16
|
+
/// `yield_to_block` ships `args` to `__kobako_yield_to_block` and returns
|
|
17
|
+
/// the raw Yield Reply bytes, or a `Trap` — surfaced through the frontend's
|
|
18
18
|
/// trap-error mapping — when the re-entry traps, the guest returns an empty
|
|
19
19
|
/// result, or a payload exceeds the 16 MiB cap.
|
|
20
20
|
pub trait Yielder {
|
|
21
|
-
fn
|
|
21
|
+
fn yield_to_block(&mut self, args: &[u8]) -> Result<Vec<u8>, Trap>;
|
|
22
22
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.13.0](https://github.com/elct9620/kobako/compare/kobako-transport-v0.12.0...kobako-transport-v0.13.0) (2026-07-29)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### ⚠ BREAKING CHANGES
|
|
7
|
+
|
|
8
|
+
* **runtime:** mark the sets that grow, and say why the closed ones do not
|
|
9
|
+
* **transport:** let every envelope spell its success arm the same way
|
|
10
|
+
* **transport:** name the core envelope's types after what they carry
|
|
11
|
+
* **wire:** carry a Reply's fault arm on the envelope
|
|
12
|
+
* **transport:** split the invocation module along the line its doc draws
|
|
13
|
+
|
|
14
|
+
### Features
|
|
15
|
+
|
|
16
|
+
* **runtime:** mark the sets that grow, and say why the closed ones do not ([1be2449](https://github.com/elct9620/kobako/commit/1be24492961e2c2a3a317f784cc2f20ae584dcbf))
|
|
17
|
+
* **transport:** add kobako-transport, one implementation of the envelope ([23f2401](https://github.com/elct9620/kobako/commit/23f240111a4c59e8f521bec911f9cf1cd7287744))
|
|
18
|
+
* **transport:** give the ABI's fixed values one definition each ([94754ec](https://github.com/elct9620/kobako/commit/94754ecb538286077b6b58d90504b458ac0e4a2c))
|
|
19
|
+
* **wire:** carry a Reply's fault arm on the envelope ([0bce850](https://github.com/elct9620/kobako/commit/0bce850b3416696e92ae6ee12c353c4c21c8e583))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Bug Fixes
|
|
23
|
+
|
|
24
|
+
* **wire:** keep the ABI at 3, which no release has shipped ([2b301b7](https://github.com/elct9620/kobako/commit/2b301b777384c0edf79426805cfb1cc08688aa95))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
### Code Refactoring
|
|
28
|
+
|
|
29
|
+
* **transport:** let every envelope spell its success arm the same way ([df595e0](https://github.com/elct9620/kobako/commit/df595e08097af9e3e0a0c5ed52a4a6f854eb21dc))
|
|
30
|
+
* **transport:** name the core envelope's types after what they carry ([b44de65](https://github.com/elct9620/kobako/commit/b44de6502953826c567d4ef8a594561479f50c1d))
|
|
31
|
+
* **transport:** split the invocation module along the line its doc draws ([4d3a2d3](https://github.com/elct9620/kobako/commit/4d3a2d3060167d71a807e203688186740fd1485b))
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# kobako-transport — the fixed tier of the kobako wire.
|
|
2
|
+
#
|
|
3
|
+
# The core envelope: the outer frame every host↔guest message rides in,
|
|
4
|
+
# carrying routing and outcome attribution and handing the payload
|
|
5
|
+
# through untouched. A host, a payload codec, and a guest are chosen
|
|
6
|
+
# independently only because this layer is the same in every assembly,
|
|
7
|
+
# so it depends on nothing and everything else depends on it.
|
|
8
|
+
#
|
|
9
|
+
# Self-contained by design: the Ruby gem ships this crate without the
|
|
10
|
+
# `crates/` workspace manifest, so no `workspace = true` inheritance
|
|
11
|
+
# fields may appear here.
|
|
12
|
+
|
|
13
|
+
[package]
|
|
14
|
+
name = "kobako-transport"
|
|
15
|
+
version = "0.13.0"
|
|
16
|
+
edition = "2021"
|
|
17
|
+
description = "The kobako core envelope — the fixed host↔guest wire frame every assembly shares."
|
|
18
|
+
license = "Apache-2.0"
|
|
19
|
+
repository = "https://github.com/elct9620/kobako"
|
|
20
|
+
readme = "README.md"
|
|
21
|
+
keywords = ["wasm", "mruby", "sandbox", "wasi"]
|
|
22
|
+
categories = ["wasm", "encoding"]
|
|
23
|
+
|
|
24
|
+
# No dependencies, and none may be added: a tier every other tier
|
|
25
|
+
# depends on cannot carry a choice any of them might want to make
|
|
26
|
+
# differently.
|
|
27
|
+
[dependencies]
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# kobako-transport
|
|
2
|
+
|
|
3
|
+
The fixed tier of the [kobako](https://github.com/elct9620/kobako) wire —
|
|
4
|
+
an in-process Wasm sandbox for running untrusted mruby scripts.
|
|
5
|
+
|
|
6
|
+
kobako is assembled from three parts chosen independently: a host, a
|
|
7
|
+
payload codec, and a guest. That works only because two things are the
|
|
8
|
+
same in every assembly. This crate is both of them, and nothing else:
|
|
9
|
+
|
|
10
|
+
- `envelope` — the **core envelope**, the outer frame each message rides
|
|
11
|
+
in: `Call` / `Reply` / `YieldReply` for a dispatch round-trip,
|
|
12
|
+
`Outcome` / `Panic` for how an invocation ended, `Run` / `Preamble` /
|
|
13
|
+
`Snippets` for what an invocation is handed, and the `ErrorRecord` a
|
|
14
|
+
guest failure carries
|
|
15
|
+
- `abi` — the values a host and a guest must already agree on to
|
|
16
|
+
exchange a byte: the version, the packed `(ptr, len)` return layout,
|
|
17
|
+
the invocation-channel frame prefix, and the message size cap
|
|
18
|
+
|
|
19
|
+
The envelope reads a message's routing fields and its ok-versus-fault
|
|
20
|
+
tag without decoding a payload byte; everything the resolved method
|
|
21
|
+
consumes rides through as an opaque `payload` this layer never reads.
|
|
22
|
+
A decoded envelope borrows the buffer it came from, so that payload
|
|
23
|
+
reaches its reader as a view rather than a copy.
|
|
24
|
+
|
|
25
|
+
This crate depends on no other, and every kobako tier depends on it:
|
|
26
|
+
`kobako-runtime` and `kobako-wasmtime` on the host, `kobako-core` and
|
|
27
|
+
`kobako-mruby` in the guest, and any third-party engine, codec, or
|
|
28
|
+
guest that composes against them.
|
|
29
|
+
|
|
30
|
+
The byte layout is specified in
|
|
31
|
+
[`docs/wire/envelope.md`](https://github.com/elct9620/kobako/blob/main/docs/wire/envelope.md);
|
|
32
|
+
the golden vectors in this crate are derived from that document.
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
```toml
|
|
37
|
+
[dependencies]
|
|
38
|
+
kobako-transport = "0.13.0" # x-release-please-version
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## License
|
|
42
|
+
|
|
43
|
+
Apache-2.0
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
//! The ABI's fixed values — the numbers and layouts a host and a guest
|
|
2
|
+
//! must already agree on before either can read the other's bytes.
|
|
3
|
+
//!
|
|
4
|
+
//! The exports and the host import that carry the ABI are declared where
|
|
5
|
+
//! they are implemented: the guest contract crate emits the exports, the
|
|
6
|
+
//! driver resolves them. What lives here is what both sides must spell the
|
|
7
|
+
//! same way, and each value is spelled once.
|
|
8
|
+
//!
|
|
9
|
+
//! [ABI signatures]: ../../../docs/wire-codec.md
|
|
10
|
+
|
|
11
|
+
/// The Guest ABI version. A host accepts a Guest Binary only when the
|
|
12
|
+
/// version it reports equals this one, so a wire change is an increment
|
|
13
|
+
/// rather than a negotiation.
|
|
14
|
+
pub const ABI_VERSION: u32 = 3;
|
|
15
|
+
|
|
16
|
+
/// Width in bytes of the length prefix that precedes each invocation-channel
|
|
17
|
+
/// frame and the outcome buffer.
|
|
18
|
+
pub const FRAME_LEN_SIZE: usize = 4;
|
|
19
|
+
|
|
20
|
+
/// Single-dispatch size cap: 16 MiB in either direction, applied to the
|
|
21
|
+
/// whole envelope rather than the payload alone. A larger transfer is a
|
|
22
|
+
/// wire violation the receiving side refuses before allocating for it.
|
|
23
|
+
pub const MAX_DISPATCH_PAYLOAD: usize = 16 * 1024 * 1024;
|
|
24
|
+
|
|
25
|
+
/// Allocation ceiling on a length-prefixed frame. A reader learns a frame's
|
|
26
|
+
/// length before it can check anything else about it, so this bounds what a
|
|
27
|
+
/// declared length may make it allocate. It sits above
|
|
28
|
+
/// `MAX_DISPATCH_PAYLOAD` because it guards the read rather than enforcing
|
|
29
|
+
/// the contract: a frame between the two is allocated and then refused on
|
|
30
|
+
/// its merits.
|
|
31
|
+
pub const MAX_FRAME_LEN: usize = 64 * 1024 * 1024;
|
|
32
|
+
|
|
33
|
+
// The read guard must admit every frame the contract allows, so a refusal
|
|
34
|
+
// names the cap rather than the allocation ceiling.
|
|
35
|
+
const _: () = assert!(MAX_FRAME_LEN > MAX_DISPATCH_PAYLOAD);
|
|
36
|
+
|
|
37
|
+
/// Pack `(ptr, len)` into the u64 the ABI's three buffer-returning
|
|
38
|
+
/// functions answer with: the high 32 bits carry the linear-memory pointer,
|
|
39
|
+
/// the low 32 the byte length. A `len` of zero is the failure signal.
|
|
40
|
+
///
|
|
41
|
+
/// ```text
|
|
42
|
+
/// 63 32 31 0
|
|
43
|
+
/// ┌──────────┬────────────┐
|
|
44
|
+
/// │ ptr │ len │
|
|
45
|
+
/// └──────────┴────────────┘
|
|
46
|
+
/// ```
|
|
47
|
+
#[inline]
|
|
48
|
+
pub fn pack_ptr_len(ptr: u32, len: u32) -> u64 {
|
|
49
|
+
((ptr as u64) << 32) | (len as u64)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/// Read a packed u64 back into `(ptr, len)`.
|
|
53
|
+
#[inline]
|
|
54
|
+
pub fn unpack_ptr_len(packed: u64) -> (u32, u32) {
|
|
55
|
+
((packed >> 32) as u32, packed as u32)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
#[cfg(test)]
|
|
59
|
+
mod tests {
|
|
60
|
+
use super::*;
|
|
61
|
+
|
|
62
|
+
#[test]
|
|
63
|
+
fn the_layout_is_high_ptr_low_len() {
|
|
64
|
+
let packed = pack_ptr_len(0xAABB_CCDD, 0x1122_3344);
|
|
65
|
+
assert_eq!(
|
|
66
|
+
packed, 0xAABB_CCDD_1122_3344,
|
|
67
|
+
"the packed u64 must carry ptr in the high half and len in the low half"
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
#[test]
|
|
72
|
+
fn every_pair_round_trips() {
|
|
73
|
+
for &(ptr, len) in &[
|
|
74
|
+
(0u32, 0u32),
|
|
75
|
+
(0x1000, 1024),
|
|
76
|
+
(0x0001_0000, 4),
|
|
77
|
+
(0x7fff_ffff, 0xffff),
|
|
78
|
+
(1, u32::MAX),
|
|
79
|
+
(u32::MAX, 1),
|
|
80
|
+
(u32::MAX, u32::MAX),
|
|
81
|
+
] {
|
|
82
|
+
assert_eq!(
|
|
83
|
+
unpack_ptr_len(pack_ptr_len(ptr, len)),
|
|
84
|
+
(ptr, len),
|
|
85
|
+
"a ({ptr:#x}, {len:#x}) pair must survive the pack the ABI returns"
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|