kobako 0.3.0 → 0.5.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 (98) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-manifest.json +1 -0
  3. data/CHANGELOG.md +29 -0
  4. data/Cargo.lock +1 -1
  5. data/README.md +85 -6
  6. data/data/kobako.wasm +0 -0
  7. data/ext/kobako/Cargo.toml +1 -1
  8. data/ext/kobako/src/lib.rs +4 -2
  9. data/ext/kobako/src/{wasm → runtime}/cache.rs +22 -18
  10. data/ext/kobako/src/runtime/capture.rs +91 -0
  11. data/ext/kobako/src/runtime/config.rs +26 -0
  12. data/ext/kobako/src/runtime/dispatch.rs +211 -0
  13. data/ext/kobako/src/runtime/exports.rs +51 -0
  14. data/ext/kobako/src/runtime/guest_mem.rs +228 -0
  15. data/ext/kobako/src/{wasm/host_state.rs → runtime/invocation.rs} +195 -81
  16. data/ext/kobako/src/runtime/trap.rs +134 -0
  17. data/ext/kobako/src/runtime.rs +782 -0
  18. data/ext/kobako/src/snapshot.rs +110 -0
  19. data/lib/kobako/capture.rb +11 -16
  20. data/lib/kobako/catalog/handles.rb +107 -0
  21. data/lib/kobako/catalog/namespaces.rb +99 -0
  22. data/lib/kobako/{snippet/table.rb → catalog/snippets.rb} +37 -62
  23. data/lib/kobako/catalog.rb +18 -0
  24. data/lib/kobako/codec/decoder.rb +13 -7
  25. data/lib/kobako/codec/factory.rb +21 -18
  26. data/lib/kobako/codec/utils.rb +118 -29
  27. data/lib/kobako/codec.rb +6 -3
  28. data/lib/kobako/errors.rb +45 -28
  29. data/lib/kobako/fault.rb +40 -0
  30. data/lib/kobako/handle.rb +60 -0
  31. data/lib/kobako/namespace.rb +67 -0
  32. data/lib/kobako/outcome.rb +55 -29
  33. data/lib/kobako/runtime.rb +30 -0
  34. data/lib/kobako/sandbox.rb +131 -67
  35. data/lib/kobako/sandbox_options.rb +6 -9
  36. data/lib/kobako/snapshot.rb +40 -0
  37. data/lib/kobako/snippet/binary.rb +6 -7
  38. data/lib/kobako/snippet/source.rb +8 -8
  39. data/lib/kobako/snippet.rb +7 -9
  40. data/lib/kobako/transport/dispatcher.rb +195 -0
  41. data/lib/kobako/transport/error.rb +24 -0
  42. data/lib/kobako/transport/request.rb +78 -0
  43. data/lib/kobako/transport/response.rb +69 -0
  44. data/lib/kobako/transport/run.rb +141 -0
  45. data/lib/kobako/transport/yield.rb +91 -0
  46. data/lib/kobako/transport/yielder.rb +89 -0
  47. data/lib/kobako/transport.rb +24 -0
  48. data/lib/kobako/usage.rb +41 -0
  49. data/lib/kobako/version.rb +1 -1
  50. data/lib/kobako.rb +4 -3
  51. data/release-please-config.json +24 -0
  52. data/sig/kobako/capture.rbs +0 -2
  53. data/sig/kobako/{rpc/handle_table.rbs → catalog/handles.rbs} +3 -9
  54. data/sig/kobako/catalog/namespaces.rbs +17 -0
  55. data/sig/kobako/{snippet/table.rbs → catalog/snippets.rbs} +2 -11
  56. data/sig/kobako/{rpc.rbs → catalog.rbs} +1 -1
  57. data/sig/kobako/codec/decoder.rbs +2 -1
  58. data/sig/kobako/codec/factory.rbs +3 -3
  59. data/sig/kobako/codec/utils.rbs +11 -1
  60. data/sig/kobako/errors.rbs +7 -7
  61. data/sig/kobako/fault.rbs +19 -0
  62. data/sig/kobako/handle.rbs +18 -0
  63. data/sig/kobako/namespace.rbs +19 -0
  64. data/sig/kobako/outcome.rbs +2 -2
  65. data/sig/kobako/runtime.rbs +23 -0
  66. data/sig/kobako/sandbox.rbs +10 -7
  67. data/sig/kobako/snapshot.rbs +15 -0
  68. data/sig/kobako/transport/dispatcher.rbs +34 -0
  69. data/sig/kobako/transport/error.rbs +6 -0
  70. data/sig/kobako/transport/request.rbs +32 -0
  71. data/sig/kobako/transport/response.rbs +30 -0
  72. data/sig/kobako/transport/run.rbs +27 -0
  73. data/sig/kobako/transport/yield.rbs +34 -0
  74. data/sig/kobako/transport/yielder.rbs +21 -0
  75. data/sig/kobako/transport.rbs +4 -0
  76. data/sig/kobako/usage.rbs +11 -0
  77. metadata +52 -30
  78. data/ext/kobako/src/wasm/dispatch.rs +0 -161
  79. data/ext/kobako/src/wasm/instance.rs +0 -771
  80. data/ext/kobako/src/wasm.rs +0 -125
  81. data/lib/kobako/invocation.rb +0 -112
  82. data/lib/kobako/rpc/dispatcher.rb +0 -169
  83. data/lib/kobako/rpc/envelope.rb +0 -118
  84. data/lib/kobako/rpc/fault.rb +0 -41
  85. data/lib/kobako/rpc/handle.rb +0 -39
  86. data/lib/kobako/rpc/handle_table.rb +0 -107
  87. data/lib/kobako/rpc/namespace.rb +0 -74
  88. data/lib/kobako/rpc/server.rb +0 -158
  89. data/lib/kobako/rpc.rb +0 -11
  90. data/lib/kobako/wasm.rb +0 -25
  91. data/sig/kobako/invocation.rbs +0 -23
  92. data/sig/kobako/rpc/dispatcher.rbs +0 -33
  93. data/sig/kobako/rpc/envelope.rbs +0 -51
  94. data/sig/kobako/rpc/fault.rbs +0 -20
  95. data/sig/kobako/rpc/handle.rbs +0 -19
  96. data/sig/kobako/rpc/namespace.rbs +0 -24
  97. data/sig/kobako/rpc/server.rbs +0 -37
  98. data/sig/kobako/wasm.rbs +0 -39
