Skip to content

Commit

Permalink
SDP implementation is complete
Browse files Browse the repository at this point in the history
Support marshaling and unmarshaling session and
media descriptions
  • Loading branch information
Sean-Der committed May 29, 2018
1 parent a9220db commit b2e8189
Show file tree
Hide file tree
Showing 3 changed files with 252 additions and 188 deletions.
217 changes: 35 additions & 182 deletions internal/sdp/marshal.go
Original file line number Diff line number Diff line change
@@ -1,55 +1,12 @@
package sdp

import (
"bufio"
"strconv"
"strings"
import "strconv"

"github.com/pkg/errors"
)

type attributeStatus struct {
seen bool
value string
allowMultiple bool
func kvBuilder(key, value string) string {
return key + "=" + value + "\n"
}

// Detect if the current attribute is ok to be read (detect out of order errors)
// or if it has already been set
func attributeValid(statuses []*attributeStatus, attribute string) (err error) {
attrFound := false
for _, v := range statuses {
if attrFound && v.seen {
return errors.Errorf("Attribute %s was found, but later attribute %s has already been set", attribute, v.value)
}

if v.value == attribute {
if v.seen && !v.allowMultiple {
return errors.Errorf("Attribute %s was attempted to be set twice", attribute, v.value)
}
attrFound = true
v.seen = true
}
}
return nil
}

func nextLine(scanner *bufio.Scanner) (key, value string, scanStatus bool, err error) {
if scanStatus = scanner.Scan(); !scanStatus {
return key, value, scanStatus, scanner.Err()
}

if len(scanner.Text()) < 3 {
return key, value, scanStatus, errors.Errorf("line is not long enough to contain both a key and value: %s", scanner.Text())
} else if scanner.Text()[1] != '=' {
return key, value, scanStatus, errors.Errorf("line is not a proper key value pair, second character is not `=`: %s", scanner.Text())
}

return string(scanner.Text()[0]), scanner.Text()[2:], scanStatus, err
}

// Marshaw populates a SessionDescription with a raw string
//
// Marshal creates a raw string from a SessionDescription
// Some lines in each description are REQUIRED and some are OPTIONAL,
// but all MUST appear in exactly the order given here (the fixed order
// greatly enhances error detection and allows for a simple parser).
Expand All @@ -70,149 +27,45 @@ func nextLine(scanner *bufio.Scanner) (key, value string, scanStatus bool, err e
// a=* (zero or more session attribute lines)
// Zero or more media descriptions
// https://tools.ietf.org/html/rfc4566#section-5
func (s *SessionDescription) Marshal(raw string) error {
earlyEndErr := errors.Errorf("session description ended before all required values were found")
orderedSessionAttributes := []*attributeStatus{
&attributeStatus{value: "v"},
&attributeStatus{value: "o"},
&attributeStatus{value: "s"},
&attributeStatus{value: "i"},
&attributeStatus{value: "u"},
&attributeStatus{value: "e"},
&attributeStatus{value: "p"},
&attributeStatus{value: "c"},
&attributeStatus{value: "b", allowMultiple: true},
&attributeStatus{value: "t", allowMultiple: true},
&attributeStatus{value: "r", allowMultiple: true},
&attributeStatus{value: "z", allowMultiple: true},
&attributeStatus{value: "k", allowMultiple: true},
&attributeStatus{value: "a", allowMultiple: true},
&attributeStatus{value: "m", allowMultiple: true},
}

s.Reset()
scanner := bufio.NewScanner(strings.NewReader(raw))

// v=
key, value, scanStatus, err := nextLine(scanner)
if err != nil {
return err
} else if !scanStatus {
return earlyEndErr
} else if key != "v" {
return errors.Errorf("v (protocol version) was expected, but not found")
} else if s.ProtocolVersion, err = strconv.Atoi(value); err != nil {
return errors.Errorf("Failed to take protocol version to int")
}

// o=
key, value, scanStatus, err = nextLine(scanner)
if err != nil {
return err
} else if !scanStatus {
return earlyEndErr
} else if key != "o" {
return errors.Errorf("o (originator and session identifier) was expected, but not found")
}
s.Origin = value

key, value, scanStatus, err = nextLine(scanner)
if err != nil {
return err
} else if !scanStatus {
return earlyEndErr
} else if key != "s" {
return errors.Errorf("o (session name) was expected, but not found")
func (s *SessionDescription) Marshal() (raw string) {
addIfSet := func(key, value string) {
if value != "" {
raw += kvBuilder(key, value)
}
}
s.SessionName = value

const attrAlreadySet string = "attribute %s has already been set"
for {
key, value, scanStatus, err = nextLine(scanner)
if err != nil || !scanStatus {
return err
addSlice := func(key string, values []string) {
for _, v := range values {
raw += kvBuilder(key, v)
}
}

if err := attributeValid(orderedSessionAttributes, key); err != nil {
return err
}
raw += kvBuilder("v", strconv.Itoa(s.ProtocolVersion))
raw += kvBuilder("o", s.Origin)
raw += kvBuilder("s", s.SessionName)

switch key {
case "i":
s.SessionInformation = value
case "u":
s.URI = value
case "e":
s.EmailAddress = value
case "p":
s.PhoneNumber = value
case "c":
s.ConnectionData = value
case "b":
s.Bandwidth = append(s.Bandwidth, value)
case "t":
s.Timing = append(s.Timing, value)
case "r":
s.RepeatTimes = append(s.RepeatTimes, value)
case "z":
s.TimeZones = append(s.TimeZones, value)
case "k":
s.EncryptionKeys = append(s.EncryptionKeys, value)
case "a":
s.Attributes = append(s.Attributes, value)
case "m":
return s.marshalMedias(scanner, value)
default:
return errors.Errorf("Invalid session attribute: %s")
}
}
}
addIfSet("i", s.SessionInformation)
addIfSet("u", s.URI)
addIfSet("e", s.EmailAddress)
addIfSet("p", s.PhoneNumber)
addIfSet("c", s.ConnectionData)

func (s *SessionDescription) marshalMedias(scanner *bufio.Scanner, firstMediaName string) (err error) {
currentMedia := &MediaDescription{MediaName: firstMediaName}
orderedMediaAttributes := []*attributeStatus{
&attributeStatus{value: "i"},
&attributeStatus{value: "c"},
&attributeStatus{value: "b", allowMultiple: true},
&attributeStatus{value: "k", allowMultiple: true},
&attributeStatus{value: "a", allowMultiple: true},
}
resetMediaAttributes := func() {
for _, v := range orderedMediaAttributes {
v.seen = false
}
}
addSlice("b", s.Bandwidth)
addSlice("t", s.Timing)
addSlice("r", s.RepeatTimes)
addSlice("z", s.TimeZones)
addSlice("k", s.EncryptionKeys)
addSlice("a", s.Attributes)

for {
key, value, scanStatus, err := nextLine(scanner)
if err != nil || !scanStatus { // This handles EOF, finish current MediaDescription
s.MediaDescriptions = append(s.MediaDescriptions, currentMedia)
return err
}
for _, a := range s.MediaDescriptions {
raw += kvBuilder("m", a.MediaName)

if err := attributeValid(orderedMediaAttributes, key); err != nil {
return err
}
addIfSet("i", a.MediaInformation)
addIfSet("c", a.ConnectionData)

switch key {
case "m":
s.MediaDescriptions = append(s.MediaDescriptions, currentMedia)
resetMediaAttributes()
currentMedia = &MediaDescription{MediaName: value}
case "i":
currentMedia.MediaInformation = value
case "c":
currentMedia.ConnectionData = value
case "b":
currentMedia.Bandwidth = append(currentMedia.Bandwidth, value)
case "k":
currentMedia.EncryptionKeys = append(currentMedia.EncryptionKeys, value)
case "a":
currentMedia.Attributes = append(currentMedia.Attributes, value)
default:
return errors.Errorf("Invalid media attribute: %s")
}
addSlice("b", a.Bandwidth)
addSlice("k", a.EncryptionKeys)
addSlice("a", a.Attributes)
}

return nil
return raw
}

0 comments on commit b2e8189

Please sign in to comment.