Skip to content

Commit

Permalink
Ensure ICE credentials
Browse files Browse the repository at this point in the history
  • Loading branch information
mickel8 committed Oct 24, 2023
1 parent ec1c9f7 commit 02ea812
Show file tree
Hide file tree
Showing 3 changed files with 207 additions and 50 deletions.
12 changes: 3 additions & 9 deletions lib/ex_webrtc/peer_connection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -280,15 +280,11 @@ defmodule ExWebRTC.PeerConnection do

defp apply_remote_description(_type, sdp, state) do
# TODO apply steps listed in RFC 8829 5.10
media = hd(sdp.media)

with :ok <- SDPUtils.ensure_mid(sdp),
:ok <- SDPUtils.ensure_bundle(sdp),
{:ice_ufrag, {:ice_ufrag, ufrag}} <-
{:ice_ufrag, ExSDP.Media.get_attribute(media, :ice_ufrag)},
{:ice_pwd, {:ice_pwd, pwd}} <- {:ice_pwd, ExSDP.Media.get_attribute(media, :ice_pwd)},
{:ok, {ice_ufrag, ice_pwd}} <- SDPUtils.get_ice_credentials(sdp),
{:ok, new_transceivers} <- update_transceivers(state.transceivers, sdp) do
:ok = ICEAgent.set_remote_credentials(state.ice_agent, ufrag, pwd)
:ok = ICEAgent.set_remote_credentials(state.ice_agent, ice_ufrag, ice_pwd)
:ok = ICEAgent.gather_candidates(state.ice_agent)

new_remote_tracks =
Expand All @@ -306,9 +302,7 @@ defmodule ExWebRTC.PeerConnection do

{:ok, %{state | current_remote_desc: sdp, transceivers: new_transceivers}}
else
{:ice_ufrag, nil} -> {:error, :missing_ice_ufrag}
{:ice_pwd, nil} -> {:error, :missing_ice_pwd}
other -> other
{:error, _reason} = error -> error
end
end

Expand Down
81 changes: 81 additions & 0 deletions lib/ex_webrtc/sdp_utils.ex
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,85 @@ defmodule ExWebRTC.SDPUtils do
{:error, :multiple_bundle_groups}
end
end

@spec get_ice_credentials(ExSDP.t()) ::
{:ok, binary(), binary()}
| {:error,
:missing_ice_pwd
| :missing_ice_ufrag
| :missing_ice_credentials
| :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)

case {session_creds, mline_creds} do
{{nil, nil}, []} ->
{:error, :missing_ice_credentials}

{session_creds, []} ->
{:ok, session_creds}

{{nil, nil}, mline_creds} ->
with :ok <- ensure_ice_credentials_present(mline_creds),
:ok <- ensure_ice_credentials_unique(mline_creds) do
{:ok, List.first(mline_creds)}
end

{{s_ufrag, s_pwd} = session_creds, mline_creds} ->
creds =
Enum.map([session_creds | mline_creds], fn
{nil, nil} -> session_creds
{nil, pwd} -> {s_ufrag, pwd}
{ufrag, nil} -> {ufrag, s_pwd}
other -> other
end)

case ensure_ice_credentials_unique(creds) do
:ok -> {:ok, List.first(creds)}
error -> error
end
end
end

defp do_get_ice_credentials(sdp_or_mline) do
ice_ufrag =
case ExSDP.Media.get_attribute(sdp_or_mline, :ice_ufrag) do
{:ice_ufrag, ice_ufrag} -> ice_ufrag
nil -> nil
end

ice_pwd =
case ExSDP.Media.get_attribute(sdp_or_mline, :ice_pwd) do
{:ice_pwd, ice_pwd} -> ice_pwd
nil -> nil
end

{ice_ufrag, ice_pwd}
end

defp ensure_ice_credentials_present(creds) do
creds
|> Enum.find(fn {ice_ufrag, ice_pwd} -> ice_ufrag == nil or ice_pwd == nil end)
|> case do
{nil, nil} ->
{:error, :missing_ice_credentials}

{nil, _} ->
{:error, :missing_ice_ufrag}

