kobako 0.2.1 → 0.4.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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +1 -1
  3. data/README.md +205 -59
  4. data/data/kobako.wasm +0 -0
  5. data/ext/kobako/Cargo.toml +2 -2
  6. data/ext/kobako/src/wasm/cache.rs +15 -7
  7. data/ext/kobako/src/wasm/dispatch.rs +88 -36
  8. data/ext/kobako/src/wasm/host_state.rs +298 -55
  9. data/ext/kobako/src/wasm/instance.rs +477 -160
  10. data/ext/kobako/src/wasm.rs +20 -5
  11. data/lib/kobako/capture.rb +12 -10
  12. data/lib/kobako/codec/decoder.rb +3 -4
  13. data/lib/kobako/codec/encoder.rb +1 -1
  14. data/lib/kobako/codec/error.rb +3 -2
  15. data/lib/kobako/codec/factory.rb +24 -17
  16. data/lib/kobako/codec/utils.rb +105 -12
  17. data/lib/kobako/codec.rb +2 -1
  18. data/lib/kobako/errors.rb +22 -10
  19. data/lib/kobako/handle.rb +62 -0
  20. data/lib/kobako/handle_table.rb +119 -0
  21. data/lib/kobako/invocation.rb +143 -0
  22. data/lib/kobako/outcome/panic.rb +2 -2
  23. data/lib/kobako/outcome.rb +61 -24
  24. data/lib/kobako/rpc/dispatcher.rb +30 -28
  25. data/lib/kobako/rpc/envelope.rb +10 -10
  26. data/lib/kobako/rpc/fault.rb +4 -3
  27. data/lib/kobako/rpc/namespace.rb +3 -3
  28. data/lib/kobako/rpc/server.rb +23 -33
  29. data/lib/kobako/rpc/wire_error.rb +23 -0
  30. data/lib/kobako/sandbox.rb +211 -136
  31. data/lib/kobako/sandbox_options.rb +73 -0
  32. data/lib/kobako/snippet/binary.rb +30 -0
  33. data/lib/kobako/snippet/source.rb +28 -0
  34. data/lib/kobako/snippet/table.rb +174 -0
  35. data/lib/kobako/snippet.rb +20 -0
  36. data/lib/kobako/usage.rb +41 -0
  37. data/lib/kobako/version.rb +1 -1
  38. data/lib/kobako.rb +1 -0
  39. data/sig/kobako/codec/factory.rbs +1 -1
  40. data/sig/kobako/codec/utils.rbs +10 -0
  41. data/sig/kobako/errors.rbs +3 -0
  42. data/sig/kobako/handle.rbs +19 -0
  43. data/sig/kobako/handle_table.rbs +23 -0
  44. data/sig/kobako/invocation.rbs +25 -0
  45. data/sig/kobako/outcome.rbs +1 -1
  46. data/sig/kobako/rpc/dispatcher.rbs +7 -7
  47. data/sig/kobako/rpc/envelope.rbs +3 -3
  48. data/sig/kobako/rpc/server.rbs +1 -7
  49. data/sig/kobako/rpc/wire_error.rbs +6 -0
  50. data/sig/kobako/sandbox.rbs +22 -17
  51. data/sig/kobako/sandbox_options.rbs +32 -0
  52. data/sig/kobako/snippet/binary.rbs +12 -0
  53. data/sig/kobako/snippet/source.rbs +13 -0
  54. data/sig/kobako/snippet/table.rbs +36 -0
  55. data/sig/kobako/snippet.rbs +4 -0
  56. data/sig/kobako/usage.rbs +11 -0
  57. data/sig/kobako/wasm.rbs +5 -1
  58. metadata +21 -5
  59. data/lib/kobako/rpc/handle.rb +0 -38
  60. data/lib/kobako/rpc/handle_table.rb +0 -107
  61. data/sig/kobako/rpc/handle.rbs +0 -19
  62. data/sig/kobako/rpc/handle_table.rbs +0 -25
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kobako
4
+ # Kobako::SandboxOptions — immutable Value Object holding the four
5
+ # per-Sandbox configuration caps ({docs/behavior.md B-01,
6
+ # E-20}[link:../../docs/behavior.md]). Built on the +class X <
7
+ # Data.define(...)+ subclass form (the Steep-friendly shape — see
8
+ # +lib/kobako/outcome/panic.rb+).
9
+ #
10
+ # The +initialize+ method does double duty: it applies DEFAULT fallback
11
+ # for absent values and normalises (timeout to Float seconds,
12
+ # memory_limit to positive Integer bytes) before delegating to Data's
13
+ # +super+. Anything that survives +SandboxOptions.new+ is a wire-ready
14
+ # cap bundle the +Kobako::Wasm::Instance+ constructor consumes as-is.
15
+ class SandboxOptions < Data.define(:timeout, :memory_limit, :stdout_limit, :stderr_limit)
16
+ # Default wall-clock timeout for a single invocation: 60 seconds
17
+ # ({docs/behavior.md B-01}[link:../../docs/behavior.md]).
18
+ DEFAULT_TIMEOUT_SECONDS = 60.0
19
+
20
+ # Default cap on the per-invocation guest linear-memory delta:
21
+ # 1 MiB ({docs/behavior.md B-01}[link:../../docs/behavior.md]).
22
+ # The mruby image's initial allocation and prior invocations'
23
+ # watermark sit outside this budget — see B-01 Notes.
24
+ DEFAULT_MEMORY_LIMIT = 1 << 20
25
+
26
+ # Default per-channel capture ceiling: 1 MiB
27
+ # ({docs/behavior.md B-01}[link:../../docs/behavior.md]).
28
+ DEFAULT_OUTPUT_LIMIT = 1 << 20
29
+
30
+ # steep:ignore:start
31
+ def initialize(timeout: DEFAULT_TIMEOUT_SECONDS,
32
+ memory_limit: DEFAULT_MEMORY_LIMIT,
33
+ stdout_limit: nil,
34
+ stderr_limit: nil)
35
+ super(
36
+ timeout: normalize_timeout(timeout),
37
+ memory_limit: normalize_memory_limit(memory_limit),
38
+ stdout_limit: stdout_limit || DEFAULT_OUTPUT_LIMIT,
39
+ stderr_limit: stderr_limit || DEFAULT_OUTPUT_LIMIT
40
+ )
41
+ end
42
+
43
+ private
44
+
45
+ # Coerce +timeout+ into the Float seconds the ext expects, or +nil+
46
+ # to mean the cap is disabled. Any finite non-positive value is
47
+ # rejected — a zero or negative timeout would either fire instantly
48
+ # or never, both of which would surprise callers more than an early
49
+ # +ArgumentError+.
50
+ def normalize_timeout(timeout)
51
+ return nil if timeout.nil?
52
+ raise ArgumentError, "timeout must be Numeric or nil, got #{timeout.class}" unless timeout.is_a?(Numeric)
53
+
54
+ seconds = timeout.to_f
55
+ raise ArgumentError, "timeout must be > 0 (got #{timeout})" unless seconds.positive? && seconds.finite?
56
+
57
+ seconds
58
+ end
59
+
60
+ # Coerce +memory_limit+ into the byte cap the ext expects, or +nil+
61
+ # to mean unbounded. Must be a positive Integer when set; Float or
62
+ # zero/negative values are rejected.
63
+ def normalize_memory_limit(memory_limit)
64
+ return nil if memory_limit.nil?
65
+ unless memory_limit.is_a?(Integer) && memory_limit.positive?
66
+ raise ArgumentError, "memory_limit must be a positive Integer or nil, got #{memory_limit.inspect}"
67
+ end
68
+
69
+ memory_limit
70
+ end
71
+ # steep:ignore:end
72
+ end
73
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kobako
4
+ module Snippet
5
+ # Kobako::Snippet::Binary — value object representing a single
6
+ # +#preload(binary:)+ entry held by +Kobako::Snippet::Table+
7
+ # ({docs/behavior.md B-32}[link:../../../docs/behavior.md]).
8
+ #
9
+ # The +body+ is RITE bytecode (as emitted by +mrbc+) carried as an
10
+ # +ASCII_8BIT+ String so msgpack-ruby encodes it as +bin+ family on
11
+ # the wire ({docs/wire-codec.md Invocation channels}[link:../../../docs/wire-codec.md]).
12
+ # The host treats the bytes as opaque — the snippet's canonical
13
+ # name, when present, lives in the bytecode's embedded
14
+ # +debug_info+ and is resolved by the guest at load time;
15
+ # structural validation
16
+ # ({docs/behavior.md E-37 / E-38}[link:../../../docs/behavior.md])
17
+ # is deferred to the first invocation's guest replay.
18
+ #
19
+ # The class is a +Data.define+ subclass — frozen and value-equal.
20
+ # Callers (chiefly +Table+) construct instances via keyword form
21
+ # +Binary.new(body: ...)+. Wire-form construction is the +Table+'s
22
+ # responsibility.
23
+ class Binary < Data.define(:body)
24
+ # The +kind+ field value carried by bytecode snippets in their
25
+ # Frame 3 wire envelope entry
26
+ # ({docs/wire-codec.md Invocation channels}[link:../../../docs/wire-codec.md]).
27
+ KIND = "bytecode"
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kobako
4
+ module Snippet
5
+ # Kobako::Snippet::Source — value object representing a single
6
+ # +#preload(code:, name:)+ entry held by +Kobako::Snippet::Table+
7
+ # ({docs/behavior.md B-32}[link:../../../docs/behavior.md]).
8
+ #
9
+ # +name+ is the canonical +Symbol+ identity baked into the loaded
10
+ # IREP's +debug_info+; backtrace frames originating in this snippet
11
+ # surface as +(snippet:Name):line+. +body+ is the UTF-8 mruby source
12
+ # detached from the caller's reference at +Table#register+ time so
13
+ # later mutation of the original String cannot bleed through.
14
+ #
15
+ # The class is a +Data.define+ subclass — frozen, value-equal, and
16
+ # carries no mutation API. Callers (chiefly +Table+) construct
17
+ # instances via keyword form +Source.new(name: ..., body: ...)+.
18
+ # Wire-form construction is the +Table+'s responsibility, mirroring
19
+ # +Kobako::RPC.encode_request+'s pattern of reading attributes off a
20
+ # carrier rather than asking the carrier to self-describe.
21
+ class Source < Data.define(:name, :body)
22
+ # The +kind+ field value carried by source snippets in their Frame
23
+ # 3 wire envelope entry
24
+ # ({docs/wire-codec.md Invocation channels}[link:../../../docs/wire-codec.md]).
25
+ KIND = "source"
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,174 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "msgpack"
4
+
5
+ require_relative "binary"
6
+ require_relative "source"
7
+
8
+ module Kobako
9
+ module Snippet
10
+ # Kobako::Snippet::Table — per-Sandbox insertion-ordered registry of
11
+ # preloaded snippets
12
+ # ({docs/behavior.md B-32 / B-33}[link:../../../docs/behavior.md]).
13
+ #
14
+ # Entries replay against the fresh +mrb_state+ before per-invocation
15
+ # source / entrypoint resolution. Each +Source+ entry's +name+ is its
16
+ # canonical identity — the filename baked into the loaded IREP's
17
+ # +debug_info+ that surfaces in every backtrace frame originating
18
+ # from the snippet as +(snippet:Name):line+. Duplicate names within
19
+ # the +code:+ form would produce ambiguous attribution and are
20
+ # rejected at registration time
21
+ # ({docs/behavior.md E-33}[link:../../../docs/behavior.md]).
22
+ # +Binary+ entries carry no host-side name — their canonical name
23
+ # lives in the bytecode's +debug_info+ and is read by the guest at
24
+ # load time; the host does not extract it.
25
+ #
26
+ # Sealing (B-33) is governed by the owning Sandbox — the table itself
27
+ # is append-only and exposes no mutation API beyond +#register+; the
28
+ # Sandbox guards +#register+ behind the seal check before delegating.
29
+ class Table
30
+ # Ruby constant-name pattern enforced on snippet names
31
+ # ({docs/behavior.md E-34}[link:../../../docs/behavior.md]).
32
+ NAME_PATTERN = /\A[A-Z]\w*\z/
33
+
34
+ def initialize
35
+ @entries = [] # : Array[Kobako::Snippet::Source | Kobako::Snippet::Binary]
36
+ end
37
+
38
+ # Serialize the registered snippets to wire bytes. Each entry
39
+ # contributes a msgpack map shape; the collection rides as a single
40
+ # msgpack array. An empty table serializes to an empty array, never
41
+ # absent. The wire codec is an implementation detail — callers
42
+ # receive a binary +String+ that the +Kobako::Wasm+ layer ships
43
+ # through the invocation channel. Mirrors the
44
+ # +Kobako::RPC.encode_request+ pattern: entry value objects stay
45
+ # pure carriers, this method reads their attributes externally.
46
+ def encode
47
+ MessagePack.pack(@entries.map { |entry| entry_payload(entry) })
48
+ end
49
+
50
+ # Register one preloaded snippet in either of two forms
51
+ # ({docs/behavior.md B-32}[link:../../../docs/behavior.md]).
52
+ #
53
+ # * Source form +register(code: src, name: Name)+ — +src+ is the
54
+ # mruby source as a String; the bytes are re-encoded as UTF-8
55
+ # and detached from the caller's reference. +name+ is a Symbol
56
+ # or String matching +NAME_PATTERN+. Returns the Symbol form
57
+ # of +name+.
58
+ # * Binary form +register(binary: bytes)+ — +bytes+ is
59
+ # precompiled RITE bytecode as a String, duplicated and forced
60
+ # to ASCII-8BIT so msgpack-ruby ships it as +bin+. Returns
61
+ # +nil+ — bytecode entries are anonymous on the host side; any
62
+ # structural validation
63
+ # ({docs/behavior.md E-37 / E-38}[link:../../../docs/behavior.md])
64
+ # is deferred to the guest at first replay.
65
+ #
66
+ # The two forms are mutually exclusive: shape validation lives
67
+ # here so callers (chiefly +Kobako::Sandbox#preload+) collapse to
68
+ # a single delegation. Raises +ArgumentError+ on mixed forms,
69
+ # missing keywords, wrong types, malformed +name+ (E-34), or
70
+ # duplicate +code:+ +name+ (E-33).
71
+ def register(code: nil, name: nil, binary: nil)
72
+ if binary
73
+ raise ArgumentError, "cannot combine binary: with code: / name:" if code || name
74
+
75
+ register_binary!(binary)
76
+ else
77
+ register_source!(code, name)
78
+ end
79
+ end
80
+
81
+ # Iterate over registered entries in insertion order. Yields each
82
+ # entry (a +Kobako::Snippet::Source+ or +Kobako::Snippet::Binary+).
83
+ # Returns an Enumerator when no block is given.
84
+ def each(&)
85
+ @entries.each(&)
86
+ end
87
+
88
+ # Canonical names of every registered +Source+ entry, in insertion
89
+ # order. +Binary+ entries are skipped — their names live in
90
+ # bytecode +debug_info+ on the guest side and are not extracted by
91
+ # the host.
92
+ def names
93
+ @entries.filter_map { |entry| entry.name if entry.is_a?(Source) }
94
+ end
95
+
96
+ # Number of registered snippets.
97
+ def size
98
+ @entries.size
99
+ end
100
+
101
+ # Whether no snippets are registered.
102
+ def empty?
103
+ @entries.empty?
104
+ end
105
+
106
+ private
107
+
108
+ # Source-form register path. Delegates argument-shape checks to
109
+ # +ensure_source_args!+ (which returns the narrowed
110
+ # +[code, name]+ pair), normalises +name+ to a Symbol, rejects
111
+ # duplicates (E-33), and appends the Source entry.
112
+ def register_source!(code, name)
113
+ code, name = ensure_source_args!(code, name)
114
+ name_sym = normalize_name(name)
115
+ raise ArgumentError, "snippet #{name_sym.inspect} already preloaded" if names.include?(name_sym)
116
+
117
+ @entries << Source.new(name: name_sym, body: code.dup.force_encoding(Encoding::UTF_8))
118
+ name_sym
119
+ end
120
+
121
+ # Shape-only validation for the +code:+ + +name:+ pair. Returns
122
+ # the pair with +nil+ narrowed away so callers can treat both as
123
+ # present. The +code:+ type check runs before the +name:+
124
+ # presence check so callers passing +code: nil+ explicitly see
125
+ # the type error rather than the "missing keyword" error.
126
+ def ensure_source_args!(code, name)
127
+ raise ArgumentError, "missing keyword: code: + name:, or binary:" if code.nil? && name.nil?
128
+ raise ArgumentError, "code must be a String, got #{code.class}" unless code.is_a?(String)
129
+ raise ArgumentError, "missing keyword: name:" if name.nil?
130
+
131
+ [code, name]
132
+ end
133
+
134
+ # Binary-form register path. Validates the +binary:+ payload
135
+ # type and appends the Binary entry. The bytes are duplicated and
136
+ # forced to ASCII-8BIT so msgpack-ruby picks the +bin+ family on
137
+ # the wire.
138
+ def register_binary!(bytes)
139
+ raise ArgumentError, "binary must be a String, got #{bytes.class}" unless bytes.is_a?(String)
140
+
141
+ @entries << Binary.new(body: bytes.dup.force_encoding(Encoding::ASCII_8BIT))
142
+ nil
143
+ end
144
+
145
+ # Build the msgpack-ready Hash for one entry. Source entries
146
+ # contribute their host-side +name+; Binary entries omit it
147
+ # because the canonical name lives in the bytecode's embedded
148
+ # +debug_info+ and is read by the guest at load time
149
+ # ({docs/wire-codec.md Invocation channels}[link:../../../docs/wire-codec.md]).
150
+ def entry_payload(entry)
151
+ case entry
152
+ when Source
153
+ { "name" => entry.name.to_s, "kind" => Source::KIND, "body" => entry.body }
154
+ when Binary
155
+ { "kind" => Binary::KIND, "body" => entry.body }
156
+ end
157
+ end
158
+
159
+ def normalize_name(name)
160
+ unless name.is_a?(Symbol) || name.is_a?(String)
161
+ raise ArgumentError, "snippet name must be a Symbol or String, got #{name.class}"
162
+ end
163
+
164
+ name_str = name.to_s
165
+ unless NAME_PATTERN.match?(name_str)
166
+ raise ArgumentError,
167
+ "snippet name must match #{NAME_PATTERN.inspect} (got #{name.inspect})"
168
+ end
169
+
170
+ name_str.to_sym
171
+ end
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "snippet/binary"
4
+ require_relative "snippet/source"
5
+ require_relative "snippet/table"
6
+
7
+ module Kobako
8
+ # Kobako::Snippet — namespace for the per-Sandbox preloaded snippet
9
+ # registry and its entry value objects
10
+ # ({docs/behavior.md B-32 / B-33}[link:../../docs/behavior.md]).
11
+ #
12
+ # The +Table+ owns insertion-ordered storage and seal-coordination with
13
+ # the owning Sandbox; +Source+ is the value object representing a single
14
+ # +#preload(code:, name:)+ entry. Entry types live as siblings under
15
+ # this module rather than nested under +Table+ so they remain plain
16
+ # value objects with no implicit dependency on the registry that holds
17
+ # them.
18
+ module Snippet
19
+ end
20
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kobako
4
+ # Per-last-invocation resource accounting for a +Kobako::Sandbox+
5
+ # ({docs/behavior.md B-35}[link:../../docs/behavior.md]). Carries two
6
+ # readers populated by every +#eval+ / +#run+ invocation:
7
+ #
8
+ # * +wall_time+ — the Float number of seconds the guest export call
9
+ # spent inside wasmtime during the most recent invocation. The
10
+ # measurement bracket aligns with the +timeout+ deadline
11
+ # ({docs/behavior.md B-01}[link:../../docs/behavior.md]); time spent
12
+ # in host Service callbacks is included, but everything that runs
13
+ # after the guest export returns — the post-export
14
+ # +OUTCOME_BUFFER+ fetch and decode, plus stdout / stderr capture
15
+ # readout — is excluded.
16
+ # * +memory_peak+ — the Integer high-water mark, in bytes, of the
17
+ # per-invocation +memory.grow+ delta past the linear-memory size
18
+ # captured at invocation entry. Same baseline accounting as
19
+ # +memory_limit+ ({docs/behavior.md E-20}[link:../../docs/behavior.md]):
20
+ # the mruby image's initial allocation and any prior-invocation
21
+ # watermark sit outside the measurement. On +MemoryLimitError+
22
+ # +memory_peak+ never exceeds the configured cap because the
23
+ # rejected +desired+ value is not promoted into the high-water.
24
+ #
25
+ # Both readers are populated on every outcome, including +TrapError+
26
+ # branches, so the Host App can read +Sandbox#usage+ after rescuing a
27
+ # trap to diagnose how much of the budget the failing invocation
28
+ # consumed. Before the first invocation +Sandbox#usage+ returns the
29
+ # pre-invocation sentinel +Kobako::Usage::EMPTY+.
30
+ #
31
+ # Built on the +class X < Data.define(...)+ subclass form so the
32
+ # class body is fully Steep-visible; ruby/rbs upstream documents this
33
+ # as the Steep-friendly shape and the +Style/DataInheritance+ cop is
34
+ # disabled on that basis (see +.rubocop.yml+).
35
+ class Usage < Data.define(:wall_time, :memory_peak)
36
+ # Pre-invocation sentinel ({docs/behavior.md B-35}[link:../../docs/behavior.md]).
37
+ # Reused by +Sandbox+ before any invocation has run so callers do not
38
+ # need to handle a +nil+ +#usage+.
39
+ EMPTY = new(wall_time: 0.0, memory_peak: 0)
40
+ end
41
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kobako
4
- VERSION = "0.2.1"
4
+ VERSION = "0.4.0"
5
5
  end
