# `XMediaLib.SDP`
[🔗](https://github.com/Lazarus404/xmedialib/blob/main/lib/sdp.ex#L26)

Session Description Protocol (SDP) parse and encode for real-time sessions.

## What problem this solves

Before RTP flows, peers exchange SDP text listing codecs, ICE candidates, DTLS
fingerprints, and SCTP data channels. Browsers and SIP endpoints emit slightly
different attribute lines. This module is the public parse/encode entry point
so XMediaLib callers can inspect or rewrite offers and answers without
hand-rolling line parsers.

SDP is a line-oriented text format that describes a multimedia session: who
created it, when it is valid, and which media streams (audio, video, SCTP
data channels) are offered with what codecs, transports, and network hints.
In WebRTC, two endpoints exchange SDP in an offer/answer handshake (often
together with ICE candidates) so each peer knows how to send and receive
encrypted RTP or SCTP traffic.

This module is the public API. `parse/1` turns SDP wire text into
`%XMediaLib.SDP{}`; `encode/1` serializes that struct back to wire bytes
with CRLF line endings. Session-level fields live on the root struct; each
`m=` media block becomes `%XMediaLib.SDP.Media{}`. Lower-level parsing and
field helpers live in `XMediaLib.SDP.Parser` and `XMediaLib.SDP.Encoder`.

## RFCs

* [RFC 4566](https://www.rfc-editor.org/rfc/rfc4566) - SDP (obsoleted by RFC 8866)
* [RFC 8866](https://www.rfc-editor.org/rfc/rfc8866) - SDP (current)
* [RFC 8829](https://www.rfc-editor.org/rfc/rfc8829) - JSEP (browser offer/answer usage)
* [RFC 8445](https://www.rfc-editor.org/rfc/rfc8445) - ICE (`a=ice-ufrag`, `a=ice-pwd`, `a=candidate`)
* [RFC 8122](https://www.rfc-editor.org/rfc/rfc8122) - DTLS fingerprint (`a=fingerprint`, `a=setup`)
* [RFC 8841](https://www.rfc-editor.org/rfc/rfc8841) - SCTP data channels (`a=sctp-port`, `a=dcmap`)

# `attribute`

```elixir
@type attribute() :: {String.t(), String.t() | true}
```

Generic session- or media-level attribute as `{name, value}`.

Flag attributes (no colon value on the wire) decode as `{name, true}`.

# `t`

```elixir
@type t() :: %XMediaLib.SDP{
  attributes: [attribute()],
  connection: XMediaLib.SDP.Connection.t() | nil,
  groups: [XMediaLib.SDP.Group.t()],
  media: [XMediaLib.SDP.Media.t()],
  origin: XMediaLib.SDP.Origin.t() | nil,
  session_name: String.t(),
  time: [XMediaLib.SDP.Time.t()],
  version: non_neg_integer()
}
```

Parsed session description.

## Fields

* `:version` - SDP version from `v=` (always `0` on the wire today)
* `:origin` - session creator from `o=` (`%XMediaLib.SDP.Origin{}`)
* `:session_name` - human-readable title from `s=`
* `:connection` - default connection address from session-level `c=`
* `:time` - active intervals from `t=` lines
* `:groups` - BUNDLE and other grouping from `a=group:`
* `:attributes` - other session-level `a=` lines not promoted to structs
* `:media` - ordered list of `%XMediaLib.SDP.Media{}` from `m=` sections

# `encode`

```elixir
@spec encode(t()) :: {:ok, binary()} | {:error, term()}
```

Encodes `%XMediaLib.SDP{}` to an RFC-oriented binary with `\r\n` line endings.

Session-level `t=` defaults to `t=0 0` when the list is empty. A missing
`o=` line is emitted as `o=- 0 0 IN IP4 127.0.0.1`.

## Examples

    iex> sdp = %XMediaLib.SDP{version: 0, session_name: "-", time: [%XMediaLib.SDP.Time{}]}
    iex> {:ok, bin} = XMediaLib.SDP.encode(sdp)
    iex> String.starts_with?(bin, "v=0")
    true

# `parse`

```elixir
@spec parse(binary()) :: {:ok, t()} | {:error, term()}
```

Parses an SDP binary into `%XMediaLib.SDP{}`.

Unknown non-`a=` lines are skipped unless `parse/2` is called with
`strict: true`. Malformed input returns `{:error, :parse_failed}`.

## Examples

    iex> {:ok, sdp} = XMediaLib.SDP.parse("v=0\r\no=- 0 0 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\n")
    iex> sdp.version
    0

# `parse`

```elixir
@spec parse(
  binary(),
  keyword()
) :: {:ok, t()} | {:error, term()}
```

Parses an SDP binary with options.

Supported options:

  * `:strict` - when `true`, unsupported non-attribute lines return
    `{:error, {:unsupported_line, line}}` instead of being skipped

## Examples

    iex> raw = "v=0\r\no=- 0 0 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\nz=bad\r\n"
    iex> {:error, {:unsupported_line, "z=bad"}} = XMediaLib.SDP.parse(raw, strict: true)

---

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