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
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
//! A flag the bundled guest does not recognise meets the unrecognised
|
|
2
|
+
//! path: a critical one must refuse the invocation under
|
|
3
|
+
//! its own class, and an advisory one must leave the run untouched.
|
|
4
|
+
|
|
5
|
+
mod support;
|
|
6
|
+
|
|
7
|
+
use kobako_runtime::{Completion, Entry, Runtime};
|
|
8
|
+
use kobako_transport::envelope::{Flag, Origin, Outcome, Preamble};
|
|
9
|
+
|
|
10
|
+
/// The Outcome and what the script printed, or `None` when the Guest
|
|
11
|
+
/// Binary has not been built outside CI. The script prints before it
|
|
12
|
+
/// answers, so empty stdout means it never ran.
|
|
13
|
+
fn eval_with_flag(critical: bool) -> Option<(Outcome, Vec<u8>)> {
|
|
14
|
+
let driver = support::bundled_driver()?;
|
|
15
|
+
let mut preamble = Preamble::default();
|
|
16
|
+
preamble.flags = vec![Flag {
|
|
17
|
+
name: "no_such_flag".into(),
|
|
18
|
+
value: String::new(),
|
|
19
|
+
critical,
|
|
20
|
+
}];
|
|
21
|
+
|
|
22
|
+
let snapshot = driver
|
|
23
|
+
.invoke(
|
|
24
|
+
Entry::Eval {
|
|
25
|
+
source: b"puts 'ran'; 1 + 1",
|
|
26
|
+
},
|
|
27
|
+
&preamble,
|
|
28
|
+
None,
|
|
29
|
+
&[],
|
|
30
|
+
)
|
|
31
|
+
.expect("the invocation must start");
|
|
32
|
+
let Completion::Outcome(outcome) = snapshot.completion else {
|
|
33
|
+
panic!("the invocation must answer rather than trap");
|
|
34
|
+
};
|
|
35
|
+
Some((outcome, snapshot.stdout.bytes))
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// @behavior MR-017
|
|
39
|
+
#[test]
|
|
40
|
+
fn a_critical_flag_the_guest_does_not_recognise_refuses_the_invocation() {
|
|
41
|
+
let Some((outcome, stdout)) = eval_with_flag(true) else {
|
|
42
|
+
return;
|
|
43
|
+
};
|
|
44
|
+
let Outcome::Panic(panic) = outcome else {
|
|
45
|
+
panic!("a critical flag the guest does not recognise must answer with a Panic");
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
assert_eq!(
|
|
49
|
+
(panic.origin, panic.error.name.as_str(), stdout.as_slice()),
|
|
50
|
+
(Origin::Sandbox, "Kobako::UnsupportedFlagError", &b""[..]),
|
|
51
|
+
"a critical flag the bundled guest does not recognise through Driver#invoke must \
|
|
52
|
+
refuse the invocation as the Sandbox's Kobako::UnsupportedFlagError before the \
|
|
53
|
+
script runs"
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// @behavior MR-018
|
|
58
|
+
#[test]
|
|
59
|
+
fn an_advisory_flag_the_guest_does_not_recognise_is_skipped() {
|
|
60
|
+
let Some(answer) = eval_with_flag(false) else {
|
|
61
|
+
return;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
assert_eq!(
|
|
65
|
+
answer,
|
|
66
|
+
(Outcome::Ok(vec![0x02]), b"ran\n".to_vec()),
|
|
67
|
+
"an advisory flag the bundled guest does not recognise through Driver#invoke must be \
|
|
68
|
+
skipped, leaving the invocation's value as it would be without the flag"
|
|
69
|
+
);
|
|
70
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
//! The bundled guest compiles a Preamble's source snippets on an instance
|
|
2
|
+
//! of their own. What comes back has to replay in a later invocation in
|
|
3
|
+
//! place of the source, under the same flags a replay would apply, and
|
|
4
|
+
//! two compiles of the same Preamble have to agree byte for byte so
|
|
5
|
+
//! either can stand in for the other.
|
|
6
|
+
|
|
7
|
+
mod support;
|
|
8
|
+
|
|
9
|
+
use kobako_runtime::{Completion, Entry, Runtime};
|
|
10
|
+
use kobako_transport::envelope::{Flag, Outcome, Preamble, Snippet, Snippets};
|
|
11
|
+
|
|
12
|
+
use support::bundled_driver as driver;
|
|
13
|
+
|
|
14
|
+
fn preamble_with(entries: Vec<Snippet>) -> Preamble {
|
|
15
|
+
let mut preamble = Preamble::default();
|
|
16
|
+
preamble.snippets = Snippets { entries };
|
|
17
|
+
preamble
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
fn greeter() -> Preamble {
|
|
21
|
+
preamble_with(vec![Snippet::Source {
|
|
22
|
+
name: "Greeter".into(),
|
|
23
|
+
body: "module Greeter; def self.hi = 'hi'; end".into(),
|
|
24
|
+
}])
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// @behavior MR-024
|
|
28
|
+
#[test]
|
|
29
|
+
fn compiled_snippets_replay_in_a_later_invocation() {
|
|
30
|
+
let Some(driver) = driver() else {
|
|
31
|
+
return;
|
|
32
|
+
};
|
|
33
|
+
let bytecodes = driver
|
|
34
|
+
.compile(&greeter())
|
|
35
|
+
.expect("a source snippet that parses must compile");
|
|
36
|
+
let replay = preamble_with(
|
|
37
|
+
bytecodes
|
|
38
|
+
.into_iter()
|
|
39
|
+
.map(|body| Snippet::Bytecode { body })
|
|
40
|
+
.collect(),
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
let snapshot = driver
|
|
44
|
+
.invoke(
|
|
45
|
+
Entry::Eval {
|
|
46
|
+
source: b"Greeter.hi",
|
|
47
|
+
},
|
|
48
|
+
&replay,
|
|
49
|
+
None,
|
|
50
|
+
&[],
|
|
51
|
+
)
|
|
52
|
+
.expect("the invocation must start");
|
|
53
|
+
|
|
54
|
+
assert!(
|
|
55
|
+
matches!(snapshot.completion, Completion::Outcome(Outcome::Ok(ref value)) if value == b"\xa2hi"),
|
|
56
|
+
"bytecode from Driver#compile, sent back as bytecode snippets, must define what the \
|
|
57
|
+
source did for a later invocation"
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// @behavior MR-023
|
|
62
|
+
#[test]
|
|
63
|
+
fn compiling_the_same_preamble_twice_yields_the_same_bytes() {
|
|
64
|
+
let Some(driver) = driver() else {
|
|
65
|
+
return;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
assert_eq!(
|
|
69
|
+
driver.compile(&greeter()),
|
|
70
|
+
driver.compile(&greeter()),
|
|
71
|
+
"the same Preamble through Driver#compile on two fresh instances must answer \
|
|
72
|
+
byte-identical bytecode"
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// @behavior MR-025
|
|
77
|
+
#[test]
|
|
78
|
+
fn a_critical_flag_the_guest_does_not_recognise_leaves_nothing_compiled() {
|
|
79
|
+
let Some(driver) = driver() else {
|
|
80
|
+
return;
|
|
81
|
+
};
|
|
82
|
+
let mut preamble = greeter();
|
|
83
|
+
preamble.flags = vec![Flag {
|
|
84
|
+
name: "no_such_flag".into(),
|
|
85
|
+
value: String::new(),
|
|
86
|
+
critical: true,
|
|
87
|
+
}];
|
|
88
|
+
|
|
89
|
+
assert_eq!(
|
|
90
|
+
driver.compile(&preamble),
|
|
91
|
+
None,
|
|
92
|
+
"a critical flag the bundled guest does not recognise through Driver#compile must \
|
|
93
|
+
leave nothing compiled, since compiling applies the flags a replay would"
|
|
94
|
+
);
|
|
95
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//! The bundled Guest Binary every integration test drives.
|
|
2
|
+
|
|
3
|
+
use std::path::Path;
|
|
4
|
+
use std::time::Duration;
|
|
5
|
+
|
|
6
|
+
use kobako_runtime::Profile;
|
|
7
|
+
use kobako_wasmtime::{Config, Driver};
|
|
8
|
+
|
|
9
|
+
const WASM: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../data/kobako.wasm");
|
|
10
|
+
|
|
11
|
+
/// A driver over the bundled Guest Binary, or `None` when it has not
|
|
12
|
+
/// been built outside CI, where a missing build is a failure instead.
|
|
13
|
+
pub fn bundled_driver() -> Option<Driver> {
|
|
14
|
+
if !Path::new(WASM).exists() {
|
|
15
|
+
assert!(
|
|
16
|
+
std::env::var_os("CI").is_none(),
|
|
17
|
+
"data/kobako.wasm missing under CI — run `bundle exec rake wasm:build`"
|
|
18
|
+
);
|
|
19
|
+
return None;
|
|
20
|
+
}
|
|
21
|
+
let mut config = Config::default();
|
|
22
|
+
config.timeout = Some(Duration::from_secs(10));
|
|
23
|
+
config.setup_timeout = Some(Duration::from_secs(10));
|
|
24
|
+
config.memory_limit = None;
|
|
25
|
+
config.stdout_limit = None;
|
|
26
|
+
config.stderr_limit = None;
|
|
27
|
+
config.open_file_limit = None;
|
|
28
|
+
config.profile = Profile::Hermetic;
|
|
29
|
+
Some(Driver::new(Path::new(WASM), config).expect("the Guest Binary must load"))
|
|
30
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
//! A Call the host could not answer as the Service meant still reaches the
|
|
2
|
+
//! guest as an answer it can rescue, and the answer says why. The default
|
|
3
|
+
//! frontends never hand the driver either failure, so only a handler
|
|
4
|
+
//! written here reaches them.
|
|
5
|
+
|
|
6
|
+
mod support;
|
|
7
|
+
|
|
8
|
+
use std::sync::Arc;
|
|
9
|
+
|
|
10
|
+
use kobako_runtime::{Completion, DispatchHandler, Entry, Runtime, Yielder};
|
|
11
|
+
use kobako_transport::envelope::{Bindings, Call, Outcome, Preamble, Reply};
|
|
12
|
+
|
|
13
|
+
/// Answers every Call with one byte more than a message carries.
|
|
14
|
+
struct AnswersTooMuch;
|
|
15
|
+
|
|
16
|
+
impl DispatchHandler for AnswersTooMuch {
|
|
17
|
+
fn dispatch(&self, _call: Call<'_>, _yielder: &mut dyn Yielder) -> Option<Reply> {
|
|
18
|
+
Some(Reply::Ok(vec![0xc0; (16 << 20) + 1]))
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/// Breaks the handler contract by answering nothing.
|
|
23
|
+
struct AnswersNothing;
|
|
24
|
+
|
|
25
|
+
impl DispatchHandler for AnswersNothing {
|
|
26
|
+
fn dispatch(&self, _call: Call<'_>, _yielder: &mut dyn Yielder) -> Option<Reply> {
|
|
27
|
+
None
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
fn eval_against(handler: Arc<dyn DispatchHandler>, source: &[u8]) -> Option<Outcome> {
|
|
32
|
+
let driver = support::bundled_driver()?;
|
|
33
|
+
let mut preamble = Preamble::default();
|
|
34
|
+
preamble.bindings = Bindings {
|
|
35
|
+
paths: vec!["Svc".into()],
|
|
36
|
+
};
|
|
37
|
+
let snapshot = driver
|
|
38
|
+
.invoke(Entry::Eval { source }, &preamble, Some(handler), &[])
|
|
39
|
+
.expect("the invocation must start");
|
|
40
|
+
let Completion::Outcome(outcome) = snapshot.completion else {
|
|
41
|
+
panic!("the invocation must answer rather than trap");
|
|
42
|
+
};
|
|
43
|
+
Some(outcome)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// @behavior T-286
|
|
47
|
+
#[test]
|
|
48
|
+
fn an_answer_larger_than_a_message_raises_a_service_failure_naming_the_limit() {
|
|
49
|
+
let source = b"begin; Svc.go; rescue Kobako::ServiceError => e; e.message; end";
|
|
50
|
+
let Some(outcome) = eval_against(Arc::new(AnswersTooMuch), source) else {
|
|
51
|
+
return;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
let mut expected = vec![0xd9, 0x4e];
|
|
55
|
+
expected.extend_from_slice(
|
|
56
|
+
b"Sandbox could not write the Service's answer: payload exceeds the 16 MiB limit",
|
|
57
|
+
);
|
|
58
|
+
assert_eq!(
|
|
59
|
+
outcome,
|
|
60
|
+
Outcome::Ok(expected),
|
|
61
|
+
"a Service answer past the message cap through Driver#invoke must raise a Service \
|
|
62
|
+
failure in the guest naming the limit"
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// @behavior T-287
|
|
67
|
+
#[test]
|
|
68
|
+
fn a_call_the_host_could_not_answer_raises_a_transport_error_naming_why() {
|
|
69
|
+
let source = b"begin; Svc.go; rescue Kobako::Transport::Error => e; e.message; end";
|
|
70
|
+
let Some(outcome) = eval_against(Arc::new(AnswersNothing), source) else {
|
|
71
|
+
return;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
let mut expected = vec![0xd9, 0x6a];
|
|
75
|
+
expected.extend_from_slice(
|
|
76
|
+
"a Sandbox callback raised an exception instead of returning a fault — please report \
|
|
77
|
+
this as a kobako bug"
|
|
78
|
+
.as_bytes(),
|
|
79
|
+
);
|
|
80
|
+
assert_eq!(
|
|
81
|
+
outcome,
|
|
82
|
+
Outcome::Ok(expected),
|
|
83
|
+
"a Call a handler answered with nothing through Driver#invoke must raise \
|
|
84
|
+
Kobako::Transport::Error in the guest naming why"
|
|
85
|
+
);
|
|
86
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
//! A host frontend of its own may answer a guest's Call as unsupported;
|
|
2
|
+
//! the bundled guest must then hand its script a refusal it can rescue,
|
|
3
|
+
//! rather than a broken exchange. The default frontends serve every Call,
|
|
4
|
+
//! so only a handler written here reaches that answer.
|
|
5
|
+
|
|
6
|
+
mod support;
|
|
7
|
+
|
|
8
|
+
use std::sync::Arc;
|
|
9
|
+
|
|
10
|
+
use kobako_runtime::{Completion, DispatchHandler, Entry, Runtime, Yielder};
|
|
11
|
+
use kobako_transport::envelope::{Bindings, Call, Outcome, Preamble, Reply, Unsupported};
|
|
12
|
+
|
|
13
|
+
struct ServesNoCall;
|
|
14
|
+
|
|
15
|
+
impl DispatchHandler for ServesNoCall {
|
|
16
|
+
fn dispatch(&self, _call: Call<'_>, _yielder: &mut dyn Yielder) -> Option<Reply> {
|
|
17
|
+
Some(Reply::Unsupported(Unsupported::new("no calls here")))
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// @behavior T-279
|
|
22
|
+
#[test]
|
|
23
|
+
fn a_call_the_host_does_not_serve_raises_unsupported_error_in_the_guest() {
|
|
24
|
+
let Some(driver) = support::bundled_driver() else {
|
|
25
|
+
return;
|
|
26
|
+
};
|
|
27
|
+
let mut preamble = Preamble::default();
|
|
28
|
+
preamble.bindings = Bindings {
|
|
29
|
+
paths: vec!["Svc".into()],
|
|
30
|
+
};
|
|
31
|
+
let source = b"begin; Svc.go; rescue Kobako::UnsupportedError => e; e.message; end";
|
|
32
|
+
|
|
33
|
+
let snapshot = driver
|
|
34
|
+
.invoke(
|
|
35
|
+
Entry::Eval { source },
|
|
36
|
+
&preamble,
|
|
37
|
+
Some(Arc::new(ServesNoCall)),
|
|
38
|
+
&[],
|
|
39
|
+
)
|
|
40
|
+
.expect("the invocation must start");
|
|
41
|
+
|
|
42
|
+
let Completion::Outcome(outcome) = snapshot.completion else {
|
|
43
|
+
panic!("the invocation must answer rather than trap");
|
|
44
|
+
};
|
|
45
|
+
assert_eq!(
|
|
46
|
+
outcome,
|
|
47
|
+
Outcome::Ok(b"\xadno calls here".to_vec()),
|
|
48
|
+
"a Service call answered as unsupported through Driver#invoke must raise \
|
|
49
|
+
Kobako::UnsupportedError carrying the host's message"
|
|
50
|
+
);
|
|
51
|
+
}
|
data/data/kobako.wasm
CHANGED
|
Binary file
|
data/ext/kobako/Cargo.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "kobako"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.29.0"
|
|
4
4
|
edition = "2021"
|
|
5
5
|
authors = ["Aotokitsuruya <contact@aotoki.me>"]
|
|
6
6
|
license = "Apache-2.0"
|
|
@@ -10,7 +10,9 @@ publish = false
|
|
|
10
10
|
crate-type = ["cdylib"]
|
|
11
11
|
|
|
12
12
|
[dependencies]
|
|
13
|
-
|
|
13
|
+
# 0.9.3 keeps `$!` when Rust catches an exception, which a Filesystem or
|
|
14
|
+
# Service callback running while Ruby handles one would otherwise clear.
|
|
15
|
+
magnus = { version = "0.9.3", features = ["rb-sys"] }
|
|
14
16
|
# Raw Ruby FFI for the GVL release/re-acquire calls magnus does not wrap
|
|
15
17
|
# (`rb_thread_call_without_gvl` / `rb_thread_call_with_gvl`). Pinned to the
|
|
16
18
|
# rb-sys magnus resolves so both share one binding instance.
|