data/lib/kobako.rb CHANGED
@@ -4,6 +4,7 @@ require_relative "kobako/version"
4
4
  require "kobako/kobako"
5
5
  require_relative "kobako/errors"
6
6
  require_relative "kobako/rpc"
7
+ require_relative "kobako/rpc/wire_error"
7
8
  require_relative "kobako/rpc/server"
8
9
  require_relative "kobako/wasm"
9
10
  require_relative "kobako/sandbox"
@@ -23,7 +23,7 @@ module Kobako
23
23
  def unpack_symbol: (String payload) -> Symbol
24
24
  def register_handle: () -> void
25
25
  def register_fault: () -> void
26
- def unpack_handle: (String payload) -> Kobako::RPC::Handle
26
+ def unpack_handle: (String payload) -> Kobako::Handle
27
27
  def pack_fault: (Kobako::RPC::Fault fault) -> String
28
28
  def unpack_fault: (String payload) -> Kobako::RPC::Fault
29
29
  end
@@ -1,9 +1,19 @@
1
1
  module Kobako
2
2
  module Codec
3
3
  module Utils
4
+ MSGPACK_INT_RANGE: Range[Integer]
5
+
4
6
  def self?.assert_utf8!: (String string, String label) -> void
5
7
 
6
8
  def self?.wire_boundary: [T] () { () -> T } -> T
