From 01868f536bf7cdcb3fa2ea84b517f076ad653d68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20=C5=9Aled=C5=BA?= Date: Mon, 16 Dec 2024 14:19:50 +0100 Subject: [PATCH] Add PeerConneciton.set_packet_loss --- lib/ex_webrtc/dtls_transport.ex | 29 ++++++++++++++-- lib/ex_webrtc/peer_connection.ex | 16 +++++++++ test/ex_webrtc/dtls_transport_test.exs | 46 +++++++++++++++++++++++--- 3 files changed, 83 insertions(+), 8 deletions(-) diff --git a/lib/ex_webrtc/dtls_transport.ex b/lib/ex_webrtc/dtls_transport.ex index 3ce5f86..b4ae68c 100644 --- a/lib/ex_webrtc/dtls_transport.ex +++ b/lib/ex_webrtc/dtls_transport.ex @@ -96,6 +96,11 @@ defmodule ExWebRTC.DTLSTransport do GenServer.cast(dtls_transport, {:send_data, data}) end + @spec set_packet_loss(dtls_transport(), 0..100) :: :ok + def set_packet_loss(dtls_transport, packet_loss) do + GenServer.cast(dtls_transport, {:set_packet_loss, packet_loss}) + end + @spec stop(dtls_transport()) :: :ok def stop(dtls_transport) do GenServer.stop(dtls_transport) @@ -127,7 +132,8 @@ defmodule ExWebRTC.DTLSTransport do peer_fingerprint: nil, dtls_state: :new, dtls: nil, - mode: nil + mode: nil, + packet_loss: 0 } notify(state.owner, {:state_change, :new}) @@ -236,8 +242,19 @@ defmodule ExWebRTC.DTLSTransport do @impl true def handle_cast({:send_rtp, data}, %{dtls_state: :connected, ice_connected: true} = state) do case ExLibSRTP.protect(state.out_srtp, data) do - {:ok, protected} -> state.ice_transport.send_data(state.ice_pid, protected) - {:error, reason} -> Logger.warning("Unable to protect RTP: #{inspect(reason)}") + {:ok, protected} -> + case state.packet_loss do + 0 -> + state.ice_transport.send_data(state.ice_pid, protected) + + _ -> + if Enum.random(1..100) > state.packet_loss do + state.ice_transport.send_data(state.ice_pid, protected) + end + end + + {:error, reason} -> + Logger.warning("Unable to protect RTP: #{inspect(reason)}") end {:noreply, state} @@ -269,6 +286,12 @@ defmodule ExWebRTC.DTLSTransport do {:noreply, state} end + @impl true + def handle_cast({:set_packet_loss, value}, state) do + state = %{state | packet_loss: value} + {:noreply, state} + end + @impl true def handle_info(:dtls_timeout, %{buffered_local_packets: buffered_local_packets} = state) do case ExDTLS.handle_timeout(state.dtls) do diff --git a/lib/ex_webrtc/peer_connection.ex b/lib/ex_webrtc/peer_connection.ex index 0c12b4d..b2c1c20 100644 --- a/lib/ex_webrtc/peer_connection.ex +++ b/lib/ex_webrtc/peer_connection.ex @@ -181,6 +181,16 @@ defmodule ExWebRTC.PeerConnection do GenServer.cast(peer_connection, {:send_data, channel_ref, data}) end + @doc """ + Sets very simple packet loss. + + Can be used for experimental purposes. + """ + @spec set_packet_loss(peer_connection(), 0..100) :: :ok + def set_packet_loss(peer_connection, value) when value in 0..100 do + GenServer.cast(peer_connection, {:set_packet_loss, value}) + end + #### MDN-API #### @doc """ @@ -1162,6 +1172,12 @@ defmodule ExWebRTC.PeerConnection do {:noreply, %{state | sctp_transport: sctp_transport}} end + @impl true + def handle_cast({:set_packet_loss, packet_loss}, state) do + DTLSTransport.set_packet_loss(state.dtls_transport, packet_loss) + {:noreply, state} + end + @impl true def handle_info({:ex_ice, _from, {:connection_state_change, new_ice_state}}, state) do state = %{state | ice_state: new_ice_state} diff --git a/test/ex_webrtc/dtls_transport_test.exs b/test/ex_webrtc/dtls_transport_test.exs index 5d473f2..69d9073 100644 --- a/test/ex_webrtc/dtls_transport_test.exs +++ b/test/ex_webrtc/dtls_transport_test.exs @@ -9,6 +9,10 @@ defmodule ExWebRTC.DTLSTransportTest do |> ExDTLS.get_cert_fingerprint() |> Utils.hex_dump() + @rtp_header <<1::1, 0::1, 0::1, 0::1, 0::4, 0::1, 96::7, 1::16, 1::32, 1::32>> + @rtp_payload <<0>> + @rtp_packet <<@rtp_header::binary, @rtp_payload::binary>> + defmodule MockICETransport do @behaviour ExWebRTC.ICETransport @@ -175,6 +179,10 @@ defmodule ExWebRTC.DTLSTransportTest do assert :ok = check_handshake(dtls, ice_transport, ice_pid, remote_dtls) assert_receive {:dtls_transport, ^dtls, {:state_change, :connecting}} assert_receive {:dtls_transport, ^dtls, {:state_change, :connected}} + + # assert we can send rtp packets + assert :ok = DTLSTransport.send_rtp(dtls, @rtp_packet) + assert_receive {:mock_ice, <<@rtp_header::binary, _payload::binary>>} end test "finishes handshake in passive mode", %{ @@ -200,6 +208,39 @@ defmodule ExWebRTC.DTLSTransportTest do assert :ok == check_handshake(dtls, ice_transport, ice_pid, remote_dtls) assert_receive {:dtls_transport, ^dtls, {:state_change, :connecting}} assert_receive {:dtls_transport, ^dtls, {:state_change, :connected}} + + # assert we can send rtp packets + assert :ok = DTLSTransport.send_rtp(dtls, @rtp_packet) + assert_receive {:mock_ice, <<@rtp_header::binary, _payload::binary>>} + end + + test "drops packets when packet loss is set", %{ + dtls: dtls, + ice_transport: ice_transport, + ice_pid: ice_pid + } do + :ok = DTLSTransport.start_dtls(dtls, :active, @fingerprint) + remote_dtls = ExDTLS.init(mode: :server, dtls_srtp: true) + + :ok = DTLSTransport.set_ice_connected(dtls) + + assert :ok = check_handshake(dtls, ice_transport, ice_pid, remote_dtls) + assert_receive {:dtls_transport, ^dtls, {:state_change, :connecting}} + assert_receive {:dtls_transport, ^dtls, {:state_change, :connected}} + + # assert we can send data + DTLSTransport.send_rtp(dtls, @rtp_packet) + assert_receive {:mock_ice, <<@rtp_header::binary, _payload::binary>>} + + # now set packet-loss + DTLSTransport.set_packet_loss(dtls, 100) + DTLSTransport.send_rtp(dtls, @rtp_packet) + refute_receive {:mock_ice, _rtp_packet} + end + + test "stop/1", %{dtls: dtls} do + assert :ok == DTLSTransport.stop(dtls) + assert false == Process.alive?(dtls) end defp check_handshake(dtls, ice_transport, ice_pid, remote_dtls) do @@ -218,9 +259,4 @@ defmodule ExWebRTC.DTLSTransportTest do :ok end end - - test "stop/1", %{dtls: dtls} do - assert :ok == DTLSTransport.stop(dtls) - assert false == Process.alive?(dtls) - end end