Skip to content

Commit

Permalink
Move URI module from ex_ice
Browse files Browse the repository at this point in the history
  • Loading branch information
mickel8 committed Mar 21, 2024
1 parent 39f3e8d commit 1ff345e
Show file tree
Hide file tree
Showing 4 changed files with 248 additions and 6 deletions.
24 changes: 24 additions & 0 deletions lib/ex_stun/message.ex
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,30 @@ defmodule ExSTUN.Message do
:crypto.hash(:md5, username <> ":" <> realm <> ":" <> password)
end

def authenticate_lt(msg, username, realm, password) do
with {:ok, %MessageIntegrity{} = msg_int} <- get_message_integrity(msg) do
key = username <> ":" <> realm <> ":" <> password
key = :crypto.hash(:md5, key)

# + 20 for STUN message header
# - 24 for message integrity
len = msg.len_to_int + 20 - 24
<<msg_without_integrity::binary-size(len), _rest::binary>> = msg.raw
<<pre_len::binary-size(2), _len::16, post_len::binary>> = msg_without_integrity
msg_without_integrity = <<pre_len::binary, msg.len_to_int::16, post_len::binary>>

mac = :crypto.mac(:hmac, :sha, key, msg_without_integrity)

if mac == msg_int.value do
{:ok, key}
else
{:error, :no_matching_message_integrity}
end
else
{:error, _reason} = err -> err
end
end

@doc """
Authenticates a message.
Expand Down
151 changes: 151 additions & 0 deletions lib/ex_stun/uri.ex
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
73 changes: 73 additions & 0 deletions test/ex_stun/uri_test.exs
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
6 changes: 0 additions & 6 deletions test/realm_test.exs

This file was deleted.

0 comments on commit 1ff345e

Please sign in to comment.