1
0
Fork 0
mirror of https://github.com/ossrs/srs.git synced 2025-03-09 15:49:59 +00:00

GB28181: Support GB28181-2016 protocol. v5.0.74 (#3201)

01. Support GB config as StreamCaster.
02. Support disable GB by --gb28181=off.
03. Add utests for SIP examples.
04. Wireshark plugin to decode TCP/9000 as rtp.rfc4571
05. Support MPEGPS program stream codec.
06. Add utest for PS stream codec.
07. Decode MPEGPS packet stream.
08. Carry RTP and PS packet as helper in PS message.
09. Support recover from error mode.
10. Support process by a pack of PS/TS messages.
11. Add statistic for recovered and msgs dropped.
12. Recover from err position fastly.
13. Define state machine for GB session.
14. Bind context to GB session.
15. Re-invite when media disconnected.
16. Update GitHub actions with GB28181.
17. Support parse CANDIDATE by env or pip.
18. Support mux GB28181 to RTMP.
19. Support regression test by srs-bench.
This commit is contained in:
Winlin 2022-10-06 17:40:58 +08:00 committed by GitHub
parent 9c81a0e1bd
commit 5a420ece3b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
298 changed files with 43343 additions and 763 deletions

View file

@ -0,0 +1,279 @@
package sip
import (
"crypto/md5"
"encoding/hex"
"fmt"
"regexp"
"strings"
)
// currently only Digest and MD5
type Authorization struct {
realm string
nonce string
algorithm string
username string
password string
uri string
response string
method string
qop string
nc string
cnonce string
other map[string]string
}
func AuthFromValue(value string) *Authorization {
auth := &Authorization{
algorithm: "MD5",
other: make(map[string]string),
}
re := regexp.MustCompile(`([\w]+)="([^"]+)"`)
matches := re.FindAllStringSubmatch(value, -1)
for _, match := range matches {
switch match[1] {
case "realm":
auth.realm = match[2]
case "algorithm":
auth.algorithm = match[2]
case "nonce":
auth.nonce = match[2]
case "username":
auth.username = match[2]
case "uri":
auth.uri = match[2]
case "response":
auth.response = match[2]
case "qop":
for _, v := range strings.Split(match[2], ",") {
v = strings.Trim(v, " ")
if v == "auth" || v == "auth-int" {
auth.qop = "auth"
break
}
}
case "nc":
auth.nc = match[2]
case "cnonce":
auth.cnonce = match[2]
default:
auth.other[match[1]] = match[2]
}
}
return auth
}
func (auth *Authorization) Realm() string {
return auth.realm
}
func (auth *Authorization) Nonce() string {
return auth.nonce
}
func (auth *Authorization) Algorithm() string {
return auth.algorithm
}
func (auth *Authorization) Username() string {
return auth.username
}
func (auth *Authorization) SetUsername(username string) *Authorization {
auth.username = username
return auth
}
func (auth *Authorization) SetPassword(password string) *Authorization {
auth.password = password
return auth
}
func (auth *Authorization) Uri() string {
return auth.uri
}
func (auth *Authorization) SetUri(uri string) *Authorization {
auth.uri = uri
return auth
}
func (auth *Authorization) SetMethod(method string) *Authorization {
auth.method = method
return auth
}
func (auth *Authorization) Response() string {
return auth.response
}
func (auth *Authorization) SetResponse(response string) {
auth.response = response
}
func (auth *Authorization) Qop() string {
return auth.qop
}
func (auth *Authorization) SetQop(qop string) {
auth.qop = qop
}
func (auth *Authorization) Nc() string {
return auth.nc
}
func (auth *Authorization) SetNc(nc string) {
auth.nc = nc
}
func (auth *Authorization) CNonce() string {
return auth.cnonce
}
func (auth *Authorization) SetCNonce(cnonce string) {
auth.cnonce = cnonce
}
func (auth *Authorization) CalcResponse() string {
return calcResponse(
auth.username,
auth.realm,
auth.password,
auth.method,
auth.uri,
auth.nonce,
auth.qop,
auth.cnonce,
auth.nc,
)
}
func (auth *Authorization) String() string {
if auth == nil {
return "<nil>"
}
str := fmt.Sprintf(
`Digest realm="%s",algorithm=%s,nonce="%s",username="%s",uri="%s",response="%s"`,
auth.realm,
auth.algorithm,
auth.nonce,
auth.username,
auth.uri,
auth.response,
)
if auth.qop == "auth" {
str += fmt.Sprintf(`,qop=%s,nc=%s,cnonce="%s"`, auth.qop, auth.nc, auth.cnonce)
}
return str
}
// calculates Authorization response https://www.ietf.org/rfc/rfc2617.txt
func calcResponse(username, realm, password, method, uri, nonce, qop, cnonce, nc string) string {
calcA1 := func() string {
encoder := md5.New()
encoder.Write([]byte(username + ":" + realm + ":" + password))
return hex.EncodeToString(encoder.Sum(nil))
}
calcA2 := func() string {
encoder := md5.New()
encoder.Write([]byte(method + ":" + uri))
return hex.EncodeToString(encoder.Sum(nil))
}
encoder := md5.New()
encoder.Write([]byte(calcA1() + ":" + nonce + ":"))
if qop != "" {
encoder.Write([]byte(nc + ":" + cnonce + ":" + qop + ":"))
}
encoder.Write([]byte(calcA2()))
return hex.EncodeToString(encoder.Sum(nil))
}
func AuthorizeRequest(request Request, response Response, user, password MaybeString) error {
if user == nil {
return fmt.Errorf("authorize request: user is nil")
}
var authenticateHeaderName, authorizeHeaderName string
if response.StatusCode() == 401 {
// on 401 Unauthorized increase request seq num, add Authorization header and send once again
authenticateHeaderName = "WWW-Authenticate"
authorizeHeaderName = "Authorization"
} else {
// 407 Proxy authentication
authenticateHeaderName = "Proxy-Authenticate"
authorizeHeaderName = "Proxy-Authorization"
}
if hdrs := response.GetHeaders(authenticateHeaderName); len(hdrs) > 0 {
authenticateHeader := hdrs[0].(*GenericHeader)
auth := AuthFromValue(authenticateHeader.Contents).
SetMethod(string(request.Method())).
SetUri(request.Recipient().String()).
SetUsername(user.String())
if password != nil {
auth.SetPassword(password.String())
}
if auth.Qop() == "auth" {
auth.SetNc("00000001")
encoder := md5.New()
encoder.Write([]byte(user.String() + request.Recipient().String()))
if password != nil {
encoder.Write([]byte(password.String()))
}
auth.SetCNonce(hex.EncodeToString(encoder.Sum(nil)))
}
auth.SetResponse(auth.CalcResponse())
if hdrs = request.GetHeaders(authorizeHeaderName); len(hdrs) > 0 {
authorizationHeader := hdrs[0].Clone().(*GenericHeader)
authorizationHeader.Contents = auth.String()
request.ReplaceHeaders(authorizationHeader.Name(), []Header{authorizationHeader})
} else {
request.AppendHeader(&GenericHeader{
HeaderName: authorizeHeaderName,
Contents: auth.String(),
})
}
} else {
return fmt.Errorf("authorize request: header '%s' not found in response", authenticateHeaderName)
}
if viaHop, ok := request.ViaHop(); ok {
viaHop.Params.Add("branch", String{Str: GenerateBranch()})
}
if cseq, ok := request.CSeq(); ok {
cseq := cseq.Clone().(*CSeq)
cseq.SeqNo++
request.ReplaceHeaders(cseq.Name(), []Header{cseq})
}
return nil
}
type Authorizer interface {
AuthorizeRequest(request Request, response Response) error
}
type DefaultAuthorizer struct {
User MaybeString
Password MaybeString
}
func (auth *DefaultAuthorizer) AuthorizeRequest(request Request, response Response) error {
return AuthorizeRequest(request, response, auth.User, auth.Password)
}

View file

@ -0,0 +1,336 @@
package sip
import (
"fmt"
"github.com/ghettovoice/gosip/util"
)
type RequestBuilder struct {
protocol string
protocolVersion string
transport string
host string
method RequestMethod
cseq *CSeq
recipient Uri
body string
callID *CallID
via ViaHeader
from *FromHeader
to *ToHeader
contact *ContactHeader
expires *Expires
userAgent *UserAgentHeader
maxForwards *MaxForwards
supported *SupportedHeader
require *RequireHeader
allow AllowHeader
contentType *ContentType
accept *Accept
route *RouteHeader
generic map[string]Header
}
func NewRequestBuilder() *RequestBuilder {
callID := CallID(util.RandString(32))
maxForwards := MaxForwards(70)
userAgent := UserAgentHeader("GoSIP")
rb := &RequestBuilder{
protocol: "SIP",
protocolVersion: "2.0",
transport: "UDP",
host: "localhost",
cseq: &CSeq{SeqNo: 1},
body: "",
via: make(ViaHeader, 0),
callID: &callID,
userAgent: &userAgent,
maxForwards: &maxForwards,
generic: make(map[string]Header),
}
return rb
}
func (rb *RequestBuilder) SetTransport(transport string) *RequestBuilder {
if transport == "" {
rb.transport = "UDP"
} else {
rb.transport = transport
}
return rb
}
func (rb *RequestBuilder) SetHost(host string) *RequestBuilder {
if host == "" {
rb.host = "localhost"
} else {
rb.host = host
}
return rb
}
func (rb *RequestBuilder) SetMethod(method RequestMethod) *RequestBuilder {
rb.method = method
rb.cseq.MethodName = method
return rb
}
func (rb *RequestBuilder) SetSeqNo(seqNo uint) *RequestBuilder {
rb.cseq.SeqNo = uint32(seqNo)
return rb
}
func (rb *RequestBuilder) SetRecipient(uri Uri) *RequestBuilder {
rb.recipient = uri.Clone()
return rb
}
func (rb *RequestBuilder) SetBody(body string) *RequestBuilder {
rb.body = body
return rb
}
func (rb *RequestBuilder) SetCallID(callID *CallID) *RequestBuilder {
if callID != nil {
rb.callID = callID
}
return rb
}
func (rb *RequestBuilder) AddVia(via *ViaHop) *RequestBuilder {
if via.ProtocolName == "" {
via.ProtocolName = rb.protocol
}
if via.ProtocolVersion == "" {
via.ProtocolVersion = rb.protocolVersion
}
if via.Transport == "" {
via.Transport = rb.transport
}
if via.Host == "" {
via.Host = rb.host
}
if via.Params == nil {
via.Params = NewParams()
}
rb.via = append(rb.via, via)
return rb
}
func (rb *RequestBuilder) SetFrom(address *Address) *RequestBuilder {
if address == nil {
rb.from = nil
} else {
address = address.Clone()
if address.Uri.Host() == "" {
address.Uri.SetHost(rb.host)
}
rb.from = &FromHeader{
DisplayName: address.DisplayName,
Address: address.Uri,
Params: address.Params,
}
}
return rb
}
func (rb *RequestBuilder) SetTo(address *Address) *RequestBuilder {
if address == nil {
rb.to = nil
} else {
address = address.Clone()
if address.Uri.Host() == "" {
address.Uri.SetHost(rb.host)
}
rb.to = &ToHeader{
DisplayName: address.DisplayName,
Address: address.Uri,
Params: address.Params,
}
}
return rb
}
func (rb *RequestBuilder) SetContact(address *Address) *RequestBuilder {
if address == nil {
rb.contact = nil
} else {
address = address.Clone()
if address.Uri.Host() == "" {
address.Uri.SetHost(rb.host)
}
rb.contact = &ContactHeader{
DisplayName: address.DisplayName,
Address: address.Uri,
Params: address.Params,
}
}
return rb
}
func (rb *RequestBuilder) SetExpires(expires *Expires) *RequestBuilder {
rb.expires = expires
return rb
}
func (rb *RequestBuilder) SetUserAgent(userAgent *UserAgentHeader) *RequestBuilder {
rb.userAgent = userAgent
return rb
}
func (rb *RequestBuilder) SetMaxForwards(maxForwards *MaxForwards) *RequestBuilder {
rb.maxForwards = maxForwards
return rb
}
func (rb *RequestBuilder) SetAllow(methods []RequestMethod) *RequestBuilder {
rb.allow = methods
return rb
}
func (rb *RequestBuilder) SetSupported(options []string) *RequestBuilder {
if len(options) == 0 {
rb.supported = nil
} else {
rb.supported = &SupportedHeader{
Options: options,
}
}
return rb
}
func (rb *RequestBuilder) SetRequire(options []string) *RequestBuilder {
if len(options) == 0 {
rb.require = nil
} else {
rb.require = &RequireHeader{
Options: options,
}
}
return rb
}
func (rb *RequestBuilder) SetContentType(contentType *ContentType) *RequestBuilder {
rb.contentType = contentType
return rb
}
func (rb *RequestBuilder) SetAccept(accept *Accept) *RequestBuilder {
rb.accept = accept
return rb
}
func (rb *RequestBuilder) SetRoutes(routes []Uri) *RequestBuilder {
if len(routes) == 0 {
rb.route = nil
} else {
rb.route = &RouteHeader{
Addresses: routes,
}
}
return rb
}
func (rb *RequestBuilder) AddHeader(header Header) *RequestBuilder {
rb.generic[header.Name()] = header
return rb
}
func (rb *RequestBuilder) RemoveHeader(headerName string) *RequestBuilder {
if _, ok := rb.generic[headerName]; ok {
delete(rb.generic, headerName)
}
return rb
}
func (rb *RequestBuilder) Build() (Request, error) {
if rb.method == "" {
return nil, fmt.Errorf("undefined method name")
}
if rb.recipient == nil {
return nil, fmt.Errorf("empty recipient")
}
if rb.from == nil {
return nil, fmt.Errorf("empty 'From' header")
}
if rb.to == nil {
return nil, fmt.Errorf("empty 'From' header")
}
hdrs := make([]Header, 0)
if rb.route != nil {
hdrs = append(hdrs, rb.route)
}
if len(rb.via) != 0 {
via := make(ViaHeader, 0)
for _, viaHop := range rb.via {
via = append(via, viaHop)
}
hdrs = append(hdrs, via)
}
hdrs = append(hdrs, rb.cseq, rb.from, rb.to, rb.callID)
if rb.contact != nil {
hdrs = append(hdrs, rb.contact)
}
if rb.maxForwards != nil {
hdrs = append(hdrs, rb.maxForwards)
}
if rb.expires != nil {
hdrs = append(hdrs, rb.expires)
}
if rb.supported != nil {
hdrs = append(hdrs, rb.supported)
}
if rb.allow != nil {
hdrs = append(hdrs, rb.allow)
}
if rb.contentType != nil {
hdrs = append(hdrs, rb.contentType)
}
if rb.accept != nil {
hdrs = append(hdrs, rb.accept)
}
if rb.userAgent != nil {
hdrs = append(hdrs, rb.userAgent)
}
for _, header := range rb.generic {
hdrs = append(hdrs, header)
}
sipVersion := rb.protocol + "/" + rb.protocolVersion
// basic request
req := NewRequest("", rb.method, rb.recipient, sipVersion, hdrs, "", nil)
req.SetBody(rb.body, true)
return req, nil
}

View file

@ -0,0 +1,412 @@
package sip
import (
"bytes"
"fmt"
"strings"
"github.com/ghettovoice/gosip/util"
)
const (
MTU uint = 1500
DefaultHost = "127.0.0.1"
DefaultProtocol = "UDP"
DefaultUdpPort Port = 5060
DefaultTcpPort Port = 5060
DefaultTlsPort Port = 5061
DefaultWsPort Port = 80
DefaultWssPort Port = 443
)
// TODO should be refactored, currently here the pit
type Address struct {
DisplayName MaybeString
Uri Uri
Params Params
}
func NewAddressFromFromHeader(from *FromHeader) *Address {
addr := &Address{
DisplayName: from.DisplayName,
}
if from.Address != nil {
addr.Uri = from.Address.Clone()
}
if from.Params != nil {
addr.Params = from.Params.Clone()
}
return addr
}
func NewAddressFromToHeader(to *ToHeader) *Address {
addr := &Address{
DisplayName: to.DisplayName,
}
if to.Address != nil {
addr.Uri = to.Address.Clone()
}
if to.Params != nil {
addr.Params = to.Params.Clone()
}
return addr
}
func NewAddressFromContactHeader(cnt *ContactHeader) *Address {
addr := &Address{
DisplayName: cnt.DisplayName,
}
if cnt.Address != nil {
addr.Uri = cnt.Address.Clone()
}
if cnt.Params != nil {
addr.Params = cnt.Params.Clone()
}
return addr
}
func (addr *Address) String() string {
var buffer bytes.Buffer
if addr == nil {
return "<nil>"
}
if addr.DisplayName != nil {
if displayName, ok := addr.DisplayName.(String); ok && displayName.String() != "" {
buffer.WriteString(fmt.Sprintf("\"%s\" ", displayName))
}
}
buffer.WriteString(fmt.Sprintf("<%s>", addr.Uri))
if addr.Params != nil && addr.Params.Length() > 0 {
buffer.WriteString(";")
buffer.WriteString(addr.Params.ToString(';'))
}
return buffer.String()
}
func (addr *Address) Clone() *Address {
var name MaybeString
var uri Uri
var params Params
if addr.DisplayName != nil {
name = String{Str: addr.DisplayName.String()}
}
if addr.Uri != nil {
uri = addr.Uri.Clone()
}
if addr.Params != nil {
params = addr.Params.Clone()
}
return &Address{
DisplayName: name,
Uri: uri,
Params: params,
}
}
func (addr *Address) Equals(other interface{}) bool {
otherPtr, ok := other.(*Address)
if !ok {
return false
}
if addr == otherPtr {
return true
}
if addr == nil && otherPtr != nil || addr != nil && otherPtr == nil {
return false
}
res := true
if addr.DisplayName != otherPtr.DisplayName {
if addr.DisplayName == nil {
res = res && otherPtr.DisplayName == nil
} else {
res = res && addr.DisplayName.Equals(otherPtr.DisplayName)
}
}
if addr.Uri != otherPtr.Uri {
if addr.Uri == nil {
res = res && otherPtr.Uri == nil
} else {
res = res && addr.Uri.Equals(otherPtr.Uri)
}
}
if addr.Params != otherPtr.Params {
if addr.Params == nil {
res = res && otherPtr.Params == nil
} else {
res = res && addr.Params.Equals(otherPtr.Params)
}
}
return res
}
func (addr *Address) AsToHeader() *ToHeader {
to := &ToHeader{
DisplayName: addr.DisplayName,
}
if addr.Uri != nil {
to.Address = addr.Uri.Clone()
}
if addr.Params != nil {
to.Params = addr.Params.Clone()
}
return to
}
func (addr *Address) AsFromHeader() *FromHeader {
from := &FromHeader{
DisplayName: addr.DisplayName,
}
if addr.Uri != nil {
from.Address = addr.Uri.Clone()
}
if addr.Params != nil {
from.Params = addr.Params.Clone()
}
return from
}
func (addr *Address) AsContactHeader() *ContactHeader {
cnt := &ContactHeader{
DisplayName: addr.DisplayName,
}
if addr.Uri != nil {
cnt.Address = addr.Uri.Clone()
}
if addr.Params != nil {
cnt.Params = addr.Params.Clone()
}
return cnt
}
// Port number
type Port uint16
func (port *Port) Clone() *Port {
if port == nil {
return nil
}
newPort := *port
return &newPort
}
func (port *Port) String() string {
if port == nil {
return ""
}
return fmt.Sprintf("%d", *port)
}
func (port *Port) Equals(other interface{}) bool {
if p, ok := other.(*Port); ok {
return util.Uint16PtrEq((*uint16)(port), (*uint16)(p))
}
return false
}
// String wrapper
type MaybeString interface {
String() string
Equals(other interface{}) bool
}
type String struct {
Str string
}
func (str String) String() string {
return str.Str
}
func (str String) Equals(other interface{}) bool {
if v, ok := other.(string); ok {
return str.Str == v
}
if v, ok := other.(String); ok {
return str.Str == v.Str
}
return false
}
type CancelError interface {
Canceled() bool
}
type ExpireError interface {
Expired() bool
}
type MessageError interface {
error
// Malformed indicates that message is syntactically valid but has invalid headers, or
// without required headers.
Malformed() bool
// Broken or incomplete message, or not a SIP message
Broken() bool
}
// Broken or incomplete messages, or not a SIP message.
type BrokenMessageError struct {
Err error
Msg string
}
func (err *BrokenMessageError) Malformed() bool { return false }
func (err *BrokenMessageError) Broken() bool { return true }
func (err *BrokenMessageError) Error() string {
if err == nil {
return "<nil>"
}
s := "BrokenMessageError: " + err.Err.Error()
if err.Msg != "" {
s += fmt.Sprintf("\nMessage dump:\n%s", err.Msg)
}
return s
}
// syntactically valid but logically invalid message
type MalformedMessageError struct {
Err error
Msg string
}
func (err *MalformedMessageError) Malformed() bool { return true }
func (err *MalformedMessageError) Broken() bool { return false }
func (err *MalformedMessageError) Error() string {
if err == nil {
return "<nil>"
}
s := "MalformedMessageError: " + err.Err.Error()
if err.Msg != "" {
s += fmt.Sprintf("\nMessage dump:\n%s", err.Msg)
}
return s
}
type UnsupportedMessageError struct {
Err error
Msg string
}
func (err *UnsupportedMessageError) Malformed() bool { return true }
func (err *UnsupportedMessageError) Broken() bool { return false }
func (err *UnsupportedMessageError) Error() string {
if err == nil {
return "<nil>"
}
s := "UnsupportedMessageError: " + err.Err.Error()
if err.Msg != "" {
s += fmt.Sprintf("\nMessage dump:\n%s", err.Msg)
}
return s
}
type UnexpectedMessageError struct {
Err error
Msg string
}
func (err *UnexpectedMessageError) Broken() bool { return false }
func (err *UnexpectedMessageError) Malformed() bool { return false }
func (err *UnexpectedMessageError) Error() string {
if err == nil {
return "<nil>"
}
s := "UnexpectedMessageError: " + err.Err.Error()
if err.Msg != "" {
s += fmt.Sprintf("\nMessage dump:\n%s", err.Msg)
}
return s
}
const RFC3261BranchMagicCookie = "z9hG4bK"
// GenerateBranch returns random unique branch ID.
func GenerateBranch() string {
return strings.Join([]string{
RFC3261BranchMagicCookie,
util.RandString(32),
}, ".")
}
// DefaultPort returns protocol default port by network.
func DefaultPort(protocol string) Port {
switch strings.ToLower(protocol) {
case "tls":
return DefaultTlsPort
case "tcp":
return DefaultTcpPort
case "udp":
return DefaultUdpPort
case "ws":
return DefaultWsPort
case "wss":
return DefaultWssPort
default:
return DefaultTcpPort
}
}
func MakeDialogIDFromMessage(msg Message) (string, error) {
callID, ok := msg.CallID()
if !ok {
return "", fmt.Errorf("missing Call-ID header")
}
to, ok := msg.To()
if !ok {
return "", fmt.Errorf("missing To header")
}
toTag, ok := to.Params.Get("tag")
if !ok {
return "", fmt.Errorf("missing tag param in To header")
}
from, ok := msg.From()
if !ok {
return "", fmt.Errorf("missing To header")
}
fromTag, ok := from.Params.Get("tag")
if !ok {
return "", fmt.Errorf("missing tag param in From header")
}
return MakeDialogID(string(*callID), toTag.String(), fromTag.String()), nil
}
func MakeDialogID(callID, innerID, externalID string) string {
return strings.Join([]string{callID, innerID, externalID}, "__")
}

View file

@ -0,0 +1,37 @@
package sip
import "fmt"
type RequestError struct {
Request Request
Response Response
Code uint
Reason string
}
func NewRequestError(code uint, reason string, request Request, response Response) *RequestError {
err := &RequestError{
Code: code,
Reason: reason,
}
if request != nil {
err.Request = CopyRequest(request)
}
if response != nil {
err.Response = CopyResponse(response)
}
return err
}
func (err *RequestError) Error() string {
if err == nil {
return "<nil>"
}
reason := err.Reason
if err.Code != 0 {
reason += fmt.Sprintf(" (Code %d)", err.Code)
}
return fmt.Sprintf("sip.RequestError: request failed with reason '%s'", reason)
}

View file

@ -0,0 +1,230 @@
package sip
import (
"strconv"
"strings"
)
// Copyright 2009 The Go Authors. All rights reserved.
// This is actually shorten copy of escape/unescape helpers of the net/url package.
type encoding int
const (
EncodeUserPassword encoding = 1 + iota
EncodeHost
EncodeZone
EncodeQueryComponent
)
type EscapeError string
func (e EscapeError) Error() string {
return "invalid URL escape " + strconv.Quote(string(e))
}
type InvalidHostError string
func (e InvalidHostError) Error() string {
return "invalid character " + strconv.Quote(string(e)) + " in host name"
}
// unescape unescapes a string; the mode specifies
// which section of the URL string is being unescaped.
func Unescape(s string, mode encoding) (string, error) {
// Count %, check that they're well-formed.
n := 0
hasPlus := false
for i := 0; i < len(s); {
switch s[i] {
case '%':
n++
if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) {
s = s[i:]
if len(s) > 3 {
s = s[:3]
}
return "", EscapeError(s)
}
// Per https://tools.ietf.org/html/rfc3986#page-21
// in the host component %-encoding can only be used
// for non-ASCII bytes.
// But https://tools.ietf.org/html/rfc6874#section-2
// introduces %25 being allowed to escape a percent sign
// in IPv6 scoped-address literals. Yay.
if mode == EncodeHost && unhex(s[i+1]) < 8 && s[i:i+3] != "%25" {
return "", EscapeError(s[i : i+3])
}
if mode == EncodeZone {
// RFC 6874 says basically "anything goes" for zone identifiers
// and that even non-ASCII can be redundantly escaped,
// but it seems prudent to restrict %-escaped bytes here to those
// that are valid host name bytes in their unescaped form.
// That is, you can use escaping in the zone identifier but not
// to introduce bytes you couldn't just write directly.
// But Windows puts spaces here! Yay.
v := unhex(s[i+1])<<4 | unhex(s[i+2])
if s[i:i+3] != "%25" && v != ' ' && shouldEscape(v, EncodeHost) {
return "", EscapeError(s[i : i+3])
}
}
i += 3
case '+':
hasPlus = mode == EncodeQueryComponent
i++
default:
if (mode == EncodeHost || mode == EncodeZone) && s[i] < 0x80 && shouldEscape(s[i], mode) {
return "", InvalidHostError(s[i : i+1])
}
i++
}
}
if n == 0 && !hasPlus {
return s, nil
}
var t strings.Builder
t.Grow(len(s) - 2*n)
for i := 0; i < len(s); i++ {
switch s[i] {
case '%':
t.WriteByte(unhex(s[i+1])<<4 | unhex(s[i+2]))
i += 2
case '+':
t.WriteByte('+')
default:
t.WriteByte(s[i])
}
}
return t.String(), nil
}
func ishex(c byte) bool {
switch {
case '0' <= c && c <= '9':
return true
case 'a' <= c && c <= 'f':
return true
case 'A' <= c && c <= 'F':
return true
}
return false
}
func unhex(c byte) byte {
switch {
case '0' <= c && c <= '9':
return c - '0'
case 'a' <= c && c <= 'f':
return c - 'a' + 10
case 'A' <= c && c <= 'F':
return c - 'A' + 10
}
return 0
}
// Return true if the specified character should be escaped when
// appearing in a URL string, according to RFC 3986.
//
// Please be informed that for now shouldEscape does not check all
// reserved characters correctly. See golang.org/issue/5684.
func shouldEscape(c byte, mode encoding) bool {
// §2.3 Unreserved characters (alphanum)
if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || '0' <= c && c <= '9' {
return false
}
if mode == EncodeHost || mode == EncodeZone {
// §3.2.2 Host allows
// sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
// as part of reg-name.
// We add : because we include :port as part of host.
// We add [ ] because we include [ipv6]:port as part of host.
// We add < > because they're the only characters left that
// we could possibly allow, and Parse will reject them if we
// escape them (because hosts can't use %-encoding for
// ASCII bytes).
switch c {
case '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', ':', '[', ']', '<', '>', '"':
return false
}
}
switch c {
case '-', '_', '.', '~': // §2.3 Unreserved characters (mark)
return false
case '$', '&', '+', ',', '/', ':', ';', '=', '?', '@': // §2.2 Reserved characters (reserved)
// Different sections of the URL allow a few of
// the reserved characters to appear unescaped.
switch mode {
case EncodeUserPassword: // §3.2.1
// The RFC allows ';', ':', '&', '=', '+', '$', and ',' in
// userinfo, so we must escape only '@', '/', and '?'.
// The parsing of userinfo treats ':' as special so we must escape
// that too.
return c == '@' || c == '/' || c == '?' || c == ':'
case EncodeQueryComponent: // §3.4
// The RFC reserves (so we must escape) everything.
return true
}
}
// Everything else must be escaped.
return true
}
const upperhex = "0123456789ABCDEF"
func Escape(s string, mode encoding) string {
spaceCount, hexCount := 0, 0
for i := 0; i < len(s); i++ {
c := s[i]
if shouldEscape(c, mode) {
if c == ' ' && mode == EncodeQueryComponent {
spaceCount++
} else {
hexCount++
}
}
}
if spaceCount == 0 && hexCount == 0 {
return s
}
var buf [64]byte
var t []byte
required := len(s) + 2*hexCount
if required <= len(buf) {
t = buf[:required]
} else {
t = make([]byte, required)
}
if hexCount == 0 {
copy(t, s)
return string(t)
}
j := 0
for i := 0; i < len(s); i++ {
switch c := s[i]; {
case c == ' ' && mode == EncodeQueryComponent:
t[j] = c
j++
case shouldEscape(c, mode):
t[j] = '%'
t[j+1] = upperhex[c>>4]
t[j+2] = upperhex[c&15]
j += 3
default:
t[j] = s[i]
j++
}
}
return string(t)
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,542 @@
package sip
import (
"bytes"
"strings"
"sync"
uuid "github.com/satori/go.uuid"
"github.com/ghettovoice/gosip/log"
)
// A representation of a SIP method.
// This is syntactic sugar around the string type, so make sure to use
// the Equals method rather than built-in equality, or you'll fall foul of case differences.
// If you're defining your own Method, uppercase is preferred but not compulsory.
type RequestMethod string
// StatusCode - response status code: 1xx - 6xx
type StatusCode uint16
// Determine if the given method equals some other given method.
// This is syntactic sugar for case insensitive equality checking.
func (method *RequestMethod) Equals(other *RequestMethod) bool {
if method != nil && other != nil {
return strings.EqualFold(string(*method), string(*other))
} else {
return method == other
}
}
// It's nicer to avoid using raw strings to represent methods, so the following standard
// method names are defined here as constants for convenience.
const (
INVITE RequestMethod = "INVITE"
ACK RequestMethod = "ACK"
CANCEL RequestMethod = "CANCEL"
BYE RequestMethod = "BYE"
REGISTER RequestMethod = "REGISTER"
OPTIONS RequestMethod = "OPTIONS"
SUBSCRIBE RequestMethod = "SUBSCRIBE"
NOTIFY RequestMethod = "NOTIFY"
REFER RequestMethod = "REFER"
INFO RequestMethod = "INFO"
MESSAGE RequestMethod = "MESSAGE"
PRACK RequestMethod = "PRACK"
UPDATE RequestMethod = "UPDATE"
PUBLISH RequestMethod = "PUBLISH"
)
type MessageID string
func NextMessageID() MessageID {
return MessageID(uuid.Must(uuid.NewV4()).String())
}
// Message introduces common SIP message RFC 3261 - 7.
type Message interface {
MessageID() MessageID
Clone() Message
// Start line returns message start line.
StartLine() string
// String returns string representation of SIP message in RFC 3261 form.
String() string
// Short returns short string info about message.
Short() string
// SipVersion returns SIP protocol version.
SipVersion() string
// SetSipVersion sets SIP protocol version.
SetSipVersion(version string)
// Headers returns all message headers.
Headers() []Header
// GetHeaders returns slice of headers of the given type.
GetHeaders(name string) []Header
// AppendHeader appends header to message.
AppendHeader(header Header)
// PrependHeader prepends header to message.
PrependHeader(header Header)
PrependHeaderAfter(header Header, afterName string)
// RemoveHeader removes header from message.
RemoveHeader(name string)
ReplaceHeaders(name string, headers []Header)
// Body returns message body.
Body() string
// SetBody sets message body.
SetBody(body string, setContentLength bool)
/* Helper getters for common headers */
// CallID returns 'Call-ID' header.
CallID() (*CallID, bool)
// Via returns the top 'Via' header field.
Via() (ViaHeader, bool)
// ViaHop returns the first segment of the top 'Via' header.
ViaHop() (*ViaHop, bool)
// From returns 'From' header field.
From() (*FromHeader, bool)
// To returns 'To' header field.
To() (*ToHeader, bool)
// CSeq returns 'CSeq' header field.
CSeq() (*CSeq, bool)
ContentLength() (*ContentLength, bool)
ContentType() (*ContentType, bool)
Contact() (*ContactHeader, bool)
Transport() string
SetTransport(tp string)
Source() string
SetSource(src string)
Destination() string
SetDestination(dest string)
IsCancel() bool
IsAck() bool
Fields() log.Fields
WithFields(fields log.Fields) Message
}
// headers is a struct with methods to work with SIP headers.
type headers struct {
mu sync.RWMutex
// The logical SIP headers attached to this message.
headers map[string][]Header
// The order the headers should be displayed in.
headerOrder []string
}
func newHeaders(hdrs []Header) *headers {
hs := new(headers)
hs.headers = make(map[string][]Header)
hs.headerOrder = make([]string, 0)
for _, header := range hdrs {
hs.AppendHeader(header)
}
return hs
}
func (hs *headers) String() string {
buffer := bytes.Buffer{}
hs.mu.RLock()
// Construct each header in turn and add it to the message.
for typeIdx, name := range hs.headerOrder {
headers := hs.headers[name]
for idx, header := range headers {
buffer.WriteString(header.String())
if typeIdx < len(hs.headerOrder) || idx < len(headers) {
buffer.WriteString("\r\n")
}
}
}
hs.mu.RUnlock()
return buffer.String()
}
// Add the given header.
func (hs *headers) AppendHeader(header Header) {
name := strings.ToLower(header.Name())
hs.mu.Lock()
if _, ok := hs.headers[name]; ok {
hs.headers[name] = append(hs.headers[name], header)
} else {
hs.headers[name] = []Header{header}
hs.headerOrder = append(hs.headerOrder, name)
}
hs.mu.Unlock()
}
// AddFrontHeader adds header to the front of header list
// if there is no header has h's name, add h to the font of all headers
// if there are some headers have h's name, add h to front of the sublist
func (hs *headers) PrependHeader(header Header) {
name := strings.ToLower(header.Name())
hs.mu.Lock()
if hdrs, ok := hs.headers[name]; ok {
hs.headers[name] = append([]Header{header}, hdrs...)
} else {
hs.headers[name] = []Header{header}
newOrder := make([]string, 1, len(hs.headerOrder)+1)
newOrder[0] = name
hs.headerOrder = append(newOrder, hs.headerOrder...)
}
hs.mu.Unlock()
}
func (hs *headers) PrependHeaderAfter(header Header, afterName string) {
headerName := strings.ToLower(header.Name())
afterName = strings.ToLower(afterName)
hs.mu.Lock()
if _, ok := hs.headers[afterName]; ok {
afterIdx := -1
headerIdx := -1
for i, name := range hs.headerOrder {
if name == afterName {
afterIdx = i
}
if name == headerName {
headerIdx = i
}
}
if headerIdx == -1 {
hs.headers[headerName] = []Header{header}
newOrder := make([]string, 0)
newOrder = append(newOrder, hs.headerOrder[:afterIdx+1]...)
newOrder = append(newOrder, headerName)
newOrder = append(newOrder, hs.headerOrder[afterIdx+1:]...)
hs.headerOrder = newOrder
} else {
hs.headers[headerName] = append([]Header{header}, hs.headers[headerName]...)
newOrder := make([]string, 0)
if afterIdx < headerIdx {
newOrder = append(newOrder, hs.headerOrder[:afterIdx+1]...)
newOrder = append(newOrder, headerName)
newOrder = append(newOrder, hs.headerOrder[afterIdx+1:headerIdx]...)
newOrder = append(newOrder, hs.headerOrder[headerIdx+1:]...)
} else {
newOrder = append(newOrder, hs.headerOrder[:headerIdx]...)
newOrder = append(newOrder, hs.headerOrder[headerIdx+1:afterIdx+1]...)
newOrder = append(newOrder, headerName)
newOrder = append(newOrder, hs.headerOrder[afterIdx+1:]...)
}
hs.headerOrder = newOrder
}
hs.mu.Unlock()
} else {
hs.mu.Unlock()
hs.PrependHeader(header)
}
}
func (hs *headers) ReplaceHeaders(name string, headers []Header) {
name = strings.ToLower(name)
hs.mu.Lock()
if _, ok := hs.headers[name]; ok {
hs.headers[name] = headers
}
hs.mu.Unlock()
}
// Gets some headers.
func (hs *headers) Headers() []Header {
hdrs := make([]Header, 0)
hs.mu.RLock()
for _, key := range hs.headerOrder {
hdrs = append(hdrs, hs.headers[key]...)
}
hs.mu.RUnlock()
return hdrs
}
func (hs *headers) GetHeaders(name string) []Header {
name = strings.ToLower(name)
hs.mu.RLock()
defer hs.mu.RUnlock()
if hs.headers == nil {
hs.headers = map[string][]Header{}
hs.headerOrder = []string{}
}
if headers, ok := hs.headers[name]; ok {
return headers
}
return []Header{}
}
func (hs *headers) RemoveHeader(name string) {
name = strings.ToLower(name)
hs.mu.Lock()
delete(hs.headers, name)
// update order slice
for idx, entry := range hs.headerOrder {
if entry == name {
hs.headerOrder = append(hs.headerOrder[:idx], hs.headerOrder[idx+1:]...)
break
}
}
hs.mu.Unlock()
}
// CloneHeaders returns all cloned headers in slice.
func (hs *headers) CloneHeaders() []Header {
return cloneHeaders(hs)
}
func cloneHeaders(msg interface{ Headers() []Header }) []Header {
hdrs := make([]Header, 0)
for _, header := range msg.Headers() {
hdrs = append(hdrs, header.Clone())
}
return hdrs
}
func (hs *headers) CallID() (*CallID, bool) {
hdrs := hs.GetHeaders("Call-ID")
if len(hdrs) == 0 {
return nil, false
}
callId, ok := hdrs[0].(*CallID)
if !ok {
return nil, false
}
return callId, true
}
func (hs *headers) Via() (ViaHeader, bool) {
hdrs := hs.GetHeaders("Via")
if len(hdrs) == 0 {
return nil, false
}
via, ok := (hdrs[0]).(ViaHeader)
if !ok {
return nil, false
}
return via, true
}
func (hs *headers) ViaHop() (*ViaHop, bool) {
via, ok := hs.Via()
if !ok {
return nil, false
}
hops := []*ViaHop(via)
if len(hops) == 0 {
return nil, false
}
return hops[0], true
}
func (hs *headers) From() (*FromHeader, bool) {
hdrs := hs.GetHeaders("From")
if len(hdrs) == 0 {
return nil, false
}
from, ok := hdrs[0].(*FromHeader)
if !ok {
return nil, false
}
return from, true
}
func (hs *headers) To() (*ToHeader, bool) {
hdrs := hs.GetHeaders("To")
if len(hdrs) == 0 {
return nil, false
}
to, ok := hdrs[0].(*ToHeader)
if !ok {
return nil, false
}
return to, true
}
func (hs *headers) CSeq() (*CSeq, bool) {
hdrs := hs.GetHeaders("CSeq")
if len(hdrs) == 0 {
return nil, false
}
cseq, ok := hdrs[0].(*CSeq)
if !ok {
return nil, false
}
return cseq, true
}
func (hs *headers) ContentLength() (*ContentLength, bool) {
hdrs := hs.GetHeaders("Content-Length")
if len(hdrs) == 0 {
return nil, false
}
contentLength, ok := hdrs[0].(*ContentLength)
if !ok {
return nil, false
}
return contentLength, true
}
func (hs *headers) ContentType() (*ContentType, bool) {
hdrs := hs.GetHeaders("Content-Type")
if len(hdrs) == 0 {
return nil, false
}
contentType, ok := hdrs[0].(*ContentType)
if !ok {
return nil, false
}
return contentType, true
}
func (hs *headers) Contact() (*ContactHeader, bool) {
hdrs := hs.GetHeaders("Contact")
if len(hdrs) == 0 {
return nil, false
}
contactHeader, ok := hdrs[0].(*ContactHeader)
if !ok {
return nil, false
}
return contactHeader, true
}
// basic message implementation
type message struct {
// message headers
*headers
mu sync.RWMutex
messID MessageID
sipVersion string
body string
startLine func() string
tp string
src string
dest string
fields log.Fields
}
func (msg *message) MessageID() MessageID {
return msg.messID
}
func (msg *message) StartLine() string {
return msg.startLine()
}
func (msg *message) Fields() log.Fields {
msg.mu.RLock()
defer msg.mu.RUnlock()
return msg.fields.WithFields(log.Fields{
"transport": msg.tp,
"source": msg.src,
"destination": msg.dest,
})
}
func (msg *message) String() string {
var buffer bytes.Buffer
// write message start line
buffer.WriteString(msg.StartLine() + "\r\n")
// Write the headers.
msg.mu.RLock()
buffer.WriteString(msg.headers.String())
msg.mu.RUnlock()
// message body
buffer.WriteString("\r\n" + msg.Body())
return buffer.String()
}
func (msg *message) SipVersion() string {
msg.mu.RLock()
defer msg.mu.RUnlock()
return msg.sipVersion
}
func (msg *message) SetSipVersion(version string) {
msg.mu.Lock()
msg.sipVersion = version
msg.mu.Unlock()
}
func (msg *message) Body() string {
msg.mu.RLock()
defer msg.mu.RUnlock()
return msg.body
}
// SetBody sets message body, calculates it length and add 'Content-Length' header.
func (msg *message) SetBody(body string, setContentLength bool) {
msg.mu.Lock()
msg.body = body
msg.mu.Unlock()
if setContentLength {
hdrs := msg.GetHeaders("Content-Length")
if len(hdrs) == 0 {
length := ContentLength(len(body))
msg.AppendHeader(&length)
} else {
length := ContentLength(len(body))
msg.ReplaceHeaders("Content-Length", []Header{&length})
}
}
}
func (msg *message) Transport() string {
msg.mu.RLock()
defer msg.mu.RUnlock()
return msg.tp
}
func (msg *message) SetTransport(tp string) {
msg.mu.Lock()
msg.tp = strings.ToUpper(tp)
msg.mu.Unlock()
}
func (msg *message) Source() string {
msg.mu.RLock()
defer msg.mu.RUnlock()
return msg.src
}
func (msg *message) SetSource(src string) {
msg.mu.Lock()
msg.src = src
msg.mu.Unlock()
}
func (msg *message) Destination() string {
msg.mu.RLock()
defer msg.mu.RUnlock()
return msg.dest
}
func (msg *message) SetDestination(dest string) {
msg.mu.Lock()
msg.dest = dest
msg.mu.Unlock()
}
// Copy all headers of one type from one message to another.
// Appending to any headers that were already there.
func CopyHeaders(name string, from, to Message) {
name = strings.ToLower(name)
for _, h := range from.GetHeaders(name) {
to.AppendHeader(h.Clone())
}
}
func PrependCopyHeaders(name string, from, to Message) {
name = strings.ToLower(name)
for _, h := range from.GetHeaders(name) {
to.PrependHeader(h.Clone())
}
}
type MessageMapper func(msg Message) Message

View file

@ -0,0 +1,5 @@
# SIP parser
> Package implements SIP protocol parser compatible with [RFC 3261](https://tools.ietf.org/html/rfc3261)
Originally forked from [gossip](https://github.com/StefanKopieczek/gossip) library by @StefanKopieczek.

View file

@ -0,0 +1,26 @@
package parser
type Error interface {
error
// Syntax indicates that this is syntax error
Syntax() bool
}
type InvalidStartLineError string
func (err InvalidStartLineError) Syntax() bool { return true }
func (err InvalidStartLineError) Malformed() bool { return false }
func (err InvalidStartLineError) Broken() bool { return true }
func (err InvalidStartLineError) Error() string { return "parser.InvalidStartLineError: " + string(err) }
type InvalidMessageFormat string
func (err InvalidMessageFormat) Syntax() bool { return true }
func (err InvalidMessageFormat) Malformed() bool { return true }
func (err InvalidMessageFormat) Broken() bool { return true }
func (err InvalidMessageFormat) Error() string { return "parser.InvalidMessageFormat: " + string(err) }
type WriteError string
func (err WriteError) Syntax() bool { return false }
func (err WriteError) Error() string { return "parser.WriteError: " + string(err) }

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,131 @@
// Forked from github.com/StefanKopieczek/gossip by @StefanKopieczek
package parser
import (
"bufio"
"bytes"
"fmt"
"io"
"sync"
"github.com/ghettovoice/gosip/log"
)
// parserBuffer is a specialized buffer for use in the parser.
// It is written to via the non-blocking Write.
// It exposes various blocking read methods, which wait until the requested
// data is available, and then return it.
type parserBuffer struct {
mu sync.RWMutex
writer io.Writer
buffer bytes.Buffer
// Wraps parserBuffer.pipeReader
reader *bufio.Reader
// Don't access this directly except when closing.
pipeReader *io.PipeReader
log log.Logger
}
// Create a new parserBuffer object (see struct comment for object details).
// Note that resources owned by the parserBuffer may not be able to be GCed
// until the Dispose() method is called.
func newParserBuffer(logger log.Logger) *parserBuffer {
var pb parserBuffer
pb.pipeReader, pb.writer = io.Pipe()
pb.reader = bufio.NewReader(pb.pipeReader)
pb.log = logger.
WithPrefix("parser.parserBuffer").
WithFields(log.Fields{
"parser_buffer_ptr": fmt.Sprintf("%p", &pb),
})
return &pb
}
func (pb *parserBuffer) Log() log.Logger {
return pb.log
}
func (pb *parserBuffer) Write(p []byte) (n int, err error) {
pb.mu.RLock()
defer pb.mu.RUnlock()
return pb.writer.Write(p)
}
// Block until the buffer contains at least one CRLF-terminated line.
// Return the line, excluding the terminal CRLF, and delete it from the buffer.
// Returns an error if the parserbuffer has been stopped.
func (pb *parserBuffer) NextLine() (response string, err error) {
var buffer bytes.Buffer
var data string
var b byte
// There has to be a better way!
for {
data, err = pb.reader.ReadString('\r')
if err != nil {
return
}
buffer.WriteString(data)
b, err = pb.reader.ReadByte()
if err != nil {
return
}
buffer.WriteByte(b)
if b == '\n' {
response = buffer.String()
response = response[:len(response)-2]
pb.Log().Tracef("return line '%s'", response)
return
}
}
}
// Block until the buffer contains at least n characters.
// Return precisely those n characters, then delete them from the buffer.
func (pb *parserBuffer) NextChunk(n int) (response string, err error) {
var data = make([]byte, n)
var read int
for total := 0; total < n; {
read, err = pb.reader.Read(data[total:])
total += read
if err != nil {
return
}
}
response = string(data)
pb.Log().Tracef("return chunk:\n%s", response)
return
}
// Stop the parser buffer.
func (pb *parserBuffer) Stop() {
pb.mu.RLock()
if err := pb.pipeReader.Close(); err != nil {
pb.Log().Errorf("parser pipe reader close failed: %s", err)
}
pb.mu.RUnlock()
pb.Log().Trace("parser buffer stopped")
}
func (pb *parserBuffer) Reset() {
pb.mu.Lock()
pb.pipeReader, pb.writer = io.Pipe()
pb.reader.Reset(pb.pipeReader)
pb.mu.Unlock()
}

View file

@ -0,0 +1,382 @@
package sip
import (
"bytes"
"fmt"
"strconv"
"strings"
"github.com/ghettovoice/gosip/log"
)
// Request RFC 3261 - 7.1.
type Request interface {
Message
Method() RequestMethod
SetMethod(method RequestMethod)
Recipient() Uri
SetRecipient(recipient Uri)
/* Common Helpers */
IsInvite() bool
}
type request struct {
message
method RequestMethod
recipient Uri
}
func NewRequest(
messID MessageID,
method RequestMethod,
recipient Uri,
sipVersion string,
hdrs []Header,
body string,
fields log.Fields,
) Request {
req := new(request)
if messID == "" {
req.messID = NextMessageID()
} else {
req.messID = messID
}
req.startLine = req.StartLine
req.sipVersion = sipVersion
req.headers = newHeaders(hdrs)
req.method = method
req.recipient = recipient
req.body = body
req.fields = fields.WithFields(log.Fields{
"request_id": req.messID,
})
return req
}
func (req *request) Short() string {
if req == nil {
return "<nil>"
}
fields := log.Fields{
"method": req.Method(),
"recipient": req.Recipient(),
"transport": req.Transport(),
"source": req.Source(),
"destination": req.Destination(),
}
if cseq, ok := req.CSeq(); ok {
fields["sequence"] = cseq.SeqNo
}
fields = req.Fields().WithFields(fields)
return fmt.Sprintf("sip.Request<%s>", fields)
}
func (req *request) Method() RequestMethod {
req.mu.RLock()
defer req.mu.RUnlock()
return req.method
}
func (req *request) SetMethod(method RequestMethod) {
req.mu.Lock()
req.method = method
req.mu.Unlock()
}
func (req *request) Recipient() Uri {
req.mu.RLock()
defer req.mu.RUnlock()
return req.recipient
}
func (req *request) SetRecipient(recipient Uri) {
req.mu.Lock()
req.recipient = recipient
req.mu.Unlock()
}
// StartLine returns Request Line - RFC 2361 7.1.
func (req *request) StartLine() string {
var buffer bytes.Buffer
// Every SIP request starts with a Request Line - RFC 2361 7.1.
buffer.WriteString(
fmt.Sprintf(
"%s %s %s",
string(req.Method()),
req.Recipient(),
req.SipVersion(),
),
)
return buffer.String()
}
func (req *request) Clone() Message {
return cloneRequest(req, "", nil)
}
func (req *request) Fields() log.Fields {
return req.fields.WithFields(log.Fields{
"transport": req.Transport(),
"source": req.Source(),
"destination": req.Destination(),
})
}
func (req *request) WithFields(fields log.Fields) Message {
req.mu.Lock()
req.fields = req.fields.WithFields(fields)
req.mu.Unlock()
return req
}
func (req *request) IsInvite() bool {
return req.Method() == INVITE
}
func (req *request) IsAck() bool {
return req.Method() == ACK
}
func (req *request) IsCancel() bool {
return req.Method() == CANCEL
}
func (req *request) Transport() string {
if tp := req.message.Transport(); tp != "" {
return strings.ToUpper(tp)
}
var tp string
if viaHop, ok := req.ViaHop(); ok && viaHop.Transport != "" {
tp = viaHop.Transport
} else {
tp = DefaultProtocol
}
uri := req.Recipient()
if hdrs := req.GetHeaders("Route"); len(hdrs) > 0 {
routeHeader, ok := hdrs[0].(*RouteHeader)
if ok && len(routeHeader.Addresses) > 0 {
uri = routeHeader.Addresses[0]
}
}
if uri != nil {
if uri.UriParams() != nil {
if val, ok := uri.UriParams().Get("transport"); ok && !val.Equals("") {
tp = strings.ToUpper(val.String())
}
}
if uri.IsEncrypted() {
if tp == "TCP" {
tp = "TLS"
} else if tp == "WS" {
tp = "WSS"
}
}
}
if tp == "UDP" && len(req.String()) > int(MTU)-200 {
tp = "TCP"
}
return tp
}
func (req *request) Source() string {
if src := req.message.Source(); src != "" {
return src
}
viaHop, ok := req.ViaHop()
if !ok {
return ""
}
var (
host string
port Port
)
host = viaHop.Host
if viaHop.Port != nil {
port = *viaHop.Port
} else {
port = DefaultPort(req.Transport())
}
if viaHop.Params != nil {
if received, ok := viaHop.Params.Get("received"); ok && received.String() != "" {
host = received.String()
}
if rport, ok := viaHop.Params.Get("rport"); ok && rport != nil && rport.String() != "" {
if p, err := strconv.Atoi(rport.String()); err == nil {
port = Port(uint16(p))
}
}
}
return fmt.Sprintf("%v:%v", host, port)
}
func (req *request) Destination() string {
if dest := req.message.Destination(); dest != "" {
return dest
}
var uri *SipUri
if hdrs := req.GetHeaders("Route"); len(hdrs) > 0 {
routeHeader, ok := hdrs[0].(*RouteHeader)
if ok && len(routeHeader.Addresses) > 0 {
uri = routeHeader.Addresses[0].(*SipUri)
}
}
if uri == nil {
if u, ok := req.Recipient().(*SipUri); ok {
uri = u
} else {
return ""
}
}
host := uri.FHost
var port Port
if uri.FPort != nil {
port = *uri.FPort
} else {
port = DefaultPort(req.Transport())
}
return fmt.Sprintf("%v:%v", host, port)
}
// NewAckRequest creates ACK request for 2xx INVITE
// https://tools.ietf.org/html/rfc3261#section-13.2.2.4
func NewAckRequest(ackID MessageID, inviteRequest Request, inviteResponse Response, body string, fields log.Fields) Request {
recipient := inviteRequest.Recipient()
if contact, ok := inviteResponse.Contact(); ok {
// For ws and wss (like clients in browser), don't use Contact
if strings.Index(strings.ToLower(recipient.String()), "transport=ws") == -1 {
recipient = contact.Address
}
}
ackRequest := NewRequest(
ackID,
ACK,
recipient,
inviteRequest.SipVersion(),
[]Header{},
body,
inviteRequest.Fields().
WithFields(fields).
WithFields(log.Fields{
"invite_request_id": inviteRequest.MessageID(),
"invite_response_id": inviteResponse.MessageID(),
}),
)
CopyHeaders("Via", inviteRequest, ackRequest)
if inviteResponse.IsSuccess() {
// update branch, 2xx ACK is separate Tx
viaHop, _ := ackRequest.ViaHop()
viaHop.Params.Add("branch", String{Str: GenerateBranch()})
}
if len(inviteRequest.GetHeaders("Route")) > 0 {
CopyHeaders("Route", inviteRequest, ackRequest)
} else {
hdrs := inviteResponse.GetHeaders("Record-Route")
for i := len(hdrs) - 1; i >= 0; i-- {
h := hdrs[i]
uris := make([]Uri, 0)
for j := len(h.(*RecordRouteHeader).Addresses) - 1; j >= 0; j-- {
uris = append(uris, h.(*RecordRouteHeader).Addresses[j].Clone())
}
ackRequest.AppendHeader(&RouteHeader{
Addresses: uris,
})
}
}
maxForwardsHeader := MaxForwards(70)
ackRequest.AppendHeader(&maxForwardsHeader)
CopyHeaders("From", inviteRequest, ackRequest)
CopyHeaders("To", inviteResponse, ackRequest)
CopyHeaders("Call-ID", inviteRequest, ackRequest)
CopyHeaders("CSeq", inviteRequest, ackRequest)
cseq, _ := ackRequest.CSeq()
cseq.MethodName = ACK
ackRequest.SetBody("", true)
ackRequest.SetTransport(inviteRequest.Transport())
ackRequest.SetSource(inviteRequest.Source())
ackRequest.SetDestination(inviteRequest.Destination())
return ackRequest
}
func NewCancelRequest(cancelID MessageID, requestForCancel Request, fields log.Fields) Request {
cancelReq := NewRequest(
cancelID,
CANCEL,
requestForCancel.Recipient(),
requestForCancel.SipVersion(),
[]Header{},
"",
requestForCancel.Fields().
WithFields(fields).
WithFields(log.Fields{
"cancelling_request_id": requestForCancel.MessageID(),
}),
)
viaHop, _ := requestForCancel.ViaHop()
cancelReq.AppendHeader(ViaHeader{viaHop.Clone()})
CopyHeaders("Route", requestForCancel, cancelReq)
maxForwardsHeader := MaxForwards(70)
cancelReq.AppendHeader(&maxForwardsHeader)
CopyHeaders("From", requestForCancel, cancelReq)
CopyHeaders("To", requestForCancel, cancelReq)
CopyHeaders("Call-ID", requestForCancel, cancelReq)
CopyHeaders("CSeq", requestForCancel, cancelReq)
cseq, _ := cancelReq.CSeq()
cseq.MethodName = CANCEL
cancelReq.SetBody("", true)
cancelReq.SetTransport(requestForCancel.Transport())
cancelReq.SetSource(requestForCancel.Source())
cancelReq.SetDestination(requestForCancel.Destination())
return cancelReq
}
func cloneRequest(req Request, id MessageID, fields log.Fields) Request {
newFields := req.Fields()
if fields != nil {
newFields = newFields.WithFields(fields)
}
newReq := NewRequest(
id,
req.Method(),
req.Recipient().Clone(),
req.SipVersion(),
cloneHeaders(req),
req.Body(),
newFields,
)
newReq.SetTransport(req.Transport())
newReq.SetSource(req.Source())
newReq.SetDestination(req.Destination())
return newReq
}
func CopyRequest(req Request) Request {
return cloneRequest(req, req.MessageID(), nil)
}

View file

@ -0,0 +1,310 @@
package sip
import (
"bytes"
"fmt"
"strconv"
"strings"
"github.com/ghettovoice/gosip/log"
)
// Response RFC 3261 - 7.2.
type Response interface {
Message
StatusCode() StatusCode
SetStatusCode(code StatusCode)
Reason() string
SetReason(reason string)
// Previous returns previous provisional responses
Previous() []Response
SetPrevious(responses []Response)
/* Common helpers */
IsProvisional() bool
IsSuccess() bool
IsRedirection() bool
IsClientError() bool
IsServerError() bool
IsGlobalError() bool
}
type response struct {
message
status StatusCode
reason string
previous []Response
}
func NewResponse(
messID MessageID,
sipVersion string,
statusCode StatusCode,
reason string,
hdrs []Header,
body string,
fields log.Fields,
) Response {
res := new(response)
if messID == "" {
res.messID = NextMessageID()
} else {
res.messID = messID
}
res.startLine = res.StartLine
res.sipVersion = sipVersion
res.headers = newHeaders(hdrs)
res.status = statusCode
res.reason = reason
res.body = body
res.fields = fields.WithFields(log.Fields{
"response_id": res.messID,
})
res.previous = make([]Response, 0)
return res
}
func (res *response) Short() string {
if res == nil {
return "<nil>"
}
fields := log.Fields{
"status": res.StatusCode(),
"reason": res.Reason(),
"transport": res.Transport(),
"source": res.Source(),
"destination": res.Destination(),
}
if cseq, ok := res.CSeq(); ok {
fields["method"] = cseq.MethodName
fields["sequence"] = cseq.SeqNo
}
fields = res.Fields().WithFields(fields)
return fmt.Sprintf("sip.Response<%s>", fields)
}
func (res *response) StatusCode() StatusCode {
res.mu.RLock()
defer res.mu.RUnlock()
return res.status
}
func (res *response) SetStatusCode(code StatusCode) {
res.mu.Lock()
res.status = code
res.mu.Unlock()
}
func (res *response) Reason() string {
res.mu.RLock()
defer res.mu.RUnlock()
return res.reason
}
func (res *response) SetReason(reason string) {
res.mu.Lock()
res.reason = reason
res.mu.Unlock()
}
func (res *response) Previous() []Response {
res.mu.RLock()
defer res.mu.RUnlock()
return res.previous
}
func (res *response) SetPrevious(responses []Response) {
res.mu.Lock()
res.previous = responses
res.mu.Unlock()
}
// StartLine returns Response Status Line - RFC 2361 7.2.
func (res *response) StartLine() string {
var buffer bytes.Buffer
// Every SIP response starts with a Status Line - RFC 2361 7.2.
buffer.WriteString(
fmt.Sprintf(
"%s %d %s",
res.SipVersion(),
res.StatusCode(),
res.Reason(),
),
)
return buffer.String()
}
func (res *response) Clone() Message {
return cloneResponse(res, "", nil)
}
func (res *response) Fields() log.Fields {
return res.fields.WithFields(log.Fields{
"transport": res.Transport(),
"source": res.Source(),
"destination": res.Destination(),
})
}
func (res *response) WithFields(fields log.Fields) Message {
res.mu.Lock()
res.fields = res.fields.WithFields(fields)
res.mu.Unlock()
return res
}
func (res *response) IsProvisional() bool {
return res.StatusCode() < 200
}
func (res *response) IsSuccess() bool {
return res.StatusCode() >= 200 && res.StatusCode() < 300
}
func (res *response) IsRedirection() bool {
return res.StatusCode() >= 300 && res.StatusCode() < 400
}
func (res *response) IsClientError() bool {
return res.StatusCode() >= 400 && res.StatusCode() < 500
}
func (res *response) IsServerError() bool {
return res.StatusCode() >= 500 && res.StatusCode() < 600
}
func (res *response) IsGlobalError() bool {
return res.StatusCode() >= 600
}
func (res *response) IsAck() bool {
if cseq, ok := res.CSeq(); ok {
return cseq.MethodName == ACK
}
return false
}
func (res *response) IsCancel() bool {
if cseq, ok := res.CSeq(); ok {
return cseq.MethodName == CANCEL
}
return false
}
func (res *response) Transport() string {
if tp := res.message.Transport(); tp != "" {
return strings.ToUpper(tp)
}
var tp string
if viaHop, ok := res.ViaHop(); ok && viaHop.Transport != "" {
tp = viaHop.Transport
} else {
tp = DefaultProtocol
}
return tp
}
func (res *response) Destination() string {
if dest := res.message.Destination(); dest != "" {
return dest
}
viaHop, ok := res.ViaHop()
if !ok {
return ""
}
var (
host string
port Port
)
host = viaHop.Host
if viaHop.Port != nil {
port = *viaHop.Port
} else {
port = DefaultPort(res.Transport())
}
if viaHop.Params != nil {
if received, ok := viaHop.Params.Get("received"); ok && received.String() != "" {
host = received.String()
}
if rport, ok := viaHop.Params.Get("rport"); ok && rport != nil && rport.String() != "" {
if p, err := strconv.Atoi(rport.String()); err == nil {
port = Port(uint16(p))
}
}
}
return fmt.Sprintf("%v:%v", host, port)
}
// RFC 3261 - 8.2.6
func NewResponseFromRequest(
resID MessageID,
req Request,
statusCode StatusCode,
reason string,
body string,
) Response {
res := NewResponse(
resID,
req.SipVersion(),
statusCode,
reason,
[]Header{},
"",
req.Fields(),
)
CopyHeaders("Record-Route", req, res)
CopyHeaders("Via", req, res)
CopyHeaders("From", req, res)
CopyHeaders("To", req, res)
CopyHeaders("Call-ID", req, res)
CopyHeaders("CSeq", req, res)
if statusCode == 100 {
CopyHeaders("Timestamp", req, res)
}
res.SetBody(body, true)
res.SetTransport(req.Transport())
res.SetSource(req.Destination())
res.SetDestination(req.Source())
return res
}
func cloneResponse(res Response, id MessageID, fields log.Fields) Response {
newFields := res.Fields()
if fields != nil {
newFields = newFields.WithFields(fields)
}
newRes := NewResponse(
id,
res.SipVersion(),
res.StatusCode(),
res.Reason(),
cloneHeaders(res),
res.Body(),
newFields,
)
newRes.SetPrevious(res.Previous())
newRes.SetTransport(res.Transport())
newRes.SetSource(res.Source())
newRes.SetDestination(res.Destination())
return newRes
}
func CopyResponse(res Response) Response {
return cloneResponse(res, res.MessageID(), nil)
}

View file

@ -0,0 +1,28 @@
package sip
type TransactionKey string
func (key TransactionKey) String() string {
return string(key)
}
type Transaction interface {
Origin() Request
Key() TransactionKey
String() string
Errors() <-chan error
Done() <-chan bool
}
type ServerTransaction interface {
Transaction
Respond(res Response) error
Acks() <-chan Request
Cancels() <-chan Request
}
type ClientTransaction interface {
Transaction
Responses() <-chan Response
Cancel() error
}

View file

@ -0,0 +1,8 @@
package sip
type Transport interface {
Messages() <-chan Message
Send(msg Message) error
IsReliable(network string) bool
IsStreamed(network string) bool
}