Skip to content

Commit 5edce95

Browse files
authored
Include sdpMid and sdpMLineIndex for ICECandidates returned by OneICECandidate (#2990)
#### Description Currently, Pion returns an empty `sdpMid` and a 0 `sdpMLineIndex`. This PR ensures Pion returns the corresponding `sdpMid` and `sdpMLineIndex` for ICE candidates for clients that expects it. Fixes trickle issues. #### Changes 1. `ICECandidates`: New fields `SDPMid` and `SDPMLineIndex`. 2. `ICEGatherer`: `SetMediaStreamIdentification` and return the correct `SDPMid` and `SDPMLineIndex`. 3. `extractICEDetails`: Return a struct instead of multiple values. 4. `extractICEDetails` refactored the media description selection to a different function. 5. Added new tests. #### Reference issue Fixes #2690 Fixes #1833
1 parent c50ca41 commit 5edce95

10 files changed

+567
-92
lines changed

Diff for: icecandidate.go

+18-16
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,17 @@ type ICECandidate struct {
2222
RelatedAddress string `json:"relatedAddress"`
2323
RelatedPort uint16 `json:"relatedPort"`
2424
TCPType string `json:"tcpType"`
25+
SDPMid string `json:"sdpMid"`
26+
SDPMLineIndex uint16 `json:"sdpMLineIndex"`
2527
}
2628

2729
// Conversion for package ice
2830

29-
func newICECandidatesFromICE(iceCandidates []ice.Candidate) ([]ICECandidate, error) {
31+
func newICECandidatesFromICE(iceCandidates []ice.Candidate, sdpMid string, sdpMLineIndex uint16) ([]ICECandidate, error) {
3032
candidates := []ICECandidate{}
3133

3234
for _, i := range iceCandidates {
33-
c, err := newICECandidateFromICE(i)
35+
c, err := newICECandidateFromICE(i, sdpMid, sdpMLineIndex)
3436
if err != nil {
3537
return nil, err
3638
}
@@ -40,7 +42,7 @@ func newICECandidatesFromICE(iceCandidates []ice.Candidate) ([]ICECandidate, err
4042
return candidates, nil
4143
}
4244

43-
func newICECandidateFromICE(i ice.Candidate) (ICECandidate, error) {
45+
func newICECandidateFromICE(i ice.Candidate, sdpMid string, sdpMLineIndex uint16) (ICECandidate, error) {
4446
typ, err := convertTypeFromICE(i.Type())
4547
if err != nil {
4648
return ICECandidate{}, err
@@ -51,15 +53,17 @@ func newICECandidateFromICE(i ice.Candidate) (ICECandidate, error) {
5153
}
5254

5355
c := ICECandidate{
54-
statsID: i.ID(),
55-
Foundation: i.Foundation(),
56-
Priority: i.Priority(),
57-
Address: i.Address(),
58-
Protocol: protocol,
59-
Port: uint16(i.Port()),
60-
Component: i.Component(),
61-
Typ: typ,
62-
TCPType: i.TCPType().String(),
56+
statsID: i.ID(),
57+
Foundation: i.Foundation(),
58+
Priority: i.Priority(),
59+
Address: i.Address(),
60+
Protocol: protocol,
61+
Port: uint16(i.Port()),
62+
Component: i.Component(),
63+
Typ: typ,
64+
TCPType: i.TCPType().String(),
65+
SDPMid: sdpMid,
66+
SDPMLineIndex: sdpMLineIndex,
6367
}
6468

6569
if i.RelatedAddress() != nil {
@@ -155,8 +159,6 @@ func (c ICECandidate) String() string {
155159
// ToJSON returns an ICECandidateInit
156160
// as indicated by the spec https://w3c.github.io/webrtc-pc/#dom-rtcicecandidate-tojson
157161
func (c ICECandidate) ToJSON() ICECandidateInit {
158-
zeroVal := uint16(0)
159-
emptyStr := ""
160162
candidateStr := ""
161163

162164
candidate, err := c.toICE()
@@ -166,7 +168,7 @@ func (c ICECandidate) ToJSON() ICECandidateInit {
166168

167169
return ICECandidateInit{
168170
Candidate: fmt.Sprintf("candidate:%s", candidateStr),
169-
SDPMid: &emptyStr,
170-
SDPMLineIndex: &zeroVal,
171+
SDPMid: &c.SDPMid,
172+
SDPMLineIndex: &c.SDPMLineIndex,
171173
}
172174
}

Diff for: icecandidate_test.go

+65
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,54 @@ func TestConvertTypeFromICE(t *testing.T) {
167167
})
168168
}
169169

170+
func TestNewIdentifiedICECandidateFromICE(t *testing.T) {
171+
config := ice.CandidateHostConfig{
172+
Network: "udp",
173+
Address: "::1",
174+
Port: 1234,
175+
Component: 1,
176+
Foundation: "foundation",
177+
Priority: 128,
178+
}
179+
ice, err := ice.NewCandidateHost(&config)
180+
assert.NoError(t, err)
181+
182+
ct, err := newICECandidateFromICE(ice, "1", 2)
183+
assert.NoError(t, err)
184+
185+
assert.Equal(t, "1", ct.SDPMid)
186+
assert.Equal(t, uint16(2), ct.SDPMLineIndex)
187+
}
188+
189+
func TestNewIdentifiedICECandidatesFromICE(t *testing.T) {
190+
ic, err := ice.NewCandidateHost(&ice.CandidateHostConfig{
191+
Network: "udp",
192+
Address: "::1",
193+
Port: 1234,
194+
Component: 1,
195+
Foundation: "foundation",
196+
Priority: 128,
197+
})
198+
199+
assert.NoError(t, err)
200+
201+
candidates := []ice.Candidate{ic, ic, ic}
202+
203+
sdpMid := "1"
204+
sdpMLineIndex := uint16(2)
205+
206+
results, err := newICECandidatesFromICE(candidates, sdpMid, sdpMLineIndex)
207+
208+
assert.NoError(t, err)
209+
210+
assert.Equal(t, 3, len(results))
211+
212+
for _, result := range results {
213+
assert.Equal(t, sdpMid, result.SDPMid)
214+
assert.Equal(t, sdpMLineIndex, result.SDPMLineIndex)
215+
}
216+
}
217+
170218
func TestICECandidate_ToJSON(t *testing.T) {
171219
candidate := ICECandidate{
172220
Foundation: "foundation",
@@ -183,3 +231,20 @@ func TestICECandidate_ToJSON(t *testing.T) {
183231
assert.Equal(t, uint16(0), *candidateInit.SDPMLineIndex)
184232
assert.Equal(t, "candidate:foundation 1 udp 128 1.0.0.1 1234 typ host", candidateInit.Candidate)
185233
}
234+
235+
func TestICECandidateZeroSDPid(t *testing.T) {
236+
candidate := ICECandidate{}
237+
238+
assert.Equal(t, candidate.SDPMid, "")
239+
assert.Equal(t, candidate.SDPMLineIndex, uint16(0))
240+
}
241+
242+
func TestICECandidateSDPMid_ToJSON(t *testing.T) {
243+
candidate := ICECandidate{}
244+
245+
candidate.SDPMid = "0"
246+
candidate.SDPMLineIndex = 1
247+
248+
assert.Equal(t, candidate.SDPMid, "0")
249+
assert.Equal(t, candidate.SDPMLineIndex, uint16(1))
250+
}

Diff for: icegatherer.go

+30-2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ type ICEGatherer struct {
3737
onGatheringCompleteHandler atomic.Value // func()
3838

3939
api *API
40+
41+
// Used to set the corresponding media stream identification tag and media description index
42+
// for ICE candidates generated by this gatherer.
43+
sdpMid atomic.Value // string
44+
sdpMLineIndex atomic.Uint32 // uint16
4045
}
4146

4247
// NewICEGatherer creates a new NewICEGatherer.
@@ -60,6 +65,8 @@ func (api *API) NewICEGatherer(opts ICEGatherOptions) (*ICEGatherer, error) {
6065
validatedServers: validatedServers,
6166
api: api,
6267
log: api.settingEngine.LoggerFactory.NewLogger("ice"),
68+
sdpMid: atomic.Value{},
69+
sdpMLineIndex: atomic.Uint32{},
6370
}, nil
6471
}
6572

@@ -169,8 +176,16 @@ func (g *ICEGatherer) Gather() error {
169176
onGatheringCompleteHandler = handler
170177
}
171178

179+
sdpMid := ""
180+
181+
if mid, ok := g.sdpMid.Load().(string); ok {
182+
sdpMid = mid
183+
}
184+
185+
sdpMLineIndex := uint16(g.sdpMLineIndex.Load())
186+
172187
if candidate != nil {
173-
c, err := newICECandidateFromICE(candidate)
188+
c, err := newICECandidateFromICE(candidate, sdpMid, sdpMLineIndex)
174189
if err != nil {
175190
g.log.Warnf("Failed to convert ice.Candidate: %s", err)
176191
return
@@ -188,6 +203,12 @@ func (g *ICEGatherer) Gather() error {
188203
return agent.GatherCandidates()
189204
}
190205

206+
// set media stream identification tag and media description index for this gatherer
207+
func (g *ICEGatherer) setMediaStreamIdentification(mid string, mLineIndex uint16) {
208+
g.sdpMid.Store(mid)
209+
g.sdpMLineIndex.Store(uint32(mLineIndex))
210+
}
211+
191212
// Close prunes all local candidates, and closes the ports.
192213
func (g *ICEGatherer) Close() error {
193214
return g.close(false /* shouldGracefullyClose */)
@@ -264,7 +285,14 @@ func (g *ICEGatherer) GetLocalCandidates() ([]ICECandidate, error) {
264285
return nil, err
265286
}
266287

267-
return newICECandidatesFromICE(iceCandidates)
288+
sdpMid := ""
289+
if mid, ok := g.sdpMid.Load().(string); ok {
290+
sdpMid = mid
291+
}
292+
293+
sdpMLineIndex := uint16(g.sdpMLineIndex.Load())
294+
295+
return newICECandidatesFromICE(iceCandidates, sdpMid, sdpMLineIndex)
268296
}
269297

270298
// OnLocalCandidate sets an event handler which fires when a new local ICE candidate is available

Diff for: icegatherer_test.go

+69
Original file line numberDiff line numberDiff line change
@@ -156,3 +156,72 @@ func TestICEGatherer_AlreadyClosed(t *testing.T) {
156156
assert.ErrorIs(t, err, errICEAgentNotExist)
157157
})
158158
}
159+
160+
func TestNewICEGathererSetMediaStreamIdentification(t *testing.T) {
161+
// Limit runtime in case of deadlocks
162+
lim := test.TimeOut(time.Second * 20)
163+
defer lim.Stop()
164+
165+
report := test.CheckRoutines(t)
166+
defer report()
167+
168+
opts := ICEGatherOptions{
169+
ICEServers: []ICEServer{{URLs: []string{"stun:stun.l.google.com:19302"}}},
170+
}
171+
172+
gatherer, err := NewAPI().NewICEGatherer(opts)
173+
if err != nil {
174+
t.Error(err)
175+
}
176+
177+
expectedMid := "5"
178+
expectedMLineIndex := uint16(1)
179+
180+
gatherer.setMediaStreamIdentification(expectedMid, expectedMLineIndex)
181+
182+
if gatherer.State() != ICEGathererStateNew {
183+
t.Fatalf("Expected gathering state new")
184+
}
185+
186+
gatherFinished := make(chan struct{})
187+
gatherer.OnLocalCandidate(func(i *ICECandidate) {
188+
if i == nil {
189+
close(gatherFinished)
190+
} else {
191+
assert.Equal(t, expectedMid, i.SDPMid)
192+
assert.Equal(t, expectedMLineIndex, i.SDPMLineIndex)
193+
}
194+
})
195+
196+
if err = gatherer.Gather(); err != nil {
197+
t.Error(err)
198+
}
199+
200+
<-gatherFinished
201+
202+
params, err := gatherer.GetLocalParameters()
203+
if err != nil {
204+
t.Error(err)
205+
}
206+
207+
if params.UsernameFragment == "" ||
208+
params.Password == "" {
209+
t.Fatalf("Empty local username or password frag")
210+
}
211+
212+
candidates, err := gatherer.GetLocalCandidates()
213+
if err != nil {
214+
t.Error(err)
215+
}
216+
217+
if len(candidates) == 0 {
218+
t.Fatalf("No candidates gathered")
219+
}
220+
221+
for _, c := range candidates {
222+
assert.Equal(t, expectedMid, c.SDPMid)
223+
assert.Equal(t, expectedMLineIndex, c.SDPMLineIndex)
224+
}
225+
226+
assert.NoError(t, gatherer.Close())
227+
}

Diff for: icetransport.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,12 @@ func (t *ICETransport) GetSelectedCandidatePair() (*ICECandidatePair, error) {
5757
return nil, err
5858
}
5959

60-
local, err := newICECandidateFromICE(icePair.Local)
60+
local, err := newICECandidateFromICE(icePair.Local, "", 0)
6161
if err != nil {
6262
return nil, err
6363
}
6464

65-
remote, err := newICECandidateFromICE(icePair.Remote)
65+
remote, err := newICECandidateFromICE(icePair.Remote, "", 0)
6666
if err != nil {
6767
return nil, err
6868
}
@@ -118,7 +118,7 @@ func (t *ICETransport) Start(gatherer *ICEGatherer, params ICEParameters, role *
118118
return err
119119
}
120120
if err := agent.OnSelectedCandidatePairChange(func(local, remote ice.Candidate) {
121-
candidates, err := newICECandidatesFromICE([]ice.Candidate{local, remote})
121+
candidates, err := newICECandidatesFromICE([]ice.Candidate{local, remote}, "", 0)
122122
if err != nil {
123123
t.log.Warnf("%w: %s", errICECandiatesCoversionFailed, err)
124124
return

Diff for: peerconnection.go

+12-7
Original file line numberDiff line numberDiff line change
@@ -1017,6 +1017,11 @@ func (pc *PeerConnection) SetLocalDescription(desc SessionDescription) error {
10171017
})
10181018
}
10191019

1020+
mediaSection, ok := selectCandidateMediaSection(desc.parsed)
1021+
if ok {
1022+
pc.iceGatherer.setMediaStreamIdentification(mediaSection.SDPMid, mediaSection.SDPMLineIndex)
1023+
}
1024+
10201025
if pc.iceGatherer.State() == ICEGathererStateNew {
10211026
return pc.iceGatherer.Gather()
10221027
}
@@ -1151,26 +1156,26 @@ func (pc *PeerConnection) SetRemoteDescription(desc SessionDescription) error {
11511156
}
11521157
}
11531158

1154-
remoteUfrag, remotePwd, candidates, err := extractICEDetails(desc.parsed, pc.log)
1159+
iceDetails, err := extractICEDetails(desc.parsed, pc.log)
11551160
if err != nil {
11561161
return err
11571162
}
11581163

1159-
if isRenegotiation && pc.iceTransport.haveRemoteCredentialsChange(remoteUfrag, remotePwd) {
1164+
if isRenegotiation && pc.iceTransport.haveRemoteCredentialsChange(iceDetails.Ufrag, iceDetails.Password) {
11601165
// An ICE Restart only happens implicitly for a SetRemoteDescription of type offer
11611166
if !weOffer {
11621167
if err = pc.iceTransport.restart(); err != nil {
11631168
return err
11641169
}
11651170
}
11661171

1167-
if err = pc.iceTransport.setRemoteCredentials(remoteUfrag, remotePwd); err != nil {
1172+
if err = pc.iceTransport.setRemoteCredentials(iceDetails.Ufrag, iceDetails.Password); err != nil {
11681173
return err
11691174
}
11701175
}
11711176

1172-
for i := range candidates {
1173-
if err = pc.iceTransport.AddRemoteCandidate(&candidates[i]); err != nil {
1177+
for i := range iceDetails.Candidates {
1178+
if err = pc.iceTransport.AddRemoteCandidate(&iceDetails.Candidates[i]); err != nil {
11741179
return err
11751180
}
11761181
}
@@ -1218,7 +1223,7 @@ func (pc *PeerConnection) SetRemoteDescription(desc SessionDescription) error {
12181223
}
12191224

12201225
pc.ops.Enqueue(func() {
1221-
pc.startTransports(iceRole, dtlsRoleFromRemoteSDP(desc.parsed), remoteUfrag, remotePwd, fingerprint, fingerprintHash)
1226+
pc.startTransports(iceRole, dtlsRoleFromRemoteSDP(desc.parsed), iceDetails.Ufrag, iceDetails.Password, fingerprint, fingerprintHash)
12221227
if weOffer {
12231228
pc.startRTP(false, &desc, currentTransceivers)
12241229
}
@@ -1806,7 +1811,7 @@ func (pc *PeerConnection) AddICECandidate(candidate ICECandidateInit) error {
18061811
return err
18071812
}
18081813

1809-
c, err := newICECandidateFromICE(candidate)
1814+
c, err := newICECandidateFromICE(candidate, "", 0)
18101815
if err != nil {
18111816
return err
18121817
}

0 commit comments

Comments
 (0)