Current section

Files

Jump to
ex_ice lib ex_ice candidate.ex
Raw

lib/ex_ice/candidate.ex

defmodule ExICE.Candidate do
@moduledoc """
ICE candidate representation.
"""
@type type() :: :host | :srflx | :prflx | :relay
@type t() :: %__MODULE__{
id: integer(),
type: type(),
address: :inet.ip_address() | String.t(),
base_address: :inet.ip_address() | nil,
base_port: :inet.port_number() | nil,
foundation: integer(),
port: :inet.port_number(),
priority: integer(),
transport: :udp | :tcp
}
@enforce_keys [
:id,
:type,
:address,
:port,
:foundation,
:priority,
:transport
]
defstruct @enforce_keys ++ [:base_address, :base_port]
@spec marshal(t()) :: String.t()
def marshal(cand) do
component_id = 1
%__MODULE__{
foundation: foundation,
transport: transport,
priority: priority,
address: address,
port: port,
type: type
} = cand
transport = transport_to_string(transport)
address = address_to_string(address)
"#{foundation} #{component_id} #{transport} #{priority} #{address} #{port} typ #{type}"
end
@spec unmarshal(String.t()) :: {:ok, t()} | {:error, term()}
def unmarshal(string) do
with [f_str, c_str, tr_str, pr_str, a_str, po_str, "typ", ty_str] <-
String.split(string, " ", parts: 8),
{foundation, ""} <- Integer.parse(f_str),
{_component_id, ""} <- Integer.parse(c_str),
{:ok, transport} <- parse_transport(String.downcase(tr_str)),
{priority, ""} <- Integer.parse(pr_str),
{:ok, address} <- parse_address(a_str),
{port, ""} <- Integer.parse(po_str),
{:ok, type} <- parse_type(ty_str) do
{:ok,
new(
type,
address: address,
port: port,
priority: priority,
foundation: foundation,
transport: transport
)}
else
err when is_list(err) -> {:error, :invalid_candidate}
err -> err
end
end
@spec family(t()) :: :ipv4 | :ipv6
def family(%__MODULE__{address: {_, _, _, _}}), do: :ipv4
def family(%__MODULE__{address: {_, _, _, _, _, _, _, _}}), do: :ipv6
@doc false
@spec new(type(), Keyword.t()) :: t()
def new(type, config) when type in [:host, :srflx, :prflx, :relay] do
transport = Keyword.get(config, :transport, :udp)
priority = config[:priority] || ExICE.Priv.Candidate.priority(type)
address = Keyword.fetch!(config, :address)
%__MODULE__{
id: ExICE.Priv.Utils.id(),
address: address,
base_address: config[:base_address],
base_port: config[:base_port],
foundation: ExICE.Priv.Candidate.foundation(type, address, nil, transport),
port: Keyword.fetch!(config, :port),
priority: priority,
transport: transport,
type: type
}
end
defp address_to_string(address) when is_binary(address), do: address
defp address_to_string(address), do: :inet.ntoa(address)
defp transport_to_string(:udp), do: "UDP"
defp parse_transport("udp"), do: {:ok, :udp}
defp parse_transport(_other), do: {:error, :invalid_transport}
defp parse_address(address) do
if String.ends_with?(address, ".local") do
{:ok, address}
else
:inet.parse_address(String.to_charlist(address))
end
end
defp parse_type("host" <> _rest), do: {:ok, :host}
defp parse_type("srflx" <> _rest), do: {:ok, :srflx}
defp parse_type("prflx" <> _rest), do: {:ok, :prflx}
defp parse_type("relay" <> _rest), do: {:ok, :relay}
defp parse_type(_other), do: {:error, :invalid_type}
end