Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error on conflicting codec values when parsing #199

Merged
merged 1 commit into from
Mar 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 57 additions & 22 deletions util.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,16 @@
)

var (
errExtractCodecRtpmap = errors.New("could not extract codec from rtpmap")
errExtractCodecFmtp = errors.New("could not extract codec from fmtp")
errExtractCodecRtcpFb = errors.New("could not extract codec from rtcp-fb")
errPayloadTypeNotFound = errors.New("payload type not found")
errCodecNotFound = errors.New("codec not found")
errSyntaxError = errors.New("SyntaxError")
errExtractCodecRtpmap = errors.New("could not extract codec from rtpmap")
errExtractCodecFmtp = errors.New("could not extract codec from fmtp")
errExtractCodecRtcpFb = errors.New("could not extract codec from rtcp-fb")
errMultipleName = errors.New("codec has multiple names defined")
errMultipleClockRate = errors.New("codec has multiple clock rates")
errMultipleEncodingParameters = errors.New("codec has multiple encoding parameters")
errMultipleFmtp = errors.New("codec has multiple fmtp values")
errPayloadTypeNotFound = errors.New("payload type not found")
errCodecNotFound = errors.New("codec not found")
errSyntaxError = errors.New("SyntaxError")
)

// ConnectionRole indicates which of the end points should initiate the connection establishment.
Expand Down Expand Up @@ -203,30 +207,49 @@
return codec, isWildcard, nil
}

