# `XGW.Peer.Gather`
[🔗](https://github.com/Lazarus404/xgw/blob/main/lib/xgw/peer/gather.ex#L1)

Post-SDP local candidate checks and background srflx/relay gather for a Peer.

## Why this sits beside `XGW.Peer`

After `apply_descriptions/4`, a full-ICE Peer may still need server-reflexive
or TURN relay candidates that were not ready when the answer was sent. This
module (pure helpers + Task starters, not a GenServer) verifies that answer
locals match Listener gather expectations and kicks off async gather that
later emits `{:xgw, :local_candidate, ...}` / completion events via Peer.

## RFCs

* RFC 8445 -- trickle / post-answer gather of srflx and relay locals
* RFC 8489 -- STUN Binding for srflx
* RFC 8656 -- TURN Allocate for relay

# `apply_srflx_candidates`

```elixir
@spec apply_srflx_candidates(map(), [XMediaLib.SDP.Candidate.t()]) :: map()
```

Merges gathered srflx into ICE and notifies the handler.

Caller schedules connectivity-check ticks after this returns.

# `candidate_key`

```elixir
@spec candidate_key(XMediaLib.SDP.Candidate.t()) :: {term(), term(), term(), term()}
```

Dedup key for a local ICE candidate (`foundation`, component, ip, port).

## Examples

    iex> c = %XMediaLib.SDP.Candidate{
    ...>   foundation: "1", component_id: 1, ip: "192.0.2.1", port: 5000
    ...> }
    iex> XGW.Peer.Gather.candidate_key(c)
    {"1", 1, "192.0.2.1", 5000}

# `hosts_missing_srflx`

```elixir
@spec hosts_missing_srflx([XMediaLib.SDP.Candidate.t() | map()]) :: [
  XMediaLib.SDP.Candidate.t() | map()
]
```

Returns host candidates that do not yet have an srflx with the same foundation.

Used to decide whether post-apply STUN gather still has work to do.

## Examples

    iex> hosts = [%{type: "host", foundation: "1"}, %{type: "host", foundation: "2"}]
    iex> locals = hosts ++ [%{type: "srflx", foundation: "1"}]
    iex> XGW.Peer.Gather.hosts_missing_srflx(locals)
    [%{type: "host", foundation: "2"}]

# `maybe_emit_local_candidates_complete`

```elixir
@spec maybe_emit_local_candidates_complete(map()) :: map()
```

Notifies handler when post-apply gather tasks have finished.

# `start_post_apply_relay`

```elixir
@spec start_post_apply_relay(map()) :: map()
```

Starts async relay gather when TURN is configured and locals uncovered.

# `start_post_apply_srflx`

```elixir
@spec start_post_apply_srflx(map()) :: map()
```

Starts async srflx gather when STUN servers and missing hosts exist.

# `verify_local_candidates`

```elixir
@spec verify_local_candidates(map(), map()) ::
  :ok | {:error, :local_candidate_mismatch}
```

Ensures answer locals match Listener gather expectations (full ICE only).

Returns `:ok` for ICE-lite or when no Listener is attached.

---

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