Default-deny registry of named, peer-callable handlers.
This module is the entire authorisation surface that PeerNet exposes to the
network. Until a handle is registered via expose/3, peers calling that
handle name see {:error, :no_such_handle} — no module names, no stack
traces, no information about whether the handle existed at any prior time.
Each handler is a 2-arity function (caller_pubkey, args) -> result. The
caller's verified Ed25519 public key is the first argument, so handlers
can make per-peer authorisation decisions inline — and so a misbehaving
peer can't masquerade as another even if they spoof addressing fields in
the wire format.
Authorisation
An optional :authorize predicate gates which peers can reach the handle
at all. The predicate runs before the handler body, so a forbidden caller
never gets a chance to trigger handler logic:
Handlers.expose(:beam_admin, &MyMod.handle/2,
authorize: fn pubkey -> pubkey == @admin_pubkey end)Forbidden callers see {:error, :forbidden}. The handler body never runs.
Crash containment
If a handler raises, throws, or exits, the crash is caught and reported as
{:error, {:handler_crash, info}} — the connection process handling the
call stays alive and the connection is not torn down. A handler crashing is
not a connection-level fault; it's an app-level bug that should be visible
to the calling peer (in dev) but should not destabilise the link.
Examples
iex> name = :"handlers_doctest_#{System.unique_integer([:positive])}"
iex> {:ok, pid} = PeerNet.Handlers.start_link(name: name)
iex> :ok = PeerNet.Handlers.expose(pid, :echo, fn from, args -> {from, args} end)
iex> PeerNet.Handlers.dispatch(pid, :echo, <<1::256>>, %{x: 1})
{:ok, {<<1::256>>, %{x: 1}}}
Summary
Types
Per-handle options passed at expose time.
A handler function: receives caller pubkey + args, returns any term.
Functions
Returns a specification to start this module under a supervisor.
Dispatch a call from caller_pubkey to handle name with args.
Register handler under name so peers can call or send to it.
List all registered handle names.
Look up a handle's handler + options.
Remove a previously-exposed handle. No-op if absent.
Start the handlers registry.
Types
Functions
Returns a specification to start this module under a supervisor.
See Supervisor.
@spec dispatch(GenServer.server(), atom(), binary(), term()) :: {:ok, term()} | {:error, :no_such_handle | :forbidden | {:handler_crash, term()}}
Dispatch a call from caller_pubkey to handle name with args.
Returns:
{:ok, result}— handler returned normally.{:error, :no_such_handle}— handle isn't registered.{:error, :forbidden}—:authorizepredicate rejected this caller.{:error, {:handler_crash, info}}— handler raised, threw, or exited.
@spec expose(GenServer.server(), term(), term(), expose_opts()) :: :ok | {:error, :invalid_name | :invalid_handler}
Register handler under name so peers can call or send to it.
handler must be a 2-arity function (caller_pubkey, args) -> result.
Options:
:authorize—(pubkey -> boolean)predicate gating access. Peers that fail this check see{:error, :forbidden}and the handler body never runs.
Returns :ok, or {:error, :invalid_name} if name isn't an atom, or
{:error, :invalid_handler} if handler isn't a 2-arity function.
Re-exposing an existing name silently replaces the previous handler.
@spec list(GenServer.server()) :: [atom()]
List all registered handle names.
@spec lookup(GenServer.server(), atom()) :: {:ok, expose_opts(), handler()} | :error
Look up a handle's handler + options.
Returns {:ok, opts, handler} or :error. Useful for diagnostics; normal
callers should use dispatch/4.
@spec revoke(GenServer.server(), atom()) :: :ok
Remove a previously-exposed handle. No-op if absent.
@spec start_link(keyword()) :: GenServer.on_start()
Start the handlers registry.