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.
Files changed (124) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-manifest.json +1 -1
  3. data/CHANGELOG.md +127 -0
  4. data/Cargo.lock +15 -5
  5. data/README.md +96 -44
  6. data/ROADMAP.md +1 -1
  7. data/crates/kobako-runtime/CHANGELOG.md +44 -0
  8. data/crates/kobako-runtime/Cargo.toml +8 -1
  9. data/crates/kobako-runtime/README.md +1 -1
  10. data/crates/kobako-runtime/src/dispatch.rs +17 -11
  11. data/crates/kobako-runtime/src/error.rs +38 -6
  12. data/crates/kobako-runtime/src/lib.rs +13 -2
  13. data/crates/kobako-runtime/src/profile.rs +8 -0
  14. data/crates/kobako-runtime/src/runtime.rs +7 -2
  15. data/crates/kobako-runtime/src/snapshot.rs +13 -4
  16. data/crates/kobako-runtime/src/yielder.rs +4 -4
  17. data/crates/kobako-transport/CHANGELOG.md +31 -0
  18. data/crates/kobako-transport/Cargo.toml +27 -0
  19. data/crates/kobako-transport/README.md +43 -0
  20. data/crates/kobako-transport/src/abi.rs +89 -0
  21. data/crates/kobako-transport/src/envelope/bytes.rs +234 -0
  22. data/crates/kobako-transport/src/envelope/call.rs +222 -0
  23. data/crates/kobako-transport/src/envelope/error_record.rs +101 -0
  24. data/crates/kobako-transport/src/envelope/fault.rs +171 -0
  25. data/crates/kobako-transport/src/envelope/invocation_frames.rs +232 -0
  26. data/crates/kobako-transport/src/envelope/outcome.rs +261 -0
  27. data/crates/kobako-transport/src/envelope/reply.rs +240 -0
  28. data/crates/kobako-transport/src/envelope/run.rs +85 -0
  29. data/crates/kobako-transport/src/envelope.rs +73 -0
  30. data/crates/kobako-transport/src/lib.rs +21 -0
  31. data/crates/kobako-wasmtime/CHANGELOG.md +30 -0
  32. data/crates/kobako-wasmtime/Cargo.toml +5 -2
  33. data/crates/kobako-wasmtime/README.md +1 -1
  34. data/crates/kobako-wasmtime/src/config.rs +19 -12
  35. data/crates/kobako-wasmtime/src/dispatch.rs +15 -8
  36. data/crates/kobako-wasmtime/src/driver.rs +8 -27
  37. data/crates/kobako-wasmtime/src/frames.rs +11 -9
  38. data/crates/kobako-wasmtime/src/guest_mem.rs +7 -15
  39. data/crates/kobako-wasmtime/src/instance_pre.rs +2 -2
  40. data/crates/kobako-wasmtime/src/invocation.rs +1 -1
  41. data/crates/kobako-wasmtime/src/lib.rs +1 -1
  42. data/data/kobako.wasm +0 -0
  43. data/ext/kobako/Cargo.toml +7 -2
  44. data/ext/kobako/src/runtime/bridge.rs +103 -40
  45. data/ext/kobako/src/runtime/errors.rs +13 -5
  46. data/ext/kobako/src/runtime/gvl.rs +114 -0
  47. data/ext/kobako/src/runtime.rs +307 -207
  48. data/lib/kobako/catalog/extensions.rb +50 -43
  49. data/lib/kobako/catalog/handles.rb +7 -16
  50. data/lib/kobako/catalog/services.rb +24 -46
  51. data/lib/kobako/catalog/snippets.rb +16 -33
  52. data/lib/kobako/codec/decoder.rb +10 -13
  53. data/lib/kobako/codec/encoder.rb +7 -7
  54. data/lib/kobako/codec/error.rb +6 -6
  55. data/lib/kobako/codec/ext_types.rb +12 -72
  56. data/lib/kobako/codec/handle_walk.rb +6 -6
  57. data/lib/kobako/codec/state.rb +4 -56
  58. data/lib/kobako/codec/utils.rb +4 -4
  59. data/lib/kobako/codec.rb +11 -21
  60. data/lib/kobako/context.rb +208 -0
  61. data/lib/kobako/errors.rb +59 -16
  62. data/lib/kobako/execution.rb +52 -0
  63. data/lib/kobako/extension.rb +24 -10
  64. data/lib/kobako/outcome.rb +71 -128
  65. data/lib/kobako/payload/arguments.rb +59 -0
  66. data/lib/kobako/payload.rb +19 -0
  67. data/lib/kobako/pool.rb +1 -7
  68. data/lib/kobako/sandbox.rb +51 -191
  69. data/lib/kobako/sandbox_options.rb +24 -3
  70. data/lib/kobako/snippet/binary.rb +3 -4
  71. data/lib/kobako/snippet/source.rb +5 -6
  72. data/lib/kobako/snippet.rb +2 -2
  73. data/lib/kobako/transport/call.rb +21 -0
  74. data/lib/kobako/transport/dispatcher.rb +94 -142
  75. data/lib/kobako/transport/error.rb +21 -5
  76. data/lib/kobako/transport/reflection.rb +82 -0
  77. data/lib/kobako/transport/run.rb +25 -33
  78. data/lib/kobako/transport/yielder.rb +27 -36
  79. data/lib/kobako/transport.rb +11 -8
  80. data/lib/kobako/unresolved.rb +17 -0
  81. data/lib/kobako/usage.rb +7 -7
  82. data/lib/kobako/version.rb +1 -1
  83. data/lib/kobako.rb +1 -0
  84. data/release-please-config.json +47 -2
  85. data/sig/kobako/catalog/extensions.rbs +4 -6
  86. data/sig/kobako/catalog/handles.rbs +0 -2
  87. data/sig/kobako/catalog/services.rbs +4 -4
  88. data/sig/kobako/catalog/snippets.rbs +4 -2
  89. data/sig/kobako/codec/error.rbs +4 -4
  90. data/sig/kobako/codec/ext_types.rbs +0 -7
  91. data/sig/kobako/codec/state.rbs +0 -4
  92. data/sig/kobako/codec.rbs +4 -5
  93. data/sig/kobako/context.rbs +36 -0
  94. data/sig/kobako/errors.rbs +27 -6
  95. data/sig/kobako/execution.rbs +24 -0
  96. data/sig/kobako/extension.rbs +4 -2
  97. data/sig/kobako/outcome.rbs +12 -13
  98. data/sig/kobako/payload/arguments.rbs +26 -0
  99. data/sig/kobako/payload.rbs +4 -0
  100. data/sig/kobako/pool.rbs +0 -2
  101. data/sig/kobako/runtime.rbs +44 -14
  102. data/sig/kobako/sandbox.rbs +5 -24
  103. data/sig/kobako/sandbox_options.rbs +9 -2
  104. data/sig/kobako/snippet/binary.rbs +1 -1
  105. data/sig/kobako/snippet/source.rbs +1 -1
  106. data/sig/kobako/transport/call.rbs +21 -0
  107. data/sig/kobako/transport/dispatcher.rbs +9 -11
  108. data/sig/kobako/transport/error.rbs +11 -0
  109. data/sig/kobako/transport/reflection.rbs +15 -0
  110. data/sig/kobako/transport/run.rbs +1 -1
  111. data/sig/kobako/transport/yielder.rbs +2 -2
  112. data/sig/kobako/transport.rbs +8 -2
  113. data/sig/kobako/unresolved.rbs +4 -0
  114. metadata +30 -11
  115. data/lib/kobako/fault.rb +0 -38
  116. data/lib/kobako/outcome/panic.rb +0 -42
  117. data/lib/kobako/transport/request.rb +0 -76
  118. data/lib/kobako/transport/response.rb +0 -83
  119. data/lib/kobako/transport/yield.rb +0 -94
  120. data/sig/kobako/fault.rbs +0 -19
  121. data/sig/kobako/outcome/panic.rbs +0 -34
  122. data/sig/kobako/transport/request.rbs +0 -29
  123. data/sig/kobako/transport/response.rbs +0 -33
  124. 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.11.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" }
