status-go/vendor/github.com/pion/sdp/v3/unmarshal.go

957 lines
23 KiB
Go

// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package sdp
import (
"errors"
"fmt"
"net/url"
"strconv"
"strings"
"sync"
)
var (
errSDPInvalidSyntax = errors.New("sdp: invalid syntax")
errSDPInvalidNumericValue = errors.New("sdp: invalid numeric value")
errSDPInvalidValue = errors.New("sdp: invalid value")
errSDPInvalidPortValue = errors.New("sdp: invalid port value")
errSDPCacheInvalid = errors.New("sdp: invalid cache")
//nolint: gochecknoglobals
unmarshalCachePool = sync.Pool{
New: func() interface{} {
return &unmarshalCache{}
},
}
)
// UnmarshalString is the primary function that deserializes the session description
// message and stores it inside of a structured SessionDescription object.
//
// The States Transition Table describes the computation flow between functions
// (namely s1, s2, s3, ...) for a parsing procedure that complies with the
// specifications laid out by the rfc4566#section-5 as well as by JavaScript
// Session Establishment Protocol draft. Links:
//
// https://tools.ietf.org/html/rfc4566#section-5
// https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-24
//
// https://tools.ietf.org/html/rfc4566#section-5
// Session description
//
// v= (protocol version)
// o= (originator and session identifier)
// s= (session name)
// i=* (session information)
// u=* (URI of description)
// e=* (email address)
// p=* (phone number)
// c=* (connection information -- not required if included in
// all media)
// b=* (zero or more bandwidth information lines)
// One or more time descriptions ("t=" and "r=" lines; see below)
// z=* (time zone adjustments)
// k=* (encryption key)
// a=* (zero or more session attribute lines)
// Zero or more media descriptions
//
// Time description
//
// t= (time the session is active)
// r=* (zero or more repeat times)
//
// Media description, if present
//
// m= (media name and transport address)
// i=* (media title)
// c=* (connection information -- optional if included at
// session level)
// b=* (zero or more bandwidth information lines)
// k=* (encryption key)
// a=* (zero or more media attribute lines)
//
// In order to generate the following state table and draw subsequent
// deterministic finite-state automota ("DFA") the following regex was used to
// derive the DFA:
//
// vosi?u?e?p?c?b*(tr*)+z?k?a*(mi?c?b*k?a*)*
//
// possible place and state to exit:
//
// ** * * * ** * * * *
// 99 1 1 1 11 1 1 1 1
// 3 1 1 26 5 5 4 4
//
// Please pay close attention to the `k`, and `a` parsing states. In the table
// below in order to distinguish between the states belonging to the media
// description as opposed to the session description, the states are marked
// with an asterisk ("a*", "k*").
// +--------+----+-------+----+-----+----+-----+---+----+----+---+---+-----+---+---+----+---+----+
// | STATES | a* | a*,k* | a | a,k | b | b,c | e | i | m | o | p | r,t | s | t | u | v | z |
// +--------+----+-------+----+-----+----+-----+---+----+----+---+---+-----+---+---+----+---+----+
// | s1 | | | | | | | | | | | | | | | | 2 | |
// | s2 | | | | | | | | | | 3 | | | | | | | |
// | s3 | | | | | | | | | | | | | 4 | | | | |
// | s4 | | | | | | 5 | 6 | 7 | | | 8 | | | 9 | 10 | | |
// | s5 | | | | | 5 | | | | | | | | | 9 | | | |
// | s6 | | | | | | 5 | | | | | 8 | | | 9 | | | |
// | s7 | | | | | | 5 | 6 | | | | 8 | | | 9 | 10 | | |
// | s8 | | | | | | 5 | | | | | | | | 9 | | | |
// | s9 | | | | 11 | | | | | 12 | | | 9 | | | | | 13 |
// | s10 | | | | | | 5 | 6 | | | | 8 | | | 9 | | | |
// | s11 | | | 11 | | | | | | 12 | | | | | | | | |
// | s12 | | 14 | | | | 15 | | 16 | 12 | | | | | | | | |
// | s13 | | | | 11 | | | | | 12 | | | | | | | | |
// | s14 | 14 | | | | | | | | 12 | | | | | | | | |
// | s15 | | 14 | | | 15 | | | | 12 | | | | | | | | |
// | s16 | | 14 | | | | 15 | | | 12 | | | | | | | | |
// +--------+----+-------+----+-----+----+-----+---+----+----+---+---+-----+---+---+----+---+----+
func (s *SessionDescription) UnmarshalString(value string) error {
var ok bool
l := new(lexer)
if l.cache, ok = unmarshalCachePool.Get().(*unmarshalCache); !ok {
return errSDPCacheInvalid
}
defer unmarshalCachePool.Put(l.cache)
l.cache.reset()
l.desc = s
l.value = value
for state := s1; state != nil; {
var err error
state, err = state(l)
if err != nil {
return err
}
}
s.Attributes = l.cache.cloneSessionAttributes()
populateMediaAttributes(l.cache, l.desc)
return nil
}
// Unmarshal converts the value into a []byte and then calls UnmarshalString.
// Callers should use the more performant UnmarshalString
func (s *SessionDescription) Unmarshal(value []byte) error {
return s.UnmarshalString(string(value))
}
func s1(l *lexer) (stateFn, error) {
return l.handleType(func(key byte) stateFn {
if key == 'v' {
return unmarshalProtocolVersion
}
return nil
})
}
func s2(l *lexer) (stateFn, error) {
return l.handleType(func(key byte) stateFn {
if key == 'o' {
return unmarshalOrigin
}
return nil
})
}
func s3(l *lexer) (stateFn, error) {
return l.handleType(func(key byte) stateFn {
if key == 's' {
return unmarshalSessionName
}
return nil
})
}
func s4(l *lexer) (stateFn, error) {
return l.handleType(func(key byte) stateFn {
switch key {
case 'i':
return unmarshalSessionInformation
case 'u':
return unmarshalURI
case 'e':
return unmarshalEmail
case 'p':
return unmarshalPhone
case 'c':
return unmarshalSessionConnectionInformation
case 'b':
return unmarshalSessionBandwidth
case 't':
return unmarshalTiming
}
return nil
})
}
func s5(l *lexer) (stateFn, error) {
return l.handleType(func(key byte) stateFn {
switch key {
case 'b':
return unmarshalSessionBandwidth
case 't':
return unmarshalTiming
}
return nil
})
}
func s6(l *lexer) (stateFn, error) {
return l.handleType(func(key byte) stateFn {
switch key {
case 'p':
return unmarshalPhone
case 'c':
return unmarshalSessionConnectionInformation
case 'b':
return unmarshalSessionBandwidth
case 't':
return unmarshalTiming
}
return nil
})
}
func s7(l *lexer) (stateFn, error) {
return l.handleType(func(key byte) stateFn {
switch key {
case 'u':
return unmarshalURI
case 'e':
return unmarshalEmail
case 'p':
return unmarshalPhone
case 'c':
return unmarshalSessionConnectionInformation
case 'b':
return unmarshalSessionBandwidth
case 't':
return unmarshalTiming
}
return nil
})
}
func s8(l *lexer) (stateFn, error) {
return l.handleType(func(key byte) stateFn {
switch key {
case 'c':
return unmarshalSessionConnectionInformation
case 'b':
return unmarshalSessionBandwidth
case 't':
return unmarshalTiming
}
return nil
})
}
func s9(l *lexer) (stateFn, error) {
return l.handleType(func(key byte) stateFn {
switch key {
case 'z':
return unmarshalTimeZones
case 'k':
return unmarshalSessionEncryptionKey
case 'a':
return unmarshalSessionAttribute
case 'r':
return unmarshalRepeatTimes
case 't':
return unmarshalTiming
case 'm':
return unmarshalMediaDescription
}
return nil
})
}
func s10(l *lexer) (stateFn, error) {
return l.handleType(func(key byte) stateFn {
switch key {
case 'e':
return unmarshalEmail
case 'p':
return unmarshalPhone
case 'c':
return unmarshalSessionConnectionInformation
case 'b':
return unmarshalSessionBandwidth
case 't':
return unmarshalTiming
}
return nil
})
}
func s11(l *lexer) (stateFn, error) {
return l.handleType(func(key byte) stateFn {
switch key {
case 'a':
return unmarshalSessionAttribute
case 'm':
return unmarshalMediaDescription
}
return nil
})
}
func s12(l *lexer) (stateFn, error) {
return l.handleType(func(key byte) stateFn {
switch key {
case 'a':
return unmarshalMediaAttribute
case 'k':
return unmarshalMediaEncryptionKey
case 'b':
return unmarshalMediaBandwidth
case 'c':
return unmarshalMediaConnectionInformation
case 'i':
return unmarshalMediaTitle
case 'm':
return unmarshalMediaDescription
}
return nil
})
}
func s13(l *lexer) (stateFn, error) {
return l.handleType(func(key byte) stateFn {
switch key {
case 'a':
return unmarshalSessionAttribute
case 'k':
return unmarshalSessionEncryptionKey
case 'm':
return unmarshalMediaDescription
}
return nil
})
}
func s14(l *lexer) (stateFn, error) {
return l.handleType(func(key byte) stateFn {
switch key {
case 'a':
return unmarshalMediaAttribute
case 'k':
// Non-spec ordering
return unmarshalMediaEncryptionKey
case 'b':
// Non-spec ordering
return unmarshalMediaBandwidth
case 'c':
// Non-spec ordering
return unmarshalMediaConnectionInformation
case 'i':
// Non-spec ordering
return unmarshalMediaTitle
case 'm':
return unmarshalMediaDescription
}
return nil
})
}
func s15(l *lexer) (stateFn, error) {
return l.handleType(func(key byte) stateFn {
switch key {
case 'a':
return unmarshalMediaAttribute
case 'k':
return unmarshalMediaEncryptionKey
case 'b':
return unmarshalMediaBandwidth
case 'c':
return unmarshalMediaConnectionInformation
case 'i':
// Non-spec ordering
return unmarshalMediaTitle
case 'm':
return unmarshalMediaDescription
}
return nil
})
}
func s16(l *lexer) (stateFn, error) {
return l.handleType(func(key byte) stateFn {
switch key {
case 'a':
return unmarshalMediaAttribute
case 'k':
return unmarshalMediaEncryptionKey
case 'c':
return unmarshalMediaConnectionInformation
case 'b':
return unmarshalMediaBandwidth
case 'i':
// Non-spec ordering
return unmarshalMediaTitle
case 'm':
return unmarshalMediaDescription
}
return nil
})
}
func unmarshalProtocolVersion(l *lexer) (stateFn, error) {
version, err := l.readUint64Field()
if err != nil {
return nil, err
}
// As off the latest draft of the rfc this value is required to be 0.
// https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-24#section-5.8.1
if version != 0 {
return nil, fmt.Errorf("%w `%v`", errSDPInvalidValue, version)
}
if err := l.nextLine(); err != nil {
return nil, err
}
return s2, nil
}
func unmarshalOrigin(l *lexer) (stateFn, error) {
var err error
l.desc.Origin.Username, err = l.readField()
if err != nil {
return nil, err
}
l.desc.Origin.SessionID, err = l.readUint64Field()
if err != nil {
return nil, err
}
l.desc.Origin.SessionVersion, err = l.readUint64Field()
if err != nil {
return nil, err
}
l.desc.Origin.NetworkType, err = l.readField()
if err != nil {
return nil, err
}
// Set according to currently registered with IANA
// https://tools.ietf.org/html/rfc4566#section-8.2.6
if !anyOf(l.desc.Origin.NetworkType, "IN") {
return nil, fmt.Errorf("%w `%v`", errSDPInvalidValue, l.desc.Origin.NetworkType)
}
l.desc.Origin.AddressType, err = l.readField()
if err != nil {
return nil, err
}
// Set according to currently registered with IANA
// https://tools.ietf.org/html/rfc4566#section-8.2.7
if !anyOf(l.desc.Origin.AddressType, "IP4", "IP6") {
return nil, fmt.Errorf("%w `%v`", errSDPInvalidValue, l.desc.Origin.AddressType)
}
l.desc.Origin.UnicastAddress, err = l.readField()
if err != nil {
return nil, err
}
if err := l.nextLine(); err != nil {
return nil, err
}
return s3, nil
}
func unmarshalSessionName(l *lexer) (stateFn, error) {
value, err := l.readLine()
if err != nil {
return nil, err
}
l.desc.SessionName = SessionName(value)
return s4, nil
}
func unmarshalSessionInformation(l *lexer) (stateFn, error) {
value, err := l.readLine()
if err != nil {
return nil, err
}
sessionInformation := Information(value)
l.desc.SessionInformation = &sessionInformation
return s7, nil
}
func unmarshalURI(l *lexer) (stateFn, error) {
value, err := l.readLine()
if err != nil {
return nil, err
}
l.desc.URI, err = url.Parse(value)
if err != nil {
return nil, err
}
return s10, nil
}
func unmarshalEmail(l *lexer) (stateFn, error) {
value, err := l.readLine()
if err != nil {
return nil, err
}
emailAddress := EmailAddress(value)
l.desc.EmailAddress = &emailAddress
return s6, nil
}
func unmarshalPhone(l *lexer) (stateFn, error) {
value, err := l.readLine()
if err != nil {
return nil, err
}
phoneNumber := PhoneNumber(value)
l.desc.PhoneNumber = &phoneNumber
return s8, nil
}
func unmarshalSessionConnectionInformation(l *lexer) (stateFn, error) {
var err error
l.desc.ConnectionInformation, err = l.unmarshalConnectionInformation()
if err != nil {
return nil, err
}
return s5, nil
}
func (l *lexer) unmarshalConnectionInformation() (*ConnectionInformation, error) {
var err error
var c ConnectionInformation
c.NetworkType, err = l.readField()
if err != nil {
return nil, err
}
// Set according to currently registered with IANA
// https://tools.ietf.org/html/rfc4566#section-8.2.6
if !anyOf(c.NetworkType, "IN") {
return nil, fmt.Errorf("%w `%v`", errSDPInvalidValue, c.NetworkType)
}
c.AddressType, err = l.readField()
if err != nil {
return nil, err
}
// Set according to currently registered with IANA
// https://tools.ietf.org/html/rfc4566#section-8.2.7
if !anyOf(c.AddressType, "IP4", "IP6") {
return nil, fmt.Errorf("%w `%v`", errSDPInvalidValue, c.AddressType)
}
address, err := l.readField()
if err != nil {
return nil, err
}
if address != "" {
c.Address = new(Address)
c.Address.Address = address
}
if err := l.nextLine(); err != nil {
return nil, err
}
return &c, nil
}
func unmarshalSessionBandwidth(l *lexer) (stateFn, error) {
value, err := l.readLine()
if err != nil {
return nil, err
}
bandwidth, err := unmarshalBandwidth(value)
if err != nil {
return nil, fmt.Errorf("%w `b=%v`", errSDPInvalidValue, value)
}
l.desc.Bandwidth = append(l.desc.Bandwidth, *bandwidth)
return s5, nil
}
func unmarshalBandwidth(value string) (*Bandwidth, error) {
parts := strings.Split(value, ":")
if len(parts) != 2 {
return nil, fmt.Errorf("%w `b=%v`", errSDPInvalidValue, parts)
}
experimental := strings.HasPrefix(parts[0], "X-")
if experimental {
parts[0] = strings.TrimPrefix(parts[0], "X-")
} else if !anyOf(parts[0], "CT", "AS", "TIAS", "RS", "RR") {
// Set according to currently registered with IANA
// https://tools.ietf.org/html/rfc4566#section-5.8
// https://tools.ietf.org/html/rfc3890#section-6.2
// https://tools.ietf.org/html/rfc3556#section-2
return nil, fmt.Errorf("%w `%v`", errSDPInvalidValue, parts[0])
}
bandwidth, err := strconv.ParseUint(parts[1], 10, 64)
if err != nil {
return nil, fmt.Errorf("%w `%v`", errSDPInvalidNumericValue, parts[1])
}
return &Bandwidth{
Experimental: experimental,
Type: parts[0],
Bandwidth: bandwidth,
}, nil
}
func unmarshalTiming(l *lexer) (stateFn, error) {
var err error
var td TimeDescription
td.Timing.StartTime, err = l.readUint64Field()
if err != nil {
return nil, err
}
td.Timing.StopTime, err = l.readUint64Field()
if err != nil {
return nil, err
}
if err := l.nextLine(); err != nil {
return nil, err
}
l.desc.TimeDescriptions = append(l.desc.TimeDescriptions, td)
return s9, nil
}
func unmarshalRepeatTimes(l *lexer) (stateFn, error) {
var err error
var newRepeatTime RepeatTime
latestTimeDesc := &l.desc.TimeDescriptions[len(l.desc.TimeDescriptions)-1]
field, err := l.readField()
if err != nil {
return nil, err
}
newRepeatTime.Interval, err = parseTimeUnits(field)
if err != nil {
return nil, fmt.Errorf("%w `%v`", errSDPInvalidValue, field)
}
field, err = l.readField()
if err != nil {
return nil, err
}
newRepeatTime.Duration, err = parseTimeUnits(field)
if err != nil {
return nil, fmt.Errorf("%w `%v`", errSDPInvalidValue, field)
}
for {
field, err := l.readField()
if err != nil {
return nil, err
}
if field == "" {
break
}
offset, err := parseTimeUnits(field)
if err != nil {
return nil, fmt.Errorf("%w `%v`", errSDPInvalidValue, field)
}
newRepeatTime.Offsets = append(newRepeatTime.Offsets, offset)
}
if err := l.nextLine(); err != nil {
return nil, err
}
latestTimeDesc.RepeatTimes = append(latestTimeDesc.RepeatTimes, newRepeatTime)
return s9, nil
}
func unmarshalTimeZones(l *lexer) (stateFn, error) {
// These fields are transimitted in pairs
// z=<adjustment time> <offset> <adjustment time> <offset> ....
// so we are making sure that there are actually multiple of 2 total.
for {
var err error
var timeZone TimeZone
timeZone.AdjustmentTime, err = l.readUint64Field()
if err != nil {
return nil, err
}
offset, err := l.readField()
if err != nil {
return nil, err
}
if offset == "" {
break
}
timeZone.Offset, err = parseTimeUnits(offset)
if err != nil {
return nil, err
}
l.desc.TimeZones = append(l.desc.TimeZones, timeZone)
}
if err := l.nextLine(); err != nil {
return nil, err
}
return s13, nil
}
func unmarshalSessionEncryptionKey(l *lexer) (stateFn, error) {
value, err := l.readLine()
if err != nil {
return nil, err
}
encryptionKey := EncryptionKey(value)
l.desc.EncryptionKey = &encryptionKey
return s11, nil
}
func unmarshalSessionAttribute(l *lexer) (stateFn, error) {
value, err := l.readLine()
if err != nil {
return nil, err
}
i := strings.IndexRune(value, ':')
a := l.cache.getSessionAttribute()
if i > 0 {
a.Key = value[:i]
a.Value = value[i+1:]
} else {
a.Key = value
}
return s11, nil
}
func unmarshalMediaDescription(l *lexer) (stateFn, error) {
populateMediaAttributes(l.cache, l.desc)
var newMediaDesc MediaDescription
// <media>
field, err := l.readField()
if err != nil {
return nil, err
}
// Set according to currently registered with IANA
// https://tools.ietf.org/html/rfc4566#section-5.14
if !anyOf(field, "audio", "video", "text", "application", "message") {
return nil, fmt.Errorf("%w `%v`", errSDPInvalidValue, field)
}
newMediaDesc.MediaName.Media = field
// <port>
field, err = l.readField()
if err != nil {
return nil, err
}
parts := strings.Split(field, "/")
newMediaDesc.MediaName.Port.Value, err = parsePort(parts[0])
if err != nil {
return nil, fmt.Errorf("%w `%v`", errSDPInvalidPortValue, parts[0])
}
if len(parts) > 1 {
var portRange int
portRange, err = strconv.Atoi(parts[1])
if err != nil {
return nil, fmt.Errorf("%w `%v`", errSDPInvalidValue, parts)
}
newMediaDesc.MediaName.Port.Range = &portRange
}
// <proto>
field, err = l.readField()
if err != nil {
return nil, err
}
// Set according to currently registered with IANA
// https://tools.ietf.org/html/rfc4566#section-5.14
// https://tools.ietf.org/html/rfc4975#section-8.1
for _, proto := range strings.Split(field, "/") {
if !anyOf(proto, "UDP", "RTP", "AVP", "SAVP", "SAVPF", "TLS", "DTLS", "SCTP", "AVPF", "TCP", "MSRP", "BFCP", "UDT", "IX") {
return nil, fmt.Errorf("%w `%v`", errSDPInvalidNumericValue, field)
}
newMediaDesc.MediaName.Protos = append(newMediaDesc.MediaName.Protos, proto)
}
// <fmt>...
for {
field, err = l.readField()
if err != nil {
return nil, err
}
if field == "" {
break
}
newMediaDesc.MediaName.Formats = append(newMediaDesc.MediaName.Formats, field)
}
if err := l.nextLine(); err != nil {
return nil, err
}
l.desc.MediaDescriptions = append(l.desc.MediaDescriptions, &newMediaDesc)
return s12, nil
}
func unmarshalMediaTitle(l *lexer) (stateFn, error) {
value, err := l.readLine()
if err != nil {
return nil, err
}
latestMediaDesc := l.desc.MediaDescriptions[len(l.desc.MediaDescriptions)-1]
mediaTitle := Information(value)
latestMediaDesc.MediaTitle = &mediaTitle
return s16, nil
}
func unmarshalMediaConnectionInformation(l *lexer) (stateFn, error) {
var err error
latestMediaDesc := l.desc.MediaDescriptions[len(l.desc.MediaDescriptions)-1]
latestMediaDesc.ConnectionInformation, err = l.unmarshalConnectionInformation()
if err != nil {
return nil, err
}
return s15, nil
}
func unmarshalMediaBandwidth(l *lexer) (stateFn, error) {
value, err := l.readLine()
if err != nil {
return nil, err
}
latestMediaDesc := l.desc.MediaDescriptions[len(l.desc.MediaDescriptions)-1]
bandwidth, err := unmarshalBandwidth(value)
if err != nil {
return nil, fmt.Errorf("%w `b=%v`", errSDPInvalidSyntax, value)
}
latestMediaDesc.Bandwidth = append(latestMediaDesc.Bandwidth, *bandwidth)
return s15, nil
}
func unmarshalMediaEncryptionKey(l *lexer) (stateFn, error) {
value, err := l.readLine()
if err != nil {
return nil, err
}
latestMediaDesc := l.desc.MediaDescriptions[len(l.desc.MediaDescriptions)-1]
encryptionKey := EncryptionKey(value)
latestMediaDesc.EncryptionKey = &encryptionKey
return s14, nil
}
func unmarshalMediaAttribute(l *lexer) (stateFn, error) {
value, err := l.readLine()
if err != nil {
return nil, err
}
i := strings.IndexRune(value, ':')
a := l.cache.getMediaAttribute()
if i > 0 {
a.Key = value[:i]
a.Value = value[i+1:]
} else {
a.Key = value
}
return s14, nil
}
func parseTimeUnits(value string) (num int64, err error) {
if len(value) == 0 {
return 0, fmt.Errorf("%w `%v`", errSDPInvalidValue, value)
}
k := timeShorthand(value[len(value)-1])
if k > 0 {
num, err = strconv.ParseInt(value[:len(value)-1], 10, 64)
} else {
k = 1
num, err = strconv.ParseInt(value, 10, 64)
}
if err != nil {
return 0, fmt.Errorf("%w `%v`", errSDPInvalidValue, value)
}
return num * k, nil
}
func timeShorthand(b byte) int64 {
// Some time offsets in the protocol can be provided with a shorthand
// notation. This code ensures to convert it to NTP timestamp format.
switch b {
case 'd': // days
return 86400
case 'h': // hours
return 3600
case 'm': // minutes
return 60
case 's': // seconds (allowed for completeness)
return 1
default:
return 0
}
}
func parsePort(value string) (int, error) {
port, err := strconv.Atoi(value)
if err != nil {
return 0, fmt.Errorf("%w `%v`", errSDPInvalidPortValue, port)
}
if port < 0 || port > 65536 {
return 0, fmt.Errorf("%w -- out of range `%v`", errSDPInvalidPortValue, port)
}
return port, nil
}
func populateMediaAttributes(c *unmarshalCache, s *SessionDescription) {
if len(s.MediaDescriptions) != 0 {
lastMediaDesc := s.MediaDescriptions[len(s.MediaDescriptions)-1]
lastMediaDesc.Attributes = c.cloneMediaAttributes()
}
}