9
+
10
+ def self?.wire_representable?: (untyped value) -> bool
11
+
12
+ def self?.deep_wrap: (untyped value, Kobako::HandleTable handle_table) -> untyped
13
+
14
+ def self?.primitive_wire_type?: (untyped value) -> bool
15
+
16
+ def self?.container_wire_representable?: (untyped value) -> bool
7
17
  end
8
18
  end
9
19
  end
@@ -49,4 +49,7 @@ module Kobako
49
49
 
50
50
  class HandleTableExhausted < HandleTableError
51
51
  end
52
+
53
+ class BytecodeError < SandboxError
54
+ end
52
55
  end
@@ -0,0 +1,19 @@
1
+ module Kobako
2
+ class Handle
3
+ MIN_ID: Integer
4
+ MAX_ID: Integer
5
+
6
+ attr_reader id: Integer
7
+
8
+ def initialize: (Integer id) -> void
9
+ | (id: Integer) -> void
10
+
11
+ def self.from_wire: (Integer id) -> Handle
12
+
13
+ def with: (?id: Integer) -> Handle
14
+
15
+ def ==: (untyped other) -> bool
16
+
17
+ def hash: () -> Integer
18
+ end
19
+ end
@@ -0,0 +1,23 @@
1
+ module Kobako
2
+ class HandleTable
3
+ def initialize: (?next_id: Integer) -> void
4
+
5
+ def alloc: (untyped object) -> Kobako::Handle
6
+
7
+ def fetch: (Integer id) -> untyped
8
+
9
+ def release: (Integer id) -> untyped
10
+
11
+ def reset!: () -> self
12
+
13
+ def mark_disconnected: (Integer id) -> self
14
+
15
+ def size: () -> Integer
16
+
17
+ def include?: (Integer id) -> bool
18
+
19
+ private
20
+
21
+ def require_bound!: (Integer id) -> void
22
+ end
23
+ end
@@ -0,0 +1,25 @@
1
+ module Kobako
2
+ class Invocation < Data
3
+ NAME_PATTERN: Regexp
4
+
5
+ attr_reader entrypoint: Symbol
6
+ attr_reader args: Array[untyped]
7
+ attr_reader kwargs: Hash[Symbol, untyped]
8
+
9
+ def self.new: (entrypoint: Symbol | String, ?args: Array[untyped], ?kwargs: Hash[untyped, untyped]) -> Invocation
10
+
11
+ def initialize: (entrypoint: Symbol | String, ?args: Array[untyped], ?kwargs: Hash[untyped, untyped]) -> void
12
+
13
+ def encode: (Kobako::HandleTable handle_table) -> String
14
+
15
+ private
16
+
17
+ def normalize_entrypoint: (Symbol | String target) -> Symbol
18
+
19
+ def validate_args!: (Array[untyped] args) -> Array[untyped]
20
+
21
+ def validate_kwargs!: (Hash[untyped, untyped] kwargs) -> Hash[Symbol, untyped]
22
+
23
+ def forged_handle_message: (String slot) -> String
24
+ end
25
+ end
@@ -19,6 +19,6 @@ module Kobako
19
19
 
