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= .... // 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 // 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 // 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 } // 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) } // ... 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 }