kobako 0.28.0 → 0.30.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 +377 -0
- data/Cargo.lock +181 -154
- data/README.md +99 -459
- data/ROADMAP.md +7 -1
- data/SECURITY.md +1 -1
- data/crates/kobako-runtime/CHANGELOG.md +354 -0
- data/crates/kobako-runtime/Cargo.toml +38 -7
- data/crates/kobako-runtime/README.md +17 -8
- data/crates/kobako-runtime/src/dispatch.rs +9 -1
- data/crates/kobako-runtime/src/endpoint/buffer.rs +192 -0
- data/crates/kobako-runtime/src/endpoint/capture.rs +115 -0
- data/crates/kobako-runtime/src/endpoint/dispatch.rs +253 -0
- data/crates/kobako-runtime/src/endpoint/invoke.rs +194 -0
- data/crates/kobako-runtime/src/endpoint/memory_cap.rs +238 -0
- data/crates/kobako-runtime/src/endpoint.rs +166 -0
- data/crates/kobako-runtime/src/filesystem/bind/fid.rs +82 -0
- data/crates/kobako-runtime/src/filesystem/bind/lookup.rs +236 -0
- data/crates/kobako-runtime/src/filesystem/bind/walk.rs +249 -0
- data/crates/kobako-runtime/src/filesystem/bind.rs +2665 -0
- data/crates/kobako-runtime/src/filesystem/overlay/file.rs +89 -0
- data/crates/kobako-runtime/src/filesystem/overlay/inode.rs +106 -0
- data/crates/kobako-runtime/src/filesystem/overlay/path.rs +285 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/copy_up.rs +135 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/dir.rs +263 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/namei.rs +135 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/readdir.rs +57 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack.rs +301 -0
- data/crates/kobako-runtime/src/filesystem/overlay.rs +3469 -0
- data/crates/kobako-runtime/src/filesystem/path.rs +21 -0
- data/crates/kobako-runtime/src/filesystem/pipe.rs +221 -0
- data/crates/kobako-runtime/src/filesystem/rdir.rs +101 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs/tmpfile.rs +32 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs/whiteout.rs +85 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs/xattr.rs +22 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs.rs +1985 -0
- data/crates/kobako-runtime/src/filesystem/wrap.rs +224 -0
- data/crates/kobako-runtime/src/filesystem.rs +26 -0
- data/crates/kobako-runtime/src/grant.rs +20 -0
- data/crates/kobako-runtime/src/hashtable.rs +44 -0
- data/crates/kobako-runtime/src/lib.rs +16 -1
- data/crates/kobako-runtime/src/locking/seqlock.rs +66 -0
- data/crates/kobako-runtime/src/locking.rs +218 -0
- data/crates/kobako-runtime/src/mount.rs +105 -0
- data/crates/kobako-runtime/src/profile.rs +140 -4
- data/crates/kobako-runtime/src/runtime.rs +12 -0
- data/crates/kobako-runtime/src/sched.rs +210 -0
- data/crates/kobako-runtime/src/security.rs +696 -0
- data/crates/kobako-runtime/src/snapshot.rs +20 -5
- data/crates/kobako-runtime/src/vfs/attr.rs +162 -0
- data/crates/kobako-runtime/src/vfs/cred.rs +334 -0
- data/crates/kobako-runtime/src/vfs/dentry.rs +1412 -0
- data/crates/kobako-runtime/src/vfs/errno.rs +153 -0
- data/crates/kobako-runtime/src/vfs/file.rs +1915 -0
- data/crates/kobako-runtime/src/vfs/inode.rs +429 -0
- data/crates/kobako-runtime/src/vfs/kern_path.rs +217 -0
- data/crates/kobako-runtime/src/vfs/mount.rs +59 -0
- data/crates/kobako-runtime/src/vfs/namei.rs +5708 -0
- data/crates/kobako-runtime/src/vfs/openat2.rs +116 -0
- data/crates/kobako-runtime/src/vfs/path.rs +64 -0
- data/crates/kobako-runtime/src/vfs/readdir.rs +163 -0
- data/crates/kobako-runtime/src/vfs/stat.rs +157 -0
- data/crates/kobako-runtime/src/vfs/super_block.rs +122 -0
- data/crates/kobako-runtime/src/vfs/utimes.rs +289 -0
- data/crates/kobako-runtime/src/vfs.rs +60 -0
- data/crates/kobako-runtime/src/yielder.rs +11 -6
- data/crates/kobako-runtime/tests/linux_baseline/calls.rs +171 -0
- data/crates/kobako-runtime/tests/linux_baseline/concurrent.rs +276 -0
- data/crates/kobako-runtime/tests/linux_baseline/kobako.rs +323 -0
- data/crates/kobako-runtime/tests/linux_baseline/linux-baseline-expected.txt +16 -0
- data/crates/kobako-runtime/tests/linux_baseline/linux.rs +449 -0
- data/crates/kobako-runtime/tests/linux_baseline/main.rs +611 -0
- data/crates/kobako-runtime/tests/linux_baseline/permissions.rs +236 -0
- data/crates/kobako-transport/CHANGELOG.md +23 -0
- data/crates/kobako-transport/Cargo.toml +3 -3
- data/crates/kobako-transport/README.md +6 -5
- data/crates/kobako-transport/src/abi.rs +1 -1
- data/crates/kobako-transport/src/envelope/bytes.rs +33 -25
- data/crates/kobako-transport/src/envelope/call.rs +5 -5
- data/crates/kobako-transport/src/envelope/compile_reply.rs +2 -2
- data/crates/kobako-transport/src/envelope/error_record.rs +3 -3
- data/crates/kobako-transport/src/envelope/fault.rs +4 -3
- data/crates/kobako-transport/src/envelope/outcome.rs +3 -3
- data/crates/kobako-transport/src/envelope/preamble.rs +24 -24
- data/crates/kobako-transport/src/envelope/reply.rs +34 -16
- data/crates/kobako-transport/src/envelope/request.rs +19 -9
- data/crates/kobako-transport/src/envelope/run.rs +1 -1
- data/crates/kobako-transport/src/envelope/unsupported.rs +2 -2
- data/crates/kobako-transport/src/envelope.rs +2 -2
- data/crates/kobako-wasmtime/CHANGELOG.md +265 -0
- data/crates/kobako-wasmtime/Cargo.toml +25 -19
- data/crates/kobako-wasmtime/README.md +13 -7
- data/crates/kobako-wasmtime/src/abi.rs +75 -49
- data/crates/kobako-wasmtime/src/ambient.rs +64 -6
- data/crates/kobako-wasmtime/src/cache.rs +188 -77
- data/crates/kobako-wasmtime/src/config.rs +77 -5
- data/crates/kobako-wasmtime/src/descriptors.rs +200 -0
- data/crates/kobako-wasmtime/src/driver.rs +70 -135
- data/crates/kobako-wasmtime/src/exports.rs +5 -16
- data/crates/kobako-wasmtime/src/guest.rs +38 -0
- data/crates/kobako-wasmtime/src/imports.rs +52 -0
- data/crates/kobako-wasmtime/src/instance_pre.rs +18 -46
- data/crates/kobako-wasmtime/src/invocation.rs +43 -74
- data/crates/kobako-wasmtime/src/lib.rs +5 -4
- data/crates/kobako-wasmtime/src/limiter.rs +6 -230
- data/crates/kobako-wasmtime/src/preview1/differential/mount.rs +632 -0
- data/crates/kobako-wasmtime/src/preview1/differential.rs +486 -0
- data/crates/kobako-wasmtime/src/preview1/errno.rs +135 -0
- data/crates/kobako-wasmtime/src/preview1/fd.rs +1420 -0
- data/crates/kobako-wasmtime/src/preview1/memory.rs +172 -0
- data/crates/kobako-wasmtime/src/preview1/mount_view/wrap.rs +211 -0
- data/crates/kobako-wasmtime/src/preview1/mount_view.rs +3766 -0
- data/crates/kobako-wasmtime/src/preview1/path.rs +302 -0
- data/crates/kobako-wasmtime/src/preview1/poll.rs +381 -0
- data/crates/kobako-wasmtime/src/preview1/sock.rs +13 -0
- data/crates/kobako-wasmtime/src/preview1/testsuite-expected.txt +6 -0
- data/crates/kobako-wasmtime/src/preview1/testsuite.rs +157 -0
- data/crates/kobako-wasmtime/src/preview1.rs +512 -0
- data/crates/kobako-wasmtime/src/trap.rs +45 -25
- data/crates/kobako-wasmtime/src/wasi.rs +333 -0
- data/crates/kobako-wasmtime/tests/integration/compile_posture.rs +43 -0
- data/crates/kobako-wasmtime/tests/integration/guest_file.rs +1085 -0
- data/crates/kobako-wasmtime/tests/integration/main.rs +29 -0
- data/crates/kobako-wasmtime/tests/{preamble_flags.rs → integration/preamble_flags.rs} +4 -3
- data/crates/kobako-wasmtime/tests/{snippet_compile.rs → integration/snippet_compile.rs} +5 -4
- data/crates/kobako-wasmtime/tests/{support → integration/support}/mod.rs +9 -13
- data/crates/kobako-wasmtime/tests/integration/unanswered_call.rs +86 -0
- data/crates/kobako-wasmtime/tests/{unsupported_call.rs → integration/unsupported_call.rs} +2 -1
- data/crates/kobako-wasmtime/witx/typenames.witx +750 -0
- data/crates/kobako-wasmtime/witx/wasi_snapshot_preview1.witx +441 -0
- data/data/kobako.wasm +0 -0
- data/ext/kobako/Cargo.toml +4 -2
- data/ext/kobako/src/runtime/bridge.rs +154 -81
- data/ext/kobako/src/runtime/errors.rs +1 -1
- data/ext/kobako/src/runtime/escape.rs +67 -0
- data/ext/kobako/src/runtime/filesystem/grants.rs +107 -0
- data/ext/kobako/src/runtime/filesystem/ruby.rs +708 -0
- data/ext/kobako/src/runtime/filesystem.rs +208 -0
- data/ext/kobako/src/runtime/gvl.rs +49 -8
- 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 +125 -199
- data/lib/kobako/catalog/extensions.rb +5 -5
- data/lib/kobako/catalog/flags.rb +2 -14
- data/lib/kobako/catalog/grants.rb +30 -0
- data/lib/kobako/catalog/handles.rb +6 -6
- data/lib/kobako/catalog/mount.rb +19 -0
- data/lib/kobako/catalog/services.rb +13 -2
- data/lib/kobako/catalog/snippets.rb +11 -15
- data/lib/kobako/catalog.rb +11 -0
- data/lib/kobako/codec/decoder.rb +10 -9
- data/lib/kobako/codec/ext_types.rb +4 -6
- data/lib/kobako/codec/handle_walk.rb +4 -6
- data/lib/kobako/codec/nesting.rb +7 -7
- data/lib/kobako/codec/state.rb +19 -3
- data/lib/kobako/codec/utils.rb +1 -1
- data/lib/kobako/codec/value_budget.rb +86 -0
- data/lib/kobako/codec.rb +7 -0
- data/lib/kobako/context.rb +40 -13
- data/lib/kobako/errors.rb +15 -14
- data/lib/kobako/filesystem.rb +69 -0
- data/lib/kobako/payload/arguments.rb +2 -2
- data/lib/kobako/pool.rb +6 -6
- data/lib/kobako/sandbox.rb +55 -27
- data/lib/kobako/sandbox_options.rb +39 -13
- data/lib/kobako/snippet/{binary.rb → bytecode.rb} +2 -2
- data/lib/kobako/snippet/source.rb +1 -1
- data/lib/kobako/snippet.rb +1 -1
- data/lib/kobako/transport/block_failure.rb +35 -0
- data/lib/kobako/transport/dispatcher.rb +33 -29
- data/lib/kobako/transport/exposure.rb +7 -9
- data/lib/kobako/transport/reflection.rb +79 -23
- data/lib/kobako/transport/run.rb +5 -5
- data/lib/kobako/transport/yielder.rb +9 -31
- data/lib/kobako/transport.rb +1 -0
- data/lib/kobako/version.rb +1 -1
- data/lib/kobako.rb +35 -0
- data/release-please-config.json +1 -0
- data/sig/kobako/catalog/extensions.rbs +3 -3
- data/sig/kobako/catalog/flags.rbs +0 -4
- data/sig/kobako/catalog/grants.rbs +13 -0
- data/sig/kobako/catalog/handles.rbs +3 -3
- data/sig/kobako/catalog/mount.rbs +24 -0
- data/sig/kobako/catalog/services.rbs +5 -1
- data/sig/kobako/catalog/snippets.rbs +4 -4
- data/sig/kobako/catalog.rbs +1 -0
- data/sig/kobako/codec/decoder.rbs +1 -1
- data/sig/kobako/codec/handle_walk.rbs +1 -1
- data/sig/kobako/codec/nesting.rbs +2 -2
- data/sig/kobako/codec/state.rbs +7 -1
- data/sig/kobako/codec/utils.rbs +1 -1
- data/sig/kobako/codec/value_budget.rbs +23 -0
- data/sig/kobako/codec.rbs +3 -2
- data/sig/kobako/context.rbs +14 -6
- data/sig/kobako/errors.rbs +4 -4
- data/sig/kobako/filesystem.rbs +93 -0
- data/sig/kobako/guest_yielder.rbs +9 -0
- data/sig/kobako/payload/arguments.rbs +1 -1
- data/sig/kobako/pool.rbs +3 -3
- data/sig/kobako/runtime.rbs +21 -49
- data/sig/kobako/sandbox.rbs +9 -5
- data/sig/kobako/sandbox_options.rbs +25 -3
- 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 +4 -4
- data/sig/kobako/transport/exposure.rbs +2 -2
- data/sig/kobako/transport/reflection.rbs +14 -1
- data/sig/kobako/transport/run.rbs +3 -3
- data/sig/kobako/transport/yielder.rbs +3 -7
- data/sig/kobako/transport.rbs +3 -3
- metadata +116 -15
- data/crates/kobako-wasmtime/src/capture.rs +0 -96
- data/crates/kobako-wasmtime/src/dispatch.rs +0 -107
- data/crates/kobako-wasmtime/src/guest_mem.rs +0 -265
- data/crates/kobako-wasmtime/src/io.rs +0 -286
- data/sig/kobako/snippet/binary.rbs +0 -12
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
//! Holds what a caller is refused to the Linux that refuses it: a Policy to the
|
|
2
|
+
//! same rules under Landlock, and a Bind to a process of another user.
|
|
3
|
+
|
|
4
|
+
use std::collections::BTreeMap;
|
|
5
|
+
use std::ffi::CString;
|
|
6
|
+
use std::fs;
|
|
7
|
+
use std::io;
|
|
8
|
+
use std::os::unix::fs::PermissionsExt;
|
|
9
|
+
use std::sync::{Arc, PoisonError};
|
|
10
|
+
use std::thread;
|
|
11
|
+
|
|
12
|
+
use kobako_runtime::filesystem::Filesystem;
|
|
13
|
+
use kobako_runtime::mount::{Access, Policy};
|
|
14
|
+
|
|
15
|
+
use crate::calls::{Call, Rng};
|
|
16
|
+
use crate::kobako::Kobako;
|
|
17
|
+
use crate::linux::Linux;
|
|
18
|
+
use crate::{assert_named, bind, class, note, seed_directory, Difference, Setup, BASE, SERIAL};
|
|
19
|
+
|
|
20
|
+
/// Landlock's right to read a file and to list a directory.
|
|
21
|
+
const LANDLOCK_READ: u64 = (1 << 2) | (1 << 3);
|
|
22
|
+
/// Every right Landlock's filesystem rules name.
|
|
23
|
+
const LANDLOCK_ALL: u64 = (1 << 16) - 1;
|
|
24
|
+
|
|
25
|
+
const SYS_LANDLOCK_CREATE_RULESET: libc::c_long = 444;
|
|
26
|
+
const SYS_LANDLOCK_ADD_RULE: libc::c_long = 445;
|
|
27
|
+
const SYS_LANDLOCK_RESTRICT_SELF: libc::c_long = 446;
|
|
28
|
+
const LANDLOCK_RULE_PATH_BENEATH: libc::c_long = 1;
|
|
29
|
+
|
|
30
|
+
#[repr(C)]
|
|
31
|
+
struct RulesetAttr {
|
|
32
|
+
handled_access_fs: u64,
|
|
33
|
+
handled_access_net: u64,
|
|
34
|
+
scoped: u64,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
#[repr(C, packed)]
|
|
38
|
+
struct PathBeneath {
|
|
39
|
+
allowed_access: u64,
|
|
40
|
+
parent_fd: i32,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/// Reads anywhere, and changes only beneath `d`.
|
|
44
|
+
struct WritesBeneathD;
|
|
45
|
+
|
|
46
|
+
impl Policy for WritesBeneathD {
|
|
47
|
+
fn allows(&self, path: &str, access: Access) -> bool {
|
|
48
|
+
access == Access::Read || path.starts_with("/baseline/d/")
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/// Hold this thread to the rule `WritesBeneathD` states, over `root`, and leave it
|
|
53
|
+
/// `unjudged` to do anything, as kobako's own host directory is no guest's.
|
|
54
|
+
fn restrict_as_writes_beneath_d(root: &str, unjudged: &str) {
|
|
55
|
+
let attr = RulesetAttr {
|
|
56
|
+
handled_access_fs: LANDLOCK_ALL,
|
|
57
|
+
handled_access_net: 0,
|
|
58
|
+
scoped: 0,
|
|
59
|
+
};
|
|
60
|
+
// SAFETY: each pointer names a live value of the size its call is told, and
|
|
61
|
+
// each descriptor is one this call opened.
|
|
62
|
+
unsafe {
|
|
63
|
+
assert_eq!(libc::prctl(libc::PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0), 0);
|
|
64
|
+
let size = size_of::<RulesetAttr>();
|
|
65
|
+
let ruleset = libc::syscall(SYS_LANDLOCK_CREATE_RULESET, &raw const attr, size, 0);
|
|
66
|
+
assert!(
|
|
67
|
+
ruleset >= 0,
|
|
68
|
+
"this kernel must enforce Landlock: {}",
|
|
69
|
+
io::Error::last_os_error()
|
|
70
|
+
);
|
|
71
|
+
let rules = [
|
|
72
|
+
(root.to_string(), LANDLOCK_READ),
|
|
73
|
+
(format!("{root}/d"), LANDLOCK_ALL),
|
|
74
|
+
(unjudged.to_string(), LANDLOCK_ALL),
|
|
75
|
+
];
|
|
76
|
+
for (path, allowed_access) in rules {
|
|
77
|
+
let path = CString::new(path).expect("no path holds a NUL");
|
|
78
|
+
let parent_fd = libc::open(path.as_ptr(), libc::O_PATH | libc::O_CLOEXEC);
|
|
79
|
+
assert!(parent_fd >= 0, "{}", io::Error::last_os_error());
|
|
80
|
+
let rule = PathBeneath {
|
|
81
|
+
allowed_access,
|
|
82
|
+
parent_fd,
|
|
83
|
+
};
|
|
84
|
+
let added = libc::syscall(
|
|
85
|
+
SYS_LANDLOCK_ADD_RULE,
|
|
86
|
+
ruleset,
|
|
87
|
+
LANDLOCK_RULE_PATH_BENEATH,
|
|
88
|
+
&raw const rule,
|
|
89
|
+
0,
|
|
90
|
+
);
|
|
91
|
+
assert_eq!(added, 0, "{}", io::Error::last_os_error());
|
|
92
|
+
libc::close(parent_fd);
|
|
93
|
+
}
|
|
94
|
+
assert_eq!(libc::syscall(SYS_LANDLOCK_RESTRICT_SELF, ruleset, 0), 0);
|
|
95
|
+
libc::close(ruleset as i32);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/// Run `sequence` through both sides until they disagree, as `differs` says of
|
|
100
|
+
/// two answers, noting what it does.
|
|
101
|
+
fn compare(
|
|
102
|
+
sequence: &[Call],
|
|
103
|
+
mut kobako: Kobako,
|
|
104
|
+
mut linux: Linux,
|
|
105
|
+
differs: impl Fn(&str, &str) -> bool,
|
|
106
|
+
found: &mut BTreeMap<String, Difference>,
|
|
107
|
+
) {
|
|
108
|
+
for (at, call) in sequence.iter().enumerate() {
|
|
109
|
+
let (theirs, ours) = (linux.run(call), kobako.run(call));
|
|
110
|
+
if differs(&ours, &theirs) {
|
|
111
|
+
let detail = format!("kobako={ours} linux={theirs}");
|
|
112
|
+
note(found, class(call, &ours, &theirs), &sequence[..=at], detail);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
fn sequence(seed: u64, longest: u64, paths: &[String]) -> Vec<Call> {
|
|
119
|
+
let mut rng = Rng::new(seed);
|
|
120
|
+
let length = 1 + rng.random_below(longest);
|
|
121
|
+
(0..length).map(|_| rng.call(paths)).collect()
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
#[test]
|
|
125
|
+
#[ignore = "needs root and a mount namespace; run by rake crates:linux_baseline"]
|
|
126
|
+
fn a_policy_answers_as_the_same_rules_under_landlock() {
|
|
127
|
+
let _serial = SERIAL.lock().unwrap_or_else(PoisonError::into_inner);
|
|
128
|
+
let setup = Setup::Bind;
|
|
129
|
+
setup.prepare();
|
|
130
|
+
let paths = setup.paths();
|
|
131
|
+
let mut found = BTreeMap::new();
|
|
132
|
+
for seed in 1..=15_000 {
|
|
133
|
+
let (root, kobako_dir) = (setup.dir("linux"), setup.dir("kobako"));
|
|
134
|
+
seed_directory(&kobako_dir);
|
|
135
|
+
seed_directory(&root);
|
|
136
|
+
let filesystem =
|
|
137
|
+
Filesystem::new(Arc::new(bind(&kobako_dir))).expect("the instance must stand");
|
|
138
|
+
let kobako = Kobako::with_policy(filesystem, Arc::new(WritesBeneathD));
|
|
139
|
+
let linux = Linux::new(&root, false);
|
|
140
|
+
let calls = sequence(seed, 30, &paths);
|
|
141
|
+
let found = &mut found;
|
|
142
|
+
thread::scope(|scope| {
|
|
143
|
+
scope
|
|
144
|
+
.spawn(|| {
|
|
145
|
+
restrict_as_writes_beneath_d(&root, &kobako_dir);
|
|
146
|
+
compare(&calls, kobako, linux, |ours, theirs| ours != theirs, found);
|
|
147
|
+
})
|
|
148
|
+
.join()
|
|
149
|
+
.expect("a sequence must not panic");
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
assert_named("policy", &found);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/// A tree another user meets refused in each way a mode refuses: a directory it
|
|
156
|
+
/// may only read, search, or not reach, and files it may only read, only write,
|
|
157
|
+
/// or not open.
|
|
158
|
+
fn seed_permissions(dir: &str) {
|
|
159
|
+
let mode = |path: String, mode| {
|
|
160
|
+
fs::set_permissions(path, fs::Permissions::from_mode(mode)).expect("the mode must be set");
|
|
161
|
+
};
|
|
162
|
+
let _ = fs::remove_dir_all(dir);
|
|
163
|
+
fs::create_dir_all(dir).expect("the host directory");
|
|
164
|
+
mode(dir.to_string(), 0o777);
|
|
165
|
+
for (name, bits) in [
|
|
166
|
+
("ro", 0o755),
|
|
167
|
+
("nox", 0o704),
|
|
168
|
+
("nor", 0o701),
|
|
169
|
+
("no", 0o700),
|
|
170
|
+
("w", 0o777),
|
|
171
|
+
] {
|
|
172
|
+
let sub = format!("{dir}/{name}");
|
|
173
|
+
fs::create_dir(&sub).expect("the host directory");
|
|
174
|
+
fs::write(format!("{sub}/f"), "x").expect("the host file");
|
|
175
|
+
mode(format!("{sub}/f"), 0o666);
|
|
176
|
+
mode(sub, bits);
|
|
177
|
+
}
|
|
178
|
+
for (name, bits) in [("fro", 0o644), ("fwo", 0o602), ("fno", 0o600)] {
|
|
179
|
+
fs::write(format!("{dir}/{name}"), "x").expect("the host file");
|
|
180
|
+
mode(format!("{dir}/{name}"), bits);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const PERMISSION_PATHS: [&str; 22] = [
|
|
185
|
+
".", "x", "ro", "ro/", "ro/f", "ro/x", "nox", "nox/", "nox/f", "nox/x", "nor", "nor/", "nor/f",
|
|
186
|
+
"nor/x", "no", "no/", "no/f", "fro", "fwo", "fno", "w/f", "w/x",
|
|
187
|
+
];
|
|
188
|
+
|
|
189
|
+
/// Whether one side succeeded where the other was refused. kobako has no layer
|
|
190
|
+
/// of permissions, so which refusal answers first is the host's to reach, and two
|
|
191
|
+
/// refusals of one call agree whatever each names.
|
|
192
|
+
fn one_succeeded(ours: &str, theirs: &str) -> bool {
|
|
193
|
+
let refused = |answer: &str| answer.starts_with('E');
|
|
194
|
+
refused(ours) != refused(theirs)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
#[test]
|
|
198
|
+
#[ignore = "needs root and a mount namespace; run by rake crates:linux_baseline"]
|
|
199
|
+
fn a_bind_refuses_another_user_as_its_host_does() {
|
|
200
|
+
let _serial = SERIAL.lock().unwrap_or_else(PoisonError::into_inner);
|
|
201
|
+
let setup = Setup::Bind;
|
|
202
|
+
setup.prepare();
|
|
203
|
+
fs::set_permissions(BASE, fs::Permissions::from_mode(0o755)).expect("the mode must be set");
|
|
204
|
+
let paths = PERMISSION_PATHS.map(str::to_string);
|
|
205
|
+
let mut found = BTreeMap::new();
|
|
206
|
+
for seed in 1..=12_000 {
|
|
207
|
+
let (root, kobako_dir) = (setup.dir("linux"), setup.dir("kobako"));
|
|
208
|
+
seed_permissions(&kobako_dir);
|
|
209
|
+
seed_permissions(&root);
|
|
210
|
+
let filesystem =
|
|
211
|
+
Filesystem::new(Arc::new(bind(&kobako_dir))).expect("the instance must stand");
|
|
212
|
+
let calls = sequence(seed, 20, &paths);
|
|
213
|
+
let found = &mut found;
|
|
214
|
+
thread::scope(|scope| {
|
|
215
|
+
scope
|
|
216
|
+
.spawn(|| {
|
|
217
|
+
// SAFETY: these change only this thread's credentials, as raw
|
|
218
|
+
// syscalls do where libc's wrappers would change every thread's.
|
|
219
|
+
unsafe {
|
|
220
|
+
assert_eq!(
|
|
221
|
+
libc::syscall(libc::SYS_setgroups, 0, std::ptr::null::<u32>()),
|
|
222
|
+
0
|
|
223
|
+
);
|
|
224
|
+
assert_eq!(libc::syscall(libc::SYS_setresgid, 1000, 1000, 1000), 0);
|
|
225
|
+
assert_eq!(libc::syscall(libc::SYS_setresuid, 1000, 1000, 1000), 0);
|
|
226
|
+
}
|
|
227
|
+
let kobako = Kobako::new(filesystem, Access::ReadWrite, false);
|
|
228
|
+
let linux = Linux::new(&root, false);
|
|
229
|
+
compare(&calls, kobako, linux, one_succeeded, found);
|
|
230
|
+
})
|
|
231
|
+
.join()
|
|
232
|
+
.expect("a sequence must not panic");
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
assert_named("another-user", &found);
|
|
236
|
+
}
|
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.20.0](https://github.com/elct9620/kobako/compare/kobako-transport-v0.19.0...kobako-transport-v0.20.0) (2026-11-07)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **transport:** call the Envelope by its name when one fails to decode ([411c8d5](https://github.com/elct9620/kobako/commit/411c8d505e2d939b8014b5406b1a9625b865f02b))
|
|
9
|
+
|
|
10
|
+
## [0.19.0](https://github.com/elct9620/kobako/compare/kobako-transport-v0.18.0...kobako-transport-v0.19.0) (2026-10-26)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### ⚠ BREAKING CHANGES
|
|
14
|
+
|
|
15
|
+
* the Guest ABI is version 5, so a Guest Binary built against version 4 is refused at Sandbox construction until rebuilt. In kobako-transport, Reply gains a Break arm and YieldReply::Break carries no payload. The Yielder trait of kobako-runtime gains frame and takes the frame in yield_to_block; the Guest trait of kobako-core and the MrbGuest trait of kobako-mruby take the frame in yield_to_block; Downcall::Yield carries frame and args.
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
* let a block's break or return unwind a Service as a plain method ([4e9eca0](https://github.com/elct9620/kobako/commit/4e9eca05f1647c8b6409fc00d08317b431c0c648))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Bug Fixes
|
|
23
|
+
|
|
24
|
+
* run a stored block as the block of the Call that carried it ([bf33dc1](https://github.com/elct9620/kobako/commit/bf33dc1fd40c83498c6c12b0be52b3a835a144cf))
|
|
25
|
+
|
|
3
26
|
## [0.18.0](https://github.com/elct9620/kobako/compare/kobako-transport-v0.17.0...kobako-transport-v0.18.0) (2026-10-13)
|
|
4
27
|
|
|
5
28
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# kobako-transport — the fixed tier of the kobako wire.
|
|
2
2
|
#
|
|
3
|
-
# The
|
|
3
|
+
# The Envelope: the outer frame every host↔guest message rides in,
|
|
4
4
|
# carrying routing and outcome attribution and handing the payload
|
|
5
5
|
# through untouched. A host, a payload codec, and a guest are chosen
|
|
6
6
|
# independently only because this layer is the same in every assembly,
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
|
|
13
13
|
[package]
|
|
14
14
|
name = "kobako-transport"
|
|
15
|
-
version = "0.
|
|
15
|
+
version = "0.20.0"
|
|
16
16
|
edition = "2021"
|
|
17
|
-
description = "The kobako
|
|
17
|
+
description = "The kobako Envelope —the fixed host↔guest wire frame every assembly shares."
|
|
18
18
|
license = "Apache-2.0"
|
|
19
19
|
repository = "https://github.com/elct9620/kobako"
|
|
20
20
|
readme = "README.md"
|
|
@@ -9,7 +9,7 @@ same in every assembly. This crate is both of them, and nothing else.
|
|
|
9
9
|
|
|
10
10
|
| Module | What it fixes |
|
|
11
11
|
|---|---|
|
|
12
|
-
| `envelope` | the **
|
|
12
|
+
| `envelope` | the **Envelope**, the outer frame each message rides in |
|
|
13
13
|
| `abi` | the values both sides agree on before exchanging a byte |
|
|
14
14
|
|
|
15
15
|
The envelope covers the downcall's and the upcall's requests under one
|
|
@@ -28,7 +28,7 @@ so that payload reaches its reader as a view rather than a copy.
|
|
|
28
28
|
|
|
29
29
|
```text
|
|
30
30
|
Call { method, payload }
|
|
31
|
-
|
|
31
|
+
+-- opaque here; read by whoever owns the schema
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
## Dependency Position
|
|
@@ -37,8 +37,9 @@ This crate depends on no other, and every kobako tier depends on it —
|
|
|
37
37
|
including any third-party engine, codec, or guest composing against them.
|
|
38
38
|
|
|
39
39
|
```text
|
|
40
|
-
host kobako-runtime
|
|
41
|
-
|
|
40
|
+
host kobako-runtime, kobako-wasmtime --+
|
|
41
|
+
+--> kobako-transport
|
|
42
|
+
guest kobako-core, kobako-mruby --------+
|
|
42
43
|
```
|
|
43
44
|
|
|
44
45
|
The golden vectors in this crate are derived from the kobako repository's
|
|
@@ -51,7 +52,7 @@ reaches the same types through `kobako-core`.
|
|
|
51
52
|
|
|
52
53
|
```toml
|
|
53
54
|
[dependencies]
|
|
54
|
-
kobako-transport = "0.
|
|
55
|
+
kobako-transport = "0.20.0" # x-release-please-version
|
|
55
56
|
```
|
|
56
57
|
|
|
57
58
|
## License
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
/// version it reports equals this one, so a framing or symbol change is
|
|
11
11
|
/// an increment rather than a negotiation. A new request tag needs
|
|
12
12
|
/// neither, but a new answer arm is an increment.
|
|
13
|
-
pub const ABI_VERSION: u32 =
|
|
13
|
+
pub const ABI_VERSION: u32 = 5;
|
|
14
14
|
|
|
15
15
|
/// Width in bytes of the length prefix that frames a message on WASI
|
|
16
16
|
/// stdin, such as the Preamble.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//! Byte primitives for the
|
|
1
|
+
//! Byte primitives for the Envelope: unsigned big-endian integers,
|
|
2
2
|
//! length-prefixed byte strings, counted lists, and the trailing remainder.
|
|
3
3
|
//!
|
|
4
4
|
//! `Reader` borrows rather than copies, so a decoded envelope's payload is
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
use super::DecodeError;
|
|
9
9
|
|
|
10
|
-
const MALFORMED: DecodeError = DecodeError::new("
|
|
10
|
+
const MALFORMED: DecodeError = DecodeError::new("Envelope ended before its declared length");
|
|
11
11
|
|
|
12
12
|
/// Cursor over one message. Every read either advances past a complete
|
|
13
13
|
/// field or fails, so a truncated message can never be mistaken for a
|
|
@@ -22,13 +22,13 @@ impl<'a> Reader<'a> {
|
|
|
22
22
|
Reader { bytes, pos: 0 }
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
pub(crate) fn
|
|
25
|
+
pub(crate) fn read_u8(&mut self) -> Result<u8, DecodeError> {
|
|
26
26
|
let byte = *self.bytes.get(self.pos).ok_or(MALFORMED)?;
|
|
27
27
|
self.pos += 1;
|
|
28
28
|
Ok(byte)
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
pub(crate) fn
|
|
31
|
+
pub(crate) fn read_u32(&mut self) -> Result<u32, DecodeError> {
|
|
32
32
|
let end = self.pos.checked_add(4).ok_or(MALFORMED)?;
|
|
33
33
|
let slice = self.bytes.get(self.pos..end).ok_or(MALFORMED)?;
|
|
34
34
|
self.pos = end;
|
|
@@ -37,8 +37,8 @@ impl<'a> Reader<'a> {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/// A `u32` length followed by that many bytes.
|
|
40
|
-
pub(crate) fn
|
|
41
|
-
let len = self.
|
|
40
|
+
pub(crate) fn read_bytes(&mut self) -> Result<&'a [u8], DecodeError> {
|
|
41
|
+
let len = self.read_u32()? as usize;
|
|
42
42
|
let end = self.pos.checked_add(len).ok_or(MALFORMED)?;
|
|
43
43
|
let slice = self.bytes.get(self.pos..end).ok_or(MALFORMED)?;
|
|
44
44
|
self.pos = end;
|
|
@@ -48,9 +48,9 @@ impl<'a> Reader<'a> {
|
|
|
48
48
|
/// A byte string carrying UTF-8. Every text field in the envelope is
|
|
49
49
|
/// validated at decode so a frontend receives `&str` without repeating
|
|
50
50
|
/// the check.
|
|
51
|
-
pub(crate) fn
|
|
52
|
-
core::str::from_utf8(self.
|
|
53
|
-
.map_err(|_| DecodeError::new("
|
|
51
|
+
pub(crate) fn read_text(&mut self) -> Result<&'a str, DecodeError> {
|
|
52
|
+
core::str::from_utf8(self.read_bytes()?)
|
|
53
|
+
.map_err(|_| DecodeError::new("Envelope text field is not valid UTF-8"))
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
/// A `u32` count followed by that many byte strings, each carrying
|
|
@@ -58,21 +58,21 @@ impl<'a> Reader<'a> {
|
|
|
58
58
|
///
|
|
59
59
|
/// The elements are copied rather than borrowed: a binding's path and
|
|
60
60
|
/// a panic's `available` names outlive the frame buffer they arrive in.
|
|
61
|
-
pub(crate) fn
|
|
62
|
-
self.
|
|
61
|
+
pub(crate) fn read_text_list(&mut self) -> Result<Vec<String>, DecodeError> {
|
|
62
|
+
self.read_list(|reader| Ok(reader.read_text()?.to_owned()))
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
/// A `u32` count followed by that many byte strings of any content,
|
|
66
|
-
/// copied for the same reason as `
|
|
67
|
-
pub(crate) fn
|
|
68
|
-
self.
|
|
66
|
+
/// copied for the same reason as `read_text_list`.
|
|
67
|
+
pub(crate) fn read_bytes_list(&mut self) -> Result<Vec<Vec<u8>>, DecodeError> {
|
|
68
|
+
self.read_list(|reader| Ok(reader.read_bytes()?.to_vec()))
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
fn
|
|
71
|
+
fn read_list<T>(
|
|
72
72
|
&mut self,
|
|
73
73
|
mut element: impl FnMut(&mut Self) -> Result<T, DecodeError>,
|
|
74
74
|
) -> Result<Vec<T>, DecodeError> {
|
|
75
|
-
let count = self.
|
|
75
|
+
let count = self.read_u32()? as usize;
|
|
76
76
|
// Every element opens with a `u32` length, so a count past what the
|
|
77
77
|
// bytes left can hold is refused before it sizes the allocation.
|
|
78
78
|
if count > self.remaining().len() / size_of::<u32>() {
|
|
@@ -106,7 +106,7 @@ impl<'a> Reader<'a> {
|
|
|
106
106
|
Ok(())
|
|
107
107
|
} else {
|
|
108
108
|
Err(DecodeError::new(
|
|
109
|
-
"
|
|
109
|
+
"Envelope carries bytes past its last field",
|
|
110
110
|
))
|
|
111
111
|
}
|
|
112
112
|
}
|
|
@@ -175,15 +175,23 @@ mod tests {
|
|
|
175
175
|
let encoded = w.into_bytes();
|
|
176
176
|
|
|
177
177
|
let mut r = Reader::new(&encoded);
|
|
178
|
-
assert_eq!(r.u8(), Ok(7), "a u8 must round-trip through the writer");
|
|
179
|
-
assert_eq!(r.u32(), Ok(0xdead_beef), "a u32 must round-trip big-endian");
|
|
180
178
|
assert_eq!(
|
|
181
|
-
r.
|
|
179
|
+
r.read_u8(),
|
|
180
|
+
Ok(7),
|
|
181
|
+
"a u8 must round-trip through the writer"
|
|
182
|
+
);
|
|
183
|
+
assert_eq!(
|
|
184
|
+
r.read_u32(),
|
|
185
|
+
Ok(0xdead_beef),
|
|
186
|
+
"a u32 must round-trip big-endian"
|
|
187
|
+
);
|
|
188
|
+
assert_eq!(
|
|
189
|
+
r.read_bytes(),
|
|
182
190
|
Ok(&b"kv"[..]),
|
|
183
191
|
"a length-prefixed byte string must round-trip"
|
|
184
192
|
);
|
|
185
193
|
assert_eq!(
|
|
186
|
-
r.
|
|
194
|
+
r.read_text_list(),
|
|
187
195
|
Ok(vec!["a".to_owned(), "bc".to_owned()]),
|
|
188
196
|
"a counted list must round-trip element for element"
|
|
189
197
|
);
|
|
@@ -213,7 +221,7 @@ mod tests {
|
|
|
213
221
|
let bytes = [0, 0, 0, 8, b'h', b'i'];
|
|
214
222
|
let mut r = Reader::new(&bytes);
|
|
215
223
|
assert!(
|
|
216
|
-
r.
|
|
224
|
+
r.read_bytes().is_err(),
|
|
217
225
|
"a byte string whose length overruns the message must be rejected, not truncated"
|
|
218
226
|
);
|
|
219
227
|
}
|
|
@@ -225,7 +233,7 @@ mod tests {
|
|
|
225
233
|
let bytes = [0xff, 0xff, 0xff, 0xff];
|
|
226
234
|
let mut r = Reader::new(&bytes);
|
|
227
235
|
assert!(
|
|
228
|
-
r.
|
|
236
|
+
r.read_text_list().is_err(),
|
|
229
237
|
"a list count the message cannot satisfy must be rejected before any allocation"
|
|
230
238
|
);
|
|
231
239
|
}
|
|
@@ -238,7 +246,7 @@ mod tests {
|
|
|
238
246
|
let encoded = w.into_bytes();
|
|
239
247
|
let mut r = Reader::new(&encoded);
|
|
240
248
|
assert!(
|
|
241
|
-
r.
|
|
249
|
+
r.read_text().is_err(),
|
|
242
250
|
"a text field carrying non-UTF-8 bytes must be rejected at decode"
|
|
243
251
|
);
|
|
244
252
|
}
|
|
@@ -250,7 +258,7 @@ mod tests {
|
|
|
250
258
|
w.bytes(b"ok").remainder(b"!");
|
|
251
259
|
let encoded = w.into_bytes();
|
|
252
260
|
let mut r = Reader::new(&encoded);
|
|
253
|
-
r.
|
|
261
|
+
r.read_bytes().unwrap();
|
|
254
262
|
assert!(
|
|
255
263
|
r.finish().is_err(),
|
|
256
264
|
"bytes past an envelope's last field must fail loudly as a framing desync"
|
|
@@ -36,9 +36,9 @@ pub struct Call<'a> {
|
|
|
36
36
|
impl<'a> Call<'a> {
|
|
37
37
|
pub fn decode(bytes: &'a [u8]) -> Result<Self, DecodeError> {
|
|
38
38
|
let mut reader = Reader::new(bytes);
|
|
39
|
-
let target = match reader.
|
|
40
|
-
KIND_PATH => Target::Path(reader.
|
|
41
|
-
KIND_HANDLE => Target::Handle(reader.
|
|
39
|
+
let target = match reader.read_u8()? {
|
|
40
|
+
KIND_PATH => Target::Path(reader.read_text()?),
|
|
41
|
+
KIND_HANDLE => Target::Handle(reader.read_u32()?),
|
|
42
42
|
_ => return Err(DecodeError::new("Call kind must be 0 (path) or 1 (handle)")),
|
|
43
43
|
};
|
|
44
44
|
if let Target::Handle(0) = target {
|
|
@@ -46,8 +46,8 @@ impl<'a> Call<'a> {
|
|
|
46
46
|
"Call target Handle id 0 is the invalid sentinel",
|
|
47
47
|
));
|
|
48
48
|
}
|
|
49
|
-
let method = reader.
|
|
50
|
-
let block_given = match reader.
|
|
49
|
+
let method = reader.read_text()?;
|
|
50
|
+
let block_given = match reader.read_u8()? {
|
|
51
51
|
0 => false,
|
|
52
52
|
1 => true,
|
|
53
53
|
_ => return Err(DecodeError::new("Call block_given must be 0 or 1")),
|
|
@@ -20,9 +20,9 @@ pub enum CompileReply {
|
|
|
20
20
|
impl CompileReply {
|
|
21
21
|
pub fn decode(bytes: &[u8]) -> Result<Self, DecodeError> {
|
|
22
22
|
let mut reader = Reader::new(bytes);
|
|
23
|
-
match reader.
|
|
23
|
+
match reader.read_u8()? {
|
|
24
24
|
tag::OK => {
|
|
25
|
-
let bytecodes = reader.
|
|
25
|
+
let bytecodes = reader.read_bytes_list()?;
|
|
26
26
|
reader.finish()?;
|
|
27
27
|
Ok(CompileReply::Ok(bytecodes))
|
|
28
28
|
}
|
|
@@ -21,9 +21,9 @@ pub struct ErrorRecord {
|
|
|
21
21
|
impl ErrorRecord {
|
|
22
22
|
pub(crate) fn read(reader: &mut Reader<'_>) -> Result<Self, DecodeError> {
|
|
23
23
|
Ok(ErrorRecord {
|
|
24
|
-
name: reader.
|
|
25
|
-
message: reader.
|
|
26
|
-
backtrace: reader.
|
|
24
|
+
name: reader.read_text()?.to_owned(),
|
|
25
|
+
message: reader.read_text()?.to_owned(),
|
|
26
|
+
backtrace: reader.read_text_list()?,
|
|
27
27
|
})
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -32,7 +32,8 @@ const KIND_BLOCK: u8 = 4;
|
|
|
32
32
|
pub enum FaultKind {
|
|
33
33
|
/// A Ruby exception raised inside the Service method.
|
|
34
34
|
Runtime,
|
|
35
|
-
/// The
|
|
35
|
+
/// The Service method refused its arguments, whether the call would
|
|
36
|
+
/// not bind or the method rejected what it was given.
|
|
36
37
|
Argument,
|
|
37
38
|
/// No such member or method.
|
|
38
39
|
Undefined,
|
|
@@ -115,8 +116,8 @@ impl Fault {
|
|
|
115
116
|
}
|
|
116
117
|
|
|
117
118
|
pub(crate) fn read(reader: &mut Reader<'_>) -> Result<Self, DecodeError> {
|
|
118
|
-
let kind = FaultKind::from_tag(reader.
|
|
119
|
-
let message = reader.
|
|
119
|
+
let kind = FaultKind::from_tag(reader.read_u8()?);
|
|
120
|
+
let message = reader.read_text()?.to_owned();
|
|
120
121
|
// A Fault gains fields as the contract does, and it is the last
|
|
121
122
|
// thing a Reply carries, so whatever follows the fields this
|
|
122
123
|
// reader knows belongs to a later version of them.
|
|
@@ -66,12 +66,12 @@ pub struct Panic {
|
|
|
66
66
|
impl Outcome {
|
|
67
67
|
pub fn decode(bytes: &[u8]) -> Result<Self, DecodeError> {
|
|
68
68
|
let mut reader = Reader::new(bytes);
|
|
69
|
-
match reader.
|
|
69
|
+
match reader.read_u8()? {
|
|
70
70
|
tag::OK => Ok(Outcome::Ok(reader.remaining().to_vec())),
|
|
71
71
|
tag::PANIC => {
|
|
72
|
-
let origin = Origin::from_name(reader.
|
|
72
|
+
let origin = Origin::from_name(reader.read_text()?);
|
|
73
73
|
let error = ErrorRecord::read(&mut reader)?;
|
|
74
|
-
let available = reader.
|
|
74
|
+
let available = reader.read_text_list()?;
|
|
75
75
|
reader.finish()?;
|
|
76
76
|
Ok(Outcome::Panic(Panic {
|
|
77
77
|
origin,
|