# `XGW.DTLS.Transport`
[🔗](https://github.com/Lazarus404/xgw/blob/main/lib/xgw/dtls/transport.ex#L1)

Adapts OTP `:ssl` DTLS to XGW's shared Listener socket.

## Role in the stack

Erlang's SSL application talks to the network through a small **callback
info** (`cb_info`) module. This module is that adapter: inbound DTLS
datagrams are **injected** into the connection owner after the Listener
demuxes them, and outbound DTLS bytes go out through the same UDP (or
ICE-TCP / TURN-wrapped) path the Peer already selected.

Without this layer, OTP would open a second UDP port and break WebRTC's
"one 5-tuple for STUN + DTLS + SRTP" model.

## RFCs

* RFC 9147 / RFC 6347 -- DTLS datagram transport
* RFC 7983 -- sharing one 5-tuple with STUN and SRTP
* RFC 8656 -- TURN Send Indication / ChannelData when the selected path is relay
* RFC 6544 -- ICE-TCP framing when the selected path is TCP

# `socket`

```elixir
@type socket() :: port()
```

UDP socket used as the DTLS datagram transport (`:gen_udp` port).

# `clear_inject_owner`

```elixir
@spec clear_inject_owner(
  socket(),
  {:inet.ip_address(), :inet.port_number()}
) :: :ok
```

Clears per-remote inject owner (multi-peer shared socket).

# `clear_relay_send`

```elixir
@spec clear_relay_send(socket(), {:inet.ip_address(), :inet.port_number()} | nil) ::
  :ok
```

Clears TURN relay send routing.

With `peer`, clears only that remote on a shared Listener socket.

# `clear_tcp_send`

```elixir
@spec clear_tcp_send(socket(), {:inet.ip_address(), :inet.port_number()} | nil) :: :ok
```

Clears ICE-TCP send routing so `send/4` uses UDP (or TURN) again.

With `peer`, clears only that remote on a shared Listener socket.

# `close`

```elixir
@spec close(socket()) :: :ok
```

No-op close for `:ssl` transport callbacks (Listener owns the UDP socket).

# `controlling_process`

```elixir
@spec controlling_process(socket(), pid()) :: :ok
```

Sets the process that receives injected `{:udp, socket, ip, port, data}` messages.

Flushes any packets queued before an owner was set. Ignored owners (see
`ignore_owner/1`) leave the previous owner unchanged.

# `getopts`

```elixir
@spec getopts(socket(), [atom()]) :: {:ok, keyword()} | {:error, term()}
```

Returns inet options for the underlying UDP socket.

# `ignore_owner`

```elixir
@spec ignore_owner(pid()) :: :ok
```

Marks `pid` so later `controlling_process/2` calls do not make it the DTLS UDP owner.

Used before `:ssl.controlling_process(ssl_sock, peer)` so Peer receives `{:ssl, ...}`
without stealing post-handshake DTLS injects needed for SCTP.

# `inject`

```elixir
@spec inject(socket(), :inet.ip_address(), :inet.port_number(), binary()) :: :ok
```

Delivers an inbound DTLS datagram to the current owner, or queues until one is set.

# `open`

```elixir
@spec open(
  :inet.port_number(),
  keyword()
) :: {:ok, socket()} | {:error, term()}
```

Opens a UDP socket suitable for `:ssl` DTLS `cb_info` (binary, inactive).

When the process dictionary has `{__MODULE__, :listener}`, registers the
socket in `:persistent_term` for inject/send routing.

# `peername`

```elixir
@spec peername(socket()) :: {:ok, {:inet.ip_address(), :inet.port_number()}}
```

Peer address for `:ssl` callbacks.

When TURN relay send is armed, returns the remote ICE peer for this
association (or the singleton); otherwise a placeholder (host DTLS over
shared UDP is connectionless from OTP's view).

# `port`

```elixir
@spec port(socket()) :: :inet.port_number()
```

Local UDP port from `sockname/1`.

# `reset_owner`

```elixir
@spec reset_owner(socket()) :: :ok
```

Restores the DTLS UDP owner to the packet demux (or clears it) and drops pending.

Keeps `listener` / `relay_send` / `demux` entries. Used when restarting ICE/DTLS
on a shared Listener so inbound injects still reach OTP's demux.

# `send`

```elixir
@spec send(socket(), :inet.ip_address(), :inet.port_number(), iodata()) ::
  :ok | {:error, term()}
```

Sends a DTLS datagram toward `ip`/`port`.

When ICE-TCP or TURN relay send is armed, forwards via framed TCP or TURN
instead of a direct UDP send.

# `set_inject_owner`

```elixir
@spec set_inject_owner(
  socket(),
  {:inet.ip_address(), :inet.port_number()},
  pid() | nil
) :: :ok
```

Binds DTLS injects from `remote` to the current demux/owner (or `owner` if given).

Used when a Peer arms DTLS for a selected ICE remote on a shared Listener socket.

# `set_relay_send`

```elixir
@spec set_relay_send(
  socket(),
  map(),
  {:inet.ip_address(), :inet.port_number()},
  keyword()
) :: :ok
```

Routes subsequent `send/4` datagrams through TURN toward `peer`.

When `channel:` is set (0x4000..0x7FFF), uses ChannelData; otherwise Send Indication.
Used after ICE nominates a relay local so DTLS appears to come from the relayed
address (browser selected the relay path).

# `set_tcp_send`

```elixir
@spec set_tcp_send(socket(), port(), {:inet.ip_address(), :inet.port_number()}) :: :ok
```

Routes subsequent DTLS `send/4` datagrams as ICE-TCP framed payloads on `tcp_sock`.

OTP cannot DTLS-upgrade a `gen_tcp` socket (`{:dtls_upgrade, :notsup}`); ICE-TCP
therefore reuses the UDP DTLS stack and forwards records over the nominated TCP
connection with RFC 4571 framing.

# `setopts`

```elixir
@spec setopts(
  socket(),
  keyword()
) :: :ok | {:error, term()}
```

Applies inet options, ignoring `:active` / `:deliver` (Listener owns recv mode).

# `sockname`

```elixir
@spec sockname(socket()) ::
  {:ok, {:inet.ip_address(), :inet.port_number()}} | {:error, term()}
```

Local sockname via the owning Listener GenServer.

---

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