diff --git a/lib/ex_webrtc/peer_connection.ex b/lib/ex_webrtc/peer_connection.ex index 884e3b8..495c597 100644 --- a/lib/ex_webrtc/peer_connection.ex +++ b/lib/ex_webrtc/peer_connection.ex @@ -1398,10 +1398,8 @@ defmodule ExWebRTC.PeerConnection do defp apply_remote_description(%SessionDescription{type: type, sdp: raw_sdp}, state) do with {:ok, next_sig_state} <- next_signaling_state(state.signaling_state, :remote, type), {:ok, sdp} <- parse_sdp(raw_sdp), - :ok <- SDPUtils.ensure_mid(sdp), - :ok <- SDPUtils.ensure_bundle(sdp), - :ok <- SDPUtils.ensure_rtcp_mux(sdp), - {:ok, {ice_ufrag, ice_pwd}} <- SDPUtils.get_ice_credentials(sdp), + :ok <- SDPUtils.ensure_valid(sdp), + {:ok, ice_creds} <- SDPUtils.get_ice_credentials(sdp), {:ok, {:fingerprint, {:sha256, peer_fingerprint}}} <- SDPUtils.get_cert_fingerprint(sdp), {:ok, dtls_role} <- SDPUtils.get_dtls_role(sdp) do config = Configuration.update(state.config, sdp) @@ -1420,11 +1418,19 @@ defmodule ExWebRTC.PeerConnection do dtls_role = if dtls_role in [:actpass, :passive], do: :active, else: :passive DTLSTransport.start_dtls(state.dtls_transport, dtls_role, peer_fingerprint) + # ice_creds will be nil if all of the mlines in the description are rejected + # in such case, if this is the first remote description, connection won't be established # TODO: this can result in ICE restart (when it should, e.g. when this is answer) - :ok = state.ice_transport.set_remote_credentials(state.ice_pid, ice_ufrag, ice_pwd) + case ice_creds do + nil -> + :ok + + {ice_ufrag, ice_pwd} -> + :ok = state.ice_transport.set_remote_credentials(state.ice_pid, ice_ufrag, ice_pwd) - for candidate <- SDPUtils.get_ice_candidates(sdp) do - state.ice_transport.add_remote_candidate(state.ice_pid, candidate) + for candidate <- SDPUtils.get_ice_candidates(sdp) do + state.ice_transport.add_remote_candidate(state.ice_pid, candidate) + end end state = diff --git a/lib/ex_webrtc/sdp_utils.ex b/lib/ex_webrtc/sdp_utils.ex index 1d0ddfd..1f1a5ad 100644 --- a/lib/ex_webrtc/sdp_utils.ex +++ b/lib/ex_webrtc/sdp_utils.ex @@ -8,8 +8,24 @@ defmodule ExWebRTC.SDPUtils do @type extension() :: {Extension.SourceDescription, atom()} - @spec ensure_mid(ExSDP.t()) :: :ok | {:error, :missing_mid | :duplicated_mid} - def ensure_mid(sdp) do + @spec ensure_valid(ExSDP.t()) :: :ok | {:error, term()} + def ensure_valid(sdp) do + with :ok <- ensure_non_empty(sdp), + :ok <- ensure_mid(sdp), + :ok <- ensure_bundle(sdp) do + ensure_rtcp_mux(sdp) + end + end + + defp ensure_non_empty(sdp) do + if Enum.empty?(sdp.media) do + {:error, :empty_sdp} + else + :ok + end + end + + defp ensure_mid(sdp) do sdp.media |> Enum.reduce_while({:ok, []}, fn media, {:ok, acc} -> case ExSDP.get_attributes(media, :mid) do @@ -24,14 +40,7 @@ defmodule ExWebRTC.SDPUtils do end end - @spec ensure_bundle(ExSDP.t()) :: - :ok - | {:error, - :non_exhaustive_bundle_group - | :missing_bundle_group - | :multiple_bundle_groups - | :invalid_bundle_group} - def ensure_bundle(sdp) do + defp ensure_bundle(sdp) do groups = ExSDP.get_attributes(sdp, ExSDP.Attribute.Group) mline_mids = get_bundle_mids(sdp.media) @@ -56,8 +65,7 @@ defmodule ExWebRTC.SDPUtils do Enum.filter(groups, fn %ExSDP.Attribute.Group{semantics: name} -> name == to_filter end) end - @spec ensure_rtcp_mux(ExSDP.t()) :: :ok | {:error, :missing_rtcp_mux} - def ensure_rtcp_mux(sdp) do + defp ensure_rtcp_mux(sdp) do sdp.media |> Enum.all?(&(ExSDP.get_attribute(&1, :rtcp_mux) == :rtcp_mux)) |> case do @@ -116,7 +124,7 @@ defmodule ExWebRTC.SDPUtils do end @spec get_ice_credentials(ExSDP.t()) :: - {:ok, {binary(), binary()}} + {:ok, {binary(), binary()} | nil} | {:error, :missing_ice_pwd | :missing_ice_ufrag @@ -124,12 +132,16 @@ defmodule ExWebRTC.SDPUtils do | :conflicting_ice_credentials} def get_ice_credentials(sdp) do session_creds = do_get_ice_credentials(sdp) - mline_creds = Enum.map(sdp.media, fn mline -> do_get_ice_credentials(mline) end) + + mline_creds = + sdp.media + |> Enum.reject(&rejected?/1) + |> Enum.map(&do_get_ice_credentials/1) case {session_creds, mline_creds} do # no session creds and no mlines (empty SDP) {{nil, nil}, []} -> - {:error, :missing_ice_credentials} + {:ok, nil} # session creds but no mlines (empty SDP) {session_creds, []} -> diff --git a/test/ex_webrtc/peer_connection_test.exs b/test/ex_webrtc/peer_connection_test.exs index c376fb0..6f31f2a 100644 --- a/test/ex_webrtc/peer_connection_test.exs +++ b/test/ex_webrtc/peer_connection_test.exs @@ -467,6 +467,56 @@ defmodule ExWebRTC.PeerConnectionTest do assert expected_result == PeerConnection.set_remote_description(pc, offer) end) end + + test "mline rejected" do + {:ok, pc} = PeerConnection.start_link() + + {:ok, _sender} = PeerConnection.add_track(pc, MediaStreamTrack.new(:video)) + {:ok, _sender} = PeerConnection.add_track(pc, MediaStreamTrack.new(:audio)) + {:ok, offer} = PeerConnection.create_offer(pc) + :ok = PeerConnection.set_local_description(pc, offer) + + # first m-line is rejected = ice ufrag and passwd are random (thats what Chromium seems to do) + sdp = + """ + v=0 + o=- 4455712322662451611 2 IN IP4 127.0.0.1 + s=- + t=0 0 + a=group:BUNDLE 1 + a=extmap-allow-mixed + a=msid-semantic: WMS + m=video 0 UDP/TLS/RTP/SAVPF 0 + c=IN IP4 0.0.0.0 + a=rtcp:9 IN IP4 0.0.0.0 + a=ice-ufrag:oIRa + a=ice-pwd:10rPa8NrMCm602mt1OVkUHJs + a=ice-options:trickle + a=fingerprint:sha-256 C8:06:34:28:7C:5B:55:00:42:61:54:D2:84:29:B5:07:3D:9A:6C:5C:C6:79:B1:B0:A8:12:30:AD:26:36:93:45 + a=setup:active + a=mid:0 + a=sendonly + a=rtcp-mux + m=audio 9 UDP/TLS/RTP/SAVPF 111 + c=IN IP4 0.0.0.0 + a=rtcp:9 IN IP4 0.0.0.0 + a=ice-ufrag:Anyh + a=ice-pwd:cdaU0jbHlOTf98BNTRoZxMo1 + a=ice-options:trickle + a=fingerprint:sha-256 C8:06:34:28:7C:5B:55:00:42:61:54:D2:84:29:B5:07:3D:9A:6C:5C:C6:79:B1:B0:A8:12:30:AD:26:36:93:45 + a=setup:active + a=mid:1 + a=recvonly + a=rtcp-mux + a=rtpmap:111 opus/48000/2 + a=rtcp-fb:111 transport-cc + a=fmtp:111 minptime=10;useinbandfec=1 + """ + + answer = %SessionDescription{type: :answer, sdp: sdp} + + assert :ok = PeerConnection.set_remote_description(pc, answer) + end end describe "add_transceiver/3" do