Packages

Elixir libraries and OTP behaviours for building telecom applications with SIP protocol and media handling

Current section

Files

Jump to
parrot_platform lib parrot sip headers subscription_state.ex
Raw

lib/parrot/sip/headers/subscription_state.ex

defmodule Parrot.Sip.Headers.SubscriptionState do
@moduledoc """
Module for working with SIP Subscription-State headers as defined in RFC 3265 Section 7.2.3.
The Subscription-State header field provides subscription state information.
It is used in NOTIFY requests to convey the status of a subscription.
The header field contains a value that indicates the state of the subscription.
It can also contain parameters that provide additional information about the subscription.
The Subscription-State header is mandatory in all NOTIFY requests and indicates:
- active: The subscription is active and notifications will continue
- pending: The subscription has been received but not yet authorized
- terminated: The subscription has ended and no more notifications will be sent
Important parameters include:
- expires: For active/pending states, indicates remaining subscription time
- reason: For terminated state, indicates why the subscription ended
- retry-after: Suggests when to retry after certain termination reasons
Common termination reasons:
- deactivated: Subscription terminated by subscriber
- timeout: Subscription expired
- probation: Subscription in probation due to failed notifications
- rejected: Subscription rejected by notifier
- giveup: Subscription ended due to fatal error
- noresource: Resource no longer exists
References:
- RFC 3265 Section 7.2.3: Subscription-State Header Field
- RFC 3265 Section 3.2.4: Subscriber NOTIFY Behavior
- RFC 3265 Section 3.1.6.2: Notifier NOTIFY Behavior
- RFC 3265 Section 4.2.3: Subscription State Machine
"""
@states [:active, :pending, :terminated]
defstruct [
:state,
:parameters
]
@type t :: %__MODULE__{
state: :active | :pending | :terminated,
parameters: %{String.t() => String.t()}
}
@doc """
Creates a new Subscription-State header.
## Examples
iex> Parrot.Sip.Headers.SubscriptionState.new(:active, %{"expires" => "3600"})
%Parrot.Sip.Headers.SubscriptionState{state: :active, parameters: %{"expires" => "3600"}}
iex> Parrot.Sip.Headers.SubscriptionState.new(:terminated, %{"reason" => "timeout"})
%Parrot.Sip.Headers.SubscriptionState{state: :terminated, parameters: %{"reason" => "timeout"}}
"""
@spec new(atom(), map()) :: t()
def new(state, parameters \\ %{}) when state in @states do
%__MODULE__{
state: state,
parameters: parameters
}
end
@doc """
Parses a Subscription-State header string into a struct.
## Examples
iex> Parrot.Sip.Headers.SubscriptionState.parse("active;expires=3600")
%Parrot.Sip.Headers.SubscriptionState{state: :active, parameters: %{"expires" => "3600"}}
iex> Parrot.Sip.Headers.SubscriptionState.parse("terminated;reason=timeout")
%Parrot.Sip.Headers.SubscriptionState{state: :terminated, parameters: %{"reason" => "timeout"}}
"""
@spec parse(String.t()) :: t()
def parse(string) when is_binary(string) do
case String.split(string, ";", parts: 2) do
[state] ->
%__MODULE__{
state: String.to_existing_atom(state),
parameters: %{}
}
[state, params_string] ->
parameters =
params_string
|> String.split(";")
|> Enum.map(&String.split(&1, "=", parts: 2))
|> Enum.map(fn [key, value] -> {key, value} end)
|> Enum.into(%{})
%__MODULE__{
state: String.to_existing_atom(state),
parameters: parameters
}
end
end
@doc """
Formats a Subscription-State struct as a string.
## Examples
iex> subscription_state = %Parrot.Sip.Headers.SubscriptionState{state: :active, parameters: %{"expires" => "3600"}}
iex> Parrot.Sip.Headers.SubscriptionState.format(subscription_state)
"active;expires=3600"
iex> subscription_state = %Parrot.Sip.Headers.SubscriptionState{state: :terminated, parameters: %{"reason" => "timeout"}}
iex> Parrot.Sip.Headers.SubscriptionState.format(subscription_state)
"terminated;reason=timeout"
"""
@spec format(t()) :: String.t()
def format(%__MODULE__{} = subscription_state) do
params_string =
if Enum.empty?(subscription_state.parameters) do
""
else
";" <>
(subscription_state.parameters
|> Enum.map(fn {key, value} -> "#{key}=#{value}" end)
|> Enum.join(";"))
end
"#{Atom.to_string(subscription_state.state)}#{params_string}"
end
@doc """
Returns a new Subscription-State with a reason parameter added.
## Examples
iex> state = Parrot.Sip.Headers.SubscriptionState.new(:terminated)
iex> Parrot.Sip.Headers.SubscriptionState.set_reason("noresource", state)
%Parrot.Sip.Headers.SubscriptionState{state: :terminated, parameters: %{"reason" => "noresource"}}
"""
@spec set_reason(String.t(), t()) :: t()
def set_reason(reason, %__MODULE__{} = subscription_state) when is_binary(reason) do
%__MODULE__{
subscription_state
| parameters: Map.put(subscription_state.parameters, "reason", reason)
}
end
@doc """
Returns a new Subscription-State with an expires parameter added.
## Examples
iex> state = Parrot.Sip.Headers.SubscriptionState.new(:active)
iex> Parrot.Sip.Headers.SubscriptionState.set_expires("3600", state)
%Parrot.Sip.Headers.SubscriptionState{state: :active, parameters: %{"expires" => "3600"}}
"""
@spec set_expires(String.t(), t()) :: t()
def set_expires(expires, %__MODULE__{} = subscription_state) when is_binary(expires) do
%__MODULE__{
subscription_state
| parameters: Map.put(subscription_state.parameters, "expires", expires)
}
end
@doc """
Checks if the subscription state is terminated.
## Examples
iex> state = Parrot.Sip.Headers.SubscriptionState.new(:terminated)
iex> Parrot.Sip.Headers.SubscriptionState.terminated?(state)
true
iex> state = Parrot.Sip.Headers.SubscriptionState.new(:active)
iex> Parrot.Sip.Headers.SubscriptionState.terminated?(state)
false
"""
@spec terminated?(t()) :: boolean()
def terminated?(%__MODULE__{state: :terminated}), do: true
def terminated?(%__MODULE__{}), do: false
end