Skip to content

Commit

Permalink
Fix create_offer
Browse files Browse the repository at this point in the history
  • Loading branch information
LVala committed Oct 30, 2023
1 parent c680e44 commit 5cc7328
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 10 deletions.
65 changes: 56 additions & 9 deletions lib/ex_webrtc/peer_connection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -192,26 +192,34 @@ defmodule ExWebRTC.PeerConnection do
@impl true
def handle_call({:create_answer, _options}, _from, state)
when state.signaling_state in [:have_remote_offer, :have_local_pranswer] do
{:offer, remote_offer} = state.pending_remote_desc

{:ok, ice_ufrag, ice_pwd} = ICEAgent.get_local_credentials(state.ice_agent)
{:ok, dtls_fingerprint} = ExDTLS.get_cert_fingerprint(state.dtls_client)

answer = %ExSDP{ExSDP.new() | timing: %ExSDP.Timing{start_time: 0, stop_time: 0}}

answer =
case ExSDP.get_attribute(remote_offer, :ice_options) do
{:ice_options, "trickle"} = attr -> ExSDP.add_attribute(answer, attr)
_other -> answer
end

config =
[
ice_ufrag: ice_ufrag,
ice_pwd: ice_pwd,
ice_options: "trickle",
fingerprint: {:sha256, Utils.hex_dump(dtls_fingerprint)},
# TODO offer will always contain actpass
# and answer should contain active
# see RFC 8829 sec. 5.3.1
setup: :passive
setup: :active
]

# TODO: rejected media sections
mlines =
Enum.map(state.transceivers, fn transceiver ->
RTPTransceiver.to_mline(transceiver, config)
Enum.map(remote_offer.media, fn mline ->
{:mid, mid} = ExSDP.Media.get_attribute(mline, :mid)
{_ix, transceiver} = RTPTransceiver.find_by_mid(state.transceivers, mid)
SDPUtils.get_answer_mline(mline, transceiver, config)
end)

mids =
Expand Down Expand Up @@ -394,7 +402,9 @@ defmodule ExWebRTC.PeerConnection do

defp apply_local_description(type, sdp, state) do
new_transceivers = update_local_transceivers(type, state.transceivers, sdp)
{:ok, %{state | current_local_desc: sdp, transceivers: new_transceivers}}
state = set_description(:local, type, sdp, state)

{:ok, %{state | transceivers: new_transceivers}}
end

defp update_local_transceivers(:offer, transceivers, sdp) do
Expand All @@ -411,7 +421,7 @@ defmodule ExWebRTC.PeerConnection do
transceivers
end

defp apply_remote_description(_type, sdp, state) do
defp apply_remote_description(type, sdp, state) do
# TODO apply steps listed in RFC 8829 5.10
with :ok <- SDPUtils.ensure_mid(sdp),
:ok <- SDPUtils.ensure_bundle(sdp),
Expand All @@ -433,7 +443,9 @@ defmodule ExWebRTC.PeerConnection do
notify(state.owner, {:track, track})
end

{:ok, %{state | current_remote_desc: sdp, transceivers: new_transceivers}}
state = set_description(:remote, type, sdp, state)

{:ok, %{state | transceivers: new_transceivers}}
else
error -> error
end
Expand Down Expand Up @@ -518,5 +530,40 @@ 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}

defp set_description(:local, :answer, sdp, state) do
# NOTICE: internaly, we don't create SessionDescription
# as it would require serialization of sdp
%{
state
| current_local_desc: {:answer, sdp},
current_remote_desc: state.pending_remote_desc,
pending_local_desc: nil,
pending_remote_desc: nil,
# W3C 4.4.1.5 (.4.7.5.2) suggests setting these to "", not nil
last_offer: nil,
last_answer: nil
}
end

defp set_description(:local, other, sdp, state) when other in [:answer, :pranswer] do
%{state | pending_local_desc: {other, sdp}}
end