{_, nil} ->
{:error, :missing_ice_pwd}

nil ->
:ok
end
end

defp ensure_ice_credentials_unique(creds) do
case Enum.uniq(creds) do
[_] -> :ok
_ -> {:error, :conflicting_ice_credentials}
end
end
end
164 changes: 123 additions & 41 deletions test/peer_connection_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ defmodule ExWebRTC.PeerConnectionTest do
a=ssrc:184440407 msid:- 1259ea70-c6b7-445a-9c20-49cec7433ccb
"""

@audio_mline ExSDP.Media.new("audio", 9, "UDP/TLS/RTP/SAVPF", [108])
|> ExSDP.Media.add_attributes(mid: "0", ice_ufrag: "someufrag", ice_pwd: "somepwd")

@video_mline ExSDP.Media.new("video", 9, "UDP/TLS/RTP/SAVPF", [96])
|> ExSDP.Media.add_attributes(mid: "1", ice_ufrag: "someufrag", ice_pwd: "somepwd")

test "track notification" do
{:ok, pc} = PeerConnection.start_link()

Expand All @@ -96,46 +102,122 @@ defmodule ExWebRTC.PeerConnectionTest do
refute_receive {:ex_webrtc, ^pc, {:track, %MediaStreamTrack{}}}
end

test "set_remote_description/2" do
{:ok, pc} = PeerConnection.start_link()

raw_sdp = ExSDP.new()

audio_mline =
ExSDP.Media.new("audio", 9, "UDP/TLS/RTP/SAVPF", [108])
|> ExSDP.Media.add_attributes(mid: "0", ice_ufrag: "someufrag", ice_pwd: "somepwd")

video_mline =
ExSDP.Media.new("video", 9, "UDP/TLS/RTP/SAVPF", [96])
|> ExSDP.Media.add_attributes(mid: "1", ice_ufrag: "someufrag", ice_pwd: "somepwd")

sdp = ExSDP.add_media(raw_sdp, audio_mline) |> to_string()
offer = %SessionDescription{type: :offer, sdp: sdp}
assert {:error, :missing_bundle_group} = PeerConnection.set_remote_description(pc, offer)

mline = ExSDP.Media.add_attribute(audio_mline, {:mid, "1"})
sdp = ExSDP.add_media(raw_sdp, mline) |> to_string()
offer = %SessionDescription{type: :offer, sdp: sdp}
assert {:error, :duplicated_mid} = PeerConnection.set_remote_description(pc, offer)

mline = ExSDP.Media.new("audio", 9, "UDP/TLS/RTP/SAVPF", [96])
sdp = ExSDP.add_media(raw_sdp, mline) |> to_string()
offer = %SessionDescription{type: :offer, sdp: sdp}
assert {:error, :missing_mid} = PeerConnection.set_remote_description(pc, offer)

sdp =
raw_sdp
|> ExSDP.add_attribute(%ExSDP.Attribute.Group{semantics: "BUNDLE", mids: [0]})
|> ExSDP.add_media(audio_mline)

offer = %SessionDescription{type: :offer, sdp: to_string(sdp)}
assert :ok == PeerConnection.set_remote_description(pc, offer)

sdp = ExSDP.add_media(sdp, video_mline) |> to_string()

offer = %SessionDescription{type: :offer, sdp: sdp}

assert {:error, :non_exhaustive_bundle_group} ==
PeerConnection.set_remote_description(pc, offer)
describe "set_remote_description/2" do
test "mid" do
{:ok, pc} = PeerConnection.start_link()

raw_sdp = ExSDP.new()

mline = ExSDP.Media.add_attribute(@audio_mline, {:mid, "1"})
sdp = ExSDP.add_media(raw_sdp, mline) |> to_string()
offer = %SessionDescription{type: :offer, sdp: sdp}
assert {:error, :duplicated_mid} = PeerConnection.set_remote_description(pc, offer)

mline = ExSDP.Media.new("audio", 9, "UDP/TLS/RTP/SAVPF", [96])
sdp = ExSDP.add_media(raw_sdp, mline) |> to_string()
offer = %SessionDescription{type: :offer, sdp: sdp}
assert {:error, :missing_mid} = PeerConnection.set_remote_description(pc, offer)
end

test "BUNDLE group" do
{:ok, pc} = PeerConnection.start_link()

sdp = ExSDP.add_media(ExSDP.new(), [@audio_mline, @video_mline])

[
{nil, {:error, :missing_bundle_group}},
{%ExSDP.Attribute.Group{semantics: "BUNDLE", mids: [0]},
{:error, :non_exhaustive_bundle_group}},
{%ExSDP.Attribute.Group{semantics: "BUNDLE", mids: [0, 1]}, :ok}
]
|> Enum.each(fn {bundle_group, expected_result} ->
sdp = ExSDP.add_attribute(sdp, bundle_group) |> to_string()
offer = %SessionDescription{type: :offer, sdp: sdp}
assert expected_result == PeerConnection.set_remote_description(pc, offer)
end)
end

@tag :debug
test "ice credentials" do
{:ok, pc} = PeerConnection.start_link()

raw_sdp = ExSDP.new()

[
{{nil, nil}, {"someufrag", "somepwd"}, {"someufrag", "somepwd"}, :ok},
{{"someufrag", "somepwd"}, {"someufrag", "somepwd"}, {nil, nil}, :ok},
{{"someufrag", "somepwd"}, {nil, nil}, {nil, nil}, :ok},
{{"someufrag", "somepwd"}, {"someufrag", nil}, {nil, "somepwd"}, :ok},
{{nil, nil}, {"someufrag", "somepwd"}, {nil, nil}, {:error, :missing_ice_credentials}},
{{nil, nil}, {"someufrag", "somepwd"}, {"someufrag", nil}, {:error, :missing_ice_pwd}},
{{nil, nil}, {"someufrag", "somepwd"}, {nil, "somepwd"}, {:error, :missing_ice_ufrag}},
{{nil, nil}, {nil, nil}, {nil, nil}, {:error, :missing_ice_credentials}},
{{nil, nil}, {"someufrag", "somepwd"}, {"someufrag", "someotherpwd"},
{:error, :conflicting_ice_credentials}}
]
|> Enum.each(fn {{s_ufrag, s_pwd}, {a_ufrag, a_pwd}, {v_ufrag, v_pwd}, expected_result} ->
audio_mline =
ExSDP.Media.new("audio", 9, "UDP/TLS/RTP/SAVPF", [108])
|> ExSDP.Media.add_attributes(mid: "0")

video_mline =
ExSDP.Media.new("video", 9, "UDP/TLS/RTP/SAVPF", [96])
|> ExSDP.Media.add_attributes(mid: "1")

audio_mline =
if a_ufrag do
ExSDP.Media.add_attribute(audio_mline, {:ice_ufrag, a_ufrag})
else
audio_mline
end

audio_mline =
if a_pwd do
ExSDP.Media.add_attribute(audio_mline, {:ice_pwd, a_pwd})
else
audio_mline
end

video_mline =
if v_ufrag do
ExSDP.Media.add_attribute(video_mline, {:ice_ufrag, v_ufrag})
else
video_mline
end

video_mline =
if v_pwd do
ExSDP.Media.add_attribute(video_mline, {:ice_pwd, v_pwd})
else
video_mline
end

sdp =
ExSDP.add_attribute(raw_sdp, %ExSDP.Attribute.Group{semantics: "BUNDLE", mids: [0, 1]})

sdp =
if s_ufrag do
ExSDP.add_attribute(sdp, {:ice_ufrag, s_ufrag})
else
sdp
end

sdp =
if s_pwd do
ExSDP.add_attribute(sdp, {:ice_pwd, s_pwd})
else
sdp
end

sdp =
sdp
|> ExSDP.add_media([audio_mline, video_mline])
|> to_string()

offer = %SessionDescription{type: :offer, sdp: sdp}

assert expected_result == PeerConnection.set_remote_description(pc, offer)
end)
end
end
end

0 comments on commit 02ea812

Please sign in to comment.