Skip to content

Commit

Permalink
Preserve ICE candidate Extensions
Browse files Browse the repository at this point in the history
Ensure that ICE candidates are retained when parsed and stringified,
and converted to ICE.candidate.
  • Loading branch information
JoeTurki committed Jan 31, 2025
1 parent 7c3b128 commit a0d7d02
Show file tree
Hide file tree
Showing 2 changed files with 171 additions and 5 deletions.
70 changes: 65 additions & 5 deletions icecandidate.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type ICECandidate struct {
TCPType string `json:"tcpType"`
SDPMid string `json:"sdpMid"`
SDPMLineIndex uint16 `json:"sdpMLineIndex"`
extensions string
}

// Conversion for package ice.
Expand Down Expand Up @@ -69,6 +70,8 @@ func newICECandidateFromICE(candidate ice.Candidate, sdpMid string, sdpMLineInde
SDPMLineIndex: sdpMLineIndex,
}

newCandidate.setExtensions(candidate.Extensions())

if candidate.RelatedAddress() != nil {
newCandidate.RelatedAddress = candidate.RelatedAddress().Address
newCandidate.RelatedPort = uint16(candidate.RelatedAddress().Port) //nolint:gosec // G115
Expand All @@ -77,7 +80,7 @@ func newICECandidateFromICE(candidate ice.Candidate, sdpMid string, sdpMLineInde
return newCandidate, nil
}

func (c ICECandidate) toICE() (ice.Candidate, error) {
func (c ICECandidate) toICE() (cand ice.Candidate, err error) {
candidateID := c.statsID
switch c.Typ {
case ICECandidateTypeHost:
Expand All @@ -92,7 +95,7 @@ func (c ICECandidate) toICE() (ice.Candidate, error) {
Priority: c.Priority,
}

return ice.NewCandidateHost(&config)
cand, err = ice.NewCandidateHost(&config)
case ICECandidateTypeSrflx:
config := ice.CandidateServerReflexiveConfig{
CandidateID: candidateID,
Expand All @@ -106,7 +109,7 @@ func (c ICECandidate) toICE() (ice.Candidate, error) {
RelPort: int(c.RelatedPort),
}

return ice.NewCandidateServerReflexive(&config)
cand, err = ice.NewCandidateServerReflexive(&config)
case ICECandidateTypePrflx:
config := ice.CandidatePeerReflexiveConfig{
CandidateID: candidateID,
Expand All @@ -120,7 +123,7 @@ func (c ICECandidate) toICE() (ice.Candidate, error) {
RelPort: int(c.RelatedPort),
}

return ice.NewCandidatePeerReflexive(&config)
cand, err = ice.NewCandidatePeerReflexive(&config)
case ICECandidateTypeRelay:
config := ice.CandidateRelayConfig{
CandidateID: candidateID,
Expand All @@ -134,10 +137,67 @@ func (c ICECandidate) toICE() (ice.Candidate, error) {
RelPort: int(c.RelatedPort),
}

return ice.NewCandidateRelay(&config)
cand, err = ice.NewCandidateRelay(&config)

Check warning on line 140 in icecandidate.go

View check run for this annotation

Codecov / codecov/patch

icecandidate.go#L140

Added line #L140 was not covered by tests
default:
return nil, fmt.Errorf("%w: %s", errICECandidateTypeUnknown, c.Typ)
}

if cand != nil && err == nil {
err = c.exportExtensions(cand)
}

return cand, err
}

func (c *ICECandidate) setExtensions(ext []ice.CandidateExtension) {
var extensions string

for i := range ext {
if i > 0 {
extensions += " "
}

extensions += ext[i].Key + " " + ext[i].Value
}

c.extensions = extensions
}

func (c *ICECandidate) exportExtensions(cand ice.Candidate) error {
extensions := c.extensions
var ext ice.CandidateExtension
var field string

for i, start := 0, 0; i < len(extensions); i++ {
switch {
case extensions[i] == ' ':
field = extensions[start:i]
start = i + 1
case i == len(extensions)-1:
field = extensions[start:]
default:
continue
}

// Extension keys can't be empty
hasKey := ext.Key != ""
if !hasKey {
ext.Key = field
} else {
ext.Value = field
}

// Extension value can be empty
if hasKey || i == len(extensions)-1 {
if err := cand.AddExtension(ext); err != nil {
return err
}

Check warning on line 194 in icecandidate.go

View check run for this annotation

Codecov / codecov/patch

icecandidate.go#L193-L194

Added lines #L193 - L194 were not covered by tests

ext = ice.CandidateExtension{}
}
}

return nil
}

func convertTypeFromICE(t ice.CandidateType) (ICECandidateType, error) {
Expand Down
106 changes: 106 additions & 0 deletions icecandidate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,3 +248,109 @@ func TestICECandidateSDPMid_ToJSON(t *testing.T) {
assert.Equal(t, candidate.SDPMid, "0")
assert.Equal(t, candidate.SDPMLineIndex, uint16(1))
}

func TestICECandidateExtensions_ToJSON(t *testing.T) {
candidates := []struct {
candidate string
extensions []ice.CandidateExtension
}{
{
"2637185494 1 udp 2121932543 192.168.1.4 50723 typ host generation 1 ufrag Jzd0 network-id 1",
[]ice.CandidateExtension{
{
Key: "generation",
Value: "1",
},
{
Key: "ufrag",
Value: "Jzd0",
},
{
Key: "network-id",
Value: "1",
},
},
},
{
"1052353102 1 tcp 2128609279 192.168.0.196 0 typ host tcptype active ufrag Jzd0 network-id 1",
[]ice.CandidateExtension{
{
Key: "tcptype",
Value: "active",
},
{
Key: "ufrag",
Value: "Jzd0",
},
{
Key: "network-id",
Value: "1",
},
},
},
{
"1052353102 1 tcp 2128609279 192.168.0.196 0 typ host tcptype active ufrag Jzd0 network-id 1 empty-ext ",
[]ice.CandidateExtension{
{
Key: "tcptype",
Value: "active",
},
{
Key: "ufrag",
Value: "Jzd0",
},
{
Key: "network-id",
Value: "1",
},
{
Key: "empty-ext",
Value: "",
},
},
},
{
"1052353102 1 tcp 2128609279 192.168.0.196 0 typ host tcptype active ufrag Jzd0 empty-ext network-id 1",
[]ice.CandidateExtension{
{
Key: "tcptype",
Value: "active",
},
{
Key: "ufrag",
Value: "Jzd0",
},
{
Key: "empty-ext",
Value: "",
},
{
Key: "network-id",
Value: "1",
},
},
},
}

for _, cand := range candidates {
cand := cand
candidate, err := ice.UnmarshalCandidate(cand.candidate)
assert.NoError(t, err)

sdpMid := "1"
sdpMLineIndex := uint16(2)

iceCandidate, err := newICECandidateFromICE(candidate, sdpMid, sdpMLineIndex)
assert.NoError(t, err)

candidateInit := iceCandidate.ToJSON()

assert.Equal(t, sdpMLineIndex, *candidateInit.SDPMLineIndex)
assert.Equal(t, "candidate:"+cand.candidate, candidateInit.Candidate)

iceBack, err := iceCandidate.toICE()

assert.NoError(t, err)
assert.Equal(t, cand.extensions, iceBack.Extensions())
}
}

0 comments on commit a0d7d02

Please sign in to comment.