kobako 0.28.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.
Files changed (137) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-manifest.json +1 -1
  3. data/CHANGELOG.md +170 -0
  4. data/Cargo.lock +111 -82
  5. data/README.md +154 -36
  6. data/ROADMAP.md +5 -0
  7. data/SECURITY.md +1 -1
  8. data/crates/kobako-runtime/CHANGELOG.md +86 -0
  9. data/crates/kobako-runtime/Cargo.toml +27 -6
  10. data/crates/kobako-runtime/README.md +13 -8
  11. data/crates/kobako-runtime/src/dispatch.rs +8 -0
  12. data/crates/kobako-runtime/src/filesystem/directory/spelling.rs +194 -0
  13. data/crates/kobako-runtime/src/filesystem/directory.rs +1082 -0
  14. data/crates/kobako-runtime/src/filesystem/memory.rs +1100 -0
  15. data/crates/kobako-runtime/src/filesystem/overlay/exclusive.rs +88 -0
  16. data/crates/kobako-runtime/src/filesystem/overlay/stack.rs +808 -0
  17. data/crates/kobako-runtime/src/filesystem/overlay.rs +1955 -0
  18. data/crates/kobako-runtime/src/filesystem/path.rs +60 -0
  19. data/crates/kobako-runtime/src/filesystem/temporary.rs +167 -0
  20. data/crates/kobako-runtime/src/filesystem.rs +392 -0
  21. data/crates/kobako-runtime/src/grant.rs +20 -0
  22. data/crates/kobako-runtime/src/lib.rs +11 -1
  23. data/crates/kobako-runtime/src/mount.rs +178 -0
  24. data/crates/kobako-runtime/src/profile.rs +4 -4
  25. data/crates/kobako-runtime/src/runtime.rs +12 -0
  26. data/crates/kobako-runtime/src/snapshot.rs +20 -5
  27. data/crates/kobako-runtime/src/yielder.rs +11 -6
  28. data/crates/kobako-transport/CHANGELOG.md +17 -0
  29. data/crates/kobako-transport/Cargo.toml +1 -1
  30. data/crates/kobako-transport/README.md +1 -1
  31. data/crates/kobako-transport/src/abi.rs +1 -1
  32. data/crates/kobako-transport/src/envelope/fault.rs +2 -1
  33. data/crates/kobako-transport/src/envelope/preamble.rs +12 -12
  34. data/crates/kobako-transport/src/envelope/reply.rs +32 -14
  35. data/crates/kobako-transport/src/envelope/request.rs +18 -8
  36. data/crates/kobako-wasmtime/CHANGELOG.md +119 -0
  37. data/crates/kobako-wasmtime/Cargo.toml +21 -16
  38. data/crates/kobako-wasmtime/README.md +5 -3
  39. data/crates/kobako-wasmtime/src/abi.rs +66 -32
  40. data/crates/kobako-wasmtime/src/ambient.rs +63 -4
  41. data/crates/kobako-wasmtime/src/cache.rs +185 -74
  42. data/crates/kobako-wasmtime/src/config.rs +58 -1
  43. data/crates/kobako-wasmtime/src/descriptors.rs +167 -0
  44. data/crates/kobako-wasmtime/src/dispatch.rs +47 -29
  45. data/crates/kobako-wasmtime/src/driver.rs +46 -44
  46. data/crates/kobako-wasmtime/src/guest_mem.rs +13 -7
  47. data/crates/kobako-wasmtime/src/imports.rs +46 -0
  48. data/crates/kobako-wasmtime/src/instance_pre.rs +17 -45
  49. data/crates/kobako-wasmtime/src/invocation.rs +35 -46
  50. data/crates/kobako-wasmtime/src/io.rs +5 -202
  51. data/crates/kobako-wasmtime/src/lib.rs +4 -0
  52. data/crates/kobako-wasmtime/src/preview1/args.rs +60 -0
  53. data/crates/kobako-wasmtime/src/preview1/beneath.rs +188 -0
  54. data/crates/kobako-wasmtime/src/preview1/differential/mount.rs +609 -0
  55. data/crates/kobako-wasmtime/src/preview1/differential.rs +372 -0
  56. data/crates/kobako-wasmtime/src/preview1/errno.rs +267 -0
  57. data/crates/kobako-wasmtime/src/preview1/fd.rs +672 -0
  58. data/crates/kobako-wasmtime/src/preview1/memory.rs +205 -0
  59. data/crates/kobako-wasmtime/src/preview1/mount_view.rs +2499 -0
  60. data/crates/kobako-wasmtime/src/preview1/path.rs +421 -0
  61. data/crates/kobako-wasmtime/src/preview1/poll.rs +333 -0
  62. data/crates/kobako-wasmtime/src/preview1/sock.rs +30 -0
  63. data/crates/kobako-wasmtime/src/preview1.rs +461 -0
  64. data/crates/kobako-wasmtime/src/trap.rs +39 -19
  65. data/crates/kobako-wasmtime/src/wasi.rs +317 -0
  66. data/crates/kobako-wasmtime/tests/compile_posture.rs +43 -0
  67. data/crates/kobako-wasmtime/tests/guest_file.rs +1054 -0
  68. data/crates/kobako-wasmtime/tests/preamble_flags.rs +3 -2
  69. data/crates/kobako-wasmtime/tests/snippet_compile.rs +4 -3
  70. data/crates/kobako-wasmtime/tests/support/mod.rs +9 -13
  71. data/crates/kobako-wasmtime/tests/unanswered_call.rs +86 -0
  72. data/crates/kobako-wasmtime/tests/unsupported_call.rs +1 -0
  73. data/data/kobako.wasm +0 -0
  74. data/ext/kobako/Cargo.toml +4 -2
  75. data/ext/kobako/src/runtime/bridge.rs +154 -81
  76. data/ext/kobako/src/runtime/escape.rs +53 -0
  77. data/ext/kobako/src/runtime/filesystem/grants.rs +91 -0
  78. data/ext/kobako/src/runtime/filesystem/ruby.rs +400 -0
  79. data/ext/kobako/src/runtime/filesystem.rs +236 -0
  80. data/ext/kobako/src/runtime/gvl.rs +7 -1
  81. data/ext/kobako/src/runtime/pinned_value.rs +31 -0
  82. data/ext/kobako/src/runtime/snapshot.rs +127 -0
  83. data/ext/kobako/src/runtime.rs +120 -199
  84. data/lib/kobako/catalog/flags.rb +2 -14
  85. data/lib/kobako/catalog/grants.rb +30 -0
  86. data/lib/kobako/catalog/mount.rb +27 -0
  87. data/lib/kobako/catalog/services.rb +11 -0
  88. data/lib/kobako/catalog/snippets.rb +5 -9
  89. data/lib/kobako/catalog.rb +11 -0
  90. data/lib/kobako/codec/decoder.rb +1 -0
  91. data/lib/kobako/codec/ext_types.rb +1 -1
  92. data/lib/kobako/codec/state.rb +19 -3
  93. data/lib/kobako/codec/value_budget.rb +86 -0
  94. data/lib/kobako/codec.rb +7 -0
  95. data/lib/kobako/context.rb +38 -11
  96. data/lib/kobako/errors.rb +15 -14
  97. data/lib/kobako/filesystem.rb +38 -0
  98. data/lib/kobako/pool.rb +2 -2
  99. data/lib/kobako/sandbox.rb +40 -12
  100. data/lib/kobako/sandbox_options.rb +39 -12
  101. data/lib/kobako/snippet/{binary.rb → bytecode.rb} +1 -1
  102. data/lib/kobako/snippet.rb +1 -1
  103. data/lib/kobako/transport/block_failure.rb +35 -0
  104. data/lib/kobako/transport/dispatcher.rb +30 -26
  105. data/lib/kobako/transport/exposure.rb +5 -7
  106. data/lib/kobako/transport/reflection.rb +79 -23
  107. data/lib/kobako/transport/yielder.rb +8 -30
  108. data/lib/kobako/transport.rb +1 -0
  109. data/lib/kobako/version.rb +1 -1
  110. data/lib/kobako.rb +1 -0
  111. data/sig/kobako/catalog/flags.rbs +0 -4
  112. data/sig/kobako/catalog/grants.rbs +13 -0
  113. data/sig/kobako/catalog/mount.rbs +28 -0
  114. data/sig/kobako/catalog/services.rbs +4 -0
  115. data/sig/kobako/catalog/snippets.rbs +1 -1
  116. data/sig/kobako/catalog.rbs +1 -0
  117. data/sig/kobako/codec/state.rbs +7 -1
  118. data/sig/kobako/codec/value_budget.rbs +23 -0
  119. data/sig/kobako/codec.rbs +2 -0
  120. data/sig/kobako/context.rbs +14 -6
  121. data/sig/kobako/errors.rbs +4 -4
  122. data/sig/kobako/filesystem.rbs +88 -0
  123. data/sig/kobako/guest_yielder.rbs +9 -0
  124. data/sig/kobako/pool.rbs +1 -1
  125. data/sig/kobako/runtime.rbs +21 -49
  126. data/sig/kobako/sandbox.rbs +7 -1
  127. data/sig/kobako/sandbox_options.rbs +24 -2
  128. data/sig/kobako/snapshot.rbs +20 -0
  129. data/sig/kobako/snippet/bytecode.rbs +12 -0
  130. data/sig/kobako/transport/block_failure.rbs +13 -0
  131. data/sig/kobako/transport/dispatcher.rbs +3 -3
  132. data/sig/kobako/transport/exposure.rbs +1 -1
  133. data/sig/kobako/transport/reflection.rbs +14 -1
  134. data/sig/kobako/transport/yielder.rbs +3 -7
  135. data/sig/kobako/transport.rbs +2 -2
  136. metadata +60 -7
  137. data/sig/kobako/snippet/binary.rbs +0 -12
