Skip to content

Commit 6a4f216

Browse files
committed
Move URI module from ex_ice
1 parent 692783d commit 6a4f216

File tree

3 files changed

+224
-6
lines changed

3 files changed

+224
-6
lines changed

lib/ex_stun/uri.ex

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
defmodule ExSTUN.URI do
2+
@moduledoc """
3+
Module representing STUN/TURN URI.
4+
5+
Implementation of RFC 7064 and RFC 7065.
6+
7+
We could try to use URI module from Elixir
8+
but RFC 7064 and RFC 7065 state:
9+
10+
While these two ABNF productions are defined in [RFC3986]
11+
as components of the generic hierarchical URI, this does
12+
not imply that the "stun" and "stuns" URI schemes are
13+
hierarchical URIs. Developers MUST NOT use a generic
14+
hierarchical URI parser to parse a "stun" or "stuns" URI.
15+
16+
"""
17+
18+
@type scheme :: :stun | :stuns | :turn | :turns
19+
20+
@type transport :: :udp | :tcp
21+
22+
@type t() :: %__MODULE__{
23+
scheme: scheme(),
24+
host: String.t(),
25+
port: :inet.port_number(),
26+
transport: transport()
27+
}
28+
29+
@enforce_keys [:scheme, :host, :port, :transport]
30+
defstruct @enforce_keys
31+
32+
@default_udp_tcp_port 3478
33+
@default_tls_port 5349
34+
35+
@doc """
36+
The same as parse/1 but raises on error.
37+
"""
38+
@spec parse!(String.t()) :: t()
39+
def parse!(uri) do
40+
case parse(uri) do
41+
{:ok, uri} -> uri
42+
:error -> raise "Invalid URI"
43+
end
44+
end
45+
46+
@doc """
47+
Parses URI string into `t:t/0`.
48+
"""
49+
@spec parse(String.t()) :: {:ok, t()} | :error
50+
def parse("stun" <> ":" <> host_port) do
51+
do_parse_stun(:stun, host_port)
52+
end
53+
54+
def parse("stuns" <> ":" <> host_port) do
55+
do_parse_stun(:stuns, host_port)
56+
end
57+
58+
def parse("turn" <> ":" <> host_port_transport) do
59+
do_parse_turn(:turn, host_port_transport)
60+
end
61+
62+
def parse("turns" <> ":" <> host_port_transport) do
63+
do_parse_turn(:turns, host_port_transport)
64+
end
65+
66+
def parse(_other), do: :error
67+
68+
defp do_parse_stun(scheme, host_port) do
69+
transport = if scheme == :stun, do: :udp, else: :tcp
70+
default_port = if scheme == :stun, do: @default_udp_tcp_port, else: @default_tls_port
71+
72+
with {:ok, host, rest} <- parse_host(host_port),
73+
{:ok, port, ""} <- parse_port(rest) do
74+
{:ok,
75+
%__MODULE__{
76+
scheme: scheme,
77+
host: host,
78+
port: port || default_port,
79+
transport: transport
80+
}}
81+
end
82+
end
83+
84+
defp do_parse_turn(scheme, host_port_transport) do
85+
default_port = if scheme == :turn, do: @default_udp_tcp_port, else: @default_tls_port
86+
default_transport = if scheme == :turn, do: :udp, else: :tcp
87+
88+
with {:ok, host, rest} <- parse_host(host_port_transport),
89+
{:ok, port, rest} <- parse_port(rest),
90+
{:ok, transport} <- parse_transport(rest) do
91+
{:ok,
92+
%__MODULE__{
93+
scheme: scheme,
94+
host: host,
95+
port: port || default_port,
96+
transport: transport || default_transport
97+
}}
98+
end
99+
end
100+
101+
defp parse_host(data) do
102+
case String.split(data, ":", parts: 2) do
103+
[host, rest] when host != "" ->
104+
{:ok, host, rest}
105+
106+
[host] when host != "" ->
107+
case String.split(host, "?transport=") do
108+
[host, rest] when host != "" -> {:ok, host, "?transport=" <> rest}
109+
[host] -> {:ok, host, ""}
110+
_ -> :error
111+
end
112+
113+
_ ->
114+
:error
115+
end
116+
end
117+
118+
defp parse_port(""), do: {:ok, nil, ""}
119+
defp parse_port("?transport=" <> rest), do: {:ok, nil, rest}
120+
121+
defp parse_port(data) do
122+
case String.split(data, "?transport=", parts: 2) do
123+
[port, rest] ->
124+
case do_parse_port(port) do
125+
{:ok, port} -> {:ok, port, rest}
126+
:error -> :error
127+
end
128+
129+
[port] ->
130+
case do_parse_port(port) do
131+
{:ok, port} -> {:ok, port, ""}
132+
:error -> :error
133+
end
134+
135+
_ ->
136+
:error
137+
end
138+
end
139+
140+
defp do_parse_port(port) do
141+
case Integer.parse(port) do
142+
{port, ""} -> {:ok, port}
143+
:error -> :error
144+
end
145+
end
146+
147+
defp parse_transport(""), do: {:ok, nil}
148+
defp parse_transport("udp"), do: {:ok, :udp}
149+
defp parse_transport("tcp"), do: {:ok, :tcp}
150+
defp parse_transport(_), do: :error
151+
end

