Skip to content

Commit 99d4e94

Browse files
committed
Prepare for release
1 parent 6790fa2 commit 99d4e94

File tree

6 files changed

+125
-21
lines changed

6 files changed

+125
-21
lines changed

README.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,26 @@
11
# ExICE
22

3+
[![Package](https://img.shields.io/badge/-Package-important)](https://hex.pm/packages/ex_ice)
4+
[![Documentation](https://img.shields.io/badge/-Documentation-blueviolet)](https://hexdocs.pm/ex_ice)
35
[![codecov](https://codecov.io/gh/elixir-webrtc/ex_ice/branch/master/graph/badge.svg?token=83POQD1KST)](https://codecov.io/gh/elixir-webrtc/ex_ice)
46

5-
Implementation of Trickle ICE protocol - [RFC 8445](https://datatracker.ietf.org/doc/html/rfc8445) and [RFC 8838](https://datatracker.ietf.org/doc/html/rfc8838)
7+
Trickle ICE implementation.
8+
9+
RFC implemented:
10+
* [RFC 8445](https://datatracker.ietf.org/doc/html/rfc8445)
11+
* [RFC 8838](https://datatracker.ietf.org/doc/html/rfc8838)
12+
13+
## Features
14+
* compatible both with aggressive and regular nomination
15+
* role conflict resolution
16+
* supports host, prflx, srflx and remote relay candidates (support for local relay candidates is planned)
17+
* transaction pacing
18+
* keepalives on valid and selected pairs
19+
20+
## Limitations
21+
* there is always only one stream and one component -
22+
we don't plan to add support for multiple streams and components
23+
as WebRTC multiplexes traffic on a single socket but PRs are welcomed
624

725
## Installation
826

@@ -14,4 +32,13 @@ def deps do
1432
end
1533
```
1634

35+
## Usage
36+
37+
See our [example](https://github.com/elixir-webrtc/ex_ice/tree/master/example),
38+
[integration tests](https://github.com/elixir-webrtc/ex_ice/blob/master/test/integration/p2p_test.exs),
39+
and [documentation](https://hexdocs.pm/ex_ice/readme.html) for usage examples.
40+
41+
We also provide a very simple [signalling server](https://github.com/elixir-webrtc/ex_ice/tree/master/signalling_server), which can be used
42+
to connect two ICE agents.
43+
1744

lib/candidate.ex

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ defmodule ExICE.Candidate do
3434
type(),
3535
:inet.ip_address(),
3636
:inet.port_number(),
37-
:inet.ip_address(),
38-
:inet.port_number(),
39-
:inet.socket(),
37+
:inet.ip_address() | nil,
38+
:inet.port_number() | nil,
39+
:inet.socket() | nil,
4040
priority: integer()
4141
) :: t()
4242
def new(type, address, port, base_address, base_port, socket, opts \\ [])

lib/candidate_pair.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ defmodule ExICE.CandidatePair do
5555
}
5656
end
5757

58-
@spec schedule_keepalive(t(), Process.dest()) :: :ok
58+
@spec schedule_keepalive(t(), Process.dest()) :: t()
5959
def schedule_keepalive(pair, dest \\ self())
6060

6161
def schedule_keepalive(%{keepalive_timer: timer} = pair, dest) when is_reference(timer) do

lib/checklist.ex

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ defmodule ExICE.Checklist do
77

88
@type t() :: map()
99

10-
@spec get_next_pair(t()) :: CandidatePair.t()
10+
@spec get_next_pair(t()) :: CandidatePair.t() | nil
1111
def get_next_pair(checklist) do
1212
# FIXME correctly handle frozen pairs, according to sec 6.1.4.2
1313
checklist
@@ -16,27 +16,27 @@ defmodule ExICE.Checklist do
1616
|> elem(1)
1717
end
1818

19-
@spec get_pair_for_nomination(t()) :: CandidatePair.t()
19+
@spec get_pair_for_nomination(t()) :: CandidatePair.t() | nil
2020
def get_pair_for_nomination(checklist) do
2121
checklist
2222
|> Enum.filter(fn {_id, pair} -> pair.valid? end)
2323
|> Enum.max_by(fn {_id, pair} -> pair.priority end, fn -> {nil, nil} end)
2424
|> elem(1)
2525
end
2626

27-
@spec get_valid_pair(t()) :: CandidatePair.t()
27+
@spec get_valid_pair(t()) :: CandidatePair.t() | nil
2828
def get_valid_pair(checklist) do
2929
checklist
3030
|> Enum.find({nil, nil}, fn {_id, pair} -> pair.valid? end)
3131
|> elem(1)
3232
end
3333

34-
@spec find_pair(t(), CandidatePair.t()) :: CandidatePair.t()
34+
@spec find_pair(t(), CandidatePair.t()) :: CandidatePair.t() | nil
3535
def find_pair(checklist, pair) do
3636
find_pair(checklist, pair.local_cand, pair.remote_cand)
3737
end
3838

39-
@spec find_pair(t(), Candidate.t(), Candidate.t()) :: CandidatePair.t()
39+
@spec find_pair(t(), Candidate.t(), Candidate.t()) :: CandidatePair.t() | nil
4040
def find_pair(checklist, local_cand, remote_cand) do
4141
# TODO which pairs are actually the same?
4242
checklist
@@ -75,7 +75,7 @@ defmodule ExICE.Checklist do
7575

7676
@spec prune(t()) :: t()
7777
def prune(checklist) do
78-
# This is done according to RFC 8838 sec. 10
78+
# This is done according to RFC 8838 sec. 10
7979
{waiting, in_flight_or_done} =
8080
Enum.split_with(checklist, fn {_id, p} -> p.state in [:waiting, :frozen] end)
8181

lib/ice_agent.ex

Lines changed: 60 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ defmodule ExICE.ICEAgent do
3232

3333
@conn_check_handler %{controlling: ControllingHandler, controlled: ControlledHandler}
3434

35+
@typedoc """
36+
ICE agent role.
37+
38+
`:controlling` agent is responsible for nominating a pair.
39+
"""
3540
@type role() :: :controlling | :controlled
3641

3742
@typedoc """
@@ -72,52 +77,98 @@ defmodule ExICE.ICEAgent do
7277
stun_servers: [String.t()]
7378
]
7479

75-
defguard are_pairs_equal(p1, p2)
76-
when p1.local_cand.base_address == p2.local_cand.base_address and
77-
p1.local_cand.base_port == p2.local_cand.base_port and
78-
p1.local_cand.address == p2.local_cand.address and
79-
p1.local_cand.port == p2.local_cand.port and
80-
p1.remote_cand.address == p2.remote_cand.address and
81-
p1.remote_cand.port == p2.remote_cand.port
80+
defguardp are_pairs_equal(p1, p2)
81+
when p1.local_cand.base_address == p2.local_cand.base_address and
82+
p1.local_cand.base_port == p2.local_cand.base_port and
83+
p1.local_cand.address == p2.local_cand.address and
84+
p1.local_cand.port == p2.local_cand.port and
85+
p1.remote_cand.address == p2.remote_cand.address and
86+
p1.remote_cand.port == p2.remote_cand.port
8287

83-
defguard is_response(class) when class in [:success_response, :error_response]
88+
defguardp is_response(class) when class in [:success_response, :error_response]
8489

90+
@doc """
91+
Starts and links a new ICE agent.
92+
93+
Process calling this function is called a `controlling process` and
94+
has to be prepared for receiving ExICE messages described by `t:signal/0`.
95+
"""
8596
@spec start_link(role(), opts()) :: GenServer.on_start()
8697
def start_link(role, opts \\ []) do
8798
GenServer.start_link(__MODULE__, opts ++ [role: role, controlling_process: self()])
8899
end
89100

101+
@doc """
102+
Gets local credentials.
103+
104+
They remain unchanged until ICE restart.
105+
"""
90106
@spec get_local_credentials(pid()) :: {:ok, ufrag :: binary(), pwd :: binary()}
91107
def get_local_credentials(ice_agent) do
92108
GenServer.call(ice_agent, :get_local_credentials)
93109
end
94110

111+
@doc """
112+
Sets remote credentials.
113+
114+
Call to this function is mandatory to start connectivity checks.
115+
"""
95116
@spec set_remote_credentials(pid(), binary(), binary()) :: :ok
96117
def set_remote_credentials(ice_agent, ufrag, passwd)
97118
when is_binary(ufrag) and is_binary(passwd) do
98119
GenServer.cast(ice_agent, {:set_remote_credentials, ufrag, passwd})
99120
end
100121

122+
@doc """
123+
Starts ICE gathering process.
124+
125+
Once a new candidate is discovered, it is sent as a message to the controlling process.
126+
See `t:signal/0` for a message structure.
127+
"""
101128
@spec gather_candidates(pid()) :: :ok
102129
def gather_candidates(ice_agent) do
103130
GenServer.cast(ice_agent, :gather_candidates)
104131
end
105132

133+
@doc """
134+
Adds a remote candidate.
135+
136+
If an ICE agent has already gathered any local candidates and
137+
have remote credentials set, adding a remote candidate will start
138+
connectivity checks.
139+
"""
106140
@spec add_remote_candidate(pid(), String.t()) :: :ok
107141
def add_remote_candidate(ice_agent, candidate) when is_binary(candidate) do
108142
GenServer.cast(ice_agent, {:add_remote_candidate, candidate})
109143
end
110144

145+
@doc """
146+
Informs ICE agent that a remote side finished its gathering process.
147+
148+
Call to this function is mandatory to nominate a pair (when an agent is the `controlling` one)
149+
and in turn move to the `completed` state.
150+
"""
111151
@spec end_of_candidates(pid()) :: :ok
112152
def end_of_candidates(ice_agent) do
113153
GenServer.cast(ice_agent, :end_of_candidates)
114154
end
115155

156+
@doc """
157+
Sends data.
158+
159+
Can only be called after moving to the `connected` state.
160+
"""
116161
@spec send_data(pid(), binary()) :: :ok
117162
def send_data(ice_agent, data) when is_binary(data) do
118163
GenServer.cast(ice_agent, {:send_data, data})
119164
end
120165

166+
@doc """
167+
Restarts ICE.
168+
169+
If there were any valid pairs in the previous ICE session,
170+
data can still be sent.
171+
"""
121172
@spec restart(pid()) :: :ok
122173
def restart(ice_agent) do
123174
GenServer.cast(ice_agent, :restart)
@@ -998,7 +1049,7 @@ defmodule ExICE.ICEAgent do
9981049
Checklist pair: #{checklist_pair.id}.
9991050
""")
10001051

1001-
# if we get here, don't update discovered_pair_id and succeeded_pair_id of
1052+
# if we get here, don't update discovered_pair_id and succeeded_pair_id of
10021053
# the checklist pair as they are already set
10031054
conn_check_pair = %CandidatePair{
10041055
conn_check_pair

mix.exs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
11
defmodule ExICE.MixProject do
22
use Mix.Project
33

4+
@version "0.1.0"
5+
@source_url "https://github.com/elixir-webrtc/ex_ice"
6+
47
def project do
58
[
69
app: :ex_ice,
7-
version: "0.0.1",
10+
version: "0.1.0",
811
elixir: "~> 1.13",
912
start_permanent: Mix.env() == :prod,
13+
description: "Implementation of trickle ICE protocol",
14+
package: package(),
1015
deps: deps(),
1116

17+
# docs
18+
docs: docs(),
19+
source_url: @source_url,
20+
1221
# code coverage
1322
test_coverage: [tool: ExCoveralls],
1423
preferred_cli_env: [
@@ -27,6 +36,13 @@ defmodule ExICE.MixProject do
2736
]
2837
end
2938

39+
def package do
40+
[
41+
licenses: ["Apache-2.0"],
42+
links: %{"GitHub" => "https://github.com/elixir-webrtc/ex_ice"}
43+
]
44+
end
45+
3046
defp deps do
3147
[
3248
{:ex_stun, "~> 0.1.0"},
@@ -35,4 +51,14 @@ defmodule ExICE.MixProject do
3551
{:credo, "~> 1.6", only: [:dev, :test], runtime: false}
3652
]
3753
end
54+
55+
defp docs do
56+
[
57+
main: "readme",
58+
extras: ["README.md"],
59+
source_ref: "v#{@version}",
60+
formatters: ["html"],
61+
nest_modules_by_prefix: [ExICE]
62+
]
63+
end
3864
end

0 commit comments

Comments
 (0)