From 16f95d6e436b23568939783d788d2ca612202afa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Wala?= Date: Fri, 17 Nov 2023 17:27:11 +0100 Subject: [PATCH] Add general `DTLSTransport` tests --- lib/ex_webrtc/dtls_transport.ex | 14 +-- test/ex_webrtc/dtls_transport_test.exs | 132 +++++++++++++++++++++++++ 2 files changed, 139 insertions(+), 7 deletions(-) create mode 100644 test/ex_webrtc/dtls_transport_test.exs diff --git a/lib/ex_webrtc/dtls_transport.ex b/lib/ex_webrtc/dtls_transport.ex index 9a2165f4..9734005a 100644 --- a/lib/ex_webrtc/dtls_transport.ex +++ b/lib/ex_webrtc/dtls_transport.ex @@ -13,8 +13,8 @@ defmodule ExWebRTC.DTLSTransport do @doc false @spec start_link(ExICE.ICEAgent.opts(), GenServer.server()) :: GenServer.on_start() - def start_link(ice_config, peer_connection \\ self()) do - GenServer.start_link(__MODULE__, [ice_config, peer_connection]) + def start_link(ice_config, ice_module \\ ICEAgent) do + GenServer.start_link(__MODULE__, [ice_config, ice_module, self()]) end @doc false @@ -42,18 +42,18 @@ defmodule ExWebRTC.DTLSTransport do end @impl true - def init([ice_config, peer_connection]) do + def init([ice_config, ice_module, owner]) do # temporary hack to generate certs dtls = ExDTLS.init(client_mode: true, dtls_srtp: true) cert = ExDTLS.get_cert(dtls) pkey = ExDTLS.get_pkey(dtls) fingerprint = ExDTLS.get_cert_fingerprint(dtls) - {:ok, ice_agent} = ICEAgent.start_link(:controlled, ice_config) + {:ok, ice_agent} = ice_module.start_link(:controlled, ice_config) srtp = ExLibSRTP.new() state = %{ - peer_connection: peer_connection, + owner: owner, ice_agent: ice_agent, ice_state: nil, buffered_packets: nil, @@ -146,7 +146,7 @@ defmodule ExWebRTC.DTLSTransport do # forward everything, except for data, to peer connection process case msg do {:data, _data} -> :ok - _other -> send(state.peer_connection, ice_msg) + _other -> send(state.owner, ice_msg) end {:noreply, state} @@ -196,7 +196,7 @@ defmodule ExWebRTC.DTLSTransport do case ExLibSRTP.unprotect(state.srtp, data) do {:ok, payload} -> # TODO: temporarily, everything goes to peer connection process - send(state.peer_connection, {:rtp_data, payload}) + send(state.owner, {:rtp_data, payload}) {:error, reason} -> Logger.warning("Failed to decrypt SRTP, reason: #{inspect(reason)}") diff --git a/test/ex_webrtc/dtls_transport_test.exs b/test/ex_webrtc/dtls_transport_test.exs new file mode 100644 index 00000000..d2c8dbb9 --- /dev/null +++ b/test/ex_webrtc/dtls_transport_test.exs @@ -0,0 +1,132 @@ +defmodule ExWebRTC.DTLSTransportTest do + use ExUnit.Case, async: true + + alias ExWebRTC.DTLSTransport + + defmodule FakeICEAgent do + use GenServer + + def start_link(_mode, config) do + GenServer.start_link(__MODULE__, config) + end + + def send_data(ice_agent, data) do + GenServer.cast(ice_agent, {:send_data, data}) + end + + @impl true + def init(tester: tester), do: {:ok, tester} + + @impl true + def handle_cast({:send_data, data}, tester) do + send(tester, {:fake_ice, data}) + {:noreply, tester} + end + end + + setup do + assert {:ok, dtls} = DTLSTransport.start_link([tester: self()], FakeICEAgent) + + %{dtls: dtls} + end + + test "forwards non-data ICE messages", %{dtls: dtls} do + message = "test message" + + send_ice(dtls, message) + assert_receive {:ex_ice, _from, ^message} + + send_ice(dtls, {:data, <<1, 2, 3>>}) + refute_receive {:ex_ice, _from, _msg} + end + + test "cannot send data when handshake not finished", %{dtls: dtls} do + DTLSTransport.send_data(dtls, <<1, 2, 3>>) + + refute_receive {:fake_ice, _data} + end + + test "cannot start dtls more than once", %{dtls: dtls} do + assert :ok = DTLSTransport.start_dtls(dtls, :passive) + assert {:error, :already_started} = DTLSTransport.start_dtls(dtls, :passive) + end + + test "initiates DTLS handshake when in active mode", %{dtls: dtls} do + :ok = DTLSTransport.start_dtls(dtls, :active) + + send_ice(dtls, :connected) + + assert_receive {:fake_ice, packets} + assert is_binary(packets) + end + + test "won't initiate DTLS handshake when in passive mode", %{dtls: dtls} do + :ok = DTLSTransport.start_dtls(dtls, :passive) + + send_ice(dtls, :connected) + + refute_receive({:fake_ice, _msg}) + end + + test "will retransmit after initiating handshake", %{dtls: dtls} do + :ok = DTLSTransport.start_dtls(dtls, :active) + + send_ice(dtls, :connected) + + assert_receive {:fake_ice, _packets} + assert_receive {:fake_ice, _retransmited}, 1200 + end + + test "will buffer packets and send when connected", %{dtls: dtls} do + :ok = DTLSTransport.start_dtls(dtls, :passive) + + remote_dtls = ExDTLS.init(client_mode: true, dtls_srtp: true) + {packets, _timeout} = ExDTLS.do_handshake(remote_dtls) + + send_ice(dtls, {:data, packets}) + refute_receive {:fake_ice, _packets} + + send_ice(dtls, :connected) + assert_receive {:fake_ice, packets} + assert is_binary(packets) + end + + test "finishes handshake in actice mode", %{dtls: dtls} do + :ok = DTLSTransport.start_dtls(dtls, :active) + remote_dtls = ExDTLS.init(client_mode: false, dtls_srtp: true) + + send_ice(dtls, :connected) + + assert :ok = check_handshake(dtls, remote_dtls) + end + + test "finishes handshake in passive mode", %{dtls: dtls} do + :ok = DTLSTransport.start_dtls(dtls, :passive) + send_ice(dtls, :connected) + + remote_dtls = ExDTLS.init(client_mode: true, dtls_srtp: true) + {packets, _timeout} = ExDTLS.do_handshake(remote_dtls) + send_ice(dtls, {:data, packets}) + + assert :ok == check_handshake(dtls, remote_dtls) + end + + defp check_handshake(dtls, remote_dtls) do + assert_receive {:fake_ice, packets} + + case ExDTLS.handle_data(remote_dtls, packets) do + {:handshake_packets, packets, _timeout} -> + send_ice(dtls, {:data, packets}) + check_handshake(dtls, remote_dtls) + + {:handshake_finished, _, _, _, packets} -> + send_ice(dtls, {:data, packets}) + :ok + + {:handshake_finished, _, _, _} -> + :ok + end + end + + defp send_ice(dtls, msg), do: send(dtls, {:ex_ice, "dummy_pid", msg}) +end