-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
248 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
defmodule ExSTUN.URI do | ||
@moduledoc """ | ||
Module representing STUN/TURN URI. | ||
Implementation of RFC 7064 and RFC 7065. | ||
We could try to use URI module from Elixir | ||
but RFC 7064 and RFC 7065 state: | ||
While these two ABNF productions are defined in [RFC3986] | ||
as components of the generic hierarchical URI, this does | ||
not imply that the "stun" and "stuns" URI schemes are | ||
hierarchical URIs. Developers MUST NOT use a generic | ||
hierarchical URI parser to parse a "stun" or "stuns" URI. | ||
""" | ||
|
||
@type scheme :: :stun | :stuns | :turn | :turns | ||
|
||
@type transport :: :udp | :tcp | ||
|
||
@type t() :: %__MODULE__{ | ||
scheme: scheme(), | ||
host: String.t(), | ||
port: :inet.port_number(), | ||
transport: transport() | ||
} | ||
|
||
@enforce_keys [:scheme, :host, :port, :transport] | ||
defstruct @enforce_keys | ||
|
||
@default_udp_tcp_port 3478 | ||
@default_tls_port 5349 | ||
|
||
@doc """ | ||
The same as parse/1 but raises on error. | ||
""" | ||
@spec parse!(String.t()) :: t() | ||
def parse!(uri) do | ||
case parse(uri) do | ||
{:ok, uri} -> uri | ||
:error -> raise "Invalid URI" | ||
end | ||
end | ||
|
||
@doc """ | ||
Parses URI string into `t:t/0`. | ||
""" | ||
@spec parse(String.t()) :: {:ok, t()} | :error | ||
def parse("stun" <> ":" <> host_port) do | ||
do_parse_stun(:stun, host_port) | ||
end | ||
|
||
def parse("stuns" <> ":" <> host_port) do | ||
do_parse_stun(:stuns, host_port) | ||
end | ||
|
||
def parse("turn" <> ":" <> host_port_transport) do | ||
do_parse_turn(:turn, host_port_transport) | ||
end | ||
|
||
def parse("turns" <> ":" <> host_port_transport) do | ||
do_parse_turn(:turns, host_port_transport) | ||
end | ||
|
||
def parse(_other), do: :error | ||
|
||
defp do_parse_stun(scheme, host_port) do | ||
transport = if scheme == :stun, do: :udp, else: :tcp | ||
default_port = if scheme == :stun, do: @default_udp_tcp_port, else: @default_tls_port | ||
|
||
with {:ok, host, rest} <- parse_host(host_port), | ||
{:ok, port, ""} <- parse_port(rest) do | ||
{:ok, | ||
%__MODULE__{ | ||
scheme: scheme, | ||
host: host, | ||
port: port || default_port, | ||
transport: transport | ||
}} | ||
end | ||
end | ||
|
||
defp do_parse_turn(scheme, host_port_transport) do | ||
default_port = if scheme == :turn, do: @default_udp_tcp_port, else: @default_tls_port | ||
default_transport = if scheme == :turn, do: :udp, else: :tcp | ||
|
||
with {:ok, host, rest} <- parse_host(host_port_transport), | ||
{:ok, port, rest} <- parse_port(rest), | ||
{:ok, transport} <- parse_transport(rest) do | ||
{:ok, | ||
%__MODULE__{ | ||
scheme: scheme, | ||
host: host, | ||
port: port || default_port, | ||
transport: transport || default_transport | ||
}} | ||
end | ||
end | ||
|
||
defp parse_host(data) do | ||
case String.split(data, ":", parts: 2) do | ||
[host, rest] when host != "" -> | ||
{:ok, host, rest} | ||
|
||
[host] when host != "" -> | ||
case String.split(host, "?transport=") do | ||
[host, rest] when host != "" -> {:ok, host, "?transport=" <> rest} | ||
[host] -> {:ok, host, ""} | ||
_ -> :error | ||
end | ||
|
||
_ -> | ||
:error | ||
end | ||
end | ||
|
||
defp parse_port(""), do: {:ok, nil, ""} | ||
defp parse_port("?transport=" <> rest), do: {:ok, nil, rest} | ||
|
||
defp parse_port(data) do | ||
case String.split(data, "?transport=", parts: 2) do | ||
[port, rest] -> | ||
case do_parse_port(port) do | ||
{:ok, port} -> {:ok, port, rest} | ||
:error -> :error | ||
end | ||
|
||
[port] -> | ||
case do_parse_port(port) do | ||
{:ok, port} -> {:ok, port, ""} | ||
:error -> :error | ||
end | ||
|
||
_ -> | ||
:error | ||
end | ||
end | ||
|
||
defp do_parse_port(port) do | ||
case Integer.parse(port) do | ||
{port, ""} -> {:ok, port} | ||
:error -> :error | ||
end | ||
end | ||
|
||
defp parse_transport(""), do: {:ok, nil} | ||
defp parse_transport("udp"), do: {:ok, :udp} | ||
defp parse_transport("tcp"), do: {:ok, :tcp} | ||
defp parse_transport(_), do: :error | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
defmodule ExSTUN.URITest do | ||
use ExUnit.Case, async: true | ||
|
||
alias ExSTUN.URI | ||
|
||
describe "parse/1" do | ||
test "with valid URI" do | ||
for {uri_string, expected_uri} <- [ | ||
{ | ||
"stun:stun.l.google.com:19302", | ||
%URI{scheme: :stun, host: "stun.l.google.com", port: 19_302, transport: :udp} | ||
}, | ||
{ | ||
"stuns:stun.l.google.com:19302", | ||
%URI{scheme: :stuns, host: "stun.l.google.com", port: 19_302, transport: :tcp} | ||
}, | ||
{ | ||
"stun:stun.l.google.com", | ||
%URI{scheme: :stun, host: "stun.l.google.com", port: 3478, transport: :udp} | ||
}, | ||
{ | ||
"stuns:stun.l.google.com", | ||
%URI{scheme: :stuns, host: "stun.l.google.com", port: 5349, transport: :tcp} | ||
}, | ||
{ | ||
"turn:example.org", | ||
%URI{scheme: :turn, host: "example.org", port: 3478, transport: :udp} | ||
}, | ||
{ | ||
"turns:example.org", | ||
%URI{scheme: :turns, host: "example.org", port: 5349, transport: :tcp} | ||
}, | ||
{ | ||
"turn:example.org:8000", | ||
%URI{scheme: :turn, host: "example.org", port: 8000, transport: :udp} | ||
}, | ||
{ | ||
"turn:example.org?transport=udp", | ||
%URI{scheme: :turn, host: "example.org", port: 3478, transport: :udp} | ||
}, | ||
{ | ||
"turn:example.org:1234?transport=udp", | ||
%URI{scheme: :turn, host: "example.org", port: 1234, transport: :udp} | ||
}, | ||
{ | ||
"turn:example.org?transport=tcp", | ||
%URI{scheme: :turn, host: "example.org", port: 3478, transport: :tcp} | ||
}, | ||
{ | ||
"turns:example.org?transport=tcp", | ||
%URI{scheme: :turns, host: "example.org", port: 5349, transport: :tcp} | ||
} | ||
] do | ||
assert {:ok, expected_uri} == URI.parse(uri_string) | ||
end | ||
end | ||
|
||
test "with invalid URI" do | ||
for invalid_uri_string <- [ | ||
"", | ||
"some random string", | ||
"stun:", | ||
"stun::", | ||
"stun::19302", | ||
"abcd:stun.l.google.com:19302", | ||
"stun:stun.l.google.com:ab123", | ||
"stuns:stun.l.google.com:ab123" | ||
] do | ||
assert :error == URI.parse(invalid_uri_string) | ||
end | ||
end | ||
end | ||
end |
This file was deleted.
Oops, something went wrong.