Class: Kobako::Catalog::Handles

Inherits:
Object
  • Object
show all
Defined in:
lib/kobako/catalog/handles.rb,
sig/kobako/catalog/handles.rbs

Overview

Host-side mapping from opaque integer Handle IDs to Ruby objects. The table is owned by Kobako::Sandbox and injected into the per-Sandbox Kobako::Catalog::Namespaces so guest→host dispatch resolves Handle targets and arguments against the same table that host→guest wire encoding allocates into.

Lifecycle invariants:

- Handle IDs are allocated by a monotonically increasing counter
scoped to a single invocation. The first ID issued in an
invocation is 1; ID 0 is reserved as the invalid sentinel and is
never returned by +#alloc+.

- At every invocation boundary (via +#reset!+), every Handle issued
under the old state becomes invalid. Reset applies uniformly
regardless of allocation source (Service return or host-injected
argument).

- The cap is +0x7fff_ffff+ (2³¹ − 1). Allocation beyond the cap
raises immediately — no silent truncation, no wrap, no ID reuse.

Instance Method Summary collapse

Constructor Details

#initialize(next_id: 1) ⇒ Handles

Build a fresh, empty table. next_id is an internal seam that sets the starting value of the monotonic counter (defaults to 1); tests pass a value near Kobako::Handle::MAX_ID to exercise the cap-exhaustion path without 2³¹ allocations.

Parameters:

  • next_id: (Integer) (defaults to: 1)


32
33
34
35
# File 'lib/kobako/catalog/handles.rb', line 32

def initialize(next_id: 1)
  @entries = {} # : Hash[Integer, untyped]
  @next_id = next_id
end

Instance Method Details

#alloc(object) ⇒ Kobako::Handle

Bind object in the table and return a Kobako::Handle token for it. object is any host-side Ruby object to bind. Returns a freshly-allocated Kobako::Handle whose #id falls in [Kobako::Handle::MIN_ID, Kobako::Handle::MAX_ID]. Raises Kobako::HandleExhaustedError if the next ID would exceed the cap. The cap is anchored on Kobako::Handle — the wire codec and the allocator share the same invariant.

Returning a Handle (rather than a bare Integer id) keeps the allocator's output a domain entity. An id is the Handle's only content, so the same internal Kobako::Handle.restore constructor serves both this allocator and the codec's wire-decode path.

Parameters:

  • object (Object)

Returns:



49
50
51
52
53
54
55
56
# File 'lib/kobako/catalog/handles.rb', line 49

def alloc(object)
  reject_unwrappable!(object)
  ensure_capacity!
  id = @next_id
  @entries[id] = object
  @next_id = id + 1
  Kobako::Handle.restore(id)
end

#ensure_capacity!void

This method returns an undefined value.

Guard #alloc against issuing an ID past the cap. Returns nil on success; raises Kobako::HandleExhaustedError at exhaustion.



100
101
102
103
104
105
106
107
# File 'lib/kobako/catalog/handles.rb', line 100

def ensure_capacity!
  cap = Kobako::Handle::MAX_ID
  return unless @next_id > cap

  raise HandleExhaustedError,
        "Out of handle allocations: too many host objects were referenced " \
        "in a single invocation (limit #{cap})"
end

#fetch(id) ⇒ Object

Resolve a Handle ID to its bound object. id is a Handle ID previously returned by #alloc. Returns the bound object. Raises Kobako::SandboxError if id is not currently bound.

Parameters:

  • id (Integer)

Returns:

  • (Object)


61
62
63
64
# File 'lib/kobako/catalog/handles.rb', line 61

def fetch(id)
  require_bound!(id)
  @entries[id]
end

#reject_unwrappable!(object) ⇒ void

This method returns an undefined value.

Refuse to mint a Capability Handle for a reflective gadget: a Binding / Method / UnboundMethod would hand the guest a callable proxy onto host reflection (a returned Binding reaches Binding#eval). Raising here keeps the rule at the single mint point, so it holds on both the Service-return and the #run host→guest auto-wrap paths.

Parameters:

  • object (Object)


91
92
93
94
95
96
# File 'lib/kobako/catalog/handles.rb', line 91

def reject_unwrappable!(object)
  case object
  when Binding, Method, UnboundMethod
    raise SandboxError, "a #{object.class} cannot cross as a Capability Handle"
  end
end

#require_bound!(id) ⇒ void

This method returns an undefined value.

Single source of truth for the "unknown Handle id" raise used by #fetch. Returns nil on success; raises Kobako::SandboxError when id is not currently bound.

Parameters:

  • id (Integer)

Raises:



112
113
114
115
116
# File 'lib/kobako/catalog/handles.rb', line 112

def require_bound!(id)
  return if @entries.key?(id)

  raise SandboxError, "unknown Handle id: #{id.inspect}"
end

#reset!self

Clear all entries AND reset the counter to 1. Called at the per-invocation boundary by Kobako::Sandbox. Returns self.

Returns:

  • (self)


68
69
70
71
72
# File 'lib/kobako/catalog/handles.rb', line 68

def reset!
  @entries.clear
  @next_id = 1
  self
end

#sizeInteger

Number of currently-bound entries. Used by tests of the Dispatcher and Codec::HandleWalk#deep_wrap to observe whether each path allocates exactly the Handle entries it should — the Handles table itself never consults its own size, but the surrounding code's allocation contract is part of the observable boundary.

Returns:

  • (Integer)


79
80
81
# File 'lib/kobako/catalog/handles.rb', line 79

def size
  @entries.size
end