@@ -0,0 +1,317 @@
1
+ //! The guest's WASI context for one instance: the Preamble on stdin,
2
+ //! a capture pipe for each of stdout and stderr, the ambient authority
3
+ //! the instance's purpose grants, and a preopen for each Mount an
4
+ //! invocation was handed.
5
+
6
+ use wasmtime_wasi::p1::WasiP1Ctx;
7
+ use wasmtime_wasi::p2::pipe::{MemoryInputPipe, MemoryOutputPipe};
8
+ use wasmtime_wasi::WasiCtxBuilder;
9
+
10
+ use crate::ambient::{self, Clocks};
11
+ use crate::config::Config;
12
+ use crate::descriptors::Descriptors;
13
+ use crate::{capture, guest_mem};
14
+ use kobako_runtime::error::Trap;
15
+ use kobako_runtime::grant::Grant;
16
+ use kobako_runtime::mount::Mount;
17
+ use kobako_runtime::profile::Profile;
18
+ use kobako_transport::abi::FRAME_LEN_SIZE;
19
+
20
+ /// One instance's WASI context: its descriptor table, the clocks its
21
+ /// waits pass on, and the capture pipes stdout and stderr write into, so
22
+ /// the captures are read back from the same owner that wired them.
23
+ pub(crate) struct Wasi {
24
+ ctx: WasiP1Ctx,
25
+ descriptors: Descriptors,
26
+ clocks: Clocks,
27
+ stdout: MemoryOutputPipe,
28
+ stderr: MemoryOutputPipe,
29
+ }
30
+
31
+ impl Wasi {
32
+ /// An invocation's context, granting the posture the Sandbox requested
33
+ /// and presenting each of the invocation's Mounts as a preopen, from
34
+ /// descriptor 3 in order.
35
+ pub(crate) fn for_invocation(
36
+ config: &Config,
37
+ preamble: &[u8],
38
+ grants: &[Grant],
39
+ ) -> Result<Self, Trap> {
40
+ Self::new(
41
+ preamble,
42
+ config.stdout_limit,
43
+ config.stderr_limit,
44
+ config.open_file_limit,
45
+ config.profile,
46
+ grants.iter().filter_map(to_mount).collect(),
47
+ )
48
+ }
49
+
50
+ /// A compile's context, at the strongest posture whatever the Sandbox
51
+ /// requested: compiling the Snippets needs no ambient authority, so
52
+ /// it is granted none.
53
+ pub(crate) fn for_compile(config: &Config, preamble: &[u8]) -> Result<Self, Trap> {
54
+ Self::new(
55
+ preamble,
56
+ config.stdout_limit,
57
+ config.stderr_limit,
58
+ config.open_file_limit,
59
+ Profile::Hermetic,
60
+ Vec::new(),
61
+ )
62
+ }
63
+
64
+ /// The ABI version probe's context. Instantiating it runs the
65
+ /// artifact's start section, yet reading a version needs no ambient
66
+ /// authority and no output: the probe takes the strongest posture and
67
+ /// pipes sized to keep nothing.
68
+ pub(crate) fn for_probe() -> Result<Self, Trap> {
69
+ Self::new(&[], Some(0), Some(0), None, Profile::Hermetic, Vec::new())
70
+ }
71
+
72
+ /// An uncapped output channel has no ceiling on the host: a guest
73
+ /// rewriting one buffer grows the pipe, not its own memory. The 16 MiB
74
+ /// cap keeps the Preamble's `u32` length prefix from wrapping.
75
+ fn new(
76
+ preamble: &[u8],
77
+ stdout_limit: Option<usize>,
78
+ stderr_limit: Option<usize>,
79
+ open_file_limit: Option<usize>,
80
+ profile: Profile,
81
+ mounts: Vec<Mount>,
82
+ ) -> Result<Self, Trap> {
83
+ // The Preamble carries the same 16 MiB cap as a downcall request
84
+ // (`io::write_request`): past it, the length prefix would silently wrap.
85
+ guest_mem::checked_payload_len(preamble.len())
86
+ .map_err(|msg| Trap::Other(msg.to_string()))?;
87
+
88
+ let mut stdin_content: Vec<u8> = Vec::with_capacity(FRAME_LEN_SIZE + preamble.len());
89
+ stdin_content.extend_from_slice(&(preamble.len() as u32).to_be_bytes());
90
+ stdin_content.extend_from_slice(preamble);
91
+
92
+ let stdout = MemoryOutputPipe::new(capture::pipe_capacity(stdout_limit));
93
+ let stderr = MemoryOutputPipe::new(capture::pipe_capacity(stderr_limit));
94
+
95
+ // stdio lives in kobako's descriptor table, which answers every
96
+ // call that reads a descriptor (`crate::preview1`); the builder
97
+ // keeps only what the remaining imports read.
98
+ let descriptors = Descriptors::new(
99
+ MemoryInputPipe::new(stdin_content),
100
+ stdout.clone(),
101
+ stderr.clone(),
102
+ mounts,
103
+ open_file_limit,
104
+ );
105
+
106
+ let mut builder = WasiCtxBuilder::new();
107
+ // The profile decides the ambient authority granted: the
108
+ // hermetic rung denies the preview1 time and entropy imports (see
109
+ // `ambient`), the permissive rung leaves the live WASI sources.
110
+ // Ambient filesystem, environment, and network stay absent on both rungs —
111
+ // the builder grants none unless asked. The exhaustive match makes
112
+ // a future ladder rung a compile error here, not authority granted
113
+ // silently.
114
+ let clocks = match profile {
115
+ Profile::Hermetic => {
116
+ builder.wall_clock(ambient::FrozenWallClock);
117
+ builder.monotonic_clock(ambient::FrozenMonotonicClock);
118
+ builder.secure_random(ambient::deterministic_rng());
119
+ Clocks::Frozen
120
+ }
121
+ Profile::Permissive => {
122
+ let monotonic = ambient::LiveMonotonicClock::new();
123
+ builder.monotonic_clock(monotonic);
124
+ Clocks::Live(monotonic)
125
+ }
126
+ };
127
+
128
+ Ok(Self {
129
+ ctx: builder.build_p1(),
130
+ descriptors,
131
+ clocks,
132
+ stdout,
133
+ stderr,
134
+ })
135
+ }
136
+
137
+ pub(crate) fn ctx_mut(&mut self) -> &mut WasiP1Ctx {
138
+ &mut self.ctx
139
+ }
140
+
141
+ pub(crate) fn descriptors(&self) -> &Descriptors {
142
+ &self.descriptors
143
+ }
144
+
145
+ pub(crate) fn descriptors_mut(&mut self) -> &mut Descriptors {
146
+ &mut self.descriptors
147
+ }
148
+
149
+ pub(crate) fn clocks(&self) -> Clocks {
150
+ self.clocks
151
+ }
152
+
153
+ pub(crate) fn stdout_bytes(&self) -> Vec<u8> {
154
+ self.stdout.contents().to_vec()
155
+ }
156
+
157
+ pub(crate) fn stderr_bytes(&self) -> Vec<u8> {
158
+ self.stderr.contents().to_vec()
159
+ }
160
+ }
161
+
162
+ /// A Mount as this invocation presents it. A kind this driver does not
163
+ /// support has no descriptor to stand behind; `Driver::supports` keeps a
164
+ /// frontend from handing one over.
165
+ fn to_mount(grant: &Grant) -> Option<Mount> {
166
+ match grant {
167
+ Grant::Mount(mount) => Some(mount.for_invocation()),
168
+ _ => None,
169
+ }
170
+ }
171
+
172
+ #[cfg(test)]
173
+ mod tests {
174
+ //! Witness what an invocation's context grants at each profile rung:
175
+ //! `wasi:clocks` / `wasi:random` frozen under `Hermetic` and live
176
+ //! under `Permissive`, and no ambient filesystem, environment, or
177
+ //! socket under either.
178
+ use wasmtime::{AsContextMut, Module, Store};
179
+
180
+ use super::*;
181
+ use crate::cache::shared_engine;
182
+ use crate::imports;
183
+ use crate::invocation::Invocation;
184
+
185
+ /// Preview1 `errno` for a descriptor the context does not hold.
186
+ const ERRNO_BADF: i64 = 8;
187
+
188
+ /// Preview1 probe with one export per ambient source: `clock_ns`
189
+ /// reads the realtime clock, `random_word` reads eight entropy bytes,
190
+ /// `environ_count` counts environment variables, and `prestat_errno`
191
+ /// / `accept_errno` ask descriptor 3 for a directory and a
192
+ /// connection. Preview1 numbers preopens from 3, so nothing there
193
+ /// means no directory and no socket was granted.
194
+ const PROBE_WAT: &str = r#"
195
+ (module
196
+ (import "wasi_snapshot_preview1" "clock_time_get"
197
+ (func $clock (param i32 i64 i32) (result i32)))
198
+ (import "wasi_snapshot_preview1" "random_get"
199
+ (func $random (param i32 i32) (result i32)))
200
+ (import "wasi_snapshot_preview1" "environ_sizes_get"
201
+ (func $environ (param i32 i32) (result i32)))
202
+ (import "wasi_snapshot_preview1" "fd_prestat_get"
203
+ (func $prestat (param i32 i32) (result i32)))
204
+ (import "wasi_snapshot_preview1" "sock_accept"
205
+ (func $accept (param i32 i32 i32) (result i32)))
206
+ (memory (export "memory") 1)
207
+ (func (export "clock_ns") (result i64)
208
+ (drop (call $clock (i32.const 0) (i64.const 1) (i32.const 0)))
209
+ (i64.load (i32.const 0)))
210
+ (func (export "random_word") (result i64)
211
+ (drop (call $random (i32.const 8) (i32.const 8)))
212
+ (i64.load (i32.const 8)))
213
+ (func (export "environ_count") (result i64)
214
+ (drop (call $environ (i32.const 16) (i32.const 20)))
215
+ (i64.extend_i32_u (i32.load (i32.const 16))))
216
+ (func (export "prestat_errno") (result i64)
217
+ (i64.extend_i32_u (call $prestat (i32.const 3) (i32.const 24))))
218
+ (func (export "accept_errno") (result i64)
219
+ (i64.extend_i32_u (call $accept (i32.const 3) (i32.const 0) (i32.const 24)))))
220
+ "#;
221
+
222
+ /// Instantiate the probe over an invocation's WASI context built at
223
+ /// `profile` and return a reader for its exports.
224
+ fn probe(profile: Profile) -> impl FnMut(&str) -> i64 {
225
+ let engine = shared_engine().expect("shared engine must be constructible");
226
+ let config = Config {
227
+ timeout: None,
228
+ setup_timeout: None,
229
+ memory_limit: None,
230
+ stdout_limit: None,
231
+ stderr_limit: None,
232
+ open_file_limit: None,
233
+ profile,
234
+ };
235
+ let wasi = Wasi::for_invocation(&config, &[], &[]).expect("WASI context must build");
236
+ let mut store = Store::new(engine, Invocation::new(None, wasi));
237
+ store.set_epoch_deadline(crate::trap::NO_TIMEOUT_EPOCH_DELTA);
238
+
239
+ let linker = imports::linker().expect("host imports must link");
240
+ let module = Module::new(engine, PROBE_WAT).expect("probe module must compile");
241
+ let instance = linker
242
+ .instantiate(&mut store, &module)
243
+ .expect("probe module must instantiate");
244
+
245
+ move |name| {
246
+ instance
247
+ .get_typed_func::<(), i64>(store.as_context_mut(), name)
248
+ .expect("probe export must resolve")
249
+ .call(store.as_context_mut(), ())
250
+ .expect("probe export must run")
251
+ }
252
+ }
253
+
254
+ /// Assert that the context built at `profile` grants no ambient
255
+ /// directory, environment variable, or socket.
256
+ fn assert_grants_no_ambient_resource(profile: Profile) {
257
+ let mut read = probe(profile);
258
+ assert_eq!(
259
+ read("prestat_errno"),
260
+ ERRNO_BADF,
261
+ "a {profile:?} guest's WASI context must preopen no directory"
262
+ );
263
+ assert_eq!(
264
+ read("environ_count"),
265
+ 0,
266
+ "a {profile:?} guest's WASI context must carry no environment variable"
267
+ );
268
+ assert_eq!(
269
+ read("accept_errno"),
270
+ ERRNO_BADF,
271
+ "a {profile:?} guest's WASI context must hold no socket"
272
+ );
273
+ }
274
+
275
+ // @behavior RT-047 RT-048
276
+ #[test]
277
+ fn hermetic_denies_ambient_time_and_entropy() {
278
+ let mut read = probe(Profile::Hermetic);
279
+ assert_eq!(
280
+ read("clock_ns"),
281
+ 0,
282
+ "a hermetic guest's wasi:clocks must read the Unix epoch, not host time"
283
+ );
284
+ assert_eq!(
285
+ read("random_word"),
286
+ 0,
287
+ "a hermetic guest's wasi:random must yield the constant stream, not host entropy"
288
+ );
289
+ }
290
+
291
+ // @behavior RT-049 RT-050
292
+ #[test]
293
+ fn permissive_grants_live_ambient_time_and_entropy() {
294
+ let mut read = probe(Profile::Permissive);
295
+ assert!(
296
+ read("clock_ns") > 0,
297
+ "a permissive guest's wasi:clocks must read live host time"
298
+ );
299
+ assert_ne!(
300
+ read("random_word"),
301
+ 0,
302
+ "a permissive guest's wasi:random must yield host entropy"
303
+ );
304
+ }
305
+
306
+ // @behavior RT-067
307
+ #[test]
308
+ fn hermetic_grants_no_ambient_resource() {
309
+ assert_grants_no_ambient_resource(Profile::Hermetic);
310
+ }
311
+
312
+ // @behavior RT-067
313
+ #[test]
314
+ fn permissive_grants_no_ambient_resource() {
315
+ assert_grants_no_ambient_resource(Profile::Permissive);
316
+ }
317
+ }
@@ -0,0 +1,43 @@
1
+ //! A compile reads no host time or entropy, whatever posture the Sandbox
2
+ //! requested. Compiling runs no script, so the bundled guest has no way to
3
+ //! show what its clock read; a fixture guest whose compile answers the
4
+ //! ambient bytes it read shows it instead.
5
+
6
+ use std::path::Path;
7
+
8
+ use kobako_runtime::{Profile, Runtime};
9
+ use kobako_transport::envelope::{Preamble, Snippet, Snippets};
10
+ use kobako_wasmtime::{Config, Driver};
11
+
12
+ const FIXTURE: &str = concat!(
13
+ env!("CARGO_MANIFEST_DIR"),
14
+ "/../../test/fixtures/minimal_compile_ambient.wat"
15
+ );
16
+
17
+ // @behavior RT-072
18
+ #[test]
19
+ fn a_compile_reads_no_host_time_or_entropy_under_a_permissive_request() {
20
+ let mut config = Config::default();
21
+ config.timeout = None;
22
+ config.setup_timeout = None;
23
+ config.memory_limit = None;
24
+ config.stdout_limit = None;
25
+ config.stderr_limit = None;
26
+ config.open_file_limit = None;
27
+ config.profile = Profile::Permissive;
28
+ let driver = Driver::new(Path::new(FIXTURE), config).expect("the fixture guest must load");
29
+ let mut preamble = Preamble::default();
30
+ preamble.snippets = Snippets {
31
+ entries: vec![Snippet::Source {
32
+ name: "Only".into(),
33
+ body: "1".into(),
34
+ }],
35
+ };
36
+
37
+ assert_eq!(
38
+ driver.compile(&preamble),
39
+ Some(vec![vec![0; 16]]),
40
+ "a permissive request through Driver#compile must read the Unix epoch and the \
41
+ constant stream, not host time and entropy"
42
+ );
43
+ }