Skip to content

Commit

Permalink
Add Generic NACK feedback packet type
Browse files Browse the repository at this point in the history
  • Loading branch information
LVala committed Mar 8, 2024
1 parent fa56038 commit 1d5ba51
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 0 deletions.
2 changes: 2 additions & 0 deletions lib/ex_rtcp/packet.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ defmodule ExRTCP.Packet do
| __MODULE__.ReceiverReport.t()
| __MODULE__.SourceDescription.t()
| __MODULE__.Goodbye.t()
| __MODULE__.TransportFeedback.NACK.t()
| __MODULE__.TransportFeedback.CC.t()

@typedoc """
Expand Down Expand Up @@ -87,6 +88,7 @@ defmodule ExRTCP.Packet do
defp get_type_module(201, _count), do: {:ok, __MODULE__.ReceiverReport}
defp get_type_module(202, _count), do: {:ok, __MODULE__.SourceDescription}
defp get_type_module(203, _count), do: {:ok, __MODULE__.Goodbye}
defp get_type_module(205, 1), do: {:ok, __MODULE__.TransportFeedback.NACK}
defp get_type_module(205, 15), do: {:ok, __MODULE__.TransportFeedback.CC}
defp get_type_module(_type, _count), do: {:error, :unknown_type}
end
83 changes: 83 additions & 0 deletions lib/ex_rtcp/packet/transport_feedback/nack.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
defmodule ExRTCP.Packet.TransportFeedback.NACK do
@moduledoc """
Transport layer feedback message with generic NACKs
packet type (`RFC 4585`, sec. 6.2.1).
"""

alias ExRTCP.Packet

@behaviour ExRTCP.PacketTranscoder

@packet_type 205
@feedback_type 1

@typedoc """
Generic NACK message, as described in `RFC 4585`, sec. 6.2.1.
`blp` must be a 16-bit binary.
"""
@type generic_nack() :: %{
pid: Packet.uint16(),
blp: binary()
}

@typedoc """
Struct representing Transport layer NACK feedback RTCP message.
"""
@type t() :: %__MODULE__{
sender_ssrc: Packet.uint32(),
media_ssrc: Packet.uint32(),
nacks: [generic_nack()]
}

@enforce_keys [:sender_ssrc, :media_ssrc, :nacks]
defstruct @enforce_keys

@impl true
def encode(packet) do
%__MODULE__{
sender_ssrc: sender_ssrc,
media_ssrc: media_ssrc,
nacks: nacks
} = packet

encoded_nacks = encode_nacks(nacks)
encoded = <<sender_ssrc::32, media_ssrc::32, encoded_nacks::binary>>

{encoded, @feedback_type, @packet_type}
end

defp encode_nacks(nacks, acc \\ <<>>)
defp encode_nacks([], acc), do: acc

defp encode_nacks([nack | nacks], acc) do
%{pid: pid, blp: blp} = nack
encoded = <<pid::16, blp::binary-size(2)>>

encode_nacks(nacks, acc <> encoded)
end

@impl true
def decode(<<sender_ssrc::32, media_ssrc::32, rest::binary>>, _count) do
with {:ok, nacks} <- decode_nacks(rest) do
packet = %__MODULE__{
sender_ssrc: sender_ssrc,
media_ssrc: media_ssrc,
nacks: nacks
}

{:ok, packet}
end
end

def decode(_raw, _count), do: {:error, :invalid_packet}

defp decode_nacks(raw, acc \\ [])
defp decode_nacks(<<>>, acc), do: {:ok, Enum.reverse(acc)}

defp decode_nacks(<<pid::16, blp::binary-size(2), rest::binary>>, acc) do
nack = %{pid: pid, blp: blp}
decode_nacks(rest, [nack | acc])
end

defp decode_nacks(_raw, _acc), do: {:error, :invalid_packet}
end
60 changes: 60 additions & 0 deletions test/ex_rtcp/packet/transport_feedback/nack_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
defmodule ExRTCP.Packet.TransportFeedback.NACKTest do
use ExUnit.Case, async: true

alias ExRTCP.Packet.TransportFeedback.NACK

@sender_ssrc 123_321
@media_ssrc 112_231

test "encode/1" do
pid_1 = 35_042
blp_1 = <<5, 1>>
pid_2 = 21_042
blp_2 = <<14, 81>>

packet = %NACK{
sender_ssrc: @sender_ssrc,
media_ssrc: @media_ssrc,
nacks: [%{pid: pid_1, blp: blp_1}, %{pid: pid_2, blp: blp_2}]
}

assert {encoded, 1, 205} = NACK.encode(packet)

assert <<
@sender_ssrc::32,
@media_ssrc::32,
pid_1::16,
blp_1::binary-size(2),
pid_2::16,
blp_2::binary-size(2)
>> == encoded
end

describe "decode/2" do
test "valid packet" do
pid_1 = 35_043
blp_1 = <<2, 9>>
nack_1 = <<pid_1::16, blp_1::binary>>
pid_2 = 53_221
blp_2 = <<4, 5>>
nack_2 = <<pid_2::16, blp_2::binary>>

raw_packet = <<@sender_ssrc::32, @media_ssrc::32, nack_1::binary, nack_2::binary>>

assert {:ok, packet} = NACK.decode(raw_packet, 15)

assert %NACK{
sender_ssrc: @sender_ssrc,
media_ssrc: @media_ssrc,
nacks: [%{pid: ^pid_1, blp: ^blp_1}, %{pid: ^pid_2, blp: ^blp_2}]
} = packet
end

test "invalid packet" do
# blp of first nack is cut after 8 bits
raw_packet = <<@sender_ssrc::32, @media_ssrc::32, 53::16, 0>>

assert {:error, :invalid_packet} = NACK.decode(raw_packet, 15)
end
end
end

0 comments on commit 1d5ba51

Please sign in to comment.