Packages
parrot_platform
0.0.1-alpha.2
Elixir libraries and OTP behaviours for building telecom applications with SIP protocol and media handling
Current section
Files
Jump to
Current section
Files
lib/parrot/sip/uas.ex
defmodule Parrot.Sip.UAS do
@moduledoc """
Parrot SIP Stack
UAS (User Agent Server)
This module provides functionality for the server side of SIP transactions,
including handling incoming requests and generating responses.
"""
alias Parrot.Sip.Transaction
alias Parrot.Sip.Dialog
alias Parrot.Sip.Handler
alias Parrot.Sip.TransactionStatem
alias Parrot.Sip.Message
require Logger
@spec process(any(), Message.t(), Handler.handler()) ::
:ok
def process(trans, sip_msg0, handler) do
Logger.debug("UAS: process #{inspect(sip_msg0.method)}")
process_list = [
fn sip_msg ->
# Replace ersip_uas.process_request with pure Elixir validation
validate_request(sip_msg)
end,
fn sip_msg ->
Logger.debug("UAS: process_request #{inspect(sip_msg.method)}")
# Dialog.uas_request currently only returns :process
Dialog.uas_request(sip_msg)
Logger.debug("UAS: process_request process #{inspect(sip_msg.method)}")
{:process, sip_msg}
end,
fn sip_msg ->
case sip_msg.method == :cancel do
false -> {:process, sip_msg}
true -> TransactionStatem.server_cancel(sip_msg)
end
end,
fn sip_msg ->
# Instead of make_uas, use the Transaction struct directly.
# Handler.uas_request expects a transaction struct with role: :uas
Handler.uas_request(trans, sip_msg, handler)
end
]
case do_process(process_list, sip_msg0) do
{:reply, resp} -> TransactionStatem.server_response(resp, trans)
_ -> :ok
end
end
@spec process_ack(Message.t(), Handler.handler()) :: :ok
def process_ack(req_sip_msg, _handler) do
# Dialog.uas_find currently always returns :not_found
Dialog.uas_find(req_sip_msg)
require Logger
Logger.warning("uas: cannot find dialog for ACK")
:ok
end
@spec process_cancel(Transaction.t(), Handler.handler()) :: :ok
def process_cancel(trans, handler) do
id = {:uas_id, trans}
# TODO: in-dialog CANCEL?
Handler.uas_cancel(id, handler)
end
@spec response(Message.t(), Transaction.t()) :: :ok
def response(resp_sip_msg0, %Transaction{request: req_sip_msg} = transaction) do
Logger.debug("UAS: response #{inspect(resp_sip_msg0.method)}")
resp_sip_msg = Dialog.uas_response(resp_sip_msg0, req_sip_msg)
TransactionStatem.server_response(resp_sip_msg, transaction)
end
@spec response_retransmit(Message.t(), Transaction.t()) :: :ok
def response_retransmit(resp_sip_msg, transaction) do
TransactionStatem.server_response(resp_sip_msg, transaction)
end
@spec sipmsg(Transaction.t()) :: Message.t()
def sipmsg(%Transaction{request: req_sip_msg}), do: req_sip_msg
@spec make_reply(integer(), binary(), Transaction.t(), Message.t()) :: Message.t()
def make_reply(code, reason_phrase, %Transaction{} = _transaction, req_sip_msg) do
Logger.debug("UAS: make_reply #{inspect(code)} #{inspect(reason_phrase)}")
# Create response using Message.reply
response = Message.reply(req_sip_msg, code, reason_phrase)
# For dialog-creating responses (2xx to INVITE/SUBSCRIBE), add a To tag if not present
case {response.headers["to"], code, req_sip_msg.method} do
{%{parameters: params} = to_header, status_code, method}
when status_code >= 200 and status_code < 300 and
method in [:invite, :subscribe] ->
# Check if tag already exists
if Map.has_key?(params, "tag") do
response
else
# Generate and add To tag for dialog-creating responses
tag = generate_tag()
updated_to = %{to_header | parameters: Map.put(params, "tag", tag)}
%{response | headers: Map.put(response.headers, "to", updated_to)}
end
_ ->
# Keep response as is (non-dialog creating)
response
end
end
@spec set_owner(integer(), pid(), Transaction.t()) :: :ok
def set_owner(auto_resp_code, pid, transaction) do
TransactionStatem.server_set_owner(auto_resp_code, pid, transaction)
end
# Internal implementation
# Generate a random tag for responses
@spec generate_tag() :: binary()
defp generate_tag do
:crypto.strong_rand_bytes(6)
|> Base.encode32(case: :lower, padding: false)
end
# Validate incoming SIP request - replaces ersip_uas.process_request
@spec validate_request(Message.t()) :: {:process, Message.t()} | {:reply, Message.t()}
defp validate_request(%Message{method: method} = sip_msg) do
allowed_methods = [:invite, :ack, :bye, :cancel, :options, :register]
case method in allowed_methods do
true ->
# Method is allowed, continue processing
{:process, sip_msg}
false ->
# Method not allowed, return 405 Method Not Allowed
response = make_method_not_allowed_response(sip_msg, allowed_methods)
{:reply, response}
end
end
# Create 405 Method Not Allowed response
@spec make_method_not_allowed_response(Message.t(), [atom()]) :: Message.t()
defp make_method_not_allowed_response(req_msg, allowed_methods) do
allow_header = Enum.map(allowed_methods, &to_string/1) |> Enum.join(", ")
response = Message.reply(req_msg, 405, "Method Not Allowed")
%{response | headers: Map.put(response.headers, "allow", allow_header)}
end
@spec do_process(
[
(Message.t() ->
:ok | {:process, Message.t()} | {:reply, Message.t()})
],
Message.t()
) :: :ok | {:reply, Message.t()}
defp do_process([], _), do: :ok
defp do_process([f | rest], sip_msg) do
case f.(sip_msg) do
:ok -> :ok
{:reply, _} = reply -> reply
{:process, sip_msg1} -> do_process(rest, sip_msg1)
end
end
end