defmodule Membrane.RTCP.SdesPacket do @moduledoc """ Parses Source Description (SDES) RTCP Packets defined in [RFC3550](https://tools.ietf.org/html/rfc3550#section-6.5) """ alias Membrane.RTCP.Packet defmodule Chunk do @moduledoc false @type t :: %__MODULE__{ cname: String.t() | nil, name: String.t() | nil, email: String.t() | nil, phone: String.t() | nil, loc: String.t() | nil, tool: String.t() | nil, note: String.t() | nil, priv: {String.t(), String.t()} | nil } defstruct [ :cname, :name, :email, :phone, :loc, :tool, :note, :priv ] end defstruct chunks: [] @type t() :: %__MODULE__{ chunks: %{ required(Membrane.RTP.ssrc_t()) => Keyword.t() } } @packet_type 202 @sdes_type_atom %{ 1 => :cname, 2 => :name, 3 => :email, 4 => :phone, 5 => :loc, 6 => :tool, 7 => :note, 8 => :priv } @sdes_type_from_atom %{ :cname => 1, :name => 2, :email => 3, :phone => 4, :loc => 5, :tool => 6, :note => 7, :priv => 8 } @behaviour Packet @impl true def decode(packet, ssrc_count) do with {:ok, chunks} <- parse_chunks(packet, []), true <- ssrc_count == length(chunks) do chunks = chunks |> Enum.into(%{}, fn {ssrc, items} -> {ssrc, struct(__MODULE__.Chunk, items)} end) {:ok, %__MODULE__{chunks: chunks}} else {:error, _reason} = err -> err false -> {:error, :invalid_ssrc_count} end end defp parse_chunks(<<>>, acc), do: {:ok, acc} defp parse_chunks(<>, acc) do with {:ok, items, rest} <- parse_items(rest) do parse_chunks(rest, [{ssrc, items} | acc]) end end defp parse_items(sdes_item, acc \\ []) # null item denoting end of the list defp parse_items(<<0::8, rest::binary>>, acc) do # skip padding unitil next 32-bit boundary to_skip = rest |> bit_size |> rem(32) <<_skipped::size(to_skip), next_chunk::binary>> = rest {:ok, acc, next_chunk} end # PRIV item defp parse_items(<<8::8, length::8, content::binary-size(length), rest::binary>>, acc) do <> = content item = {@sdes_type_atom[8], {prefix, value}} parse_items(rest, [item | acc]) end defp parse_items(<>, acc) when si_type in 0..7 do item = {@sdes_type_atom[si_type], value} parse_items(rest, [item | acc]) end defp parse_items(<<_si_type::8, _::binary>>, _acc) do {:error, :unknown_si_type} end @impl true def encode(%__MODULE__{chunks: chunks}) do chunk_list = chunks |> Enum.to_list() body = chunk_list |> Enum.map_join(fn {ssrc, chunk} -> <> end) {body, @packet_type, length(chunk_list)} end @spec encode_chunk(chunk :: %__MODULE__.Chunk{}) :: binary() defp encode_chunk(chunk) do body = chunk |> Map.from_struct() |> Enum.map_join(fn {_key, nil} -> <<>> {:priv, {prefix, value}} -> prefix_len = byte_size(prefix) total_len = 1 + prefix_len + byte_size(value) <<8::8, total_len::8, prefix_len::8, prefix::binary(), value::binary()>> {key, value} -> <<@sdes_type_from_atom[key]::8, byte_size(value)::8, value::binary()>> end) pad_bits = 32 - (body |> bit_size() |> rem(32)) end_marker = <<0::size(pad_bits)>> body <> end_marker end end