alphanum = ALPHA / DIGIT
reserved = ";" / "/" / "?" / ":" / "@" / "&" / "=" / "+" / "$" / ","
unreserved = alphanum / mark
mark = "-" / "_" / "." / "!" / "~" / "*" / "'" / "(" / ")"
escaped = "%" HEXDIG HEXDIG
LWS = [*WSP CRLF] 1*WSP
SWS = [LWS]
HCOLON = *( SP / HTAB ) ":" SWS
TEXT-UTF8-TRIM = 1*TEXT-UTF8char *(*LWS TEXT-UTF8char)
TEXT-UTF8char = %x21-7E / UTF8-NONASCII
UTF8-NONASCII = %xC0-DF 1UTF8-CONT / %xE0-EF 2UTF8-CONT / %xF0-F7 3UTF8-CONT / %xF8-Fb 4UTF8-CONT / %xFC-FD 5UTF8-CONT
UTF8-CONT = %x80-BF
LHEX = DIGIT
token = 1*(alphanum / "-" / "." / "!" / "%" / "*" / "_" / "+" / "`" / "'" / "~" )
separators = "(" / ")" / "<" / ">" / "@" / "," / ";" / ":" / "\" / DQUOTE / "/" / "[" / "]" / "?" / "=" / "{" / "}" / SP / HTAB
word = 1*(alphanum / "-" / "." / "!" / "%" / "*" / "_" / "+" / "`" / "'" / "~" / "(" / ")" / "<" / ">" / ":" / "\" / DQUOTE / "/" / "[" / "]" / "?" / "{" / "}" )
STAR = SWS "*" SWS
SLASH = SWS "/" SWS
EQUAL = SWS "=" SWS
LPAREN = SWS "(" SWS
RPAREN = SWS ")" SWS
RAQUOT = ">" SWS
LAQUOT = SWS "<"
COMMA = SWS "," SWS
SEMI = SWS ";" SWS
COLON = SWS ":" SWS
LDQUOT = SWS DQUOTE
RDQUOT = DQUOTE SWS
comment = LPAREN *(ctext / quoted-pair / comment) RPAREN
ctext = %x21-27 / %x2A-5B / %x5D-7E / UTF8-NONASCII / LWS
quoted-string = SWS DQUOTE *(qdtext / quoted-pair ) DQUOTE
qdtext = LWS / %x21 / %x23-5B / %x5D-7E / UTF8-NONASCII
quoted-pair = "\" (%x00-09 / %x0B-0C / %x0E-7F)
SIP-URI = "sip:" [ userinfo ] hostport uri-parameters [ headers ] !!!
  [_, uinfo, [[hostport]], _, _] = values
  {:ok, state, %{
    scheme: "sip",
    userinfo: (case uinfo do
      [] -> nil
      [uinfo] -> to_string(uinfo)
    end),
    hostport: hostport
  }}
!!!
SIPS-URI = "sips:" [ userinfo ] hostport uri-parameters [ headers ]
userinfo = ( user / telephone-subscriber ) [ ":" password ] "@" !!!
  [u, _, _] = values
  {:ok, state, :lists.flatten(u)}
!!!
user = 1*( unreserved / escaped / user-unreserved )
user-unreserved = "&" / "=" / "+" / "$" / "," / ";" / "?" / "/"
password = *( unreserved / escaped / "&" / "=" / "+" / "$" / "," )
hostport = host [ ":" port ] !!!
  [[[host]], [port]] = values
  port = case port do
    [] -> 5060
    port ->
      [_, port] = :lists.flatten(port)
      port
  end
  {:ok, state, %{host: host, port: port}}
!!!
host = hostname / IPv4address / IPv6reference !!!
  {:ok, state, to_string(rule)}
!!!
hostname = *( domainlabel "." ) toplabel [ "." ]
domainlabel = alphanum / alphanum *( alphanum / "-" ) alphanum
toplabel = ALPHA / ALPHA *( alphanum / "-" ) alphanum
IPv4address = 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT
IPv6reference = "[" IPv6address "]"
IPv6address = hexpart [ ":" IPv4address ]
hexpart = hexseq / hexseq "::" [ hexseq ] / "::" [ hexseq ]
hexseq = hex4 *( ":" hex4)
hex4 = 1*4HEXDIG
port = 1*DIGIT !!!
  {port, ""} = Integer.parse to_string(rule)
  {:ok, state, port}
!!!
uri-parameters = *( ";" uri-parameter)
uri-parameter = transport-param / user-param / method-param / ttl-param / maddr-param / lr-param / other-param
transport-param = "transport=" ( "udp" / "tcp" / "sctp" / "tls" / other-transport)
other-transport = token
user-param = "user=" ( "phone" / "ip" / other-user)
other-user = token
method-param = "method=" Method
ttl-param = "ttl=" ttl
maddr-param = "maddr=" host
lr-param = "lr"
other-param = pname [ "=" pvalue ]
pname = 1*paramchar
pvalue = 1*paramchar
paramchar = param-unreserved / unreserved / escaped
param-unreserved = "[" / "]" / "/" / ":" / "&" / "+" / "$"
headers = "?" header *( "&" header )
header = hname "=" hvalue
hname = 1*( hnv-unreserved / unreserved / escaped )
hvalue = *( hnv-unreserved / unreserved / escaped )
hnv-unreserved = "[" / "]" / "/" / "?" / ":" / "+" / "$"
SIP-message = Request / Response !!!
  {:ok, Map.put(state, :request, true)}
!!!

Request = Request-Line *( message-header ) CRLF [ message-body ]
Request-Line   =  Method SP Request-URI SP SIP-Version CRLF !!!
  [[[method]], _, [[[uri]]], _, _, _] = values
  state = Map.put state, :method, method
  state = Map.put state, :uri, uri
  {:ok, state}
!!!

Request-URI = SIP-URI / SIPS-URI / absoluteURI !!!
  [[uri]] = values
  {:ok, state, uri}
!!!

absoluteURI = scheme ":" ( hier-part / opaque-part )
hier-part = ( net-path / abs-path ) [ "?" query ]
net-path = "//" authority [ abs-path ]
abs-path = "/" path-segments
opaque-part = uric-no-slash *uric
uric = reserved / unreserved / escaped
uric-no-slash = unreserved / escaped / ";" / "?" / ":" / "@" / "&" / "=" / "+" / "$" / ","
path-segments = segment *( "/" segment )
segment = *pchar *( ";" param )
param = *pchar
pchar = unreserved / escaped / ":" / "@" / "&" / "=" / "+" / "$" / ","
scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
authority = srvr / reg-name
srvr = [ [ userinfo "@" ] hostport ]
reg-name = 1*( unreserved / escaped / "$" / "," / ";" / ":" / "@" / "&" / "=" / "+" )
query = *uric
SIP-Version = "SIP" "/" 1*DIGIT "." 1*DIGIT
message-header = (Accept / Accept-Encoding / Accept-Language / Alert-Info / Allow / Authentication-Info / Authorization / Call-ID / Call-Info / Contact / Content-Disposition / Content-Encoding / Content-Language / Content-Length / Content-Type / CSeq / Date / Error-Info / Expires / From / In-Reply-To / Max-Forwards / MIME-Version / Min-Expires / Organization / Priority / Proxy-Authenticate / Proxy-Authorization / Proxy-Require / Record-Route / Reply-To / Require / Retry-After / Route / Server / Subject / Supported / Timestamp / To / Unsupported / User-Agent / Via / Warning / WWW-Authenticate / extension-header) CRLF
INVITEm = %x49.4E.56.49.54.45
ACKm = %x41.43.4B
OPTIONSm = %x4F.50.54.49.4F.4E.53
BYEm = %x42.59.45
CANCELm = %x43.41.4E.43.45.4C
REGISTERm = %x52.45.47.49.53.54.45.52
Method = INVITEm / ACKm / OPTIONSm / BYEm / CANCELm / REGISTERm / extension-method !!!
  {:ok, state, String.to_atom(String.downcase(to_string(rule)))}
!!!
extension-method = token
Response = Status-Line *( message-header ) CRLF [ message-body ]
Status-Line = SIP-Version SP Status-Code SP Reason-Phrase CRLF
Status-Code = Informational / Redirection / Success / Client-Error / Server-Error / Global-Failure / extension-code
extension-code = 3DIGIT
Reason-Phrase = *(reserved / unreserved / escaped / UTF8-NONASCII / UTF8-CONT / SP / HTAB)
Informational = "100" / "180" / "181" / "182" / "183"
Success = "200"
Redirection = "300" / "301" / "302" / "305" / "380"
Client-Error = "400" /"401" /"402" /"403" /"404" /"405" /"406" /"407" /"408" /"410" /"413" /"414" /"415" /"416" /"420" /"421" /"423" /"480" /"481" /"482" /"483" /"484" /"485" /"486" /"487" /"488" /"491" /"493"
Server-Error = "500" / "501" / "502" / "503" / "504" / "505" / "513"
Global-Failure = "600" / "603" / "604" / "606"
Accept = "Accept" HCOLON [ accept-range *(COMMA accept-range) ]
accept-range = media-range *(SEMI accept-param)
media-range = ( "*/*" / ( m-type SLASH "*" ) / ( m-type SLASH m-subtype )) *( SEMI m-parameter )
accept-param = ("q" EQUAL qvalue) / generic-param
qvalue = ( "0" [ "." 0*3DIGIT ] ) / ( "1" [ "." 0*3("0") ] )
generic-param = token [ EQUAL gen-value ]
gen-value = token / host / quoted-string
Accept-Encoding = "Accept-Encoding" HCOLON [ encoding *(COMMA encoding) ]
encoding = codings *(SEMI accept-param)
codings = content-coding / "*"
content-coding = token
Accept-Language = "Accept-Language" HCOLON [ language *(COMMA language) ]
language = language-range *(SEMI accept-param)
language-range = ( ( 1*8ALPHA *( "-" 1*8ALPHA ) ) / "*" )
Alert-Info = "Alert-Info" HCOLON alert-param *(COMMA alert-param)
alert-param = LAQUOT absoluteURI RAQUOT *( SEMI generic-param )
Allow = "Allow" HCOLON [Method *(COMMA Method)]
Authorization = "Authorization" HCOLON credentials
credentials = ("Digest" LWS digest-response) / other-response
digest-response = dig-resp *(COMMA dig-resp)
dig-resp = username / realm / nonce / digest-uri / dresponse / algorithm / cnonce / opaque / message-qop / nonce-count / auth-param
username = "username" EQUAL username-value
username-value = quoted-string
digest-uri = "uri" EQUAL LDQUOT digest-uri-value RDQUOT
digest-uri-value = rquest-uri
message-qop = "qop" EQUAL qop-value
cnonce = "cnonce" EQUAL cnonce-value
cnonce-value = nonce-value
nonce-count = "nc" EQUAL nc-value
nc-value = 8LHEX
dresponse = "response" EQUAL request-digest
request-digest = LDQUOT 32LHEX RDQUOT
auth-param = auth-param-name EQUAL ( token / quoted-string )
auth-param-name = token
other-response = auth-scheme LWS auth-param *(COMMA auth-param)
auth-scheme = token
Authentication-Info = "Authentication-Info" HCOLON ainfo *(COMMA ainfo)
ainfo = nextnonce / message-qop / response-auth / cnonce / nonce-count
nextnonce = "nextnonce" EQUAL nonce-value
response-auth = "rspauth" EQUAL response-digest
response-digest = LDQUOT *LHEX RDQUOT
Call-ID = ( "Call-ID" / "i" ) HCOLON callid
callid = word [ "@" word ]
Call-Info = "Call-Info" HCOLON info *(COMMA info)
info = LAQUOT absoluteURI RAQUOT *( SEMI info-param)
info-param = ( "purpose" EQUAL ( "icon" / "info" / "card" / token ) ) / generic-param
Contact = ("Contact" / "m" ) HCOLON ( STAR / (contact-param *(COMMA contact-param)))
contact-param = (name-addr / addr-spec) *(SEMI contact-params)
name-addr = [ display-name ] LAQUOT addr-spec RAQUOT !!!
  [name, _, [[[addr]]], _] = values
  name = case name do
    [] -> nil
    name -> to_string(:lists.flatten(name))
  end
  {:ok, state, %{
    display_name: name,
    addr: addr
  }}
!!!
addr-spec = SIP-URI / SIPS-URI / absoluteURI !!!
  [[uri]] = values
  {:ok, state, uri}
!!!
display-name = *(token LWS)/ quoted-string
contact-params = c-p-q / c-p-expires / contact-extension
c-p-q = "q" EQUAL qvalue
c-p-expires = "expires" EQUAL delta-seconds
contact-extension = generic-param
delta-seconds = 1*DIGIT
Content-Disposition = "Content-Disposition" HCOLON disp-type *( SEMI disp-param )
disp-type = "render" / "session" / "icon" / "alert" / disp-extension-token
disp-param = handling-param / generic-param
handling-param = "handling" EQUAL ( "optional" / "required" / other-handling )
other-handling = token
disp-extension-token = token
Content-Encoding = ( "Content-Encoding" / "e" ) HCOLON content-coding *(COMMA content-coding)
Content-Language = "Content-Language" HCOLON language-tag *(COMMA language-tag)
language-tag = primary-tag *( "-" subtag )
primary-tag = 1*8ALPHA
subtag = 1*8ALPHA
Content-Length = ( "Content-Length" / "l" ) HCOLON 1*DIGIT
Content-Type = ( "Content-Type" / "c" ) HCOLON media-type
media-type = m-type SLASH m-subtype *(SEMI m-parameter)
m-type = discrete-type / composite-type
discrete-type = "text" / "image" / "audio" / "video" / "application" / extension-token
composite-type = "message" / "multipart" / extension-token
extension-token = ietf-token / x-token
ietf-token = token
x-token = "x-" token
m-subtype = extension-token / iana-token
iana-token = token
m-parameter = m-attribute EQUAL m-value
m-attribute = token
m-value = token / quoted-string
CSeq = "CSeq" HCOLON 1*DIGIT LWS Method
Date = "Date" HCOLON SIP-date
SIP-date = rfc1123-date
rfc1123-date = wkday "," SP date1 SP time SP "GMT"
date1 = 2DIGIT SP month SP 4DIGIT
time = 2DIGIT ":" 2DIGIT ":" 2DIGIT
wkday = "Mon" / "Tue" / "Wed" / "Thu" / "Fri" / "Sat" / "Sun"
month = "Jan" / "Feb" / "Mar" / "Apr" / "May" / "Jun" / "Jul" / "Aug" / "Sep" / "Oct" / "Nov" / "Dec"
Error-Info = "Error-Info" HCOLON error-uri *(COMMA error-uri)
error-uri = LAQUOT absoluteURI RAQUOT *( SEMI generic-param )
Expires = "Expires" HCOLON delta-seconds
From = ( "From" / "f" ) HCOLON from-spec !!!
  [_, _, [[[from]]]] = values
  headers = Map.put(state.headers, "from", from)
  {:ok, Map.put(state, :headers, headers)}
!!!
from-spec = ( name-addr / addr-spec ) *( SEMI from-param ) !!!
  [[from], _] = values
  {:ok, state, :lists.flatten(from)}
!!!
from-param = tag-param / generic-param
tag-param = "tag" EQUAL token
In-Reply-To = "In-Reply-To" HCOLON callid *(COMMA callid)
Max-Forwards = "Max-Forwards" HCOLON 1*DIGIT
MIME-Version = "MIME-Version" HCOLON 1*DIGIT "." 1*DIGIT
Min-Expires = "Min-Expires" HCOLON delta-seconds
Organization = "Organization" HCOLON [TEXT-UTF8-TRIM]
Priority = "Priority" HCOLON priority-value
priority-value = "emergency" / "urgent" / "normal" / "non-urgent" / other-priority
other-priority = token
Proxy-Authenticate  =  "Proxy-Authenticate" HCOLON challenge
challenge = ("Digest" LWS digest-cln *(COMMA digest-cln)) / other-challenge
other-challenge = auth-scheme LWS auth-param *(COMMA auth-param)
digest-cln = realm / domain / nonce / opaque / stale / algorithm / qop-options / auth-param
realm = "realm" EQUAL realm-value
realm-value = quoted-string
domain = "domain" EQUAL LDQUOT URI *( 1*SP URI ) RDQUOT
URI = absoluteURI / abs-path
nonce = "nonce" EQUAL nonce-value
nonce-value = quoted-string
opaque = "opaque" EQUAL quoted-string
stale = "stale" EQUAL ( "true" / "false" )
algorithm = "algorithm" EQUAL ( "MD5" / "MD5-sess" / token )
qop-options = "qop" EQUAL LDQUOT qop-value *("," qop-value) RDQUOT
qop-value = "auth" / "auth-int" / token
Proxy-Authorization = "Proxy-Authorization" HCOLON credentials
Proxy-Require = "Proxy-Require" HCOLON option-tag *(COMMA option-tag)
option-tag = token
Record-Route = "Record-Route" HCOLON rec-route *(COMMA rec-route)
rec-route = name-addr *( SEMI rr-param )
rr-param = generic-param
Reply-To = "Reply-To" HCOLON rplyto-spec
rplyto-spec = ( name-addr / addr-spec ) *( SEMI rplyto-param )
rplyto-param = generic-param
Require = "Require" HCOLON option-tag *(COMMA option-tag)
Retry-After = "Retry-After" HCOLON delta-seconds [ comment ] *( SEMI retry-param )
retry-param = ("duration" EQUAL delta-seconds) / generic-param
Route = "Route" HCOLON route-param *(COMMA route-param)
route-param = name-addr *( SEMI rr-param )
Server = "Server" HCOLON server-val *(LWS server-val)
server-val = product / comment
product = token [SLASH product-version]
product-version = token
Subject = ( "Subject" / "s" ) HCOLON [TEXT-UTF8-TRIM]
Supported = ( "Supported" / "k" ) HCOLON [option-tag *(COMMA option-tag)]
Timestamp = "Timestamp" HCOLON 1*(DIGIT) [ "." *(DIGIT) ] [ LWS delay ]
delay = *(DIGIT) [ "." *(DIGIT) ]
To = ( "To" / "t" ) HCOLON ( name-addr / addr-spec ) *( SEMI to-param )
to-param = tag-param / generic-param
Unsupported = "Unsupported" HCOLON option-tag *(COMMA option-tag)
User-Agent = "User-Agent" HCOLON server-val *(LWS server-val)
Via = ( "Via" / "v" ) HCOLON via-parm *(COMMA via-parm)
via-parm = sent-protocol LWS sent-by *( SEMI via-params )
via-params = via-ttl / via-maddr / via-received / via-branch / via-extension
via-ttl = "ttl" EQUAL ttl
via-maddr = "maddr" EQUAL host
via-received = "received" EQUAL (IPv4address / IPv6address)
via-branch = "branch" EQUAL token
via-extension = generic-param
sent-protocol = protocol-name SLASH protocol-version SLASH transport
protocol-name = "SIP" / token
protocol-version = token
transport = "UDP" / "TCP" / "TLS" / "SCTP" / other-transport
sent-by = host [ COLON port ]
ttl = 1*3DIGIT
Warning = "Warning" HCOLON warning-value *(COMMA warning-value)
warning-value = warn-code SP warn-agent SP warn-text
warn-code = 3DIGIT
warn-agent = hostport / pseudonym
warn-text = quoted-string
pseudonym = token
WWW-Authenticate = "WWW-Authenticate" HCOLON challenge
extension-header = header-name HCOLON header-value
header-name = token
header-value = *(TEXT-UTF8char / UTF8-CONT / LWS)
message-body = *OCTET
ALPHA = %x41-5A / %x61-7A
BIT = "0" / "1"
CHAR = %x01-7F
CR = %x0D
CRLF = CR LF
CTL = %x00-1F / %x7F
DIGIT = %x30-39
DQUOTE = %x22
HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F"
HTAB = %x09
LF = %x0A
LWSP = *(WSP / CRLF WSP)
OCTET = %x00-FF
SP = %x20
VCHAR = %x21-7E
WSP = SP / HTAB
telephone-uri = "tel:" telephone-subscriber
telephone-subscriber = global-number / local-number
global-number = global-number-digits *par
local-number = local-number-digits *par context *par
par = parameter / extension / isdn-subaddress
isdn-subaddress = ";isub=" 1*paramchar
extension = ";ext=" 1*phonedigit
context = ";phone-context=" descriptor
descriptor = domainname / global-number-digits
global-number-digits = "+" *phonedigit DIGIT *phonedigit
local-number-digits = *phonedigit-hex (HEXDIG / "*" / "#") *phonedigit-hex
domainname = *( domainlabel "." ) toplabel [ "." ]
domainlabel = alphanum / alphanum *( alphanum / "-" ) alphanum
toplabel = ALPHA / ALPHA *( alphanum / "-" ) alphanum
parameter = ";" pname ["=" pvalue ]
pname = 1*( alphanum / "-" )
pvalue = 1*paramchar
paramchar = param-unreserved / unreserved / pct-encoded
unreserved = alphanum / mark
mark = "-" / "_" / "." / "!" / "~" / "*" / "'" / "(" / ")"
pct-encoded = "%" HEXDIG HEXDIG
param-unreserved = "[" / "]" / "/" / ":" / "&" / "+" / "$"
phonedigit = DIGIT / visual-separator
phonedigit-hex = HEXDIG / "*" / "#" / visual-separator
visual-separator = "-" / "." / "(" / ")"
alphanum = ALPHA / DIGIT
reserved = ";" / "/" / "?" / ":" / "@" / "&" / "=" / "+" / "$" / ","
uric = reserved / unreserved / pct-encoded
