diff options
Diffstat (limited to 'vendor/github.com/pion/sdp/v3/unmarshal.go')
-rw-r--r-- | vendor/github.com/pion/sdp/v3/unmarshal.go | 907 |
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 +} |