# `XGW.ICE.Checklist`
[🔗](https://github.com/Lazarus404/xgw/blob/main/lib/xgw/ice/checklist.ex#L1)

Forms, sorts, and advances ICE candidate **pairs** through check states.

## Why a checklist exists

Each local candidate can be paired with each remote candidate that shares
component id, transport, and address family. ICE tries those pairs in
priority order with STUN Binding checks. This module is the pure state
machine for that list: build/extend pairs, mark in-progress/succeeded/failed,
nominate, and pick the next check.

States follow RFC 8445 (`:waiting`, `:in_progress`, `:succeeded`, `:failed`,
`:frozen`).

ponytail: serial check queue (one `:in_progress` pair at a time); upgrade path:
parallel checks per foundation with per-pair RTO timers.

## RFCs

* RFC 8445 -- candidate pair formation, priority, checklist states, nomination

# `pair`

```elixir
@type pair() :: %{
  local: XMediaLib.SDP.Candidate.t(),
  remote: XMediaLib.SDP.Candidate.t(),
  priority: non_neg_integer(),
  state: pair_state(),
  nominated?: boolean(),
  transaction_id: non_neg_integer() | nil,
  rto_ms: non_neg_integer(),
  retransmit_count: non_neg_integer(),
  last_sent_ms: integer() | nil
}
```

One local x remote candidate pair on the checklist.

## Fields

* `:local` / `:remote` - ICE candidates forming the pair
* `:priority` - RFC 8445 pair priority
* `:state` - checklist state
* `:nominated?` - true after USE-CANDIDATE / nomination
* `:transaction_id` - STUN transaction for the in-flight check
* `:rto_ms` / `:retransmit_count` / `:last_sent_ms` - retransmit timer state

# `pair_state`

```elixir
@type pair_state() :: :waiting | :in_progress | :succeeded | :failed | :frozen
```

Checklist pair state (`:waiting` | `:in_progress` | `:succeeded` | `:failed` | `:frozen`).

# `all_failed?`

```elixir
@spec all_failed?([pair()]) :: boolean()
```

Returns true when all pairs are failed.

## Examples

    iex> XGW.ICE.Checklist.all_failed?([])
    false

    iex> XGW.ICE.Checklist.all_failed?([%{state: :failed}, %{state: :failed}])
    true

# `build`

```elixir
@spec build(
  [XMediaLib.SDP.Candidate.t()],
  [XMediaLib.SDP.Candidate.t()],
  XGW.ICE.role()
) :: [pair()]
```

Builds UDP component-1 pairs from local and remote candidate lists.

# `extend`

```elixir
@spec extend(
  [pair()],
  [XMediaLib.SDP.Candidate.t()],
  [XMediaLib.SDP.Candidate.t()],
  XGW.ICE.role()
) ::
  [pair()]
```

Extends an existing checklist with pairs for new locals/remotes without resetting
in-progress or succeeded pair state (trickle during checking).

# `find_by_remote`

```elixir
@spec find_by_remote([pair()], XGW.ICE.endpoint(), XMediaLib.SDP.Candidate.t() | nil) ::
  pair() | nil
```

Finds pair matching remote endpoint and optional local candidate.

# `find_by_transaction`

```elixir
@spec find_by_transaction([pair()], non_neg_integer()) :: pair() | nil
```

Finds pair by outbound transaction id.

# `mark_failed`

```elixir
@spec mark_failed([pair()], pair()) :: [pair()]
```

Marks a pair failed (RTO exhausted).

# `mark_in_progress`

```elixir
@spec mark_in_progress([pair()], pair(), non_neg_integer(), integer()) :: [pair()]
```

Marks a pair `:in_progress` with a new transaction id.

# `mark_pair_succeeded`

```elixir
@spec mark_pair_succeeded([pair()], pair()) :: [pair()]
```

Marks a specific pair succeeded by identity.

# `mark_succeeded`

```elixir
@spec mark_succeeded([pair()], non_neg_integer()) :: [pair()]
```

Marks the pair matching `transaction_id` as succeeded.

# `next_check`

```elixir
@spec next_check([pair()]) :: pair() | nil
```

Returns the next pair needing an outbound check, or nil.

Prefers an already `:in_progress` pair (serial queue) over the next `:waiting`.

## Examples

    iex> waiting = %{state: :waiting}
    iex> busy = %{state: :in_progress}
    iex> XGW.ICE.Checklist.next_check([waiting, busy])
    %{state: :in_progress}

# `nominate`

```elixir
@spec nominate([pair()], XMediaLib.SDP.Candidate.t(), XMediaLib.SDP.Candidate.t()) ::
  [pair()]
```

Marks the pair matching remote/local as nominated.

# `nominated_succeeded?`

```elixir
@spec nominated_succeeded?([pair()]) :: boolean()
```

Returns true when a nominated pair has succeeded.

## Examples

    iex> XGW.ICE.Checklist.nominated_succeeded?([
    ...>   %{nominated?: true, state: :succeeded}
    ...> ])
    true

# `prune_redundant`

```elixir
@spec prune_redundant([pair()]) :: [pair()]
```

Drops lower-priority pairs sharing the same local and remote foundation.

## Examples

    iex> a = %{local: %{foundation: "1"}, remote: %{foundation: "a"}, priority: 10}
    iex> b = %{local: %{foundation: "1"}, remote: %{foundation: "a"}, priority: 1}
    iex> c = %{local: %{foundation: "2"}, remote: %{foundation: "a"}, priority: 5}
    iex> length(XGW.ICE.Checklist.prune_redundant([a, b, c]))
    2

# `rto_expired?`

```elixir
@spec rto_expired?(pair(), integer()) :: boolean()
```

Returns true when RTO has expired for an in-progress pair.

## Examples

    iex> XGW.ICE.Checklist.rto_expired?(
    ...>   %{state: :in_progress, last_sent_ms: 0, rto_ms: 500},
    ...>   500
    ...> )
    true

    iex> XGW.ICE.Checklist.rto_expired?(%{state: :waiting}, 1000)
    false

# `same_pair?`

```elixir
@spec same_pair?(pair(), pair()) :: boolean()
```

Returns true when two pairs share the same local and remote foundations.

## Examples

    iex> a = %{local: %{foundation: "1"}, remote: %{foundation: "x"}}
    iex> b = %{local: %{foundation: "1"}, remote: %{foundation: "x"}}
    iex> XGW.ICE.Checklist.same_pair?(a, b)
    true

# `schedule_retransmit`

```elixir
@spec schedule_retransmit([pair()], pair()) :: [pair()]
```

Schedules retransmit: doubles RTO unless max retransmits reached (-> failed).

# `sort`

```elixir
@spec sort([pair()]) :: [pair()]
```

Sorts pairs descending by priority.

## Examples

    iex> pairs = [%{priority: 1}, %{priority: 9}, %{priority: 5}]
    iex> Enum.map(XGW.ICE.Checklist.sort(pairs), & &1.priority)
    [9, 5, 1]

---

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