@@ -0,0 +1,34 @@
1
+ module Kobako
2
+ module Transport
3
+ TAG_OK: Integer
4
+ TAG_BREAK: Integer
5
+ TAG_RESERVED: Integer
6
+ TAG_ERROR: Integer
7
+ LIVE_TAGS: Array[Integer]
8
+
9
+ class Yield
10
+ attr_reader tag: Integer
11
+ attr_reader value: untyped
12
+
13
+ def self.decode: (String bytes) -> Yield
14
+
15
+ def self.reject_dead_tag!: (Integer tag) -> void
16
+
17
+ def initialize: (tag: Integer, value: untyped) -> void
18
+
19
+ def with: (?tag: Integer, ?value: untyped) -> Yield
20
+
21
+ def ok?: () -> bool
22
+
23
+ def break?: () -> bool
24
+
25
+ def error?: () -> bool
26
+
27
+ def encode: () -> String
28
+
29
+ def ==: (untyped other) -> bool
30
+
31
+ def hash: () -> Integer
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,21 @@
1
+ module Kobako
2
+ module Transport
3
+ class Yielder
4
+ @yield_to_guest: ^(String) -> String
5
+ @break_tag: Symbol
6
+ @active: bool
7
+
8
+ def initialize: (^(String) -> String yield_to_guest, Symbol break_tag) -> void
9
+
10
+ def yield: (*untyped args) -> untyped
11
+
12
+ def to_proc: () -> Proc
13
+
14
+ def invalidate!: () -> void
15
+
16
+ private
17
+
18
+ def yield_failure: (untyped payload, default: String) -> RuntimeError
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,4 @@
1
+ module Kobako
2
+ module Transport
3
+ end
4
+ end
@@ -0,0 +1,11 @@
1
+ module Kobako
2
+ class Usage < Data
3
+ EMPTY: Usage
4
+
5
+ attr_reader wall_time: Float
6
+ attr_reader memory_peak: Integer
7
+
8
+ def self.new: (wall_time: Float, memory_peak: Integer) -> Usage
9
+ | (Float wall_time, Integer memory_peak) -> Usage
10
+ end
11
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kobako
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aotokitsuruya
@@ -29,14 +29,14 @@ dependencies:
29
29
  requirements:
