kobako 0.4.0 → 0.5.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 -0
- data/CHANGELOG.md +29 -0
- data/Cargo.lock +1 -1
- data/README.md +0 -1
- data/data/kobako.wasm +0 -0
- data/ext/kobako/Cargo.toml +1 -1
- data/ext/kobako/src/lib.rs +4 -2
- data/ext/kobako/src/{wasm → runtime}/cache.rs +12 -16
- data/ext/kobako/src/runtime/capture.rs +91 -0
- data/ext/kobako/src/runtime/config.rs +26 -0
- data/ext/kobako/src/runtime/dispatch.rs +211 -0
- data/ext/kobako/src/runtime/exports.rs +51 -0
- data/ext/kobako/src/runtime/guest_mem.rs +228 -0
- data/ext/kobako/src/{wasm/host_state.rs → runtime/invocation.rs} +94 -86
- data/ext/kobako/src/runtime/trap.rs +134 -0
- data/ext/kobako/src/runtime.rs +782 -0
- data/ext/kobako/src/snapshot.rs +110 -0
- data/lib/kobako/capture.rb +11 -16
- data/lib/kobako/catalog/handles.rb +107 -0
- data/lib/kobako/catalog/namespaces.rb +99 -0
- data/lib/kobako/{snippet/table.rb → catalog/snippets.rb} +37 -62
- data/lib/kobako/catalog.rb +18 -0
- data/lib/kobako/codec/decoder.rb +13 -5
- data/lib/kobako/codec/factory.rb +12 -12
- data/lib/kobako/codec/utils.rb +56 -59
- data/lib/kobako/codec.rb +6 -3
- data/lib/kobako/errors.rb +45 -28
- data/lib/kobako/fault.rb +40 -0
- data/lib/kobako/handle.rb +4 -6
- data/lib/kobako/namespace.rb +67 -0
- data/lib/kobako/outcome.rb +31 -35
- data/lib/kobako/runtime.rb +30 -0
- data/lib/kobako/sandbox.rb +83 -72
- data/lib/kobako/sandbox_options.rb +6 -9
- data/lib/kobako/snapshot.rb +40 -0
- data/lib/kobako/snippet/binary.rb +6 -7
- data/lib/kobako/snippet/source.rb +8 -8
- data/lib/kobako/snippet.rb +7 -9
- data/lib/kobako/transport/dispatcher.rb +195 -0
- data/lib/kobako/{rpc/wire_error.rb → transport/error.rb} +7 -6
- data/lib/kobako/transport/request.rb +78 -0
- data/lib/kobako/transport/response.rb +69 -0
- data/lib/kobako/transport/run.rb +141 -0
- data/lib/kobako/transport/yield.rb +91 -0
- data/lib/kobako/transport/yielder.rb +89 -0
- data/lib/kobako/transport.rb +24 -0
- data/lib/kobako/version.rb +1 -1
- data/lib/kobako.rb +4 -4
- data/release-please-config.json +24 -0
- data/sig/kobako/capture.rbs +0 -2
- data/sig/kobako/catalog/handles.rbs +19 -0
- data/sig/kobako/catalog/namespaces.rbs +17 -0
- data/sig/kobako/{snippet/table.rbs → catalog/snippets.rbs} +2 -11
- data/sig/kobako/{rpc.rbs → catalog.rbs} +1 -1
- data/sig/kobako/codec/decoder.rbs +2 -1
- data/sig/kobako/codec/factory.rbs +2 -2
- data/sig/kobako/codec/utils.rbs +5 -5
- data/sig/kobako/errors.rbs +7 -7
- data/sig/kobako/fault.rbs +19 -0
- data/sig/kobako/handle.rbs +2 -3
- data/sig/kobako/namespace.rbs +19 -0
- data/sig/kobako/outcome.rbs +2 -2
- data/sig/kobako/runtime.rbs +23 -0
- data/sig/kobako/sandbox.rbs +5 -8
- data/sig/kobako/snapshot.rbs +15 -0
- data/sig/kobako/transport/dispatcher.rbs +34 -0
- data/sig/kobako/transport/error.rbs +6 -0
- data/sig/kobako/transport/request.rbs +32 -0
- data/sig/kobako/transport/response.rbs +30 -0
- data/sig/kobako/transport/run.rbs +27 -0
- data/sig/kobako/transport/yield.rbs +34 -0
- data/sig/kobako/transport/yielder.rbs +21 -0
- data/sig/kobako/transport.rbs +4 -0
- metadata +48 -30
- data/ext/kobako/src/wasm/dispatch.rs +0 -162
- data/ext/kobako/src/wasm/instance.rs +0 -873
- data/ext/kobako/src/wasm.rs +0 -126
- data/lib/kobako/handle_table.rb +0 -119
- data/lib/kobako/invocation.rb +0 -143
- data/lib/kobako/rpc/dispatcher.rb +0 -171
- data/lib/kobako/rpc/envelope.rb +0 -118
- data/lib/kobako/rpc/fault.rb +0 -41
- data/lib/kobako/rpc/namespace.rb +0 -74
- data/lib/kobako/rpc/server.rb +0 -146
- data/lib/kobako/rpc.rb +0 -11
- data/lib/kobako/wasm.rb +0 -25
- data/sig/kobako/handle_table.rbs +0 -23
- data/sig/kobako/invocation.rbs +0 -25
- data/sig/kobako/rpc/dispatcher.rbs +0 -33
- data/sig/kobako/rpc/envelope.rbs +0 -51
- data/sig/kobako/rpc/fault.rbs +0 -20
- data/sig/kobako/rpc/namespace.rbs +0 -24
- data/sig/kobako/rpc/server.rbs +0 -31
- data/sig/kobako/rpc/wire_error.rbs +0 -6
- data/sig/kobako/wasm.rbs +0 -41
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
//! Host-side dispatch for the `__kobako_dispatch` import.
|
|
2
|
-
//!
|
|
3
|
-
//! When the guest invokes the wasm import declared in
|
|
4
|
-
//! `wasm/kobako-wasm/src/abi.rs`, wasmtime calls back into the host
|
|
5
|
-
//! through the closure built in [`super::instance::Instance::build`].
|
|
6
|
-
//! That closure delegates here. The dispatcher (docs/behavior.md B-12 / B-13):
|
|
7
|
-
//!
|
|
8
|
-
//! 1. Reads the Request bytes from guest linear memory.
|
|
9
|
-
//! 2. Hands them to the Ruby-side `Kobako::RPC::Server` and recovers
|
|
10
|
-
//! Response bytes.
|
|
11
|
-
//! 3. Allocates a guest buffer via `__kobako_alloc(len)` invoked
|
|
12
|
-
//! through `Caller::get_export`.
|
|
13
|
-
//! 4. Writes the Response bytes into the guest buffer.
|
|
14
|
-
//! 5. Returns packed `(ptr<<32)|len` for the guest to decode.
|
|
15
|
-
//!
|
|
16
|
-
//! Returns 0 on any step failure. `Kobako::Sandbox` always installs a
|
|
17
|
-
//! Server before invoking the guest, so reaching the dispatcher with
|
|
18
|
-
//! no Server bound is itself a wire-layer fault; the guest maps a 0
|
|
19
|
-
//! return to a trap. Failures during normal dispatch surface as
|
|
20
|
-
//! Response.err envelopes from the Server itself — they never reach
|
|
21
|
-
//! this 0-return path.
|
|
22
|
-
//!
|
|
23
|
-
//! ## Why this module writes to `stderr`
|
|
24
|
-
//!
|
|
25
|
-
//! This file is the one place in `ext/` that deliberately prints
|
|
26
|
-
//! through `eprintln!`. The host normally surfaces faults by
|
|
27
|
-
//! raising a `MagnusError` back into Ruby; the dispatcher contract
|
|
28
|
-
//! is the exception — it must return a packed `i64` to the guest
|
|
29
|
-
//! and cannot raise, so a 0 return is the only signal the wasm side
|
|
30
|
-
//! receives. The guest collapses every 0 into the same trap, so the
|
|
31
|
-
//! Ruby host has no way to attribute the failure to a specific
|
|
32
|
-
//! step (missing `memory` export vs. no Server bound vs. Server
|
|
33
|
-
//! raised vs. `__kobako_alloc` returned 0 vs. `memory.write`
|
|
34
|
-
//! rejected).
|
|
35
|
-
//!
|
|
36
|
-
//! [`handle`] writes a single `[kobako-dispatch] <reason>` line to
|
|
37
|
-
//! `stderr` on each failure path so operators have a breadcrumb to
|
|
38
|
-
//! correlate the trap with the actual cause. The line is emitted in
|
|
39
|
-
//! both debug and release builds on purpose: dispatcher failures
|
|
40
|
-
//! are wire-layer faults rather than expected error paths
|
|
41
|
-
//! (`Kobako::Sandbox` always installs a Server, the Server is
|
|
42
|
-
//! contracted never to raise, etc.), so the "release-build noise"
|
|
43
|
-
//! cost is bounded — under normal operation the line is never
|
|
44
|
-
//! written. Operators that need to silence the channel can redirect
|
|
45
|
-
//! the host process's stderr, but the kobako convention is "ext
|
|
46
|
-
//! never logs" plus this single, named exception.
|
|
47
|
-
|
|
48
|
-
use magnus::value::{Opaque, ReprValue};
|
|
49
|
-
use magnus::{Error as MagnusError, RString, Ruby, Value};
|
|
50
|
-
use wasmtime::{Caller, Extern};
|
|
51
|
-
|
|
52
|
-
use super::host_state::HostState;
|
|
53
|
-
|
|
54
|
-
/// Drive a single `__kobako_dispatch` invocation end-to-end. Entry point
|
|
55
|
-
/// from the wasmtime closure built in [`super::instance::Instance::build`].
|
|
56
|
-
///
|
|
57
|
-
/// Returns the packed `(ptr<<32)|len` u64 on success, 0 on any
|
|
58
|
-
/// wire-layer fault. Failure paths log a `[kobako-dispatch]` line to
|
|
59
|
-
/// `stderr` so operators have a breadcrumb when the guest sees a 0
|
|
60
|
-
/// return and traps; before this every failure was silent. The Server
|
|
61
|
-
/// itself is contracted never to raise (it folds Service exceptions
|
|
62
|
-
/// into Response.err envelopes), so reaching the failure path is
|
|
63
|
-
/// always a wiring bug or wire-layer fault rather than an expected
|
|
64
|
-
/// path.
|
|
65
|
-
pub(crate) fn handle(caller: &mut Caller<'_, HostState>, req_ptr: i32, req_len: i32) -> i64 {
|
|
66
|
-
match try_handle(caller, req_ptr, req_len) {
|
|
67
|
-
Ok(packed) => packed,
|
|
68
|
-
Err(reason) => {
|
|
69
|
-
eprintln!("[kobako-dispatch] {}", reason);
|
|
70
|
-
0
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/// Result-returning core of [`handle`]. Pulled out so each early
|
|
76
|
-
/// failure path carries a diagnostic string instead of an opaque 0.
|
|
77
|
-
fn try_handle(
|
|
78
|
-
caller: &mut Caller<'_, HostState>,
|
|
79
|
-
req_ptr: i32,
|
|
80
|
-
req_len: i32,
|
|
81
|
-
) -> Result<i64, &'static str> {
|
|
82
|
-
let req_bytes = read_caller_memory(caller, req_ptr, req_len).ok_or(
|
|
83
|
-
"Sandbox runtime does not export linear memory, or RPC request slice falls outside it",
|
|
84
|
-
)?;
|
|
85
|
-
|
|
86
|
-
// `Kobako::Sandbox` always installs an RPC server before invoking
|
|
87
|
-
// the runtime, so reaching this branch indicates a misuse rather
|
|
88
|
-
// than a normal control path.
|
|
89
|
-
let server = caller
|
|
90
|
-
.data()
|
|
91
|
-
.server()
|
|
92
|
-
.ok_or("RPC dispatched outside an active Sandbox#run — internal wiring bug")?;
|
|
93
|
-
|
|
94
|
-
let resp_bytes = invoke_server(server, &req_bytes).map_err(|_| {
|
|
95
|
-
"RPC server raised an exception instead of returning a fault — please report this as a kobako bug"
|
|
96
|
-
})?;
|
|
97
|
-
|
|
98
|
-
write_response(caller, &resp_bytes)
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/// Call the Ruby Server's `#dispatch(request_bytes)` method and return
|
|
102
|
-
/// the encoded Response bytes. Errors here mean the Server itself
|
|
103
|
-
/// failed (it is contracted never to raise — see
|
|
104
|
-
/// `Kobako::RPC::Server#dispatch`), which we treat as a wire-layer fault.
|
|
105
|
-
fn invoke_server(server: Opaque<Value>, req_bytes: &[u8]) -> Result<Vec<u8>, MagnusError> {
|
|
106
|
-
// The wasmtime callback runs on the same Ruby thread that called the
|
|
107
|
-
// active Sandbox invocation (#eval or #run) — the invariant SPEC
|
|
108
|
-
// Implementation Standards Architecture pins for the host gem — so
|
|
109
|
-
// `Ruby::get()` is always available here. Panicking with `expect`
|
|
110
|
-
// localises the violation rather than letting a nonsense error
|
|
111
|
-
// propagate.
|
|
112
|
-
let ruby = Ruby::get().expect("Ruby handle unavailable in __kobako_dispatch");
|
|
113
|
-
let server_value: Value = ruby.get_inner(server);
|
|
114
|
-
let req_str = ruby.str_from_slice(req_bytes);
|
|
115
|
-
let resp: RString = server_value.funcall("dispatch", (req_str,))?;
|
|
116
|
-
Ok(super::rstring_to_vec(resp))
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/// Allocate a guest-side buffer through `__kobako_alloc` and copy the
|
|
120
|
-
/// response bytes into it. Returns the packed `(ptr<<32)|len` u64.
|
|
121
|
-
/// Each failure path carries a `&'static str` reason so the dispatcher
|
|
122
|
-
/// wrapper can surface a useful diagnostic rather than a silent 0.
|
|
123
|
-
fn write_response(caller: &mut Caller<'_, HostState>, bytes: &[u8]) -> Result<i64, &'static str> {
|
|
124
|
-
let alloc = match caller.get_export("__kobako_alloc") {
|
|
125
|
-
Some(Extern::Func(f)) => f
|
|
126
|
-
.typed::<i32, i32>(&*caller)
|
|
127
|
-
.map_err(|_| "Sandbox runtime's allocation hook has the wrong signature")?,
|
|
128
|
-
_ => return Err("Sandbox runtime is missing the allocation hook"),
|
|
129
|
-
};
|
|
130
|
-
let len_i32 = i32::try_from(bytes.len()).map_err(|_| "RPC response exceeds 2 GiB")?;
|
|
131
|
-
let ptr = alloc
|
|
132
|
-
.call(&mut *caller, len_i32)
|
|
133
|
-
.map_err(|_| "Sandbox allocation trapped while preparing the RPC response")?;
|
|
134
|
-
if ptr == 0 {
|
|
135
|
-
return Err("Sandbox is out of memory while preparing the RPC response");
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
let mem = match caller.get_export("memory") {
|
|
139
|
-
Some(Extern::Memory(m)) => m,
|
|
140
|
-
_ => return Err("Sandbox runtime does not export linear memory"),
|
|
141
|
-
};
|
|
142
|
-
mem.write(&mut *caller, ptr as usize, bytes)
|
|
143
|
-
.map_err(|_| "could not write the RPC response into Sandbox memory (range invalid)")?;
|
|
144
|
-
|
|
145
|
-
let ptr_u32 = ptr as u32;
|
|
146
|
-
let len_u32 = bytes.len() as u32;
|
|
147
|
-
Ok(((ptr_u32 as i64) << 32) | (len_u32 as i64))
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/// Copy `[ptr, ptr+len)` out of the guest's linear memory as seen from
|
|
151
|
-
/// `caller`. Returns `None` when `memory` is not exported or the slice
|
|
152
|
-
/// falls outside the live memory range.
|
|
153
|
-
fn read_caller_memory(caller: &mut Caller<'_, HostState>, ptr: i32, len: i32) -> Option<Vec<u8>> {
|
|
154
|
-
let mem = match caller.get_export("memory") {
|
|
155
|
-
Some(Extern::Memory(m)) => m,
|
|
156
|
-
_ => return None,
|
|
157
|
-
};
|
|
158
|
-
let data = mem.data(&caller);
|
|
159
|
-
let start = ptr as usize;
|
|
160
|
-
let end = start.checked_add(len as usize)?;
|
|
161
|
-
data.get(start..end).map(|s| s.to_vec())
|
|
162
|
-
}
|