Skip to content

Commit b58acee

Browse files
authored
Add set_role/2 (#72)
1 parent 977359f commit b58acee

File tree

5 files changed

+145
-26
lines changed

5 files changed

+145
-26
lines changed

lib/ex_ice/ice_agent.ex

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ defmodule ExICE.ICEAgent do
5252
All notifications are by default sent to a process that spawns `ExICE`.
5353
This behavior can be overwritten using the following options.
5454
55+
* `role` - agent's role. If not set, it can be later choosen with `set_role/2`. Please note, that
56+
until role is set, adding remote candidates or gathering local candidates won't possible, and calls to these
57+
functions will be ignored. Defaults to `nil`.
5558
* `ip_filter` - filter applied when gathering host candidates
5659
* `ports` - ports that will be used when gathering host candidates, otherwise the ports are chosen by the OS
5760
* `ice_servers` - list of STUN/TURN servers
@@ -64,6 +67,7 @@ defmodule ExICE.ICEAgent do
6467
* `on_new_candidate` - where to send new candidates. Defaults to a process that spawns `ExICE`.
6568
"""
6669
@type opts() :: [
70+
role: role() | nil,
6771
ip_filter: ip_filter(),
6872
ports: Enumerable.t(non_neg_integer()),
6973
ice_servers: [
@@ -86,9 +90,9 @@ defmodule ExICE.ICEAgent do
8690
Process calling this function is called a `controlling process` and
8791
has to be prepared for receiving ExICE messages described by `t:signal/0`.
8892
"""
89-
@spec start_link(role(), opts()) :: GenServer.on_start()
90-
def start_link(role, opts \\ []) do
91-
GenServer.start_link(__MODULE__, opts ++ [role: role, controlling_process: self()])
93+
@spec start_link(opts()) :: GenServer.on_start()
94+
def start_link(opts \\ []) when is_list(opts) do
95+
GenServer.start_link(__MODULE__, opts ++ [controlling_process: self()])
9296
end
9397

9498
@doc """
@@ -123,6 +127,14 @@ defmodule ExICE.ICEAgent do
123127
GenServer.call(ice_agent, {:on_new_candidate, send_to})
124128
end
125129

130+
@doc """
131+
Gets agent's role.
132+
"""
133+
@spec get_role(pid()) :: ExICE.Agent.t() | nil
134+
def get_role(ice_agent) do
135+
GenServer.call(ice_agent, :get_role)
136+
end
137+
126138
@doc """
127139
Gets local credentials.
128140
@@ -152,6 +164,20 @@ defmodule ExICE.ICEAgent do
152164
GenServer.call(ice_agent, :get_remote_candidates)
153165
end
154166

167+
@doc """
168+
Sets agent's role.
169+
170+
In case of WebRTC, agent's role depends on who sends the first offer.
171+
Since an agent has to be initialized at the very beginning, there is no
172+
possibility to set its role in the constructor.
173+
174+
This function can only be called once. Subsequent calls will be ignored.
175+
"""
176+
@spec set_role(pid(), role()) :: :ok
177+
def set_role(ice_agent, role) do
178+
GenServer.cast(ice_agent, {:set_role, role})
179+
end
180+
155181
@doc """
156182
Sets remote credentials.
157183
@@ -283,6 +309,12 @@ defmodule ExICE.ICEAgent do
283309
{:reply, :ok, %{state | ice_agent: ice_agent}}
284310
end
285311

312+
@impl true
313+
def handle_call(:get_role, _from, state) do
314+
role = ExICE.Priv.ICEAgent.get_role(state.ice_agent)
315+
{:reply, role, state}
316+
end
317+
286318
@impl true
287319
def handle_call(:get_local_credentials, _from, state) do
288320
{local_ufrag, local_pwd} = ExICE.Priv.ICEAgent.get_local_credentials(state.ice_agent)
@@ -307,6 +339,12 @@ defmodule ExICE.ICEAgent do
307339
{:reply, stats, state}
308340
end
309341

342+
@impl true
343+
def handle_cast({:set_role, role}, state) do
344+
ice_agent = ExICE.Priv.ICEAgent.set_role(state.ice_agent, role)
345+
{:noreply, %{state | ice_agent: ice_agent}}
346+
end
347+
310348
@impl true
311349
def handle_cast({:set_remote_credentials, ufrag, pwd}, state) do
312350
ice_agent = ExICE.Priv.ICEAgent.set_remote_credentials(state.ice_agent, ufrag, pwd)

lib/ex_ice/priv/ice_agent.ex

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ defmodule ExICE.Priv.ICEAgent do
153153
transport_module: transport_module,
154154
gatherer: Gatherer.new(if_discovery_module, transport_module, ip_filter, ports),
155155
ice_transport_policy: opts[:ice_transport_policy] || :all,
156-
role: Keyword.fetch!(opts, :role),
156+
role: opts[:role],
157157
tiebreaker: generate_tiebreaker(),
158158
local_ufrag: local_ufrag,
159159
local_pwd: local_pwd,
@@ -182,6 +182,9 @@ defmodule ExICE.Priv.ICEAgent do
182182
%__MODULE__{ice_agent | on_new_candidate: send_to}
183183
end
184184

185+
@spec get_role(t()) :: ExICE.Agent.role() | nil
186+
def get_role(ice_agent), do: ice_agent.role
187+
185188
@spec get_local_credentials(t()) :: {binary(), binary()}
186189
def get_local_credentials(ice_agent) do
187190
{ice_agent.local_ufrag, ice_agent.local_pwd}
@@ -227,6 +230,16 @@ defmodule ExICE.Priv.ICEAgent do
227230
}
228231
end
229232

233+
@spec set_role(t(), ExICE.ICEAgent.role()) :: t()
234+
def set_role(%__MODULE__{role: nil} = ice_agent, role) do
235+
%__MODULE__{ice_agent | role: role}
236+
end
237+
238+
def set_role(%__MODULE__{} = ice_agent, _role) do
239+
Logger.warning("Can't set role. Role already set. Ignoring.")
240+
ice_agent
241+
end
242+
230243
@spec set_remote_credentials(t(), binary(), binary()) :: t()
231244
def set_remote_credentials(%__MODULE__{state: :failed} = ice_agent, _, _) do
232245
Logger.debug("Tried to set remote credentials in failed state. ICE restart needed. Ignoring.")
@@ -269,6 +282,11 @@ defmodule ExICE.Priv.ICEAgent do
269282
ice_agent
270283
end
271284

285+
def gather_candidates(%__MODULE__{role: nil} = ice_agent) do
286+
Logger.warning("Can't gather candidates without role. Set the role with `set_role/2`.")
287+
ice_agent
288+
end
289+
272290
def gather_candidates(%__MODULE__{gathering_state: :gathering} = ice_agent) do
273291
Logger.warning("Can't gather candidates. Gathering already in progress. Ignoring.")
274292
ice_agent
@@ -341,19 +359,30 @@ defmodule ExICE.Priv.ICEAgent do
341359

342360
def add_remote_candidate(%__MODULE__{eoc: true} = ice_agent, remote_cand) do
343361
Logger.warning("""
344-
Received remote candidate after end-of-candidates. Ignoring.
362+
Can't add remote candidate after end-of-candidates. Ignoring. \
345363
Candidate: #{inspect(remote_cand)}\
346364
""")
347365

348366
ice_agent
349367
end
350368

369+
def add_remote_candidate(%__MODULE__{role: nil} = ice_agent, remote_cand) do
370+
Logger.warning("""
371+
Can't add remote candidate without role. \
372+
Set the role with `set_role/2`. Ignoring. \
373+
Candidate: #{inspect(remote_cand)}
374+
""")
375+
376+
ice_agent
377+
end
378+
351379
def add_remote_candidate(
352380
%__MODULE__{remote_ufrag: nil, remote_pwd: nil} = ice_agent,
353381
remote_cand
354382
) do
355383
Logger.warning("""
356-
Received remote candidate but there are no remote credentials. Ignoring.
384+
Can't add remote candidate without remote credentials. \
385+
Set remote credentials with `set_remote_credentials/3`. Ignoring. \
357386
Candidate: #{inspect(remote_cand)}\
358387
""")
359388

test/ice_agent_test.exs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,20 @@ defmodule ExICE.ICEAgentTest do
33

44
alias ExICE.ICEAgent
55

6+
test "get_role/1" do
7+
{:ok, agent} = ICEAgent.start_link(role: :controlling)
8+
assert ICEAgent.get_role(agent) == :controlling
9+
end
10+
11+
test "set_role/2" do
12+
{:ok, agent} = ICEAgent.start_link()
13+
assert ICEAgent.get_role(agent) == nil
14+
assert :ok == ICEAgent.set_role(agent, :controlling)
15+
assert ICEAgent.get_role(agent) == :controlling
16+
end
17+
618
test "gather_candidates/1" do
7-
{:ok, agent} = ICEAgent.start_link(:controlling)
19+
{:ok, agent} = ICEAgent.start_link(role: :controlling)
820
:ok = ICEAgent.gather_candidates(agent)
921

1022
assert_receive {:ex_ice, ^agent, {:gathering_state_change, :gathering}}
@@ -19,7 +31,7 @@ defmodule ExICE.ICEAgentTest do
1931
end
2032

2133
test "get_stats/1" do
22-
{:ok, agent} = ICEAgent.start_link(:controlling)
34+
{:ok, agent} = ICEAgent.start_link(role: :controlling)
2335

2436
assert %{
2537
bytes_sent: 0,

test/integration/p2p_test.exs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ defmodule ExICE.Integration.P2PTest do
1717
end
1818

1919
{:ok, agent1} =
20-
ICEAgent.start_link(:controlling, ip_filter: ip_filter, ice_servers: ice_servers)
20+
ICEAgent.start_link(role: :controlling, ip_filter: ip_filter, ice_servers: ice_servers)
2121

22-
{:ok, agent2} = ICEAgent.start_link(:controlled, ip_filter: ip_filter, ice_servers: [])
22+
{:ok, agent2} = ICEAgent.start_link(role: :controlled, ip_filter: ip_filter, ice_servers: [])
2323

2424
{:ok, a1_ufrag, a1_pwd} = ICEAgent.get_local_credentials(agent1)
2525
{:ok, a2_ufrag, a2_pwd} = ICEAgent.get_local_credentials(agent2)
@@ -94,10 +94,10 @@ defmodule ExICE.Integration.P2PTest do
9494
end
9595

9696
{:ok, agent1} =
97-
ICEAgent.start_link(:controlled, ip_filter: ip_filter, ice_servers: ice_servers)
97+
ICEAgent.start_link(role: :controlled, ip_filter: ip_filter, ice_servers: ice_servers)
9898

9999
{:ok, agent2} =
100-
ICEAgent.start_link(:controlled, ip_filter: ip_filter, ice_servers: ice_servers)
100+
ICEAgent.start_link(role: :controlled, ip_filter: ip_filter, ice_servers: ice_servers)
101101

102102
{:ok, a1_ufrag, a1_pwd} = ICEAgent.get_local_credentials(agent1)
103103
{:ok, a2_ufrag, a2_pwd} = ICEAgent.get_local_credentials(agent2)
@@ -144,14 +144,15 @@ defmodule ExICE.Integration.P2PTest do
144144
end
145145

146146
{:ok, agent1} =
147-
ICEAgent.start_link(:controlling,
147+
ICEAgent.start_link(
148+
role: :controlling,
148149
ip_filter: ip_filter,
149150
ice_servers: ice_servers,
150151
ice_transport_policy: :relay
151152
)
152153

153154
{:ok, agent2} =
154-
ICEAgent.start_link(:controlled, ip_filter: ip_filter, ice_servers: [])
155+
ICEAgent.start_link(role: :controlled, ip_filter: ip_filter, ice_servers: [])
155156

156157
{:ok, a1_ufrag, a1_pwd} = ICEAgent.get_local_credentials(agent1)
157158
{:ok, a2_ufrag, a2_pwd} = ICEAgent.get_local_credentials(agent2)

test/priv/ice_agent_test.exs

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -143,36 +143,75 @@ defmodule ExICE.Priv.ICEAgentTest do
143143
assert %{} == ice_agent.remote_cands
144144
end
145145

146+
test "without role", %{ice_agent: ice_agent} do
147+
ice_agent = %{ice_agent | role: nil}
148+
ice_agent = ICEAgent.add_remote_candidate(ice_agent, @remote_cand)
149+
assert %{} == ice_agent.remote_cands
150+
end
151+
146152
test "after setting end-of-candidates", %{ice_agent: ice_agent} do
147153
ice_agent = ICEAgent.end_of_candidates(ice_agent)
148154
ice_agent = ICEAgent.add_remote_candidate(ice_agent, @remote_cand)
149155
assert %{} == ice_agent.remote_cands
150156
end
151157
end
152158

153-
test "gather_candidates/1 creates pairs if there already are any remote candidates" do
159+
test "set_role/2" do
154160
ice_agent =
155161
ICEAgent.new(
156162
controlling_process: self(),
157-
role: :controlling,
163+
role: nil,
158164
if_discovery_module: IfDiscovery.Mock,
159165
transport_module: Transport.Mock
160166
)
161167
|> ICEAgent.set_remote_credentials("remoteufrag", "remotepwd")
162168

163-
ice_agent = ICEAgent.add_remote_candidate(ice_agent, @remote_cand)
169+
ice_agent = ICEAgent.set_role(ice_agent, :controlling)
170+
assert ice_agent.role == :controlling
164171

165-
# assert that there are no pairs and no local cands
166-
assert [] == Map.values(ice_agent.checklist)
167-
assert [] == Map.values(ice_agent.local_cands)
172+
# role shouldn't change as we don't allow for changing the role once it has been initialized
173+
ice_agent = ICEAgent.set_role(ice_agent, :controlled)
174+
assert ice_agent.role == :controlling
168175

169-
# gather candidates
170-
ice_agent = ICEAgent.gather_candidates(ice_agent)
171-
[host_cand] = Map.values(ice_agent.local_cands)
176+
# assert that timer has not been fired as there is no work to do
177+
refute_receive :ta_timeout
178+
end
179+
180+
describe "gather_candidates/1" do
181+
setup do
182+
ice_agent =
183+
ICEAgent.new(
184+
controlling_process: self(),
185+
role: :controlling,
186+
if_discovery_module: IfDiscovery.Mock,
187+
transport_module: Transport.Mock
188+
)
189+
|> ICEAgent.set_remote_credentials("remoteufrag", "remotepwd")
190+
191+
%{ice_agent: ice_agent}
192+
end
193+
194+
test "when there already are remote candidates", %{ice_agent: ice_agent} do
195+
ice_agent = ICEAgent.add_remote_candidate(ice_agent, @remote_cand)
196+
197+
# assert that there are no pairs and no local cands
198+
assert [] == Map.values(ice_agent.checklist)
199+
assert [] == Map.values(ice_agent.local_cands)
172200

173-
# assert that a new pair has been created
174-
assert [%CandidatePair{} = cand_pair] = Map.values(ice_agent.checklist)
175-
assert cand_pair.local_cand_id == host_cand.base.id
201+
# gather candidates
202+
ice_agent = ICEAgent.gather_candidates(ice_agent)
203+
[host_cand] = Map.values(ice_agent.local_cands)
204+
205+
# assert that a new pair has been created
206+
assert [%CandidatePair{} = cand_pair] = Map.values(ice_agent.checklist)
207+
assert cand_pair.local_cand_id == host_cand.base.id
208+
end
209+
210+
test "without role", %{ice_agent: ice_agent} do
211+
ice_agent = %{ice_agent | role: nil}
212+
ice_agent = ICEAgent.gather_candidates(ice_agent)
213+
assert %{} == ice_agent.local_cands
214+
end
176215
end
177216

178217
test "handle_udp/5" do

0 commit comments

Comments
 (0)