Current section

Files

Jump to
sippet lib sippet message status_line.ex
Raw

lib/sippet/message/status_line.ex

defmodule Sippet.Message.StatusLine do
@moduledoc """
A SIP Status-Line struct, composed by the SIP-Version, Status-Code and the
Reason-Phrase.
The `start_line` of responses are represented by this struct. The RFC 3261
represents the Status-Line as:
Status-Line = SIP-Version SP Status-Code SP Reason-Phrase CRLF
The above `SIP-Version` is represented by a `{major, minor}` tuple, which
assumes the value `{2, 0}` in standard implementations.
The `Status-Code` is a 3-digit integer in the interval 100-699 indicating the
outcome of an attempt to understand and satisfy a request.
The `Reason-Phrase` is a binary representing a short textual description of
the `Status-Code`.
The `Status-Code` is intended for use by automata, whereas the
`Reason-Phrase` is intended for the human user.
"""
defstruct [
status_code: nil,
reason_phrase: nil,
version: nil
]
@type status_code :: 100..699
@type version :: {integer, integer}
@type t :: %__MODULE__{
status_code: status_code,
reason_phrase: binary,
version: version
}
@default_status_codes %{
100 => "Trying",
180 => "Ringing",
181 => "Call Is Being Forwarded",
182 => "Queued",
183 => "Session Progress",
199 => "Early Dialog Terminated",
200 => "OK",
202 => "Accepted",
204 => "No Notification",
300 => "Multiple Choices",
301 => "Moved Permanently",
302 => "Moved Temporarily",
305 => "Use Proxy",
380 => "Alternative Service",
400 => "Bad Request",
401 => "Unauthorized",
402 => "Payment Required",
403 => "Forbidden",
404 => "Not Found",
405 => "Method Not Allowed",
406 => "Not Acceptable",
407 => "Proxy Authentication Required",
408 => "Request Timeout",
410 => "Gone",
412 => "Conditional Request Failed",
413 => "Request Entity Too Large",
414 => "Request-URI Too Long",
415 => "Unsupported Media Type",
416 => "Unsupported URI Scheme",
417 => "Unknown Resource-Priority",
420 => "Bad Extension",
421 => "Extension Required",
422 => "Session Interval Too Small",
423 => "Interval Too Brief",
424 => "Bad Location Information",
428 => "Use Identity Header",
429 => "Provide Referrer Identity",
430 => "Flow Failed",
433 => "Anonymity Disallowed",
436 => "Bad Identity-Info",
437 => "Unsupported Certificate",
438 => "Invalid Identity Header",
439 => "First Hop Lacks Outbound Support",
440 => "Max-Breadth Exceeded",
469 => "Bad Info Package",
470 => "Consent Needed",
480 => "Temporarily Unavailable",
481 => "Call/Transaction Does Not Exist",
482 => "Loop Detected",
483 => "Too Many Hops",
484 => "Address Incomplete",
485 => "Ambiguous",
486 => "Busy Here",
487 => "Request Terminated",
488 => "Not Acceptable Here",
489 => "Bad Event",
491 => "Request Pending",
493 => "Undecipherable",
494 => "Security Agreement Required",
500 => "Server Internal Error",
501 => "Not Implemented",
502 => "Bad Gateway",
503 => "Service Unavailable",
504 => "Server Time-out",
505 => "Version Not Supported",
513 => "Message Too Large",
580 => "Precondition Failure",
600 => "Busy Everywhere",
603 => "Decline",
604 => "Does Not Exist Anywhere",
606 => "Not Acceptable"
}
@doc """
Returns a Status-Line struct.
The `reason_phrase` is obtained from default values.
The function will throw an exception if the `status_code` is not in the valid
range `100..699` or if the `status_code` does not have a default reason
phrase.
The version will assume the default value `{2, 0}`.
## Examples
iex> Sippet.Message.StatusLine.new(400)
%Sippet.Message.StatusLine{reason_phrase: "Bad Request", status_code: 400,
version: {2, 0}}
"""
@spec new(status_code) :: t | no_return
def new(status_code) when is_integer(status_code),
do: new(status_code, default_reason!(status_code))
@doc """
Creates a Status-Line struct using a given reason phrase.
In this function, the `reason_phrase` can be anything the application wants.
The function will throw an exception if the `status_code` is not in the valid
range `100..699`.
The version will assume the default value `{2, 0}`.
## Examples
iex> Sippet.Message.StatusLine.new(499, "Foobar")
%Sippet.Message.StatusLine{reason_phrase: "Foobar", status_code: 499,
version: {2, 0}}
"""
@spec new(status_code, reason_phrase :: binary) :: t
def new(status_code, reason_phrase)
when is_integer(status_code) and is_binary(reason_phrase) do
%__MODULE__{
status_code: do_raise_if_invalid(status_code),
reason_phrase: reason_phrase,
version: {2, 0}
}
end
defp do_raise_if_invalid(status_code) do
if status_code < 100 || status_code >= 700 do
raise "invalid status code, got: #{inspect(status_code)}"
else
status_code
end
end
@doc """
Returns an integer representing the status code class in the range `[1, 6]`.
## Examples
iex> alias Sippet.Message.StatusLine
iex> StatusLine.new(202) |> StatusLine.status_code_class()
2
"""
@spec status_code_class(t) :: 1..6
def status_code_class(%__MODULE__{status_code: status_code}) do
div(status_code, 100)
end
@doc """
Returns a binary representing the default reason phrase for the given
`status_code`.
If the `status_code` does not have a corresponding default reason phrase,
returns `nil`.
## Examples
iex> Sippet.Message.StatusLine.default_reason(202)
"Accepted"
iex> Sippet.Message.StatusLine.default_reason(499)
nil
"""
@spec default_reason(status_code) :: binary | nil
def default_reason(status_code) do
defaults = @default_status_codes
if defaults |> Map.has_key?(status_code) do
defaults[status_code]
else
nil
end
end
@doc """
Returns a binary representing the default reason phrase for the given
`status_code`.
If the `status_code` does not have a corresponding default reason phrase,
throws an exception.
## Examples
iex> Sippet.Message.StatusLine.default_reason!(202)
"Accepted"
iex> Sippet.Message.StatusLine.default_reason!(499)
** (ArgumentError) status code 499 does not have a default reason phrase
"""
@spec default_reason!(status_code) :: binary | no_return
def default_reason!(status_code) do
case status_code |> do_raise_if_invalid() |> default_reason() do
nil ->
raise ArgumentError, "status code #{inspect status_code} " <>
"does not have a default reason phrase"
reason_phrase ->
reason_phrase
end
end
@doc """
Returns a binary which corresponds to the text representation of the given
Status-Line.
It does not includes an ending line CRLF.
## Examples
iex> alias Sippet.StatusLine
iex> StatusLine.new(202) |> StatusLine.to_string
"SIP/2.0 202 Accepted"
"""
@spec to_string(t) :: binary
defdelegate to_string(value), to: String.Chars.Sippet.Message.StatusLine
@doc """
Returns an iodata which corresponds to the text representation of the given
Status-Line.
It does not includes an ending line CRLF.
## Examples
iex> alias Sippet.StatusLine
iex> StatusLine.new(202) |> StatusLine.to_iodata
["SIP/", "2", ".", "0", " ", "202", " ", "Accepted"]
"""
@spec to_iodata(t) :: iodata
def to_iodata(%Sippet.Message.StatusLine{version: {major, minor},
status_code: status_code, reason_phrase: reason_phrase}) do
["SIP/", Integer.to_string(major), ".", Integer.to_string(minor),
" ", Integer.to_string(status_code),
" ", reason_phrase]
end
end
defimpl String.Chars, for: Sippet.Message.StatusLine do
alias Sippet.Message.StatusLine, as: StatusLine
def to_string(%StatusLine{} = status_line) do
status_line
|> StatusLine.to_iodata()
|> IO.iodata_to_binary
end
end