Packages
sippet
1.0.16
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.ex
defmodule Sippet do
@moduledoc """
Holds the Sippet stack.
Network transport protocols should be registered during initialization:
def init(_) do
Sippet.register_transport(:udp, false)
...
end
Messages are dispatched to transports by sending the following message:
send(pid, {:send_message, message, host, port, transaction})
Whenever a message is received by a transport, the function
`Sippet.handle_transport_message` is called, which will validate and route
messages through the transaction layer or send directly to the core.
"""
use Supervisor
import Kernel, except: [send: 2]
alias Sippet.{Message, Transactions}
alias Sippet.Message.{RequestLine, StatusLine}
require Logger
@typedoc "A SIP message request"
@type request :: Message.request()
@typedoc "A SIP message response"
@type response :: Message.response()
@typedoc "An network error that occurred while sending a message"
@type reason :: term
@typedoc "A client transaction identifier"
@type client_key :: Transactions.Client.Key.t()
@typedoc "A server transaction identifier"
@type server_key :: Transactions.Server.Key.t()
@typedoc "Sippet identifier"
@type sippet :: atom
@doc """
Handles the sigil `~K`.
It returns a client or server transaction key depending on the number of
parameters passed.
## Examples
iex> import Sippet, only: [sigil_K: 2]
iex> Sippet.Transactions.Client.Key.new("z9hG4bK230f2.1", :invite)
~K[z9hG4bK230f2.1|:invite]
iex> ~K[z9hG4bK230f2.1|INVITE]
~K[z9hG4bK230f2.1|:invite]
iex> Sippet.Transactions.Server.Key.new("z9hG4bK74b21", :invite, {"client.biloxi.example.com", 5060})
~K[z9hG4bK74b21|:invite|client.biloxi.example.com:5060]
iex> ~K[z9hG4bK74b21|INVITE|client.biloxi.example.com:5060]
~K[z9hG4bK74b21|:invite|client.biloxi.example.com:5060]
"""
def sigil_K(string, _) do
case String.split(string, "|") do
[branch, method] ->
Transactions.Client.Key.new(branch, sigil_to_method(method))
[branch, method, sentby] ->
[host, port] = String.split(sentby, ":")
Transactions.Server.Key.new(
branch,
sigil_to_method(method),
{host, String.to_integer(port)}
)
end
end
defp sigil_to_method(method) do
case method do
":" <> rest -> Message.to_method(rest)
other -> Message.to_method(other)
end
end
@doc """
Sends a message (request or response) using transactions if possible.
Requests of method `:ack` is sent directly to the transport layer.
A `Sippet.Transactions.Client` is created for requests to handle client
retransmissions, when the transport presumes it, and match response
retransmissions, so the `Sippet.Core` doesn't get retransmissions other than
200 OK for `:invite` requests.
In case of success, returns `:ok`.
"""
@spec send(sippet, request | response) :: :ok | {:error, reason}
def send(sippet, message) when is_atom(sippet) do
unless Message.valid?(message) do
raise ArgumentError, "expected :message argument to be a valid SIP message"
end
do_send(sippet, message)
end
defp do_send(sippet, %Message{start_line: %RequestLine{method: :ack}} = request),
do: Sippet.Router.send_transport_message(sippet, request, nil)
defp do_send(sippet, %Message{start_line: %RequestLine{}} = outgoing_request),
do: Sippet.Router.send_transaction_request(sippet, outgoing_request)
defp do_send(sippet, %Message{start_line: %StatusLine{}} = outgoing_response),
do: Sippet.Router.send_transaction_response(sippet, outgoing_response)
@doc """
Verifies if the transport protocol used to send the given message is
reliable.
"""
@spec reliable?(sippet, Message.t()) :: boolean
def reliable?(sippet, %Message{headers: %{via: [via | _]}})
when is_atom(sippet) do
{_version, protocol, _host_and_port, _params} = via
case Registry.lookup(sippet, {:transport, protocol}) do
[{_, reliable}] ->
reliable
_ ->
raise ArgumentError, message: "protocol not registered"
end
end
@doc """
Registers a transport for a given protocol.
"""
@spec register_transport(sippet, atom, boolean) :: :ok | {:error, :already_registered}
def register_transport(sippet, protocol, reliable)
when is_atom(sippet) and is_atom(protocol) and is_boolean(reliable) do
case Registry.register(sippet, {:transport, protocol}, reliable) do
{:ok, _} ->
:ok
{:error, {:already_registered, _}} ->
{:error, :already_registered}
end
end
@doc """
Registers the stack core.
"""
@spec register_core(sippet, atom) :: :ok
def register_core(sippet, module)
when is_atom(sippet) and is_atom(module) do
Registry.put_meta(sippet, :core, module)
end
@doc """
Terminates a client or server transaction forcefully.
This function is not generally executed by entities; there is a single case
where it is fundamental, which is when a client transaction is in proceeding
state for a long time, and the transaction has to be finished forcibly, or it
will never finish by itself.
If a transaction with such a key does not exist, it will be silently ignored.
"""
@spec terminate(sippet, client_key | server_key) :: :ok
def terminate(sippet, key) do
case Registry.lookup(sippet, {:transaction, key}) do
[] ->
:ok
[{pid, _}] ->
# Send the response through the existing server key.
case key do
%Transactions.Client.Key{} ->
Transactions.Client.terminate(pid)
%Transactions.Server.Key{} ->
Transactions.Server.terminate(pid)
end
end
end
@doc false
def start_link(options) when is_list(options) do
case Keyword.fetch(options, :name) do
{:ok, name} when is_atom(name) ->
:ok
{:ok, other} ->
raise ArgumentError, "expected :name to be an atom, got: #{inspect(other)}"
:error ->
raise ArgumentError, "expected :name option to be present"
end
Supervisor.start_link(__MODULE__, options)
end
@doc false
def child_spec(options) do
%{
id: __MODULE__,
start: {__MODULE__, :start_link, [options]}
}
end
@impl true
def init(options) do
children = [
{Registry, name: options[:name], keys: :unique, partitions: System.schedulers_online()},
{DynamicSupervisor, strategy: :one_for_one, name: supervisor_name(options[:name])}
]
Supervisor.init(children, strategy: :one_for_one)
end
@doc false
def supervisor_name(name) do
Module.concat(name, "Supervisor")
end
end