30
30
  - - "~>"
31
31
  - !ruby/object:Gem::Version
32
- version: 0.9.91
32
+ version: 0.9.128
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: 0.9.91
39
+ version: 0.9.128
40
40
  description: kobako provides an in-process Wasm sandbox (wasmtime + mruby) with a
41
41
  MessagePack-based host/guest RPC, allowing Ruby applications to execute untrusted
42
42
  mruby scripts under capability-based Service injection.
@@ -47,6 +47,8 @@ extensions:
47
47
  - ext/kobako/extconf.rb
48
48
  extra_rdoc_files: []
49
49
  files:
50
+ - ".release-please-manifest.json"
51
+ - CHANGELOG.md
50
52
  - Cargo.lock
51
53
  - Cargo.toml
52
54
  - LICENSE
@@ -55,13 +57,22 @@ files:
55
57
  - ext/kobako/Cargo.toml
56
58
  - ext/kobako/extconf.rb
57
59
  - ext/kobako/src/lib.rs
58
- - ext/kobako/src/wasm.rs
59
- - ext/kobako/src/wasm/cache.rs
60
- - ext/kobako/src/wasm/dispatch.rs
61
- - ext/kobako/src/wasm/host_state.rs
62
- - ext/kobako/src/wasm/instance.rs
60
+ - ext/kobako/src/runtime.rs
61
+ - ext/kobako/src/runtime/cache.rs
62
+ - ext/kobako/src/runtime/capture.rs
63
+ - ext/kobako/src/runtime/config.rs
64
+ - ext/kobako/src/runtime/dispatch.rs
65
+ - ext/kobako/src/runtime/exports.rs
66
+ - ext/kobako/src/runtime/guest_mem.rs
67
+ - ext/kobako/src/runtime/invocation.rs
68
+ - ext/kobako/src/runtime/trap.rs
69
+ - ext/kobako/src/snapshot.rs
63
70
  - lib/kobako.rb
64
71
  - lib/kobako/capture.rb
72
+ - lib/kobako/catalog.rb
73
+ - lib/kobako/catalog/handles.rb
74
+ - lib/kobako/catalog/namespaces.rb
75
+ - lib/kobako/catalog/snippets.rb
65
76
  - lib/kobako/codec.rb
66
77
  - lib/kobako/codec/decoder.rb
67
78
  - lib/kobako/codec/encoder.rb
@@ -69,51 +80,62 @@ files:
69
80
  - lib/kobako/codec/factory.rb
70
81
  - lib/kobako/codec/utils.rb
71
82
  - lib/kobako/errors.rb
72
- - lib/kobako/invocation.rb
83
+ - lib/kobako/fault.rb
84
+ - lib/kobako/handle.rb
85
+ - lib/kobako/namespace.rb
73
86
  - lib/kobako/outcome.rb
74
87
  - lib/kobako/outcome/panic.rb
75
- - lib/kobako/rpc.rb
76
- - lib/kobako/rpc/dispatcher.rb
77
- - lib/kobako/rpc/envelope.rb
78
- - lib/kobako/rpc/fault.rb
79
- - lib/kobako/rpc/handle.rb
80
- - lib/kobako/rpc/handle_table.rb
81
- - lib/kobako/rpc/namespace.rb
82
- - lib/kobako/rpc/server.rb
88
+ - lib/kobako/runtime.rb
83
89
  - lib/kobako/sandbox.rb
84
90
  - lib/kobako/sandbox_options.rb
