Parrot.UasHandler behaviour (Parrot Platform v0.0.1-alpha.2)
Behaviour for implementing SIP User Agent Server (UAS) handlers in Parrot.
The Parrot.UasHandler
behaviour is the primary interface for building SIP server applications
with the Parrot framework. By implementing this behaviour, you can handle incoming
SIP requests and control how your application responds to various SIP methods.
This behaviour follows the SIP protocol as defined in RFC 3261.
Summary
Parrot handles all the complex SIP protocol details (transactions, retransmissions, timers, dialog state) automatically. Your handler only needs to implement the business logic for each SIP method you want to support.
Basic Usage
defmodule MyApp.UasHandler do
use Parrot.UasHandler
@impl true
def init(args) do
{:ok, %{calls: %{}}}
end
@impl true
def handle_invite(message, state) do
# Accept the call with 200 OK
{:respond, 200, "OK", %{"content-type" => "application/sdp"}, sdp_body, state}
end
@impl true
def handle_bye(_message, state) do
# End the call
{:respond, 200, "OK", %{}, "", state}
end
end
State Management
Each handler instance maintains its own state across the lifecycle of a dialog/call. The state is passed to each callback and the updated state from your response is preserved for subsequent callbacks.
Response Types
Handler callbacks can return several response types:
{:respond, status, reason, headers, body, state}
- Send a SIP response{:proxy, uri, state}
- Proxy the request to another URI{:noreply, state}
- Don't send a response (e.g., for ACK){:stop, reason, state}
- Stop the handler process
Request Structure
The request
parameter passed to callbacks is a Parrot.Sip.Message
struct containing:
method
- The SIP method (atom like:invite
,:bye
, etc.)uri
- The Request-URIheaders
- Map of SIP headersbody
- Message body (typically SDP for INVITE)from
- Parsed From headerto
- Parsed To headercall_id
- Call-ID valuecseq
- Parsed CSeq header
Using the Behaviour
When you use Parrot.Handler
, default implementations are provided for all callbacks
that return appropriate error responses (501 Not Implemented for most methods).
You only need to override the methods your application supports.
Summary
Types
Dialog process identifier
A SIP request message.
Possible responses from handler callbacks.
Handler state - can be any term
Transaction process identifier
Callbacks
Handle ACK requests.
Handle BYE requests.
Handle CANCEL requests.
Handle INFO requests.
Handle INVITE requests.
Handle MESSAGE requests.
Handle NOTIFY requests.
Handle OPTIONS requests.
Handle PUBLISH requests.
Handle REGISTER requests.
Handle SUBSCRIBE requests.
Handle arbitrary Erlang messages sent to the handler process.
Initialize the handler state.
Functions
Use this module to implement the UAS Handler behaviour with default implementations.
Types
@type dialog() :: pid()
Dialog process identifier
@type request() :: Parrot.Sip.Message.t()
A SIP request message.
This is a Parrot.Sip.Message
struct with at least these fields:
method
- The SIP method as an atom (:invite
,:bye
, etc.)uri
- The Request-URI as a stringheaders
- Map of headers with lowercase string keysbody
- Binary message body
@type response() :: {:respond, status :: 100..699, reason :: String.t(), headers :: map(), body :: binary(), state()} | {:proxy, uri :: String.t(), state()} | {:noreply, state()} | {:stop, reason :: term(), state()}
Possible responses from handler callbacks.
{:respond, status, reason, headers, body, state}
- Send a SIP response{:proxy, uri, state}
- Proxy the request to another URI{:noreply, state}
- Don't send a response{:stop, reason, state}
- Stop the handler process
@type state() :: term()
Handler state - can be any term
@type transaction() :: pid()
Transaction process identifier
Callbacks
Handle ACK requests.
ACK confirms receipt of a final response to INVITE as defined in RFC 3261 Section 13.2.2.4. For 2xx responses, ACK is a separate transaction. For non-2xx, it's part of the INVITE transaction.
Usually returns {:noreply, state}
as ACK doesn't need a response.
Parameters
request
- The ACK request messagestate
- Current handler state
Returns
Standard handler response tuple (usually {:noreply, state}
).
Example
@impl true
def handle_ack(message, state) do
# ACK received, call is now fully established
Logger.info("Call established: #{message.headers["call-id"]}")
{:noreply, state}
end
Handle BYE requests.
BYE terminates an established session (hangs up a call) as defined in RFC 3261 Section 15.
Parameters
request
- The BYE request messagestate
- Current handler state
Returns
Standard handler response tuple.
Example
@impl true
def handle_bye(message, state) do
call_id = message.headers["call-id"]
cleanup_call_resources(call_id)
new_state = remove_call(state, call_id)
{:respond, 200, "OK", %{}, "", new_state}
end
Handle CANCEL requests.
CANCEL cancels a pending INVITE transaction as defined in RFC 3261 Section 9. Note that CANCEL only affects the INVITE transaction - if the call is already established, use BYE instead.
Parameters
request
- The CANCEL request messagestate
- Current handler state
Returns
Standard handler response tuple.
@callback handle_info(request(), state()) :: response()
@callback handle_info(msg :: term(), state()) :: {:noreply, state()} | {:stop, reason :: term(), state()}
Handle INFO requests.
INFO sends application-level information during a session as defined in RFC 6086. Common uses:
- DTMF digits (though RFC 4733 is preferred)
- Video refresh requests
- Call progress updates
Parameters
request
- The INFO request messagestate
- Current handler state
Returns
Standard handler response tuple.
Handle INVITE requests.
INVITE establishes a new session (call) as defined in RFC 3261 Section 13. This is typically where you:
- Negotiate media capabilities via SDP (RFC 3264)
- Decide whether to accept, reject, or redirect the call
- Set up media handling
Parameters
request
- The INVITE request messagestate
- Current handler state
Returns
Standard handler response tuple.
Examples
@impl true
def handle_invite(message, state) do
cond do
not authorized?(message) ->
{:respond, 403, "Forbidden", %{}, "", state}
busy?(state) ->
{:respond, 486, "Busy Here", %{}, "", state}
true ->
sdp_answer = generate_sdp_answer(message.body)
{:respond, 200, "OK", %{"content-type" => "application/sdp"}, sdp_answer, state}
end
end
Handle MESSAGE requests.
MESSAGE sends instant messages using SIP as defined in RFC 3428. The body contains the message content.
Parameters
request
- The MESSAGE request messagestate
- Current handler state
Returns
Standard handler response tuple.
Example
@impl true
def handle_message(message, state) do
from = message.headers["from"]
content_type = message.headers["content-type"]
case content_type do
"text/plain" ->
deliver_message(from, message.body)
{:respond, 200, "OK", %{}, "", state}
_ ->
{:respond, 415, "Unsupported Media Type", %{}, "", state}
end
end
Handle NOTIFY requests.
NOTIFY carries event notification data for active subscriptions as defined in RFC 6665.
Parameters
request
- The NOTIFY request messagestate
- Current handler state
Returns
Standard handler response tuple.
Handle OPTIONS requests.
OPTIONS is used to query the capabilities of a SIP endpoint as defined in RFC 3261 Section 11. Typically used for:
- Keep-alive/ping functionality
- Discovering supported methods and features
- Pre-flight checks before sending INVITE
Parameters
request
- The OPTIONS request messagestate
- Current handler state
Returns
Standard handler response tuple.
Example
@impl true
def handle_options(_message, state) do
headers = %{
"allow" => "INVITE, ACK, CANCEL, BYE, OPTIONS",
"accept" => "application/sdp",
"supported" => "replaces, timer"
}
{:respond, 200, "OK", headers, "", state}
end
Handle PUBLISH requests.
PUBLISH updates event state information as defined in RFC 3903. Often used for presence.
Parameters
request
- The PUBLISH request messagestate
- Current handler state
Returns
Standard handler response tuple.
Handle REGISTER requests.
REGISTER is used to bind a user's address-of-record to one or more contact addresses as specified in RFC 3261 Section 10. This is typically used for:
- User location registration
- NAT keepalive
- Presence updates
Parameters
request
- The REGISTER request messagestate
- Current handler state
Returns
Standard handler response tuple.
Example
@impl true
def handle_register(message, state) do
case authenticate(message) do
:ok ->
store_registration(message)
{:respond, 200, "OK", %{}, "", state}
:unauthorized ->
challenge = generate_auth_challenge()
{:respond, 401, "Unauthorized", %{"www-authenticate" => challenge}, "", state}
end
end
Handle SUBSCRIBE requests.
SUBSCRIBE creates a subscription for event notification as defined in RFC 6665. Common uses:
- Presence (buddy lists) - RFC 3856
- Message waiting indication - RFC 3842
- Conference state - RFC 4575
- Dialog state - RFC 4235
Parameters
request
- The SUBSCRIBE request messagestate
- Current handler state
Returns
Standard handler response tuple.
Example
@impl true
def handle_subscribe(message, state) do
event = message.headers["event"]
case event do
"presence" ->
# Create subscription and send immediate NOTIFY
sub_id = create_subscription(message)
send_notify(sub_id, current_presence_state())
{:respond, 200, "OK", %{}, "", state}
_ ->
{:respond, 489, "Bad Event", %{}, "", state}
end
end
@callback handle_transaction_invite_trying(request(), transaction(), state()) :: response()
Handle arbitrary Erlang messages sent to the handler process.
This callback is called when the handler receives non-SIP messages via
send/2
or similar. Useful for:
- Timers
- Internal events
- Integration with other parts of your system
Parameters
msg
- Any Erlang termstate
- Current handler state
Returns
{:noreply, state}
- Continue with new state{:stop, reason, state}
- Stop the handler
Example
@impl true
def handle_info({:call_timeout, call_id}, state) do
# Hangup calls that have been active too long
case Map.get(state.calls, call_id) do
nil ->
{:noreply, state}
_call ->
send_bye(call_id)
{:noreply, remove_call(state, call_id)}
end
end
Initialize the handler state.
Called when a new handler process is started. This happens when:
- A new dialog is created (new INVITE)
- A new out-of-dialog request arrives
Parameters
args
- Arguments passed when starting the handler
Returns
{:ok, state}
- Initialize with the given state{:stop, reason}
- Prevent the handler from starting
Example
@impl true
def init(_args) do
{:ok, %{
calls: %{},
config: Application.get_env(:my_app, :sip_config)
}}
end
Functions
Use this module to implement the UAS Handler behaviour with default implementations.
When you use Parrot.UasHandler
, you get:
- The
@behaviour Parrot.UasHandler
declaration - Default implementations for all callbacks
- The ability to override only the callbacks you need
Example
defmodule MyApp.UasHandler do
use Parrot.UasHandler
@impl true
def init(args) do
{:ok, %{}}
end
@impl true
def handle_invite(message, state) do
# Your custom INVITE handling
{:respond, 200, "OK", %{}, "", state}
end
# All other methods will use the default implementations
end