Packages
sippet
1.0.12
1.0.16
1.0.15
1.0.14
1.0.13
1.0.12
1.0.11
1.0.10
1.0.9
1.0.8
1.0.7
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.9
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.9
0.1.8
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
An Elixir Session Initiation Protocol (SIP) stack.
Current section
Files
Jump to
Current section
Files
lib/sippet/router.ex
defmodule Sippet.Router do
@moduledoc false
alias Sippet.{Message, Transactions, URI}
alias Sippet.Message.{RequestLine, StatusLine}
require Logger
import Sippet, only: [supervisor_name: 1]
@doc false
def handle_transport_message(sippet, iodata, from) when is_list(iodata) do
binary =
iodata
|> IO.iodata_to_binary()
handle_transport_message(sippet, binary, from)
end
def handle_transport_message(_sippet, "", _from), do: :ok
def handle_transport_message(sippet, "\n" <> rest, from),
do: handle_transport_message(sippet, rest, from)
def handle_transport_message(sippet, "\r\n" <> rest, from),
do: handle_transport_message(sippet, rest, from)
def handle_transport_message(sippet, raw, from) do
with {:ok, message} <- parse_message(raw),
prepared_message <- update_via(message, from),
:ok <- Message.validate(prepared_message, from) do
receive_transport_message(sippet, prepared_message)
else
{:error, reason} ->
Logger.error(fn ->
{protocol, address, port} = from
[
"discarded message from ",
"#{ip_to_string(address)}:#{port}/#{protocol}: ",
"#{inspect(reason)}"
]
end)
end
end
defp parse_message(packet) do
case String.split(packet, ~r{\r?\n\r?\n}, parts: 2) do
[header, body] ->
parse_message(header, body)
[header] ->
parse_message(header, "")
end
end
defp parse_message(header, body) do
case Message.parse(header) do
{:ok, message} -> {:ok, %{message | body: body}}
other -> other
end
end
defp ip_to_string(ip) when is_binary(ip), do: ip
defp ip_to_string(ip) when is_tuple(ip), do: :inet.ntoa(ip) |> to_string()
defp update_via(%Message{start_line: %RequestLine{}} = request, {:wss, _ip, _from_port}), do: request
defp update_via(%Message{start_line: %RequestLine{}} = request, {:ws, _ip, _from_port}), do: request
defp update_via(%Message{start_line: %RequestLine{}} = request, {_protocol, ip, from_port}) do
request
|> Message.update_header_back(:via, fn
{version, protocol, {via_host, via_port}, params} ->
host = ip |> ip_to_string()
params =
if host != via_host do
params |> Map.put("received", host)
else
params
end
params =
if from_port != via_port do
params |> Map.put("rport", to_string(from_port))
else
params
end
{version, protocol, {via_host, via_port}, params}
end)
end
defp update_via(%Message{start_line: %StatusLine{}} = response, _from), do: response
@doc false
def receive_transport_error(sippet, transaction_key, reason) do
case Registry.lookup(sippet, {:transaction, transaction_key}) do
[] ->
Logger.warning(fn ->
case transaction_key do
%Transactions.Client.Key{} ->
"client key #{inspect(transaction_key)} not found"
%Transactions.Server.Key{} ->
"server key #{inspect(transaction_key)} not found"
end
end)
[{pid, _}] ->
# Send the response through the existing server key.
case transaction_key do
%Transactions.Client.Key{} ->
Transactions.Client.receive_error(pid, reason)
%Transactions.Server.Key{} ->
Transactions.Server.receive_error(pid, reason)
end
end
:ok
end
@doc false
def send_transport_message(sippet, message, key) do
{protocol, host, port} = get_destination(message)
GenServer.call(
{:via, Registry, {sippet, {:transport, protocol}}},
{:send_message, message, host, port, key}
)
end
@doc false
def to_core(sippet, fun, args) do
case Registry.meta(sippet, :core) do
:error ->
raise RuntimeError, "Core not initialized"
{:ok, module} ->
apply(module, fun, args)
end
end
@doc false
def send_transaction_request(sippet, %Message{start_line: %RequestLine{}} = outgoing_request) do
transaction = Transactions.Client.Key.new(outgoing_request)
# Create a new client transaction now. The request is passed to the
# transport once it starts.
case start_client(sippet, transaction, outgoing_request) do
{:ok, _} ->
:ok
{:ok, _, _} ->
:ok
_errors ->
Logger.warning(fn ->
"client transaction #{transaction} already exists"
end)
{:error, :already_started}
end
end
@doc false
def send_transaction_response(sippet, %Message{start_line: %StatusLine{}} = outgoing_response) do
server_key = Transactions.Server.Key.new(outgoing_response)
case Registry.lookup(sippet, {:transaction, server_key}) do
[] ->
{:error, :no_transaction}
[{pid, _}] ->
# Send the response through the existing server transaction.
Transactions.Server.send_response(pid, outgoing_response)
end
end
@doc false
defp receive_transport_message(sippet, %Message{start_line: %RequestLine{}} = incoming_request) do
transaction = Transactions.Server.Key.new(incoming_request)
case Registry.lookup(sippet, {:transaction, transaction}) do
[] ->
if incoming_request.start_line.method == :ack do
# Redirect to the core directly. ACKs sent out of transactions
# pertain to the core.
to_core(sippet, :receive_request, [incoming_request, nil])
else
# Start a new server transaction now. The transaction will redirect
# to the core once it starts. It will return errors only if there was
# some kind of race condition when receiving the request.
start_server(sippet, transaction, incoming_request)
end
[{pid, _}] ->
# Redirect the request to the existing transaction. These are tipically
# retransmissions or ACKs for 200 OK responses.
Transactions.Server.receive_request(pid, incoming_request)
end
end
@doc false
defp receive_transport_message(sippet, %Message{start_line: %StatusLine{}} = incoming_response) do
transaction = Transactions.Client.Key.new(incoming_response)
case Registry.lookup(sippet, {:transaction, transaction}) do
[] ->
# Redirect the response to core. These are tipically retransmissions of
# 200 OK for sent INVITE requests, and they have to be handled directly
# by the core in order to catch the correct media handling.
to_core(sippet, :receive_response, [incoming_response, nil])
[{pid, _}] ->
# Redirect the response to the existing client transaction. If needed,
# the client transaction will redirect to the core from there.
Transactions.Client.receive_response(pid, incoming_response)
end
end
defp start_client(
sippet,
%Transactions.Client.Key{} = key,
%Message{start_line: %RequestLine{}} = outgoing_request
) do
module =
case key.method do
:invite -> Transactions.Client.Invite
_otherwise -> Transactions.Client.NonInvite
end
initial_data = Transactions.Client.State.new(outgoing_request, key, sippet)
DynamicSupervisor.start_child(
supervisor_name(sippet),
{module, [initial_data, [name: {:via, Registry, {sippet, {:transaction, key}}}]]}
)
end
defp start_server(
sippet,
%Transactions.Server.Key{} = key,
%Message{start_line: %RequestLine{}} = incoming_request
) do
module =
case key.method do
:invite -> Transactions.Server.Invite
_otherwise -> Transactions.Server.NonInvite
end
initial_data = Transactions.Server.State.new(incoming_request, key, sippet)
DynamicSupervisor.start_child(
supervisor_name(sippet),
{module, [initial_data, [name: {:via, Registry, {sippet, {:transaction, key}}}]]}
)
end
defp get_destination(%Message{target: target}) when is_tuple(target),
do: target
defp get_destination(%Message{start_line: %StatusLine{}, headers: %{via: via}} = message) do
{_version, protocol, {host, port}, params} = hd(via)
{host, port} =
if Message.response?(message) do
host =
case params do
%{"received" => received} -> received
_otherwise -> host
end
port =
case params do
%{"rport" => ""} -> port
%{"rport" => rport} -> rport |> String.to_integer()
_otherwise -> port
end
{host, port}
else
{host, port}
end
{protocol, host, port}
end
defp get_destination(%Message{start_line: %RequestLine{request_uri: uri}} = request) do
host = uri.host
port = uri.port
params =
if uri.parameters == nil do
%{}
else
URI.decode_parameters(uri.parameters)
end
protocol =
if params |> Map.has_key?("transport") do
Sippet.Message.to_protocol(params["transport"])
else
{_version, protocol, _sent_by, _params} = hd(request.headers.via)
protocol
end
{protocol, host, port}
end
end