XGW.Descriptions (xgw v0.1.0)

Copy Markdown View Source

Turns negotiated Session Description Protocol (SDP) text into a transport config map that XGW.Peer can use.

What SDP is (for readers new to WebRTC)

Before two endpoints send audio or video, they exchange short text documents called SDP offers and answers. Those documents list:

  • which codecs and media lines are in use
  • ICE usernames/passwords used to authenticate connectivity probes
  • the DTLS certificate fingerprint used to trust the encryption handshake
  • optional candidates (IP/port addresses to try)
  • whether a data channel (SCTP) is negotiated

Signaling (HTTP, WebSocket, SIP) carries those documents. XGW does not invent SDP; it consumes the pair your app already negotiated (for example via WebSocket/JSEP or SIP + xsdp) and extracts only the fields needed to run ICE/DTLS/SRTP.

Behavior notes

Uses XMediaLib.SDP only. ICE-lite v1 picks the first bundled DTLS-SRTP m-line for fingerprint and ICE credentials. Session-level a=fingerprint / ICE / setup attributes (common in Firefox) are inherited onto media lines when those fields are absent at media level.

RFCs

  • RFC 8866 -- Session Description Protocol (SDP)
  • RFC 8829 -- JSEP (offer/answer usage in WebRTC)
  • RFC 8445 -- ICE credentials and candidates in SDP
  • RFC 8122 -- a=fingerprint
  • RFC 8841 -- SDP for SCTP data channels (when an application m-line is present)

Summary

Types

Parsed transport and media config for one peer.

Per-m-line media summary from the remote offer.

Negotiated SCTP data-channel ports when an application m-line is present.

SDP offer or answer as binary or parsed struct.

Functions

Parses offer and answer into peer config.

Compares two SDP fingerprints (case-insensitive value).

Normalizes an SDP fingerprint value to uppercase trimmed hex.

Parses SDP input into %XMediaLib.SDP{}.

Parses a restart offer for new remote ICE credentials and candidates.

Types

config()

@type config() :: %{
  remote_fingerprint: XMediaLib.SDP.Fingerprint.t(),
  local_fingerprint: XMediaLib.SDP.Fingerprint.t(),
  setup: XMediaLib.SDP.Media.setup(),
  bundle_mids: [String.t()],
  media_by_mid: %{optional(String.t()) => media_info()},
  remote_candidates: [XMediaLib.SDP.Candidate.t()],
  local_candidates: [XMediaLib.SDP.Candidate.t()],
  ssrc_to_mid: %{optional(non_neg_integer()) => String.t()},
  sctp: sctp_info() | nil,
  ice: %{
    local_ufrag: String.t(),
    local_pwd: String.t(),
    remote_ufrag: String.t(),
    remote_pwd: String.t(),
    local_setup: XMediaLib.SDP.Media.setup(),
    remote_setup: XMediaLib.SDP.Media.setup()
  }
}

Parsed transport and media config for one peer.

Fields

  • :remote_fingerprint / :local_fingerprint - DTLS cert digests from SDP
  • :setup - local DTLS setup (:passive / :active / ...)
  • :bundle_mids - BUNDLE group mids from the offer
  • :media_by_mid - per-mid codec/SSRC summary from the offer
  • :remote_candidates / :local_candidates - ICE candidates from offer/answer
  • :ssrc_to_mid - offer SSRC map used to tag inbound RTP meta
  • :sctp - negotiated data-channel ports, or nil
  • :ice - local/remote ufrag, pwd, and setup atoms

media_info()

@type media_info() :: %{
  mid: String.t() | nil,
  type: XMediaLib.SDP.Media.type() | nil,
  protocol: XMediaLib.SDP.Media.protocol() | nil,
  rtcp_mux: boolean(),
  codecs: [XMediaLib.SDP.Codec.t()],
  ssrcs: [XMediaLib.SDP.Ssrc.t()]
}

Per-m-line media summary from the remote offer.

Fields

  • :mid - media identification tag
  • :type / :protocol - SDP media type and proto
  • :rtcp_mux - whether RTCP shares the RTP port
  • :codecs / :ssrcs - negotiated codecs and SSRCs

sctp_info()

@type sctp_info() :: %{
  remote_port: non_neg_integer(),
  local_port: non_neg_integer(),
  max_message_size: non_neg_integer() | nil
}

Negotiated SCTP data-channel ports when an application m-line is present.

Fields

  • :remote_port / :local_port - SCTP port numbers from offer/answer
  • :max_message_size - optional max message size from SDP

sdp_input()

@type sdp_input() :: binary() | XMediaLib.SDP.t()

SDP offer or answer as binary or parsed struct.

Functions

build(remote_offer, local_answer, opts \\ [])

@spec build(sdp_input(), sdp_input(), keyword()) :: {:ok, config()} | {:error, term()}

Parses offer and answer into peer config.

Returns {:error, :invalid_sdp}, {:error, :missing_fingerprint}, {:error, :missing_ice_credentials}, or {:error, :invalid_setup} when ice_lite: true and local setup is not :passive.

fingerprints_match?(fingerprint1, fingerprint2)

@spec fingerprints_match?(
  XMediaLib.SDP.Fingerprint.t(),
  XMediaLib.SDP.Fingerprint.t()
) :: boolean()

Compares two SDP fingerprints (case-insensitive value).

Examples

iex> a = %XMediaLib.SDP.Fingerprint{algorithm: "sha-256", value: "aa:bb:cc"}
iex> b = %XMediaLib.SDP.Fingerprint{algorithm: "SHA-256", value: "AA:BB:CC"}
iex> XGW.Descriptions.fingerprints_match?(a, b)
true

iex> a = %XMediaLib.SDP.Fingerprint{algorithm: "sha-256", value: "aa:bb:cc"}
iex> b = %XMediaLib.SDP.Fingerprint{algorithm: "sha-256", value: "dd:ee:ff"}
iex> XGW.Descriptions.fingerprints_match?(a, b)
false

normalize_fingerprint(value)

@spec normalize_fingerprint(String.t()) :: String.t()

Normalizes an SDP fingerprint value to uppercase trimmed hex.

Examples

iex> XGW.Descriptions.normalize_fingerprint("aa:bb:cc")
"AA:BB:CC"

iex> XGW.Descriptions.normalize_fingerprint("  aa:bb  ")
"AA:BB"

parse_sdp(sdp)

@spec parse_sdp(sdp_input()) :: {:ok, XMediaLib.SDP.t()} | {:error, :invalid_sdp}

Parses SDP input into %XMediaLib.SDP{}.

Accepts a parsed struct or binary. Returns {:error, :invalid_sdp} for non-string, non-struct input.

restart_remote(offer)

@spec restart_remote(sdp_input()) ::
  {:ok,
   %{
     remote_ice_ufrag: String.t(),
     remote_ice_pwd: String.t(),
     remote_candidates: [XMediaLib.SDP.Candidate.t()]
   }}
  | {:error, term()}

Parses a restart offer for new remote ICE credentials and candidates.

Local ufrag/pwd are unchanged in v1; only remote side updates.