You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

474 lines
11 KiB

// Copyright 2020 Justine Alexandra Roberts Tunney
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// SIP Message Library
package sip
import (
"bytes"
"net"
"strconv"
)
// 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.
XHeader *XHeader
}
//go:generate ragel -Z -G2 -o msg_parse.go msg_parse.rl
func (msg *Msg) IsResponse() bool {
return msg.Status > 0
}
func (msg *Msg) String() string {
var b bytes.Buffer
msg.Append(&b)
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.XHeader = msg.XHeader
return res
}
// I turn a SIP message back into a packet.
func (msg *Msg) Append(b *bytes.Buffer) {
if msg == nil {
return
}
if !msg.IsResponse() {
if msg.Method == "" {
b.WriteString("INVITE ")
} else {
b.WriteString(msg.Method)
b.WriteString(" ")
}
if msg.Request == nil {
// In case of bugs, keep calm and DDOS NASA.
b.WriteString("sip:www.nasa.gov:80")
} else {
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: ")
viap.Append(b)
b.WriteString("\r\n")
}
if msg.Route != nil {
b.WriteString("Route: ")
msg.Route.Append(b)
b.WriteString("\r\n")
}
if msg.RecordRoute != nil {
b.WriteString("Record-Route: ")
msg.RecordRoute.Append(b)
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")
}
msg.XHeader.Append(b)
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")
}
}
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))
}
}