From b3109ce383bc6d2a5726bd4e07e75d0d6772613b Mon Sep 17 00:00:00 2001 From: Joe Turki Date: Sat, 4 Jan 2025 07:20:21 -0600 Subject: [PATCH] Add sdpMid and sdpMLineIndex to ICECandidates --- icecandidate.go | 36 +++++++++++-- icecandidate_test.go | 65 ++++++++++++++++++++++++ icegatherer.go | 32 +++++++++++- icegatherer_test.go | 69 +++++++++++++++++++++++++ peerconnection.go | 15 +++--- peerconnection_go_test.go | 103 ++++++++++++++++++++++++++++++++++++++ sdp.go | 62 ++++++++++++++--------- sdp_test.go | 51 +++++++++++-------- 8 files changed, 375 insertions(+), 58 deletions(-) diff --git a/icecandidate.go b/icecandidate.go index c36d520543a..ab84ec1f158 100644 --- a/icecandidate.go +++ b/icecandidate.go @@ -22,6 +22,8 @@ type ICECandidate struct { RelatedAddress string `json:"relatedAddress"` RelatedPort uint16 `json:"relatedPort"` TCPType string `json:"tcpType"` + SDPMid string `json:"sdpMid"` + SDPMLineIndex uint16 `json:"sdpMLineIndex"` } // Conversion for package ice @@ -70,6 +72,34 @@ func newICECandidateFromICE(i ice.Candidate) (ICECandidate, error) { return c, nil } +// newIdentifiedICECandidateFromICE creates a new ICECandidate with the specified media stream identification tag and media description index. +func newIdentifiedICECandidateFromICE(candidate ice.Candidate, sdpMid string, sdpMLineIndex uint16) (ICECandidate, error) { + c, err := newICECandidateFromICE(candidate) + if err != nil { + return ICECandidate{}, err + } + + c.SDPMid = sdpMid + c.SDPMLineIndex = sdpMLineIndex + + return c, nil +} + +// newICECandidatesFromICE creates ICECandidates from ice.Candidates with the specified media stream identification tag and media description index. +func newIdentifiedICECandidatesFromICE(iceCandidates []ice.Candidate, sdpMid string, sdpMLineIndex uint16) ([]ICECandidate, error) { + candidates := []ICECandidate{} + + for _, i := range iceCandidates { + c, err := newIdentifiedICECandidateFromICE(i, sdpMid, sdpMLineIndex) + if err != nil { + return nil, err + } + candidates = append(candidates, c) + } + + return candidates, nil +} + func (c ICECandidate) toICE() (ice.Candidate, error) { candidateID := c.statsID switch c.Typ { @@ -155,8 +185,6 @@ func (c ICECandidate) String() string { // ToJSON returns an ICECandidateInit // as indicated by the spec https://w3c.github.io/webrtc-pc/#dom-rtcicecandidate-tojson func (c ICECandidate) ToJSON() ICECandidateInit { - zeroVal := uint16(0) - emptyStr := "" candidateStr := "" candidate, err := c.toICE() @@ -166,7 +194,7 @@ func (c ICECandidate) ToJSON() ICECandidateInit { return ICECandidateInit{ Candidate: fmt.Sprintf("candidate:%s", candidateStr), - SDPMid: &emptyStr, - SDPMLineIndex: &zeroVal, + SDPMid: &c.SDPMid, + SDPMLineIndex: &c.SDPMLineIndex, } } diff --git a/icecandidate_test.go b/icecandidate_test.go index 8b3fa132254..40777325775 100644 --- a/icecandidate_test.go +++ b/icecandidate_test.go @@ -167,6 +167,54 @@ func TestConvertTypeFromICE(t *testing.T) { }) } +func TestNewIdentifiedICECandidateFromICE(t *testing.T) { + config := ice.CandidateHostConfig{ + Network: "udp", + Address: "::1", + Port: 1234, + Component: 1, + Foundation: "foundation", + Priority: 128, + } + ice, err := ice.NewCandidateHost(&config) + assert.NoError(t, err) + + ct, err := newIdentifiedICECandidateFromICE(ice, "1", 2) + assert.NoError(t, err) + + assert.Equal(t, "1", ct.SDPMid) + assert.Equal(t, uint16(2), ct.SDPMLineIndex) +} + +func TestNewIdentifiedICECandidatesFromICE(t *testing.T) { + ic, err := ice.NewCandidateHost(&ice.CandidateHostConfig{ + Network: "udp", + Address: "::1", + Port: 1234, + Component: 1, + Foundation: "foundation", + Priority: 128, + }) + + assert.NoError(t, err) + + candidates := []ice.Candidate{ic, ic, ic} + + sdpMid := "1" + sdpMLineIndex := uint16(2) + + results, err := newIdentifiedICECandidatesFromICE(candidates, sdpMid, sdpMLineIndex) + + assert.NoError(t, err) + + assert.Equal(t, 3, len(results)) + + for _, result := range results { + assert.Equal(t, sdpMid, result.SDPMid) + assert.Equal(t, sdpMLineIndex, result.SDPMLineIndex) + } +} + func TestICECandidate_ToJSON(t *testing.T) { candidate := ICECandidate{ Foundation: "foundation", @@ -183,3 +231,20 @@ func TestICECandidate_ToJSON(t *testing.T) { assert.Equal(t, uint16(0), *candidateInit.SDPMLineIndex) assert.Equal(t, "candidate:foundation 1 udp 128 1.0.0.1 1234 typ host", candidateInit.Candidate) } + +func TestICECandidateZeroSDPid(t *testing.T) { + candidate := ICECandidate{} + + assert.Equal(t, candidate.SDPMid, "") + assert.Equal(t, candidate.SDPMLineIndex, uint16(0)) +} + +func TestICECandidateSDPMid_ToJSON(t *testing.T) { + candidate := ICECandidate{} + + candidate.SDPMid = "0" + candidate.SDPMLineIndex = 1 + + assert.Equal(t, candidate.SDPMid, "0") + assert.Equal(t, candidate.SDPMLineIndex, uint16(1)) +} diff --git a/icegatherer.go b/icegatherer.go index b15c72b545d..161cb7f06ed 100644 --- a/icegatherer.go +++ b/icegatherer.go @@ -37,6 +37,11 @@ type ICEGatherer struct { onGatheringCompleteHandler atomic.Value // func() api *API + + // Used to set the corresponding media stream identification tag and media description index + // for ICE candidates generated by this gatherer. + sdpMid atomic.Value // string + sdpMLineIndex atomic.Uint32 // uint16 } // NewICEGatherer creates a new NewICEGatherer. @@ -60,6 +65,8 @@ func (api *API) NewICEGatherer(opts ICEGatherOptions) (*ICEGatherer, error) { validatedServers: validatedServers, api: api, log: api.settingEngine.LoggerFactory.NewLogger("ice"), + sdpMid: atomic.Value{}, + sdpMLineIndex: atomic.Uint32{}, }, nil } @@ -169,8 +176,16 @@ func (g *ICEGatherer) Gather() error { onGatheringCompleteHandler = handler } + sdpMid := "" + + if mid, ok := g.sdpMid.Load().(string); ok { + sdpMid = mid + } + + sdpMLineIndex := uint16(g.sdpMLineIndex.Load()) + if candidate != nil { - c, err := newICECandidateFromICE(candidate) + c, err := newIdentifiedICECandidateFromICE(candidate, sdpMid, sdpMLineIndex) if err != nil { g.log.Warnf("Failed to convert ice.Candidate: %s", err) return @@ -188,6 +203,12 @@ func (g *ICEGatherer) Gather() error { return agent.GatherCandidates() } +// set media stream identification tag and media description index for this gatherer +func (g *ICEGatherer) setMediaStreamIdentification(mid string, mLineIndex uint16) { + g.sdpMid.Store(mid) + g.sdpMLineIndex.Store(uint32(mLineIndex)) +} + // Close prunes all local candidates, and closes the ports. func (g *ICEGatherer) Close() error { return g.close(false /* shouldGracefullyClose */) @@ -264,7 +285,14 @@ func (g *ICEGatherer) GetLocalCandidates() ([]ICECandidate, error) { return nil, err } - return newICECandidatesFromICE(iceCandidates) + sdpMid := "" + if mid, ok := g.sdpMid.Load().(string); ok { + sdpMid = mid + } + + sdpMLineIndex := uint16(g.sdpMLineIndex.Load()) + + return newIdentifiedICECandidatesFromICE(iceCandidates, sdpMid, sdpMLineIndex) } // OnLocalCandidate sets an event handler which fires when a new local ICE candidate is available diff --git a/icegatherer_test.go b/icegatherer_test.go index 6327824286c..6f7bd8db553 100644 --- a/icegatherer_test.go +++ b/icegatherer_test.go @@ -156,3 +156,72 @@ func TestICEGatherer_AlreadyClosed(t *testing.T) { assert.ErrorIs(t, err, errICEAgentNotExist) }) } + +func TestNewICEGathererSetMediaStreamIdentification(t *testing.T) { + // Limit runtime in case of deadlocks + lim := test.TimeOut(time.Second * 20) + defer lim.Stop() + + report := test.CheckRoutines(t) + defer report() + + opts := ICEGatherOptions{ + ICEServers: []ICEServer{{URLs: []string{"stun:stun.l.google.com:19302"}}}, + } + + gatherer, err := NewAPI().NewICEGatherer(opts) + if err != nil { + t.Error(err) + } + + expectedMid := "5" + expectedMLineIndex := uint16(1) + + gatherer.setMediaStreamIdentification(expectedMid, expectedMLineIndex) + + if gatherer.State() != ICEGathererStateNew { + t.Fatalf("Expected gathering state new") + } + + gatherFinished := make(chan struct{}) + gatherer.OnLocalCandidate(func(i *ICECandidate) { + if i == nil { + close(gatherFinished) + } else { + assert.Equal(t, expectedMid, i.SDPMid) + assert.Equal(t, expectedMLineIndex, i.SDPMLineIndex) + } + }) + + if err = gatherer.Gather(); err != nil { + t.Error(err) + } + + <-gatherFinished + + params, err := gatherer.GetLocalParameters() + if err != nil { + t.Error(err) + } + + if params.UsernameFragment == "" || + params.Password == "" { + t.Fatalf("Empty local username or password frag") + } + + candidates, err := gatherer.GetLocalCandidates() + if err != nil { + t.Error(err) + } + + if len(candidates) == 0 { + t.Fatalf("No candidates gathered") + } + + for _, c := range candidates { + assert.Equal(t, expectedMid, c.SDPMid) + assert.Equal(t, expectedMLineIndex, c.SDPMLineIndex) + } + + assert.NoError(t, gatherer.Close()) +} diff --git a/peerconnection.go b/peerconnection.go index 6c518013fd0..5cd38f167e9 100644 --- a/peerconnection.go +++ b/peerconnection.go @@ -1151,12 +1151,15 @@ func (pc *PeerConnection) SetRemoteDescription(desc SessionDescription) error { } } - remoteUfrag, remotePwd, candidates, err := extractICEDetails(desc.parsed, pc.log) + iceDetails, err := extractICEDetails(desc.parsed, pc.log) if err != nil { return err } - if isRenegotiation && pc.iceTransport.haveRemoteCredentialsChange(remoteUfrag, remotePwd) { + // This is just so it outputs the correct mid and mLineIndex for the ice candidates + pc.iceGatherer.setMediaStreamIdentification(iceDetails.SDPMid, iceDetails.SDPMLineIndex) + + if isRenegotiation && pc.iceTransport.haveRemoteCredentialsChange(iceDetails.Ufrag, iceDetails.Password) { // An ICE Restart only happens implicitly for a SetRemoteDescription of type offer if !weOffer { if err = pc.iceTransport.restart(); err != nil { @@ -1164,13 +1167,13 @@ func (pc *PeerConnection) SetRemoteDescription(desc SessionDescription) error { } } - if err = pc.iceTransport.setRemoteCredentials(remoteUfrag, remotePwd); err != nil { + if err = pc.iceTransport.setRemoteCredentials(iceDetails.Ufrag, iceDetails.Password); err != nil { return err } } - for i := range candidates { - if err = pc.iceTransport.AddRemoteCandidate(&candidates[i]); err != nil { + for i := range iceDetails.Candidates { + if err = pc.iceTransport.AddRemoteCandidate(&iceDetails.Candidates[i]); err != nil { return err } } @@ -1218,7 +1221,7 @@ func (pc *PeerConnection) SetRemoteDescription(desc SessionDescription) error { } pc.ops.Enqueue(func() { - pc.startTransports(iceRole, dtlsRoleFromRemoteSDP(desc.parsed), remoteUfrag, remotePwd, fingerprint, fingerprintHash) + pc.startTransports(iceRole, dtlsRoleFromRemoteSDP(desc.parsed), iceDetails.Ufrag, iceDetails.Password, fingerprint, fingerprintHash) if weOffer { pc.startRTP(false, &desc, currentTransceivers) } diff --git a/peerconnection_go_test.go b/peerconnection_go_test.go index 3de9acf5f2a..02315846600 100644 --- a/peerconnection_go_test.go +++ b/peerconnection_go_test.go @@ -1665,3 +1665,106 @@ func TestPeerConnectionNoNULLCipherDefault(t *testing.T) { <-peerConnectionClosed closePairNow(t, offerPC, answerPC) } + +// https://github.com/pion/webrtc/issues/2690 +func TestPeerConnectionTrickleMediaStreamIdentification(t *testing.T) { + const remoteSdp = `v=0 +o=- 1735985477255306 1 IN IP4 127.0.0.1 +s=VideoRoom 1234 +t=0 0 +a=group:BUNDLE 0 1 +a=ice-options:trickle +a=fingerprint:sha-256 61:BF:17:29:C0:EF:B2:77:75:79:64:F9:D8:D0:03:6C:5A:D3:9A:BC:E5:F4:5A:05:4C:3C:3B:A0:B4:2B:CF:A8 +a=extmap-allow-mixed +a=msid-semantic: WMS * +m=audio 9 UDP/TLS/RTP/SAVPF 111 +c=IN IP4 127.0.0.1 +a=sendonly +a=mid:0 +a=rtcp-mux +a=ice-ufrag:xv3r +a=ice-pwd:NT22yM6JeOsahq00U9ZJS/ +a=ice-options:trickle +a=setup:actpass +a=rtpmap:111 opus/48000/2 +a=rtcp-fb:111 transport-cc +a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level +a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid +a=fmtp:111 useinbandfec=1 +a=msid:janus janus0 +a=ssrc:2280306597 cname:janus +m=video 9 UDP/TLS/RTP/SAVPF 96 97 +c=IN IP4 127.0.0.1 +a=sendonly +a=mid:1 +a=rtcp-mux +a=ice-ufrag:xv3r +a=ice-pwd:NT22yM6JeOsahq00U9ZJS/ +a=ice-options:trickle +a=setup:actpass +a=rtpmap:96 VP8/90000 +a=rtcp-fb:96 ccm fir +a=rtcp-fb:96 nack +a=rtcp-fb:96 nack pli +a=rtcp-fb:96 goog-remb +a=rtcp-fb:96 transport-cc +a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time +a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01 +a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid +a=extmap:12 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay +a=extmap:13 urn:3gpp:video-orientation +a=rtpmap:97 rtx/90000 +a=fmtp:97 apt=96 +a=ssrc-group:FID 4099488402 29586368 +a=msid:janus janus1 +a=ssrc:4099488402 cname:janus +a=ssrc:29586368 cname:janus +` + + mediaEngine := &MediaEngine{} + + assert.NoError(t, mediaEngine.RegisterCodec(RTPCodecParameters{ + RTPCodecCapability: RTPCodecCapability{MimeType: MimeTypeVP8, ClockRate: 90000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil}, + PayloadType: 96, + }, RTPCodecTypeVideo)) + assert.NoError(t, mediaEngine.RegisterCodec(RTPCodecParameters{ + RTPCodecCapability: RTPCodecCapability{MimeType: MimeTypeOpus, ClockRate: 48000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil}, + PayloadType: 111, + }, RTPCodecTypeAudio)) + + api := NewAPI(WithMediaEngine(mediaEngine)) + pc, err := api.NewPeerConnection(Configuration{ + ICEServers: []ICEServer{ + { + URLs: []string{"stun:stun.l.google.com:19302"}, + }, + }, + }) + assert.NoError(t, err) + + pc.OnICECandidate(func(candidate *ICECandidate) { + if candidate == nil { + return + } + + assert.NotEmpty(t, candidate.SDPMid) + + assert.Contains(t, []string{"0", "1"}, candidate.SDPMid) + assert.Contains(t, []uint16{0, 1}, candidate.SDPMLineIndex) + }) + + assert.NoError(t, pc.SetRemoteDescription(SessionDescription{ + Type: SDPTypeOffer, + SDP: remoteSdp, + })) + + gatherComplete := GatheringCompletePromise(pc) + ans, _ := pc.CreateAnswer(nil) + assert.NoError(t, pc.SetLocalDescription(ans)) + + <-gatherComplete + + assert.NoError(t, pc.Close()) + + assert.Equal(t, PeerConnectionStateClosed, pc.ConnectionState()) +} diff --git a/sdp.go b/sdp.go index 10a24edc405..cf2e92d304a 100644 --- a/sdp.go +++ b/sdp.go @@ -816,59 +816,71 @@ func extractICEDetailsFromMedia(media *sdp.MediaDescription, log logging.Leveled return remoteUfrag, remotePwd, candidates, nil } -func extractICEDetails(desc *sdp.SessionDescription, log logging.LeveledLogger) (string, string, []ICECandidate, error) { // nolint:gocognit - remoteCandidates := []ICECandidate{} - remotePwd := "" - remoteUfrag := "" +type sdpICEDetails struct { + Ufrag string + Password string + Candidates []ICECandidate + SDPMid string + SDPMLineIndex uint16 +} + +func extractICEDetails(desc *sdp.SessionDescription, log logging.LeveledLogger) (*sdpICEDetails, error) { // nolint:gocognit + details := &sdpICEDetails{ + Candidates: []ICECandidate{}, + } // Ufrag and Pw are allow at session level and thus have highest prio if ufrag, haveUfrag := desc.Attribute("ice-ufrag"); haveUfrag { - remoteUfrag = ufrag + details.Ufrag = ufrag } if pwd, havePwd := desc.Attribute("ice-pwd"); havePwd { - remotePwd = pwd + details.Password = pwd } bundleID := extractBundleID(desc) missing := true - for _, m := range desc.MediaDescriptions { - mid := getMidValue(m) + for mLineIndex, mediaDescr := range desc.MediaDescriptions { + mid := getMidValue(mediaDescr) // If bundled, only take ICE detail from bundle master section if bundleID != "" { if mid == bundleID { - ufrag, pwd, candidates, err := extractICEDetailsFromMedia(m, log) + ufrag, pwd, candidates, err := extractICEDetailsFromMedia(mediaDescr, log) if err != nil { - return "", "", nil, err + return nil, err } - if remoteUfrag == "" && ufrag != "" { - remoteUfrag = ufrag - remotePwd = pwd + if details.Ufrag == "" && ufrag != "" { + details.Ufrag = ufrag + details.Password = pwd } - remoteCandidates = candidates + details.Candidates = candidates + details.SDPMid = mid + details.SDPMLineIndex = uint16(mLineIndex) } } else if missing { // For not-bundled, take ICE details from the first media section - ufrag, pwd, candidates, err := extractICEDetailsFromMedia(m, log) + ufrag, pwd, candidates, err := extractICEDetailsFromMedia(mediaDescr, log) if err != nil { - return "", "", nil, err + return nil, err } - if remoteUfrag == "" && ufrag != "" { - remoteUfrag = ufrag - remotePwd = pwd + if details.Ufrag == "" && ufrag != "" { + details.Ufrag = ufrag + details.Password = pwd } - remoteCandidates = candidates + details.Candidates = candidates + details.SDPMid = mid + details.SDPMLineIndex = uint16(mLineIndex) missing = false } } - if remoteUfrag == "" { - return "", "", nil, ErrSessionDescriptionMissingIceUfrag - } else if remotePwd == "" { - return "", "", nil, ErrSessionDescriptionMissingIcePwd + if details.Ufrag == "" { + return nil, ErrSessionDescriptionMissingIceUfrag + } else if details.Password == "" { + return nil, ErrSessionDescriptionMissingIcePwd } - return remoteUfrag, remotePwd, remoteCandidates, nil + return details, nil } func haveApplicationMediaSection(desc *sdp.SessionDescription) bool { diff --git a/sdp_test.go b/sdp_test.go index 46a3b8de504..f5a9e288a4f 100644 --- a/sdp_test.go +++ b/sdp_test.go @@ -130,7 +130,7 @@ func TestExtractICEDetails(t *testing.T) { }, } - _, _, _, err := extractICEDetails(s, nil) + _, err := extractICEDetails(s, nil) assert.Equal(t, err, ErrSessionDescriptionMissingIcePwd) }) @@ -141,7 +141,7 @@ func TestExtractICEDetails(t *testing.T) { }, } - _, _, _, err := extractICEDetails(s, nil) + _, err := extractICEDetails(s, nil) assert.Equal(t, err, ErrSessionDescriptionMissingIceUfrag) }) @@ -154,10 +154,12 @@ func TestExtractICEDetails(t *testing.T) { MediaDescriptions: []*sdp.MediaDescription{}, } - ufrag, pwd, _, err := extractICEDetails(s, nil) - assert.Equal(t, ufrag, defaultUfrag) - assert.Equal(t, pwd, defaultPwd) + details, err := extractICEDetails(s, nil) assert.NoError(t, err) + assert.Equal(t, details.Ufrag, defaultUfrag) + assert.Equal(t, details.Password, defaultPwd) + assert.Equal(t, details.SDPMLineIndex, uint16(0)) + assert.Equal(t, details.SDPMid, "") }) t.Run("ice details at media level", func(t *testing.T) { @@ -172,14 +174,14 @@ func TestExtractICEDetails(t *testing.T) { }, } - ufrag, pwd, _, err := extractICEDetails(s, nil) - assert.Equal(t, ufrag, defaultUfrag) - assert.Equal(t, pwd, defaultPwd) + details, err := extractICEDetails(s, nil) assert.NoError(t, err) + assert.Equal(t, details.Ufrag, defaultUfrag) + assert.Equal(t, details.Password, defaultPwd) }) t.Run("ice details at session preferred over media", func(t *testing.T) { - s := &sdp.SessionDescription{ + descr := &sdp.SessionDescription{ Attributes: []sdp.Attribute{ {Key: "ice-ufrag", Value: defaultUfrag}, {Key: "ice-pwd", Value: defaultPwd}, @@ -194,14 +196,16 @@ func TestExtractICEDetails(t *testing.T) { }, } - ufrag, pwd, _, err := extractICEDetails(s, nil) - assert.Equal(t, ufrag, defaultUfrag) - assert.Equal(t, pwd, defaultPwd) + details, err := extractICEDetails(descr, nil) assert.NoError(t, err) + assert.Equal(t, details.Ufrag, defaultUfrag) + assert.Equal(t, details.Password, defaultPwd) + assert.Equal(t, details.SDPMLineIndex, uint16(0)) + assert.Equal(t, details.SDPMid, "") }) t.Run("ice details from bundle media section", func(t *testing.T) { - s := &sdp.SessionDescription{ + descr := &sdp.SessionDescription{ Attributes: []sdp.Attribute{ {Key: "group", Value: "BUNDLE 5 2"}, }, @@ -223,19 +227,22 @@ func TestExtractICEDetails(t *testing.T) { }, } - ufrag, pwd, _, err := extractICEDetails(s, nil) - assert.Equal(t, ufrag, defaultUfrag) - assert.Equal(t, pwd, defaultPwd) + details, err := extractICEDetails(descr, nil) assert.NoError(t, err) + assert.Equal(t, details.Ufrag, defaultUfrag) + assert.Equal(t, details.Password, defaultPwd) + assert.Equal(t, details.SDPMLineIndex, uint16(1)) + assert.Equal(t, details.SDPMid, "5") }) t.Run("ice details from first media section", func(t *testing.T) { - s := &sdp.SessionDescription{ + descr := &sdp.SessionDescription{ MediaDescriptions: []*sdp.MediaDescription{ { Attributes: []sdp.Attribute{ {Key: "ice-ufrag", Value: defaultUfrag}, {Key: "ice-pwd", Value: defaultPwd}, + {Key: "mid", Value: "5"}, }, }, { @@ -247,10 +254,12 @@ func TestExtractICEDetails(t *testing.T) { }, } - ufrag, pwd, _, err := extractICEDetails(s, nil) - assert.Equal(t, ufrag, defaultUfrag) - assert.Equal(t, pwd, defaultPwd) + details, err := extractICEDetails(descr, nil) assert.NoError(t, err) + assert.Equal(t, details.Ufrag, defaultUfrag) + assert.Equal(t, details.Password, defaultPwd) + assert.Equal(t, details.SDPMLineIndex, uint16(0)) + assert.Equal(t, details.SDPMid, "5") }) t.Run("Missing pwd at session level", func(t *testing.T) { @@ -261,7 +270,7 @@ func TestExtractICEDetails(t *testing.T) { }, } - _, _, _, err := extractICEDetails(s, nil) + _, err := extractICEDetails(s, nil) assert.Equal(t, err, ErrSessionDescriptionMissingIcePwd) }) }