kobako 0.27.0 → 0.29.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 +241 -0
- data/Cargo.lock +132 -126
- data/README.md +240 -147
- data/ROADMAP.md +28 -22
- data/SECURITY.md +2 -2
- data/crates/kobako-runtime/CHANGELOG.md +117 -0
- data/crates/kobako-runtime/Cargo.toml +27 -6
- data/crates/kobako-runtime/README.md +16 -10
- data/crates/kobako-runtime/src/dispatch.rs +8 -0
- data/crates/kobako-runtime/src/error.rs +8 -9
- data/crates/kobako-runtime/src/filesystem/directory/spelling.rs +194 -0
- data/crates/kobako-runtime/src/filesystem/directory.rs +1082 -0
- data/crates/kobako-runtime/src/filesystem/memory.rs +1100 -0
- data/crates/kobako-runtime/src/filesystem/overlay/exclusive.rs +88 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack.rs +808 -0
- data/crates/kobako-runtime/src/filesystem/overlay.rs +1955 -0
- data/crates/kobako-runtime/src/filesystem/path.rs +60 -0
- data/crates/kobako-runtime/src/filesystem/temporary.rs +167 -0
- data/crates/kobako-runtime/src/filesystem.rs +392 -0
- data/crates/kobako-runtime/src/grant.rs +20 -0
- data/crates/kobako-runtime/src/lib.rs +12 -2
- data/crates/kobako-runtime/src/mount.rs +178 -0
- data/crates/kobako-runtime/src/profile.rs +4 -4
- data/crates/kobako-runtime/src/runtime.rs +43 -17
- data/crates/kobako-runtime/src/snapshot.rs +33 -15
- data/crates/kobako-runtime/src/yielder.rs +14 -6
- data/crates/kobako-transport/CHANGELOG.md +45 -0
- data/crates/kobako-transport/Cargo.toml +1 -1
- data/crates/kobako-transport/README.md +21 -12
- data/crates/kobako-transport/src/abi.rs +11 -14
- data/crates/kobako-transport/src/envelope/bytes.rs +18 -6
- data/crates/kobako-transport/src/envelope/compile_reply.rs +139 -0
- data/crates/kobako-transport/src/envelope/fault.rs +2 -1
- data/crates/kobako-transport/src/envelope/outcome.rs +17 -16
- data/crates/kobako-transport/src/envelope/preamble.rs +513 -0
- data/crates/kobako-transport/src/envelope/reply.rs +103 -61
- data/crates/kobako-transport/src/envelope/request.rs +305 -0
- data/crates/kobako-transport/src/envelope/run.rs +1 -2
- data/crates/kobako-transport/src/envelope/tag.rs +49 -0
- data/crates/kobako-transport/src/envelope/unsupported.rs +105 -0
- data/crates/kobako-transport/src/envelope.rs +19 -12
- data/crates/kobako-transport/src/lib.rs +0 -2
- data/crates/kobako-wasmtime/CHANGELOG.md +163 -0
- data/crates/kobako-wasmtime/Cargo.toml +23 -16
- data/crates/kobako-wasmtime/README.md +6 -4
- data/crates/kobako-wasmtime/src/abi.rs +67 -36
- data/crates/kobako-wasmtime/src/ambient.rs +64 -5
- data/crates/kobako-wasmtime/src/cache.rs +204 -127
- data/crates/kobako-wasmtime/src/capture.rs +4 -11
- data/crates/kobako-wasmtime/src/config.rs +69 -15
- data/crates/kobako-wasmtime/src/descriptors.rs +167 -0
- data/crates/kobako-wasmtime/src/dispatch.rs +95 -87
- data/crates/kobako-wasmtime/src/driver.rs +174 -125
- data/crates/kobako-wasmtime/src/exports.rs +88 -34
- data/crates/kobako-wasmtime/src/guest_mem.rs +151 -138
- data/crates/kobako-wasmtime/src/imports.rs +46 -0
- data/crates/kobako-wasmtime/src/instance_pre.rs +19 -58
- data/crates/kobako-wasmtime/src/invocation.rs +84 -412
- data/crates/kobako-wasmtime/src/io.rs +89 -0
- data/crates/kobako-wasmtime/src/lib.rs +7 -24
- data/crates/kobako-wasmtime/src/limiter.rs +253 -0
- data/crates/kobako-wasmtime/src/preview1/args.rs +60 -0
- data/crates/kobako-wasmtime/src/preview1/beneath.rs +188 -0
- data/crates/kobako-wasmtime/src/preview1/differential/mount.rs +609 -0
- data/crates/kobako-wasmtime/src/preview1/differential.rs +372 -0
- data/crates/kobako-wasmtime/src/preview1/errno.rs +267 -0
- data/crates/kobako-wasmtime/src/preview1/fd.rs +672 -0
- data/crates/kobako-wasmtime/src/preview1/memory.rs +205 -0
- data/crates/kobako-wasmtime/src/preview1/mount_view.rs +2499 -0
- data/crates/kobako-wasmtime/src/preview1/path.rs +421 -0
- data/crates/kobako-wasmtime/src/preview1/poll.rs +333 -0
- data/crates/kobako-wasmtime/src/preview1/sock.rs +30 -0
- data/crates/kobako-wasmtime/src/preview1.rs +461 -0
- data/crates/kobako-wasmtime/src/trap.rs +66 -59
- data/crates/kobako-wasmtime/src/wasi.rs +317 -0
- data/crates/kobako-wasmtime/tests/compile_posture.rs +43 -0
- data/crates/kobako-wasmtime/tests/guest_file.rs +1054 -0
- data/crates/kobako-wasmtime/tests/preamble_flags.rs +70 -0
- data/crates/kobako-wasmtime/tests/snippet_compile.rs +95 -0
- data/crates/kobako-wasmtime/tests/support/mod.rs +30 -0
- data/crates/kobako-wasmtime/tests/unanswered_call.rs +86 -0
- data/crates/kobako-wasmtime/tests/unsupported_call.rs +51 -0
- data/data/kobako.wasm +0 -0
- data/ext/kobako/Cargo.toml +4 -2
- data/ext/kobako/src/runtime/bridge.rs +177 -113
- data/ext/kobako/src/runtime/errors.rs +26 -32
- data/ext/kobako/src/runtime/escape.rs +53 -0
- data/ext/kobako/src/runtime/filesystem/grants.rs +91 -0
- data/ext/kobako/src/runtime/filesystem/ruby.rs +400 -0
- data/ext/kobako/src/runtime/filesystem.rs +236 -0
- data/ext/kobako/src/runtime/gvl.rs +11 -13
- data/ext/kobako/src/runtime/pinned_value.rs +31 -0
- data/ext/kobako/src/runtime/snapshot.rs +127 -0
- data/ext/kobako/src/runtime.rs +190 -312
- data/lib/kobako/capture.rb +1 -4
- data/lib/kobako/catalog/extensions.rb +21 -59
- data/lib/kobako/catalog/flags.rb +29 -0
- data/lib/kobako/catalog/grants.rb +30 -0
- data/lib/kobako/catalog/handles.rb +7 -54
- data/lib/kobako/catalog/mount.rb +27 -0
- data/lib/kobako/catalog/services.rb +17 -60
- data/lib/kobako/catalog/snippets.rb +41 -69
- data/lib/kobako/catalog.rb +13 -10
- data/lib/kobako/codec/decoder.rb +10 -28
- data/lib/kobako/codec/encoder.rb +3 -22
- data/lib/kobako/codec/error.rb +6 -27
- data/lib/kobako/codec/ext_types.rb +72 -94
- data/lib/kobako/codec/handle_walk.rb +65 -130
- data/lib/kobako/codec/nesting.rb +3 -8
- data/lib/kobako/codec/state.rb +21 -11
- data/lib/kobako/codec/utils.rb +6 -34
- data/lib/kobako/codec/value_budget.rb +86 -0
- data/lib/kobako/codec.rb +10 -22
- data/lib/kobako/constant_name.rb +8 -0
- data/lib/kobako/context.rb +83 -117
- data/lib/kobako/errors.rb +57 -132
- data/lib/kobako/execution.rb +16 -21
- data/lib/kobako/extension.rb +23 -40
- data/lib/kobako/filesystem.rb +38 -0
- data/lib/kobako/handle.rb +7 -12
- data/lib/kobako/outcome.rb +51 -80
- data/lib/kobako/payload/arguments.rb +1 -16
- data/lib/kobako/payload.rb +4 -12
- data/lib/kobako/pool.rb +25 -55
- data/lib/kobako/runtime.rb +7 -21
- data/lib/kobako/sandbox.rb +128 -137
- data/lib/kobako/sandbox_options.rb +71 -90
- data/lib/kobako/snippet/bytecode.rb +11 -0
- data/lib/kobako/snippet/source.rb +1 -16
- data/lib/kobako/snippet.rb +2 -10
- data/lib/kobako/transport/block_failure.rb +35 -0
- data/lib/kobako/transport/call.rb +1 -14
- data/lib/kobako/transport/dispatcher.rb +127 -206
- data/lib/kobako/transport/error.rb +6 -20
- data/lib/kobako/transport/exposure.rb +10 -17
- data/lib/kobako/transport/reflection.rb +108 -63
- data/lib/kobako/transport/run.rb +17 -71
- data/lib/kobako/transport/yielder.rb +20 -94
- data/lib/kobako/transport.rb +4 -15
- data/lib/kobako/unresolved.rb +4 -11
- data/lib/kobako/usage.rb +2 -6
- data/lib/kobako/version.rb +1 -1
- data/lib/kobako.rb +1 -0
- data/sig/kobako/catalog/extensions.rbs +2 -2
- data/sig/kobako/catalog/flags.rbs +13 -0
- data/sig/kobako/catalog/grants.rbs +13 -0
- data/sig/kobako/catalog/mount.rbs +28 -0
- data/sig/kobako/catalog/services.rbs +3 -5
- data/sig/kobako/catalog/snippets.rbs +5 -5
- data/sig/kobako/catalog.rbs +1 -0
- data/sig/kobako/codec/ext_types.rbs +10 -8
- data/sig/kobako/codec/handle_walk.rbs +9 -7
- data/sig/kobako/codec/state.rbs +7 -1
- data/sig/kobako/codec/utils.rbs +2 -2
- data/sig/kobako/codec/value_budget.rbs +23 -0
- data/sig/kobako/codec.rbs +4 -2
- data/sig/kobako/constant_name.rbs +3 -0
- data/sig/kobako/context.rbs +15 -8
- data/sig/kobako/errors.rbs +10 -4
- data/sig/kobako/filesystem.rbs +88 -0
- data/sig/kobako/guest_yielder.rbs +9 -0
- data/sig/kobako/outcome.rbs +6 -8
- data/sig/kobako/pool.rbs +1 -1
- data/sig/kobako/runtime.rbs +21 -45
- data/sig/kobako/sandbox.rbs +11 -1
- data/sig/kobako/sandbox_options.rbs +25 -7
- data/sig/kobako/snapshot.rbs +20 -0
- data/sig/kobako/snippet/bytecode.rbs +12 -0
- data/sig/kobako/transport/block_failure.rbs +13 -0
- data/sig/kobako/transport/dispatcher.rbs +15 -19
- data/sig/kobako/transport/exposure.rbs +1 -1
- data/sig/kobako/transport/reflection.rbs +19 -4
- data/sig/kobako/transport/run.rbs +1 -3
- data/sig/kobako/transport/yielder.rbs +5 -9
- data/sig/kobako/transport.rbs +9 -8
- metadata +75 -10
- data/crates/kobako-transport/src/envelope/invocation_frames.rs +0 -241
- data/crates/kobako-wasmtime/src/frames.rs +0 -273
- data/lib/kobako/snippet/binary.rb +0 -26
- data/rust-toolchain.toml +0 -15
- data/sig/kobako/snippet/binary.rbs +0 -12
|
@@ -1,494 +1,166 @@
|
|
|
1
|
-
//! Per-invocation host state — the
|
|
2
|
-
//!
|
|
3
|
-
//! for the lifetime of one `Driver` invoke call).
|
|
1
|
+
//! Per-invocation host state — one `Invocation` per OS thread for the
|
|
2
|
+
//! lifetime of one `Driver` invoke call.
|
|
4
3
|
//!
|
|
5
4
|
//! Owned as the data of each per-invocation `wasmtime::Store`
|
|
6
5
|
//! and threaded through every host import —
|
|
7
|
-
//! the `
|
|
8
|
-
//!
|
|
9
|
-
//!
|
|
10
|
-
//! export call.
|
|
6
|
+
//! the `__kobako_upcall` dispatcher reads the bound dispatch handler,
|
|
7
|
+
//! and the WASI imports read the invocation's `Wasi` context, which the
|
|
8
|
+
//! Store is created with.
|
|
11
9
|
//!
|
|
12
|
-
//! The slot also carries the per-invocation wall-clock deadline
|
|
13
|
-
//!
|
|
14
|
-
//!
|
|
15
|
-
//!
|
|
16
|
-
//! callbacks installed in `Driver::new_store`. The
|
|
17
|
-
//! memory cap measures only the `memory.grow` delta past the linear-
|
|
18
|
-
//! memory size captured at invocation entry — the image's initial
|
|
19
|
-
//! allocation is outside the budget.
|
|
20
|
-
//!
|
|
21
|
-
//! [SPEC.md Single-Invocation Slot]: ../../../SPEC.md
|
|
10
|
+
//! The slot also carries the per-invocation wall-clock deadline and the
|
|
11
|
+
//! memory cap `MemoryLimiter`, both read from the wasmtime
|
|
12
|
+
//! `epoch_deadline_callback` / `ResourceLimiter` callbacks installed in
|
|
13
|
+
//! `Driver::new_store`.
|
|
22
14
|
|
|
23
15
|
use std::sync::Arc;
|
|
24
16
|
use std::time::{Duration, Instant};
|
|
25
17
|
|
|
26
|
-
use wasmtime::ResourceLimiter;
|
|
27
|
-
use wasmtime_wasi::p1::WasiP1Ctx;
|
|
28
|
-
use wasmtime_wasi::p2::pipe::MemoryOutputPipe;
|
|
29
|
-
|
|
30
18
|
use kobako_runtime::dispatch::DispatchHandler;
|
|
31
19
|
|
|
32
|
-
|
|
33
|
-
|
|
20
|
+
use crate::exports::Exports;
|
|
21
|
+
use crate::limiter::MemoryLimiter;
|
|
22
|
+
use crate::wasi::Wasi;
|
|
23
|
+
|
|
24
|
+
/// Per-invocation host state, owned by the invocation's own Store.
|
|
25
|
+
/// Threaded through every host import callback.
|
|
34
26
|
///
|
|
35
|
-
/// All field access is mediated by methods on this type — the
|
|
36
|
-
///
|
|
37
|
-
/// `Invocation::
|
|
38
|
-
/// `Invocation::
|
|
39
|
-
///
|
|
40
|
-
/// `Invocation::stderr_bytes`. The fields are private so the mutation
|
|
41
|
-
/// surface stays narrow.
|
|
27
|
+
/// All field access is mediated by methods on this type — the `Wasi`
|
|
28
|
+
/// context arrives with the invocation, the dispatch handler is set once
|
|
29
|
+
/// via `Invocation::bind_on_dispatch`, and the captures are read back
|
|
30
|
+
/// through `Invocation::wasi` after the run. The fields are private so
|
|
31
|
+
/// the mutation surface stays narrow.
|
|
42
32
|
pub(crate) struct Invocation {
|
|
43
|
-
wasi:
|
|
44
|
-
stdout_pipe: Option<MemoryOutputPipe>,
|
|
45
|
-
stderr_pipe: Option<MemoryOutputPipe>,
|
|
33
|
+
wasi: Wasi,
|
|
46
34
|
on_dispatch: Option<Arc<dyn DispatchHandler>>,
|
|
47
35
|
deadline: Option<Instant>,
|
|
48
36
|
limiter: MemoryLimiter,
|
|
49
37
|
wall_entry: Option<Instant>,
|
|
50
38
|
wall_time: Duration,
|
|
39
|
+
reentry_trap: Option<wasmtime::Error>,
|
|
40
|
+
exports: Option<Exports>,
|
|
41
|
+
open_calls: u32,
|
|
51
42
|
}
|
|
52
43
|
|
|
53
44
|
impl Invocation {
|
|
54
|
-
|
|
55
|
-
/// `Sandbox#memory_limit` cap in bytes (or `None` to disable the cap);
|
|
56
|
-
/// it is read from the wasmtime `ResourceLimiter` callback every
|
|
57
|
-
/// time the guest grows linear memory.
|
|
58
|
-
pub(crate) fn new(memory_limit: Option<usize>) -> Self {
|
|
45
|
+
pub(crate) fn new(memory_limit: Option<usize>, wasi: Wasi) -> Self {
|
|
59
46
|
Self {
|
|
60
|
-
wasi
|
|
61
|
-
stdout_pipe: None,
|
|
62
|
-
stderr_pipe: None,
|
|
47
|
+
wasi,
|
|
63
48
|
on_dispatch: None,
|
|
64
49
|
deadline: None,
|
|
65
50
|
limiter: MemoryLimiter::new(memory_limit),
|
|
66
51
|
wall_entry: None,
|
|
67
52
|
wall_time: Duration::ZERO,
|
|
53
|
+
reentry_trap: None,
|
|
54
|
+
exports: None,
|
|
55
|
+
open_calls: 0,
|
|
68
56
|
}
|
|
69
57
|
}
|
|
70
58
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
59
|
+
pub(crate) fn set_exports(&mut self, exports: Exports) {
|
|
60
|
+
self.exports = Some(exports);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/// `None` until the instance exists, so a host import reached before
|
|
64
|
+
/// then finds no guest to call back into.
|
|
65
|
+
pub(crate) fn exports(&self) -> Option<Exports> {
|
|
66
|
+
self.exports.clone()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// The first trap wins: the upcall import ends the invocation with it,
|
|
70
|
+
/// so the trap keeps its own kind.
|
|
71
|
+
pub(crate) fn record_reentry_trap(&mut self, trap: wasmtime::Error) {
|
|
72
|
+
self.reentry_trap.get_or_insert(trap);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/// Once a callback into the guest has trapped, the guest is past
|
|
76
|
+
/// resuming.
|
|
77
|
+
pub(crate) fn reentry_trapped(&self) -> bool {
|
|
78
|
+
self.reentry_trap.is_some()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
pub(crate) fn take_reentry_trap(&mut self) -> Option<wasmtime::Error> {
|
|
82
|
+
self.reentry_trap.take()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/// Count a Call the host is about to answer and name its frame, as the
|
|
86
|
+
/// guest counts the Calls it has open.
|
|
87
|
+
pub(crate) fn open_call(&mut self) -> u32 {
|
|
88
|
+
self.open_calls += 1;
|
|
89
|
+
self.open_calls - 1
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
pub(crate) fn close_call(&mut self) {
|
|
93
|
+
self.open_calls -= 1;
|
|
84
94
|
}
|
|
85
95
|
|
|
86
|
-
/// Bind the dispatch handler for this invocation. From this point on,
|
|
87
|
-
/// every `__kobako_dispatch` host import invocation hands the handler
|
|
88
|
-
/// the Call bytes and expects encoded Reply bytes back.
|
|
89
96
|
pub(crate) fn bind_on_dispatch(&mut self, handler: Arc<dyn DispatchHandler>) {
|
|
90
97
|
self.on_dispatch = Some(handler);
|
|
91
98
|
}
|
|
92
99
|
|
|
93
|
-
///
|
|
94
|
-
///
|
|
95
|
-
pub(crate) fn
|
|
96
|
-
self.
|
|
97
|
-
.as_ref()
|
|
98
|
-
.map(|p| p.contents().to_vec())
|
|
99
|
-
.unwrap_or_default()
|
|
100
|
+
/// A clone, so the borrow on the `Caller` is released and the
|
|
101
|
+
/// dispatcher can re-borrow it to write the Reply.
|
|
102
|
+
pub(crate) fn on_dispatch(&self) -> Option<Arc<dyn DispatchHandler>> {
|
|
103
|
+
self.on_dispatch.clone()
|
|
100
104
|
}
|
|
101
105
|
|
|
102
|
-
///
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
self.stderr_pipe
|
|
106
|
+
/// The trap ending an invocation its host gave up on.
|
|
107
|
+
pub(crate) fn abandonment(&self) -> Option<wasmtime::Error> {
|
|
108
|
+
self.on_dispatch
|
|
106
109
|
.as_ref()
|
|
107
|
-
.
|
|
108
|
-
.
|
|
110
|
+
.is_some_and(|handler| handler.abandoned())
|
|
111
|
+
.then(|| wasmtime::Error::msg("the host abandoned the invocation"))
|
|
109
112
|
}
|
|
110
113
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
/// `Caller` so the dispatcher can re-borrow it to write the response.
|
|
114
|
-
/// None means no handler has been bound yet via
|
|
115
|
-
/// `Invocation::bind_on_dispatch`.
|
|
116
|
-
pub(crate) fn on_dispatch(&self) -> Option<Arc<dyn DispatchHandler>> {
|
|
117
|
-
self.on_dispatch.clone()
|
|
114
|
+
pub(crate) fn wasi(&self) -> &Wasi {
|
|
115
|
+
&self.wasi
|
|
118
116
|
}
|
|
119
117
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
/// `Invocation::install_wasi` running at the top of every `Driver`
|
|
123
|
-
/// invoke, so reaching this branch with `None` signals a host-side
|
|
124
|
-
/// wiring bug.
|
|
125
|
-
pub(crate) fn wasi_mut(&mut self) -> &mut WasiP1Ctx {
|
|
126
|
-
self.wasi.as_mut().expect(
|
|
127
|
-
"WASI context not initialised — the driver must install frames before any WASI use",
|
|
128
|
-
)
|
|
118
|
+
pub(crate) fn wasi_mut(&mut self) -> &mut Wasi {
|
|
119
|
+
&mut self.wasi
|
|
129
120
|
}
|
|
130
121
|
|
|
131
|
-
/// Replace the per-run wall-clock deadline. `Some(at)` makes the
|
|
132
|
-
/// epoch-deadline callback trap once `Instant::now() >= at`; `None`
|
|
133
|
-
/// disables the cap. Called from `Driver::prime_caps` at the top of
|
|
134
|
-
/// every invocation (`#eval` and `#run`).
|
|
135
122
|
pub(crate) fn set_deadline(&mut self, deadline: Option<Instant>) {
|
|
136
123
|
self.deadline = deadline;
|
|
137
124
|
}
|
|
138
125
|
|
|
139
|
-
/// Return the current per-run deadline. Read from the epoch-deadline
|
|
140
|
-
/// callback installed by `Driver::new_store`.
|
|
141
126
|
pub(crate) fn deadline(&self) -> Option<Instant> {
|
|
142
127
|
self.deadline
|
|
143
128
|
}
|
|
144
129
|
|
|
145
|
-
/// Mutable handle to the embedded `MemoryLimiter`. Required by
|
|
146
|
-
/// the wasmtime `ResourceLimiter` callback wiring in
|
|
147
|
-
/// `Driver::new_store`
|
|
148
|
-
/// (`store.limiter(|state| state.limiter_mut())`); kept private to
|
|
149
|
-
/// the wasm submodule so the only public surface for arming the
|
|
150
|
-
/// cap goes through `Invocation::arm_memory_cap` /
|
|
151
|
-
/// `Invocation::disarm_memory_cap`.
|
|
152
130
|
pub(crate) fn limiter_mut(&mut self) -> &mut MemoryLimiter {
|
|
153
131
|
&mut self.limiter
|
|
154
132
|
}
|
|
155
133
|
|
|
156
|
-
///
|
|
157
|
-
///
|
|
158
|
-
///
|
|
159
|
-
/// the cap, so the mruby image's initial allocation and the
|
|
160
|
-
/// high-water mark left by prior invocations do not consume the
|
|
161
|
-
/// budget. Paired with `Invocation::disarm_memory_cap` around the
|
|
162
|
-
/// call to the corresponding `__kobako_*` export so post-run host
|
|
163
|
-
/// bookkeeping (e.g. fetching the OUTCOME_BUFFER) is not
|
|
164
|
-
/// attributed to the user script.
|
|
134
|
+
/// Only the `memory.grow` delta past `baseline` is charged, so the
|
|
135
|
+
/// image's initial allocation and the high-water mark of earlier
|
|
136
|
+
/// invocations do not consume the budget.
|
|
165
137
|
pub(crate) fn arm_memory_cap(&mut self, baseline: usize) {
|
|
166
138
|
self.limiter.activate(baseline);
|
|
167
139
|
}
|
|
168
140
|
|
|
169
|
-
///
|
|
170
|
-
///
|
|
141
|
+
/// Disarmed once the export returns, so post-run host bookkeeping such
|
|
142
|
+
/// as reading the answer back is not charged to the script.
|
|
171
143
|
pub(crate) fn disarm_memory_cap(&mut self) {
|
|
172
144
|
self.limiter.deactivate();
|
|
173
145
|
}
|
|
174
146
|
|
|
175
|
-
///
|
|
176
|
-
///
|
|
177
|
-
/// invocation immediately before the guest export call so the
|
|
178
|
-
/// bracket matches the `timeout` deadline accounting and
|
|
179
|
-
/// excludes post-run host bookkeeping such as `OUTCOME_BUFFER`
|
|
180
|
-
/// decoding.
|
|
147
|
+
/// Stamped right before the guest export call, so the bracket matches
|
|
148
|
+
/// the `timeout` accounting and excludes post-run host bookkeeping.
|
|
181
149
|
pub(crate) fn start_wall_clock(&mut self) {
|
|
182
150
|
self.wall_entry = Some(Instant::now());
|
|
183
151
|
}
|
|
184
152
|
|
|
185
|
-
/// Close the `wall_time` measurement
|
|
186
|
-
/// started by `Invocation::start_wall_clock`. Idempotent — a
|
|
187
|
-
/// stop with no matching start (e.g. if the guest export call
|
|
188
|
-
/// never executed because of a host-side allocation failure)
|
|
189
|
-
/// leaves the previously-recorded value untouched.
|
|
190
153
|
pub(crate) fn stop_wall_clock(&mut self) {
|
|
191
154
|
if let Some(entry) = self.wall_entry.take() {
|
|
192
155
|
self.wall_time = entry.elapsed();
|
|
193
156
|
}
|
|
194
157
|
}
|
|
195
158
|
|
|
196
|
-
/// Return the wall-clock duration the most recent invocation
|
|
197
|
-
/// spent inside the guest export call.
|
|
198
|
-
/// Zero before the first invocation.
|
|
199
159
|
pub(crate) fn wall_time(&self) -> Duration {
|
|
200
160
|
self.wall_time
|
|
201
161
|
}
|
|
202
162
|
|
|
203
|
-
/// Return the `memory_peak` — the high-
|
|
204
|
-
/// water mark of the per-invocation `memory.grow` delta past the
|
|
205
|
-
/// linear-memory size captured at invocation entry. Zero before
|
|
206
|
-
/// the first invocation.
|
|
207
163
|
pub(crate) fn memory_peak(&self) -> usize {
|
|
208
164
|
self.limiter.peak()
|
|
209
165
|
}
|
|
210
166
|
}
|
|
211
|
-
|
|
212
|
-
/// Resource limiter that enforces the per-invocation `memory_limit`
|
|
213
|
-
/// cap.
|
|
214
|
-
///
|
|
215
|
-
/// `max_memory` is the byte cap on per-invocation growth (`None` disables
|
|
216
|
-
/// the cap). `baseline` is the linear-memory size captured at invocation
|
|
217
|
-
/// entry by `MemoryLimiter::activate`; the limiter charges only the
|
|
218
|
-
/// `memory.grow` delta past `baseline` against `max_memory`, so the
|
|
219
|
-
/// mruby image's initial allocation and any high-water mark left by
|
|
220
|
-
/// prior invocations on the same Sandbox do not consume the budget.
|
|
221
|
-
/// `cap_active` gates whether the cap is enforced — wasmtime's
|
|
222
|
-
/// `ResourceLimiter` also fires for the module's declared initial
|
|
223
|
-
/// allocation at instantiation time, but the cap stays dormant until
|
|
224
|
-
/// `MemoryLimiter::activate` flips the flag for one `Driver` invoke
|
|
225
|
-
/// call. When `cap_active` is `false`, the limiter always allows
|
|
226
|
-
/// growth.
|
|
227
|
-
///
|
|
228
|
-
/// When `memory.grow` would push the per-invocation delta past
|
|
229
|
-
/// `max_memory`, the limiter returns `MemoryLimitTrap` from
|
|
230
|
-
/// `memory_growing`; wasmtime turns that into the trap surfaced to the
|
|
231
|
-
/// host as a guest invocation failure.
|
|
232
|
-
#[derive(Debug, Clone, Copy)]
|
|
233
|
-
pub(crate) struct MemoryLimiter {
|
|
234
|
-
max_memory: Option<usize>,
|
|
235
|
-
baseline: usize,
|
|
236
|
-
cap_active: bool,
|
|
237
|
-
peak: usize,
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
impl MemoryLimiter {
|
|
241
|
-
fn new(max_memory: Option<usize>) -> Self {
|
|
242
|
-
Self {
|
|
243
|
-
max_memory,
|
|
244
|
-
baseline: 0,
|
|
245
|
-
cap_active: false,
|
|
246
|
-
peak: 0,
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/// Arm the cap so subsequent `memory.grow` calls are charged
|
|
251
|
-
/// against `max_memory` starting from `baseline` bytes. Called via
|
|
252
|
-
/// `Invocation::arm_memory_cap` at the top of every invocation;
|
|
253
|
-
/// the cap is dormant by default — the module's declared initial
|
|
254
|
-
/// memory is allocated during `Linker::instantiate` and the
|
|
255
|
-
/// per-invocation budget excludes anything that existed before
|
|
256
|
-
/// arming. Also clears the
|
|
257
|
-
/// per-invocation `MemoryLimiter::peak` high-water so the
|
|
258
|
-
/// `memory_peak` accounting restarts from
|
|
259
|
-
/// zero for the new invocation.
|
|
260
|
-
fn activate(&mut self, baseline: usize) {
|
|
261
|
-
self.baseline = baseline;
|
|
262
|
-
self.cap_active = true;
|
|
263
|
-
self.peak = 0;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/// Disarm the cap so post-run host bookkeeping (e.g. fetching the
|
|
267
|
-
/// OUTCOME_BUFFER, which can grow guest memory transiently) is
|
|
268
|
-
/// not attributed to the user script. Paired with
|
|
269
|
-
/// `MemoryLimiter::activate`.
|
|
270
|
-
fn deactivate(&mut self) {
|
|
271
|
-
self.cap_active = false;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/// Return the high-water mark of the per-invocation
|
|
275
|
-
/// `memory.grow` delta past `baseline` observed since the last
|
|
276
|
-
/// `MemoryLimiter::activate`. Read after the guest export
|
|
277
|
-
/// returns to populate `Kobako::Usage#memory_peak`.
|
|
278
|
-
/// Pinned to the last accepted grow —
|
|
279
|
-
/// rejected `desired` values that trip the memory
|
|
280
|
-
/// cap never update the peak, so the reported value never exceeds
|
|
281
|
-
/// `memory_limit`.
|
|
282
|
-
pub(crate) fn peak(&self) -> usize {
|
|
283
|
-
self.peak
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
impl ResourceLimiter for MemoryLimiter {
|
|
288
|
-
fn memory_growing(
|
|
289
|
-
&mut self,
|
|
290
|
-
_current: usize,
|
|
291
|
-
desired: usize,
|
|
292
|
-
_maximum: Option<usize>,
|
|
293
|
-
) -> wasmtime::Result<bool> {
|
|
294
|
-
if !self.cap_active {
|
|
295
|
-
return Ok(true);
|
|
296
|
-
}
|
|
297
|
-
let delta = desired.saturating_sub(self.baseline);
|
|
298
|
-
if let Some(limit) = self.max_memory {
|
|
299
|
-
if delta > limit {
|
|
300
|
-
return Err(wasmtime::Error::new(MemoryLimitTrap { desired, limit }));
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
if delta > self.peak {
|
|
304
|
-
self.peak = delta;
|
|
305
|
-
}
|
|
306
|
-
Ok(true)
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
fn table_growing(
|
|
310
|
-
&mut self,
|
|
311
|
-
_current: usize,
|
|
312
|
-
_desired: usize,
|
|
313
|
-
_maximum: Option<usize>,
|
|
314
|
-
) -> wasmtime::Result<bool> {
|
|
315
|
-
Ok(true)
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/// Marker error returned from `MemoryLimiter::memory_growing` when the
|
|
320
|
-
/// per-invocation memory cap is exceeded. Downcast from the wasmtime
|
|
321
|
-
/// trap error to classify the failure as a memory-limit `Trap`. Callers
|
|
322
|
-
/// use the `Display` impl below — no field is read directly — so the
|
|
323
|
-
/// inner state stays private.
|
|
324
|
-
#[derive(Debug)]
|
|
325
|
-
pub(crate) struct MemoryLimitTrap {
|
|
326
|
-
desired: usize,
|
|
327
|
-
limit: usize,
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
impl MemoryLimitTrap {
|
|
331
|
-
/// Construct a trap with the given `desired` / `limit` pair. Used
|
|
332
|
-
/// internally by `MemoryLimiter::memory_growing` in production and
|
|
333
|
-
/// by the sibling-module `classify_trap` unit tests to materialise
|
|
334
|
-
/// a representative error for downcast routing.
|
|
335
|
-
#[cfg(test)]
|
|
336
|
-
pub(crate) fn new(desired: usize, limit: usize) -> Self {
|
|
337
|
-
Self { desired, limit }
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
impl std::fmt::Display for MemoryLimitTrap {
|
|
342
|
-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
343
|
-
write!(
|
|
344
|
-
f,
|
|
345
|
-
"memory usage exceeded memory_limit: \
|
|
346
|
-
requested={} bytes, limit={} bytes",
|
|
347
|
-
self.desired, self.limit
|
|
348
|
-
)
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
impl std::error::Error for MemoryLimitTrap {}
|
|
353
|
-
|
|
354
|
-
/// Marker error returned from the epoch-deadline callback when the
|
|
355
|
-
/// wall-clock deadline is exceeded. Downcast from the wasmtime trap
|
|
356
|
-
/// error to classify the failure as a timeout `Trap`.
|
|
357
|
-
#[derive(Debug)]
|
|
358
|
-
pub(crate) struct TimeoutTrap;
|
|
359
|
-
|
|
360
|
-
impl std::fmt::Display for TimeoutTrap {
|
|
361
|
-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
362
|
-
write!(f, "wall-clock deadline exceeded")
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
impl std::error::Error for TimeoutTrap {}
|
|
367
|
-
|
|
368
|
-
#[cfg(test)]
|
|
369
|
-
mod tests {
|
|
370
|
-
//! Unit tests for `MemoryLimiter` — the per-invocation memory
|
|
371
|
-
//! delta cap. The Ruby-facing E2E suite exercises the full path
|
|
372
|
-
//! through wasmtime; these tests pin the pure delta arithmetic so
|
|
373
|
-
//! a regression that breaks the baseline accounting (e.g. dropping
|
|
374
|
-
//! the `baseline` subtraction, or letting `activate` carry stale
|
|
375
|
-
//! state across invocations) is caught without spinning up a
|
|
376
|
-
//! Store.
|
|
377
|
-
use super::{MemoryLimitTrap, MemoryLimiter};
|
|
378
|
-
use wasmtime::ResourceLimiter;
|
|
379
|
-
|
|
380
|
-
fn assert_growing(limiter: &mut MemoryLimiter, desired: usize) {
|
|
381
|
-
assert!(
|
|
382
|
-
limiter.memory_growing(0, desired, None).unwrap(),
|
|
383
|
-
"expected memory_growing({desired}) to allow growth"
|
|
384
|
-
);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
fn assert_trapping(limiter: &mut MemoryLimiter, desired: usize) {
|
|
388
|
-
let err = limiter
|
|
389
|
-
.memory_growing(0, desired, None)
|
|
390
|
-
.expect_err("expected memory_growing to trap");
|
|
391
|
-
assert!(
|
|
392
|
-
err.downcast_ref::<MemoryLimitTrap>().is_some(),
|
|
393
|
-
"expected MemoryLimitTrap, got {err:?}"
|
|
394
|
-
);
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// @behavior S-113
|
|
398
|
-
#[test]
|
|
399
|
-
fn dormant_limiter_allows_any_growth() {
|
|
400
|
-
let mut limiter = MemoryLimiter::new(Some(1 << 20));
|
|
401
|
-
// Without `activate`, the cap is dormant — the module's
|
|
402
|
-
// declared initial allocation must pass through unconditionally.
|
|
403
|
-
assert_growing(&mut limiter, 100 << 20);
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
// @behavior S-114
|
|
407
|
-
#[test]
|
|
408
|
-
fn delta_below_cap_passes_after_activate() {
|
|
409
|
-
let mut limiter = MemoryLimiter::new(Some(1 << 20));
|
|
410
|
-
limiter.activate(2 << 20);
|
|
411
|
-
// baseline=2 MiB, desired=2.5 MiB → delta=0.5 MiB ≤ 1 MiB cap.
|
|
412
|
-
assert_growing(&mut limiter, (2 << 20) + (1 << 19));
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
// @behavior S-121
|
|
416
|
-
#[test]
|
|
417
|
-
fn delta_past_cap_traps_with_memory_limit_trap() {
|
|
418
|
-
let mut limiter = MemoryLimiter::new(Some(1 << 20));
|
|
419
|
-
limiter.activate(2 << 20);
|
|
420
|
-
// baseline=2 MiB, desired=4 MiB → delta=2 MiB > 1 MiB cap.
|
|
421
|
-
assert_trapping(&mut limiter, 4 << 20);
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
// @behavior S-008
|
|
425
|
-
#[test]
|
|
426
|
-
fn activate_resets_baseline_on_each_invocation() {
|
|
427
|
-
let mut limiter = MemoryLimiter::new(Some(1 << 20));
|
|
428
|
-
limiter.activate(2 << 20);
|
|
429
|
-
assert_growing(&mut limiter, (2 << 20) + (1 << 20));
|
|
430
|
-
// Second invocation: linear memory has grown to 3 MiB. Re-arming
|
|
431
|
-
// must re-anchor the baseline so the next 1 MiB of growth fits
|
|
432
|
-
// the per-invocation budget rather than being charged against
|
|
433
|
-
// the prior invocation's residue.
|
|
434
|
-
limiter.activate(3 << 20);
|
|
435
|
-
assert_growing(&mut limiter, (3 << 20) + (1 << 20));
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
// @behavior S-009
|
|
439
|
-
#[test]
|
|
440
|
-
fn disabled_cap_ignores_delta_size() {
|
|
441
|
-
let mut limiter = MemoryLimiter::new(None);
|
|
442
|
-
limiter.activate(0);
|
|
443
|
-
assert_growing(&mut limiter, 100 << 20);
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
// @behavior S-116
|
|
447
|
-
#[test]
|
|
448
|
-
fn peak_starts_at_zero_before_any_grow() {
|
|
449
|
-
let limiter = MemoryLimiter::new(Some(1 << 20));
|
|
450
|
-
assert_eq!(limiter.peak(), 0);
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
// @behavior S-115
|
|
454
|
-
#[test]
|
|
455
|
-
fn peak_tracks_high_water_of_delta_past_baseline() {
|
|
456
|
-
let mut limiter = MemoryLimiter::new(Some(1 << 20));
|
|
457
|
-
limiter.activate(2 << 20);
|
|
458
|
-
assert_growing(&mut limiter, (2 << 20) + (1 << 18)); // delta=256 KiB
|
|
459
|
-
assert_growing(&mut limiter, (2 << 20) + (1 << 19)); // delta=512 KiB (new peak)
|
|
460
|
-
assert_growing(&mut limiter, (2 << 20) + (1 << 17)); // delta=128 KiB (below peak)
|
|
461
|
-
assert_eq!(limiter.peak(), 1 << 19);
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
// @behavior S-062
|
|
465
|
-
#[test]
|
|
466
|
-
fn trap_does_not_update_peak() {
|
|
467
|
-
let mut limiter = MemoryLimiter::new(Some(1 << 20));
|
|
468
|
-
limiter.activate(2 << 20);
|
|
469
|
-
assert_growing(&mut limiter, (2 << 20) + (1 << 19)); // delta=512 KiB
|
|
470
|
-
assert_trapping(&mut limiter, (2 << 20) + (2 << 20)); // would be 2 MiB > 1 MiB cap
|
|
471
|
-
// Peak reflects the last accepted grow, not the rejected desired.
|
|
472
|
-
assert_eq!(limiter.peak(), 1 << 19);
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
// @behavior S-117
|
|
476
|
-
#[test]
|
|
477
|
-
fn activate_resets_peak_for_new_invocation() {
|
|
478
|
-
let mut limiter = MemoryLimiter::new(Some(1 << 20));
|
|
479
|
-
limiter.activate(2 << 20);
|
|
480
|
-
assert_growing(&mut limiter, (2 << 20) + (1 << 19));
|
|
481
|
-
assert_eq!(limiter.peak(), 1 << 19);
|
|
482
|
-
limiter.activate(3 << 20);
|
|
483
|
-
assert_eq!(limiter.peak(), 0);
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
// @behavior S-118
|
|
487
|
-
#[test]
|
|
488
|
-
fn disabled_cap_still_tracks_peak() {
|
|
489
|
-
let mut limiter = MemoryLimiter::new(None);
|
|
490
|
-
limiter.activate(1 << 20);
|
|
491
|
-
assert_growing(&mut limiter, (1 << 20) + (4 << 20));
|
|
492
|
-
assert_eq!(limiter.peak(), 4 << 20);
|
|
493
|
-
}
|
|
494
|
-
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
//! Per-invocation I/O between the host and the guest: it writes the
|
|
2
|
+
//! entry's downcall into a freshly allocated guest buffer and reads the
|
|
3
|
+
//! Outcome the downcall answered with.
|
|
4
|
+
|
|
5
|
+
use wasmtime::Store as WtStore;
|
|
6
|
+
|
|
7
|
+
use crate::exports::Exports;
|
|
8
|
+
use crate::guest_mem::{self, ReadError, WriteError};
|
|
9
|
+
use crate::invocation::Invocation;
|
|
10
|
+
use kobako_runtime::error::{InvokeError, SetupError, Trap};
|
|
11
|
+
use kobako_transport::envelope::{CompileReply, Outcome};
|
|
12
|
+
|
|
13
|
+
/// A missing or trapping allocator is an engine fault, a `Trap`; one that
|
|
14
|
+
/// runs but returns 0 leaves the runtime intact, so it is a `SetupError`.
|
|
15
|
+
pub(crate) fn write_request(
|
|
16
|
+
store: &mut WtStore<Invocation>,
|
|
17
|
+
exports: &Exports,
|
|
18
|
+
envelope: &[u8],
|
|
19
|
+
) -> Result<(i32, i32), InvokeError> {
|
|
20
|
+
let trap = |message: String| -> InvokeError { Trap::Other(message).into() };
|
|
21
|
+
let ptr =
|
|
22
|
+
guest_mem::alloc_and_write(&mut *store, exports, envelope).map_err(|err| match err {
|
|
23
|
+
WriteError::OutOfMemory => {
|
|
24
|
+
SetupError::Intact("could not allocate input buffer (out of memory)".to_string())
|
|
25
|
+
.into()
|
|
26
|
+
}
|
|
27
|
+
WriteError::TooLarge => trap("payload exceeds the 16 MiB limit".to_string()),
|
|
28
|
+
WriteError::Incompatible(message) => trap(message.to_string()),
|
|
29
|
+
WriteError::Trapped(e) => crate::trap::cap_trap(&e)
|
|
30
|
+
.unwrap_or_else(|| Trap::Other(format!("failed to allocate input buffer: {e}")))
|
|
31
|
+
.into(),
|
|
32
|
+
WriteError::OutOfBounds => {
|
|
33
|
+
trap("the input buffer lies outside the Sandbox's memory".to_string())
|
|
34
|
+
}
|
|
35
|
+
})?;
|
|
36
|
+
// The write has already held the length to the payload cap.
|
|
37
|
+
Ok((ptr as i32, envelope.len() as i32))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/// A guest that answered without an Outcome the envelope can frame — an
|
|
41
|
+
/// empty answer included — has nothing left to attribute, so it is a
|
|
42
|
+
/// `Trap` like every other way of failing to read the result.
|
|
43
|
+
pub(crate) fn read_outcome(
|
|
44
|
+
store: &WtStore<Invocation>,
|
|
45
|
+
exports: &Exports,
|
|
46
|
+
packed: u64,
|
|
47
|
+
) -> Result<Outcome, Trap> {
|
|
48
|
+
let bytes = read_answer(store, exports, packed)?;
|
|
49
|
+
Outcome::decode(&bytes).map_err(|_| {
|
|
50
|
+
Trap::Other(
|
|
51
|
+
if bytes.is_empty() {
|
|
52
|
+
"Sandbox exited without producing a result"
|
|
53
|
+
} else {
|
|
54
|
+
"Sandbox produced an unrecognised result"
|
|
55
|
+
}
|
|
56
|
+
.to_string(),
|
|
57
|
+
)
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/// Held to the same 16 MiB bound as an Outcome; an answer that does not
|
|
62
|
+
/// frame is a `Trap` the caller treats like any other failed compile.
|
|
63
|
+
pub(crate) fn read_compile_reply(
|
|
64
|
+
store: &WtStore<Invocation>,
|
|
65
|
+
exports: &Exports,
|
|
66
|
+
packed: u64,
|
|
67
|
+
) -> Result<CompileReply, Trap> {
|
|
68
|
+
let bytes = read_answer(store, exports, packed)?;
|
|
69
|
+
CompileReply::decode(&bytes)
|
|
70
|
+
.map_err(|_| Trap::Other("Sandbox produced an unrecognised compile answer".to_string()))
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
fn read_answer(
|
|
74
|
+
store: &WtStore<Invocation>,
|
|
75
|
+
exports: &Exports,
|
|
76
|
+
packed: u64,
|
|
77
|
+
) -> Result<Vec<u8>, Trap> {
|
|
78
|
+
let mem = exports.require_memory()?;
|
|
79
|
+
let (ptr, len) = guest_mem::unpack_guest_buffer(packed);
|
|
80
|
+
guest_mem::read_buffer(store, mem, ptr, len).map_err(|err| {
|
|
81
|
+
Trap::Other(
|
|
82
|
+
match err {
|
|
83
|
+
ReadError::TooLarge => "result payload exceeds the 16 MiB limit",
|
|
84
|
+
ReadError::OutOfBounds => "the Sandbox result is out of bounds",
|
|
85
|
+
}
|
|
86
|
+
.to_string(),
|
|
87
|
+
)
|
|
88
|
+
})
|
|
89
|
+
}
|