20
20
  def self?.panic_target_class: (Panic panic) -> (singleton(SandboxError) | singleton(ServiceError))
21
21
 
22
- def self?.build_wire_violation_error: (String message) -> SandboxError
22
+ def self?.build_wire_violation_error: (String message, ?wire_error: String?) -> RPC::WireError
23
23
  end
24
24
  end
@@ -7,25 +7,25 @@ module Kobako
7
7
  class DisconnectedTargetError < StandardError
8
8
  end
9
9
 
10
- def self?.dispatch: (String request_bytes, Server server) -> String
10
+ def self?.dispatch: (String request_bytes, Server server, Kobako::HandleTable handle_table) -> String
11
11
 
12
12
  def self?.encode_caught_error: (StandardError error) -> String
13
13
 
14
14
  def self?.invoke: (untyped target, String method, Array[untyped] args, Hash[Symbol, untyped] kwargs) -> untyped
15
15
 
16
- def self?.resolve_arg: (untyped value, HandleTable handle_table) -> untyped
16
+ def self?.resolve_arg: (untyped value, Kobako::HandleTable handle_table) -> untyped
17
17
 
18
- def self?.resolve_target: (String | Kobako::RPC::Handle target, Server server, HandleTable handle_table) -> untyped
18
+ def self?.resolve_target: (String | Kobako::Handle target, Server server, Kobako::HandleTable handle_table) -> untyped
19
19
 