defp set_description(:remote, :answer, sdp, state) do
%{
state
| current_remote_desc: {:answer, sdp},
current_local_desc: state.pending_local_desc,
pending_remote_desc: nil,
pending_local_desc: nil,
last_offer: nil,
last_answer: nil
}
end

defp set_description(:remote, other, sdp, state) when other in [:offer, :pranswer] do
%{state | pending_remote_desc: {other, sdp}}
end

defp notify(pid, msg), do: send(pid, {:ex_webrtc, self(), msg})
end
55 changes: 55 additions & 0 deletions lib/ex_webrtc/sdp_utils.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,51 @@
defmodule ExWebRTC.SDPUtils do
@moduledoc false

alias ExWebRTC.RTPTransceiver

@spec get_answer_mline(ExSDP.Media.t(), RTPTransceiver.t(), Keyword.t()) :: ExSDP.Media.t()
def get_answer_mline(mline, transceiver, config) do
# TODO: we need to filter the media formats according to our capabilities
media_formats =
Enum.filter(mline.attributes, fn
%ExSDP.Attribute.RTPMapping{} -> true
%ExSDP.Attribute.FMTP{} -> true
_other -> false
end)

payload_types =
Enum.flat_map(media_formats, fn
%ExSDP.Attribute.RTPMapping{payload_type: pt} -> [pt]
_other -> []
end)

offered_direction = ExSDP.Media.get_attribute(mline, :direction)
direction = get_direction(offered_direction, transceiver.direction)

attributes =
[
direction,
{:mid, transceiver.mid},
{:ice_ufrag, Keyword.fetch!(config, :ice_ufrag)},
{:ice_pwd, Keyword.fetch!(config, :ice_pwd)},
{:ice_options, Keyword.fetch!(config, :ice_options)},
{:fingerprint, Keyword.fetch!(config, :fingerprint)},
{:setup, Keyword.fetch!(config, :setup)},
# TODO: probably should fail if the offer doesn't contain rtcp-mux
# as we don't support non-muxed streams
:rtcp_mux
]

# TODO: validation of some the stuff in remote SDP
%ExSDP.Media{
ExSDP.Media.new(mline.type, 9, mline.protocol, payload_types)
| # mline must be followed by a cline, which must contain
# the default value "IN IP4 0.0.0.0" (as there are no candidates yet)
connection_data: [%ExSDP.ConnectionData{address: {0, 0, 0, 0}}]
}
|> ExSDP.Media.add_attributes(attributes ++ media_formats)
end

@spec get_media_direction(ExSDP.Media.t()) ::
:sendrecv | :sendonly | :recvonly | :inactive | nil
def get_media_direction(media) do
Expand Down Expand Up @@ -145,4 +190,14 @@ defmodule ExWebRTC.SDPUtils do
_ -> {:error, :conflicting_ice_credentials}
end
end

# RFC 3264 (6.1) + RFC 8829 (5.3.1)
# AFAIK one of the cases should always match
# bc we won't assign/create an inactive transceiver to i.e. sendonly mline
# also neither of the arguments should ever be :stopped
defp get_direction(_, :inactive), do: :inactive
defp get_direction(:sendonly, t) when t in [:sendrecv, :recvonly], do: :recvonly
defp get_direction(:recvonly, t) when t in [:sendrecv, :sendonly], do: :sendonly
defp get_direction(o, other) when o in [:sendrecv, nil], do: other
defp get_direction(:inactive, _), do: :inactive
end
2 changes: 1 addition & 1 deletion lib/ex_webrtc/session_description.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ defmodule ExWebRTC.SessionDescription do
defstruct @enforce_keys

@spec from_json(%{String.t() => String.t()}) :: {:ok, t()} | :error
def from_init(%{"type" => type})
def from_json(%{"type" => type})
when type not in ["answer", "offer", "pranswer", "rollback"],
do: :error

Expand Down

0 comments on commit 5cc7328

Please sign in to comment.