lib/membrane/rtcp/sender_report_packet.ex

defmodule Membrane.RTCP.SenderReportPacket do
  @moduledoc """
  Parses and constructs RTCP Sender Report defined in
  [RFC3550](https://tools.ietf.org/html/rfc3550#section-6.4.1)
  """

  @behaviour Membrane.RTCP.Packet

  alias Membrane.RTCP.ReportPacketBlock
  alias Membrane.RTP

  defstruct [:ssrc, :reports, :sender_info]

  @type sender_info_t :: %{
          wallclock_timestamp: Membrane.Time.t(),
          rtp_timestamp: non_neg_integer(),
          sender_packet_count: non_neg_integer(),
          sender_octet_count: non_neg_integer()
        }

  @type t :: %__MODULE__{
          ssrc: RTP.ssrc_t(),
          reports: [ReportPacketBlock.t()],
          sender_info: sender_info_t()
        }

  @impl true
  def encode(report) do
    sender_info = encode_sender_info(report.sender_info)
    blocks = report.reports |> Enum.map_join(&ReportPacketBlock.encode/1)

    reports_count = report.reports |> length()

    body = <<report.ssrc::32>> <> sender_info <> blocks

    {body, reports_count}
  end

  defp encode_sender_info(sender_info) do
    ntp_timestamp = Membrane.Time.to_ntp_timestamp(sender_info.wallclock_timestamp)

    <<ntp_timestamp::bitstring-size(64), sender_info.rtp_timestamp::32,
      sender_info.sender_packet_count::32, sender_info.sender_octet_count::32>>
  end

  @impl true
  def decode(
        <<ssrc::32, ntp_timestamp::bitstring-size(64), rtp_time::32, packet_count::32,
          octet_count::32, blocks::binary>>,
        reports_count
      ) do
    wallclock_ts = Membrane.Time.from_ntp_timestamp(ntp_timestamp)

    sender_info = %{
      wallclock_timestamp: wallclock_ts,
      rtp_timestamp: rtp_time,
      sender_packet_count: packet_count,
      sender_octet_count: octet_count
    }

    with {:ok, reports} <- ReportPacketBlock.decode(blocks),
         true <- reports_count == length(reports) do
      {:ok, %__MODULE__{ssrc: ssrc, reports: reports, sender_info: sender_info}}
    else
      false -> {:error, :invalid_reports_count}
      err -> err
    end
  end

  def decode(_packet, _reports_count) do
    {:error, :sr_too_short}
  end
end