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.
Files changed (218) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-manifest.json +1 -1
  3. data/CHANGELOG.md +377 -0
  4. data/Cargo.lock +181 -154
  5. data/README.md +99 -459
  6. data/ROADMAP.md +7 -1
  7. data/SECURITY.md +1 -1
  8. data/crates/kobako-runtime/CHANGELOG.md +354 -0
  9. data/crates/kobako-runtime/Cargo.toml +38 -7
  10. data/crates/kobako-runtime/README.md +17 -8
  11. data/crates/kobako-runtime/src/dispatch.rs +9 -1
  12. data/crates/kobako-runtime/src/endpoint/buffer.rs +192 -0
  13. data/crates/kobako-runtime/src/endpoint/capture.rs +115 -0
  14. data/crates/kobako-runtime/src/endpoint/dispatch.rs +253 -0
  15. data/crates/kobako-runtime/src/endpoint/invoke.rs +194 -0
  16. data/crates/kobako-runtime/src/endpoint/memory_cap.rs +238 -0
  17. data/crates/kobako-runtime/src/endpoint.rs +166 -0
  18. data/crates/kobako-runtime/src/filesystem/bind/fid.rs +82 -0
  19. data/crates/kobako-runtime/src/filesystem/bind/lookup.rs +236 -0
  20. data/crates/kobako-runtime/src/filesystem/bind/walk.rs +249 -0
  21. data/crates/kobako-runtime/src/filesystem/bind.rs +2665 -0
  22. data/crates/kobako-runtime/src/filesystem/overlay/file.rs +89 -0
  23. data/crates/kobako-runtime/src/filesystem/overlay/inode.rs +106 -0
  24. data/crates/kobako-runtime/src/filesystem/overlay/path.rs +285 -0
  25. data/crates/kobako-runtime/src/filesystem/overlay/stack/copy_up.rs +135 -0
  26. data/crates/kobako-runtime/src/filesystem/overlay/stack/dir.rs +263 -0
  27. data/crates/kobako-runtime/src/filesystem/overlay/stack/namei.rs +135 -0
  28. data/crates/kobako-runtime/src/filesystem/overlay/stack/readdir.rs +57 -0
  29. data/crates/kobako-runtime/src/filesystem/overlay/stack.rs +301 -0
  30. data/crates/kobako-runtime/src/filesystem/overlay.rs +3469 -0
  31. data/crates/kobako-runtime/src/filesystem/path.rs +21 -0
  32. data/crates/kobako-runtime/src/filesystem/pipe.rs +221 -0
  33. data/crates/kobako-runtime/src/filesystem/rdir.rs +101 -0
  34. data/crates/kobako-runtime/src/filesystem/tmpfs/tmpfile.rs +32 -0
  35. data/crates/kobako-runtime/src/filesystem/tmpfs/whiteout.rs +85 -0
  36. data/crates/kobako-runtime/src/filesystem/tmpfs/xattr.rs +22 -0
  37. data/crates/kobako-runtime/src/filesystem/tmpfs.rs +1985 -0
  38. data/crates/kobako-runtime/src/filesystem/wrap.rs +224 -0
  39. data/crates/kobako-runtime/src/filesystem.rs +26 -0
  40. data/crates/kobako-runtime/src/grant.rs +20 -0
  41. data/crates/kobako-runtime/src/hashtable.rs +44 -0
  42. data/crates/kobako-runtime/src/lib.rs +16 -1
  43. data/crates/kobako-runtime/src/locking/seqlock.rs +66 -0
  44. data/crates/kobako-runtime/src/locking.rs +218 -0
  45. data/crates/kobako-runtime/src/mount.rs +105 -0
  46. data/crates/kobako-runtime/src/profile.rs +140 -4
  47. data/crates/kobako-runtime/src/runtime.rs +12 -0
  48. data/crates/kobako-runtime/src/sched.rs +210 -0
  49. data/crates/kobako-runtime/src/security.rs +696 -0
  50. data/crates/kobako-runtime/src/snapshot.rs +20 -5
  51. data/crates/kobako-runtime/src/vfs/attr.rs +162 -0
  52. data/crates/kobako-runtime/src/vfs/cred.rs +334 -0
  53. data/crates/kobako-runtime/src/vfs/dentry.rs +1412 -0
  54. data/crates/kobako-runtime/src/vfs/errno.rs +153 -0
  55. data/crates/kobako-runtime/src/vfs/file.rs +1915 -0
  56. data/crates/kobako-runtime/src/vfs/inode.rs +429 -0
  57. data/crates/kobako-runtime/src/vfs/kern_path.rs +217 -0
  58. data/crates/kobako-runtime/src/vfs/mount.rs +59 -0
  59. data/crates/kobako-runtime/src/vfs/namei.rs +5708 -0
  60. data/crates/kobako-runtime/src/vfs/openat2.rs +116 -0
  61. data/crates/kobako-runtime/src/vfs/path.rs +64 -0
  62. data/crates/kobako-runtime/src/vfs/readdir.rs +163 -0
  63. data/crates/kobako-runtime/src/vfs/stat.rs +157 -0
  64. data/crates/kobako-runtime/src/vfs/super_block.rs +122 -0
  65. data/crates/kobako-runtime/src/vfs/utimes.rs +289 -0
  66. data/crates/kobako-runtime/src/vfs.rs +60 -0
  67. data/crates/kobako-runtime/src/yielder.rs +11 -6
  68. data/crates/kobako-runtime/tests/linux_baseline/calls.rs +171 -0
  69. data/crates/kobako-runtime/tests/linux_baseline/concurrent.rs +276 -0
  70. data/crates/kobako-runtime/tests/linux_baseline/kobako.rs +323 -0
  71. data/crates/kobako-runtime/tests/linux_baseline/linux-baseline-expected.txt +16 -0
  72. data/crates/kobako-runtime/tests/linux_baseline/linux.rs +449 -0
  73. data/crates/kobako-runtime/tests/linux_baseline/main.rs +611 -0
  74. data/crates/kobako-runtime/tests/linux_baseline/permissions.rs +236 -0
  75. data/crates/kobako-transport/CHANGELOG.md +23 -0
  76. data/crates/kobako-transport/Cargo.toml +3 -3
  77. data/crates/kobako-transport/README.md +6 -5
  78. data/crates/kobako-transport/src/abi.rs +1 -1
  79. data/crates/kobako-transport/src/envelope/bytes.rs +33 -25
  80. data/crates/kobako-transport/src/envelope/call.rs +5 -5
  81. data/crates/kobako-transport/src/envelope/compile_reply.rs +2 -2
  82. data/crates/kobako-transport/src/envelope/error_record.rs +3 -3
  83. data/crates/kobako-transport/src/envelope/fault.rs +4 -3
  84. data/crates/kobako-transport/src/envelope/outcome.rs +3 -3
  85. data/crates/kobako-transport/src/envelope/preamble.rs +24 -24
  86. data/crates/kobako-transport/src/envelope/reply.rs +34 -16
  87. data/crates/kobako-transport/src/envelope/request.rs +19 -9
  88. data/crates/kobako-transport/src/envelope/run.rs +1 -1
  89. data/crates/kobako-transport/src/envelope/unsupported.rs +2 -2
  90. data/crates/kobako-transport/src/envelope.rs +2 -2
  91. data/crates/kobako-wasmtime/CHANGELOG.md +265 -0
  92. data/crates/kobako-wasmtime/Cargo.toml +25 -19
  93. data/crates/kobako-wasmtime/README.md +13 -7
  94. data/crates/kobako-wasmtime/src/abi.rs +75 -49
  95. data/crates/kobako-wasmtime/src/ambient.rs +64 -6
  96. data/crates/kobako-wasmtime/src/cache.rs +188 -77
  97. data/crates/kobako-wasmtime/src/config.rs +77 -5
  98. data/crates/kobako-wasmtime/src/descriptors.rs +200 -0
  99. data/crates/kobako-wasmtime/src/driver.rs +70 -135
  100. data/crates/kobako-wasmtime/src/exports.rs +5 -16
  101. data/crates/kobako-wasmtime/src/guest.rs +38 -0
  102. data/crates/kobako-wasmtime/src/imports.rs +52 -0
  103. data/crates/kobako-wasmtime/src/instance_pre.rs +18 -46
  104. data/crates/kobako-wasmtime/src/invocation.rs +43 -74
  105. data/crates/kobako-wasmtime/src/lib.rs +5 -4
  106. data/crates/kobako-wasmtime/src/limiter.rs +6 -230
  107. data/crates/kobako-wasmtime/src/preview1/differential/mount.rs +632 -0
  108. data/crates/kobako-wasmtime/src/preview1/differential.rs +486 -0
  109. data/crates/kobako-wasmtime/src/preview1/errno.rs +135 -0
  110. data/crates/kobako-wasmtime/src/preview1/fd.rs +1420 -0
  111. data/crates/kobako-wasmtime/src/preview1/memory.rs +172 -0
  112. data/crates/kobako-wasmtime/src/preview1/mount_view/wrap.rs +211 -0
  113. data/crates/kobako-wasmtime/src/preview1/mount_view.rs +3766 -0
  114. data/crates/kobako-wasmtime/src/preview1/path.rs +302 -0
  115. data/crates/kobako-wasmtime/src/preview1/poll.rs +381 -0
  116. data/crates/kobako-wasmtime/src/preview1/sock.rs +13 -0
  117. data/crates/kobako-wasmtime/src/preview1/testsuite-expected.txt +6 -0
  118. data/crates/kobako-wasmtime/src/preview1/testsuite.rs +157 -0
  119. data/crates/kobako-wasmtime/src/preview1.rs +512 -0
  120. data/crates/kobako-wasmtime/src/trap.rs +45 -25
  121. data/crates/kobako-wasmtime/src/wasi.rs +333 -0
  122. data/crates/kobako-wasmtime/tests/integration/compile_posture.rs +43 -0
  123. data/crates/kobako-wasmtime/tests/integration/guest_file.rs +1085 -0
  124. data/crates/kobako-wasmtime/tests/integration/main.rs +29 -0
  125. data/crates/kobako-wasmtime/tests/{preamble_flags.rs → integration/preamble_flags.rs} +4 -3
  126. data/crates/kobako-wasmtime/tests/{snippet_compile.rs → integration/snippet_compile.rs} +5 -4
  127. data/crates/kobako-wasmtime/tests/{support → integration/support}/mod.rs +9 -13
  128. data/crates/kobako-wasmtime/tests/integration/unanswered_call.rs +86 -0
  129. data/crates/kobako-wasmtime/tests/{unsupported_call.rs → integration/unsupported_call.rs} +2 -1
  130. data/crates/kobako-wasmtime/witx/typenames.witx +750 -0
  131. data/crates/kobako-wasmtime/witx/wasi_snapshot_preview1.witx +441 -0
  132. data/data/kobako.wasm +0 -0
  133. data/ext/kobako/Cargo.toml +4 -2
  134. data/ext/kobako/src/runtime/bridge.rs +154 -81
  135. data/ext/kobako/src/runtime/errors.rs +1 -1
  136. data/ext/kobako/src/runtime/escape.rs +67 -0
  137. data/ext/kobako/src/runtime/filesystem/grants.rs +107 -0
  138. data/ext/kobako/src/runtime/filesystem/ruby.rs +708 -0
  139. data/ext/kobako/src/runtime/filesystem.rs +208 -0
  140. data/ext/kobako/src/runtime/gvl.rs +49 -8
  141. data/ext/kobako/src/runtime/pinned_value.rs +31 -0
  142. data/ext/kobako/src/runtime/snapshot.rs +127 -0
  143. data/ext/kobako/src/runtime.rs +125 -199
  144. data/lib/kobako/catalog/extensions.rb +5 -5
  145. data/lib/kobako/catalog/flags.rb +2 -14
  146. data/lib/kobako/catalog/grants.rb +30 -0
  147. data/lib/kobako/catalog/handles.rb +6 -6
  148. data/lib/kobako/catalog/mount.rb +19 -0
  149. data/lib/kobako/catalog/services.rb +13 -2
  150. data/lib/kobako/catalog/snippets.rb +11 -15
  151. data/lib/kobako/catalog.rb +11 -0
  152. data/lib/kobako/codec/decoder.rb +10 -9
  153. data/lib/kobako/codec/ext_types.rb +4 -6
  154. data/lib/kobako/codec/handle_walk.rb +4 -6
  155. data/lib/kobako/codec/nesting.rb +7 -7
  156. data/lib/kobako/codec/state.rb +19 -3
  157. data/lib/kobako/codec/utils.rb +1 -1
  158. data/lib/kobako/codec/value_budget.rb +86 -0
  159. data/lib/kobako/codec.rb +7 -0
  160. data/lib/kobako/context.rb +40 -13
  161. data/lib/kobako/errors.rb +15 -14
  162. data/lib/kobako/filesystem.rb +69 -0
  163. data/lib/kobako/payload/arguments.rb +2 -2
  164. data/lib/kobako/pool.rb +6 -6
  165. data/lib/kobako/sandbox.rb +55 -27
  166. data/lib/kobako/sandbox_options.rb +39 -13
  167. data/lib/kobako/snippet/{binary.rb → bytecode.rb} +2 -2
  168. data/lib/kobako/snippet/source.rb +1 -1
  169. data/lib/kobako/snippet.rb +1 -1
  170. data/lib/kobako/transport/block_failure.rb +35 -0
  171. data/lib/kobako/transport/dispatcher.rb +33 -29
  172. data/lib/kobako/transport/exposure.rb +7 -9
  173. data/lib/kobako/transport/reflection.rb +79 -23
  174. data/lib/kobako/transport/run.rb +5 -5
  175. data/lib/kobako/transport/yielder.rb +9 -31
  176. data/lib/kobako/transport.rb +1 -0
  177. data/lib/kobako/version.rb +1 -1
  178. data/lib/kobako.rb +35 -0
  179. data/release-please-config.json +1 -0
  180. data/sig/kobako/catalog/extensions.rbs +3 -3
  181. data/sig/kobako/catalog/flags.rbs +0 -4
  182. data/sig/kobako/catalog/grants.rbs +13 -0
  183. data/sig/kobako/catalog/handles.rbs +3 -3
  184. data/sig/kobako/catalog/mount.rbs +24 -0
  185. data/sig/kobako/catalog/services.rbs +5 -1
  186. data/sig/kobako/catalog/snippets.rbs +4 -4
  187. data/sig/kobako/catalog.rbs +1 -0
  188. data/sig/kobako/codec/decoder.rbs +1 -1
  189. data/sig/kobako/codec/handle_walk.rbs +1 -1
  190. data/sig/kobako/codec/nesting.rbs +2 -2
  191. data/sig/kobako/codec/state.rbs +7 -1
  192. data/sig/kobako/codec/utils.rbs +1 -1
  193. data/sig/kobako/codec/value_budget.rbs +23 -0
  194. data/sig/kobako/codec.rbs +3 -2
  195. data/sig/kobako/context.rbs +14 -6
  196. data/sig/kobako/errors.rbs +4 -4
  197. data/sig/kobako/filesystem.rbs +93 -0
  198. data/sig/kobako/guest_yielder.rbs +9 -0
  199. data/sig/kobako/payload/arguments.rbs +1 -1
  200. data/sig/kobako/pool.rbs +3 -3
  201. data/sig/kobako/runtime.rbs +21 -49
  202. data/sig/kobako/sandbox.rbs +9 -5
  203. data/sig/kobako/sandbox_options.rbs +25 -3
  204. data/sig/kobako/snapshot.rbs +20 -0
  205. data/sig/kobako/snippet/bytecode.rbs +12 -0
  206. data/sig/kobako/transport/block_failure.rbs +13 -0
  207. data/sig/kobako/transport/dispatcher.rbs +4 -4
  208. data/sig/kobako/transport/exposure.rbs +2 -2
  209. data/sig/kobako/transport/reflection.rbs +14 -1
  210. data/sig/kobako/transport/run.rbs +3 -3
  211. data/sig/kobako/transport/yielder.rbs +3 -7
  212. data/sig/kobako/transport.rbs +3 -3
  213. metadata +116 -15
  214. data/crates/kobako-wasmtime/src/capture.rs +0 -96
  215. data/crates/kobako-wasmtime/src/dispatch.rs +0 -107
  216. data/crates/kobako-wasmtime/src/guest_mem.rs +0 -265
  217. data/crates/kobako-wasmtime/src/io.rs +0 -286
  218. data/sig/kobako/snippet/binary.rbs +0 -12