20
20
  def self?.resolve_path: (String path, Server server) -> untyped
21
21
 
22
- def self?.resolve_handle: (Kobako::RPC::Handle handle, HandleTable handle_table) -> untyped
22
+ def self?.resolve_handle: (Kobako::Handle handle, Kobako::HandleTable handle_table) -> untyped
23
23
 
24
- def self?.require_live_object!: (Integer id, HandleTable handle_table) -> untyped
24
+ def self?.require_live_object!: (Integer id, Kobako::HandleTable handle_table) -> untyped
25
25
 
26
- def self?.encode_ok: (untyped value, Server server) -> String
26
+ def self?.encode_ok: (untyped value, Kobako::HandleTable handle_table) -> String
27
27
 
28
- def self?.wrap_as_handle: (untyped value, Server server) -> Kobako::RPC::Handle
28
+ def self?.wrap_as_handle: (untyped value, Kobako::HandleTable handle_table) -> Kobako::Handle
29
29
 
30
30
  def self?.encode_error: (String type, String message) -> String
31
31
  end
@@ -4,14 +4,14 @@ module Kobako
4
4
  STATUS_ERROR: Integer
5
5
 
6
6
  class Request
7
- attr_reader target: String | Kobako::RPC::Handle
7
+ attr_reader target: String | Kobako::Handle
8
8
  attr_reader method_name: String
