Skip to content

Commit

Permalink
Add ICETransport behaviour (#25)
Browse files Browse the repository at this point in the history
  • Loading branch information
mickel8 authored Nov 28, 2023
1 parent 363500a commit b0535cd
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 66 deletions.
44 changes: 25 additions & 19 deletions lib/ex_webrtc/dtls_transport.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ defmodule ExWebRTC.DTLSTransport do

require Logger

alias ExICE.ICEAgent
alias ExWebRTC.Utils
alias ExWebRTC.{DefaultICETransport, ICETransport, Utils}

@type dtls_transport() :: GenServer.server()

Expand All @@ -31,15 +30,21 @@ defmodule ExWebRTC.DTLSTransport do
@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
GenServer.start_link(__MODULE__, [ice_config, ice_module, self()])
@spec start_link(ICETransport.t(), Keyword.t()) :: GenServer.on_start()
def start_link(ice_transport \\ DefaultICETransport, ice_config) do
behaviour = ice_transport.__info__(:attributes)[:behaviour] || []

unless ICETransport in behaviour do
raise "DTLSTransport requires ice_transport to implement ExWebRTC.ICETransport beahviour."
end

GenServer.start_link(__MODULE__, [ice_transport, ice_config, self()])
end

@doc false
@spec get_ice_agent(dtls_transport()) :: GenServer.server()
def get_ice_agent(dtls_transport) do
GenServer.call(dtls_transport, :get_ice_agent)
@spec get_ice_transport(dtls_transport()) :: {module(), pid()}
def get_ice_transport(dtls_transport) do
GenServer.call(dtls_transport, :get_ice_transport)
end

@doc false
Expand Down Expand Up @@ -68,15 +73,16 @@ defmodule ExWebRTC.DTLSTransport do
end

@impl true
def init([ice_config, ice_module, owner]) do
def init([ice_transport, ice_config, owner]) do
{pkey, cert} = ExDTLS.generate_key_cert()
fingerprint = ExDTLS.get_cert_fingerprint(cert)

{:ok, ice_agent} = ice_module.start_link(:controlled, ice_config)
{:ok, ice_pid} = ice_transport.start_link(:controlled, ice_config)

state = %{
owner: owner,
ice_agent: ice_agent,
ice_transport: ice_transport,
ice_pid: ice_pid,
ice_state: nil,
buffered_packets: nil,
cert: cert,
Expand All @@ -97,8 +103,8 @@ defmodule ExWebRTC.DTLSTransport do
end

@impl true
def handle_call(:get_ice_agent, _from, state) do
{:reply, state.ice_agent, state}
def handle_call(:get_ice_transport, _from, state) do
{:reply, {state.ice_transport, state.ice_pid}, state}
end

@impl true
Expand Down Expand Up @@ -134,7 +140,7 @@ defmodule ExWebRTC.DTLSTransport do
def handle_cast({:send_rtp, data}, %{dtls_state: :connected, ice_state: ice_state} = state)
when ice_state in [:connected, :completed] do
case ExLibSRTP.protect(state.out_srtp, data) do
{:ok, protected} -> ICEAgent.send_data(state.ice_agent, protected)
{:ok, protected} -> state.ice_transport.send_data(state.ice_pid, protected)
{:error, reason} -> Logger.error("Unable to protect RTP: #{inspect(reason)}")
end

Expand All @@ -160,7 +166,7 @@ defmodule ExWebRTC.DTLSTransport do
) do
case ExDTLS.handle_timeout(state.dtls) do
{:retransmit, packets, timeout} when ice_state in [:connected, :completed] ->
ICEAgent.send_data(state.ice_agent, packets)
state.ice_transport.send_data(state.ice_pid, packets)
Process.send_after(self(), :dtls_timeout, timeout)

{:retransmit, ^buffered_packets, timeout} ->
Expand Down Expand Up @@ -199,7 +205,7 @@ defmodule ExWebRTC.DTLSTransport do
# TODO: handle {:connection_closed, _}
case ExDTLS.handle_data(state.dtls, data) do
{:handshake_packets, packets, timeout} when state.ice_state in [:connected, :completed] ->
:ok = ICEAgent.send_data(state.ice_agent, packets)
:ok = state.ice_transport.send_data(state.ice_pid, packets)
Process.send_after(self(), :dtls_timeout, timeout)
update_dtls_state(state, :connecting)

Expand All @@ -215,7 +221,7 @@ defmodule ExWebRTC.DTLSTransport do

{:handshake_finished, lkm, rkm, profile, packets} ->
Logger.debug("DTLS handshake finished")
ICEAgent.send_data(state.ice_agent, packets)
state.ice_transport.send_data(state.ice_pid, packets)

peer_fingerprint =
state.dtls
Expand Down Expand Up @@ -275,7 +281,7 @@ defmodule ExWebRTC.DTLSTransport do
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)
:ok = state.ice_transport.send_data(state.ice_pid, packets)
update_dtls_state(state, :connecting)
else
state
Expand All @@ -286,7 +292,7 @@ defmodule ExWebRTC.DTLSTransport do
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)
:ok = state.ice_transport.send_data(state.ice_pid, state.buffered_packets)
%{state | ice_state: new_ice_state, buffered_packets: nil}
else
state
Expand Down
40 changes: 40 additions & 0 deletions lib/ex_webrtc/ice_transport.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
defmodule ExWebRTC.ICETransport do
@moduledoc false