test/ex_stun/uri_test.exs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
defmodule ExSTUN.URITest do
2+
use ExUnit.Case, async: true
3+
4+
alias ExSTUN.URI
5+
6+
describe "parse/1" do
7+
test "with valid URI" do
8+
for {uri_string, expected_uri} <- [
9+
{
10+
"stun:stun.l.google.com:19302",
11+
%URI{scheme: :stun, host: "stun.l.google.com", port: 19_302, transport: :udp}
12+
},
13+
{
14+
"stuns:stun.l.google.com:19302",
15+
%URI{scheme: :stuns, host: "stun.l.google.com", port: 19_302, transport: :tcp}
16+
},
17+
{
18+
"stun:stun.l.google.com",
19+
%URI{scheme: :stun, host: "stun.l.google.com", port: 3478, transport: :udp}
20+
},
21+
{
22+
"stuns:stun.l.google.com",
23+
%URI{scheme: :stuns, host: "stun.l.google.com", port: 5349, transport: :tcp}
24+
},
25+
{
26+
"turn:example.org",
27+
%URI{scheme: :turn, host: "example.org", port: 3478, transport: :udp}
28+
},
29+
{
30+
"turns:example.org",
31+
%URI{scheme: :turns, host: "example.org", port: 5349, transport: :tcp}
32+
},
33+
{
34+
"turn:example.org:8000",
35+
%URI{scheme: :turn, host: "example.org", port: 8000, transport: :udp}
36+
},
37+
{
38+
"turn:example.org?transport=udp",
39+
%URI{scheme: :turn, host: "example.org", port: 3478, transport: :udp}
40+
},
41+
{
42+
"turn:example.org:1234?transport=udp",
43+
%URI{scheme: :turn, host: "example.org", port: 1234, transport: :udp}
44+
},
45+
{
46+
"turn:example.org?transport=tcp",
47+
%URI{scheme: :turn, host: "example.org", port: 3478, transport: :tcp}
48+
},
49+
{
50+
"turns:example.org?transport=tcp",
51+
%URI{scheme: :turns, host: "example.org", port: 5349, transport: :tcp}
52+
}
53+
] do
54+
assert {:ok, expected_uri} == URI.parse(uri_string)
55+
end
56+
end
57+
58+
test "with invalid URI" do
59+
for invalid_uri_string <- [
60+
"",
61+
"some random string",
62+
"stun:",
63+
"stun::",
64+
"stun::19302",
65+
"abcd:stun.l.google.com:19302",
66+
"stun:stun.l.google.com:ab123",
67+
"stuns:stun.l.google.com:ab123"
68+
] do
69+
assert :error == URI.parse(invalid_uri_string)
70+
end
71+
end
72+
end
73+
end

test/realm_test.exs

Lines changed: 0 additions & 6 deletions
This file was deleted.

0 commit comments

Comments
 (0)