// SIP Message Library package sip import ( "bytes" "errors" "log" "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 { magic int // Used for pseudo msgs or to mark direction OutboundProxy string // Use Route instead if possible IsResponse bool // This is a response (like 404 GO DIE) 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]) 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:] if k == "" || v == "" { log.Println("[NOTICE]", "blank header found", hdr) } } else { log.Println("[NOTICE]", "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("Via header - " + err.Error()) } viap = &(*viap).Next case "to": okTo = true msg.To, err = ParseAddr(v) if err != nil { return nil, errors.New("To header - " + err.Error()) } case "from": okFrom = true msg.From, err = ParseAddr(v) if err != nil { return nil, errors.New("From header - " + err.Error()) } case "contact": *contactp, err = ParseAddr(v) if err != nil { return nil, errors.New("Contact header - " + err.Error()) } contactp = &(*contactp).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) { msg.Payload = msg.Payload[0:cl] log.Println("[DEBUG]", "discarding extra sip payload bytes") } else if cl > len(msg.Payload) { return nil, errors.New("content-length > len(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()) } routep = &(*routep).Next case "record-route": *rroutep, err = ParseAddr(v) if err != nil { return nil, errors.New("Bad Record-Route header: " + err.Error()) } rroutep = &(*rroutep).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 msg, nil } func (msg *Msg) parseFirstLine(s string) (err error) { toks := strings.Split(s, " ") if toks != nil && len(toks) == 3 && toks[2] == "SIP/2.0" { msg.Phrase = "" msg.Status = 0 msg.Method = toks[0] msg.Request = new(URI) msg.Request, err = ParseURI(toks[1]) } else if toks != nil && len(toks) == 3 && toks[0] == "SIP/2.0" { msg.IsResponse = true msg.Method = "" msg.Request = nil msg.Phrase = toks[2] msg.Status, err = strconv.Atoi(toks[1]) if err != nil { return errors.New("Invalid status") } } else { err = errors.New("Bad protocol or request line") } return err } 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 == "" { if v, ok := Phrases[msg.Status]; ok { msg.Phrase = v } else { return errors.New("Msg.Phrase not set and Msg.Status is unknown") } } 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.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 }