# module implementing this behaviour
@type t() :: module()

@callback start_link(ExICE.ICEAgent.role(), Keyword.t()) :: {:ok, pid()}
@callback add_remote_candidate(pid(), candidate :: String.t()) :: :ok
@callback end_of_candidates(pid()) :: :ok
@callback gather_candidates(pid()) :: :ok
@callback get_local_credentials(pid()) :: {:ok, ufrag :: binary(), pwd :: binary()}
@callback restart(pid()) :: :ok
@callback send_data(pid(), binary()) :: :ok
@callback set_remote_credentials(pid(), ufrag :: binary(), pwd :: binary()) :: :ok
end

defmodule ExWebRTC.DefaultICETransport do
@moduledoc false

@behaviour ExWebRTC.ICETransport

alias ExICE.ICEAgent

@impl true
defdelegate add_remote_candidate(pid, candidate), to: ICEAgent
@impl true
defdelegate end_of_candidates(pid), to: ICEAgent
@impl true
defdelegate gather_candidates(pid), to: ICEAgent
@impl true
defdelegate get_local_credentials(pid), to: ICEAgent
@impl true
defdelegate restart(pid), to: ICEAgent
@impl true
defdelegate send_data(pid, data), to: ICEAgent
@impl true
defdelegate set_remote_credentials(pid, ufrag, pwd), to: ICEAgent
@impl true
defdelegate start_link(role, opts), to: ICEAgent
end
22 changes: 13 additions & 9 deletions lib/ex_webrtc/peer_connection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ defmodule ExWebRTC.PeerConnection do
require Logger

alias __MODULE__.{Configuration, Demuxer}
alias ExICE.ICEAgent

alias ExWebRTC.{
DTLSTransport,
Expand Down Expand Up @@ -121,7 +120,7 @@ defmodule ExWebRTC.PeerConnection do
def init({owner, config}) do
ice_config = [stun_servers: config.ice_servers]
{:ok, dtls_transport} = DTLSTransport.start_link(ice_config)
ice_agent = DTLSTransport.get_ice_agent(dtls_transport)
{ice_transport, ice_pid} = DTLSTransport.get_ice_transport(dtls_transport)

state = %{
owner: owner,
Expand All @@ -130,7 +129,8 @@ defmodule ExWebRTC.PeerConnection do
pending_local_desc: nil,
current_remote_desc: nil,
pending_remote_desc: nil,
ice_agent: ice_agent,
ice_transport: ice_transport,
ice_pid: ice_pid,
dtls_transport: dtls_transport,
demuxer: %Demuxer{},
transceivers: [],
Expand Down Expand Up @@ -159,13 +159,14 @@ defmodule ExWebRTC.PeerConnection do
# TODO: handle subsequent offers

if Keyword.get(options, :ice_restart, false) do
:ok = ICEAgent.restart(state.ice_agent)
:ok = state.ice_transport.restart(state.ice_pid)
end

next_mid = find_next_mid(state)
transceivers = assign_mids(state.transceivers, next_mid)

{:ok, ice_ufrag, ice_pwd} = ICEAgent.get_local_credentials(state.ice_agent)
{:ok, ice_ufrag, ice_pwd} =
state.ice_transport.get_local_credentials(state.ice_pid)

offer =
%ExSDP{ExSDP.new() | timing: %ExSDP.Timing{start_time: 0, stop_time: 0}}
Expand Down Expand Up @@ -219,7 +220,8 @@ defmodule ExWebRTC.PeerConnection do
def handle_call({:create_answer, _options}, _from, state) do
{:offer, remote_offer} = state.pending_remote_desc

{:ok, ice_ufrag, ice_pwd} = ICEAgent.get_local_credentials(state.ice_agent)
{:ok, ice_ufrag, ice_pwd} =
state.ice_transport.get_local_credentials(state.ice_pid)

answer =
%ExSDP{ExSDP.new() | timing: %ExSDP.Timing{start_time: 0, stop_time: 0}}
Expand Down Expand Up @@ -315,7 +317,7 @@ defmodule ExWebRTC.PeerConnection do
@impl true
def handle_call({:add_ice_candidate, candidate}, _from, state) do
with "candidate:" <> attr <- candidate.candidate do
ICEAgent.add_remote_candidate(state.ice_agent, attr)
state.ice_transport.add_remote_candidate(state.ice_pid, attr)
end

{:reply, :ok, state}
Expand Down Expand Up @@ -496,8 +498,10 @@ defmodule ExWebRTC.PeerConnection do
{:ok, {:fingerprint, {:sha256, peer_fingerprint}}} <- SDPUtils.get_cert_fingerprint(sdp),
{:ok, new_transceivers} <-
update_remote_transceivers(state.transceivers, sdp, state.config) do
:ok = ICEAgent.set_remote_credentials(state.ice_agent, ice_ufrag, ice_pwd)
:ok = ICEAgent.gather_candidates(state.ice_agent)
:ok =
state.ice_transport.set_remote_credentials(state.ice_pid, ice_ufrag, ice_pwd)

:ok = state.ice_transport.gather_candidates(state.ice_pid)

# TODO: this needs a look

Expand Down
Loading

0 comments on commit b0535cd

Please sign in to comment.