kobako 0.20.0-aarch64-linux → 0.21.0-aarch64-linux
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 +107 -0
- data/README.md +96 -44
- data/ROADMAP.md +1 -1
- data/crates/kobako-runtime/CHANGELOG.md +37 -0
- data/crates/kobako-runtime/README.md +1 -1
- data/crates/kobako-transport/CHANGELOG.md +31 -0
- data/crates/kobako-transport/README.md +43 -0
- data/crates/kobako-wasmtime/CHANGELOG.md +23 -0
- data/crates/kobako-wasmtime/README.md +1 -1
- data/data/kobako.wasm +0 -0
- data/lib/kobako/3.3/kobako.so +0 -0
- data/lib/kobako/3.4/kobako.so +0 -0
- data/lib/kobako/4.0/kobako.so +0 -0
- data/lib/kobako/catalog/services.rb +13 -21
- data/lib/kobako/catalog/snippets.rb +16 -34
- data/lib/kobako/codec/decoder.rb +10 -13
- data/lib/kobako/codec/encoder.rb +7 -7
- data/lib/kobako/codec/error.rb +6 -6
- data/lib/kobako/codec/ext_types.rb +12 -72
- data/lib/kobako/codec/handle_walk.rb +6 -6
- data/lib/kobako/codec/state.rb +4 -56
- data/lib/kobako/codec/utils.rb +4 -4
- data/lib/kobako/codec.rb +11 -21
- data/lib/kobako/context.rb +23 -17
- data/lib/kobako/errors.rb +32 -11
- data/lib/kobako/outcome.rb +71 -128
- data/lib/kobako/payload/arguments.rb +59 -0
- data/lib/kobako/payload.rb +19 -0
- data/lib/kobako/sandbox.rb +12 -13
- data/lib/kobako/sandbox_options.rb +1 -1
- data/lib/kobako/snippet/binary.rb +3 -4
- data/lib/kobako/snippet/source.rb +5 -6
- data/lib/kobako/snippet.rb +2 -2
- data/lib/kobako/transport/call.rb +21 -0
- data/lib/kobako/transport/dispatcher.rb +86 -135
- data/lib/kobako/transport/error.rb +21 -5
- data/lib/kobako/transport/reflection.rb +82 -0
- data/lib/kobako/transport/run.rb +25 -33
- data/lib/kobako/transport/yielder.rb +27 -36
- data/lib/kobako/transport.rb +11 -8
- data/lib/kobako/usage.rb +1 -1
- data/lib/kobako/version.rb +1 -1
- data/release-please-config.json +47 -2
- data/sig/kobako/catalog/services.rbs +1 -1
- data/sig/kobako/catalog/snippets.rbs +4 -4
- data/sig/kobako/codec/error.rbs +4 -4
- data/sig/kobako/codec/ext_types.rbs +0 -7
- data/sig/kobako/codec/state.rbs +0 -4
- data/sig/kobako/codec.rbs +4 -5
- data/sig/kobako/context.rbs +4 -4
- data/sig/kobako/errors.rbs +15 -3
- data/sig/kobako/outcome.rbs +12 -13
- data/sig/kobako/payload/arguments.rbs +26 -0
- data/sig/kobako/payload.rbs +4 -0
- data/sig/kobako/pool.rbs +0 -2
- data/sig/kobako/runtime.rbs +15 -13
- data/sig/kobako/snippet/binary.rbs +1 -1
- data/sig/kobako/snippet/source.rbs +1 -1
- data/sig/kobako/transport/call.rbs +21 -0
- data/sig/kobako/transport/dispatcher.rbs +8 -10
- data/sig/kobako/transport/error.rbs +11 -0
- data/sig/kobako/transport/reflection.rbs +15 -0
- data/sig/kobako/transport/run.rbs +1 -1
- data/sig/kobako/transport/yielder.rbs +2 -2
- data/sig/kobako/transport.rbs +8 -2
- metadata +12 -12
- data/lib/kobako/fault.rb +0 -38
- data/lib/kobako/outcome/panic.rb +0 -42
- data/lib/kobako/transport/request.rb +0 -76
- data/lib/kobako/transport/response.rb +0 -83
- data/lib/kobako/transport/yield.rb +0 -94
- data/sig/kobako/fault.rbs +0 -19
- data/sig/kobako/outcome/panic.rbs +0 -34
- data/sig/kobako/transport/request.rbs +0 -29
- data/sig/kobako/transport/response.rbs +0 -33
- data/sig/kobako/transport/yield.rbs +0 -34
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# kobako-transport
|
|
2
|
+
|
|
3
|
+
The fixed tier of the [kobako](https://github.com/elct9620/kobako) wire —
|
|
4
|
+
an in-process Wasm sandbox for running untrusted mruby scripts.
|
|
5
|
+
|
|
6
|
+
kobako is assembled from three parts chosen independently: a host, a
|
|
7
|
+
payload codec, and a guest. That works only because two things are the
|
|
8
|
+
same in every assembly. This crate is both of them, and nothing else:
|
|
9
|
+
|
|
10
|
+
- `envelope` — the **core envelope**, the outer frame each message rides
|
|
11
|
+
in: `Call` / `Reply` / `YieldReply` for a dispatch round-trip,
|
|
12
|
+
`Outcome` / `Panic` for how an invocation ended, `Run` / `Preamble` /
|
|
13
|
+
`Snippets` for what an invocation is handed, and the `ErrorRecord` a
|
|
14
|
+
guest failure carries
|
|
15
|
+
- `abi` — the values a host and a guest must already agree on to
|
|
16
|
+
exchange a byte: the version, the packed `(ptr, len)` return layout,
|
|
17
|
+
the invocation-channel frame prefix, and the message size cap
|
|
18
|
+
|
|
19
|
+
The envelope reads a message's routing fields and its ok-versus-fault
|
|
20
|
+
tag without decoding a payload byte; everything the resolved method
|
|
21
|
+
consumes rides through as an opaque `payload` this layer never reads.
|
|
22
|
+
A decoded envelope borrows the buffer it came from, so that payload
|
|
23
|
+
reaches its reader as a view rather than a copy.
|
|
24
|
+
|
|
25
|
+
This crate depends on no other, and every kobako tier depends on it:
|
|
26
|
+
`kobako-runtime` and `kobako-wasmtime` on the host, `kobako-core` and
|
|
27
|
+
`kobako-mruby` in the guest, and any third-party engine, codec, or
|
|
28
|
+
guest that composes against them.
|
|
29
|
+
|
|
30
|
+
The byte layout is specified in
|
|
31
|
+
[`docs/wire/envelope.md`](https://github.com/elct9620/kobako/blob/main/docs/wire/envelope.md);
|
|
32
|
+
the golden vectors in this crate are derived from that document.
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
```toml
|
|
37
|
+
[dependencies]
|
|
38
|
+
kobako-transport = "0.13.0" # x-release-please-version
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## License
|
|
42
|
+
|
|
43
|
+
Apache-2.0
|
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.13.0](https://github.com/elct9620/kobako/compare/kobako-wasmtime-v0.12.0...kobako-wasmtime-v0.13.0) (2026-07-29)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### ⚠ BREAKING CHANGES
|
|
7
|
+
|
|
8
|
+
* **wasmtime:** put every cap in one struct, named the way the field is read
|
|
9
|
+
* **runtime:** give the engine contract names an implementer can write
|
|
10
|
+
* **transport:** name the core envelope's types after what they carry
|
|
11
|
+
* **wire:** route every tier through the one envelope
|
|
12
|
+
* **wire:** carry dispatch over the core envelope with an opaque payload
|
|
13
|
+
|
|
14
|
+
### Features
|
|
15
|
+
|
|
16
|
+
* **wire:** carry dispatch over the core envelope with an opaque payload ([556104b](https://github.com/elct9620/kobako/commit/556104bf86fdf481b5368b70af83eb0add4b2708))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Code Refactoring
|
|
20
|
+
|
|
21
|
+
* **runtime:** give the engine contract names an implementer can write ([0a41491](https://github.com/elct9620/kobako/commit/0a41491360cf17def8245c0758679e92572b990f))
|
|
22
|
+
* **transport:** name the core envelope's types after what they carry ([b44de65](https://github.com/elct9620/kobako/commit/b44de6502953826c567d4ef8a594561479f50c1d))
|
|
23
|
+
* **wasmtime:** put every cap in one struct, named the way the field is read ([3f57860](https://github.com/elct9620/kobako/commit/3f578608b044d1ade0ae35231266cf3c9e517c02))
|
|
24
|
+
* **wire:** route every tier through the one envelope ([c5cd33a](https://github.com/elct9620/kobako/commit/c5cd33a5346f49857fa6e1f45c9cf9b9bea0ff77))
|
|
25
|
+
|
|
3
26
|
## [0.12.0](https://github.com/elct9620/kobako/compare/kobako-wasmtime-v0.11.0...kobako-wasmtime-v0.12.0) (2026-07-24)
|
|
4
27
|
|
|
5
28
|
|
data/data/kobako.wasm
CHANGED
|
Binary file
|
data/lib/kobako/3.3/kobako.so
CHANGED
|
Binary file
|
data/lib/kobako/3.4/kobako.so
CHANGED
|
Binary file
|
data/lib/kobako/4.0/kobako.so
CHANGED
|
Binary file
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "../codec"
|
|
4
3
|
require_relative "../errors"
|
|
5
4
|
|
|
6
5
|
module Kobako
|
|
7
6
|
module Catalog
|
|
8
7
|
# Kobako::Catalog::Services — per-Sandbox registry of Service
|
|
9
8
|
# bindings keyed by their constant-path name. Holds the flat
|
|
10
|
-
# path→object table and the
|
|
9
|
+
# path→object table and the declared path set every invocation
|
|
10
|
+
# announces on Frame 1.
|
|
11
11
|
#
|
|
12
12
|
# Public API:
|
|
13
13
|
#
|
|
14
14
|
# services = Kobako::Catalog::Services.new
|
|
15
15
|
# services.bind("MyService::KV", kv_object) # => services (chainable)
|
|
16
|
-
# services.
|
|
16
|
+
# services.paths # => ["MyService::KV"]
|
|
17
17
|
# services.lookup("MyService::KV") # => kv_object
|
|
18
18
|
#
|
|
19
19
|
# Per-dispatch routing is +Kobako::Transport::Dispatcher+'s
|
|
@@ -31,7 +31,6 @@ module Kobako
|
|
|
31
31
|
def initialize
|
|
32
32
|
@bindings = {} # : Hash[String, untyped]
|
|
33
33
|
@sealed = false
|
|
34
|
-
@encoded = nil # : String?
|
|
35
34
|
end
|
|
36
35
|
|
|
37
36
|
# Bind +object+ as the Service reachable at +path+ — a +Symbol+ or
|
|
@@ -66,27 +65,20 @@ module Kobako
|
|
|
66
65
|
@bindings[target_str]
|
|
67
66
|
end
|
|
68
67
|
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
#
|
|
76
|
-
# The bytes are pinned eagerly by #seal! at the first invocation, so
|
|
77
|
-
# every subsequent call is a pure read of the frozen preamble — a bind
|
|
78
|
-
# can never reach the registry after the seal to alter Frame 1.
|
|
79
|
-
def encode
|
|
80
|
-
@encoded || Codec::Encoder.encode(@bindings.keys).freeze
|
|
68
|
+
# The bound constant paths in bind order — +["MyService::KV",
|
|
69
|
+
# "File"]+ — which +Runtime#eval+ / +#run+ frame into the Frame 1
|
|
70
|
+
# preamble. The registry holds the bindings; the wire layout is the
|
|
71
|
+
# native side's.
|
|
72
|
+
def paths
|
|
73
|
+
@bindings.keys
|
|
81
74
|
end
|
|
82
75
|
|
|
83
|
-
# Mark the registry as sealed
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
#
|
|
76
|
+
# Mark the registry as sealed so registration can never alter the
|
|
77
|
+
# declared path set afterwards. Called by +Sandbox+ on the first
|
|
78
|
+
# invocation; afterwards #bind raises ArgumentError. Idempotent;
|
|
79
|
+
# returns +self+.
|
|
87
80
|
def seal!
|
|
88
81
|
@sealed = true
|
|
89
|
-
@encoded ||= Codec::Encoder.encode(@bindings.keys).freeze
|
|
90
82
|
self
|
|
91
83
|
end
|
|
92
84
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "../codec"
|
|
4
3
|
require_relative "../snippet"
|
|
5
4
|
|
|
6
5
|
module Kobako
|
|
@@ -28,32 +27,16 @@ module Kobako
|
|
|
28
27
|
|
|
29
28
|
def initialize
|
|
30
29
|
@entries = [] # : Array[Kobako::Snippet::Source | Kobako::Snippet::Binary]
|
|
31
|
-
@encoded = nil # : String?
|
|
32
30
|
end
|
|
33
31
|
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
# self-encode.
|
|
43
|
-
#
|
|
44
|
-
# Before the first invocation this recomputes from the still-open
|
|
45
|
-
# table; #seal! then pins the bytes, so every later call is a pure
|
|
46
|
-
# read of the frozen memo.
|
|
47
|
-
def encode
|
|
48
|
-
@encoded || Codec::Encoder.encode(@entries.map { |entry| entry_payload(entry) }).freeze
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
# Freeze the Frame 3 table bytes at the first invocation so the
|
|
52
|
-
# replayed snippet set can never change afterwards. Called by +Sandbox+
|
|
53
|
-
# alongside the Service / Extension seals. Idempotent; returns +self+.
|
|
54
|
-
def seal!
|
|
55
|
-
@encoded ||= Codec::Encoder.encode(@entries.map { |entry| entry_payload(entry) }).freeze
|
|
56
|
-
self
|
|
32
|
+
# The registered snippets in insertion order, each projected as the
|
|
33
|
+
# +[kind, name, body]+ triple +Runtime#eval+ / +#run+ frame into
|
|
34
|
+
# Frame 3 — +kind+ names the form as a Symbol, +name+ is +nil+ for
|
|
35
|
+
# the bytecode form. The entry value objects stay pure carriers, so
|
|
36
|
+
# this collection-tier method reads their attributes externally
|
|
37
|
+
# rather than asking each entry to project itself.
|
|
38
|
+
def entries
|
|
39
|
+
@entries.map { |entry| entry_tuple(entry) }
|
|
57
40
|
end
|
|
58
41
|
|
|
59
42
|
# Register one preloaded snippet in either of two forms.
|
|
@@ -124,17 +107,16 @@ module Kobako
|
|
|
124
107
|
nil
|
|
125
108
|
end
|
|
126
109
|
|
|
127
|
-
#
|
|
128
|
-
# contribute their host-side +name+; Binary entries
|
|
129
|
-
# because the canonical name lives in the bytecode's embedded
|
|
130
|
-
# +debug_info+ and is read by the guest at load time
|
|
131
|
-
|
|
132
|
-
def entry_payload(entry)
|
|
110
|
+
# Project one entry as its +[kind, name, body]+ triple. Source
|
|
111
|
+
# entries contribute their host-side +name+; Binary entries carry
|
|
112
|
+
# +nil+ because the canonical name lives in the bytecode's embedded
|
|
113
|
+
# +debug_info+ and is read by the guest at load time.
|
|
114
|
+
def entry_tuple(entry)
|
|
133
115
|
case entry
|
|
134
116
|
when Snippet::Source
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
117
|
+
[Snippet::Source::KIND, entry.name.to_s, entry.body]
|
|
118
|
+
else
|
|
119
|
+
[Snippet::Binary::KIND, nil, entry.body]
|
|
138
120
|
end
|
|
139
121
|
end
|
|
140
122
|
|
data/lib/kobako/codec/decoder.rb
CHANGED
|
@@ -9,10 +9,10 @@ require_relative "utils"
|
|
|
9
9
|
module Kobako
|
|
10
10
|
module Codec
|
|
11
11
|
# Module-level entry point for the host side of the kobako wire
|
|
12
|
-
# ({docs/wire-
|
|
12
|
+
# ({docs/wire/payload-msgpack.md}[link:../../../docs/wire/payload-msgpack.md] § Type Mapping).
|
|
13
13
|
#
|
|
14
14
|
# Translates msgpack gem exceptions into the kobako error taxonomy
|
|
15
|
-
# (
|
|
15
|
+
# (TruncatedInputError, InvalidTypeError, InvalidEncodingError, UnsupportedTypeError) so
|
|
16
16
|
# callers can pattern-match on the SPEC's wire-violation categories
|
|
17
17
|
# without leaking the gem's internal exception classes.
|
|
18
18
|
#
|
|
@@ -21,14 +21,14 @@ module Kobako
|
|
|
21
21
|
# because callers always decode exactly one wire value at a time.
|
|
22
22
|
module Decoder
|
|
23
23
|
# Decode +bytes+ into one Ruby value and validate transitively
|
|
24
|
-
# against the SPEC type mapping. Raises
|
|
25
|
-
# or
|
|
24
|
+
# against the SPEC type mapping. Raises TruncatedInputError, InvalidTypeError,
|
|
25
|
+
# or InvalidEncodingError on wire violations.
|
|
26
26
|
#
|
|
27
27
|
# When a block is given, the decoded value is yielded and the block's
|
|
28
28
|
# result is returned — wire Value Objects use this to build themselves
|
|
29
29
|
# from the decoded payload. The block runs inside this method's
|
|
30
30
|
# rescue, so a Value Object's +ArgumentError+ invariant failure
|
|
31
|
-
# surfaces as
|
|
31
|
+
# surfaces as InvalidTypeError without a separate Utils.with_boundary
|
|
32
32
|
# wrapper at the call site.
|
|
33
33
|
def self.decode(bytes)
|
|
34
34
|
value = FACTORY.load(bytes.b)
|
|
@@ -37,26 +37,23 @@ module Kobako
|
|
|
37
37
|
# msgpack gem raises the format/type errors below; +ArgumentError+
|
|
38
38
|
# comes from our ext-type validators (Handle id range, Exception type
|
|
39
39
|
# whitelist) and from a yielded block's Value Object invariants — both
|
|
40
|
-
# are wire violations, so both map to
|
|
40
|
+
# are wire violations, so both map to InvalidTypeError.
|
|
41
41
|
rescue ::MessagePack::UnknownExtTypeError, ::MessagePack::MalformedFormatError,
|
|
42
42
|
::MessagePack::StackError, ::ArgumentError => e
|
|
43
|
-
raise
|
|
43
|
+
raise InvalidTypeError, e.message
|
|
44
44
|
# +UnpackError+ is the gem's umbrella class for short-read /
|
|
45
45
|
# incomplete-buffer faults; +EOFError+ covers underflow at the
|
|
46
46
|
# buffer edge.
|
|
47
47
|
rescue ::MessagePack::UnpackError, ::EOFError => e
|
|
48
|
-
raise
|
|
48
|
+
raise TruncatedInputError, e.message
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
# SPEC pins +str+ family payloads to UTF-8
|
|
52
|
-
# ({docs/wire-
|
|
52
|
+
# ({docs/wire/payload-msgpack.md}[link:../../../docs/wire/payload-msgpack.md] § str/bin
|
|
53
53
|
# Encoding Rules). The msgpack gem returns UTF-8-tagged Strings for
|
|
54
54
|
# str family but does not validate the bytes; +bin+ family decodes
|
|
55
55
|
# to ASCII-8BIT. Walk the tree once and reject invalid UTF-8 in any
|
|
56
|
-
# str-typed leaf via Utils.assert_utf8!.
|
|
57
|
-
# payloads are validated transitively: +ExtTypes#unpack_fault+
|
|
58
|
-
# feeds the inner ext-0x02 bytes back through this Decoder, so their
|
|
59
|
-
# +str+ fields are already covered by the time control returns here.
|
|
56
|
+
# str-typed leaf via Utils.assert_utf8!.
|
|
60
57
|
class << self
|
|
61
58
|
private
|
|
62
59
|
|
data/lib/kobako/codec/encoder.rb
CHANGED
|
@@ -8,13 +8,13 @@ require_relative "ext_types"
|
|
|
8
8
|
module Kobako
|
|
9
9
|
module Codec
|
|
10
10
|
# Module-level entry point for the host side of the kobako wire
|
|
11
|
-
# ({docs/wire-
|
|
11
|
+
# ({docs/wire/payload-msgpack.md}[link:../../../docs/wire/payload-msgpack.md] § Type Mapping).
|
|
12
12
|
#
|
|
13
13
|
# The codec backbone is the official +msgpack+ gem: integers, floats,
|
|
14
14
|
# strings, arrays, and maps go through the gem's narrowest-encoding
|
|
15
|
-
# logic; the
|
|
16
|
-
# Capability Handle
|
|
17
|
-
#
|
|
15
|
+
# logic; the two kobako-specific ext types (0x00 Symbol, 0x01
|
|
16
|
+
# Capability Handle) are registered by ExtTypes on the process-wide
|
|
17
|
+
# factory.
|
|
18
18
|
#
|
|
19
19
|
# Public API is a single function — +.encode+. The codec is stateless;
|
|
20
20
|
# there is no buffer accumulator and no streaming write API. Callers
|
|
@@ -22,8 +22,8 @@ module Kobako
|
|
|
22
22
|
# themselves.
|
|
23
23
|
module Encoder
|
|
24
24
|
# Encode +value+ to wire bytes (binary-encoded String).
|
|
25
|
-
# SPEC's
|
|
26
|
-
# rejected as +
|
|
25
|
+
# SPEC's 11-entry type mapping is a closed set: a value outside it is
|
|
26
|
+
# rejected as +UnsupportedTypeError+ by the factory's +BasicObject+ guard
|
|
27
27
|
# (ExtTypes#register_unrepresentable), which raises before the msgpack
|
|
28
28
|
# gem can route the value through +to_msgpack+ — so a permissive
|
|
29
29
|
# +method_missing+ object cannot answer that probe and mis-encode. The
|
|
@@ -33,7 +33,7 @@ module Kobako
|
|
|
33
33
|
def self.encode(value)
|
|
34
34
|
FACTORY.dump(value)
|
|
35
35
|
rescue ::RangeError, ::NoMethodError => e
|
|
36
|
-
raise
|
|
36
|
+
raise UnsupportedTypeError, e.message
|
|
37
37
|
end
|
|
38
38
|
end
|
|
39
39
|
end
|
data/lib/kobako/codec/error.rb
CHANGED
|
@@ -5,7 +5,7 @@ module Kobako
|
|
|
5
5
|
# Base class for all wire-codec faults raised by the pure-Ruby host codec.
|
|
6
6
|
#
|
|
7
7
|
# The wire codec implements the binary contract pinned in
|
|
8
|
-
# {docs/wire-
|
|
8
|
+
# {docs/wire/payload-msgpack.md}[link:../../../docs/wire/payload-msgpack.md] § Type Mapping.
|
|
9
9
|
# Every wire violation surfaces as a
|
|
10
10
|
# subclass of Error so callers can pattern-match on the specific
|
|
11
11
|
# fault while still rescuing all codec faults via this base class.
|
|
@@ -15,20 +15,20 @@ module Kobako
|
|
|
15
15
|
class Error < StandardError; end
|
|
16
16
|
|
|
17
17
|
# Input ended before the type prefix or payload was fully consumed.
|
|
18
|
-
class
|
|
18
|
+
class TruncatedInputError < Error; end
|
|
19
19
|
|
|
20
|
-
# The type byte at the current position is not in the
|
|
20
|
+
# The type byte at the current position is not in the 11-entry kobako
|
|
21
21
|
# type mapping (e.g. an unknown ext code, or a reserved msgpack tag).
|
|
22
|
-
class
|
|
22
|
+
class InvalidTypeError < Error; end
|
|
23
23
|
|
|
24
24
|
# A msgpack +str+ payload was not valid UTF-8, or an ext 0x00 Symbol
|
|
25
25
|
# payload was not valid UTF-8 — both are wire violations per SPEC.
|
|
26
|
-
class
|
|
26
|
+
class InvalidEncodingError < Error; end
|
|
27
27
|
|
|
28
28
|
# The encoder was handed a Ruby object whose type has no wire
|
|
29
29
|
# representation (e.g. Range, Time). Higher layers may catch this
|
|
30
30
|
# and re-route the value through Handle allocation, but at the
|
|
31
31
|
# codec level it is a hard error.
|
|
32
|
-
class
|
|
32
|
+
class UnsupportedTypeError < Error; end
|
|
33
33
|
end
|
|
34
34
|
end
|
|
@@ -6,45 +6,39 @@ require_relative "error"
|
|
|
6
6
|
require_relative "utils"
|
|
7
7
|
require_relative "state"
|
|
8
8
|
require_relative "../handle"
|
|
9
|
-
require_relative "../fault"
|
|
10
9
|
|
|
11
10
|
module Kobako
|
|
12
11
|
module Codec
|
|
13
12
|
# The kobako wire ext-type conversions
|
|
14
|
-
# ({docs/wire-
|
|
13
|
+
# ({docs/wire/payload-msgpack.md}[link:../../../docs/wire/payload-msgpack.md] § Ext Types)
|
|
15
14
|
# as pure functions: per-operation decode state is threaded in as an
|
|
16
15
|
# argument, so the module itself holds nothing. #build_factory assembles
|
|
17
16
|
# the one +MessagePack::Factory+ these conversions are registered on.
|
|
18
17
|
module ExtTypes
|
|
19
18
|
# MessagePack ext type code reserved for Symbol
|
|
20
|
-
# ({docs/wire-
|
|
19
|
+
# ({docs/wire/payload-msgpack.md}[link:../../../docs/wire/payload-msgpack.md] § Ext Types
|
|
21
20
|
# → ext 0x00). Module-private — mirrors +codec::EXT_SYMBOL+ on the
|
|
22
21
|
# Rust side.
|
|
23
22
|
EXT_SYMBOL = 0x00
|
|
24
23
|
# MessagePack ext type code reserved for Capability Handle
|
|
25
|
-
# ({docs/wire-
|
|
24
|
+
# ({docs/wire/payload-msgpack.md}[link:../../../docs/wire/payload-msgpack.md] § Ext Types
|
|
26
25
|
# → ext 0x01). Module-private — mirrors +codec::EXT_HANDLE+ on the
|
|
27
26
|
# Rust side.
|
|
28
27
|
EXT_HANDLE = 0x01
|
|
29
|
-
|
|
30
|
-
# ({docs/wire-codec.md}[link:../../../docs/wire-codec.md] § Ext Types
|
|
31
|
-
# → ext 0x02). Module-private — mirrors +codec::EXT_ERRENV+ on the
|
|
32
|
-
# Rust side.
|
|
33
|
-
EXT_ERRENV = 0x02
|
|
34
|
-
private_constant :EXT_SYMBOL, :EXT_HANDLE, :EXT_ERRENV
|
|
28
|
+
private_constant :EXT_SYMBOL, :EXT_HANDLE
|
|
35
29
|
|
|
36
30
|
# Inert ext id the unrepresentable-value guard registers under. It is
|
|
37
31
|
# never emitted (the guard's packer always raises) and never decoded
|
|
38
32
|
# (no unpacker is registered, so the id stays an UnknownExtTypeError on
|
|
39
33
|
# the wire), so it is not a wire ext type: deliberately not named
|
|
40
|
-
# +EXT_*+ like the
|
|
34
|
+
# +EXT_*+ like the two real ext codes, since it has no Rust-side mirror
|
|
41
35
|
# and must stay outside the wire-symmetry inventory.
|
|
42
36
|
UNREPRESENTABLE_GUARD_ID = 0x7F
|
|
43
37
|
private_constant :UNREPRESENTABLE_GUARD_ID
|
|
44
38
|
|
|
45
39
|
module_function
|
|
46
40
|
|
|
47
|
-
# Assemble a +MessagePack::Factory+ with the
|
|
41
|
+
# Assemble a +MessagePack::Factory+ with the two kobako ext types plus
|
|
48
42
|
# the unrepresentable-value guard registered, frozen because
|
|
49
43
|
# registration is its only mutation and happens exactly once. The
|
|
50
44
|
# stateful conversions resolve their per-operation state at call time,
|
|
@@ -53,7 +47,6 @@ module Kobako
|
|
|
53
47
|
factory = MessagePack::Factory.new
|
|
54
48
|
register_symbol(factory)
|
|
55
49
|
register_handle(factory)
|
|
56
|
-
register_fault(factory)
|
|
57
50
|
register_unrepresentable(factory)
|
|
58
51
|
factory.freeze
|
|
59
52
|
end
|
|
@@ -64,7 +57,7 @@ module Kobako
|
|
|
64
57
|
end
|
|
65
58
|
|
|
66
59
|
# Validate the ext-0x00 payload as UTF-8 and intern. Raises
|
|
67
|
-
#
|
|
60
|
+
# InvalidEncodingError on invalid bytes — SPEC forbids the
|
|
68
61
|
# binary-encoding fallback that msgpack-gem's default unpacker
|
|
69
62
|
# would otherwise apply. The re-tag step lives here because the
|
|
70
63
|
# msgpack ext-type unpacker hands us binary bytes; the assertion
|
|
@@ -86,64 +79,19 @@ module Kobako
|
|
|
86
79
|
# Peel off the fixext-4 frame, hand the bytes to the
|
|
87
80
|
# Host-Gem-internal +Kobako::Handle.restore+ factory, and
|
|
88
81
|
# translate the +ArgumentError+ raised by Handle's invariants
|
|
89
|
-
# into a wire-layer +
|
|
82
|
+
# into a wire-layer +InvalidTypeError+ via Codec::Utils.with_boundary.
|
|
90
83
|
# The Value Object owns the id-range contract; this method only
|
|
91
84
|
# owns the frame shape. Records the Handle sighting on +state+ so a
|
|
92
85
|
# Handle-free decode can skip the downstream resolution walk.
|
|
93
86
|
def unpack_handle(payload, state)
|
|
94
87
|
state.record_handle!
|
|
95
88
|
bytes = payload.b
|
|
96
|
-
raise
|
|
89
|
+
raise InvalidTypeError, "Handle payload must be 4 bytes, got #{bytes.bytesize}" unless bytes.bytesize == 4
|
|
97
90
|
|
|
98
91
|
id = bytes.unpack1("N") # : Integer
|
|
99
92
|
Codec::Utils.with_boundary { Kobako::Handle.restore(id) }
|
|
100
93
|
end
|
|
101
94
|
|
|
102
|
-
# Encode the inner ext-0x02 map via Encoder (not the raw factory) so
|
|
103
|
-
# the embedded payload flows through the same boundary as a top-level
|
|
104
|
-
# encode — nested kobako values (Handle, nested Fault) reach the
|
|
105
|
-
# registered ext-type packers. A +details+ chain nested past the
|
|
106
|
-
# +state+ depth cap has no wire representation and surfaces as
|
|
107
|
-
# +UnsupportedType+. In a payload position (+state+ inside a
|
|
108
|
-
# forbid_faults bracket) the envelope has no wire representation at
|
|
109
|
-
# all, so the refusal routes the value into the position's
|
|
110
|
-
# non-representable handling — the Dispatcher's auto-wrap rescue,
|
|
111
|
-
# or a raise at the yield site.
|
|
112
|
-
def pack_fault(fault, state)
|
|
113
|
-
if state.faults_forbidden?
|
|
114
|
-
raise UnsupportedType, "Kobako::Fault has no wire representation in a payload position"
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
state.within_ext_frame(UnsupportedType) do
|
|
118
|
-
Encoder.encode("type" => fault.type, "message" => fault.message, "details" => fault.details)
|
|
119
|
-
end
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
# Peel the embedded msgpack map and hand it to +Kobako::Fault.new+
|
|
123
|
-
# inside Decoder.decode's block form, so the value-object's
|
|
124
|
-
# +ArgumentError+ invariants surface as +InvalidType+ through the
|
|
125
|
-
# decoder boundary. Inner decode goes through Decoder (not the raw
|
|
126
|
-
# factory) so the embedded +str+ payloads flow through the same
|
|
127
|
-
# UTF-8 validation as a top-level decode. A nested ext 0x02 in
|
|
128
|
-
# +details+ re-enters this method, so the +state+ ext-frame guard
|
|
129
|
-
# bounds the chain depth to keep it from exhausting the native stack.
|
|
130
|
-
# In a payload position (+state+ inside a forbid_faults bracket) the
|
|
131
|
-
# envelope is a wire violation outright — its sole legal position is
|
|
132
|
-
# the Response fault field.
|
|
133
|
-
def unpack_fault(payload, state)
|
|
134
|
-
if state.faults_forbidden?
|
|
135
|
-
raise InvalidType, "Fault envelope (ext 0x02) is not a legal value in a payload position"
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
state.within_ext_frame(InvalidType) do
|
|
139
|
-
Decoder.decode(payload) do |map|
|
|
140
|
-
raise InvalidType, "Fault payload must be a map" unless map.is_a?(Hash)
|
|
141
|
-
|
|
142
|
-
Kobako::Fault.new(type: map["type"], message: map["message"], details: map["details"])
|
|
143
|
-
end
|
|
144
|
-
end
|
|
145
|
-
end
|
|
146
|
-
|
|
147
95
|
def register_symbol(factory)
|
|
148
96
|
factory.register_type(
|
|
149
97
|
EXT_SYMBOL, Symbol,
|
|
@@ -160,17 +108,9 @@ module Kobako
|
|
|
160
108
|
)
|
|
161
109
|
end
|
|
162
110
|
|
|
163
|
-
def register_fault(factory)
|
|
164
|
-
factory.register_type(
|
|
165
|
-
EXT_ERRENV, Kobako::Fault,
|
|
166
|
-
packer: ->(fault) { ExtTypes.pack_fault(fault, State.current) },
|
|
167
|
-
unpacker: ->(payload) { ExtTypes.unpack_fault(payload, State.current) }
|
|
168
|
-
)
|
|
169
|
-
end
|
|
170
|
-
|
|
171
111
|
# A catch-all packer that rejects any value with no wire representation
|
|
172
|
-
# as +
|
|
173
|
-
# BasicObject-based proxies; the narrower Symbol / Handle
|
|
112
|
+
# as +UnsupportedTypeError+. Registered on +BasicObject+ so it also covers
|
|
113
|
+
# BasicObject-based proxies; the narrower Symbol / Handle
|
|
174
114
|
# registrations still win by most-specific match, and native types never
|
|
175
115
|
# reach it. Packer-only: the guard never writes bytes, so its id is inert
|
|
176
116
|
# and the decode surface stays fail-closed.
|
|
@@ -184,7 +124,7 @@ module Kobako
|
|
|
184
124
|
def register_unrepresentable(factory)
|
|
185
125
|
factory.register_type(
|
|
186
126
|
UNREPRESENTABLE_GUARD_ID, BasicObject,
|
|
187
|
-
packer: ->(_value) { raise
|
|
127
|
+
packer: ->(_value) { raise UnsupportedTypeError, "value has no wire representation" }
|
|
188
128
|
)
|
|
189
129
|
end
|
|
190
130
|
end
|
|
@@ -11,8 +11,8 @@ module Kobako
|
|
|
11
11
|
# path; #deep_restore resolves each wire-decoded Handle back to its
|
|
12
12
|
# host object on every guest→host value path. #representable? is the
|
|
13
13
|
# by-value codec-type predicate that decides which leaves #deep_wrap
|
|
14
|
-
# must wrap: the closed
|
|
15
|
-
# ({docs/wire-
|
|
14
|
+
# must wrap: the closed 11-entry wire type set
|
|
15
|
+
# ({docs/wire/payload-msgpack.md}[link:../../../docs/wire/payload-msgpack.md] § Type
|
|
16
16
|
# Mapping).
|
|
17
17
|
#
|
|
18
18
|
# All helpers are pure except #deep_wrap, whose only side effect is
|
|
@@ -23,7 +23,7 @@ module Kobako
|
|
|
23
23
|
# Inclusive Integer range the msgpack gem encodes without raising
|
|
24
24
|
# +RangeError+ at encode time — signed +int 64+ minimum through
|
|
25
25
|
# unsigned +uint 64+ maximum
|
|
26
|
-
# ({docs/wire-
|
|
26
|
+
# ({docs/wire/payload-msgpack.md}[link:../../../docs/wire/payload-msgpack.md] § Type
|
|
27
27
|
# Mapping #3, the +fixint+ / +int 8..64+ / +uint 8..64+ union).
|
|
28
28
|
# Anchored as a +Range+ so #primitive_type? stays a single
|
|
29
29
|
# dispatch line. This is the codec's encode domain — not to
|
|
@@ -33,16 +33,16 @@ module Kobako
|
|
|
33
33
|
MSGPACK_INT_RANGE = (-(2**63)..((2**64) - 1))
|
|
34
34
|
|
|
35
35
|
# Codec-type predicate
|
|
36
|
-
# ({docs/wire-
|
|
36
|
+
# ({docs/wire/payload-msgpack.md}[link:../../../docs/wire/payload-msgpack.md] § Type
|
|
37
37
|
# Mapping). Returns +true+ when +value+ belongs to the closed
|
|
38
|
-
#
|
|
38
|
+
# 11-entry codec type set — +nil+, +TrueClass+, +FalseClass+,
|
|
39
39
|
# +Integer+ (in the +i64..u64+ value domain), +Float+, +String+,
|
|
40
40
|
# +Symbol+, +Kobako::Handle+, +Array+ whose every element is itself
|
|
41
41
|
# representable, or +Hash+ whose every key and value are
|
|
42
42
|
# representable. Integers outside the codec's signed-64 /
|
|
43
43
|
# unsigned-64 union are rejected so the predicate agrees with the
|
|
44
44
|
# msgpack gem's encode-time +RangeError+ behaviour the codec
|
|
45
|
-
# already surfaces as
|
|
45
|
+
# already surfaces as UnsupportedTypeError.
|
|
46
46
|
#
|
|
47
47
|
# The optional +depth+ bounds the recursive descent at the wire's
|
|
48
48
|
# structural nesting cap so a self-referential container — reachable
|