@@ -26,7 +26,7 @@ map the neutral types onto their own language surface.
26
26
 
27
27
  ```toml
28
28
  [dependencies]
29
- kobako-runtime = "0.11.0" # x-release-please-version
29
+ kobako-runtime = "0.13.0" # x-release-please-version
30
30
  ```
31
31
 
32
32
  ## License
@@ -1,22 +1,28 @@
1
1
  //! Engine-neutral guest→host dispatch contract, free of any `magnus`
2
2
  //! dependency.
3
3
  //!
4
- //! The wasm runtime hands a handler the raw Request bytes a guest produced
5
- //! and expects raw Response bytes back. What the handler *is* — a Ruby Proc,
6
- //! a Rust closure — is the frontend's concern; the runtime only sees this
7
- //! trait. The Ruby ext conforms by bridging its dispatch Proc behind it.
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 request bytes plus a `Yielder` for re-entering
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 raw Response bytes — or `None` when the handler itself failed, in
16
- /// which case the runtime walks its 0-return wire-fault path. The bound
17
- /// handler is contracted to fold application failures into a `Response.err`
18
- /// envelope, so `None` signals a contract violation (the handler raised)
19
- /// rather than a normal dispatch outcome.
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, request: &[u8], yielder: &mut dyn Yielder) -> Option<Vec<u8>>;
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
- #[derive(Debug)]
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
- pub enum Error {
57
+ #[non_exhaustive]
58
+ pub enum InvokeError {
43
59
  Trap(Trap),
44
60
  Setup(SetupError),
45
61
  }
46
62
 
47
- impl From<Trap> for Error {
63
+ impl From<Trap> for InvokeError {
48
64
  fn from(trap: Trap) -> Self {
49
- Error::Trap(trap)
65
+ InvokeError::Trap(trap)
50
66
  }
51
67
  }
52
68
 
53
- impl From<SetupError> for Error {
69
+ impl From<SetupError> for InvokeError {
54
70
  fn from(err: SetupError) -> Self {
55
- Error::Setup(err)
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
- //! dispatch / yield re-entry traits a frontend supplies.
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::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, Error>;
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
- /// OUTCOME_BUFFER bytes the guest returned; `Trap` is an engine fault
23
- /// after the export call started, kept as a value so the rest of the
24
- /// `Snapshot` survives it.
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
- //! YieldResponse bytes. What backs the re-entry — a wasmtime `Caller`, some
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
- /// `yield_block` ships `args` to `__kobako_yield_to_block` and returns the
17
- /// raw YieldResponse bytes, or a `Trap` — surfaced through the frontend's
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 yield_block(&mut self, args: &[u8]) -> Result<Vec<u8>, Trap>;
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
+ }