9
9
  attr_reader args: Array[untyped]
10
10
  attr_reader kwargs: Hash[Symbol, untyped]
11
11
 
12
- def initialize: (target: String | Kobako::RPC::Handle, method: String, ?args: Array[untyped], ?kwargs: Hash[Symbol, untyped]) -> void
12
+ def initialize: (target: String | Kobako::Handle, method: String, ?args: Array[untyped], ?kwargs: Hash[Symbol, untyped]) -> void
13
13
 
14
- def with: (?target: String | Kobako::RPC::Handle, ?method: String, ?args: Array[untyped], ?kwargs: Hash[Symbol, untyped]) -> Request
14
+ def with: (?target: String | Kobako::Handle, ?method: String, ?args: Array[untyped], ?kwargs: Hash[Symbol, untyped]) -> Request
15
15
 
16
16
  def ==: (untyped other) -> bool
17
17
 
@@ -1,9 +1,7 @@
1
1
  module Kobako
2
2
  module RPC
3
3
  class Server
4
- attr_reader handle_table: HandleTable
5
-
6
- def initialize: (?handle_table: HandleTable) -> void
4
+ def initialize: (?handle_table: Kobako::HandleTable) -> void
7
5
 
8
6
  def define: (Symbol | String name) -> Kobako::RPC::Namespace
