summaryrefslogtreecommitdiff
path: root/vendor/github.com/pion/sdp/v3/unmarshal.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/pion/sdp/v3/unmarshal.go')
-rw-r--r--vendor/github.com/pion/sdp/v3/unmarshal.go907
1 files changed, 907 insertions, 0 deletions
diff --git a/vendor/github.com/pion/sdp/v3/unmarshal.go b/vendor/github.com/pion/sdp/v3/unmarshal.go
new file mode 100644
index 0000000..92e0dcf
--- /dev/null
+++ b/vendor/github.com/pion/sdp/v3/unmarshal.go
@@ -0,0 +1,907 @@
+package sdp
+
+import (
+ "errors"
+ "fmt"
+ "net/url"
+ "strconv"
+ "strings"
+)
+
+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")
+)
+
+// Unmarshal 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) Unmarshal(value []byte) error {
+ l := new(lexer)
+ l.desc = s
+ l.value = value
+ for state := s1; state != nil; {
+ var err error
+ state, err = state(l)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func s1(l *lexer) (stateFn, error) {
+ return l.handleType(func(key string) stateFn {
+ if key == "v=" {
+ return unmarshalProtocolVersion
+ }
+ return nil
+ })
+}
+
+func s2(l *lexer) (stateFn, error) {
+ return l.handleType(func(key string) stateFn {
+ if key == "o=" {
+ return unmarshalOrigin
+ }
+ return nil
+ })
+}
+
+func s3(l *lexer) (stateFn, error) {
+ return l.handleType(func(key string) stateFn {
+ if key == "s=" {
+ return unmarshalSessionName
+ }
+ return nil
+ })
+}
+
+func s4(l *lexer) (stateFn, error) {
+ return l.handleType(func(key string) 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 string) stateFn {
+ switch key {
+ case "b=":
+ return unmarshalSessionBandwidth
+ case "t=":
+ return unmarshalTiming
+ }
+ return nil
+ })
+}
+
+func s6(l *lexer) (stateFn, error) {
+ return l.handleType(func(key string) 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 string) 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 string) 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 string) 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 string) 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 string) stateFn {
+ switch key {
+ case "a=":
+ return unmarshalSessionAttribute
+ case "m=":
+ return unmarshalMediaDescription
+ }
+ return nil
+ })
+}
+
+func s12(l *lexer) (stateFn, error) {
+ return l.handleType(func(key string) 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 string) 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 string) 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 string) 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 string) 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") {
+ // Set according to currently registered with IANA
+ // https://tools.ietf.org/html/rfc4566#section-5.8
+ 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, ':')
+ var a Attribute
+ if i > 0 {
+ a = NewAttribute(value[:i], value[i+1:])
+ } else {
+ a = NewPropertyAttribute(value)
+ }
+
+ l.desc.Attributes = append(l.desc.Attributes, a)
+ return s11, nil
+}
+
+func unmarshalMediaDescription(l *lexer) (stateFn, error) {
+ 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
+ for _, proto := range strings.Split(field, "/") {
+ if !anyOf(proto, "UDP", "RTP", "AVP", "SAVP", "SAVPF", "TLS", "DTLS", "SCTP", "AVPF") {
+ 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, ':')
+ var a Attribute
+ if i > 0 {
+ a = NewAttribute(value[:i], value[i+1:])
+ } else {
+ a = NewPropertyAttribute(value)
+ }
+
+ latestMediaDesc := l.desc.MediaDescriptions[len(l.desc.MediaDescriptions)-1]
+ latestMediaDesc.Attributes = append(latestMediaDesc.Attributes, a)
+ return s14, nil
+}
+
+func parseTimeUnits(value string) (num int64, err error) {
+ 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
+}