// -*-go-*-
|
|
|
|
package sip
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"github.com/jart/gosip/sdp"
|
|
"strings"
|
|
)
|
|
|
|
%% machine msg;
|
|
%% write data;
|
|
|
|
// ParseMsg turns a a SIP message into a data structure.
|
|
func ParseMsg(s string) (msg *Msg, err error) {
|
|
if s == "" {
|
|
return nil, errors.New("Empty SIP message")
|
|
}
|
|
return ParseMsgBytes([]byte(s))
|
|
}
|
|
|
|
// ParseMsg turns a a SIP message byte slice into a data structure.
|
|
func ParseMsgBytes(data []byte) (msg *Msg, err error) {
|
|
if data == nil {
|
|
return nil, nil
|
|
}
|
|
msg = new(Msg)
|
|
fromp := &msg.From
|
|
top := &msg.To
|
|
viap := &msg.Via
|
|
routep := &msg.Route
|
|
rroutep := &msg.RecordRoute
|
|
contactp := &msg.Contact
|
|
paidp := &msg.PAssertedIdentity
|
|
rpidp := &msg.RemotePartyID
|
|
cs := 0
|
|
p := 0
|
|
pe := len(data)
|
|
eof := len(data)
|
|
line := 1
|
|
linep := 0
|
|
buf := make([]byte, len(data))
|
|
amt := 0
|
|
mark := 0
|
|
clen := 0
|
|
ctype := ""
|
|
var name string
|
|
var hex byte
|
|
var value *string
|
|
var addr *Addr
|
|
var addrpp ***Addr
|
|
|
|
%%{
|
|
action break {
|
|
fbreak;
|
|
}
|
|
|
|
action mark {
|
|
mark = p
|
|
}
|
|
|
|
action start {
|
|
amt = 0
|
|
}
|
|
|
|
action append {
|
|
buf[amt] = fc
|
|
amt++
|
|
}
|
|
|
|
action space {
|
|
buf[amt] = ' '
|
|
amt++
|
|
}
|
|
|
|
action erase {
|
|
amt--
|
|
}
|
|
|
|
action collapse {
|
|
amt = appendCollapse(buf, amt, fc)
|
|
}
|
|
|
|
action hexHi {
|
|
hex = unhex(fc) * 16
|
|
}
|
|
|
|
action hexLo {
|
|
hex += unhex(fc)
|
|
buf[amt] = hex
|
|
amt++
|
|
}
|
|
|
|
action lower {
|
|
amt = appendLower(buf, amt, fc)
|
|
}
|
|
|
|
action Method {
|
|
msg.Method = string(data[mark:p])
|
|
}
|
|
|
|
action VersionMajor {
|
|
msg.VersionMajor = msg.VersionMajor * 10 + (fc - 0x30)
|
|
}
|
|
|
|
action VersionMinor {
|
|
msg.VersionMinor = msg.VersionMinor * 10 + (fc - 0x30)
|
|
}
|
|
|
|
action RequestURI {
|
|
msg.Request, err = ParseURIBytes(data[mark:p])
|
|
if err != nil { return nil, err }
|
|
}
|
|
|
|
action StatusCode {
|
|
msg.Status = msg.Status * 10 + (int(fc) - 0x30)
|
|
}
|
|
|
|
action ReasonPhrase {
|
|
msg.Phrase = string(buf[0:amt])
|
|
}
|
|
|
|
action goto_avalue {
|
|
fhold;
|
|
fgoto avalue;
|
|
}
|
|
|
|
action goto_header {
|
|
fgoto header;
|
|
}
|
|
|
|
action goto_svalue {
|
|
fhold;
|
|
fgoto svalue;
|
|
}
|
|
|
|
action goto_xheader {
|
|
fhold;
|
|
fgoto xheader;
|
|
}
|
|
|
|
# Shorthand notation.
|
|
action gxh {
|
|
fhold;
|
|
fgoto xheader;
|
|
}
|
|
|
|
action store_name {
|
|
name = string(data[mark:p])
|
|
}
|
|
|
|
action store_value {{
|
|
b := data[mark:p - 1]
|
|
if value != nil {
|
|
*value = string(b)
|
|
} else {
|
|
if msg.Headers == nil {
|
|
msg.Headers = Headers{}
|
|
}
|
|
msg.Headers[name] = string(b)
|
|
}
|
|
}}
|
|
|
|
action store_addr {
|
|
// TODO(jart): Why does this fire multiple times?
|
|
if addr != nil {
|
|
**addrpp = addr
|
|
*addrpp = &addr.Next
|
|
addr = nil
|
|
}
|
|
}
|
|
|
|
action new_addr {
|
|
addr = new(Addr)
|
|
}
|
|
|
|
action addr_display {
|
|
addr.Display = strings.TrimRight(string(buf[0:amt]), " \t\r\n")
|
|
}
|
|
|
|
action addr_uri {
|
|
addr.Uri, err = ParseURIBytes(data[mark:p])
|
|
if err != nil { return nil, err }
|
|
}
|
|
|
|
action addr_param {
|
|
if addr.Params == nil {
|
|
addr.Params = Params{}
|
|
}
|
|
addr.Params[name] = string(buf[0:amt])
|
|
}
|
|
|
|
action CallID {
|
|
msg.CallID = string(data[mark:p])
|
|
}
|
|
|
|
action ContentLength {
|
|
clen = clen * 10 + (int(fc) - 0x30)
|
|
}
|
|
|
|
action ContentType {
|
|
ctype = string(data[mark:p])
|
|
}
|
|
|
|
action CSeq {
|
|
msg.CSeq = msg.CSeq * 10 + (int(fc) - 0x30)
|
|
}
|
|
|
|
action CSeqMethod {
|
|
msg.CSeqMethod = string(data[mark:p])
|
|
}
|
|
|
|
action Expires {
|
|
msg.Expires = msg.Expires * 10 + (int(fc) - 0x30)
|
|
}
|
|
|
|
action MaxForwards {
|
|
msg.MaxForwards = msg.MaxForwards * 10 + (int(fc) - 0x30)
|
|
}
|
|
|
|
action MinExpires {
|
|
msg.MinExpires = msg.MinExpires * 10 + (int(fc) - 0x30)
|
|
}
|
|
|
|
action Via {
|
|
*viap, err = ParseVia(string(data[mark:p]))
|
|
if err != nil { return nil, err }
|
|
for *viap != nil { viap = &(*viap).Next }
|
|
}
|
|
|
|
action lookAheadWSP { p + 2 < pe && (data[p+2] == ' ' || data[p+2] == '\t') }
|
|
|
|
# https://tools.ietf.org/html/rfc2234
|
|
SP = " ";
|
|
HTAB = "\t";
|
|
CR = "\r";
|
|
LF = "\n" @{ line++; linep = p; };
|
|
CRLF = CR LF;
|
|
WSP = SP | HTAB;
|
|
LWS = ( WSP* ( CR when lookAheadWSP ) LF )? WSP+;
|
|
SWS = LWS?;
|
|
UTF8_CONT = 0x80..0xBF @append;
|
|
UTF8_NONASCII = 0xC0..0xDF @append UTF8_CONT {1}
|
|
| 0xE0..0xEF @append UTF8_CONT {2}
|
|
| 0xF0..0xF7 @append UTF8_CONT {3}
|
|
| 0xF8..0xFb @append UTF8_CONT {4}
|
|
| 0xFC..0xFD @append UTF8_CONT {5};
|
|
UTF8 = 0x21..0x7F @append | UTF8_NONASCII;
|
|
UTF8_TRIM = ( UTF8+ (LWS* UTF8)* ) >start @collapse;
|
|
|
|
# https://tools.ietf.org/html/rfc3261#section-25.1
|
|
reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | "," ;
|
|
mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")" ;
|
|
unreserved = alpha | digit | mark ;
|
|
tokenc = alpha | digit | "-" | "." | "!" | "%" | "*" | "_"
|
|
| "+" | "`" | "'" | "~" ;
|
|
separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\\"
|
|
| "\"" | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP
|
|
| HTAB ;
|
|
wordc = alpha | digit | "-" | "." | "!" | "%" | "*" | "_"
|
|
| "+" | "`" | "'" | "~" | "(" | ")" | "<" | ">" | ":"
|
|
| "\\" | "\"" | "/" | "[" | "]" | "?" | "{" | "}" ;
|
|
schmchars = alpha | digit | "+" | "-" | "." ;
|
|
word = wordc+ ;
|
|
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 ;
|
|
HCOLON = WSP* ":" SWS ;
|
|
LDQUOT = SWS "\"" ;
|
|
RDQUOT = "\"" SWS ;
|
|
ctext = 0x21..0x27 | 0x2A..0x5B | 0x5D..0x7E | UTF8_NONASCII | LWS;
|
|
quoted_pair = "\\" ( 0x00..0x09 | 0x0B..0x0C | 0x0E..0x7F ) @append;
|
|
comment = LPAREN ( ctext | quoted_pair )* <: RPAREN; # TODO(jart): Nested parens.
|
|
qdtext = UTF8_NONASCII | LWS @append | ( 0x21 | 0x23..0x5B | 0x5D..0x7E ) @append;
|
|
escaped = "%" ( xdigit @hexHi ) ( xdigit @hexLo ) ;
|
|
uric = reserved | unreserved | "%" ;
|
|
uric_no_slash = unreserved | escaped | ";" | "?" | ":" | "@" | "&" | "="
|
|
| "+" | "$" | "," ;
|
|
uri = alpha schmchars* ":" uric+;
|
|
token = tokenc+;
|
|
tokenhost = ( tokenc | "[" | "]" | ":" )+;
|
|
reasonc = UTF8_NONASCII | ( reserved | unreserved | SP | HTAB ) @append;
|
|
reasonmc = escaped | reasonc;
|
|
cid = word ( "@" word )?;
|
|
|
|
quoted_content = ( qdtext | quoted_pair )* >start;
|
|
quoted_string = LDQUOT quoted_content <: RDQUOT;
|
|
param_name = token >mark %store_name;
|
|
param_content = tokenhost >start @append;
|
|
param_value = param_content | quoted_string;
|
|
param = ( param_name ( EQUAL param_value )? ) %addr_param;
|
|
display = ( token @append LWS %space )* >start;
|
|
display_name = ( display | quoted_string ) %addr_display;
|
|
addr_spec = uri >mark %addr_uri;
|
|
name_addr = display_name? LAQUOT addr_spec RAQUOT;
|
|
addr_omg = ( ( addr_spec -- ";" ) | name_addr ) ( SEMI param )*;
|
|
addr = addr_omg >new_addr %store_addr;
|
|
|
|
Method = token >mark %Method;
|
|
SIPVersionNo = digit+ @VersionMajor "." digit+ @VersionMinor;
|
|
RequestURI = ^SP+ >mark %RequestURI;
|
|
StatusCode = ( digit @StatusCode ) {3};
|
|
ReasonPhrase = reasonmc+ >start %ReasonPhrase;
|
|
hval = ( UTF8 | LWS )* >mark;
|
|
|
|
# Address Headers
|
|
aname = ("Contact"i | "m"i) %{addrpp=&contactp}
|
|
| ("From"i | "f"i) %{addrpp=&fromp}
|
|
| "P-Asserted-Identity"i %{addrpp=&paidp}
|
|
| "Record-Route"i %{addrpp=&rroutep}
|
|
| "Remote-Party-ID"i %{addrpp=&rpidp}
|
|
| "Route"i %{addrpp=&routep}
|
|
| ("To"i | "t"i) %{addrpp=&top}
|
|
;
|
|
|
|
# String Headers
|
|
sname = "Accept"i %{value=&msg.Accept}
|
|
| ("Accept-Contact"i | "a"i) %{value=&msg.AcceptContact}
|
|
| "Accept-Encoding"i %{value=&msg.AcceptEncoding}
|
|
| "Accept-Language"i %{value=&msg.AcceptLanguage}
|
|
| ("Allow"i | "u"i) %{value=&msg.Allow}
|
|
| ("Allow-Events"i | "u"i) %{value=&msg.AllowEvents}
|
|
| "Alert-Info"i %{value=&msg.AlertInfo}
|
|
| "Authentication-Info"i %{value=&msg.AuthenticationInfo}
|
|
| "Authorization"i %{value=&msg.Authorization}
|
|
| "Content-Disposition"i %{value=&msg.ContentDisposition}
|
|
| "Content-Language"i %{value=&msg.ContentLanguage}
|
|
| ("Content-Encoding"i | "e"i) %{value=&msg.ContentEncoding}
|
|
| "Call-Info"i %{value=&msg.CallInfo}
|
|
| "Date"i %{value=&msg.Date}
|
|
| "Error-Info"i %{value=&msg.ErrorInfo}
|
|
| ("Event"i | "o"i) %{value=&msg.Event}
|
|
| "In-Reply-To"i %{value=&msg.InReplyTo}
|
|
| "Reply-To"i %{value=&msg.ReplyTo}
|
|
| "MIME-Version"i %{value=&msg.MIMEVersion}
|
|
| "Organization"i %{value=&msg.Organization}
|
|
| "Priority"i %{value=&msg.Priority}
|
|
| "Proxy-Authenticate"i %{value=&msg.ProxyAuthenticate}
|
|
| "Proxy-Authorization"i %{value=&msg.ProxyAuthorization}
|
|
| "Proxy-Require"i %{value=&msg.ProxyRequire}
|
|
| ("Refer-To"i | "r"i) %{value=&msg.ReferTo}
|
|
| ("Referred-By"i | "b"i) %{value=&msg.ReferredBy}
|
|
| "Require"i %{value=&msg.Require}
|
|
| "Retry-After"i %{value=&msg.RetryAfter}
|
|
| "Server"i %{value=&msg.Server}
|
|
| ("Subject"i | "s"i) %{value=&msg.Subject}
|
|
| ("Supported"i | "k"i) %{value=&msg.Supported}
|
|
| "Timestamp"i %{value=&msg.Timestamp}
|
|
| "Unsupported"i %{value=&msg.Unsupported}
|
|
| "User-Agent"i %{value=&msg.UserAgent}
|
|
| "Warning"i %{value=&msg.Warning}
|
|
| "WWW-Authenticate"i %{value=&msg.WWWAuthenticate}
|
|
;
|
|
|
|
# Custom Headers
|
|
cheader = ("Call-ID"i | "i"i) @!gxh HCOLON cid >mark %CallID
|
|
| ("Content-Length"i | "l"i) @!gxh HCOLON digit+ >{clen=0} @ContentLength
|
|
| ("Content-Type"i | "c"i) @!gxh HCOLON <: hval %ContentType
|
|
| "CSeq"i @!gxh HCOLON (digit+ @CSeq) LWS token >mark %CSeqMethod
|
|
| ("Expires"i | "l"i) @!gxh HCOLON digit+ >{msg.Expires=0} @Expires
|
|
| ("Max-Forwards"i | "l"i) @!gxh HCOLON digit+ >{msg.MaxForwards=0} @MaxForwards
|
|
| ("Min-Expires"i | "l"i) @!gxh HCOLON digit+ >{msg.MinExpires=0} @MinExpires
|
|
| ("Via"i | "v"i) @!gxh HCOLON <: hval %Via
|
|
;
|
|
|
|
avalue := addr ( COMMA addr )* CR LF @goto_header;
|
|
svalue := hval CR LF @store_value @goto_header;
|
|
xheader := token >mark %store_name HCOLON <: any @{value=nil} @goto_svalue;
|
|
header := CR LF @break
|
|
| cheader CR LF @goto_header
|
|
| aname >mark @err(goto_xheader) HCOLON <: any @goto_avalue
|
|
| sname >mark @err(goto_xheader) HCOLON <: any @goto_svalue
|
|
;
|
|
|
|
SIPVersion = "SIP/" SIPVersionNo;
|
|
Request = Method SP RequestURI SP SIPVersion CR LF @goto_header;
|
|
Response = SIPVersion SP StatusCode SP ReasonPhrase CR LF @goto_header;
|
|
main := Request | Response;
|
|
|
|
write init;
|
|
write exec;
|
|
}%%
|
|
|
|
if cs < msg_first_final {
|
|
if p == pe {
|
|
return nil, errors.New(fmt.Sprintf("Incomplete SIP message: %s", data))
|
|
} else {
|
|
return nil, errors.New(fmt.Sprintf("Error in SIP message at line %d offset %d:\n%s", line, p - linep, data))
|
|
}
|
|
}
|
|
|
|
if clen > 0 {
|
|
if clen != len(data) - p {
|
|
return nil, errors.New(fmt.Sprintf("Content-Length incorrect: %d != %d", clen, len(data) - p))
|
|
}
|
|
if ctype == sdp.ContentType {
|
|
ms, err := sdp.Parse(string(data[p:len(data)]))
|
|
if err != nil { return nil, err }
|
|
msg.Payload = ms
|
|
} else {
|
|
msg.Payload = &MiscPayload{T: ctype, D: data[p:len(data)]}
|
|
}
|
|
}
|
|
|
|
return msg, nil
|
|
}
|