// SIP Message Library
|
|
|
|
package sip
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
type Headers map[string]string
|
|
|
|
// Msg represents a SIP message. This can either be a request or a response.
|
|
// These fields are never nil unless otherwise specified.
|
|
type Msg struct {
|
|
// Special non-SIP fields.
|
|
SourceAddr *net.UDPAddr // Set by transport layer as received address
|
|
|
|
// Fields that aren't headers.
|
|
IsResponse bool // This is a response (like 404 Not Found)
|
|
Method string // Indicates type of request (if request)
|
|
Request *URI // dest URI (nil if response)
|
|
Status int // Indicates happiness of response (if response)
|
|
Phrase string // Explains happiness of response (if response)
|
|
Payload string // Stuff that comes after two line breaks
|
|
|
|
// Mandatory headers.
|
|
Via *Via // Linked list of agents traversed (must have one)
|
|
Route *Addr // Used for goose routing and loose routing
|
|
RecordRoute *Addr // Used for loose routing
|
|
From *Addr // Logical sender of message
|
|
To *Addr // Logical destination of message
|
|
CallID string // Identifies call from invite to bye
|
|
CSeq int // Counter for network packet ordering
|
|
CSeqMethod string // Helps with matching to orig message
|
|
|
|
// Convenience headers.
|
|
MaxForwards int // 0 has context specific meaning
|
|
MinExpires int // Registrars need this when responding
|
|
Expires int // Seconds registration should expire
|
|
Paid *Addr // P-Asserted-Identity or nil (used for PSTN ANI)
|
|
Rpid *Addr // Remote-Party-Id or nil
|
|
Contact *Addr // Where we send response packets or nil
|
|
|
|
// All the other headers (never nil)
|
|
Headers Headers
|
|
}
|
|
|
|
func (msg *Msg) String() string {
|
|
if msg == nil {
|
|
return ""
|
|
}
|
|
var b bytes.Buffer
|
|
if err := msg.Append(&b); err != nil {
|
|
log.Println("Bad SIP message!", err)
|
|
return ""
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
// Parses a SIP message into a data structure. This takes ~70 µs on average.
|
|
func ParseMsg(packet string) (msg *Msg, err error) {
|
|
msg = new(Msg)
|
|
if packet == "" {
|
|
return nil, errors.New("Empty msg")
|
|
}
|
|
if n := strings.Index(packet, "\r\n\r\n"); n > 0 {
|
|
packet, msg.Payload = packet[0:n], packet[n+4:]
|
|
}
|
|
lines := strings.Split(packet, "\r\n")
|
|
if lines == nil || len(lines) < 2 {
|
|
return nil, errors.New("Too few lines")
|
|
}
|
|
var k, v string
|
|
var okVia, okTo, okFrom, okCallID, okComputer bool
|
|
err = msg.parseFirstLine(lines[0])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
hdrs := lines[1:]
|
|
msg.Headers = make(map[string]string, len(hdrs))
|
|
msg.MaxForwards = 70
|
|
viap := &msg.Via
|
|
contactp := &msg.Contact
|
|
routep := &msg.Route
|
|
rroutep := &msg.RecordRoute
|
|
for _, hdr := range hdrs {
|
|
if hdr == "" {
|
|
continue
|
|
}
|
|
if hdr[0] == ' ' || hdr[0] == '\t' {
|
|
v = strings.Trim(hdr, "\t ") // Line continuation.
|
|
} else {
|
|
if i := strings.Index(hdr, ": "); i > 0 {
|
|
k, v = hdr[0:i], hdr[i+2:]
|
|
k = strings.Trim(k, " \t")
|
|
v = strings.Trim(v, " \t")
|
|
k = uncompactHeader(k)
|
|
if k == "" || v == "" {
|
|
log.Println("Blank header found:", hdr)
|
|
}
|
|
} else {
|
|
log.Println("Header missing delimiter:", hdr)
|
|
continue
|
|
}
|
|
}
|
|
switch strings.ToLower(k) {
|
|
case "call-id":
|
|
okCallID = true
|
|
msg.CallID = v
|
|
case "via":
|
|
okVia = true
|
|
*viap, err = ParseVia(v)
|
|
if err != nil {
|
|
return nil, errors.New("Bad Via header: " + err.Error())
|
|
} else {
|
|
viap = &(*viap).Next
|
|
}
|
|
case "to":
|
|
okTo = true
|
|
msg.To, err = ParseAddr(v)
|
|
if err != nil {
|
|
return nil, errors.New("Bad To header: " + err.Error())
|
|
}
|
|
case "from":
|
|
okFrom = true
|
|
msg.From, err = ParseAddr(v)
|
|
if err != nil {
|
|
return nil, errors.New("Bad From header: " + err.Error())
|
|
}
|
|
case "contact":
|
|
*contactp, err = ParseAddr(v)
|
|
if err != nil {
|
|
return nil, errors.New("Bad Contact header: " + err.Error())
|
|
} else {
|
|
contactp = &(*contactp).Last().Next
|
|
}
|
|
case "cseq":
|
|
okComputer = false
|
|
if n := strings.Index(v, " "); n > 0 {
|
|
sseq, method := v[0:n], v[n+1:]
|
|
if seq, err := strconv.Atoi(sseq); err == nil {
|
|
msg.CSeq, msg.CSeqMethod = seq, method
|
|
okComputer = true
|
|
}
|
|
}
|
|
if !okComputer {
|
|
return nil, errors.New("Bad CSeq Header")
|
|
}
|
|
case "content-length":
|
|
if cl, err := strconv.Atoi(v); err == nil {
|
|
if cl != len(msg.Payload) {
|
|
return nil, errors.New(fmt.Sprintf(
|
|
"Content-Length (%d) differs from payload length (%d)",
|
|
cl, len(msg.Payload)))
|
|
}
|
|
} else {
|
|
return nil, errors.New("Bad Content-Length header")
|
|
}
|
|
case "expires":
|
|
if cl, err := strconv.Atoi(v); err == nil && cl >= 0 {
|
|
msg.Expires = cl
|
|
} else {
|
|
return nil, errors.New("Bad Expires header")
|
|
}
|
|
case "min-expires":
|
|
if cl, err := strconv.Atoi(v); err == nil && cl > 0 {
|
|
msg.MinExpires = cl
|
|
} else {
|
|
return nil, errors.New("Bad Min-Expires header")
|
|
}
|
|
case "max-forwards":
|
|
if cl, err := strconv.Atoi(v); err == nil && cl > 0 {
|
|
msg.MaxForwards = cl
|
|
} else {
|
|
return nil, errors.New("Bad Max-Forwards header")
|
|
}
|
|
case "route":
|
|
*routep, err = ParseAddr(v)
|
|
if err != nil {
|
|
return nil, errors.New("Bad Route header: " + err.Error())
|
|
} else {
|
|
routep = &(*routep).Last().Next
|
|
}
|
|
case "record-route":
|
|
*rroutep, err = ParseAddr(v)
|
|
if err != nil {
|
|
return nil, errors.New("Bad Record-Route header: " + err.Error())
|
|
} else {
|
|
rroutep = &(*rroutep).Last().Next
|
|
}
|
|
case "p-asserted-identity":
|
|
msg.Paid, err = ParseAddr(v)
|
|
if err != nil {
|
|
return nil, errors.New("Bad P-Asserted-Identity header: " + err.Error())
|
|
}
|
|
case "remote-party-id":
|
|
msg.Rpid, err = ParseAddr(v)
|
|
if err != nil {
|
|
return nil, errors.New("Bad Remote-Party-ID header: " + err.Error())
|
|
}
|
|
default:
|
|
msg.Headers[k] = v
|
|
}
|
|
}
|
|
if !okVia || !okTo || !okFrom || !okCallID || !okComputer {
|
|
return nil, errors.New("Missing mandatory headers")
|
|
}
|
|
return
|
|
}
|
|
|
|
func (msg *Msg) Copy() *Msg {
|
|
if msg == nil {
|
|
return nil
|
|
}
|
|
res := new(Msg)
|
|
*res = *msg
|
|
res.To = msg.To.Copy()
|
|
res.From = msg.From.Copy()
|
|
res.Via = msg.Via.Copy()
|
|
res.Paid = msg.Paid.Copy()
|
|
res.Rpid = msg.Rpid.Copy()
|
|
res.Route = msg.Route.Copy()
|
|
res.Request = msg.Request.Copy()
|
|
res.Contact = msg.Contact.Copy()
|
|
res.RecordRoute = msg.RecordRoute.Copy()
|
|
res.Headers = make(map[string]string, len(msg.Headers))
|
|
for k, v := range msg.Headers {
|
|
res.Headers[k] = v
|
|
}
|
|
return res
|
|
}
|
|
|
|
// i turn a sip message back into a packet
|
|
func (msg *Msg) Append(b *bytes.Buffer) error {
|
|
if !msg.IsResponse {
|
|
if msg.Method == "" {
|
|
return errors.New("Msg.Method not set")
|
|
}
|
|
if msg.Request == nil {
|
|
return errors.New("msg.Request not set")
|
|
}
|
|
b.WriteString(msg.Method)
|
|
b.WriteString(" ")
|
|
msg.Request.Append(b)
|
|
b.WriteString(" SIP/2.0\r\n")
|
|
} else {
|
|
if msg.Status < 100 {
|
|
return errors.New("Msg.Status < 100")
|
|
}
|
|
if msg.Status >= 700 {
|
|
return errors.New("Msg.Status >= 700")
|
|
}
|
|
if msg.Phrase == "" {
|
|
msg.Phrase = Phrase(msg.Status)
|
|
}
|
|
b.WriteString("SIP/2.0 ")
|
|
b.WriteString(strconv.Itoa(msg.Status))
|
|
b.WriteString(" ")
|
|
b.WriteString(msg.Phrase)
|
|
b.WriteString("\r\n")
|
|
}
|
|
|
|
if msg.Via == nil {
|
|
return errors.New("Need moar Via headers")
|
|
}
|
|
for viap := msg.Via; viap != nil; viap = viap.Next {
|
|
b.WriteString("Via: ")
|
|
if err := viap.Append(b); err != nil {
|
|
return err
|
|
}
|
|
b.WriteString("\r\n")
|
|
}
|
|
|
|
if msg.Route != nil {
|
|
b.WriteString("Route: ")
|
|
if err := msg.Route.Append(b); err != nil {
|
|
return err
|
|
}
|
|
b.WriteString("\r\n")
|
|
}
|
|
|
|
if msg.RecordRoute != nil {
|
|
b.WriteString("Record-Route: ")
|
|
if err := msg.RecordRoute.Append(b); err != nil {
|
|
return err
|
|
}
|
|
b.WriteString("\r\n")
|
|
}
|
|
|
|
if msg.MaxForwards < 0 {
|
|
return errors.New("MaxForwards is less than 0!!")
|
|
} else if msg.MaxForwards == 0 {
|
|
b.WriteString("Max-Forwards: 70\r\n")
|
|
} else {
|
|
b.WriteString("Max-Forwards: ")
|
|
b.WriteString(strconv.Itoa(msg.MaxForwards))
|
|
b.WriteString("\r\n")
|
|
}
|
|
|
|
b.WriteString("From: ")
|
|
msg.From.Append(b)
|
|
b.WriteString("\r\n")
|
|
|
|
b.WriteString("To: ")
|
|
msg.To.Append(b)
|
|
b.WriteString("\r\n")
|
|
|
|
if msg.CallID == "" {
|
|
return errors.New("CallID is blank")
|
|
}
|
|
b.WriteString("Call-ID: ")
|
|
b.WriteString(msg.CallID)
|
|
b.WriteString("\r\n")
|
|
|
|
if msg.CSeq < 0 || msg.CSeqMethod == "" {
|
|
return errors.New("Bad CSeq")
|
|
}
|
|
b.WriteString("CSeq: ")
|
|
b.WriteString(strconv.Itoa(msg.CSeq))
|
|
b.WriteString(" ")
|
|
b.WriteString(msg.CSeqMethod)
|
|
b.WriteString("\r\n")
|
|
|
|
if msg.Contact != nil {
|
|
b.WriteString("Contact: ")
|
|
msg.Contact.Append(b)
|
|
b.WriteString("\r\n")
|
|
}
|
|
|
|
// Expires is allowed to be 0 for for REGISTER stuff.
|
|
if msg.Expires > 0 ||
|
|
msg.Method == "REGISTER" || msg.CSeqMethod == "REGISTER" {
|
|
b.WriteString("Expires: ")
|
|
b.WriteString(strconv.Itoa(msg.Expires))
|
|
b.WriteString("\r\n")
|
|
}
|
|
|
|
if msg.MinExpires > 0 {
|
|
b.WriteString("Min-Expires: ")
|
|
b.WriteString(strconv.Itoa(msg.MinExpires))
|
|
b.WriteString("\r\n")
|
|
}
|
|
|
|
if msg.Headers != nil {
|
|
for k, v := range msg.Headers {
|
|
if k == "" || v == "" {
|
|
return errors.New("Header blank")
|
|
}
|
|
b.WriteString(k)
|
|
b.WriteString(": ")
|
|
b.WriteString(v)
|
|
b.WriteString("\r\n")
|
|
}
|
|
}
|
|
|
|
if msg.Paid != nil {
|
|
b.WriteString("P-Asserted-Identity: ")
|
|
msg.Paid.Append(b)
|
|
b.WriteString("\r\n")
|
|
}
|
|
|
|
if msg.Rpid != nil {
|
|
b.WriteString("Remote-Party-ID: ")
|
|
msg.Rpid.Append(b)
|
|
b.WriteString("\r\n")
|
|
}
|
|
|
|
b.WriteString("Content-Length: ")
|
|
b.WriteString(strconv.Itoa(len(msg.Payload)))
|
|
b.WriteString("\r\n\r\n")
|
|
b.WriteString(msg.Payload)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (msg *Msg) parseFirstLine(s string) error {
|
|
i := strings.Index(s, "SIP/2.0")
|
|
if i == -1 {
|
|
return errors.New("Not a SIP message")
|
|
} else if i == 0 {
|
|
msg.IsResponse = true
|
|
toks := strings.SplitN(s, " ", 3)
|
|
if len(toks) < 2 {
|
|
return errors.New("Bad response status line")
|
|
}
|
|
s, err := strconv.Atoi(toks[1])
|
|
if err != nil {
|
|
return errors.New("Bad response status code")
|
|
}
|
|
msg.Status = s
|
|
if len(toks) == 3 {
|
|
msg.Phrase = toks[2]
|
|
} else {
|
|
msg.Phrase = Phrase(msg.Status)
|
|
}
|
|
} else {
|
|
j := strings.Index(s, " ")
|
|
msg.Method = s[:j]
|
|
msg.Request = new(URI)
|
|
r, err := ParseURI(s[j+1 : i-1])
|
|
msg.Request = r
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|