From 79192f2a1d2b43546a524ff99ee95b32e8e02961 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20=C5=9Aled=C5=BA?= Date: Mon, 27 Nov 2023 10:57:05 +0100 Subject: [PATCH] Add PeerConnection state machine (#20) --- lib/ex_webrtc/dtls_transport.ex | 74 +++++++++++++------- lib/ex_webrtc/peer_connection.ex | 69 +++++++++++++++++-- mix.exs | 16 ++--- mix.lock | 12 +++- test/ex_webrtc/dtls_transport_test.exs | 17 +++-- test/ex_webrtc/peer_connection_test.exs | 90 +++++++++++++++++++++++++ 6 files changed, 234 insertions(+), 44 deletions(-) diff --git a/lib/ex_webrtc/dtls_transport.ex b/lib/ex_webrtc/dtls_transport.ex index 9734005a..34b83d8f 100644 --- a/lib/ex_webrtc/dtls_transport.ex +++ b/lib/ex_webrtc/dtls_transport.ex @@ -11,6 +11,24 @@ defmodule ExWebRTC.DTLSTransport do @type dtls_transport() :: GenServer.server() + # Messages sent by the DTLSTransport + @typedoc false + @type signal() :: {:dtls_transport, pid(), state_change() | rtp_data()} + + # Message sent when DTLSTransport changes its state + @typedoc false + @type state_change() :: {:state_change, dtls_state()} + + # Message sent when a new RTP packet arrives. + # Packet is decrypted. + @typedoc false + @type rtp_data() :: {:rtp_data, binary()} + + # Possible DTLSTransport states. + # For the exact meaning, refer to the [WebRTC W3C, sec. 5.5.1](https://www.w3.org/TR/webrtc/#rtcdtlstransportstate-enum) + @typedoc false + @type dtls_state() :: :new | :connecting | :connected | :closed | :failed + @doc false @spec start_link(ExICE.ICEAgent.opts(), GenServer.server()) :: GenServer.on_start() def start_link(ice_config, ice_module \\ ICEAgent) do @@ -66,6 +84,8 @@ defmodule ExWebRTC.DTLSTransport do mode: nil } + notify(state.owner, {:state_change, :new}) + {:ok, state} end @@ -163,7 +183,7 @@ defmodule ExWebRTC.DTLSTransport do {:handshake_packets, packets, timeout} when state.ice_state in [:connected, :completed] -> :ok = ICEAgent.send_data(state.ice_agent, packets) Process.send_after(self(), :dtls_timeout, timeout) - %{state | dtls_state: :connecting} + update_dtls_state(state, :connecting) {:handshake_packets, packets, timeout} -> Logger.debug(""" @@ -172,19 +192,20 @@ defmodule ExWebRTC.DTLSTransport do """) Process.send_after(self(), :dtls_timeout, timeout) - %{state | dtls_state: :connecting, buffered_packets: packets} + state = %{state | buffered_packets: packets} + update_dtls_state(state, :connecting) {:handshake_finished, _, remote_keying_material, profile, packets} -> Logger.debug("DTLS handshake finished") ICEAgent.send_data(state.ice_agent, packets) # TODO: validate fingerprint state = setup_srtp(state, remote_keying_material, profile) - %{state | dtls_state: :connected} + update_dtls_state(state, :connected) {:handshake_finished, _, remote_keying_material, profile} -> Logger.debug("DTLS handshake finished") state = setup_srtp(state, remote_keying_material, profile) - %{state | dtls_state: :connected} + update_dtls_state(state, :connected) :handshake_want_read -> state @@ -196,7 +217,7 @@ defmodule ExWebRTC.DTLSTransport do case ExLibSRTP.unprotect(state.srtp, data) do {:ok, payload} -> # TODO: temporarily, everything goes to peer connection process - send(state.owner, {:rtp_data, payload}) + notify(state.owner, {:rtp_data, payload}) {:error, reason} -> Logger.warning("Failed to decrypt SRTP, reason: #{inspect(reason)}") @@ -213,35 +234,33 @@ defmodule ExWebRTC.DTLSTransport do state end - # I hope ExICE will be refactord so new state is a tuple - defp handle_ice(new_state, %{dtls_state: :new} = state) - when new_state in [:connected, :completed] do - state = - if state.mode == :active do - {packets, timeout} = ExDTLS.do_handshake(state.dtls) - Process.send_after(self(), :dtls_timeout, timeout) - :ok = ICEAgent.send_data(state.ice_agent, packets) - %{state | dtls_state: :connecting} - else - state - end + defp handle_ice({:connection_state_change, new_ice_state}, %{dtls_state: :new} = state) + when new_ice_state in [:connected, :completed] do + state = %{state | ice_state: new_ice_state} - %{state | ice_state: new_state} + if state.mode == :active do + {packets, timeout} = ExDTLS.do_handshake(state.dtls) + Process.send_after(self(), :dtls_timeout, timeout) + :ok = ICEAgent.send_data(state.ice_agent, packets) + update_dtls_state(state, :connecting) + else + state + end end - defp handle_ice(new_state, state) - when new_state in [:connected, :completed] do + defp handle_ice({:connection_state_change, new_ice_state}, state) + when new_ice_state in [:connected, :completed] do if state.buffered_packets do Logger.debug("Sending buffered DTLS packets") :ok = ICEAgent.send_data(state.ice_agent, state.buffered_packets) - %{state | ice_state: new_state, buffered_packets: nil} + %{state | ice_state: new_ice_state, buffered_packets: nil} else state end end - defp handle_ice(new_state, state) when is_atom(new_state) do - %{state | ice_state: new_state} + defp handle_ice({:connection_state_change, new_ice_state}, state) do + %{state | ice_state: new_ice_state} end defp handle_ice(_msg, state), do: state @@ -260,4 +279,13 @@ defmodule ExWebRTC.DTLSTransport do :ok = ExLibSRTP.add_stream(state.srtp, policy) state end + + defp update_dtls_state(%{dtls_state: dtls_state} = state, dtls_state), do: state + + defp update_dtls_state(state, new_dtls_state) do + notify(state.owner, {:state_change, new_dtls_state}) + %{state | dtls_state: new_dtls_state} + end + + defp notify(dst, msg), do: send(dst, {:dtls_transport, self(), msg}) end diff --git a/lib/ex_webrtc/peer_connection.ex b/lib/ex_webrtc/peer_connection.ex index 9c0f127c..73fbacd5 100644 --- a/lib/ex_webrtc/peer_connection.ex +++ b/lib/ex_webrtc/peer_connection.ex @@ -31,6 +31,20 @@ defmodule ExWebRTC.PeerConnection do streams: [:TODO] ] + @typedoc """ + Messages sent by the ExWebRTC. + """ + @type signal() :: {:ex_webrtc, pid(), connection_state_change()} + + @type connection_state_change() :: {:connection_state_change, connection_state()} + + @typedoc """ + Possible PeerConnection states. + + For the exact meaning, refer to the [WebRTC W3C, section 4.3.3](https://www.w3.org/TR/webrtc/#rtcpeerconnectionstate-enum) + """ + @type connection_state() :: :closed | :failed | :disconnected | :new | :connecting | :connected + @enforce_keys [:config, :owner] defstruct @enforce_keys ++ [ @@ -39,11 +53,13 @@ defmodule ExWebRTC.PeerConnection do :current_remote_desc, :pending_remote_desc, :ice_agent, - :ice_state, :dtls_transport, demuxer: %Demuxer{}, transceivers: [], + ice_state: nil, + dtls_state: nil, signaling_state: :stable, + conn_state: :new, last_offer: nil, last_answer: nil ] @@ -119,9 +135,13 @@ defmodule ExWebRTC.PeerConnection do owner: owner, config: config, ice_agent: ice_agent, - dtls_transport: dtls_transport + dtls_transport: dtls_transport, + ice_state: :new, + dtls_state: :new } + notify(state.owner, {:connection_state_change, :new}) + {:ok, state} end @@ -327,8 +347,11 @@ defmodule ExWebRTC.PeerConnection do end @impl true - def handle_info({:ex_ice, _from, :connected}, state) do - {:noreply, %__MODULE__{state | ice_state: :connected}} + def handle_info({:ex_ice, _from, {:connection_state_change, new_ice_state}}, state) do + state = %__MODULE__{state | ice_state: new_ice_state} + next_conn_state = next_conn_state(new_ice_state, state.dtls_state) + state = update_conn_state(state, next_conn_state) + {:noreply, state} end @impl true @@ -346,7 +369,15 @@ defmodule ExWebRTC.PeerConnection do end @impl true - def handle_info({:rtp_data, data}, state) do + def handle_info({:dtls_transport, _pid, {:state_change, new_dtls_state}}, state) do + state = %__MODULE__{state | dtls_state: new_dtls_state} + next_conn_state = next_conn_state(state.ice_state, new_dtls_state) + state = update_conn_state(state, next_conn_state) + {:noreply, state} + end + + @impl true + def handle_info({:dtls_transport, _pid, {:rtp_data, data}}, state) do case Demuxer.demux(state.demuxer, data) do {:ok, demuxer, mid, packet} -> notify(state.owner, {:data, {mid, packet}}) @@ -514,6 +545,34 @@ defmodule ExWebRTC.PeerConnection do defp maybe_next_state(:have_remote_pranswer, :remote, :answer), do: {:ok, :stable} defp maybe_next_state(:have_remote_pranswer, _, _), do: {:error, :invalid_transition} + # TODO support :disconnected state - our ICE doesn't provide disconnected state for now + # TODO support :closed state + # the order of these clauses is important + defp next_conn_state(ice_state, dtls_state) + + defp next_conn_state(ice_state, dtls_state) when ice_state == :failed or dtls_state == :failed, + do: :failed + + defp next_conn_state(:failed, _dtls_state), do: :failed + + defp next_conn_state(_ice_state, :failed), do: :failed + + defp next_conn_state(:new, :new), do: :new + + defp next_conn_state(ice_state, dtls_state) + when ice_state in [:new, :checking] or dtls_state in [:new, :connecting], + do: :connecting + + defp next_conn_state(ice_state, :connected) when ice_state in [:connected, :completed], + do: :connected + + defp update_conn_state(%{conn_state: conn_state} = state, conn_state), do: state + + defp update_conn_state(state, new_conn_state) do + notify(state.owner, {:connection_state_change, new_conn_state}) + %{state | conn_state: new_conn_state} + end + defp set_description(:local, :answer, sdp, state) do # NOTICE: internaly, we don't create SessionDescription # as it would require serialization of sdp diff --git a/mix.exs b/mix.exs index 478fd53b..06d7795c 100644 --- a/mix.exs +++ b/mix.exs @@ -45,16 +45,16 @@ defmodule ExWebRTC.MixProject do defp deps do [ - {:ex_sdp, "~> 0.13"}, - {:ex_ice, "~> 0.1"}, - {:ex_dtls, "~> 0.14"}, - {:ex_libsrtp, "~> 0.6"}, - {:ex_rtp, "~> 0.2"}, - {:ex_rtcp, "~> 0.1"}, + {:ex_sdp, "~> 0.13.0"}, + {:ex_ice, "~> 0.2.0"}, + {:ex_dtls, "~> 0.14.0"}, + {:ex_libsrtp, "~> 0.6.0"}, + {:ex_rtp, "~> 0.2.0"}, + {:ex_rtcp, "~> 0.1.0"}, # dev/test - {:excoveralls, "~> 0.14", only: [:dev, :test], runtime: false}, - {:ex_doc, "~> 0.30", only: :dev, runtime: false}, + {:excoveralls, "~> 0.17.0", only: [:dev, :test], runtime: false}, + {:ex_doc, "~> 0.30.0", only: :dev, runtime: false}, {:credo, "~> 1.7", only: [:dev, :test], runtime: false}, {:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false} ] diff --git a/mix.lock b/mix.lock index 12c45908..7bfc9044 100644 --- a/mix.lock +++ b/mix.lock @@ -1,16 +1,17 @@ %{ "bunch": {:hex, :bunch, "1.6.0", "4775f8cdf5e801c06beed3913b0bd53fceec9d63380cdcccbda6be125a6cfd54", [:mix], [], "hexpm", "ef4e9abf83f0299d599daed3764d19e8eac5d27a5237e5e4d5e2c129cfeb9a22"}, "bunch_native": {:hex, :bunch_native, "0.5.0", "8ac1536789a597599c10b652e0b526d8833348c19e4739a0759a2bedfd924e63", [:mix], [{:bundlex, "~> 1.0", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "24190c760e32b23b36edeb2dc4852515c7c5b3b8675b1a864e0715bdd1c8f80d"}, - "bundlex": {:hex, :bundlex, "1.2.0", "a89869208a019376a38e8a10e1bd573dcbeae8addd381c2cd74e2817010bef8f", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:qex, "~> 0.5", [hex: :qex, repo: "hexpm", optional: false]}, {:req, "~> 0.4.0", [hex: :req, repo: "hexpm", optional: false]}, {:secure_random, "~> 0.5", [hex: :secure_random, repo: "hexpm", optional: false]}, {:zarex, "~> 1.0", [hex: :zarex, repo: "hexpm", optional: false]}], "hexpm", "d2182b91a2a53847baadf4745ad2291853e786ad28671f474a611e7703dbca9b"}, + "bundlex": {:hex, :bundlex, "1.3.1", "5791b4037df961f092eac9a51d8df91030a80381e442e580a3f4d82c9e5d34f0", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:qex, "~> 0.5", [hex: :qex, repo: "hexpm", optional: false]}, {:req, "~> 0.4.0", [hex: :req, repo: "hexpm", optional: false]}, {:secure_random, "~> 0.5", [hex: :secure_random, repo: "hexpm", optional: false]}, {:zarex, "~> 1.0", [hex: :zarex, repo: "hexpm", optional: false]}], "hexpm", "9651ddc7e627dd1bd0eed9aaaba3de8b4bbc06c10980089f7276cdb82bb3fc51"}, "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, "castore": {:hex, :castore, "1.0.4", "ff4d0fb2e6411c0479b1d965a814ea6d00e51eb2f58697446e9c41a97d940b28", [:mix], [], "hexpm", "9418c1b8144e11656f0be99943db4caf04612e3eaecefb5dae9a2a87565584f8"}, + "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, "credo": {:hex, :credo, "1.7.0", "6119bee47272e85995598ee04f2ebbed3e947678dee048d10b5feca139435f75", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "6839fcf63d1f0d1c0f450abc8564a57c43d644077ab96f2934563e68b8a769d7"}, "dialyxir": {:hex, :dialyxir, "1.4.1", "a22ed1e7bd3a3e3f197b68d806ef66acb61ee8f57b3ac85fc5d57354c5482a93", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "84b795d6d7796297cca5a3118444b80c7d94f7ce247d49886e7c291e1ae49801"}, "earmark_parser": {:hex, :earmark_parser, "1.4.34", "b0fbb4fd333ee7e9babc07e9573796850759cd12796fcf2fec59cf0031cbaad9", [:mix], [], "hexpm", "cc0d7a6f2367e4504867b4ec38ceee24e89ee6bca9c7b94a6d940f54aba2e8d5"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "ex_doc": {:hex, :ex_doc, "0.30.6", "5f8b54854b240a2b55c9734c4b1d0dd7bdd41f71a095d42a70445c03cf05a281", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "bd48f2ddacf4e482c727f9293d9498e0881597eae6ddc3d9562bd7923375109f"}, "ex_dtls": {:hex, :ex_dtls, "0.14.0", "f2e589a24396599551c6b142c3a6a7a55f7fa772c90716888ed42bf3c994cb2d", [:mix], [{:unifex, "~> 1.0", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "7fa79815d8dbcee3b1464c3590527fb85fae9592b6e092f2554c5974e2c5847a"}, - "ex_ice": {:hex, :ex_ice, "0.1.0", "2653c884872d8769cf9fc655c74002a63ed6c21be1b3c2badfa42bdc74de2355", [:mix], [{:ex_stun, "~> 0.1.0", [hex: :ex_stun, repo: "hexpm", optional: false]}], "hexpm", "e2539a321f87f31997ba974d532d00511e5828f2f113b550b1ef6aa799dd2ffe"}, + "ex_ice": {:hex, :ex_ice, "0.2.0", "6002de5b1c0561f8ff50238a6d04cb99eabf9b7db91031b6f0580d67888f0365", [:mix], [{:ex_stun, "~> 0.1.0", [hex: :ex_stun, repo: "hexpm", optional: false]}], "hexpm", "1def9449009241918b31ff269f344e5c4bd15257ba5cf95d1edc1250c28af39b"}, "ex_libsrtp": {:hex, :ex_libsrtp, "0.6.0", "d96cd7fc1780157614f0bf47d31587e5eab953b43067f4885849f8177ec452a9", [:mix], [{:bunch, "~> 1.3", [hex: :bunch, repo: "hexpm", optional: false]}, {:unifex, "~> 1.0", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "e9ce8a507a658f7e2df72fae82a4b3ba0a056c175f0bc490e79ab03058e094d5"}, "ex_rctp": {:git, "https://github.com/elixir-webrtc/ex_rtcp.git", "c0cf2b7f995e34d13cee4cbb228376a55700fb6a", []}, "ex_rtcp": {:hex, :ex_rtcp, "0.1.0", "2e02d23fc6ccc7e00aed13358ffdbcb23e34d3a7f35c66cadaa54447383ecae4", [:mix], [], "hexpm", "1c9a7e636f3950fbcefedce31f3e4ca60b84ea80ad519789f9d215167c60cb2b"}, @@ -20,21 +21,28 @@ "excoveralls": {:hex, :excoveralls, "0.17.1", "83fa7906ef23aa7fc8ad7ee469c357a63b1b3d55dd701ff5b9ce1f72442b2874", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "95bc6fda953e84c60f14da4a198880336205464e75383ec0f570180567985ae0"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, "finch": {:hex, :finch, "0.16.0", "40733f02c89f94a112518071c0a91fe86069560f5dbdb39f9150042f44dcfb1a", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f660174c4d519e5fec629016054d60edd822cdfe2b7270836739ac2f97735ec5"}, + "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"}, "hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"}, + "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, + "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"}, + "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, "mint": {:hex, :mint, "1.5.1", "8db5239e56738552d85af398798c80648db0e90f343c8469f6c6d8898944fb6f", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "4a63e1e76a7c3956abd2c72f370a0d0aecddc3976dea5c27eccbecfa5e7d5b1e"}, "nimble_options": {:hex, :nimble_options, "1.0.2", "92098a74df0072ff37d0c12ace58574d26880e522c22801437151a159392270e", [:mix], [], "hexpm", "fd12a8db2021036ce12a309f26f564ec367373265b53e25403f0ee697380f1b8"}, "nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"}, "nimble_pool": {:hex, :nimble_pool, "1.0.0", "5eb82705d138f4dd4423f69ceb19ac667b3b492ae570c9f5c900bb3d2f50a847", [:mix], [], "hexpm", "80be3b882d2d351882256087078e1b1952a28bf98d0a287be87e4a24a710b67a"}, + "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, "qex": {:hex, :qex, "0.5.1", "0d82c0f008551d24fffb99d97f8299afcb8ea9cf99582b770bd004ed5af63fd6", [:mix], [], "hexpm", "935a39fdaf2445834b95951456559e9dc2063d0a055742c558a99987b38d6bab"}, "req": {:hex, :req, "0.4.5", "2071bbedd280f107b9e33e1ddff2beb3991ec1ae06caa2cca2ab756393d8aca5", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.9", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "dd23e9c7303ddeb2dee09ff11ad8102cca019e38394456f265fb7b9655c64dd8"}, "secure_random": {:hex, :secure_random, "0.5.1", "c5532b37c89d175c328f5196a0c2a5680b15ebce3e654da37129a9fe40ebf51b", [:mix], [], "hexpm", "1b9754f15e3940a143baafd19da12293f100044df69ea12db5d72878312ae6ab"}, "shmex": {:hex, :shmex, "0.5.0", "7dc4fb1a8bd851085a652605d690bdd070628717864b442f53d3447326bcd3e8", [:mix], [{:bunch_native, "~> 0.5.0", [hex: :bunch_native, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.0", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "b67bb1e22734758397c84458dbb746519e28eac210423c267c7248e59fc97bdc"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, "unifex": {:hex, :unifex, "1.1.0", "26b1bcb6c3b3454e1ea15f85b2e570aaa5b5c609566aa9f5c2e0a8b213379d6b", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.0", [hex: :bundlex, repo: "hexpm", optional: false]}, {:shmex, "~> 0.5.0", [hex: :shmex, repo: "hexpm", optional: false]}], "hexpm", "d8f47e9e3240301f5b20eec5792d1d4341e1a3a268d94f7204703b48da4aaa06"}, "uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm", "c790593b4c3b601f5dc2378baae7efaf5b3d73c4c6456ba85759905be792f2ac"}, "zarex": {:hex, :zarex, "1.0.3", "a9e9527a1c31df7f39499819bd76ccb15b0b4e479eed5a4a40db9df7ad7db25c", [:mix], [], "hexpm", "4400a7d33bbf222383ce9a3d5ec9411798eb2b12e86c65ad8e6ac08d8116ca8b"}, diff --git a/test/ex_webrtc/dtls_transport_test.exs b/test/ex_webrtc/dtls_transport_test.exs index 9763fc9e..db8ab4fe 100644 --- a/test/ex_webrtc/dtls_transport_test.exs +++ b/test/ex_webrtc/dtls_transport_test.exs @@ -37,6 +37,7 @@ defmodule ExWebRTC.DTLSTransportTest do setup do assert {:ok, dtls} = DTLSTransport.start_link([tester: self()], FakeICEAgent) + assert_receive {:dtls_transport, ^dtls, {:state_change, :new}} ice = DTLSTransport.get_ice_agent(dtls) assert is_pid(ice) @@ -67,7 +68,7 @@ defmodule ExWebRTC.DTLSTransportTest do test "initiates DTLS handshake when in active mode", %{dtls: dtls, ice: ice} do :ok = DTLSTransport.start_dtls(dtls, :active) - FakeICEAgent.send_dtls(ice, :connected) + FakeICEAgent.send_dtls(ice, {:connection_state_change, :connected}) assert_receive {:fake_ice, packets} assert is_binary(packets) @@ -76,7 +77,7 @@ defmodule ExWebRTC.DTLSTransportTest do test "won't initiate DTLS handshake when in passive mode", %{dtls: dtls, ice: ice} do :ok = DTLSTransport.start_dtls(dtls, :passive) - FakeICEAgent.send_dtls(ice, :connected) + FakeICEAgent.send_dtls(ice, {:connection_state_change, :connected}) refute_receive({:fake_ice, _msg}) end @@ -84,7 +85,7 @@ defmodule ExWebRTC.DTLSTransportTest do test "will retransmit after initiating handshake", %{dtls: dtls, ice: ice} do :ok = DTLSTransport.start_dtls(dtls, :active) - FakeICEAgent.send_dtls(ice, :connected) + FakeICEAgent.send_dtls(ice, {:connection_state_change, :connected}) assert_receive {:fake_ice, _packets} assert_receive {:fake_ice, _retransmited}, 1100 @@ -99,7 +100,7 @@ defmodule ExWebRTC.DTLSTransportTest do FakeICEAgent.send_dtls(ice, {:data, packets}) refute_receive {:fake_ice, _packets} - FakeICEAgent.send_dtls(ice, :connected) + FakeICEAgent.send_dtls(ice, {:connection_state_change, :connected}) assert_receive {:fake_ice, packets} assert is_binary(packets) end @@ -108,20 +109,24 @@ defmodule ExWebRTC.DTLSTransportTest do :ok = DTLSTransport.start_dtls(dtls, :active) remote_dtls = ExDTLS.init(client_mode: false, dtls_srtp: true) - FakeICEAgent.send_dtls(ice, :connected) + FakeICEAgent.send_dtls(ice, {:connection_state_change, :connected}) assert :ok = check_handshake(dtls, ice, remote_dtls) + assert_receive {:dtls_transport, ^dtls, {:state_change, :connecting}} + assert_receive {:dtls_transport, ^dtls, {:state_change, :connected}} end test "finishes handshake in passive mode", %{dtls: dtls, ice: ice} do :ok = DTLSTransport.start_dtls(dtls, :passive) - FakeICEAgent.send_dtls(ice, :connected) + FakeICEAgent.send_dtls(ice, {:connection_state_change, :connected}) remote_dtls = ExDTLS.init(client_mode: true, dtls_srtp: true) {packets, _timeout} = ExDTLS.do_handshake(remote_dtls) FakeICEAgent.send_dtls(ice, {:data, packets}) assert :ok == check_handshake(dtls, ice, remote_dtls) + assert_receive {:dtls_transport, ^dtls, {:state_change, :connecting}} + assert_receive {:dtls_transport, ^dtls, {:state_change, :connected}} end defp check_handshake(dtls, ice, remote_dtls) do diff --git a/test/ex_webrtc/peer_connection_test.exs b/test/ex_webrtc/peer_connection_test.exs index dfdeb876..2f0aa21c 100644 --- a/test/ex_webrtc/peer_connection_test.exs +++ b/test/ex_webrtc/peer_connection_test.exs @@ -42,6 +42,96 @@ defmodule ExWebRTC.PeerConnectionTest do :ok = PeerConnection.set_remote_description(pc1, answer) end + test "connection state change" do + {:ok, pc1} = PeerConnection.start_link() + assert_receive {:ex_webrtc, ^pc1, {:connection_state_change, :new}} + {:ok, _} = PeerConnection.add_transceiver(pc1, :audio) + {:ok, offer} = PeerConnection.create_offer(pc1) + :ok = PeerConnection.set_local_description(pc1, offer) + + {:ok, pc2} = PeerConnection.start_link() + assert_receive {:ex_webrtc, ^pc2, {:connection_state_change, :new}} + :ok = PeerConnection.set_remote_description(pc2, offer) + {:ok, answer} = PeerConnection.create_answer(pc2) + :ok = PeerConnection.set_local_description(pc2, answer) + + :ok = PeerConnection.set_remote_description(pc1, answer) + + assert :ok == + check_connection_state_change( + pc1, + pc2, + %{ + connecting_recv: false, + connected_recv: false + }, + %{ + connecting_recv: false, + connected_recv: false + } + ) + end + + defp check_connection_state_change( + _pc1, + _pc2, + %{ + connecting_recv: true, + connected_recv: true + }, + %{ + connecting_recv: true, + connected_recv: true + } + ), + do: :ok + + defp check_connection_state_change(pc1, pc2, pc1_states, pc2_states) do + receive do + {:ex_webrtc, ^pc1, {:ice_candidate, cand}} -> + :ok = PeerConnection.add_ice_candidate(pc2, cand) + check_connection_state_change(pc1, pc2, pc1_states, pc2_states) + + {:ex_webrtc, ^pc2, {:ice_candidate, cand}} -> + :ok = PeerConnection.add_ice_candidate(pc1, cand) + check_connection_state_change(pc1, pc2, pc1_states, pc2_states) + + {:ex_webrtc, ^pc1, {:connection_state_change, :connecting}} + when pc1_states.connecting_recv == false and pc1_states.connected_recv == false -> + check_connection_state_change(pc1, pc2, %{pc1_states | connecting_recv: true}, pc2_states) + + {:ex_webrtc, ^pc1, {:connection_state_change, :connecting}} = msg -> + raise "Unexpectedly received: #{inspect(msg)}, when pc_states is: #{inspect(pc1_states)}" + + {:ex_webrtc, ^pc2, {:connection_state_change, :connecting}} + when pc2_states.connecting_recv == false and pc2_states.connected_recv == false -> + check_connection_state_change(pc1, pc2, pc1_states, %{pc2_states | connecting_recv: true}) + + {:ex_webrtc, ^pc2, {:connection_state_change, :connecting}} = msg -> + raise "Unexpectedly received: #{inspect(msg)}, when pc_states is: #{inspect(pc2_states)}" + + {:ex_webrtc, ^pc1, {:connection_state_change, :connected}} + when pc1_states.connecting_recv == true and pc1_states.connected_recv == false -> + check_connection_state_change(pc1, pc2, %{pc1_states | connected_recv: true}, pc2_states) + + {:ex_webrtc, ^pc1, {:connection_state_change, :connected}} = msg -> + raise "Unexpectedly received: #{inspect(msg)}, when pc_states is: #{inspect(pc1_states)}" + + {:ex_webrtc, ^pc2, {:connection_state_change, :connected}} + when pc2_states.connecting_recv == true and pc2_states.connected_recv == false -> + check_connection_state_change(pc1, pc2, pc1_states, %{pc2_states | connected_recv: true}) + + {:ex_webrtc, ^pc2, {:connection_state_change, :connected}} = msg -> + raise "Unexpectedly received: #{inspect(msg)}, when pc_states is: #{inspect(pc2_states)}" + + {:ex_webrtc, ^pc1, {:connection_state_change, _state}} = msg -> + raise "Unexpectedly received: #{inspect(msg)}, when pc_states is: #{inspect(pc1_states)}" + + {:ex_webrtc, ^pc2, {:connection_state_change, _state}} = msg -> + raise "Unexpectedly received: #{inspect(msg)}, when pc_states is: #{inspect(pc2_states)}" + end + end + describe "set_remote_description/2" do test "MID" do {:ok, pc} = PeerConnection.start_link()