9
7
 
@@ -11,8 +9,6 @@ module Kobako
11
9
 
12
10
  def bound?: (String target) -> bool
13
11
 
14
- def namespaces: () -> Array[Kobako::RPC::Namespace]
15
-
16
12
  def size: () -> Integer
17
13
 
18
14
  def empty?: () -> bool
@@ -25,8 +21,6 @@ module Kobako
25
21
 
26
22
  def sealed?: () -> bool
27
23
 
28
- def reset_handles!: () -> HandleTable
29
-
30
24
  def dispatch: (String request_bytes) -> String
31
25
 
32
26
  private
@@ -0,0 +1,6 @@
1
+ module Kobako
2
+ module RPC
3
+ class WireError < Kobako::SandboxError
4
+ end
5
+ end
6
+ end
@@ -1,18 +1,18 @@
1
1
  module Kobako
2
2
  class Sandbox
3
- DEFAULT_OUTPUT_LIMIT: Integer
4
-
5
- DEFAULT_TIMEOUT_SECONDS: Float
6
-
7
- DEFAULT_MEMORY_LIMIT: Integer
3
+ extend Forwardable
8
4
 
9
5
  attr_reader wasm_path: String
10
6
  attr_reader instance: Kobako::Wasm::Instance
11
- attr_reader stdout_limit: Integer
12
- attr_reader stderr_limit: Integer
13
- attr_reader timeout: Float?
14
- attr_reader memory_limit: Integer?
7
+ attr_reader options: Kobako::SandboxOptions
15
8
  attr_reader services: Kobako::RPC::Server
