Skip to content

Commit dea14d6

Browse files
committed
Preserve ICE candidate Extensions
Ensure that ICE candidates are retained when parsed and stringified, and converted to ICE.candidate.
1 parent 7c3b128 commit dea14d6

File tree

2 files changed

+169
-5
lines changed

2 files changed

+169
-5
lines changed

icecandidate.go

+62-5
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ type ICECandidate struct {
2424
TCPType string `json:"tcpType"`
2525
SDPMid string `json:"sdpMid"`
2626
SDPMLineIndex uint16 `json:"sdpMLineIndex"`
27+
extensions string
2728
}
2829

2930
// Conversion for package ice.
@@ -69,6 +70,8 @@ func newICECandidateFromICE(candidate ice.Candidate, sdpMid string, sdpMLineInde
6970
SDPMLineIndex: sdpMLineIndex,
7071
}
7172

73+
newCandidate.setExtensions(candidate.Extensions())
74+
7275
if candidate.RelatedAddress() != nil {
7376
newCandidate.RelatedAddress = candidate.RelatedAddress().Address
7477
newCandidate.RelatedPort = uint16(candidate.RelatedAddress().Port) //nolint:gosec // G115
@@ -77,7 +80,7 @@ func newICECandidateFromICE(candidate ice.Candidate, sdpMid string, sdpMLineInde
7780
return newCandidate, nil
7881
}
7982

80-
func (c ICECandidate) toICE() (ice.Candidate, error) {
83+
func (c ICECandidate) toICE() (cand ice.Candidate, err error) {
8184
candidateID := c.statsID
8285
switch c.Typ {
8386
case ICECandidateTypeHost:
@@ -92,7 +95,7 @@ func (c ICECandidate) toICE() (ice.Candidate, error) {
9295
Priority: c.Priority,
9396
}
9497

95-
return ice.NewCandidateHost(&config)
98+
cand, err = ice.NewCandidateHost(&config)
9699
case ICECandidateTypeSrflx:
97100
config := ice.CandidateServerReflexiveConfig{
98101
CandidateID: candidateID,
@@ -106,7 +109,7 @@ func (c ICECandidate) toICE() (ice.Candidate, error) {
106109
RelPort: int(c.RelatedPort),
107110
}
108111

109-
return ice.NewCandidateServerReflexive(&config)
112+
cand, err = ice.NewCandidateServerReflexive(&config)
110113
case ICECandidateTypePrflx:
111114
config := ice.CandidatePeerReflexiveConfig{
112115
CandidateID: candidateID,
@@ -120,7 +123,7 @@ func (c ICECandidate) toICE() (ice.Candidate, error) {
120123
RelPort: int(c.RelatedPort),
121124
}
122125

123-
return ice.NewCandidatePeerReflexive(&config)
126+
cand, err = ice.NewCandidatePeerReflexive(&config)
124127
case ICECandidateTypeRelay:
125128
config := ice.CandidateRelayConfig{
126129
CandidateID: candidateID,
@@ -134,10 +137,64 @@ func (c ICECandidate) toICE() (ice.Candidate, error) {
134137
RelPort: int(c.RelatedPort),
135138
}
136139

137-
return ice.NewCandidateRelay(&config)
140+
cand, err = ice.NewCandidateRelay(&config)
138141
default:
139142
return nil, fmt.Errorf("%w: %s", errICECandidateTypeUnknown, c.Typ)
140143
}
144+
145+
if cand != nil && err == nil {
146+
err = c.exportExtensions(cand)
147+
}
148+
149+
return cand, err
150+
}
151+
152+
func (c *ICECandidate) setExtensions(ext []ice.CandidateExtension) {
153+
var extensions string
154+
155+
for i := range ext {
156+
if i > 0 {
157+
extensions += " "
158+
}
159+
160+
extensions += ext[i].Key + " " + ext[i].Value
161+
}
162+
163+
c.extensions = extensions
164+
}
165+
166+
func (c *ICECandidate) exportExtensions(cand ice.Candidate) error {
167+
extensions := c.extensions
168+
var ext ice.CandidateExtension
169+
var field string
170+
171+
for i, start := 0, 0; i < len(extensions); i++ {
172+
switch {
173+
case extensions[i] == ' ':
174+
field = extensions[start:i]
175+
start = i + 1
176+
case i == len(extensions)-1:
177+
field = extensions[start:]
178+
default:
179+
continue
180+
}
181+
182+
// Extension keys can't be empty
183+
hasKey := ext.Key != ""
184+
if !hasKey {
185+
ext.Key = field
186+
} else {
187+
ext.Value = field
188+
}
189+
190+
// Extension value can be empty
191+
if hasKey || i == len(extensions)-1 {
192+
cand.AddExtension(ext)
193+
ext = ice.CandidateExtension{}
194+
}
195+
}
196+
197+
return nil
141198
}
142199

143200
func convertTypeFromICE(t ice.CandidateType) (ICECandidateType, error) {

icecandidate_test.go

+107
Original file line numberDiff line numberDiff line change
@@ -248,3 +248,110 @@ func TestICECandidateSDPMid_ToJSON(t *testing.T) {
248248
assert.Equal(t, candidate.SDPMid, "0")
249249
assert.Equal(t, candidate.SDPMLineIndex, uint16(1))
250250
}
251+
252+
func TestICECandidateExtensions_ToJSON(t *testing.T) {
253+
candidates := []struct {
254+
candidate string
255+
extensions []ice.CandidateExtension
256+
}{
257+
{
258+
"2637185494 1 udp 2121932543 192.168.1.4 50723 typ host generation 1 ufrag Jzd0 network-id 1",
259+
[]ice.CandidateExtension{
260+
{
261+
Key: "generation",
262+
Value: "1",
263+
},
264+
{
265+
Key: "ufrag",
266+
Value: "Jzd0",
267+
},
268+
{
269+
Key: "network-id",
270+
Value: "1",
271+
},
272+
},
273+
},
274+
{
275+
"1052353102 1 tcp 2128609279 192.168.0.196 0 typ host tcptype active ufrag Jzd0 network-id 1",
276+
[]ice.CandidateExtension{
277+
{
278+
Key: "tcptype",
279+
Value: "active",
280+
},
281+
{
282+
Key: "ufrag",
283+
Value: "Jzd0",
284+
},
285+
{
286+
Key: "network-id",
287+
Value: "1",
288+
},
289+
},
290+
},
291+
{
292+
"1052353102 1 tcp 2128609279 192.168.0.196 0 typ host tcptype active ufrag Jzd0 network-id 1 empty-ext ",
293+
[]ice.CandidateExtension{
294+
{
295+
Key: "tcptype",
296+
Value: "active",
297+
},
298+
{
299+
Key: "ufrag",
300+
Value: "Jzd0",
301+
},
302+
{
303+
Key: "network-id",
304+
Value: "1",
305+
},
306+
{
307+
Key: "empty-ext",
308+
Value: "",
309+
},
310+
},
311+
},
312+
{
313+
"1052353102 1 tcp 2128609279 192.168.0.196 0 typ host tcptype active ufrag Jzd0 empty-ext network-id 1",
314+
[]ice.CandidateExtension{
315+
{
316+
Key: "tcptype",
317+
Value: "active",
318+
},
319+
{
320+
Key: "ufrag",
321+
Value: "Jzd0",
322+
},
323+
{
324+
Key: "empty-ext",
325+
Value: "",
326+
},
327+
{
328+
Key: "network-id",
329+
Value: "1",
330+
},
331+
},
332+
},
333+
}
334+
335+
for _, cand := range candidates {
336+
cand := cand
337+
candidate, err := ice.UnmarshalCandidate(cand.candidate)
338+
assert.NoError(t, err)
339+
340+
sdpMid := "1"
341+
sdpMLineIndex := uint16(2)
342+
343+
iceCandidate, err := newICECandidateFromICE(candidate, sdpMid, sdpMLineIndex)
344+
assert.NoError(t, err)
345+
346+
candidateInit := iceCandidate.ToJSON()
347+
348+
assert.Equal(t, sdpMLineIndex, *candidateInit.SDPMLineIndex)
349+
assert.Equal(t, "candidate:"+cand.candidate, candidateInit.Candidate)
350+
351+
iceBack, err := iceCandidate.toICE()
352+
353+
assert.NoError(t, err)
354+
355+
assert.Equal(t, cand.extensions, iceBack.Extensions())
356+
}
357+
}

0 commit comments

Comments
 (0)