# `Bluez.Agent`
[🔗](https://github.com/bbangert/bluez/blob/v0.1.0/lib/bluez/agent.ex#L1)

The `org.bluez.Agent1` pairing agent backing `Bluez.Gatt`'s `pair/1`
(Phase 2 of the Bluetooth proxy).

bluetoothd resolves pairing IO through an agent: when `Device1.Pair()`
is called it uses the agent registered by the *calling* D-Bus
connection, falling back to the bus-wide **default agent**. The Pair
call is made on `Bluez.Gatt`'s connection (which registers no agent),
so this process must be the default agent — `RegisterAgent` alone is
not enough; `RequestDefaultAgent` is what routes Gatt's pairings here.

Capability is `NoInputNoOutput`, so bluetoothd negotiates Just Works
with every peripheral — the same IO posture as an ESP32 ESPHome proxy.
Just Works pairing normally completes without any agent callback;
everything below is for the exceptional paths.

## Security posture

A default agent answers for *all* pairing on the adapter, including a
hypothetical inbound attempt (the adapter is never made discoverable,
so none is expected). Authorization-style callbacks
(`RequestConfirmation`, `RequestAuthorization`, `AuthorizeService`)
are therefore confirmed **only for device paths with an in-flight
`Device1.Pair()` we initiated** — `Bluez.Gatt` brackets each Pair call
with `expect_pairing/1` / `pairing_done/1` — and rejected otherwise.
PIN/passkey callbacks are always rejected: we have no IO to display or
collect one, and under NoInputNoOutput bluetoothd should never send
them.

Like `Bluez.Client`, this is both a D-Bus client (registration calls)
and a service (bluetoothd calls back into our exported object) on its
own rebus connection — its failure domain is pairing only; scanning
and GATT traffic are untouched. If the Agent is down, `Pair()` still
proceeds for devices that need no interaction; anything needing the
agent fails cleanly on the BlueZ side.

# `agent_path`

```elixir
@spec agent_path() :: String.t()
```

Object path our Agent1 implementation is exported at.

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `decide`

```elixir
@spec decide(String.t(), list(), %{optional(String.t()) =&gt; reference()}) ::
  :ack | {:reject, String.t()} | :unknown
```

Decide the reply for an inbound `org.bluez.Agent1` call. Pure — exposed
for tests.

Returns `:ack` (empty success reply), `{:reject, error_name}`, or
`:unknown` (not an Agent1 member we implement). `expected` is the
in-flight pairing map (`device_path => expiry ref`).

# `expect_pairing`

```elixir
@spec expect_pairing(String.t()) :: :ok
```

Mark `device_path` as having an in-flight `Device1.Pair()` we initiated,
so authorization callbacks for it are confirmed.

Synchronous on purpose: the caller issues `Pair()` right after, and a
cast could still be in our mailbox when bluetoothd's authorization
callback arrives — rejecting a pairing we initiated. The return is `:ok`
even when the Agent isn't running (pairing then degrades to whatever
BlueZ can do agent-less rather than crashing the pair Task) or the path
is refused (the pairing fails closed on the BlueZ side).

Expectations expire after 40000 ms as a backstop for a pair
Task that died without calling `pairing_done/1`.

# `pairing_done`

```elixir
@spec pairing_done(String.t()) :: :ok
```

Remove `device_path` from the in-flight pairing set.

# `start_link`

---

*Consult [api-reference.md](api-reference.md) for complete listing*
