Module: Kobako::Codec::HandleWalk

Defined in:
lib/kobako/codec/handle_walk.rb,
sig/kobako/codec/handle_walk.rbs

Overview

Substitutes Capability Handles into and out of a Ruby value tree at the host↔guest boundary. #deep_wrap allocates a Kobako::Handle for each non-wire-representable leaf on the host→guest #run argument path; #deep_restore resolves each wire-decoded Handle back to its host object on every guest→host value path. #representable? is the by-value codec-type predicate that decides which leaves #deep_wrap must wrap: the closed 12-entry wire type set (docs/wire-codec.md § Type Mapping).

All helpers are pure except #deep_wrap, whose only side effect is allocating new Handle ids into the supplied table.

Constant Summary collapse

MSGPACK_INT_RANGE =

Inclusive Integer range the msgpack gem encodes without raising RangeError at encode time — signed int 64 minimum through unsigned uint 64 maximum (docs/wire-codec.md § Type Mapping #3, the fixint / int 8..64 / uint 8..64 union). Anchored as a Range so #primitive_type? stays a single dispatch line. This is the codec's encode domain — not to be confused with the Handle id range, which lives on Kobako::Handle as MIN_ID / MAX_ID (1..2^31 − 1) and represents a different concept entirely.

Returns:

  • (Range[Integer])
(-(2**63)..((2**64) - 1))

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.container_representable?(value, depth) ⇒ Boolean

The container branch of #representable?: recurses into Array elements and Hash key+value pairs through the public #representable?. Not part of the public surface; reach for #representable? instead.

Returns:

  • (Boolean)


165
166
167
168
169
170
171
# File 'lib/kobako/codec/handle_walk.rb', line 165

def container_representable?(value, depth)
  case value
  when ::Array then value.all? { |element| HandleWalk.representable?(element, depth + 1) }
  when ::Hash  then value.all? { |pair| pair.all? { |member| HandleWalk.representable?(member, depth + 1) } }
  else false
  end
end

.deep_restore(value, handler) ⇒ Object

Deep-walk Array / Hash containers in value and replace every Kobako::Handle leaf with the host-side object handler resolves it to. The symmetric inverse of #deep_wrap: that walk allocates objects into Handles on the host→guest argument path; this walk resolves Handles back to their objects wherever a guest→host payload carries one — an invocation result, a yield-block result, or a dispatch argument. The walk descends through Array elements and Hash keys and values one structural level at a time; any non-Handle leaf passes through unchanged.

value is a decoded Ruby value (a Handle here is a wire-decoded Kobako::Handle, never a guest-forged one); handler must respond to #fetch(id) -> object (a host-side Kobako::Catalog::Handles). handler.fetch raises Kobako::SandboxError for an id with no live binding, the corrupted-runtime fallback.



136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/kobako/codec/handle_walk.rb', line 136

def deep_restore(value, handler)
  case value
  when ::Array then value.map { |element| HandleWalk.deep_restore(element, handler) }
  when ::Hash
    # Rebuilt with each key restored: two distinct Handle keys that
    # resolve to equal host objects collapse to the later pair, as in
    # any Ruby Hash. The guest authored this payload, so that collapse
    # is its own concern, not a fidelity guarantee the host owes it.
    value.to_h { |key, val| [HandleWalk.deep_restore(key, handler), HandleWalk.deep_restore(val, handler)] }
  when Kobako::Handle then handler.fetch(value.id)
  else value
  end
end

.deep_wrap(value, handler, depth = 0) ⇒ Object

Deep-walk Array / Hash containers in value and replace every leaf that fails #representable? with a Kobako::Handle allocated from handler. The walk only descends through representable container shapes (Array, Hash) one structural level at a time; a non-representable leaf is wrapped as-is without inspecting its internal structure. An existing Kobako::Handle is representable and passes through unchanged — auto-wrap never re-wraps a Handle. Only Hash values are wrapped: a Hash key must already be wire-representable, since a key is not auto-wrapped the way a value is — a non-representable key raises Kobako::SandboxError rather than crossing as an opaque token the guest→host restore walk could not round-trip.

value may be any Ruby value; handler must respond to #alloc(object) -> Kobako::Handle (a host-side Kobako::Catalog::Handles). Returns a structurally equivalent value whose leaves are either representable or Kobako::Handle tokens.

Recursive calls spell the HandleWalk. receiver so the dispatch stays valid even when a block is captured and run under a different self (+module_function+ privatizes the instance copies).



80
81
82
83
84
85
86
87
88
# File 'lib/kobako/codec/handle_walk.rb', line 80

def deep_wrap(value, handler, depth = 0)
  guard_nesting!(depth)
  case value
  when ::Array then value.map { |element| HandleWalk.deep_wrap(element, handler, depth + 1) }
  when ::Hash  then HandleWalk.deep_wrap_hash(value, handler, depth)
  else
    representable?(value) ? value : handler.alloc(value)
  end
end

.deep_wrap_hash(hash, handler, depth) ⇒ Object

Wrap a Hash on the host→guest argument path: each value's non-representable leaves become Handles, while each key must be wire-representable as-is. A stateful object may cross the boundary as a Hash value but not as a key — the one deliberate asymmetry with the guest→host restore walk, which resolves Handle keys the guest built.



107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/kobako/codec/handle_walk.rb', line 107

def deep_wrap_hash(hash, handler, depth)
  wrapped = {} # : Hash[untyped, untyped]
  hash.each do |key, val|
    unless HandleWalk.representable?(key, depth + 1)
      raise Kobako::SandboxError,
            "a Hash passed to #run has a key that cannot cross the sandbox boundary " \
            "(#{key.class}); only wire-representable values may be Hash keys"
    end
    wrapped[key] = HandleWalk.deep_wrap(val, handler, depth + 1)
  end
  wrapped
end

.guard_nesting!(depth) ⇒ Object

Refuse a wrap walk that has descended past the wire's structural nesting bound — a reference cycle necessarily trips it — so a #run argument fails as a clean Kobako::SandboxError rather than an unbounded host recursion.



94
95
96
97
98
99
100
# File 'lib/kobako/codec/handle_walk.rb', line 94

def guard_nesting!(depth)
  return unless depth > MAX_NESTING_DEPTH

  raise Kobako::SandboxError,
        "a #run argument nests deeper than #{MAX_NESTING_DEPTH} levels and " \
        "cannot cross the sandbox boundary (possible reference cycle)"
end

.primitive_type?(value) ⇒ Boolean

The non-container branch of #representable?: returns true for the scalar leaves and an existing Handle. Not part of the public surface; reach for #representable? instead.

Returns:

  • (Boolean)


153
154
155
156
157
158
159
# File 'lib/kobako/codec/handle_walk.rb', line 153

def primitive_type?(value)
  case value
  when ::NilClass, ::TrueClass, ::FalseClass, ::Float, ::String, ::Symbol, Kobako::Handle then true
  when ::Integer then MSGPACK_INT_RANGE.cover?(value)
  else false
  end
end

.representable?(value, depth = 0) ⇒ Boolean

Codec-type predicate (docs/wire-codec.md § Type Mapping). Returns true when value belongs to the closed 12-entry codec type set — nil, TrueClass, FalseClass, Integer (in the i64..u64 value domain), Float, String, Symbol, Kobako::Handle, Array whose every element is itself representable, or Hash whose every key and value are representable. Integers outside the codec's signed-64 / unsigned-64 union are rejected so the predicate agrees with the msgpack gem's encode-time RangeError behaviour the codec already surfaces as UnsupportedType.

The optional depth bounds the recursive descent at the wire's structural nesting cap so a self-referential container — reachable as a cyclic Hash key on the #run argument path — reads as non-representable rather than looping, letting #deep_wrap_hash refuse it instead of overflowing this walk.

Returns:

  • (Boolean)


52
53
54
55
56
# File 'lib/kobako/codec/handle_walk.rb', line 52

def representable?(value, depth = 0)
  return false if depth > MAX_NESTING_DEPTH

  primitive_type?(value) || container_representable?(value, depth)
end

Instance Method Details

#self?.container_representable?Boolean

Parameters:

  • value (Object)
  • depth (Integer)

Returns:

  • (Boolean)


18
# File 'sig/kobako/codec/handle_walk.rbs', line 18

def self?.container_representable?: (untyped value, Integer depth) -> bool

#self?.deep_restoreObject

Parameters:

Returns:

  • (Object)


14
# File 'sig/kobako/codec/handle_walk.rbs', line 14

def self?.deep_restore: (untyped value, Kobako::Codec::_HandleTable handler) -> untyped

#self?.deep_wrapObject

Parameters:

Returns:

  • (Object)


8
# File 'sig/kobako/codec/handle_walk.rbs', line 8

def self?.deep_wrap: (untyped value, Kobako::Codec::_HandleTable handler, ?Integer depth) -> untyped

#self?.deep_wrap_hashHash[untyped, untyped]

Parameters:

Returns:

  • (Hash[untyped, untyped])


12
# File 'sig/kobako/codec/handle_walk.rbs', line 12

def self?.deep_wrap_hash: (Hash[untyped, untyped] hash, Kobako::Codec::_HandleTable handler, Integer depth) -> Hash[untyped, untyped]

#self?.guard_nesting!void

This method returns an undefined value.

Parameters:

  • depth (Integer)


10
# File 'sig/kobako/codec/handle_walk.rbs', line 10

def self?.guard_nesting!: (Integer depth) -> void

#self?.primitive_type?Boolean

Parameters:

  • value (Object)

Returns:

  • (Boolean)


16
# File 'sig/kobako/codec/handle_walk.rbs', line 16

def self?.primitive_type?: (untyped value) -> bool

#self?.representable?Boolean

Parameters:

  • value (Object)
  • depth (Integer)

Returns:

  • (Boolean)


6
# File 'sig/kobako/codec/handle_walk.rbs', line 6

def self?.representable?: (untyped value, ?Integer depth) -> bool