91
+ - lib/kobako/snapshot.rb
85
92
  - lib/kobako/snippet.rb
86
93
  - lib/kobako/snippet/binary.rb
87
94
  - lib/kobako/snippet/source.rb
88
- - lib/kobako/snippet/table.rb
95
+ - lib/kobako/transport.rb
96
+ - lib/kobako/transport/dispatcher.rb
97
+ - lib/kobako/transport/error.rb
98
+ - lib/kobako/transport/request.rb
99
+ - lib/kobako/transport/response.rb
100
+ - lib/kobako/transport/run.rb
101
+ - lib/kobako/transport/yield.rb
102
+ - lib/kobako/transport/yielder.rb
103
+ - lib/kobako/usage.rb
89
104
  - lib/kobako/version.rb
90
- - lib/kobako/wasm.rb
105
+ - release-please-config.json
91
106
  - sig/kobako.rbs
92
107
  - sig/kobako/capture.rbs
108
+ - sig/kobako/catalog.rbs
109
+ - sig/kobako/catalog/handles.rbs
110
+ - sig/kobako/catalog/namespaces.rbs
111
+ - sig/kobako/catalog/snippets.rbs
93
112
  - sig/kobako/codec/decoder.rbs
94
113
  - sig/kobako/codec/encoder.rbs
95
114
  - sig/kobako/codec/error.rbs
96
115
  - sig/kobako/codec/factory.rbs
97
116
  - sig/kobako/codec/utils.rbs
98
117
  - sig/kobako/errors.rbs
99
- - sig/kobako/invocation.rbs
118
+ - sig/kobako/fault.rbs
119
+ - sig/kobako/handle.rbs
120
+ - sig/kobako/namespace.rbs
100
121
  - sig/kobako/outcome.rbs
101
122
  - sig/kobako/outcome/panic.rbs
102
- - sig/kobako/rpc.rbs
103
- - sig/kobako/rpc/dispatcher.rbs
104
- - sig/kobako/rpc/envelope.rbs
105
- - sig/kobako/rpc/fault.rbs
106
- - sig/kobako/rpc/handle.rbs
107
- - sig/kobako/rpc/handle_table.rbs
108
- - sig/kobako/rpc/namespace.rbs
109
- - sig/kobako/rpc/server.rbs
123
+ - sig/kobako/runtime.rbs
110
124
  - sig/kobako/sandbox.rbs
111
125
  - sig/kobako/sandbox_options.rbs
126
+ - sig/kobako/snapshot.rbs
112
127
  - sig/kobako/snippet.rbs
113
128
  - sig/kobako/snippet/binary.rbs
114
129
  - sig/kobako/snippet/source.rbs
115
- - sig/kobako/snippet/table.rbs
116
- - sig/kobako/wasm.rbs
130
+ - sig/kobako/transport.rbs
131
+ - sig/kobako/transport/dispatcher.rbs
132
+ - sig/kobako/transport/error.rbs
133
+ - sig/kobako/transport/request.rbs
134
+ - sig/kobako/transport/response.rbs
135
+ - sig/kobako/transport/run.rbs
136
+ - sig/kobako/transport/yield.rbs
137
+ - sig/kobako/transport/yielder.rbs
138
+ - sig/kobako/usage.rbs
117
139
  homepage: https://github.com/elct9620/kobako
118
140
  licenses:
119
141
  - Apache-2.0
