kobako 0.27.0 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +241 -0
- data/Cargo.lock +132 -126
- data/README.md +240 -147
- data/ROADMAP.md +28 -22
- data/SECURITY.md +2 -2
- data/crates/kobako-runtime/CHANGELOG.md +117 -0
- data/crates/kobako-runtime/Cargo.toml +27 -6
- data/crates/kobako-runtime/README.md +16 -10
- data/crates/kobako-runtime/src/dispatch.rs +8 -0
- data/crates/kobako-runtime/src/error.rs +8 -9
- data/crates/kobako-runtime/src/filesystem/directory/spelling.rs +194 -0
- data/crates/kobako-runtime/src/filesystem/directory.rs +1082 -0
- data/crates/kobako-runtime/src/filesystem/memory.rs +1100 -0
- data/crates/kobako-runtime/src/filesystem/overlay/exclusive.rs +88 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack.rs +808 -0
- data/crates/kobako-runtime/src/filesystem/overlay.rs +1955 -0
- data/crates/kobako-runtime/src/filesystem/path.rs +60 -0
- data/crates/kobako-runtime/src/filesystem/temporary.rs +167 -0
- data/crates/kobako-runtime/src/filesystem.rs +392 -0
- data/crates/kobako-runtime/src/grant.rs +20 -0
- data/crates/kobako-runtime/src/lib.rs +12 -2
- data/crates/kobako-runtime/src/mount.rs +178 -0
- data/crates/kobako-runtime/src/profile.rs +4 -4
- data/crates/kobako-runtime/src/runtime.rs +43 -17
- data/crates/kobako-runtime/src/snapshot.rs +33 -15
- data/crates/kobako-runtime/src/yielder.rs +14 -6
- data/crates/kobako-transport/CHANGELOG.md +45 -0
- data/crates/kobako-transport/Cargo.toml +1 -1
- data/crates/kobako-transport/README.md +21 -12
- data/crates/kobako-transport/src/abi.rs +11 -14
- data/crates/kobako-transport/src/envelope/bytes.rs +18 -6
- data/crates/kobako-transport/src/envelope/compile_reply.rs +139 -0
- data/crates/kobako-transport/src/envelope/fault.rs +2 -1
- data/crates/kobako-transport/src/envelope/outcome.rs +17 -16
- data/crates/kobako-transport/src/envelope/preamble.rs +513 -0
- data/crates/kobako-transport/src/envelope/reply.rs +103 -61
- data/crates/kobako-transport/src/envelope/request.rs +305 -0
- data/crates/kobako-transport/src/envelope/run.rs +1 -2
- data/crates/kobako-transport/src/envelope/tag.rs +49 -0
- data/crates/kobako-transport/src/envelope/unsupported.rs +105 -0
- data/crates/kobako-transport/src/envelope.rs +19 -12
- data/crates/kobako-transport/src/lib.rs +0 -2
- data/crates/kobako-wasmtime/CHANGELOG.md +163 -0
- data/crates/kobako-wasmtime/Cargo.toml +23 -16
- data/crates/kobako-wasmtime/README.md +6 -4
- data/crates/kobako-wasmtime/src/abi.rs +67 -36
- data/crates/kobako-wasmtime/src/ambient.rs +64 -5
- data/crates/kobako-wasmtime/src/cache.rs +204 -127
- data/crates/kobako-wasmtime/src/capture.rs +4 -11
- data/crates/kobako-wasmtime/src/config.rs +69 -15
- data/crates/kobako-wasmtime/src/descriptors.rs +167 -0
- data/crates/kobako-wasmtime/src/dispatch.rs +95 -87
- data/crates/kobako-wasmtime/src/driver.rs +174 -125
- data/crates/kobako-wasmtime/src/exports.rs +88 -34
- data/crates/kobako-wasmtime/src/guest_mem.rs +151 -138
- data/crates/kobako-wasmtime/src/imports.rs +46 -0
- data/crates/kobako-wasmtime/src/instance_pre.rs +19 -58
- data/crates/kobako-wasmtime/src/invocation.rs +84 -412
- data/crates/kobako-wasmtime/src/io.rs +89 -0
- data/crates/kobako-wasmtime/src/lib.rs +7 -24
- data/crates/kobako-wasmtime/src/limiter.rs +253 -0
- data/crates/kobako-wasmtime/src/preview1/args.rs +60 -0
- data/crates/kobako-wasmtime/src/preview1/beneath.rs +188 -0
- data/crates/kobako-wasmtime/src/preview1/differential/mount.rs +609 -0
- data/crates/kobako-wasmtime/src/preview1/differential.rs +372 -0
- data/crates/kobako-wasmtime/src/preview1/errno.rs +267 -0
- data/crates/kobako-wasmtime/src/preview1/fd.rs +672 -0
- data/crates/kobako-wasmtime/src/preview1/memory.rs +205 -0
- data/crates/kobako-wasmtime/src/preview1/mount_view.rs +2499 -0
- data/crates/kobako-wasmtime/src/preview1/path.rs +421 -0
- data/crates/kobako-wasmtime/src/preview1/poll.rs +333 -0
- data/crates/kobako-wasmtime/src/preview1/sock.rs +30 -0
- data/crates/kobako-wasmtime/src/preview1.rs +461 -0
- data/crates/kobako-wasmtime/src/trap.rs +66 -59
- data/crates/kobako-wasmtime/src/wasi.rs +317 -0
- data/crates/kobako-wasmtime/tests/compile_posture.rs +43 -0
- data/crates/kobako-wasmtime/tests/guest_file.rs +1054 -0
- data/crates/kobako-wasmtime/tests/preamble_flags.rs +70 -0
- data/crates/kobako-wasmtime/tests/snippet_compile.rs +95 -0
- data/crates/kobako-wasmtime/tests/support/mod.rs +30 -0
- data/crates/kobako-wasmtime/tests/unanswered_call.rs +86 -0
- data/crates/kobako-wasmtime/tests/unsupported_call.rs +51 -0
- data/data/kobako.wasm +0 -0
- data/ext/kobako/Cargo.toml +4 -2
- data/ext/kobako/src/runtime/bridge.rs +177 -113
- data/ext/kobako/src/runtime/errors.rs +26 -32
- data/ext/kobako/src/runtime/escape.rs +53 -0
- data/ext/kobako/src/runtime/filesystem/grants.rs +91 -0
- data/ext/kobako/src/runtime/filesystem/ruby.rs +400 -0
- data/ext/kobako/src/runtime/filesystem.rs +236 -0
- data/ext/kobako/src/runtime/gvl.rs +11 -13
- data/ext/kobako/src/runtime/pinned_value.rs +31 -0
- data/ext/kobako/src/runtime/snapshot.rs +127 -0
- data/ext/kobako/src/runtime.rs +190 -312
- data/lib/kobako/capture.rb +1 -4
- data/lib/kobako/catalog/extensions.rb +21 -59
- data/lib/kobako/catalog/flags.rb +29 -0
- data/lib/kobako/catalog/grants.rb +30 -0
- data/lib/kobako/catalog/handles.rb +7 -54
- data/lib/kobako/catalog/mount.rb +27 -0
- data/lib/kobako/catalog/services.rb +17 -60
- data/lib/kobako/catalog/snippets.rb +41 -69
- data/lib/kobako/catalog.rb +13 -10
- data/lib/kobako/codec/decoder.rb +10 -28
- data/lib/kobako/codec/encoder.rb +3 -22
- data/lib/kobako/codec/error.rb +6 -27
- data/lib/kobako/codec/ext_types.rb +72 -94
- data/lib/kobako/codec/handle_walk.rb +65 -130
- data/lib/kobako/codec/nesting.rb +3 -8
- data/lib/kobako/codec/state.rb +21 -11
- data/lib/kobako/codec/utils.rb +6 -34
- data/lib/kobako/codec/value_budget.rb +86 -0
- data/lib/kobako/codec.rb +10 -22
- data/lib/kobako/constant_name.rb +8 -0
- data/lib/kobako/context.rb +83 -117
- data/lib/kobako/errors.rb +57 -132
- data/lib/kobako/execution.rb +16 -21
- data/lib/kobako/extension.rb +23 -40
- data/lib/kobako/filesystem.rb +38 -0
- data/lib/kobako/handle.rb +7 -12
- data/lib/kobako/outcome.rb +51 -80
- data/lib/kobako/payload/arguments.rb +1 -16
- data/lib/kobako/payload.rb +4 -12
- data/lib/kobako/pool.rb +25 -55
- data/lib/kobako/runtime.rb +7 -21
- data/lib/kobako/sandbox.rb +128 -137
- data/lib/kobako/sandbox_options.rb +71 -90
- data/lib/kobako/snippet/bytecode.rb +11 -0
- data/lib/kobako/snippet/source.rb +1 -16
- data/lib/kobako/snippet.rb +2 -10
- data/lib/kobako/transport/block_failure.rb +35 -0
- data/lib/kobako/transport/call.rb +1 -14
- data/lib/kobako/transport/dispatcher.rb +127 -206
- data/lib/kobako/transport/error.rb +6 -20
- data/lib/kobako/transport/exposure.rb +10 -17
- data/lib/kobako/transport/reflection.rb +108 -63
- data/lib/kobako/transport/run.rb +17 -71
- data/lib/kobako/transport/yielder.rb +20 -94
- data/lib/kobako/transport.rb +4 -15
- data/lib/kobako/unresolved.rb +4 -11
- data/lib/kobako/usage.rb +2 -6
- data/lib/kobako/version.rb +1 -1
- data/lib/kobako.rb +1 -0
- data/sig/kobako/catalog/extensions.rbs +2 -2
- data/sig/kobako/catalog/flags.rbs +13 -0
- data/sig/kobako/catalog/grants.rbs +13 -0
- data/sig/kobako/catalog/mount.rbs +28 -0
- data/sig/kobako/catalog/services.rbs +3 -5
- data/sig/kobako/catalog/snippets.rbs +5 -5
- data/sig/kobako/catalog.rbs +1 -0
- data/sig/kobako/codec/ext_types.rbs +10 -8
- data/sig/kobako/codec/handle_walk.rbs +9 -7
- data/sig/kobako/codec/state.rbs +7 -1
- data/sig/kobako/codec/utils.rbs +2 -2
- data/sig/kobako/codec/value_budget.rbs +23 -0
- data/sig/kobako/codec.rbs +4 -2
- data/sig/kobako/constant_name.rbs +3 -0
- data/sig/kobako/context.rbs +15 -8
- data/sig/kobako/errors.rbs +10 -4
- data/sig/kobako/filesystem.rbs +88 -0
- data/sig/kobako/guest_yielder.rbs +9 -0
- data/sig/kobako/outcome.rbs +6 -8
- data/sig/kobako/pool.rbs +1 -1
- data/sig/kobako/runtime.rbs +21 -45
- data/sig/kobako/sandbox.rbs +11 -1
- data/sig/kobako/sandbox_options.rbs +25 -7
- data/sig/kobako/snapshot.rbs +20 -0
- data/sig/kobako/snippet/bytecode.rbs +12 -0
- data/sig/kobako/transport/block_failure.rbs +13 -0
- data/sig/kobako/transport/dispatcher.rbs +15 -19
- data/sig/kobako/transport/exposure.rbs +1 -1
- data/sig/kobako/transport/reflection.rbs +19 -4
- data/sig/kobako/transport/run.rbs +1 -3
- data/sig/kobako/transport/yielder.rbs +5 -9
- data/sig/kobako/transport.rbs +9 -8
- metadata +75 -10
- data/crates/kobako-transport/src/envelope/invocation_frames.rs +0 -241
- data/crates/kobako-wasmtime/src/frames.rs +0 -273
- data/lib/kobako/snippet/binary.rb +0 -26
- data/rust-toolchain.toml +0 -15
- data/sig/kobako/snippet/binary.rbs +0 -12
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kobako
|
|
4
|
+
module Transport
|
|
5
|
+
# The newest failure a guest block raised to a Service during one
|
|
6
|
+
# invocation, whichever open Call carried that block: a Service may run
|
|
7
|
+
# a block stored from an earlier Call, and its failure is still the
|
|
8
|
+
# guest's own.
|
|
9
|
+
class BlockFailure # :nodoc:
|
|
10
|
+
def initialize
|
|
11
|
+
@raised = nil
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Only the newest is kept: a Service that rescued an earlier one and
|
|
15
|
+
# yielded again has already handled it.
|
|
16
|
+
def remember(error)
|
|
17
|
+
@raised = error
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Identity rather than class: a Service that rescued the block's
|
|
21
|
+
# failure and raised its own has reported something else, and the
|
|
22
|
+
# guest must hear about that instead.
|
|
23
|
+
#
|
|
24
|
+
# The class travels with the message because not every block failure
|
|
25
|
+
# is an exception the guest holds — a block value the guest refused
|
|
26
|
+
# has a class to raise under and no object to continue.
|
|
27
|
+
def fault_text(error)
|
|
28
|
+
raised = @raised
|
|
29
|
+
return if raised.nil? || !error.equal?(raised)
|
|
30
|
+
|
|
31
|
+
"#{raised.klass}: #{raised.message}"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -2,20 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Kobako
|
|
4
4
|
module Transport
|
|
5
|
-
|
|
6
|
-
# core envelope. The routing fields arrive already decoded — a String
|
|
7
|
-
# for a bound constant's path, an Integer for a Capability Handle id —
|
|
8
|
-
# so the host resolves a target without interpreting a payload byte.
|
|
9
|
-
#
|
|
10
|
-
# +payload+ stays bytes here on purpose: routing never interprets a
|
|
11
|
-
# payload byte, and the one place that does — the Dispatcher — must
|
|
12
|
-
# decode it inside the Codec bracket that records whether a Handle
|
|
13
|
-
# crossed.
|
|
14
|
-
#
|
|
15
|
-
# Built on the +class X < Data.define(...)+ subclass form so the class
|
|
16
|
-
# body is fully Steep-visible; see +.rubocop.yml+ for the
|
|
17
|
-
# rationale.
|
|
18
|
-
class Call < Data.define(:target, :method_name, :block_given, :payload)
|
|
5
|
+
class Call < Data.define(:target, :method_name, :block_given, :payload) # :nodoc:
|
|
19
6
|
end
|
|
20
7
|
end
|
|
21
8
|
end
|
|
@@ -3,259 +3,180 @@
|
|
|
3
3
|
require_relative "../codec"
|
|
4
4
|
require_relative "../errors"
|
|
5
5
|
require_relative "../payload"
|
|
6
|
+
require_relative "block_failure"
|
|
6
7
|
require_relative "call"
|
|
7
8
|
require_relative "reflection"
|
|
8
9
|
require_relative "yielder"
|
|
9
10
|
|
|
10
11
|
module Kobako
|
|
11
|
-
# See lib/kobako/transport.rb for the umbrella module doc; this file
|
|
12
|
-
# owns the pure-function dispatcher that answers a routed Call.
|
|
13
12
|
module Transport
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
#
|
|
22
|
-
# The module is stateless — all mutable state is threaded through
|
|
23
|
-
# arguments so Dispatcher has no instance variables and no side
|
|
24
|
-
# effects beyond mutating the Catalog::Handles via +alloc+ when a
|
|
25
|
-
# non-wire-representable return value must be wrapped.
|
|
26
|
-
module Dispatcher
|
|
27
|
-
# Throw tag for the Yielder's break unwind back to the
|
|
28
|
-
# dispatcher's +catch+ frame. +private_constant+ is a
|
|
13
|
+
# Answers a routed Call with one arm per Reply variant: +[:ok, body, nil]+,
|
|
14
|
+
# +[:fault, message, fault_type]+, or +[:break, "", nil]+ — a block
|
|
15
|
+
# exit the guest kept carries nothing. It never raises, so every
|
|
16
|
+
# failure reaches the guest as a fault.
|
|
17
|
+
module Dispatcher # :nodoc:
|
|
18
|
+
# Thrown rather than raised, so a Service unwound by a block exit runs
|
|
19
|
+
# its +ensure+ and cannot +rescue+ the exit. +private_constant+ is a
|
|
29
20
|
# convention boundary — not a defence.
|
|
30
21
|
BREAK_THROW = :__kobako_break__
|
|
31
22
|
private_constant :BREAK_THROW
|
|
32
23
|
|
|
33
|
-
|
|
24
|
+
class UndefinedTargetError < StandardError; end # :nodoc:
|
|
34
25
|
|
|
35
|
-
|
|
36
|
-
# Fault with type="undefined". Contained at the wire boundary —
|
|
37
|
-
# not part of the public Kobako error taxonomy.
|
|
38
|
-
class UndefinedTargetError < StandardError; end
|
|
26
|
+
class UnreadableRequestError < Kobako::Codec::Error; end # :nodoc:
|
|
39
27
|
|
|
40
|
-
# The
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
# here is the Service's own exception, which answers under +runtime+
|
|
48
|
-
# wearing the +<class>: <message>+ shape that says so.
|
|
28
|
+
# The category each failure answering with its message alone takes,
|
|
29
|
+
# keyed by the class raised and ordered most specific first. kobako's
|
|
30
|
+
# own refusals are worded so none reads as a Service's exception, and
|
|
31
|
+
# +ArgumentError+, raised by a Service and by a call that will not
|
|
32
|
+
# bind alike, is named by +argument+ already. A class absent here is
|
|
33
|
+
# the Service's own exception, which answers under +runtime+ wearing
|
|
34
|
+
# the +<class>: <message>+ shape that names it.
|
|
49
35
|
#
|
|
50
36
|
# +Kobako::Codec::Error+ is the floor rather than a path of its own:
|
|
51
37
|
# a codec fault reaching the boundary unnamed is the exchange failing,
|
|
52
38
|
# and must not be dressed as something a Service raised.
|
|
53
|
-
|
|
39
|
+
BARE_FAULTS = {
|
|
54
40
|
HandleExhaustedError => "internal",
|
|
55
41
|
UndefinedTargetError => "undefined",
|
|
56
42
|
ArgumentError => "argument",
|
|
57
43
|
YieldValueError => "runtime",
|
|
44
|
+
UnsupportedError => "runtime",
|
|
58
45
|
Kobako::Codec::Error => "internal",
|
|
59
46
|
Kobako::SandboxError => "runtime"
|
|
60
47
|
}.freeze
|
|
61
|
-
private_constant :
|
|
62
|
-
|
|
63
|
-
#
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
# +Catalog::Handles+ or +Runtime+. +yield_to_guest+ is a +String → String+ callable
|
|
71
|
-
# (the ext's per-dispatch +Kobako::Runtime::GuestYielder+) used only
|
|
72
|
-
# when the Call carries +block_given: true+. Never raises — every
|
|
73
|
-
# failure path takes the fault arm instead, so the guest sees a
|
|
74
|
-
# transport error rather than a wasm trap.
|
|
75
|
-
#
|
|
76
|
-
# The decode runs inside +Codec.track_handles+ so #resolve_call_args
|
|
77
|
-
# can skip the argument walk when no Capability Handle crossed the
|
|
78
|
-
# wire.
|
|
79
|
-
def dispatch(call, resolver, handler, yield_to_guest)
|
|
80
|
-
yielder = Yielder.new(yield_to_guest, BREAK_THROW, handler) if call.block_given
|
|
81
|
-
[true, encode_ok(run(call, resolver, handler, yielder), handler), nil] # : [bool, String, String?]
|
|
48
|
+
private_constant :BARE_FAULTS
|
|
49
|
+
|
|
50
|
+
# The per-invocation state arrives as arguments so the Dispatcher
|
|
51
|
+
# stays stateless and neither the resolver nor the Context publishes
|
|
52
|
+
# accessors for it.
|
|
53
|
+
def self.dispatch(call, resolver, handles, failure, guest_yielder)
|
|
54
|
+
yielder = Yielder.new(guest_yielder, BREAK_THROW, handles, failure) if call.block_given
|
|
55
|
+
catch(BREAK_THROW) { return [:ok, encode_ok(run(call, resolver, handles, yielder), handles), nil] }
|
|
56
|
+
[:break, "", nil] # : [Symbol, String, String?]
|
|
82
57
|
# StandardError is the boundary by intent: a Service method's
|
|
83
58
|
# application fault folds into a guest-rescuable fault, while a
|
|
84
59
|
# host-process failure (NoMemoryError, SignalException, a bare Exception)
|
|
85
60
|
# stays uncaught and traps the invocation rather than being masked as a
|
|
86
61
|
# rescuable fault.
|
|
87
62
|
rescue StandardError => e
|
|
88
|
-
[
|
|
63
|
+
[:fault, *caught_fault(e, failure)] # : [Symbol, String, String?]
|
|
89
64
|
ensure
|
|
90
|
-
yielder&.invalidate
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
# Decode the payload, resolve the receiver, and run the method inside
|
|
94
|
-
# the +catch+ frame a guest +break+ unwinds to. Split from #dispatch
|
|
95
|
-
# so the reply-shaping and the failure boundary stay one glance wide.
|
|
96
|
-
def run(call, resolver, handler, yielder)
|
|
97
|
-
arguments, carried_handle = decode_arguments(call.payload)
|
|
98
|
-
exposure = resolve_target(call.target, resolver, handler)
|
|
99
|
-
args, kwargs = resolve_call_args(arguments, handler, carried_handle)
|
|
100
|
-
catch(BREAK_THROW) { invoke(exposure, call.method_name, args, kwargs, yielder) }
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
# Decode the Call's payload into its arguments, reporting whether any
|
|
104
|
-
# Capability Handle crossed. A codec fault here is a request that
|
|
105
|
-
# never became a call, restated so it cannot read as an unwritable
|
|
106
|
-
# reply — the same restatement #encode_ok makes in the other
|
|
107
|
-
# direction.
|
|
108
|
-
def decode_arguments(payload)
|
|
109
|
-
Kobako::Codec.track_handles { Payload::Arguments.decode(payload) }
|
|
110
|
-
rescue Kobako::Codec::Error => e
|
|
111
|
-
raise UnreadableRequestError, "Sandbox could not read the request: #{e.message}"
|
|
65
|
+
yielder&.invalidate
|
|
112
66
|
end
|
|
113
67
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
# Capability Handle; when it did not, every argument resolves to
|
|
117
|
-
# itself, so the decoded values pass straight through and the walk is
|
|
118
|
-
# skipped entirely. Otherwise both go through #resolve_arg so Handles
|
|
119
|
-
# round-trip back to the host-side Ruby object before the call reaches
|
|
120
|
-
# +public_send+.
|
|
121
|
-
def resolve_call_args(arguments, handler, carried_handle)
|
|
122
|
-
return [arguments.args, arguments.kwargs] unless carried_handle
|
|
68
|
+
class << self
|
|
69
|
+
private
|
|
123
70
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
71
|
+
def run(call, resolver, handles, yielder)
|
|
72
|
+
arguments, carried_handle = decode_arguments(call.payload)
|
|
73
|
+
exposure = resolve_target(call.target, resolver, handles)
|
|
74
|
+
args, kwargs = resolve_call_args(arguments, handles, carried_handle)
|
|
75
|
+
invoke(exposure, call.method_name, args, kwargs, yielder)
|
|
76
|
+
end
|
|
127
77
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
# it is the +<class>: <message>+ shape a Host App is told to keep
|
|
136
|
-
# secrets out of, so wearing it says the Service raised. kobako's own
|
|
137
|
-
# refusals answer under their own wording instead of borrowing that
|
|
138
|
-
# shape.
|
|
139
|
-
#
|
|
140
|
-
# The guest's own block failing is not the Service's to report at
|
|
141
|
-
# all, so the Yielder that raised it is asked first — it recognises
|
|
142
|
-
# its own by identity and words the failure the guest's way.
|
|
143
|
-
def caught_fault(error, yielder)
|
|
144
|
-
block_failure = yielder&.fault_text(error)
|
|
145
|
-
return fault("block", block_failure) if block_failure
|
|
78
|
+
# A codec fault here is a request that never became a call, restated
|
|
79
|
+
# so it cannot read as an unwritable reply.
|
|
80
|
+
def decode_arguments(payload)
|
|
81
|
+
Kobako::Codec.track_handles { Payload::Arguments.decode(payload) }
|
|
82
|
+
rescue Kobako::Codec::Error => e
|
|
83
|
+
raise UnreadableRequestError, "Sandbox could not read the request: #{e.message}"
|
|
84
|
+
end
|
|
146
85
|
|
|
147
|
-
|
|
148
|
-
|
|
86
|
+
def resolve_call_args(arguments, handles, carried_handle)
|
|
87
|
+
return [arguments.args, arguments.kwargs] unless carried_handle
|
|
149
88
|
|
|
150
|
-
|
|
151
|
-
|
|
89
|
+
[arguments.args.map { |v| resolve_arg(v, handles) },
|
|
90
|
+
arguments.kwargs.transform_values { |v| resolve_arg(v, handles) }]
|
|
91
|
+
end
|
|
152
92
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
target = exposure.object
|
|
169
|
-
block = yielder&.to_proc
|
|
170
|
-
if kwargs.empty?
|
|
171
|
-
target.public_send(name, *args, &block)
|
|
172
|
-
else
|
|
173
|
-
target.public_send(name, *args, **kwargs, &block)
|
|
93
|
+
# The class prefix marks a Service's own exception that its category
|
|
94
|
+
# does not name: it is the +<class>: <message>+ shape a Host App is
|
|
95
|
+
# told to keep secrets out of, so wearing it says the Service raised.
|
|
96
|
+
#
|
|
97
|
+
# The guest's own block failing is not the Service's to report at
|
|
98
|
+
# all, so the Yielder that raised it is asked first — it recognises
|
|
99
|
+
# its own by identity and words the failure the guest's way.
|
|
100
|
+
def caught_fault(error, failure)
|
|
101
|
+
block_failure = failure.fault_text(error)
|
|
102
|
+
return fault("block", block_failure) if block_failure
|
|
103
|
+
|
|
104
|
+
bare = BARE_FAULTS.find { |klass, _| error.is_a?(klass) }
|
|
105
|
+
return fault(bare.last, error.message) if bare
|
|
106
|
+
|
|
107
|
+
fault("runtime", "#{error.class}: #{error.message}")
|
|
174
108
|
end
|
|
175
|
-
end
|
|
176
109
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
110
|
+
# The wire always carries a keyword map, so an empty one is left
|
|
111
|
+
# unsplatted for methods that take no keywords.
|
|
112
|
+
def invoke(exposure, method, args, kwargs, yielder)
|
|
113
|
+
name = method.to_sym
|
|
114
|
+
reject_unreachable!(exposure, name)
|
|
115
|
+
target = exposure.object
|
|
116
|
+
block = yielder&.to_proc
|
|
117
|
+
if kwargs.empty?
|
|
118
|
+
target.public_send(name, *args, &block)
|
|
119
|
+
else
|
|
120
|
+
target.public_send(name, *args, **kwargs, &block)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
186
123
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
raise UndefinedTargetError, e.message
|
|
195
|
-
end
|
|
124
|
+
# Both the ambient-surface floor and the reference's Exposure answer
|
|
125
|
+
# through Reflection, so a rejected name discloses nothing about
|
|
126
|
+
# which of the two refused.
|
|
127
|
+
def reject_unreachable!(exposure, name)
|
|
128
|
+
reason = Reflection.refusal(exposure, name)
|
|
129
|
+
raise UndefinedTargetError, reason if reason
|
|
130
|
+
end
|
|
196
131
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
# id. No else-branch is needed — the envelope layer is the system
|
|
202
|
-
# boundary that enforces the invariant.
|
|
203
|
-
def resolve_target(target, resolver, handler)
|
|
204
|
-
case target
|
|
205
|
-
when String
|
|
206
|
-
resolve_path(target, resolver)
|
|
207
|
-
when Integer
|
|
208
|
-
resolve_handle(target, handler)
|
|
132
|
+
def resolve_arg(value, handles)
|
|
133
|
+
Kobako::Codec::HandleWalk.deep_restore(value, handles)
|
|
134
|
+
rescue Kobako::SandboxError => e
|
|
135
|
+
raise UndefinedTargetError, e.message
|
|
209
136
|
end
|
|
210
|
-
end
|
|
211
137
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
138
|
+
# The envelope already discriminated the two target forms, so no
|
|
139
|
+
# else-branch is needed.
|
|
140
|
+
def resolve_target(target, resolver, handles)
|
|
141
|
+
case target
|
|
142
|
+
when String
|
|
143
|
+
resolve_path(target, resolver)
|
|
144
|
+
when Integer
|
|
145
|
+
resolve_handle(target, handles)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
217
148
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
raise UndefinedTargetError, e.message
|
|
224
|
-
end
|
|
149
|
+
def resolve_path(path, resolver)
|
|
150
|
+
resolver.lookup(path)
|
|
151
|
+
rescue KeyError => e
|
|
152
|
+
raise UndefinedTargetError, e.message
|
|
153
|
+
end
|
|
225
154
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
# Capability Handle in place. The happy path encodes exactly once.
|
|
232
|
-
#
|
|
233
|
-
# Any other codec fault is the answer failing to encode rather than the
|
|
234
|
-
# request failing to decode, and a Service is the only side that can
|
|
235
|
-
# change what it returns — so it is named here, where the direction is
|
|
236
|
-
# known, instead of falling to the boundary's codec floor and reporting
|
|
237
|
-
# as an exchange that produced no Service outcome.
|
|
238
|
-
def encode_ok(value, handler)
|
|
239
|
-
Kobako::Codec::Nesting.assert_within_bound!(value)
|
|
240
|
-
Kobako::Codec::Encoder.encode(value)
|
|
241
|
-
rescue Kobako::Codec::UnsupportedTypeError
|
|
242
|
-
encode_ok(wrap_as_handle(value, handler), handler)
|
|
243
|
-
rescue Kobako::Codec::Error => e
|
|
244
|
-
raise Kobako::SandboxError, "Sandbox could not write the Service's answer: #{e.message}"
|
|
245
|
-
end
|
|
155
|
+
def resolve_handle(id, handles)
|
|
156
|
+
handles.exposure(id)
|
|
157
|
+
rescue Kobako::SandboxError => e
|
|
158
|
+
raise UndefinedTargetError, e.message
|
|
159
|
+
end
|
|
246
160
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
161
|
+
# A value with no wire form goes back as a Capability Handle. Any
|
|
162
|
+
# other codec fault is the answer failing to encode, which only the
|
|
163
|
+
# Service can change — so it is named here, where the direction is
|
|
164
|
+
# known, instead of falling to the boundary's codec floor.
|
|
165
|
+
def encode_ok(value, handles)
|
|
166
|
+
Kobako::Codec::Nesting.assert_within_bound!(value)
|
|
167
|
+
Kobako::Codec::Encoder.encode(value)
|
|
168
|
+
rescue Kobako::Codec::UnsupportedTypeError
|
|
169
|
+
encode_ok(handles.alloc(value), handles)
|
|
170
|
+
rescue Kobako::Codec::Error => e
|
|
171
|
+
raise Kobako::SandboxError, "Sandbox could not write the Service's answer: #{e.message}"
|
|
172
|
+
end
|
|
253
173
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
174
|
+
# Ruby core builds some exception messages as ASCII-8BIT (the arity
|
|
175
|
+
# ArgumentError, for one), and the envelope requires UTF-8 of the
|
|
176
|
+
# text fields it frames.
|
|
177
|
+
def fault(type, message)
|
|
178
|
+
[message.encode(Encoding::UTF_8, invalid: :replace, undef: :replace), type] # : [String, String]
|
|
179
|
+
end
|
|
259
180
|
end
|
|
260
181
|
end
|
|
261
182
|
end
|
|
@@ -4,31 +4,17 @@ require_relative "../errors"
|
|
|
4
4
|
|
|
5
5
|
module Kobako
|
|
6
6
|
module Transport
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
# decode. Distinct from a Wasm trap (engine signalled the guest
|
|
11
|
-
# runtime is unrecoverable) and from a normal sandbox-layer failure
|
|
12
|
-
# (the script raised but the protocol was respected): a
|
|
13
|
-
# +Transport::Error+ always indicates the guest runtime is corrupted —
|
|
14
|
-
# the only safe recovery is to discard the Sandbox and start a new
|
|
15
|
-
# invocation.
|
|
16
|
-
#
|
|
17
|
-
# Inherits from +Kobako::SandboxError+ so a single
|
|
18
|
-
# +rescue Kobako::SandboxError+ still catches it; callers that want
|
|
19
|
-
# to distinguish wire-violation paths from script failures can
|
|
20
|
-
# +rescue Kobako::Transport::Error+ directly.
|
|
7
|
+
# The guest broke the exchange with the host: it produced a value that
|
|
8
|
+
# could not be decoded, or a dispatch it made could not complete. Being a
|
|
9
|
+
# SandboxError, it is caught by rescuing that too.
|
|
21
10
|
class Error < Kobako::SandboxError
|
|
22
|
-
def initialize(message, diagnostic: nil, **)
|
|
11
|
+
def initialize(message, diagnostic: nil, **) # :nodoc:
|
|
23
12
|
super(message, **)
|
|
24
13
|
@diagnostic = diagnostic
|
|
25
14
|
end
|
|
26
15
|
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
# be …" wording, so it stays out of #message; an operator
|
|
30
|
-
# triaging a corrupted runtime still needs it, and
|
|
31
|
-
# +detailed_message+ is where Ruby puts text of exactly that kind.
|
|
16
|
+
# Appends the codec's own reason, which an operator needs and a caller
|
|
17
|
+
# cannot act on, so #message leaves it out.
|
|
32
18
|
def detailed_message(...)
|
|
33
19
|
rendered = super
|
|
34
20
|
return rendered if @diagnostic.nil?
|
|
@@ -6,16 +6,11 @@ require_relative "reflection"
|
|
|
6
6
|
|
|
7
7
|
module Kobako
|
|
8
8
|
module Transport
|
|
9
|
-
# The methods
|
|
10
|
-
#
|
|
11
|
-
# is
|
|
12
|
-
#
|
|
13
|
-
|
|
14
|
-
# cannot foresee handing over are the ones it never wrote.
|
|
15
|
-
#
|
|
16
|
-
# Built on the +class X < Data.define(...)+ subclass form so the class
|
|
17
|
-
# body is fully Steep-visible; see +.rubocop.yml+ for the rationale.
|
|
18
|
-
class Exposure < Data.define(:object, :names)
|
|
9
|
+
# The methods one reference lets the guest call, fixed when the reference
|
|
10
|
+
# is made. Only what the object's own class and the object define in
|
|
11
|
+
# source is exposed, since a Host App cannot foresee handing over what it
|
|
12
|
+
# never wrote.
|
|
13
|
+
class Exposure < Data.define(:object, :names) # :nodoc:
|
|
19
14
|
PREDICATE = :respond_to_guest?
|
|
20
15
|
private_constant :PREDICATE
|
|
21
16
|
|
|
@@ -73,7 +68,7 @@ module Kobako
|
|
|
73
68
|
end
|
|
74
69
|
|
|
75
70
|
def class_names(object, klass)
|
|
76
|
-
names =
|
|
71
|
+
names = authored_names(klass)
|
|
77
72
|
case object
|
|
78
73
|
when Struct, Data then names.concat(object.members)
|
|
79
74
|
end
|
|
@@ -83,15 +78,13 @@ module Kobako
|
|
|
83
78
|
def with_singleton(object, names)
|
|
84
79
|
return names if SINGLETON_METHODS.bind_call(object, false).empty?
|
|
85
80
|
|
|
86
|
-
names.union(
|
|
81
|
+
names.union(authored_names(SINGLETON_CLASS_OF.bind_call(object))).freeze
|
|
87
82
|
end
|
|
88
83
|
|
|
89
|
-
# The public methods +owner+ itself defines in source.
|
|
90
|
-
|
|
91
|
-
def authored(owner)
|
|
84
|
+
# The public methods +owner+ itself defines in Ruby source.
|
|
85
|
+
def authored_names(owner)
|
|
92
86
|
owner.public_instance_methods(false).select do |name|
|
|
93
|
-
|
|
94
|
-
file && !file.start_with?("<internal:")
|
|
87
|
+
Reflection.authored?(owner.instance_method(name))
|
|
95
88
|
end
|
|
96
89
|
end
|
|
97
90
|
end
|