// SIP Message Library package sip import ( "bytes" "errors" "log" "net" "strconv" ) 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 { // Fields that aren't headers. VersionMajor uint8 VersionMinor uint8 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 Payload // Stuff that comes after two line breaks // Special non-SIP fields. SourceAddr *net.UDPAddr // Set by transport layer as received address. // Important headers should be further up in the struct. From *Addr // Logical sender of message To *Addr // Logical destination of message 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 Contact *Addr // Where we send response packets or nil CallID string // Identifies call from invite to bye CSeq int // Counter for network packet ordering CSeqMethod string // Helps with matching to orig message MaxForwards int // 0 has context specific meaning UserAgent string // All the other RFC 3261 headers in plus some extras. Accept string AcceptContact string AcceptEncoding string AcceptLanguage string AlertInfo string Allow string AllowEvents string AuthenticationInfo string Authorization string CallInfo string ContentDisposition string ContentEncoding string ContentLanguage string Date string ErrorInfo string Event string Expires int // Seconds registration should expire. InReplyTo string MIMEVersion string MinExpires int // Registrars need this when responding Organization string PAssertedIdentity *Addr // P-Asserted-Identity or nil (used for PSTN ANI) Priority string ProxyAuthenticate string ProxyAuthorization string ProxyRequire string ReferTo string ReferredBy string RemotePartyID *Addr // Evil twin of P-Asserted-Identity. ReplyTo string Require string RetryAfter string Server string Subject string Supported string Timestamp string Unsupported string WWWAuthenticate string Warning string // Extension headers. Headers Headers } //go:generate ragel -Z -G2 -o msg_parse.go msg_parse.rl func (msg *Msg) IsResponse() bool { return msg.Method == "" } 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() } func (msg *Msg) Copy() *Msg { if msg == nil { return nil } res := new(Msg) *res = *msg res.Request = msg.Request.Copy() res.To = msg.To.Copy() res.From = msg.From.Copy() res.Via = msg.Via.Copy() res.PAssertedIdentity = msg.PAssertedIdentity.Copy() res.RemotePartyID = msg.RemotePartyID.Copy() res.Route = msg.Route.Copy() res.Contact = msg.Contact.Copy() res.RecordRoute = msg.RecordRoute.Copy() res.Headers = make(Headers, 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(" ") msg.appendVersion(b) b.WriteString("\r\n") } else { if msg.Phrase == "" { msg.Phrase = Phrase(msg.Status) } msg.appendVersion(b) b.WriteString(" ") b.WriteString(strconv.Itoa(msg.Status)) b.WriteString(" ") b.WriteString(msg.Phrase) 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") 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.Contact != nil { b.WriteString("Contact: ") msg.Contact.Append(b) b.WriteString("\r\n") } b.WriteString("Call-ID: ") b.WriteString(msg.CallID) b.WriteString("\r\n") b.WriteString("CSeq: ") b.WriteString(strconv.Itoa(msg.CSeq)) b.WriteString(" ") b.WriteString(msg.CSeqMethod) b.WriteString("\r\n") if msg.UserAgent != "" { b.WriteString("User-Agent: ") b.WriteString(msg.UserAgent) b.WriteString("\r\n") } if !msg.IsResponse() { 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") } } if msg.Accept != "" { b.WriteString("Accept: ") b.WriteString(msg.Accept) b.WriteString("\r\n") } if msg.AcceptEncoding != "" { b.WriteString("Accept-Encoding: ") b.WriteString(msg.AcceptEncoding) b.WriteString("\r\n") } if msg.AcceptLanguage != "" { b.WriteString("Accept-Language: ") b.WriteString(msg.AcceptLanguage) b.WriteString("\r\n") } if msg.AlertInfo != "" { b.WriteString("Alert-Info: ") b.WriteString(msg.AlertInfo) b.WriteString("\r\n") } if msg.Allow != "" { b.WriteString("Allow: ") b.WriteString(msg.Allow) b.WriteString("\r\n") } if msg.AllowEvents != "" { b.WriteString("Allow-Events: ") b.WriteString(msg.AllowEvents) b.WriteString("\r\n") } if msg.AuthenticationInfo != "" { b.WriteString("Authentication-Info: ") b.WriteString(msg.AuthenticationInfo) b.WriteString("\r\n") } if msg.Authorization != "" { b.WriteString("Authorization: ") b.WriteString(msg.Authorization) b.WriteString("\r\n") } if msg.CallInfo != "" { b.WriteString("Call-Info: ") b.WriteString(msg.CallInfo) b.WriteString("\r\n") } if msg.ContentDisposition != "" { b.WriteString("Content-Disposition: ") b.WriteString(msg.ContentDisposition) b.WriteString("\r\n") } if msg.ContentEncoding != "" { b.WriteString("Content-Encoding: ") b.WriteString(msg.ContentEncoding) b.WriteString("\r\n") } if msg.ContentLanguage != "" { b.WriteString("Content-Language: ") b.WriteString(msg.ContentLanguage) b.WriteString("\r\n") } if msg.Date != "" { b.WriteString("Date: ") b.WriteString(msg.Date) b.WriteString("\r\n") } if msg.ErrorInfo != "" { b.WriteString("Error-Info: ") b.WriteString(msg.ErrorInfo) b.WriteString("\r\n") } if msg.Event != "" { b.WriteString("Event: ") b.WriteString(msg.Event) 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.InReplyTo != "" { b.WriteString("In-Reply-To: ") b.WriteString(msg.InReplyTo) b.WriteString("\r\n") } if msg.MIMEVersion != "" { b.WriteString("MIME-Version: ") b.WriteString(msg.MIMEVersion) b.WriteString("\r\n") } if msg.MinExpires > 0 { b.WriteString("Min-Expires: ") b.WriteString(strconv.Itoa(msg.MinExpires)) b.WriteString("\r\n") } if msg.Organization != "" { b.WriteString("Organization: ") b.WriteString(msg.Organization) b.WriteString("\r\n") } if msg.PAssertedIdentity != nil { b.WriteString("P-Asserted-Identity: ") msg.PAssertedIdentity.Append(b) b.WriteString("\r\n") } if msg.Priority != "" { b.WriteString("Priority: ") b.WriteString(msg.Priority) b.WriteString("\r\n") } if msg.ProxyAuthenticate != "" { b.WriteString("Proxy-Authenticate: ") b.WriteString(msg.ProxyAuthenticate) b.WriteString("\r\n") } if msg.ProxyAuthorization != "" { b.WriteString("Proxy-Authorization: ") b.WriteString(msg.ProxyAuthorization) b.WriteString("\r\n") } if msg.ProxyRequire != "" { b.WriteString("Proxy-Require: ") b.WriteString(msg.ProxyRequire) b.WriteString("\r\n") } if msg.ReferTo != "" { b.WriteString("Refer-To: ") b.WriteString(msg.ReferTo) b.WriteString("\r\n") } if msg.ReferredBy != "" { b.WriteString("Referred-By: ") b.WriteString(msg.ReferredBy) b.WriteString("\r\n") } if msg.RemotePartyID != nil { b.WriteString("Remote-Party-ID: ") msg.RemotePartyID.Append(b) b.WriteString("\r\n") } if msg.ReplyTo != "" { b.WriteString("Reply-To: ") b.WriteString(msg.ReplyTo) b.WriteString("\r\n") } if msg.Require != "" { b.WriteString("Require: ") b.WriteString(msg.Require) b.WriteString("\r\n") } if msg.RetryAfter != "" { b.WriteString("RetryAfter: ") b.WriteString(msg.RetryAfter) b.WriteString("\r\n") } if msg.Server != "" { b.WriteString("Server: ") b.WriteString(msg.Server) b.WriteString("\r\n") } if msg.Subject != "" { b.WriteString("Subject: ") b.WriteString(msg.Subject) b.WriteString("\r\n") } if msg.Supported != "" { b.WriteString("Supported: ") b.WriteString(msg.Supported) b.WriteString("\r\n") } if msg.Timestamp != "" { b.WriteString("Timestamp: ") b.WriteString(msg.Timestamp) b.WriteString("\r\n") } if msg.Unsupported != "" { b.WriteString("Unsupported: ") b.WriteString(msg.Unsupported) b.WriteString("\r\n") } if msg.Warning != "" { b.WriteString("Warning: ") b.WriteString(msg.Warning) b.WriteString("\r\n") } if msg.WWWAuthenticate != "" { b.WriteString("WWW-Authenticate: ") b.WriteString(msg.WWWAuthenticate) b.WriteString("\r\n") } if msg.Headers != nil { for k, v := range msg.Headers { b.WriteString(k) b.WriteString(": ") b.WriteString(v) b.WriteString("\r\n") } } if msg.Payload != nil { b.WriteString("Content-Type: ") b.WriteString(msg.Payload.ContentType()) b.WriteString("\r\n") payload := msg.Payload.Data() b.WriteString("Content-Length: ") b.WriteString(strconv.Itoa(len(payload))) b.WriteString("\r\n\r\n") b.Write(payload) } else { b.WriteString("Content-Length: 0\r\n\r\n") } return nil } func (msg *Msg) appendVersion(b *bytes.Buffer) { b.WriteString("SIP/") if msg.VersionMajor == 0 { b.WriteString("2.0") } else { b.WriteString(strconv.FormatUint(uint64(msg.VersionMajor), 10)) b.WriteString(".") b.WriteString(strconv.FormatUint(uint64(msg.VersionMinor), 10)) } }