9
+ attr_reader snippets: Kobako::Snippet::Table
10
+
11
+ # Forwarded to @options via Forwardable#def_delegators.
12
+ def timeout: () -> Float?
13
+ def memory_limit: () -> Integer?
14
+ def stdout_limit: () -> Integer
15
+ def stderr_limit: () -> Integer
16
16
 
17
17
  def initialize: (
18
18
  ?wasm_path: String?,
@@ -30,24 +30,29 @@ module Kobako
30
30
 
31
31
  def stderr_truncated?: () -> bool
32
32
 
33
+ attr_reader usage: Kobako::Usage
34
+
33
35
  def define: (Symbol | String name) -> Kobako::RPC::Namespace
34
36
 
35
- def run: (String source) -> untyped
37
+ def preload: (code: String, name: Symbol | String) -> Kobako::Sandbox
38
+ | (binary: String) -> Kobako::Sandbox
36
39
 
37
- private
40
+ def run: (Symbol | String target, *untyped args, **untyped kwargs) -> untyped
38
41
 
39
- def clear_captures!: () -> void
42
+ def eval: (String code) -> untyped
40
43
 
41
- def normalize_timeout: ((Float | Integer)? timeout) -> Float?
44
+ private
42
45
 
43
- def normalize_memory_limit: (Integer? memory_limit) -> Integer?
46
+ def reset_invocation_state!: () -> void
44
47
 
45
- def reset_run_state!: () -> void
48
+ def begin_invocation!: () -> void
46
49
 
47
50
  def read_captures!: () -> void
48
51
 
49
- def run_guest: (String preamble, String source) -> void
52
+ def read_usage!: () -> void
53
+
54
+ def trap_class_for: (Kobako::Wasm::Error err) -> singleton(Kobako::TrapError)
50
55
 
51
- def take_result!: () -> untyped
56
+ def invoke!: (Symbol verb) { () -> void } -> untyped
52
57
  end
53
58
  end