PeerNet.Handlers (PeerNet v0.1.0)

Copy Markdown View Source

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

expose_opts()

@type expose_opts() :: [{:authorize, (binary() -> boolean())}]

Per-handle options passed at expose time.

handler()

@type handler() :: (binary(), term() -> term())

A handler function: receives caller pubkey + args, returns any term.

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

dispatch(server \\ __MODULE__, name, caller_pubkey, args)

@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}:authorize predicate rejected this caller.
  • {:error, {:handler_crash, info}} — handler raised, threw, or exited.

expose(server \\ __MODULE__, name, handler, opts \\ [])

@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.

list(server \\ __MODULE__)

@spec list(GenServer.server()) :: [atom()]

List all registered handle names.

lookup(server \\ __MODULE__, name)

@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.

revoke(server \\ __MODULE__, name)

@spec revoke(GenServer.server(), atom()) :: :ok

Remove a previously-exposed handle. No-op if absent.

start_link(opts \\ [])

@spec start_link(keyword()) :: GenServer.on_start()

Start the handlers registry.