func mergeCodecs(codec Codec, codecs map[uint8]Codec) {
func mergeCodecs(codec Codec, codecs map[uint8]Codec) error { // nolint: cyclop
savedCodec := codecs[codec.PayloadType]
savedCodec.PayloadType = codec.PayloadType

if codec.Name != "" {
if savedCodec.Name != "" && savedCodec.Name != codec.Name {
return errMultipleName
}

if savedCodec.PayloadType == 0 {
savedCodec.PayloadType = codec.PayloadType
}
if savedCodec.Name == "" {
savedCodec.Name = codec.Name
}
if savedCodec.ClockRate == 0 {

if codec.ClockRate != 0 {
if savedCodec.ClockRate != 0 && savedCodec.ClockRate != codec.ClockRate {
return errMultipleClockRate
}

savedCodec.ClockRate = codec.ClockRate
}
if savedCodec.EncodingParameters == "" {

if codec.EncodingParameters != "" {
if savedCodec.EncodingParameters != "" && savedCodec.EncodingParameters != codec.EncodingParameters {
return errMultipleEncodingParameters
}

savedCodec.EncodingParameters = codec.EncodingParameters
}
if savedCodec.Fmtp == "" {

if codec.Fmtp != "" {
if savedCodec.Fmtp != "" && savedCodec.Fmtp != codec.Fmtp {
return errMultipleFmtp
}

savedCodec.Fmtp = codec.Fmtp
}
savedCodec.RTCPFeedback = append(savedCodec.RTCPFeedback, codec.RTCPFeedback...)

savedCodec.RTCPFeedback = append(savedCodec.RTCPFeedback, codec.RTCPFeedback...)
codecs[savedCodec.PayloadType] = savedCodec

return nil
}

func (s *SessionDescription) buildCodecMap() map[uint8]Codec { //nolint:cyclop
func (s *SessionDescription) buildCodecMap() (map[uint8]Codec, error) { //nolint:cyclop, gocognit
codecs := map[uint8]Codec{
// static codecs that do not require a rtpmap
0: {
Expand All @@ -249,12 +272,16 @@
case strings.HasPrefix(attr, "rtpmap:"):
codec, err := parseRtpmap(attr)
if err == nil {
mergeCodecs(codec, codecs)
if err = mergeCodecs(codec, codecs); err != nil {
return nil, err
}
}
case strings.HasPrefix(attr, "fmtp:"):
codec, err := parseFmtp(attr)
if err == nil {
mergeCodecs(codec, codecs)
if err = mergeCodecs(codec, codecs); err != nil {
return nil, err
}
}
case strings.HasPrefix(attr, "rtcp-fb:"):
codec, isWildcard, err := parseRtcpFb(attr)
Expand All @@ -263,7 +290,9 @@
case isWildcard:
wildcardRTCPFeedback = append(wildcardRTCPFeedback, codec.RTCPFeedback...)
default:
mergeCodecs(codec, codecs)
if err = mergeCodecs(codec, codecs); err != nil {
return nil, err
}

Check warning on line 295 in util.go

View check run for this annotation

Codecov / codecov/patch

util.go#L294-L295

Added lines #L294 - L295 were not covered by tests
}
}
}
Expand All @@ -277,7 +306,7 @@
codecs[i] = codec
}

return codecs
return codecs, nil
}

func equivalentFmtp(want, got string) bool {
Expand Down Expand Up @@ -321,7 +350,10 @@

// GetCodecForPayloadType scans the SessionDescription for the given payload type and returns the codec.
func (s *SessionDescription) GetCodecForPayloadType(payloadType uint8) (Codec, error) {
codecs := s.buildCodecMap()
codecs, err := s.buildCodecMap()
if err != nil {
return Codec{}, err
}

Check warning on line 356 in util.go

View check run for this annotation

Codecov / codecov/patch

util.go#L355-L356

Added lines #L355 - L356 were not covered by tests

codec, ok := codecs[payloadType]
if ok {
Expand All @@ -334,7 +366,10 @@
// GetPayloadTypeForCodec scans the SessionDescription for a codec that matches the provided codec
// as closely as possible and returns its payload type.
func (s *SessionDescription) GetPayloadTypeForCodec(wanted Codec) (uint8, error) {
codecs := s.buildCodecMap()
codecs, err := s.buildCodecMap()
if err != nil {
return 0, err
}

for payloadType, codec := range codecs {
if codecsMatch(wanted, codec) {
Expand Down
45 changes: 44 additions & 1 deletion util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"errors"
"reflect"
"testing"

"github.com/stretchr/testify/require"
)

func getTestSessionDescription() SessionDescription {
Expand All @@ -19,7 +21,7 @@ func getTestSessionDescription() SessionDescription {
Value: 51372,
},
Protos: []string{"RTP", "AVP"},
Formats: []string{"120", "121", "126", "97", "98"},
Formats: []string{"120", "121", "126", "97", "98", "111"},
},
Attributes: []Attribute{
NewAttribute("fmtp:126 profile-level-id=42e01f;level-asymmetry-allowed=1;packetization-mode=1", ""),
Expand All @@ -32,6 +34,7 @@ func getTestSessionDescription() SessionDescription {
NewAttribute("rtpmap:126 H264/90000", ""),
NewAttribute("rtpmap:97 H264/90000", ""),
NewAttribute("rtpmap:98 H264/90000", ""),
NewAttribute("rtpmap:111 opus/48000/2", ""),
NewAttribute("rtcp-fb:97 ccm fir", ""),
NewAttribute("rtcp-fb:97 nack", ""),
NewAttribute("rtcp-fb:97 nack pli", ""),
Expand Down Expand Up @@ -241,3 +244,43 @@ func TestNewSessionID(t *testing.T) {
t.Error("Value around upper boundary was not generated")
}
}

func TestCodecMultipleValues(t *testing.T) {
for _, test := range []struct {
name string
attributeToAdd string
expectedError error
}{
{
"multiple name",
"rtpmap:120 VP9/90000",
errMultipleName,
},
{
"multiple clockrate",
"rtpmap:120 VP8/80000",
errMultipleClockRate,
},
{
"multiple encoding parameters",
"rtpmap:111 opus/48000/3",
errMultipleEncodingParameters,
},
{
"multiple fmtp",
"fmtp:126 multiple-fmtp",
errMultipleFmtp,
},
} {
t.Run(test.name, func(t *testing.T) {
sd := getTestSessionDescription()
sd.MediaDescriptions[0].Attributes = append(
sd.MediaDescriptions[0].Attributes,
NewPropertyAttribute(test.attributeToAdd),
)

_, err := sd.GetPayloadTypeForCodec(Codec{Name: "VP8/90000"})
require.ErrorIs(t, test.expectedError, err)
})
}
}
Loading