Skip to content

Commit

Permalink
Add ICETransport behaviour
Browse files Browse the repository at this point in the history
  • Loading branch information
mickel8 committed Nov 27, 2023
1 parent 6154a3b commit 929d96e
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 66 deletions.
42 changes: 25 additions & 17 deletions lib/ex_webrtc/dtls_transport.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ defmodule ExWebRTC.DTLSTransport do

require Logger

alias ExICE.ICEAgent
alias ExWebRTC.ICETransport
alias ExWebRTC.DefaultICETransport

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

Expand All @@ -30,15 +31,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(), ExICE.ICEAgent.opts()) :: 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()) :: GenServer.server()
def get_ice_transport(dtls_transport) do
GenServer.call(dtls_transport, :get_ice_transport)
end

@doc false
Expand All @@ -60,19 +67,20 @@ defmodule ExWebRTC.DTLSTransport do
end

@impl true
def init([ice_config, ice_module, owner]) do
def init([ice_transport, ice_config, owner]) do
# temporary hack to generate certs
dtls = ExDTLS.init(client_mode: true, dtls_srtp: true)
cert = ExDTLS.get_cert(dtls)
pkey = ExDTLS.get_pkey(dtls)
fingerprint = ExDTLS.get_cert_fingerprint(dtls)

{:ok, ice_agent} = ice_module.start_link(:controlled, ice_config)
{:ok, ice_transport_state} = ice_transport.start_link(:controlled, ice_config)
srtp = ExLibSRTP.new()

state = %{
owner: owner,
ice_agent: ice_agent,
ice_transport: ice_transport,
ice_transport_state: ice_transport_state,
ice_state: nil,
buffered_packets: nil,
cert: cert,
Expand All @@ -90,8 +98,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_transport_state}, state}
end

@impl true
Expand Down Expand Up @@ -143,7 +151,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_transport_state, packets)
Process.send_after(self(), :dtls_timeout, timeout)

{:retransmit, ^buffered_packets, timeout} ->
Expand Down Expand Up @@ -181,7 +189,7 @@ defmodule ExWebRTC.DTLSTransport do
defp handle_ice({:data, <<f, _rest::binary>> = data}, state) when f in 20..64 do
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_transport_state, packets)
Process.send_after(self(), :dtls_timeout, timeout)
update_dtls_state(state, :connecting)

Expand All @@ -197,7 +205,7 @@ defmodule ExWebRTC.DTLSTransport do

{:handshake_finished, _, remote_keying_material, profile, packets} ->
Logger.debug("DTLS handshake finished")
ICEAgent.send_data(state.ice_agent, packets)
state.ice_transport.send_data(state.ice_transport_state, packets)
# TODO: validate fingerprint
state = setup_srtp(state, remote_keying_material, profile)
update_dtls_state(state, :connected)
Expand Down Expand Up @@ -241,7 +249,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_transport_state, packets)
update_dtls_state(state, :connecting)
else
state
Expand All @@ -252,7 +260,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_transport_state, 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
29 changes: 17 additions & 12 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 @@ -52,12 +51,13 @@ defmodule ExWebRTC.PeerConnection do
:pending_local_desc,
:current_remote_desc,
:pending_remote_desc,
:ice_agent,
:ice_transport,
:ice_pid,
:ice_state,
:dtls_transport,
:dtls_state,
demuxer: %Demuxer{},
transceivers: [],
ice_state: nil,
dtls_state: nil,
signaling_state: :stable,
conn_state: :new,
last_offer: nil,
Expand Down Expand Up @@ -129,12 +129,13 @@ 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 = %__MODULE__{
owner: owner,
config: config,
ice_agent: ice_agent,
ice_transport: ice_transport,
ice_pid: ice_pid,
dtls_transport: dtls_transport,
ice_state: :new,
dtls_state: :new
Expand All @@ -156,13 +157,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 @@ -216,7 +218,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 @@ -312,7 +315,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 @@ -432,8 +435,10 @@ defmodule ExWebRTC.PeerConnection do
{:ok, {ice_ufrag, ice_pwd}} <- SDPUtils.get_ice_credentials(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)

new_remote_tracks =
new_transceivers
Expand Down
Loading

0 comments on commit 929d96e

Please sign in to comment.