@@ -66,7 +66,7 @@ impl Preamble {
66
66
  /// appear at most once; an absent one reads as empty.
67
67
  pub fn decode(bytes: &[u8]) -> Result<Self, DecodeError> {
68
68
  let mut reader = Reader::new(bytes);
69
- let count = reader.u32()? as usize;
69
+ let count = reader.read_u32()? as usize;
70
70
  // Each record costs at least six bytes, its two leading bytes and a
71
71
  // body length, so a count past the bytes left can never be
72
72
  // satisfied; refusing it bounds the work.
@@ -79,13 +79,13 @@ impl Preamble {
79
79
  let mut snippets = None;
80
80
  let mut flags = Vec::new();
81
81
  for _ in 0..count {
82
- let critical = match reader.u8()? {
82
+ let critical = match reader.read_u8()? {
83
83
  ADVISORY => false,
84
84
  CRITICAL => true,
85
85
  _ => return Err(DecodeError::new("Preamble criticality must be 0 or 1")),
86
86
  };
87
- let kind = reader.u8()?;
88
- let body = reader.bytes()?;
87
+ let kind = reader.read_u8()?;
88
+ let body = reader.read_bytes()?;
89
89
  match kind {
90
90
  tag::BINDINGS => once(&mut bindings, decode_bindings(body)?)?,
91
91
  tag::SNIPPETS => once(&mut snippets, decode_snippets(body)?)?,
@@ -147,7 +147,7 @@ fn once<T>(slot: &mut Option<T>, value: T) -> Result<(), DecodeError> {
147
147
 
148
148
  fn decode_bindings(bytes: &[u8]) -> Result<Bindings, DecodeError> {
149
149
  let mut reader = Reader::new(bytes);
150
- let paths = reader.text_list()?;
150
+ let paths = reader.read_text_list()?;
151
151
  reader.finish()?;
152
152
  Ok(Bindings { paths })
153
153
  }
@@ -160,7 +160,7 @@ fn encode_bindings(bindings: &Bindings) -> Vec<u8> {
160
160
 
161
161
  fn decode_snippets(bytes: &[u8]) -> Result<Snippets, DecodeError> {
162
162
  let mut reader = Reader::new(bytes);
163
- let count = reader.u32()? as usize;
163
+ let count = reader.read_u32()? as usize;
164
164
  // Each entry costs at least a kind byte and a `u32` length, so a count
165
165
  // past what the bytes left can hold is refused before it sizes the
166
166
  // allocation.
@@ -171,13 +171,13 @@ fn decode_snippets(bytes: &[u8]) -> Result<Snippets, DecodeError> {
171
171
  }
172
172
  let mut entries = Vec::with_capacity(count);
173
173
  for _ in 0..count {
174
- entries.push(match reader.u8()? {
174
+ entries.push(match reader.read_u8()? {
175
175
  SNIPPET_SOURCE => Snippet::Source {
176
- name: reader.text()?.to_owned(),
177
- body: reader.text()?.to_owned(),
176
+ name: reader.read_text()?.to_owned(),
177
+ body: reader.read_text()?.to_owned(),
178
178
  },
179
179
  SNIPPET_BYTECODE => Snippet::Bytecode {
180
- body: reader.bytes()?.to_vec(),
180
+ body: reader.read_bytes()?.to_vec(),
181
181
  },
182
182
  _ => return Err(DecodeError::new("Preamble snippet kind must be 0 or 1")),
183
183
  });
@@ -207,8 +207,8 @@ fn encode_snippets(snippets: &Snippets) -> Vec<u8> {
207
207
 
208
208
  fn decode_flag(bytes: &[u8], critical: bool) -> Result<Flag, DecodeError> {
209
209
  let mut reader = Reader::new(bytes);
210
- let name = reader.text()?.to_owned();
211
- let value = reader.text()?.to_owned();
210
+ let name = reader.read_text()?.to_owned();
211
+ let value = reader.read_text()?.to_owned();
212
212
  reader.finish()?;
213
213
  Ok(Flag {
214
214
  name,
@@ -243,7 +243,7 @@ mod tests {
243
243
  writer.into_bytes()
244
244
  }
245
245
 
246
- fn preamble_of(records: &[Vec<u8>]) -> Vec<u8> {
246
+ fn preamble_with(records: &[Vec<u8>]) -> Vec<u8> {
247
247
  let mut bytes = (records.len() as u32).to_be_bytes().to_vec();
248
248
  for record in records {
249
249
  bytes.extend_from_slice(record);
@@ -307,7 +307,7 @@ mod tests {
307
307
  // @behavior WE-053
308
308
  #[test]
309
309
  fn an_absent_record_reads_as_empty() {
310
- let bytes = preamble_of(&[record(0, 0x07, &encode_flag(&flag("f", "", false)))]);
310
+ let bytes = preamble_with(&[record(0, 0x07, &encode_flag(&flag("f", "", false)))]);
311
311
  let preamble = Preamble::decode(&bytes).expect("a flag-only Preamble must decode");
312
312
  assert_eq!(
313
313
  (preamble.bindings, preamble.snippets),
@@ -375,7 +375,7 @@ mod tests {
375
375
  // @behavior WE-056
376
376
  #[test]
377
377
  fn an_unknown_snippet_kind_is_refused() {
378
- let bytes = preamble_of(&[record(1, 0x06, &[0, 0, 0, 1, 9])]);
378
+ let bytes = preamble_with(&[record(1, 0x06, &[0, 0, 0, 1, 9])]);
379
379
  assert!(
380
380
  Preamble::decode(&bytes).is_err(),
381
381
  "a snippet kind that is neither source nor bytecode through Preamble::decode must \
@@ -387,7 +387,7 @@ mod tests {
387
387
  #[test]
388
388
  fn a_count_the_preamble_cannot_satisfy_is_refused() {
389
389
  let records = [0xff, 0xff, 0xff, 0xff];
390
- let snippets = preamble_of(&[record(1, 0x06, &[0xff, 0xff, 0xff, 0xff])]);
390
+ let snippets = preamble_with(&[record(1, 0x06, &[0xff, 0xff, 0xff, 0xff])]);
391
391
  assert!(
392
392
  Preamble::decode(&records).is_err() && Preamble::decode(&snippets).is_err(),
393
393
  "a record or snippet count larger than what follows through Preamble::decode must \
@@ -400,7 +400,7 @@ mod tests {
400
400
  fn trailing_bytes_are_refused() {
401
401
  let mut past_the_list = Preamble::default().encode();
402
402
  past_the_list.push(0);
403
- let past_a_body = preamble_of(&[record(1, 0x05, &[0, 0, 0, 0, 0])]);
403
+ let past_a_body = preamble_with(&[record(1, 0x05, &[0, 0, 0, 0, 0])]);
404
404
  assert!(
405
405
  Preamble::decode(&past_the_list).is_err() && Preamble::decode(&past_a_body).is_err(),
406
406
  "bytes past the last record or past a record body's last field through \
@@ -411,7 +411,7 @@ mod tests {
411
411
  // @behavior WE-077
412
412
  #[test]
413
413
  fn an_advisory_record_of_an_unknown_kind_is_skipped() {
414
- let bytes = preamble_of(&[
414
+ let bytes = preamble_with(&[
415
415
  record(0, 0x7e, b"anything"),
416
416
  record(
417
417
  1,
@@ -432,7 +432,7 @@ mod tests {
432
432
  // @behavior WE-078
433
433
  #[test]
434
434
  fn a_critical_record_of_an_unknown_kind_is_refused() {
435
- let bytes = preamble_of(&[record(1, 0x7e, b"anything")]);
435
+ let bytes = preamble_with(&[record(1, 0x7e, b"anything")]);
436
436
  assert!(
437
437
  Preamble::decode(&bytes).is_err(),
438
438
  "a critical record of a kind the registry has not assigned through \
@@ -444,7 +444,7 @@ mod tests {
444
444
  #[test]
445
445
  fn a_record_kind_assigned_to_another_message_is_refused() {
446
446
  for kind in [0x00, 0x01, 0x10] {
447
- let bytes = preamble_of(&[record(0, kind, b"")]);
447
+ let bytes = preamble_with(&[record(0, kind, b"")]);
448
448
  assert!(
449
449
  Preamble::decode(&bytes).is_err(),
450
450
  "an advisory record of kind {kind:#04x}, which the registry assigns elsewhere, \
@@ -459,7 +459,7 @@ mod tests {
459
459
  let bindings = encode_bindings(&Bindings::default());
460
460
  let snippets = encode_snippets(&Snippets::default());
461
461
  for (kind, body) in [(0x05, bindings), (0x06, snippets)] {
462
- let bytes = preamble_of(&[record(1, kind, &body), record(1, kind, &body)]);
462
+ let bytes = preamble_with(&[record(1, kind, &body), record(1, kind, &body)]);
463
463
  assert!(
464
464
  Preamble::decode(&bytes).is_err(),
465
465
  "a second record of kind {kind:#04x} through Preamble::decode must be refused"
@@ -485,7 +485,7 @@ mod tests {
485
485
  // @behavior WE-082
486
486
  #[test]
487
487
  fn a_criticality_that_is_neither_value_is_refused() {
488
- let bytes = preamble_of(&[record(2, 0x07, &encode_flag(&flag("f", "", false)))]);
488
+ let bytes = preamble_with(&[record(2, 0x07, &encode_flag(&flag("f", "", false)))]);
489
489
  assert!(
490
490
  Preamble::decode(&bytes).is_err(),
491
491
  "a criticality byte other than 0 or 1 through Preamble::decode must be refused"
@@ -504,8 +504,8 @@ mod tests {
504
504
  );
505
505
  let flag = record(0, 0x07, &encode_flag(&flag("f", "", false)));
506
506
  assert_eq!(
507
- Preamble::decode(&preamble_of(&[flag.clone(), bindings.clone()])),
508
- Preamble::decode(&preamble_of(&[bindings, flag])),
507
+ Preamble::decode(&preamble_with(&[flag.clone(), bindings.clone()])),
508
+ Preamble::decode(&preamble_with(&[bindings, flag])),
509
509
  "the same records in another order through Preamble::decode must read as the same \
510
510
  Preamble"
511
511
  );
@@ -17,6 +17,9 @@ pub enum Reply {
17
17
  /// every field of a Fault is kobako's, so a guest reads a refusal
18
18
  /// whatever schema it speaks.
19
19
  Fault(Fault),
20
+ /// A block exit unwound the method; the guest continues that exit,
21
+ /// which it kept, so nothing of it crosses.
22
+ Break,
20
23
  /// The receiver does not serve the request it was sent.
21
24
  Unsupported(Unsupported),
22
25
  }
@@ -24,14 +27,18 @@ pub enum Reply {
24
27
  impl Reply {
25
28
  pub fn decode(bytes: &[u8]) -> Result<Self, DecodeError> {
26
29
  let mut reader = Reader::new(bytes);
27
- match reader.u8()? {
30
+ match reader.read_u8()? {
28
31
  tag::OK => Ok(Reply::Ok(reader.remaining().to_vec())),
29
32
  // No `finish` here: a Fault owns everything after the tag,
30
33
  // including the fields a later contract version appends.
31
34
  tag::FAULT => Ok(Reply::Fault(Fault::read(&mut reader)?)),
35
+ tag::BREAK => {
36
+ reader.finish()?;
37
+ Ok(Reply::Break)
38
+ }
32
39
  tag::UNSUPPORTED => Ok(Reply::Unsupported(Unsupported::read(&mut reader)?)),
33
40
  _ => Err(DecodeError::new(
34
- "Reply tag must be 0x10 (ok), 0x11 (fault), or 0x15 (unsupported)",
41
+ "Reply tag must be 0x10 (ok), 0x11 (fault), 0x12 (break), or 0x15 (unsupported)",
35
42
  )),
36
43
  }
37
44
  }
@@ -46,21 +53,23 @@ impl Reply {
46
53
  writer.u8(tag::FAULT);
47
54
  fault.write(&mut writer);
48
55
  }
56
+ Reply::Break => {
57
+ writer.u8(tag::BREAK);
58
+ }
49
59
  Reply::Unsupported(refusal) => return refusal.encode(),
50
60
  }
51
61
  writer.into_bytes()
52
62
  }
53
63
  }
54
64
 
55
- /// The answer to one Yield Call. Carries a `Break` outcome the dispatch
56
- /// Reply has no counterpart for, because a block can end its caller.
65
+ /// The answer to one Yield Call.
57
66
  #[derive(Debug, Clone, PartialEq, Eq)]
58
67
  pub enum YieldReply {
59
68
  /// The block completed; the bytes are its value, codec-encoded.
60
69
  Ok(Vec<u8>),
61
- /// The block ran `break`; the bytes are the break value,
62
- /// codec-encoded. It returns to the guest rather than to host code.
63
- Break(Vec<u8>),
70
+ /// The block ran `break` or `return` to a frame past the Service.
71
+ /// The guest keeps the exit, so the Service is only unwound.
72
+ Break,
64
73
  /// The block raised, or failed in a way the host re-raises at the
65
74
  /// yield site.
66
75
  Error(ErrorRecord),
@@ -71,9 +80,12 @@ pub enum YieldReply {
71
80
  impl YieldReply {
72
81
  pub fn decode(bytes: &[u8]) -> Result<Self, DecodeError> {
73
82
  let mut reader = Reader::new(bytes);
74
- match reader.u8()? {
83
+ match reader.read_u8()? {
75
84
  tag::OK => Ok(YieldReply::Ok(reader.remaining().to_vec())),
76
- tag::BREAK => Ok(YieldReply::Break(reader.remaining().to_vec())),
85
+ tag::BREAK => {
86
+ reader.finish()?;
87
+ Ok(YieldReply::Break)
88
+ }
77
89
  tag::ERROR => {
78
90
  let record = ErrorRecord::read(&mut reader)?;
79
91
  reader.finish()?;
@@ -90,8 +102,8 @@ impl YieldReply {
90
102
  YieldReply::Ok(body) => {
91
103
  writer.u8(tag::OK).remainder(body);
92
104
  }
93
- YieldReply::Break(body) => {
94
- writer.u8(tag::BREAK).remainder(body);
105
+ YieldReply::Break => {
106
+ writer.u8(tag::BREAK);
95
107
  }
96
108
  YieldReply::Error(record) => {
97
109
  writer.u8(tag::ERROR);
@@ -116,6 +128,7 @@ mod tests {
116
128
  super::super::FaultKind::Undefined,
117
129
  "no such method",
118
130
  )),
131
+ Reply::Break,
119
132
  Reply::Unsupported(Unsupported::new("no such operation")),
120
133
  ] {
121
134
  let encoded = reply.encode();
@@ -142,7 +155,7 @@ mod tests {
142
155
  #[test]
143
156
  fn an_unknown_reply_tag_is_refused() {
144
157
  assert!(
145
- Reply::decode(&[0x12, 0]).is_err(),
158
+ Reply::decode(&[0x13, 0]).is_err(),
146
159
  "a Reply tag outside its arms must be rejected as a wire violation"
147
160
  );
148
161
  }
@@ -161,7 +174,7 @@ mod tests {
161
174
  fn every_live_yield_arm_round_trips() {
162
175
  let arms = [
163
176
  YieldReply::Ok(vec![0xa4]),
164
- YieldReply::Break(vec![0xa4]),
177
+ YieldReply::Break,
165
178
  YieldReply::Error(ErrorRecord {
166
179
  name: "LocalJumpError".into(),
167
180
  message: "no block".into(),
@@ -214,6 +227,11 @@ mod tests {
214
227
  "an unsupported Reply must encode as tag byte 0x15 followed by kind 0 and the \
215
228
  message"
216
229
  );
230
+ assert_eq!(
231
+ Reply::Break.encode(),
232
+ vec![0x12],
233
+ "a break Reply must encode as tag byte 0x12 alone, since the guest keeps the exit"
234
+ );
217
235
  }
218
236
 
219
237
  // @behavior WE-068
@@ -242,9 +260,9 @@ mod tests {
242
260
  "a Yield Reply carrying the block's value must encode as tag byte 0x10"
243
261
  );
244
262
  assert_eq!(
245
- YieldReply::Break(vec![0xc0]).encode(),
246
- vec![0x12, 0xc0],
247
- "a Yield Reply carrying a break value must encode as tag byte 0x12"
263
+ YieldReply::Break.encode(),
264
+ vec![0x12],
265
+ "a Yield Reply for a block exit must encode as tag byte 0x12 alone"
248
266
  );
249
267
  assert_eq!(
250
268
  YieldReply::Unsupported(Unsupported::new("x")).encode(),
@@ -18,9 +18,9 @@ pub enum Downcall {
18
18
  Eval(Vec<u8>),
19
19
  /// Call the entrypoint the Run names.
20
20
  Run(Run),
21
- /// Re-enter the block bound to the active dispatch; the bytes are
22
- /// its arguments, codec-encoded.
23
- Yield(Vec<u8>),
21
+ /// Re-enter the block of the open Call `frame` counts to, from `0`
22
+ /// at the outermost; `args` are its arguments, codec-encoded.
23
+ Yield { frame: u32, args: Vec<u8> },
24
24
  /// Compile the Preamble's source Snippets without running them. It
25
25
  /// carries no body: what it compiles is the Preamble an invocation
26
26
  /// would read.
@@ -37,7 +37,10 @@ impl Downcall {
37
37
  Ok(match tag {
38
38
  tag::EVAL => Downcall::Eval(body.to_vec()),
39
39
  tag::RUN => Downcall::Run(Run::decode(body)?),
40
- tag::YIELD => Downcall::Yield(body.to_vec()),
40
+ tag::YIELD => Downcall::Yield {
41
+ frame: reader.read_u32()?,
42
+ args: reader.remaining().to_vec(),
43
+ },
41
44
  tag::COMPILE if body.is_empty() => Downcall::Compile,
42
45
  tag::COMPILE => return Err(DecodeError::new("a compile request carries no body")),
43
46
  tag => Downcall::Unknown {
@@ -52,7 +55,7 @@ impl Downcall {
52
55
  match self {
53
56
  Downcall::Eval(source) => writer.u8(tag::EVAL).remainder(source),
54
57
  Downcall::Run(run) => writer.u8(tag::RUN).remainder(&run.encode()),
55
- Downcall::Yield(args) => writer.u8(tag::YIELD).remainder(args),
58
+ Downcall::Yield { frame, args } => writer.u8(tag::YIELD).u32(*frame).remainder(args),
56
59
  Downcall::Compile => writer.u8(tag::COMPILE),
57
60
  Downcall::Unknown { tag, body } => writer.u8(tag.get()).remainder(body),
58
61
  };
@@ -112,7 +115,7 @@ impl UnknownTag {
112
115
  }
113
116
 
114
117
  fn read_tag(reader: &mut Reader<'_>) -> Result<u8, DecodeError> {
115
- match reader.u8()? {
118
+ match reader.read_u8()? {
116
119
  tag::INVALID => Err(DecodeError::new("request tag 0x00 is invalid")),
117
120
  tag => Ok(tag),
118
121
  }
@@ -146,14 +149,18 @@ mod tests {
146
149
  (
147
150
  Downcall::Eval(b"1".to_vec()).encode(),
148
151
  Downcall::Run(run).encode(),
149
- Downcall::Yield(vec![0x90]).encode(),
152
+ Downcall::Yield {
153
+ frame: 2,
154
+ args: vec![0x90],
155
+ }
156
+ .encode(),
150
157
  Downcall::Compile.encode(),
151
158
  Upcall::Call(call).encode(),
152
159
  ),
153
160
  (
154
161
  vec![0x01, b'1'],
155
162
  vec![0x02, 0, 0, 0, 1, b'M', 0x90],
156
- vec![0x03, 0x90],
163
+ vec![0x03, 0, 0, 0, 2, 0x90],
157
164
  vec![0x08],
158
165
  vec![
159
166
  0x04, // tag: call
@@ -177,7 +184,10 @@ mod tests {
177
184
  entrypoint: "Main".into(),
178
185
  payload: vec![0x90],
179
186
  }),
180
- Downcall::Yield(vec![0x91, 0x01]),
187
+ Downcall::Yield {
188
+ frame: 0,
189
+ args: vec![0x91, 0x01],
190
+ },
181
191
  Downcall::Compile,
182
192
  Downcall::Unknown {
183
193
  tag: UnknownTag::new(0xf0).unwrap(),
@@ -18,7 +18,7 @@ pub struct Run {
18
18
  impl Run {
19
19
  pub fn decode(bytes: &[u8]) -> Result<Self, DecodeError> {
20
20
  let mut reader = Reader::new(bytes);
21
- let entrypoint = reader.text()?.to_owned();
21
+ let entrypoint = reader.read_text()?.to_owned();
22
22
  Ok(Run {
23
23
  entrypoint,
24
24
  payload: reader.remaining().to_vec(),
@@ -38,8 +38,8 @@ impl Unsupported {
38
38
  }
39
39
 
40
40
  pub(crate) fn read(reader: &mut Reader<'_>) -> Result<Self, DecodeError> {
41
- reader.u8()?;
42
- let message = reader.text()?.to_owned();
41
+ reader.read_u8()?;
42
+ let message = reader.read_text()?.to_owned();
43
43
  // The last thing its answer carries, so whatever follows belongs
44
44
  // to a later version of these fields, as for a Fault.
45
45
  reader.skip_unknown_fields();
@@ -1,4 +1,4 @@
1
- //! The core envelope — the fixed-layout outer frame of every host↔guest
1
+ //! The Envelope — the fixed-layout outer frame of every host↔guest
2
2
  //! message.
3
3
  //!
4
4
  //! Routing and outcome attribution live here; everything a resolved method
@@ -50,7 +50,7 @@ pub use unsupported::Unsupported;
50
50
 
51
51
  use std::fmt;
52
52
 
53
- /// A message that does not conform to the core envelope layout. Only
53
+ /// A message that does not conform to the Envelope layout. Only
54
54
  /// decoding produces one — every `encode` is infallible — so the name says
55
55
  /// which direction it comes from.
56
56
  ///