@@ -1,161 +0,0 @@
1
- //! Host-side dispatch for the `__kobako_dispatch` import.
2
- //!
3
- //! When the guest invokes the wasm import declared in
4
- //! `wasm/kobako-wasm/src/abi.rs`, wasmtime calls back into the host
5
- //! through the closure built in [`super::instance::Instance::build`].
6
- //! That closure delegates here. The dispatcher (docs/behavior.md B-12 / B-13):
7
- //!
8
- //! 1. Reads the Request bytes from guest linear memory.
9
- //! 2. Hands them to the Ruby-side `Kobako::RPC::Server` and recovers
10
- //! Response bytes.
11
- //! 3. Allocates a guest buffer via `__kobako_alloc(len)` invoked
12
- //! through `Caller::get_export`.
13
- //! 4. Writes the Response bytes into the guest buffer.
14
- //! 5. Returns packed `(ptr<<32)|len` for the guest to decode.
15
- //!
16
- //! Returns 0 on any step failure. `Kobako::Sandbox` always installs a
17
- //! Server before invoking the guest, so reaching the dispatcher with
18
- //! no Server bound is itself a wire-layer fault; the guest maps a 0
19
- //! return to a trap. Failures during normal dispatch surface as
20
- //! Response.err envelopes from the Server itself — they never reach
21
- //! this 0-return path.
22
- //!
23
- //! ## Why this module writes to `stderr`
24
- //!
25
- //! This file is the one place in `ext/` that deliberately prints
26
- //! through `eprintln!`. The host normally surfaces faults by
27
- //! raising a `MagnusError` back into Ruby; the dispatcher contract
28
- //! is the exception — it must return a packed `i64` to the guest
29
- //! and cannot raise, so a 0 return is the only signal the wasm side
30
- //! receives. The guest collapses every 0 into the same trap, so the
31
- //! Ruby host has no way to attribute the failure to a specific
32
- //! step (missing `memory` export vs. no Server bound vs. Server
33
- //! raised vs. `__kobako_alloc` returned 0 vs. `memory.write`
34
- //! rejected).
35
- //!
36
- //! [`handle`] writes a single `[kobako-dispatch] <reason>` line to
37
- //! `stderr` on each failure path so operators have a breadcrumb to
38
- //! correlate the trap with the actual cause. The line is emitted in
39
- //! both debug and release builds on purpose: dispatcher failures
40
- //! are wire-layer faults rather than expected error paths
41
- //! (`Kobako::Sandbox` always installs a Server, the Server is
42
- //! contracted never to raise, etc.), so the "release-build noise"
43
- //! cost is bounded — under normal operation the line is never
44
- //! written. Operators that need to silence the channel can redirect
45
- //! the host process's stderr, but the kobako convention is "ext
46
- //! never logs" plus this single, named exception.
47
-
48
- use magnus::value::{Opaque, ReprValue};
49
- use magnus::{Error as MagnusError, RString, Ruby, Value};
50
- use wasmtime::{Caller, Extern};
51
-
52
- use super::host_state::HostState;
53
-
54
- /// Drive a single `__kobako_dispatch` invocation end-to-end. Entry point
55
- /// from the wasmtime closure built in [`super::instance::Instance::build`].
56
- ///
57
- /// Returns the packed `(ptr<<32)|len` u64 on success, 0 on any
58
- /// wire-layer fault. Failure paths log a `[kobako-dispatch]` line to
59
- /// `stderr` so operators have a breadcrumb when the guest sees a 0
60
- /// return and traps; before this every failure was silent. The Server
61
- /// itself is contracted never to raise (it folds Service exceptions
62
- /// into Response.err envelopes), so reaching the failure path is
63
- /// always a wiring bug or wire-layer fault rather than an expected
64
- /// path.
65
- pub(crate) fn handle(caller: &mut Caller<'_, HostState>, req_ptr: i32, req_len: i32) -> i64 {
66
- match try_handle(caller, req_ptr, req_len) {
67
- Ok(packed) => packed,
68
- Err(reason) => {
69
- eprintln!("[kobako-dispatch] {}", reason);
70
- 0
71
- }
72
- }
73
- }
74
-
75
- /// Result-returning core of [`handle`]. Pulled out so each early
76
- /// failure path carries a diagnostic string instead of an opaque 0.
77
- fn try_handle(
78
- caller: &mut Caller<'_, HostState>,
79
- req_ptr: i32,
80
- req_len: i32,
81
- ) -> Result<i64, &'static str> {
82
- let req_bytes = read_caller_memory(caller, req_ptr, req_len)
83
- .ok_or("guest 'memory' export missing or request slice out of bounds")?;
84
-
85
- // `Kobako::Sandbox` always installs a Server before invoking the
86
- // guest, so reaching this branch indicates a misuse rather than a
87
- // normal control path.
88
- let server = caller
89
- .data()
90
- .server()
91
- .ok_or("no Ruby Server bound — Sandbox#run must precede __kobako_dispatch")?;
92
-
93
- let resp_bytes = invoke_server(server, &req_bytes).map_err(|_| {
94
- "Ruby Server#dispatch raised — contract is to fold faults into Response.err"
95
- })?;
96
-
97
- write_response(caller, &resp_bytes)
98
- }
99
-
100
- /// Call the Ruby Server's `#dispatch(request_bytes)` method and return
101
- /// the encoded Response bytes. Errors here mean the Server itself
102
- /// failed (it is contracted never to raise — see
103
- /// `Kobako::RPC::Server#dispatch`), which we treat as a wire-layer fault.
104
- fn invoke_server(server: Opaque<Value>, req_bytes: &[u8]) -> Result<Vec<u8>, MagnusError> {
105
- // The wasmtime callback runs on the same Ruby thread that called the
106
- // active Sandbox invocation (#eval or #run) — the invariant SPEC
107
- // Implementation Standards Architecture pins for the host gem — so
108
- // `Ruby::get()` is always available here. Panicking with `expect`
109
- // localises the violation rather than letting a nonsense error
110
- // propagate.
111
- let ruby = Ruby::get().expect("Ruby handle unavailable in __kobako_dispatch");
112
- let server_value: Value = ruby.get_inner(server);
113
- let req_str = ruby.str_from_slice(req_bytes);
114
- let resp: RString = server_value.funcall("dispatch", (req_str,))?;
115
- Ok(super::rstring_to_vec(resp))
116
- }
117
-
118
- /// Allocate a guest-side buffer through `__kobako_alloc` and copy the
119
- /// response bytes into it. Returns the packed `(ptr<<32)|len` u64.
120
- /// Each failure path carries a `&'static str` reason so the dispatcher
121
- /// wrapper can surface a useful diagnostic rather than a silent 0.
122
- fn write_response(caller: &mut Caller<'_, HostState>, bytes: &[u8]) -> Result<i64, &'static str> {
123
- let alloc = match caller.get_export("__kobako_alloc") {
124
- Some(Extern::Func(f)) => f
125
- .typed::<i32, i32>(&*caller)
126
- .map_err(|_| "guest '__kobako_alloc' export has wrong signature")?,
127
- _ => return Err("guest '__kobako_alloc' export missing"),
128
- };
129
- let len_i32 = i32::try_from(bytes.len()).map_err(|_| "response exceeds i32::MAX bytes")?;
130
- let ptr = alloc
131
- .call(&mut *caller, len_i32)
132
- .map_err(|_| "__kobako_alloc trapped")?;
133
- if ptr == 0 {
134
- return Err("__kobako_alloc returned 0 (out of memory)");
135
- }
136
-
137
- let mem = match caller.get_export("memory") {
138
- Some(Extern::Memory(m)) => m,
139
- _ => return Err("guest 'memory' export missing"),
140
- };
141
- mem.write(&mut *caller, ptr as usize, bytes)
142
- .map_err(|_| "memory.write rejected response buffer range")?;
143
-
144
- let ptr_u32 = ptr as u32;
145
- let len_u32 = bytes.len() as u32;
146
- Ok(((ptr_u32 as i64) << 32) | (len_u32 as i64))
147
- }
148
-
149
- /// Copy `[ptr, ptr+len)` out of the guest's linear memory as seen from
150
- /// `caller`. Returns `None` when `memory` is not exported or the slice
151
- /// falls outside the live memory range.
152
- fn read_caller_memory(caller: &mut Caller<'_, HostState>, ptr: i32, len: i32) -> Option<Vec<u8>> {
153
- let mem = match caller.get_export("memory") {
154
- Some(Extern::Memory(m)) => m,
155
- _ => return None,
156
- };
157
- let data = mem.data(&caller);
158
- let start = ptr as usize;
159
- let end = start.checked_add(len as usize)?;
160
- data.get(start..end).map(|s| s.to_vec())
161
- }