// 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.
|
|
|
|
package sip
|
|
|
|
import (
|
|
"github.com/jart/gosip/util"
|
|
"log"
|
|
)
|
|
|
|
const (
|
|
GosipUA = "slytherin/1.o"
|
|
GosipAllow = "INVITE, ACK, CANCEL, BYE, OPTIONS"
|
|
)
|
|
|
|
func NewRequest(tp *Transport, method string, to, from *Addr) *Msg {
|
|
return &Msg{
|
|
Method: method,
|
|
Request: to.Uri.Copy(),
|
|
Via: tp.Via.Copy().Branch(),
|
|
From: from.Or(tp.Contact).Copy().Tag(),
|
|
To: to.Copy(),
|
|
CallID: util.GenerateCallID(),
|
|
CSeq: util.GenerateCSeq(),
|
|
CSeqMethod: method,
|
|
UserAgent: GosipUA,
|
|
}
|
|
}
|
|
|
|
func NewResponse(msg *Msg, status int) *Msg {
|
|
return &Msg{
|
|
Status: status,
|
|
Phrase: Phrase(status),
|
|
Via: msg.Via,
|
|
From: msg.From,
|
|
To: msg.To,
|
|
CallID: msg.CallID,
|
|
CSeq: msg.CSeq,
|
|
CSeqMethod: msg.CSeqMethod,
|
|
RecordRoute: msg.RecordRoute,
|
|
UserAgent: GosipUA,
|
|
Allow: GosipAllow,
|
|
}
|
|
}
|
|
|
|
// http://tools.ietf.org/html/rfc3261#section-17.1.1.3
|
|
func NewAck(msg, invite *Msg) *Msg {
|
|
return &Msg{
|
|
Method: MethodAck,
|
|
Request: msg.Contact.Uri,
|
|
From: msg.From,
|
|
To: msg.To,
|
|
Via: msg.Via.Detach(),
|
|
CallID: msg.CallID,
|
|
CSeq: msg.CSeq,
|
|
CSeqMethod: "ACK",
|
|
Route: msg.RecordRoute.Reversed(),
|
|
Authorization: invite.Authorization,
|
|
ProxyAuthorization: invite.ProxyAuthorization,
|
|
UserAgent: GosipUA,
|
|
}
|
|
}
|
|
|
|
func NewCancel(invite *Msg) *Msg {
|
|
if invite.IsResponse() || invite.Method != MethodInvite {
|
|
log.Printf("Can't CANCEL anything non-INVITE:\r\n%s", invite)
|
|
}
|
|
return &Msg{
|
|
Method: MethodCancel,
|
|
Request: invite.Request,
|
|
Via: invite.Via,
|
|
From: invite.From,
|
|
To: invite.To,
|
|
CallID: invite.CallID,
|
|
CSeq: invite.CSeq,
|
|
CSeqMethod: MethodCancel,
|
|
Route: invite.Route,
|
|
}
|
|
}
|
|
|
|
func NewBye(invite, remote *Msg, lseq *int) *Msg {
|
|
if lseq == nil {
|
|
lseq = new(int)
|
|
*lseq = invite.CSeq
|
|
}
|
|
*lseq++
|
|
return &Msg{
|
|
Method: MethodBye,
|
|
Request: remote.Contact.Uri,
|
|
From: invite.From,
|
|
To: remote.To,
|
|
CallID: invite.CallID,
|
|
CSeq: *lseq,
|
|
CSeqMethod: MethodBye,
|
|
Route: remote.RecordRoute.Reversed(),
|
|
}
|
|
}
|
|
|
|
// Returns true if `resp` can be considered an appropriate response to `msg`.
|
|
// Do not use for ACKs.
|
|
func ResponseMatch(req, rsp *Msg) bool {
|
|
return (rsp.IsResponse() &&
|
|
rsp.CSeq == req.CSeq &&
|
|
rsp.CSeqMethod == req.Method &&
|
|
rsp.Via.Last().CompareHostPort(req.Via) &&
|
|
rsp.Via.Last().CompareBranch(req.Via))
|
|
}
|
|
|
|
// Returns true if `ack` can be considered an appropriate response to `msg`.
|
|
// We don't enforce a matching Via because some VoIP software will generate a
|
|
// new branch for ACKs.
|
|
func AckMatch(msg, ack *Msg) bool {
|
|
return (!ack.IsResponse() &&
|
|
ack.Method == MethodAck &&
|
|
ack.CSeq == msg.CSeq &&
|
|
ack.CSeqMethod == MethodAck &&
|
|
ack.Via.Last().CompareHostPort(msg.Via))
|
|
}
|