mirror of
https://github.com/pion/webrtc.git
synced 2025-10-29 09:52:51 +08:00
1
go.mod
1
go.mod
@@ -5,6 +5,7 @@ require (
|
|||||||
github.com/pions/dtls v1.0.2
|
github.com/pions/dtls v1.0.2
|
||||||
github.com/pions/pkg v0.0.0-20181115215726-b60cd756f712
|
github.com/pions/pkg v0.0.0-20181115215726-b60cd756f712
|
||||||
github.com/pions/sctp v1.0.0
|
github.com/pions/sctp v1.0.0
|
||||||
|
github.com/pions/sdp v1.0.0
|
||||||
github.com/pions/transport v0.0.0-20181219213214-cd29ef7d0726
|
github.com/pions/transport v0.0.0-20181219213214-cd29ef7d0726
|
||||||
github.com/pkg/errors v0.8.0
|
github.com/pkg/errors v0.8.0
|
||||||
github.com/stretchr/testify v1.2.2
|
github.com/stretchr/testify v1.2.2
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -9,6 +9,8 @@ github.com/pions/pkg v0.0.0-20181115215726-b60cd756f712 h1:ciXO7F7PusyAzW/EZJt01
|
|||||||
github.com/pions/pkg v0.0.0-20181115215726-b60cd756f712/go.mod h1:r9wKZs+Xxv2acLspex4CHQiIhFjGK1zGP+nUm/8klXA=
|
github.com/pions/pkg v0.0.0-20181115215726-b60cd756f712/go.mod h1:r9wKZs+Xxv2acLspex4CHQiIhFjGK1zGP+nUm/8klXA=
|
||||||
github.com/pions/sctp v1.0.0 h1:ScxSzY867GGr0peNGYqOdw6bPBymM0V9UcbKrsTfeDA=
|
github.com/pions/sctp v1.0.0 h1:ScxSzY867GGr0peNGYqOdw6bPBymM0V9UcbKrsTfeDA=
|
||||||
github.com/pions/sctp v1.0.0/go.mod h1:lamdubZNMS+X5BjlMmsjWrg78Aa87tjYswD7p+AIZhc=
|
github.com/pions/sctp v1.0.0/go.mod h1:lamdubZNMS+X5BjlMmsjWrg78Aa87tjYswD7p+AIZhc=
|
||||||
|
github.com/pions/sdp v1.0.0 h1:OxCczxFOKzudhJRMkntSX+Q0fZK1MQJugGh6n9hq3nw=
|
||||||
|
github.com/pions/sdp v1.0.0/go.mod h1:moNMmnVSlx8rBBb39U9t0Rdr7xvMlqiJjHlMESRad5k=
|
||||||
github.com/pions/transport v0.0.0-20181219213214-cd29ef7d0726 h1:kQFdB0RkBeK7TavRC09+5OAN73/NCSdz3AMxZS8v6H8=
|
github.com/pions/transport v0.0.0-20181219213214-cd29ef7d0726 h1:kQFdB0RkBeK7TavRC09+5OAN73/NCSdz3AMxZS8v6H8=
|
||||||
github.com/pions/transport v0.0.0-20181219213214-cd29ef7d0726/go.mod h1:HLhzI7I0k8TyiQ99hfRZNRf84lG76eaFnZHnVy/wFnM=
|
github.com/pions/transport v0.0.0-20181219213214-cd29ef7d0726/go.mod h1:HLhzI7I0k8TyiQ99hfRZNRf84lG76eaFnZHnVy/wFnM=
|
||||||
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
||||||
|
|||||||
@@ -1,111 +0,0 @@
|
|||||||
package sdp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Information describes the "i=" field which provides textual information
|
|
||||||
// about the session.
|
|
||||||
type Information string
|
|
||||||
|
|
||||||
func (i *Information) String() *string {
|
|
||||||
output := string(*i)
|
|
||||||
return &output
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConnectionInformation defines the representation for the "c=" field
|
|
||||||
// containing connection data.
|
|
||||||
type ConnectionInformation struct {
|
|
||||||
NetworkType string
|
|
||||||
AddressType string
|
|
||||||
Address *Address
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConnectionInformation) String() *string {
|
|
||||||
output := fmt.Sprintf(
|
|
||||||
"%v %v %v",
|
|
||||||
c.NetworkType,
|
|
||||||
c.AddressType,
|
|
||||||
c.Address.String(),
|
|
||||||
)
|
|
||||||
return &output
|
|
||||||
}
|
|
||||||
|
|
||||||
// Address desribes a structured address token from within the "c=" field.
|
|
||||||
type Address struct {
|
|
||||||
IP net.IP
|
|
||||||
TTL *int
|
|
||||||
Range *int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Address) String() string {
|
|
||||||
var parts []string
|
|
||||||
parts = append(parts, c.IP.String())
|
|
||||||
if c.TTL != nil {
|
|
||||||
parts = append(parts, strconv.Itoa(*c.TTL))
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Range != nil {
|
|
||||||
parts = append(parts, strconv.Itoa(*c.Range))
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.Join(parts, "/")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bandwidth describes an optional field which denotes the proposed bandwidth
|
|
||||||
// to be used by the session or media.
|
|
||||||
type Bandwidth struct {
|
|
||||||
Experimental bool
|
|
||||||
Type string
|
|
||||||
Bandwidth uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bandwidth) String() *string {
|
|
||||||
var output string
|
|
||||||
if b.Experimental {
|
|
||||||
output += "X-"
|
|
||||||
}
|
|
||||||
output += b.Type + ":" + strconv.FormatUint(b.Bandwidth, 10)
|
|
||||||
return &output
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncryptionKey describes the "k=" which conveys encryption key information.
|
|
||||||
type EncryptionKey string
|
|
||||||
|
|
||||||
func (s *EncryptionKey) String() *string {
|
|
||||||
output := string(*s)
|
|
||||||
return &output
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attribute describes the "a=" field which represents the primary means for
|
|
||||||
// extending SDP.
|
|
||||||
type Attribute struct {
|
|
||||||
Key string
|
|
||||||
Value string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPropertyAttribute constructs a new attribute
|
|
||||||
func NewPropertyAttribute(key string) Attribute {
|
|
||||||
return Attribute{
|
|
||||||
Key: key,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewAttribute constructs a new attribute
|
|
||||||
func NewAttribute(key, value string) Attribute {
|
|
||||||
return Attribute{
|
|
||||||
Key: key,
|
|
||||||
Value: value,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Attribute) String() *string {
|
|
||||||
output := a.Key
|
|
||||||
if len(a.Value) > 0 {
|
|
||||||
output += ":" + a.Value
|
|
||||||
}
|
|
||||||
return &output
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
package sdp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/pions/webrtc/pkg/ice"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ICECandidateUnmarshal takes a candidate strings and returns a ice.Candidate or nil if it fails to parse
|
|
||||||
// TODO: return error if parsing fails
|
|
||||||
func ICECandidateUnmarshal(raw string) (*ice.Candidate, error) {
|
|
||||||
split := strings.Fields(raw)
|
|
||||||
if len(split) < 8 {
|
|
||||||
return nil, fmt.Errorf("attribute not long enough to be ICE candidate (%d) %s", len(split), raw)
|
|
||||||
}
|
|
||||||
|
|
||||||
getValue := func(key string) string {
|
|
||||||
rtrnNext := false
|
|
||||||
for _, i := range split {
|
|
||||||
if rtrnNext {
|
|
||||||
return i
|
|
||||||
} else if i == key {
|
|
||||||
rtrnNext = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
port, err := strconv.Atoi(split[5])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
transport := split[2]
|
|
||||||
|
|
||||||
// TODO verify valid address
|
|
||||||
ip := net.ParseIP(split[4])
|
|
||||||
if ip == nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch getValue("typ") {
|
|
||||||
case "host":
|
|
||||||
return ice.NewCandidateHost(transport, ip, port)
|
|
||||||
case "srflx":
|
|
||||||
return ice.NewCandidateServerReflexive(transport, ip, port, "", 0) // TODO: parse related address
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("Unhandled candidate typ %s", getValue("typ"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func iceCandidateString(c *ice.Candidate, component int) string {
|
|
||||||
// TODO: calculate foundation
|
|
||||||
switch c.Type {
|
|
||||||
case ice.CandidateTypeHost:
|
|
||||||
return fmt.Sprintf("foundation %d %s %d %s %d typ host generation 0",
|
|
||||||
component, c.NetworkShort(), c.Priority(c.Type.Preference(), uint16(component)), c.IP, c.Port)
|
|
||||||
|
|
||||||
case ice.CandidateTypeServerReflexive:
|
|
||||||
return fmt.Sprintf("foundation %d %s %d %s %d typ srflx raddr %s rport %d generation 0",
|
|
||||||
component, c.NetworkShort(), c.Priority(c.Type.Preference(), uint16(component)), c.IP, c.Port,
|
|
||||||
c.RelatedAddress.Address, c.RelatedAddress.Port)
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// ICECandidateMarshal takes a candidate and returns a string representation
|
|
||||||
func ICECandidateMarshal(c *ice.Candidate) []string {
|
|
||||||
out := make([]string, 0)
|
|
||||||
|
|
||||||
out = append(out, iceCandidateString(c, 1))
|
|
||||||
out = append(out, iceCandidateString(c, 2))
|
|
||||||
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
@@ -1,159 +0,0 @@
|
|||||||
package sdp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Constants for SDP attributes used in JSEP
|
|
||||||
const (
|
|
||||||
AttrKeyIdentity = "identity"
|
|
||||||
AttrKeyGroup = "group"
|
|
||||||
AttrKeySsrc = "ssrc"
|
|
||||||
AttrKeySsrcGroup = "ssrc-group"
|
|
||||||
AttrKeyMsidSemantic = "msid-semantic"
|
|
||||||
AttrKeyConnectionSetup = "setup"
|
|
||||||
AttrKeyMID = "mid"
|
|
||||||
AttrKeyICELite = "ice-lite"
|
|
||||||
AttrKeyRtcpMux = "rtcp-mux"
|
|
||||||
AttrKeyRtcpRsize = "rtcp-rsize"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Constants for semantic tokens used in JSEP
|
|
||||||
const (
|
|
||||||
SemanticTokenLipSynchronization = "LS"
|
|
||||||
SemanticTokenFlowIdentification = "FID"
|
|
||||||
SemanticTokenForwardErrorCorrection = "FEC"
|
|
||||||
SemanticTokenWebRTCMediaStreams = "WMS"
|
|
||||||
)
|
|
||||||
|
|
||||||
// API to match draft-ietf-rtcweb-jsep
|
|
||||||
// Move to webrtc or its own package?
|
|
||||||
|
|
||||||
// NewJSEPSessionDescription creates a new SessionDescription with
|
|
||||||
// some settings that are required by the JSEP spec.
|
|
||||||
func NewJSEPSessionDescription(identity bool) *SessionDescription {
|
|
||||||
d := &SessionDescription{
|
|
||||||
Version: 0,
|
|
||||||
Origin: Origin{
|
|
||||||
Username: "-",
|
|
||||||
SessionID: newSessionID(),
|
|
||||||
SessionVersion: uint64(time.Now().Unix()),
|
|
||||||
NetworkType: "IN",
|
|
||||||
AddressType: "IP4",
|
|
||||||
UnicastAddress: "0.0.0.0",
|
|
||||||
},
|
|
||||||
SessionName: "-",
|
|
||||||
TimeDescriptions: []TimeDescription{
|
|
||||||
{
|
|
||||||
Timing: Timing{
|
|
||||||
StartTime: 0,
|
|
||||||
StopTime: 0,
|
|
||||||
},
|
|
||||||
RepeatTimes: nil,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Attributes: []Attribute{
|
|
||||||
// "Attribute(ice-options:trickle)", // TODO: implement trickle ICE
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if identity {
|
|
||||||
d.WithPropertyAttribute(AttrKeyIdentity)
|
|
||||||
}
|
|
||||||
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithPropertyAttribute adds a property attribute 'a=key' to the session description
|
|
||||||
func (s *SessionDescription) WithPropertyAttribute(key string) *SessionDescription {
|
|
||||||
s.Attributes = append(s.Attributes, NewPropertyAttribute(key))
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithValueAttribute adds a value attribute 'a=key:value' to the session description
|
|
||||||
func (s *SessionDescription) WithValueAttribute(key, value string) *SessionDescription {
|
|
||||||
s.Attributes = append(s.Attributes, NewAttribute(key, value))
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithFingerprint adds a fingerprint to the session description
|
|
||||||
func (s *SessionDescription) WithFingerprint(algorithm, value string) *SessionDescription {
|
|
||||||
return s.WithValueAttribute("fingerprint", algorithm+" "+value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMedia adds a media description to the session description
|
|
||||||
func (s *SessionDescription) WithMedia(md *MediaDescription) *SessionDescription {
|
|
||||||
s.MediaDescriptions = append(s.MediaDescriptions, md)
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewJSEPMediaDescription creates a new MediaName with
|
|
||||||
// some settings that are required by the JSEP spec.
|
|
||||||
func NewJSEPMediaDescription(codecType string, codecPrefs []string) *MediaDescription {
|
|
||||||
// TODO: handle codecPrefs
|
|
||||||
d := &MediaDescription{
|
|
||||||
MediaName: MediaName{
|
|
||||||
Media: codecType,
|
|
||||||
Port: RangedPort{Value: 9},
|
|
||||||
Protos: []string{"UDP", "TLS", "RTP", "SAVPF"},
|
|
||||||
},
|
|
||||||
ConnectionInformation: &ConnectionInformation{
|
|
||||||
NetworkType: "IN",
|
|
||||||
AddressType: "IP4",
|
|
||||||
Address: &Address{
|
|
||||||
IP: net.ParseIP("0.0.0.0"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithPropertyAttribute adds a property attribute 'a=key' to the media description
|
|
||||||
func (d *MediaDescription) WithPropertyAttribute(key string) *MediaDescription {
|
|
||||||
d.Attributes = append(d.Attributes, NewPropertyAttribute(key))
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithValueAttribute adds a value attribute 'a=key:value' to the media description
|
|
||||||
func (d *MediaDescription) WithValueAttribute(key, value string) *MediaDescription {
|
|
||||||
d.Attributes = append(d.Attributes, NewAttribute(key, value))
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithICECredentials adds ICE credentials to the media description
|
|
||||||
func (d *MediaDescription) WithICECredentials(username, password string) *MediaDescription {
|
|
||||||
return d.
|
|
||||||
WithValueAttribute("ice-ufrag", username).
|
|
||||||
WithValueAttribute("ice-pwd", password)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithCodec adds codec information to the media description
|
|
||||||
func (d *MediaDescription) WithCodec(payloadType uint8, name string, clockrate uint32, channels uint16, fmtp string) *MediaDescription {
|
|
||||||
d.MediaName.Formats = append(d.MediaName.Formats, strconv.Itoa(int(payloadType)))
|
|
||||||
rtpmap := fmt.Sprintf("%d %s/%d", payloadType, name, clockrate)
|
|
||||||
if channels > 0 {
|
|
||||||
rtpmap = rtpmap + fmt.Sprintf("/%d", channels)
|
|
||||||
}
|
|
||||||
d.WithValueAttribute("rtpmap", rtpmap)
|
|
||||||
if fmtp != "" {
|
|
||||||
d.WithValueAttribute("fmtp", fmt.Sprintf("%d %s", payloadType, fmtp))
|
|
||||||
}
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMediaSource adds media source information to the media description
|
|
||||||
func (d *MediaDescription) WithMediaSource(ssrc uint32, cname, streamLabel, label string) *MediaDescription {
|
|
||||||
return d.
|
|
||||||
WithValueAttribute("ssrc", fmt.Sprintf("%d cname:%s", ssrc, cname)). // Deprecated but not phased out?
|
|
||||||
WithValueAttribute("ssrc", fmt.Sprintf("%d msid:%s %s", ssrc, streamLabel, label)).
|
|
||||||
WithValueAttribute("ssrc", fmt.Sprintf("%d mslabel:%s", ssrc, streamLabel)). // Deprecated but not phased out?
|
|
||||||
WithValueAttribute("ssrc", fmt.Sprintf("%d label:%s", ssrc, label)) // Deprecated but not phased out?
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithCandidate adds an ICE candidate to the media description
|
|
||||||
func (d *MediaDescription) WithCandidate(value string) *MediaDescription {
|
|
||||||
return d.WithValueAttribute("candidate", value)
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
package sdp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Marshal takes a SDP struct to text
|
|
||||||
// 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)
|
|
||||||
func (s *SessionDescription) Marshal() (raw string) {
|
|
||||||
raw += keyValueBuild("v=", s.Version.String())
|
|
||||||
raw += keyValueBuild("o=", s.Origin.String())
|
|
||||||
raw += keyValueBuild("s=", s.SessionName.String())
|
|
||||||
|
|
||||||
if s.SessionInformation != nil {
|
|
||||||
raw += keyValueBuild("i=", s.SessionInformation.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.URI != nil {
|
|
||||||
uri := s.URI.String()
|
|
||||||
raw += keyValueBuild("u=", &uri)
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.EmailAddress != nil {
|
|
||||||
raw += keyValueBuild("e=", s.EmailAddress.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.PhoneNumber != nil {
|
|
||||||
raw += keyValueBuild("p=", s.PhoneNumber.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.ConnectionInformation != nil {
|
|
||||||
raw += keyValueBuild("c=", s.ConnectionInformation.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, b := range s.Bandwidth {
|
|
||||||
raw += keyValueBuild("b=", b.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, td := range s.TimeDescriptions {
|
|
||||||
raw += keyValueBuild("t=", td.Timing.String())
|
|
||||||
for _, r := range td.RepeatTimes {
|
|
||||||
raw += keyValueBuild("r=", r.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rawTimeZones := make([]string, 0)
|
|
||||||
for _, z := range s.TimeZones {
|
|
||||||
rawTimeZones = append(rawTimeZones, z.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(rawTimeZones) > 0 {
|
|
||||||
timeZones := strings.Join(rawTimeZones, " ")
|
|
||||||
raw += keyValueBuild("z=", &timeZones)
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.EncryptionKey != nil {
|
|
||||||
raw += keyValueBuild("k=", s.EncryptionKey.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, a := range s.Attributes {
|
|
||||||
raw += keyValueBuild("a=", a.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, md := range s.MediaDescriptions {
|
|
||||||
raw += keyValueBuild("m=", md.MediaName.String())
|
|
||||||
|
|
||||||
if md.MediaTitle != nil {
|
|
||||||
raw += keyValueBuild("i=", md.MediaTitle.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
if md.ConnectionInformation != nil {
|
|
||||||
raw += keyValueBuild("c=", md.ConnectionInformation.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, b := range md.Bandwidth {
|
|
||||||
raw += keyValueBuild("b=", b.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
if md.EncryptionKey != nil {
|
|
||||||
raw += keyValueBuild("k=", md.EncryptionKey.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, a := range md.Attributes {
|
|
||||||
raw += keyValueBuild("a=", a.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return raw
|
|
||||||
}
|
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
package sdp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"net/url"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
CanonicalMarshalSDP = "v=0\r\n" +
|
|
||||||
"o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r\n" +
|
|
||||||
"s=SDP Seminar\r\n" +
|
|
||||||
"i=A Seminar on the session description protocol\r\n" +
|
|
||||||
"u=http://www.example.com/seminars/sdp.pdf\r\n" +
|
|
||||||
"e=j.doe@example.com (Jane Doe)\r\n" +
|
|
||||||
"p=+1 617 555-6011\r\n" +
|
|
||||||
"c=IN IP4 224.2.17.12/127\r\n" +
|
|
||||||
"b=X-YZ:128\r\n" +
|
|
||||||
"b=AS:12345\r\n" +
|
|
||||||
"t=2873397496 2873404696\r\n" +
|
|
||||||
"t=3034423619 3042462419\r\n" +
|
|
||||||
"r=604800 3600 0 90000\r\n" +
|
|
||||||
"z=2882844526 -3600 2898848070 0\r\n" +
|
|
||||||
"k=prompt\r\n" +
|
|
||||||
"a=candidate:0 1 UDP 2113667327 203.0.113.1 54400 typ host\r\n" +
|
|
||||||
"a=recvonly\r\n" +
|
|
||||||
"m=audio 49170 RTP/AVP 0\r\n" +
|
|
||||||
"i=Vivamus a posuere nisl\r\n" +
|
|
||||||
"c=IN IP4 203.0.113.1\r\n" +
|
|
||||||
"b=X-YZ:128\r\n" +
|
|
||||||
"k=prompt\r\n" +
|
|
||||||
"a=sendrecv\r\n" +
|
|
||||||
"m=video 51372 RTP/AVP 99\r\n" +
|
|
||||||
"a=rtpmap:99 h263-1998/90000\r\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMarshalCanonical(t *testing.T) {
|
|
||||||
sd := &SessionDescription{
|
|
||||||
Version: 0,
|
|
||||||
Origin: Origin{
|
|
||||||
Username: "jdoe",
|
|
||||||
SessionID: uint64(2890844526),
|
|
||||||
SessionVersion: uint64(2890842807),
|
|
||||||
NetworkType: "IN",
|
|
||||||
AddressType: "IP4",
|
|
||||||
UnicastAddress: "10.47.16.5",
|
|
||||||
},
|
|
||||||
SessionName: "SDP Seminar",
|
|
||||||
SessionInformation: &(&struct{ x Information }{"A Seminar on the session description protocol"}).x,
|
|
||||||
URI: func() *url.URL {
|
|
||||||
uri, err := url.Parse("http://www.example.com/seminars/sdp.pdf")
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return uri
|
|
||||||
}(),
|
|
||||||
EmailAddress: &(&struct{ x EmailAddress }{"j.doe@example.com (Jane Doe)"}).x,
|
|
||||||
PhoneNumber: &(&struct{ x PhoneNumber }{"+1 617 555-6011"}).x,
|
|
||||||
ConnectionInformation: &ConnectionInformation{
|
|
||||||
NetworkType: "IN",
|
|
||||||
AddressType: "IP4",
|
|
||||||
Address: &Address{
|
|
||||||
IP: net.ParseIP("224.2.17.12"),
|
|
||||||
TTL: &(&struct{ x int }{127}).x,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Bandwidth: []Bandwidth{
|
|
||||||
{
|
|
||||||
Experimental: true,
|
|
||||||
Type: "YZ",
|
|
||||||
Bandwidth: 128,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: "AS",
|
|
||||||
Bandwidth: 12345,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
TimeDescriptions: []TimeDescription{
|
|
||||||
{
|
|
||||||
Timing: Timing{
|
|
||||||
StartTime: 2873397496,
|
|
||||||
StopTime: 2873404696,
|
|
||||||
},
|
|
||||||
RepeatTimes: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Timing: Timing{
|
|
||||||
StartTime: 3034423619,
|
|
||||||
StopTime: 3042462419,
|
|
||||||
},
|
|
||||||
RepeatTimes: []RepeatTime{
|
|
||||||
{
|
|
||||||
Interval: 604800,
|
|
||||||
Duration: 3600,
|
|
||||||
Offsets: []int64{0, 90000},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
TimeZones: []TimeZone{
|
|
||||||
{
|
|
||||||
AdjustmentTime: 2882844526,
|
|
||||||
Offset: -3600,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
AdjustmentTime: 2898848070,
|
|
||||||
Offset: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
EncryptionKey: &(&struct{ x EncryptionKey }{"prompt"}).x,
|
|
||||||
Attributes: []Attribute{
|
|
||||||
NewAttribute("candidate:0 1 UDP 2113667327 203.0.113.1 54400 typ host", ""),
|
|
||||||
NewAttribute("recvonly", ""),
|
|
||||||
},
|
|
||||||
MediaDescriptions: []*MediaDescription{
|
|
||||||
{
|
|
||||||
MediaName: MediaName{
|
|
||||||
Media: "audio",
|
|
||||||
Port: RangedPort{
|
|
||||||
Value: 49170,
|
|
||||||
},
|
|
||||||
Protos: []string{"RTP", "AVP"},
|
|
||||||
Formats: []string{"0"},
|
|
||||||
},
|
|
||||||
MediaTitle: &(&struct{ x Information }{"Vivamus a posuere nisl"}).x,
|
|
||||||
ConnectionInformation: &ConnectionInformation{
|
|
||||||
NetworkType: "IN",
|
|
||||||
AddressType: "IP4",
|
|
||||||
Address: &Address{
|
|
||||||
IP: net.ParseIP("203.0.113.1"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Bandwidth: []Bandwidth{
|
|
||||||
{
|
|
||||||
Experimental: true,
|
|
||||||
Type: "YZ",
|
|
||||||
Bandwidth: 128,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
EncryptionKey: &(&struct{ x EncryptionKey }{"prompt"}).x,
|
|
||||||
Attributes: []Attribute{
|
|
||||||
NewAttribute("sendrecv", ""),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
MediaName: MediaName{
|
|
||||||
Media: "video",
|
|
||||||
Port: RangedPort{
|
|
||||||
Value: 51372,
|
|
||||||
},
|
|
||||||
Protos: []string{"RTP", "AVP"},
|
|
||||||
Formats: []string{"99"},
|
|
||||||
},
|
|
||||||
Attributes: []Attribute{
|
|
||||||
NewAttribute("rtpmap:99 h263-1998/90000", ""),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
actual := sd.Marshal()
|
|
||||||
if actual != CanonicalMarshalSDP {
|
|
||||||
t.Errorf("error:\n\nEXPECTED:\n%v\nACTUAL:\n%v", CanonicalMarshalSDP, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
package sdp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MediaDescription represents a media type.
|
|
||||||
// https://tools.ietf.org/html/rfc4566#section-5.14
|
|
||||||
type MediaDescription struct {
|
|
||||||
// m=<media> <port>/<number of ports> <proto> <fmt> ...
|
|
||||||
// https://tools.ietf.org/html/rfc4566#section-5.14
|
|
||||||
MediaName MediaName
|
|
||||||
|
|
||||||
// i=<session description>
|
|
||||||
// https://tools.ietf.org/html/rfc4566#section-5.4
|
|
||||||
MediaTitle *Information
|
|
||||||
|
|
||||||
// c=<nettype> <addrtype> <connection-address>
|
|
||||||
// https://tools.ietf.org/html/rfc4566#section-5.7
|
|
||||||
ConnectionInformation *ConnectionInformation
|
|
||||||
|
|
||||||
// b=<bwtype>:<bandwidth>
|
|
||||||
// https://tools.ietf.org/html/rfc4566#section-5.8
|
|
||||||
Bandwidth []Bandwidth
|
|
||||||
|
|
||||||
// k=<method>
|
|
||||||
// k=<method>:<encryption key>
|
|
||||||
// https://tools.ietf.org/html/rfc4566#section-5.12
|
|
||||||
EncryptionKey *EncryptionKey
|
|
||||||
|
|
||||||
// a=<attribute>
|
|
||||||
// a=<attribute>:<value>
|
|
||||||
// https://tools.ietf.org/html/rfc4566#section-5.13
|
|
||||||
Attributes []Attribute
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attribute returns the value of an attribute and if it exists
|
|
||||||
func (s *MediaDescription) Attribute(key string) (string, bool) {
|
|
||||||
for _, a := range s.Attributes {
|
|
||||||
if a.Key == key {
|
|
||||||
return a.Value, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
// RangedPort supports special format for the media field "m=" port value. If
|
|
||||||
// it may be necessary to specify multiple transport ports, the protocol allows
|
|
||||||
// to write it as: <port>/<number of ports> where number of ports is a an
|
|
||||||
// offsetting range.
|
|
||||||
type RangedPort struct {
|
|
||||||
Value int
|
|
||||||
Range *int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *RangedPort) String() string {
|
|
||||||
output := strconv.Itoa(p.Value)
|
|
||||||
if p.Range != nil {
|
|
||||||
output += "/" + strconv.Itoa(*p.Range)
|
|
||||||
}
|
|
||||||
return output
|
|
||||||
}
|
|
||||||
|
|
||||||
// MediaName describes the "m=" field storage structure.
|
|
||||||
type MediaName struct {
|
|
||||||
Media string
|
|
||||||
Port RangedPort
|
|
||||||
Protos []string
|
|
||||||
Formats []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MediaName) String() *string {
|
|
||||||
output := strings.Join([]string{
|
|
||||||
m.Media,
|
|
||||||
m.Port.String(),
|
|
||||||
strings.Join(m.Protos, "/"),
|
|
||||||
strings.Join(m.Formats, " "),
|
|
||||||
}, " ")
|
|
||||||
return &output
|
|
||||||
}
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
package sdp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SessionDescription is a a well-defined format for conveying sufficient
|
|
||||||
// information to discover and participate in a multimedia session.
|
|
||||||
type SessionDescription struct {
|
|
||||||
// v=0
|
|
||||||
// https://tools.ietf.org/html/rfc4566#section-5.1
|
|
||||||
Version Version
|
|
||||||
|
|
||||||
// o=<username> <sess-id> <sess-version> <nettype> <addrtype> <unicast-address>
|
|
||||||
// https://tools.ietf.org/html/rfc4566#section-5.2
|
|
||||||
Origin Origin
|
|
||||||
|
|
||||||
// s=<session name>
|
|
||||||
// https://tools.ietf.org/html/rfc4566#section-5.3
|
|
||||||
SessionName SessionName
|
|
||||||
|
|
||||||
// i=<session description>
|
|
||||||
// https://tools.ietf.org/html/rfc4566#section-5.4
|
|
||||||
SessionInformation *Information
|
|
||||||
|
|
||||||
// u=<uri>
|
|
||||||
// https://tools.ietf.org/html/rfc4566#section-5.5
|
|
||||||
URI *url.URL
|
|
||||||
|
|
||||||
// e=<email-address>
|
|
||||||
// https://tools.ietf.org/html/rfc4566#section-5.6
|
|
||||||
EmailAddress *EmailAddress
|
|
||||||
|
|
||||||
// p=<phone-number>
|
|
||||||
// https://tools.ietf.org/html/rfc4566#section-5.6
|
|
||||||
PhoneNumber *PhoneNumber
|
|
||||||
|
|
||||||
// c=<nettype> <addrtype> <connection-address>
|
|
||||||
// https://tools.ietf.org/html/rfc4566#section-5.7
|
|
||||||
ConnectionInformation *ConnectionInformation
|
|
||||||
|
|
||||||
// b=<bwtype>:<bandwidth>
|
|
||||||
// https://tools.ietf.org/html/rfc4566#section-5.8
|
|
||||||
Bandwidth []Bandwidth
|
|
||||||
|
|
||||||
// https://tools.ietf.org/html/rfc4566#section-5.9
|
|
||||||
// https://tools.ietf.org/html/rfc4566#section-5.10
|
|
||||||
TimeDescriptions []TimeDescription
|
|
||||||
|
|
||||||
// z=<adjustment time> <offset> <adjustment time> <offset> ...
|
|
||||||
// https://tools.ietf.org/html/rfc4566#section-5.11
|
|
||||||
TimeZones []TimeZone
|
|
||||||
|
|
||||||
// k=<method>
|
|
||||||
// k=<method>:<encryption key>
|
|
||||||
// https://tools.ietf.org/html/rfc4566#section-5.12
|
|
||||||
EncryptionKey *EncryptionKey
|
|
||||||
|
|
||||||
// a=<attribute>
|
|
||||||
// a=<attribute>:<value>
|
|
||||||
// https://tools.ietf.org/html/rfc4566#section-5.13
|
|
||||||
Attributes []Attribute
|
|
||||||
|
|
||||||
// https://tools.ietf.org/html/rfc4566#section-5.14
|
|
||||||
MediaDescriptions []*MediaDescription
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attribute returns the value of an attribute and if it exists
|
|
||||||
func (s *SessionDescription) Attribute(key string) (string, bool) {
|
|
||||||
for _, a := range s.Attributes {
|
|
||||||
if a.Key == key {
|
|
||||||
return a.Value, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Version describes the value provided by the "v=" field which gives
|
|
||||||
// the version of the Session Description Protocol.
|
|
||||||
type Version int
|
|
||||||
|
|
||||||
func (v *Version) String() *string {
|
|
||||||
output := strconv.Itoa(int(*v))
|
|
||||||
return &output
|
|
||||||
}
|
|
||||||
|
|
||||||
// Origin defines the structure for the "o=" field which provides the
|
|
||||||
// originator of the session plus a session identifier and version number.
|
|
||||||
type Origin struct {
|
|
||||||
Username string
|
|
||||||
SessionID uint64
|
|
||||||
SessionVersion uint64
|
|
||||||
NetworkType string
|
|
||||||
AddressType string
|
|
||||||
UnicastAddress string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Origin) String() *string {
|
|
||||||
output := fmt.Sprintf(
|
|
||||||
"%v %d %d %v %v %v",
|
|
||||||
o.Username,
|
|
||||||
o.SessionID,
|
|
||||||
o.SessionVersion,
|
|
||||||
o.NetworkType,
|
|
||||||
o.AddressType,
|
|
||||||
o.UnicastAddress,
|
|
||||||
)
|
|
||||||
return &output
|
|
||||||
}
|
|
||||||
|
|
||||||
// SessionName describes a structured representations for the "s=" field
|
|
||||||
// and is the textual session name.
|
|
||||||
type SessionName string
|
|
||||||
|
|
||||||
func (s *SessionName) String() *string {
|
|
||||||
output := string(*s)
|
|
||||||
return &output
|
|
||||||
}
|
|
||||||
|
|
||||||
// EmailAddress describes a structured representations for the "e=" line
|
|
||||||
// which specifies email contact information for the person responsible for
|
|
||||||
// the conference.
|
|
||||||
type EmailAddress string
|
|
||||||
|
|
||||||
func (e *EmailAddress) String() *string {
|
|
||||||
output := string(*e)
|
|
||||||
return &output
|
|
||||||
}
|
|
||||||
|
|
||||||
// PhoneNumber describes a structured representations for the "p=" line
|
|
||||||
// specify phone contact information for the person responsible for the
|
|
||||||
// conference.
|
|
||||||
type PhoneNumber string
|
|
||||||
|
|
||||||
func (p *PhoneNumber) String() *string {
|
|
||||||
output := string(*p)
|
|
||||||
return &output
|
|
||||||
}
|
|
||||||
|
|
||||||
// TimeZone defines the structured object for "z=" line which describes
|
|
||||||
// repeated sessions scheduling.
|
|
||||||
type TimeZone struct {
|
|
||||||
AdjustmentTime uint64
|
|
||||||
Offset int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (z *TimeZone) String() string {
|
|
||||||
return strconv.FormatUint(z.AdjustmentTime, 10) + " " + strconv.FormatInt(z.Offset, 10)
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
package sdp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TimeDescription describes "t=", "r=" fields of the session description
|
|
||||||
// which are used to specify the start and stop times for a session as well as
|
|
||||||
// repeat intervals and durations for the scheduled session.
|
|
||||||
type TimeDescription struct {
|
|
||||||
// t=<start-time> <stop-time>
|
|
||||||
// https://tools.ietf.org/html/rfc4566#section-5.9
|
|
||||||
Timing Timing
|
|
||||||
|
|
||||||
// r=<repeat interval> <active duration> <offsets from start-time>
|
|
||||||
// https://tools.ietf.org/html/rfc4566#section-5.10
|
|
||||||
RepeatTimes []RepeatTime
|
|
||||||
}
|
|
||||||
|
|
||||||
// Timing defines the "t=" field's structured representation for the start and
|
|
||||||
// stop times.
|
|
||||||
type Timing struct {
|
|
||||||
StartTime uint64
|
|
||||||
StopTime uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Timing) String() *string {
|
|
||||||
output := strconv.FormatUint(t.StartTime, 10)
|
|
||||||
output += " " + strconv.FormatUint(t.StopTime, 10)
|
|
||||||
return &output
|
|
||||||
}
|
|
||||||
|
|
||||||
// RepeatTime describes the "r=" fields of the session description which
|
|
||||||
// represents the intervals and durations for repeated scheduled sessions.
|
|
||||||
type RepeatTime struct {
|
|
||||||
Interval int64
|
|
||||||
Duration int64
|
|
||||||
Offsets []int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RepeatTime) String() *string {
|
|
||||||
fields := make([]string, 0)
|
|
||||||
fields = append(fields, strconv.FormatInt(r.Interval, 10))
|
|
||||||
fields = append(fields, strconv.FormatInt(r.Duration, 10))
|
|
||||||
for _, value := range r.Offsets {
|
|
||||||
fields = append(fields, strconv.FormatInt(value, 10))
|
|
||||||
}
|
|
||||||
|
|
||||||
output := strings.Join(fields, " ")
|
|
||||||
return &output
|
|
||||||
}
|
|
||||||
@@ -1,972 +0,0 @@
|
|||||||
package sdp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/url"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Unmarshal is the primary function that deserializes the session description
|
|
||||||
// message and stores it inside of a structured SessionDescription object.
|
|
||||||
//
|
|
||||||
// The States Sransition 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*)*
|
|
||||||
//
|
|
||||||
// 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 string) error {
|
|
||||||
l := &lexer{
|
|
||||||
desc: s,
|
|
||||||
input: bufio.NewReader(strings.NewReader(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) {
|
|
||||||
key, err := readType(l.input)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Errorf("sdp: invalid syntax `%v`", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
if key == "v=" {
|
|
||||||
return unmarshalProtocolVersion, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.Errorf("sdp: invalid syntax `%v`", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func s2(l *lexer) (stateFn, error) {
|
|
||||||
key, err := readType(l.input)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Errorf("sdp: invalid syntax `%v`", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
if key == "o=" {
|
|
||||||
return unmarshalOrigin, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.Errorf("sdp: invalid syntax `%v`", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func s3(l *lexer) (stateFn, error) {
|
|
||||||
key, err := readType(l.input)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if key == "s=" {
|
|
||||||
return unmarshalSessionName, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.Errorf("sdp: invalid syntax `%v`", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func s4(l *lexer) (stateFn, error) {
|
|
||||||
key, err := readType(l.input)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Errorf("sdp: invalid syntax `%v`", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch key {
|
|
||||||
case "i=":
|
|
||||||
return unmarshalSessionInformation, nil
|
|
||||||
case "u=":
|
|
||||||
return unmarshalURI, nil
|
|
||||||
case "e=":
|
|
||||||
return unmarshalEmail, nil
|
|
||||||
case "p=":
|
|
||||||
return unmarshalPhone, nil
|
|
||||||
case "c=":
|
|
||||||
return unmarshalSessionConnectionInformation, nil
|
|
||||||
case "b=":
|
|
||||||
return unmarshalSessionBandwidth, nil
|
|
||||||
case "t=":
|
|
||||||
return unmarshalTiming, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.Errorf("sdp: invalid syntax `%v`", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func s5(l *lexer) (stateFn, error) {
|
|
||||||
key, err := readType(l.input)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch key {
|
|
||||||
case "b=":
|
|
||||||
return unmarshalSessionBandwidth, nil
|
|
||||||
case "t=":
|
|
||||||
return unmarshalTiming, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.Errorf("sdp: invalid syntax `%v`", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func s6(l *lexer) (stateFn, error) {
|
|
||||||
key, err := readType(l.input)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Errorf("sdp: invalid syntax `%v`", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch key {
|
|
||||||
case "p=":
|
|
||||||
return unmarshalPhone, nil
|
|
||||||
case "c=":
|
|
||||||
return unmarshalSessionConnectionInformation, nil
|
|
||||||
case "b=":
|
|
||||||
return unmarshalSessionBandwidth, nil
|
|
||||||
case "t=":
|
|
||||||
return unmarshalTiming, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.Errorf("sdp: invalid syntax `%v`", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func s7(l *lexer) (stateFn, error) {
|
|
||||||
key, err := readType(l.input)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Errorf("sdp: invalid syntax `%v`", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch key {
|
|
||||||
case "u=":
|
|
||||||
return unmarshalURI, nil
|
|
||||||
case "e=":
|
|
||||||
return unmarshalEmail, nil
|
|
||||||
case "p=":
|
|
||||||
return unmarshalPhone, nil
|
|
||||||
case "c=":
|
|
||||||
return unmarshalSessionConnectionInformation, nil
|
|
||||||
case "b=":
|
|
||||||
return unmarshalSessionBandwidth, nil
|
|
||||||
case "t=":
|
|
||||||
return unmarshalTiming, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.Errorf("sdp: invalid syntax `%v`", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func s8(l *lexer) (stateFn, error) {
|
|
||||||
key, err := readType(l.input)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Errorf("sdp: invalid syntax `%v`", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch key {
|
|
||||||
case "c=":
|
|
||||||
return unmarshalSessionConnectionInformation, nil
|
|
||||||
case "b=":
|
|
||||||
return unmarshalSessionBandwidth, nil
|
|
||||||
case "t=":
|
|
||||||
return unmarshalTiming, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.Errorf("sdp: invalid syntax `%v`", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func s9(l *lexer) (stateFn, error) {
|
|
||||||
key, err := readType(l.input)
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF && key == "" {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch key {
|
|
||||||
case "z=":
|
|
||||||
return unmarshalTimeZones, nil
|
|
||||||
case "k=":
|
|
||||||
return unmarshalSessionEncryptionKey, nil
|
|
||||||
case "a=":
|
|
||||||
return unmarshalSessionAttribute, nil
|
|
||||||
case "r=":
|
|
||||||
return unmarshalRepeatTimes, nil
|
|
||||||
case "t=":
|
|
||||||
return unmarshalTiming, nil
|
|
||||||
case "m=":
|
|
||||||
return unmarshalMediaDescription, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.Errorf("sdp: invalid syntax `%v`", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func s10(l *lexer) (stateFn, error) {
|
|
||||||
key, err := readType(l.input)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Errorf("sdp: invalid syntax `%v`", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch key {
|
|
||||||
case "e=":
|
|
||||||
return unmarshalEmail, nil
|
|
||||||
case "p=":
|
|
||||||
return unmarshalPhone, nil
|
|
||||||
case "c=":
|
|
||||||
return unmarshalSessionConnectionInformation, nil
|
|
||||||
case "b=":
|
|
||||||
return unmarshalSessionBandwidth, nil
|
|
||||||
case "t=":
|
|
||||||
return unmarshalTiming, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.Errorf("sdp: invalid syntax `%v`", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func s11(l *lexer) (stateFn, error) {
|
|
||||||
key, err := readType(l.input)
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF && key == "" {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch key {
|
|
||||||
case "a=":
|
|
||||||
return unmarshalSessionAttribute, nil
|
|
||||||
case "m=":
|
|
||||||
return unmarshalMediaDescription, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.Errorf("sdp: invalid syntax `%v`", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func s12(l *lexer) (stateFn, error) {
|
|
||||||
key, err := readType(l.input)
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF && key == "" {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch key {
|
|
||||||
case "a=":
|
|
||||||
return unmarshalMediaAttribute, nil
|
|
||||||
case "k=":
|
|
||||||
return unmarshalMediaEncryptionKey, nil
|
|
||||||
case "b=":
|
|
||||||
return unmarshalMediaBandwidth, nil
|
|
||||||
case "c=":
|
|
||||||
return unmarshalMediaConnectionInformation, nil
|
|
||||||
case "i=":
|
|
||||||
return unmarshalMediaTitle, nil
|
|
||||||
case "m=":
|
|
||||||
return unmarshalMediaDescription, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.Errorf("sdp: invalid syntax `%v`", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func s13(l *lexer) (stateFn, error) {
|
|
||||||
key, err := readType(l.input)
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF && key == "" {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch key {
|
|
||||||
case "a=":
|
|
||||||
return unmarshalSessionAttribute, nil
|
|
||||||
case "k=":
|
|
||||||
return unmarshalSessionEncryptionKey, nil
|
|
||||||
case "m=":
|
|
||||||
return unmarshalMediaDescription, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.Errorf("sdp: invalid syntax `%v`", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func s14(l *lexer) (stateFn, error) {
|
|
||||||
key, err := readType(l.input)
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF && key == "" {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch key {
|
|
||||||
case "a=":
|
|
||||||
return unmarshalMediaAttribute, nil
|
|
||||||
case "m=":
|
|
||||||
return unmarshalMediaDescription, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.Errorf("sdp: invalid syntax `%v`", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func s15(l *lexer) (stateFn, error) {
|
|
||||||
key, err := readType(l.input)
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF && key == "" {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch key {
|
|
||||||
case "a=":
|
|
||||||
return unmarshalMediaAttribute, nil
|
|
||||||
case "k=":
|
|
||||||
return unmarshalMediaEncryptionKey, nil
|
|
||||||
case "b=":
|
|
||||||
return unmarshalMediaBandwidth, nil
|
|
||||||
case "m=":
|
|
||||||
return unmarshalMediaDescription, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.Errorf("sdp: invalid syntax `%v`", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func s16(l *lexer) (stateFn, error) {
|
|
||||||
key, err := readType(l.input)
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF && key == "" {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch key {
|
|
||||||
case "a=":
|
|
||||||
return unmarshalMediaAttribute, nil
|
|
||||||
case "k=":
|
|
||||||
return unmarshalMediaEncryptionKey, nil
|
|
||||||
case "c=":
|
|
||||||
return unmarshalMediaConnectionInformation, nil
|
|
||||||
case "b=":
|
|
||||||
return unmarshalMediaBandwidth, nil
|
|
||||||
case "m=":
|
|
||||||
return unmarshalMediaDescription, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.Errorf("sdp: invalid syntax `%v`", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func unmarshalProtocolVersion(l *lexer) (stateFn, error) {
|
|
||||||
value, err := readValue(l.input)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
version, err := strconv.ParseInt(value, 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Errorf("sdp: invalid numeric value `%v`", version)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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, errors.Errorf("sdp: invalid value `%v`", version)
|
|
||||||
}
|
|
||||||
|
|
||||||
return s2, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func unmarshalOrigin(l *lexer) (stateFn, error) {
|
|
||||||
value, err := readValue(l.input)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
fields := strings.Fields(value)
|
|
||||||
if len(fields) != 6 {
|
|
||||||
return nil, errors.Errorf("sdp: invalid syntax `o=%v`", fields)
|
|
||||||
}
|
|
||||||
|
|
||||||
sessionID, err := strconv.ParseUint(fields[1], 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Errorf("sdp: invalid numeric value `%v`", fields[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
sessionVersion, err := strconv.ParseUint(fields[2], 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Errorf("sdp: invalid numeric value `%v`", fields[2])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set according to currently registered with IANA
|
|
||||||
// https://tools.ietf.org/html/rfc4566#section-8.2.6
|
|
||||||
if i := indexOf(fields[3], []string{"IN"}); i == -1 {
|
|
||||||
return nil, errors.Errorf("sdp: invalid value `%v`", fields[3])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set according to currently registered with IANA
|
|
||||||
// https://tools.ietf.org/html/rfc4566#section-8.2.7
|
|
||||||
if i := indexOf(fields[4], []string{"IP4", "IP6"}); i == -1 {
|
|
||||||
return nil, errors.Errorf("sdp: invalid value `%v`", fields[4])
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO validated UnicastAddress
|
|
||||||
|
|
||||||
l.desc.Origin = Origin{
|
|
||||||
Username: fields[0],
|
|
||||||
SessionID: sessionID,
|
|
||||||
SessionVersion: sessionVersion,
|
|
||||||
NetworkType: fields[3],
|
|
||||||
AddressType: fields[4],
|
|
||||||
UnicastAddress: fields[5],
|
|
||||||
}
|
|
||||||
|
|
||||||
return s3, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func unmarshalSessionName(l *lexer) (stateFn, error) {
|
|
||||||
value, err := readValue(l.input)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
l.desc.SessionName = SessionName(value)
|
|
||||||
return s4, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func unmarshalSessionInformation(l *lexer) (stateFn, error) {
|
|
||||||
value, err := readValue(l.input)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
sessionInformation := Information(value)
|
|
||||||
l.desc.SessionInformation = &sessionInformation
|
|
||||||
return s7, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func unmarshalURI(l *lexer) (stateFn, error) {
|
|
||||||
value, err := readValue(l.input)
|
|
||||||
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 := readValue(l.input)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
emailAddress := EmailAddress(value)
|
|
||||||
l.desc.EmailAddress = &emailAddress
|
|
||||||
return s6, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func unmarshalPhone(l *lexer) (stateFn, error) {
|
|
||||||
value, err := readValue(l.input)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
phoneNumber := PhoneNumber(value)
|
|
||||||
l.desc.PhoneNumber = &phoneNumber
|
|
||||||
return s8, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func unmarshalSessionConnectionInformation(l *lexer) (stateFn, error) {
|
|
||||||
value, err := readValue(l.input)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
l.desc.ConnectionInformation, err = unmarshalConnectionInformation(value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Errorf("sdp: invalid syntax `c=%v`", value)
|
|
||||||
}
|
|
||||||
return s5, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func unmarshalConnectionInformation(value string) (*ConnectionInformation, error) {
|
|
||||||
fields := strings.Fields(value)
|
|
||||||
if len(fields) < 2 {
|
|
||||||
return nil, errors.Errorf("sdp: invalid syntax `c=%v`", fields)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set according to currently registered with IANA
|
|
||||||
// https://tools.ietf.org/html/rfc4566#section-8.2.6
|
|
||||||
if i := indexOf(fields[0], []string{"IN"}); i == -1 {
|
|
||||||
return nil, errors.Errorf("sdp: invalid value `%v`", fields[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set according to currently registered with IANA
|
|
||||||
// https://tools.ietf.org/html/rfc4566#section-8.2.7
|
|
||||||
if i := indexOf(fields[1], []string{"IP4", "IP6"}); i == -1 {
|
|
||||||
return nil, errors.Errorf("sdp: invalid value `%v`", fields[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
var connAddr *Address
|
|
||||||
if len(fields) > 2 {
|
|
||||||
connAddr = &Address{}
|
|
||||||
|
|
||||||
parts := strings.Split(fields[2], "/")
|
|
||||||
connAddr.IP = net.ParseIP(parts[0])
|
|
||||||
if connAddr.IP == nil {
|
|
||||||
return nil, errors.Errorf("sdp: invalid value `%v`", fields[2])
|
|
||||||
}
|
|
||||||
|
|
||||||
isIP6 := connAddr.IP.To4() == nil
|
|
||||||
if len(parts) > 1 {
|
|
||||||
val, err := strconv.ParseInt(parts[1], 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Errorf("sdp: invalid numeric value `%v`", fields[2])
|
|
||||||
}
|
|
||||||
|
|
||||||
if isIP6 {
|
|
||||||
multi := int(val)
|
|
||||||
connAddr.Range = &multi
|
|
||||||
} else {
|
|
||||||
ttl := int(val)
|
|
||||||
connAddr.TTL = &ttl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(parts) > 2 {
|
|
||||||
val, err := strconv.ParseInt(parts[2], 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Errorf("sdp: invalid numeric value `%v`", fields[2])
|
|
||||||
}
|
|
||||||
|
|
||||||
multi := int(val)
|
|
||||||
connAddr.Range = &multi
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ConnectionInformation{
|
|
||||||
NetworkType: fields[0],
|
|
||||||
AddressType: fields[1],
|
|
||||||
Address: connAddr,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func unmarshalSessionBandwidth(l *lexer) (stateFn, error) {
|
|
||||||
value, err := readValue(l.input)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
bandwidth, err := unmarshalBandwidth(value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Errorf("sdp: invalid syntax `b=%v`", 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, errors.Errorf("sdp: invalid syntax `b=%v`", parts)
|
|
||||||
}
|
|
||||||
|
|
||||||
experimental := strings.HasPrefix(parts[0], "X-")
|
|
||||||
if experimental {
|
|
||||||
parts[0] = strings.TrimPrefix(parts[0], "X-")
|
|
||||||
} else {
|
|
||||||
// Set according to currently registered with IANA
|
|
||||||
// https://tools.ietf.org/html/rfc4566#section-5.8
|
|
||||||
if i := indexOf(parts[0], []string{"CT", "AS"}); i == -1 {
|
|
||||||
return nil, errors.Errorf("sdp: invalid value `%v`", parts[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bandwidth, err := strconv.ParseUint(parts[1], 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Errorf("sdp: invalid numeric value `%v`", parts[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Bandwidth{
|
|
||||||
Experimental: experimental,
|
|
||||||
Type: parts[0],
|
|
||||||
Bandwidth: bandwidth,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func unmarshalTiming(l *lexer) (stateFn, error) {
|
|
||||||
value, err := readValue(l.input)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
fields := strings.Fields(value)
|
|
||||||
if len(fields) < 2 {
|
|
||||||
return nil, errors.Errorf("sdp: invalid syntax `t=%v`", fields)
|
|
||||||
}
|
|
||||||
|
|
||||||
td := TimeDescription{}
|
|
||||||
|
|
||||||
td.Timing.StartTime, err = strconv.ParseUint(fields[0], 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Errorf("sdp: invalid numeric value `%v`", fields[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
td.Timing.StopTime, err = strconv.ParseUint(fields[1], 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Errorf("sdp: invalid numeric value `%v`", fields[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
l.desc.TimeDescriptions = append(l.desc.TimeDescriptions, td)
|
|
||||||
|
|
||||||
return s9, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func unmarshalRepeatTimes(l *lexer) (stateFn, error) {
|
|
||||||
value, err := readValue(l.input)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
fields := strings.Fields(value)
|
|
||||||
if len(fields) < 3 {
|
|
||||||
return nil, errors.Errorf("sdp: invalid syntax `r=%v`", fields)
|
|
||||||
}
|
|
||||||
|
|
||||||
latestTimeDesc := &l.desc.TimeDescriptions[len(l.desc.TimeDescriptions)-1]
|
|
||||||
|
|
||||||
newRepeatTime := RepeatTime{}
|
|
||||||
newRepeatTime.Interval, err = parseTimeUnits(fields[0])
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Errorf("sdp: invalid value `%v`", fields)
|
|
||||||
}
|
|
||||||
|
|
||||||
newRepeatTime.Duration, err = parseTimeUnits(fields[1])
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Errorf("sdp: invalid value `%v`", fields)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 2; i < len(fields); i++ {
|
|
||||||
offset, err := parseTimeUnits(fields[i])
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Errorf("sdp: invalid value `%v`", fields)
|
|
||||||
}
|
|
||||||
newRepeatTime.Offsets = append(newRepeatTime.Offsets, offset)
|
|
||||||
}
|
|
||||||
latestTimeDesc.RepeatTimes = append(latestTimeDesc.RepeatTimes, newRepeatTime)
|
|
||||||
|
|
||||||
return s9, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func unmarshalTimeZones(l *lexer) (stateFn, error) {
|
|
||||||
value, err := readValue(l.input)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
fields := strings.Fields(value)
|
|
||||||
if len(fields)%2 != 0 {
|
|
||||||
return nil, errors.Errorf("sdp: invalid syntax `t=%v`", fields)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < len(fields); i += 2 {
|
|
||||||
var timeZone TimeZone
|
|
||||||
|
|
||||||
timeZone.AdjustmentTime, err = strconv.ParseUint(fields[i], 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Errorf("sdp: invalid value `%v`", fields)
|
|
||||||
}
|
|
||||||
|
|
||||||
timeZone.Offset, err = parseTimeUnits(fields[i+1])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
l.desc.TimeZones = append(l.desc.TimeZones, timeZone)
|
|
||||||
}
|
|
||||||
|
|
||||||
return s13, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func unmarshalSessionEncryptionKey(l *lexer) (stateFn, error) {
|
|
||||||
value, err := readValue(l.input)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
encryptionKey := EncryptionKey(value)
|
|
||||||
l.desc.EncryptionKey = &encryptionKey
|
|
||||||
return s11, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func unmarshalSessionAttribute(l *lexer) (stateFn, error) {
|
|
||||||
value, err := readValue(l.input)
|
|
||||||
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) {
|
|
||||||
value, err := readValue(l.input)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
fields := strings.Fields(value)
|
|
||||||
if len(fields) < 4 {
|
|
||||||
return nil, errors.Errorf("sdp: invalid syntax `m=%v`", fields)
|
|
||||||
}
|
|
||||||
|
|
||||||
newMediaDesc := &MediaDescription{}
|
|
||||||
|
|
||||||
// <media>
|
|
||||||
// Set according to currently registered with IANA
|
|
||||||
// https://tools.ietf.org/html/rfc4566#section-5.14
|
|
||||||
if i := indexOf(fields[0], []string{"audio", "video", "text", "application", "message"}); i == -1 {
|
|
||||||
return nil, errors.Errorf("sdp: invalid value `%v`", fields[0])
|
|
||||||
}
|
|
||||||
newMediaDesc.MediaName.Media = fields[0]
|
|
||||||
|
|
||||||
// <port>
|
|
||||||
parts := strings.Split(fields[1], "/")
|
|
||||||
newMediaDesc.MediaName.Port.Value, err = parsePort(parts[0])
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Errorf("sdp: invalid port value `%v`", parts[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(parts) > 1 {
|
|
||||||
portRange, err := strconv.Atoi(parts[1])
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Errorf("sdp: invalid value `%v`", parts)
|
|
||||||
}
|
|
||||||
newMediaDesc.MediaName.Port.Range = &portRange
|
|
||||||
}
|
|
||||||
|
|
||||||
// <proto>
|
|
||||||
// Set according to currently registered with IANA
|
|
||||||
// https://tools.ietf.org/html/rfc4566#section-5.14
|
|
||||||
for _, proto := range strings.Split(fields[2], "/") {
|
|
||||||
if i := indexOf(proto, []string{"UDP", "RTP", "AVP", "SAVP", "SAVPF", "TLS", "DTLS", "SCTP"}); i == -1 {
|
|
||||||
return nil, errors.Errorf("sdp: invalid value `%v`", fields[2])
|
|
||||||
}
|
|
||||||
newMediaDesc.MediaName.Protos = append(newMediaDesc.MediaName.Protos, proto)
|
|
||||||
}
|
|
||||||
|
|
||||||
// <fmt>...
|
|
||||||
for i := 3; i < len(fields); i++ {
|
|
||||||
newMediaDesc.MediaName.Formats = append(newMediaDesc.MediaName.Formats, fields[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
l.desc.MediaDescriptions = append(l.desc.MediaDescriptions, newMediaDesc)
|
|
||||||
|
|
||||||
return s12, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func unmarshalMediaTitle(l *lexer) (stateFn, error) {
|
|
||||||
value, err := readValue(l.input)
|
|
||||||
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) {
|
|
||||||
value, err := readValue(l.input)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
latestMediaDesc := l.desc.MediaDescriptions[len(l.desc.MediaDescriptions)-1]
|
|
||||||
latestMediaDesc.ConnectionInformation, err = unmarshalConnectionInformation(value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Errorf("sdp: invalid syntax `c=%v`", value)
|
|
||||||
}
|
|
||||||
return s15, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func unmarshalMediaBandwidth(l *lexer) (stateFn, error) {
|
|
||||||
value, err := readValue(l.input)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
latestMediaDesc := l.desc.MediaDescriptions[len(l.desc.MediaDescriptions)-1]
|
|
||||||
bandwidth, err := unmarshalBandwidth(value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Errorf("sdp: invalid syntax `b=%v`", value)
|
|
||||||
}
|
|
||||||
latestMediaDesc.Bandwidth = append(latestMediaDesc.Bandwidth, *bandwidth)
|
|
||||||
return s15, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func unmarshalMediaEncryptionKey(l *lexer) (stateFn, error) {
|
|
||||||
value, err := readValue(l.input)
|
|
||||||
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 := readValue(l.input)
|
|
||||||
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) (int64, error) {
|
|
||||||
// Some time offsets in the protocol can be provided with a shorthand
|
|
||||||
// notation. This code ensures to convert it to NTP timestamp format.
|
|
||||||
// d - days (86400 seconds)
|
|
||||||
// h - hours (3600 seconds)
|
|
||||||
// m - minutes (60 seconds)
|
|
||||||
// s - seconds (allowed for completeness)
|
|
||||||
switch value[len(value)-1:] {
|
|
||||||
case "d":
|
|
||||||
num, err := strconv.ParseInt(value[:len(value)-1], 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return 0, errors.Errorf("sdp: invalid value `%v`", value)
|
|
||||||
}
|
|
||||||
return num * 86400, nil
|
|
||||||
case "h":
|
|
||||||
num, err := strconv.ParseInt(value[:len(value)-1], 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return 0, errors.Errorf("sdp: invalid value `%v`", value)
|
|
||||||
}
|
|
||||||
return num * 3600, nil
|
|
||||||
case "m":
|
|
||||||
num, err := strconv.ParseInt(value[:len(value)-1], 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return 0, errors.Errorf("sdp: invalid value `%v`", value)
|
|
||||||
}
|
|
||||||
return num * 60, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
num, err := strconv.ParseInt(value, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return 0, errors.Errorf("sdp: invalid value `%v`", value)
|
|
||||||
}
|
|
||||||
|
|
||||||
return num, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parsePort(value string) (int, error) {
|
|
||||||
port, err := strconv.Atoi(value)
|
|
||||||
if err != nil {
|
|
||||||
return 0, errors.Errorf("sdp: invalid port value `%v`", port)
|
|
||||||
}
|
|
||||||
|
|
||||||
if port < 0 || port > 65536 {
|
|
||||||
return 0, errors.Errorf("sdp: invalid port value -- out of range `%v`", port)
|
|
||||||
}
|
|
||||||
|
|
||||||
return port, nil
|
|
||||||
}
|
|
||||||
@@ -1,315 +0,0 @@
|
|||||||
package sdp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
BaseSDP = "v=0\r\n" +
|
|
||||||
"o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r\n" +
|
|
||||||
"s=SDP Seminar\r\n"
|
|
||||||
|
|
||||||
SessionInformationSDP = BaseSDP +
|
|
||||||
"i=A Seminar on the session description protocol\r\n" +
|
|
||||||
"t=3034423619 3042462419\r\n"
|
|
||||||
|
|
||||||
URISDP = BaseSDP +
|
|
||||||
"u=http://www.example.com/seminars/sdp.pdf\r\n" +
|
|
||||||
"t=3034423619 3042462419\r\n"
|
|
||||||
|
|
||||||
EmailAddressSDP = BaseSDP +
|
|
||||||
"e=j.doe@example.com (Jane Doe)\r\n" +
|
|
||||||
"t=3034423619 3042462419\r\n"
|
|
||||||
|
|
||||||
PhoneNumberSDP = BaseSDP +
|
|
||||||
"p=+1 617 555-6011\r\n" +
|
|
||||||
"t=3034423619 3042462419\r\n"
|
|
||||||
|
|
||||||
SessionConnectionInformationSDP = BaseSDP +
|
|
||||||
"c=IN IP4 224.2.17.12/127\r\n" +
|
|
||||||
"t=3034423619 3042462419\r\n"
|
|
||||||
|
|
||||||
SessionBandwidthSDP = BaseSDP +
|
|
||||||
"b=X-YZ:128\r\n" +
|
|
||||||
"b=AS:12345\r\n" +
|
|
||||||
"t=3034423619 3042462419\r\n"
|
|
||||||
|
|
||||||
TimingSDP = BaseSDP +
|
|
||||||
"t=2873397496 2873404696\r\n"
|
|
||||||
|
|
||||||
// Short hand time notation is converted into NTP timestamp format in
|
|
||||||
// seconds. Because of that unittest comparisons will fail as the same time
|
|
||||||
// will be expressed in different units.
|
|
||||||
RepeatTimesSDP = TimingSDP +
|
|
||||||
"r=604800 3600 0 90000\r\n" +
|
|
||||||
"r=3d 2h 0 21h\r\n"
|
|
||||||
|
|
||||||
RepeatTimesSDPExpected = TimingSDP +
|
|
||||||
"r=604800 3600 0 90000\r\n" +
|
|
||||||
"r=259200 7200 0 75600\r\n"
|
|
||||||
|
|
||||||
// The expected value looks a bit different for the same reason as mentioned
|
|
||||||
// above regarding RepeatTimes.
|
|
||||||
TimeZonesSDP = TimingSDP +
|
|
||||||
"r=2882844526 -1h 2898848070 0\r\n"
|
|
||||||
|
|
||||||
TimeZonesSDPExpected = TimingSDP +
|
|
||||||
"r=2882844526 -3600 2898848070 0\r\n"
|
|
||||||
|
|
||||||
SessionEncryptionKeySDP = TimingSDP +
|
|
||||||
"k=prompt\r\n"
|
|
||||||
|
|
||||||
SessionAttributesSDP = TimingSDP +
|
|
||||||
"a=rtpmap:96 opus/48000\r\n"
|
|
||||||
|
|
||||||
MediaNameSDP = TimingSDP +
|
|
||||||
"m=video 51372 RTP/AVP 99\r\n" +
|
|
||||||
"m=audio 54400 RTP/SAVPF 0 96\r\n"
|
|
||||||
|
|
||||||
MediaTitleSDP = MediaNameSDP +
|
|
||||||
"i=Vivamus a posuere nisl\r\n"
|
|
||||||
|
|
||||||
MediaConnectionInformationSDP = MediaNameSDP +
|
|
||||||
"c=IN IP4 203.0.113.1\r\n"
|
|
||||||
|
|
||||||
MediaBandwidthSDP = MediaNameSDP +
|
|
||||||
"b=X-YZ:128\r\n" +
|
|
||||||
"b=AS:12345\r\n"
|
|
||||||
|
|
||||||
MediaEncryptionKeySDP = MediaNameSDP +
|
|
||||||
"k=prompt\r\n"
|
|
||||||
|
|
||||||
MediaAttributesSDP = MediaNameSDP +
|
|
||||||
"a=rtpmap:99 h263-1998/90000\r\n" +
|
|
||||||
"a=candidate:0 1 UDP 2113667327 203.0.113.1 54400 typ host\r\n"
|
|
||||||
|
|
||||||
CanonicalUnmarshalSDP = "v=0\r\n" +
|
|
||||||
"o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r\n" +
|
|
||||||
"s=SDP Seminar\r\n" +
|
|
||||||
"i=A Seminar on the session description protocol\r\n" +
|
|
||||||
"u=http://www.example.com/seminars/sdp.pdf\r\n" +
|
|
||||||
"e=j.doe@example.com (Jane Doe)\r\n" +
|
|
||||||
"p=+1 617 555-6011\r\n" +
|
|
||||||
"c=IN IP4 224.2.17.12/127\r\n" +
|
|
||||||
"b=X-YZ:128\r\n" +
|
|
||||||
"b=AS:12345\r\n" +
|
|
||||||
"t=2873397496 2873404696\r\n" +
|
|
||||||
"t=3034423619 3042462419\r\n" +
|
|
||||||
"r=604800 3600 0 90000\r\n" +
|
|
||||||
"z=2882844526 -3600 2898848070 0\r\n" +
|
|
||||||
"k=prompt\r\n" +
|
|
||||||
"a=candidate:0 1 UDP 2113667327 203.0.113.1 54400 typ host\r\n" +
|
|
||||||
"a=recvonly\r\n" +
|
|
||||||
"m=audio 49170 RTP/AVP 0\r\n" +
|
|
||||||
"i=Vivamus a posuere nisl\r\n" +
|
|
||||||
"c=IN IP4 203.0.113.1\r\n" +
|
|
||||||
"b=X-YZ:128\r\n" +
|
|
||||||
"k=prompt\r\n" +
|
|
||||||
"a=sendrecv\r\n" +
|
|
||||||
"m=video 51372 RTP/AVP 99\r\n" +
|
|
||||||
"a=rtpmap:99 h263-1998/90000\r\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestUnmarshalSessionInformation(t *testing.T) {
|
|
||||||
sd := &SessionDescription{}
|
|
||||||
if err := sd.Unmarshal(SessionInformationSDP); err != nil {
|
|
||||||
t.Errorf("error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
actual := sd.Marshal()
|
|
||||||
if actual != SessionInformationSDP {
|
|
||||||
t.Errorf("error:\n\nEXPECTED:\n%v\nACTUAL:\n%v", SessionInformationSDP, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnmarshalURI(t *testing.T) {
|
|
||||||
sd := &SessionDescription{}
|
|
||||||
if err := sd.Unmarshal(URISDP); err != nil {
|
|
||||||
t.Errorf("error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
actual := sd.Marshal()
|
|
||||||
if actual != URISDP {
|
|
||||||
t.Errorf("error:\n\nEXPECTED:\n%v\nACTUAL:\n%v", URISDP, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnmarshalEmailAddress(t *testing.T) {
|
|
||||||
sd := &SessionDescription{}
|
|
||||||
if err := sd.Unmarshal(EmailAddressSDP); err != nil {
|
|
||||||
t.Errorf("error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
actual := sd.Marshal()
|
|
||||||
if actual != EmailAddressSDP {
|
|
||||||
t.Errorf("error:\n\nEXPECTED:\n%v\nACTUAL:\n%v", EmailAddressSDP, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnmarshalPhoneNumber(t *testing.T) {
|
|
||||||
sd := &SessionDescription{}
|
|
||||||
if err := sd.Unmarshal(PhoneNumberSDP); err != nil {
|
|
||||||
t.Errorf("error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
actual := sd.Marshal()
|
|
||||||
if actual != PhoneNumberSDP {
|
|
||||||
t.Errorf("error:\n\nEXPECTED:\n%v\nACTUAL:\n%v", PhoneNumberSDP, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnmarshalSessionConnectionInformation(t *testing.T) {
|
|
||||||
sd := &SessionDescription{}
|
|
||||||
if err := sd.Unmarshal(SessionConnectionInformationSDP); err != nil {
|
|
||||||
t.Errorf("error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
actual := sd.Marshal()
|
|
||||||
if actual != SessionConnectionInformationSDP {
|
|
||||||
t.Errorf("error:\n\nEXPECTED:\n%v\nACTUAL:\n%v", SessionConnectionInformationSDP, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnmarshalSessionBandwidth(t *testing.T) {
|
|
||||||
sd := &SessionDescription{}
|
|
||||||
if err := sd.Unmarshal(SessionBandwidthSDP); err != nil {
|
|
||||||
t.Errorf("error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
actual := sd.Marshal()
|
|
||||||
if actual != SessionBandwidthSDP {
|
|
||||||
t.Errorf("error:\n\nEXPECTED:\n%v\nACTUAL:\n%v", SessionBandwidthSDP, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnmarshalRepeatTimes(t *testing.T) {
|
|
||||||
sd := &SessionDescription{}
|
|
||||||
if err := sd.Unmarshal(RepeatTimesSDP); err != nil {
|
|
||||||
t.Errorf("error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
actual := sd.Marshal()
|
|
||||||
if actual != RepeatTimesSDPExpected {
|
|
||||||
t.Errorf("error:\n\nEXPECTED:\n%v\nACTUAL:\n%v", RepeatTimesSDPExpected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnmarshalTimeZones(t *testing.T) {
|
|
||||||
sd := &SessionDescription{}
|
|
||||||
if err := sd.Unmarshal(TimeZonesSDP); err != nil {
|
|
||||||
t.Errorf("error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
actual := sd.Marshal()
|
|
||||||
if actual != TimeZonesSDPExpected {
|
|
||||||
t.Errorf("error:\n\nEXPECTED:\n%v\nACTUAL:\n%v", TimeZonesSDPExpected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnmarshalSessionEncryptionKey(t *testing.T) {
|
|
||||||
sd := &SessionDescription{}
|
|
||||||
if err := sd.Unmarshal(SessionEncryptionKeySDP); err != nil {
|
|
||||||
t.Errorf("error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
actual := sd.Marshal()
|
|
||||||
if actual != SessionEncryptionKeySDP {
|
|
||||||
t.Errorf("error:\n\nEXPECTED:\n%v\nACTUAL:\n%v", SessionEncryptionKeySDP, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnmarshalSessionAttributes(t *testing.T) {
|
|
||||||
sd := &SessionDescription{}
|
|
||||||
if err := sd.Unmarshal(SessionAttributesSDP); err != nil {
|
|
||||||
t.Errorf("error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
actual := sd.Marshal()
|
|
||||||
if actual != SessionAttributesSDP {
|
|
||||||
t.Errorf("error:\n\nEXPECTED:\n%v\nACTUAL:\n%v", SessionAttributesSDP, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnmarshalMediaName(t *testing.T) {
|
|
||||||
sd := &SessionDescription{}
|
|
||||||
if err := sd.Unmarshal(MediaNameSDP); err != nil {
|
|
||||||
t.Errorf("error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
actual := sd.Marshal()
|
|
||||||
if actual != MediaNameSDP {
|
|
||||||
t.Errorf("error:\n\nEXPECTED:\n%v\nACTUAL:\n%v", MediaNameSDP, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnmarshalMediaTitle(t *testing.T) {
|
|
||||||
sd := &SessionDescription{}
|
|
||||||
if err := sd.Unmarshal(MediaTitleSDP); err != nil {
|
|
||||||
t.Errorf("error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
actual := sd.Marshal()
|
|
||||||
if actual != MediaTitleSDP {
|
|
||||||
t.Errorf("error:\n\nEXPECTED:\n%v\nACTUAL:\n%v", MediaTitleSDP, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnmarshalMediaConnectionInformation(t *testing.T) {
|
|
||||||
sd := &SessionDescription{}
|
|
||||||
if err := sd.Unmarshal(MediaConnectionInformationSDP); err != nil {
|
|
||||||
t.Errorf("error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
actual := sd.Marshal()
|
|
||||||
if actual != MediaConnectionInformationSDP {
|
|
||||||
t.Errorf("error:\n\nEXPECTED:\n%v\nACTUAL:\n%v", MediaConnectionInformationSDP, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnmarshalMediaBandwidth(t *testing.T) {
|
|
||||||
sd := &SessionDescription{}
|
|
||||||
if err := sd.Unmarshal(MediaBandwidthSDP); err != nil {
|
|
||||||
t.Errorf("error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
actual := sd.Marshal()
|
|
||||||
if actual != MediaBandwidthSDP {
|
|
||||||
t.Errorf("error:\n\nEXPECTED:\n%v\nACTUAL:\n%v", MediaBandwidthSDP, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnmarshalMediaEncryptionKey(t *testing.T) {
|
|
||||||
sd := &SessionDescription{}
|
|
||||||
if err := sd.Unmarshal(MediaEncryptionKeySDP); err != nil {
|
|
||||||
t.Errorf("error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
actual := sd.Marshal()
|
|
||||||
if actual != MediaEncryptionKeySDP {
|
|
||||||
t.Errorf("error:\n\nEXPECTED:\n%v\nACTUAL:\n%v", MediaEncryptionKeySDP, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnmarshalMediaAttributes(t *testing.T) {
|
|
||||||
sd := &SessionDescription{}
|
|
||||||
if err := sd.Unmarshal(MediaAttributesSDP); err != nil {
|
|
||||||
t.Errorf("error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
actual := sd.Marshal()
|
|
||||||
if actual != MediaAttributesSDP {
|
|
||||||
t.Errorf("error:\n\nEXPECTED:\n%v\nACTUAL:\n%v", MediaAttributesSDP, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnmarshalCanonical(t *testing.T) {
|
|
||||||
sd := &SessionDescription{}
|
|
||||||
if err := sd.Unmarshal(CanonicalUnmarshalSDP); err != nil {
|
|
||||||
t.Errorf("error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
actual := sd.Marshal()
|
|
||||||
if actual != CanonicalUnmarshalSDP {
|
|
||||||
t.Errorf("error:\n\nEXPECTED:\n%v\nACTUAL:\n%v", CanonicalUnmarshalSDP, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,168 +0,0 @@
|
|||||||
package sdp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"math/rand"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ConnectionRole indicates which of the end points should initiate the connection establishment
|
|
||||||
type ConnectionRole int
|
|
||||||
|
|
||||||
const (
|
|
||||||
// ConnectionRoleActive indicates the endpoint will initiate an outgoing connection.
|
|
||||||
ConnectionRoleActive ConnectionRole = iota + 1
|
|
||||||
|
|
||||||
// ConnectionRolePassive indicates the endpoint will accept an incoming connection.
|
|
||||||
ConnectionRolePassive
|
|
||||||
|
|
||||||
// ConnectionRoleActpass indicates the endpoint is willing to accept an incoming connection or to initiate an outgoing connection.
|
|
||||||
ConnectionRoleActpass
|
|
||||||
|
|
||||||
// ConnectionRoleHoldconn indicates the endpoint does not want the connection to be established for the time being.
|
|
||||||
ConnectionRoleHoldconn
|
|
||||||
)
|
|
||||||
|
|
||||||
func (t ConnectionRole) String() string {
|
|
||||||
switch t {
|
|
||||||
case ConnectionRoleActive:
|
|
||||||
return "active"
|
|
||||||
case ConnectionRolePassive:
|
|
||||||
return "passive"
|
|
||||||
case ConnectionRoleActpass:
|
|
||||||
return "actpass"
|
|
||||||
case ConnectionRoleHoldconn:
|
|
||||||
return "holdconn"
|
|
||||||
default:
|
|
||||||
return "Unknown"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newSessionID() uint64 {
|
|
||||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
||||||
return uint64(r.Uint32()*2) >> 2
|
|
||||||
}
|
|
||||||
|
|
||||||
// Codec represents a codec
|
|
||||||
type Codec struct {
|
|
||||||
PayloadType uint8
|
|
||||||
Name string
|
|
||||||
ClockRate uint32
|
|
||||||
EncodingParameters string
|
|
||||||
Fmtp string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c Codec) String() string {
|
|
||||||
return fmt.Sprintf("%d %s/%d/%s", c.PayloadType, c.Name, c.ClockRate, c.EncodingParameters)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCodecForPayloadType scans the SessionDescription for the given payloadType and returns the codec
|
|
||||||
func (s *SessionDescription) GetCodecForPayloadType(payloadType uint8) (Codec, error) {
|
|
||||||
codec := Codec{
|
|
||||||
PayloadType: payloadType,
|
|
||||||
}
|
|
||||||
|
|
||||||
found := false
|
|
||||||
payloadTypeString := strconv.Itoa(int(payloadType))
|
|
||||||
rtpmapPrefix := "rtpmap:" + payloadTypeString
|
|
||||||
fmtpPrefix := "fmtp:" + payloadTypeString
|
|
||||||
|
|
||||||
for _, m := range s.MediaDescriptions {
|
|
||||||
for _, a := range m.Attributes {
|
|
||||||
if strings.HasPrefix(*a.String(), rtpmapPrefix) {
|
|
||||||
found = true
|
|
||||||
// a=rtpmap:<payload type> <encoding name>/<clock rate> [/<encoding parameters>]
|
|
||||||
split := strings.Split(*a.String(), " ")
|
|
||||||
if len(split) == 2 {
|
|
||||||
split = strings.Split(split[1], "/")
|
|
||||||
codec.Name = split[0]
|
|
||||||
parts := len(split)
|
|
||||||
if parts > 1 {
|
|
||||||
rate, err := strconv.Atoi(split[1])
|
|
||||||
if err != nil {
|
|
||||||
return codec, err
|
|
||||||
}
|
|
||||||
codec.ClockRate = uint32(rate)
|
|
||||||
}
|
|
||||||
if parts > 2 {
|
|
||||||
codec.EncodingParameters = split[2]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if strings.HasPrefix(*a.String(), fmtpPrefix) {
|
|
||||||
// a=fmtp:<format> <format specific parameters>
|
|
||||||
split := strings.Split(*a.String(), " ")
|
|
||||||
if len(split) == 2 {
|
|
||||||
codec.Fmtp = split[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if found {
|
|
||||||
return codec, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return codec, errors.New("payload type not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
type lexer struct {
|
|
||||||
desc *SessionDescription
|
|
||||||
input *bufio.Reader
|
|
||||||
}
|
|
||||||
|
|
||||||
type stateFn func(*lexer) (stateFn, error)
|
|
||||||
|
|
||||||
func readType(input *bufio.Reader) (string, error) {
|
|
||||||
key, err := input.ReadString('=')
|
|
||||||
if err != nil {
|
|
||||||
return key, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(key) != 2 {
|
|
||||||
return key, errors.Errorf("sdp: invalid syntax `%v`", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
return key, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readValue(input *bufio.Reader) (string, error) {
|
|
||||||
line, err := input.ReadString('\n')
|
|
||||||
if err != nil && err != io.EOF {
|
|
||||||
return line, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(line) == 0 {
|
|
||||||
return line, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if line[len(line)-1] == '\n' {
|
|
||||||
drop := 1
|
|
||||||
if len(line) > 1 && line[len(line)-2] == '\r' {
|
|
||||||
drop = 2
|
|
||||||
}
|
|
||||||
line = line[:len(line)-drop]
|
|
||||||
}
|
|
||||||
|
|
||||||
return line, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func indexOf(element string, data []string) int {
|
|
||||||
for k, v := range data {
|
|
||||||
if element == v {
|
|
||||||
return k
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
func keyValueBuild(key string, value *string) string {
|
|
||||||
if value != nil {
|
|
||||||
return key + *value + "\r\n"
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,7 @@ package webrtc
|
|||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/pions/webrtc/internal/sdp"
|
"github.com/pions/sdp"
|
||||||
"github.com/pions/webrtc/pkg/rtp"
|
"github.com/pions/webrtc/pkg/rtp"
|
||||||
"github.com/pions/webrtc/pkg/rtp/codecs"
|
"github.com/pions/webrtc/pkg/rtp/codecs"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
|
||||||
"github.com/pions/datachannel"
|
"github.com/pions/datachannel"
|
||||||
|
"github.com/pions/sdp"
|
||||||
"github.com/pions/webrtc/internal/network"
|
"github.com/pions/webrtc/internal/network"
|
||||||
"github.com/pions/webrtc/internal/sdp"
|
|
||||||
"github.com/pions/webrtc/pkg/ice"
|
"github.com/pions/webrtc/pkg/ice"
|
||||||
"github.com/pions/webrtc/pkg/media"
|
"github.com/pions/webrtc/pkg/media"
|
||||||
"github.com/pions/webrtc/pkg/rtcerr"
|
"github.com/pions/webrtc/pkg/rtcerr"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package webrtc
|
package webrtc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/pions/webrtc/internal/sdp"
|
"github.com/pions/sdp"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RTCSessionDescription is used to expose local and remote session descriptions.
|
// RTCSessionDescription is used to expose local and remote session descriptions.
|
||||||
|
|||||||
Reference in New Issue
Block a user