Shared Interactive Connectivity Establishment (ICE) types and pure helpers.
What ICE is
Home and office networks usually put devices behind a NAT: the address your laptop thinks it has is not the address the public internet sees. Firewalls may also block inbound UDP. ICE (RFC 8445) is the WebRTC procedure for discovering workable address/port pairs (candidates), probing them with STUN Binding checks, and nominating one pair for media.
Candidate kinds you will see in XGW:
- host -- a local interface address on this machine
- srflx (server reflexive) -- the public address discovered via STUN
- relay -- an address on a TURN server that forwards packets for you
- prflx (peer reflexive) -- learned from the far side during a check
This module holds types and pure functions used by both ICE-lite
(XGW.ICE.Lite, controlled-only responses) and ICE-full
(XGW.ICE.Full, gather + checklist + outbound checks). Gathering lives in
XGW.ICE.Gather; pair state in XGW.ICE.Checklist. Candidates on the wire
are %XMediaLib.SDP.Candidate{} structs.
RFCs
- RFC 8445 -- ICE (candidates, roles, priorities, foundations, nomination)
- RFC 8489 -- STUN Binding used as connectivity checks
- RFC 8656 -- TURN relay candidates (gather/checklist integration)
Summary
Types
ICE candidate type (RFC 8445).
Remote UDP endpoint {ip, port}.
ICE agent connection phase (full ICE state machine).
ICE controlling vs controlled role.
ICE candidate transport.
Functions
SDP a=candidate: attribute value (without the a= prefix).
RFC 8445 section 5.1.2 candidate priority from type, component, local preference.
Coerces trickle input to %Candidate{}.
Returns {ip, port} for a parsed candidate.
Formats an IPv4/IPv6 tuple as a dotted/colon string for SDP.
RFC 8445 section 5.1.1 candidate foundation (stable for agent lifetime).
RFC 8445 section 5.1.2 host candidate priority.
Local preference encoded in a host candidate priority.
Merges remote candidates: dedup by {foundation, component_id, ip, port},
sort descending by priority (RFC 8445).
RFC 8445 section 6.1.2.1 pair priority from controlling/controlled priorities.
Builds a peer-reflexive remote candidate from an inbound check source address.
Maps answerer DTLS setup to ICE role for full-ICE answers.
True when both candidates resolve to the same IP version (IPv4 or IPv6).
Maps SDP transport string to an atom.
Maps SDP candidate type string to an atom.
RFC 8445 section 5.1.2 type preference for a candidate type.
True when candidate has a parseable, non-unspecified IP (not mDNS / 0.0.0.0).
Returns true when the candidate has a foundation and allowed type.
Validates trickle :mid / :mline_index against bundled mids when present.
Types
@type candidate_type() :: :host | :srflx | :relay | :prflx
ICE candidate type (RFC 8445).
@type endpoint() :: {:inet.ip_address(), :inet.port_number()}
Remote UDP endpoint {ip, port}.
@type ice_phase() ::
:gathering | :checking | :connected | :completed | :failed | :disconnected
ICE agent connection phase (full ICE state machine).
@type role() :: :controlling | :controlled
ICE controlling vs controlled role.
@type transport() :: :udp | :tcp
ICE candidate transport.
Functions
@spec candidate_line(XMediaLib.SDP.Candidate.t()) :: String.t()
SDP a=candidate: attribute value (without the a= prefix).
Examples
iex> c = %XMediaLib.SDP.Candidate{
...> foundation: "1",
...> component_id: 1,
...> transport: "udp",
...> priority: 100,
...> ip: "192.0.2.1",
...> port: 5000,
...> type: "host"
...> }
iex> XGW.ICE.candidate_line(c)
"candidate:1 1 UDP 100 192.0.2.1 5000 typ host"
@spec candidate_priority(candidate_type(), non_neg_integer(), non_neg_integer()) :: non_neg_integer()
RFC 8445 section 5.1.2 candidate priority from type, component, local preference.
Examples
iex> XGW.ICE.candidate_priority(:host, 1, 65_535) == XGW.ICE.host_priority(1)
true
@spec coerce_candidate(XMediaLib.SDP.Candidate.t() | String.t()) :: {:ok, XMediaLib.SDP.Candidate.t()} | {:error, term()}
Coerces trickle input to %Candidate{}.
@spec endpoint(XMediaLib.SDP.Candidate.t()) :: endpoint()
Returns {ip, port} for a parsed candidate.
Examples
iex> XGW.ICE.endpoint(%XMediaLib.SDP.Candidate{ip: "192.0.2.1", port: 5000})
{{192, 0, 2, 1}, 5000}
@spec format_ip(:inet.ip_address()) :: String.t()
Formats an IPv4/IPv6 tuple as a dotted/colon string for SDP.
Examples
iex> XGW.ICE.format_ip({192, 0, 2, 1})
"192.0.2.1"
@spec foundation(String.t(), :inet.ip_address() | String.t(), String.t()) :: String.t()
RFC 8445 section 5.1.1 candidate foundation (stable for agent lifetime).
Hash of (type, base_ip, transport) masked to 32 bits.
Examples
iex> XGW.ICE.foundation("host", {192, 0, 2, 1}, "udp") ==
...> XGW.ICE.foundation("host", "192.0.2.1", "UDP")
true
@spec host_priority(non_neg_integer(), non_neg_integer()) :: non_neg_integer()
RFC 8445 section 5.1.2 host candidate priority.
local_preference defaults to 65535 (single interface).
Examples
iex> XGW.ICE.host_priority(1, 65_535) == XGW.ICE.candidate_priority(:host, 1, 65_535)
true
@spec local_preference_from_priority(XMediaLib.SDP.Candidate.t()) :: non_neg_integer()
Local preference encoded in a host candidate priority.
@spec merge_remote([XMediaLib.SDP.Candidate.t()], [XMediaLib.SDP.Candidate.t()]) :: [ XMediaLib.SDP.Candidate.t() ]
Merges remote candidates: dedup by {foundation, component_id, ip, port},
sort descending by priority (RFC 8445).
Examples
iex> low = %XMediaLib.SDP.Candidate{
...> foundation: "1", component_id: 1, transport: "udp",
...> priority: 100, ip: "192.0.2.1", port: 5000, type: "host"
...> }
iex> high = %{low | foundation: "2", priority: 200, ip: "192.0.2.2", port: 5001}
iex> Enum.map(XGW.ICE.merge_remote([low], [high, high]), & &1.priority)
[200, 100]
@spec pair_priority(role(), non_neg_integer(), non_neg_integer()) :: non_neg_integer()
RFC 8445 section 6.1.2.1 pair priority from controlling/controlled priorities.
Examples
iex> XGW.ICE.pair_priority(:controlling, 100, 50)
Bitwise.bsl(50, 32) + 2 * 100 + 1
iex> XGW.ICE.pair_priority(:controlled, 100, 50)
Bitwise.bsl(50, 32) + 2 * 100 + 0
@spec prflx_candidate(endpoint(), non_neg_integer(), String.t()) :: XMediaLib.SDP.Candidate.t()
Builds a peer-reflexive remote candidate from an inbound check source address.
@spec role(XMediaLib.SDP.Media.setup(), XMediaLib.SDP.Media.setup()) :: {:ok, role()} | {:error, :unsupported_setup}
Maps answerer DTLS setup to ICE role for full-ICE answers.
XGW convention (not RFC 8445 pt.5.1.1): offer actpass + answer passive
(DTLS server) -> ICE :controlled; offer actpass + answer active ->
:controlling. ICE-lite agents are always controlled per RFC 8445
pt.6.1.1 regardless of this helper.
Examples
iex> XGW.ICE.role(:actpass, :passive)
{:ok, :controlled}
iex> XGW.ICE.role(:actpass, :active)
{:ok, :controlling}
iex> XGW.ICE.role(:active, :passive)
{:error, :unsupported_setup}
@spec same_address_family?(XMediaLib.SDP.Candidate.t(), XMediaLib.SDP.Candidate.t()) :: boolean()
True when both candidates resolve to the same IP version (IPv4 or IPv6).
Examples
iex> a = %XMediaLib.SDP.Candidate{ip: "192.0.2.1", port: 1}
iex> b = %XMediaLib.SDP.Candidate{ip: "198.51.100.1", port: 2}
iex> XGW.ICE.same_address_family?(a, b)
true
Maps SDP transport string to an atom.
Examples
iex> XGW.ICE.transport_atom("UDP")
:udp
@spec type_atom(String.t()) :: candidate_type() | nil
Maps SDP candidate type string to an atom.
Examples
iex> XGW.ICE.type_atom("host")
:host
iex> XGW.ICE.type_atom("nope")
nil
@spec type_preference(candidate_type()) :: non_neg_integer()
RFC 8445 section 5.1.2 type preference for a candidate type.
Examples
iex> XGW.ICE.type_preference(:host)
126
iex> XGW.ICE.type_preference(:relay)
0
@spec usable_endpoint?(XMediaLib.SDP.Candidate.t()) :: boolean()
True when candidate has a parseable, non-unspecified IP (not mDNS / 0.0.0.0).
Examples
iex> XGW.ICE.usable_endpoint?(%XMediaLib.SDP.Candidate{ip: "192.0.2.1", port: 1})
true
iex> XGW.ICE.usable_endpoint?(%XMediaLib.SDP.Candidate{ip: "0.0.0.0", port: 1})
false
@spec valid?(XMediaLib.SDP.Candidate.t()) :: boolean()
Returns true when the candidate has a foundation and allowed type.
Examples
iex> XGW.ICE.valid?(%XMediaLib.SDP.Candidate{
...> foundation: "1", type: "host", ip: "192.0.2.1", port: 1
...> })
true
iex> XGW.ICE.valid?(%XMediaLib.SDP.Candidate{foundation: nil, type: "host"})
false
Validates trickle :mid / :mline_index against bundled mids when present.