Skip to content

Commit

Permalink
Add methods to add and remove extensions
Browse files Browse the repository at this point in the history
Added `AddExtension` and `RemoveExtension` methods to `ICECandidate`,
allowing extensions to be managed dynamically
  • Loading branch information
JoeTurki committed Jan 30, 2025
1 parent cad1676 commit ed8c6f4
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 4 deletions.
8 changes: 7 additions & 1 deletion candidate.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,18 @@ type Candidate interface {
// https://datatracker.ietf.org/doc/html/rfc5245#section-15.1
//.
Extensions() []CandidateExtension

// GetExtension returns the value of the extension attribute associated with the ICECandidate.
// Extension attributes are defined in RFC 5245, Section 15.1:
// https://datatracker.ietf.org/doc/html/rfc5245#section-15.1
//.
GetExtension(key string) (value CandidateExtension, ok bool)
// AddExtension adds an extension attribute to the ICECandidate.
// If an extension with the same key already exists, it will be overwritten.
// Extension attributes are defined in RFC 5245, Section 15.1:
AddExtension(extension CandidateExtension) error
// RemoveExtension removes an extension attribute from the ICECandidate.
// Extension attributes are defined in RFC 5245, Section 15.1:
RemoveExtension(key string) (ok bool)

String() string
Type() CandidateType
Expand Down
47 changes: 44 additions & 3 deletions candidate_base.go
Original file line number Diff line number Diff line change
Expand Up @@ -543,8 +543,8 @@ func (c *candidateBase) Marshal() string {
// as defined in https://tools.ietf.org/html/rfc5245#section-15.1
// .
type CandidateExtension struct {
Key string
Value string
Key string `json:"key"`
Value string `json:"value"`
}

func (c *candidateBase) Extensions() []CandidateExtension {
Expand Down Expand Up @@ -576,7 +576,7 @@ func (c *candidateBase) GetExtension(key string) (CandidateExtension, bool) {
}

// TCPType was manually set.
if key == "tcptype" && c.TCPType() != TCPTypeUnspecified {
if key == "tcptype" && c.TCPType() != TCPTypeUnspecified { //nolint:goconst
extension.Value = c.TCPType().String()

return extension, true
Expand All @@ -585,6 +585,47 @@ func (c *candidateBase) GetExtension(key string) (CandidateExtension, bool) {
return extension, false
}

func (c *candidateBase) AddExtension(ext CandidateExtension) error {
if ext.Key == "tcptype" {
tcpType := NewTCPType(ext.Value)
if tcpType == TCPTypeUnspecified {
return fmt.Errorf("%w: invalid or unsupported TCPtype %s", errParseTCPType, ext.Value)
}

c.tcpType = tcpType
}

if ext.Key == "" {
return fmt.Errorf("%w: key is empty", errParseExtension)
}

// per spec, Extensions aren't explicitly unique, we only set the first one.
// If the exteion is set multiple times.
for i := range c.extensions {
if c.extensions[i].Key == ext.Key {
c.extensions[i] = ext

return nil
}
}

c.extensions = append(c.extensions, ext)

return nil
}

func (c *candidateBase) RemoveExtension(key string) bool {
for i := range c.extensions {
if c.extensions[i].Key == key {
c.extensions = append(c.extensions[:i], c.extensions[i+1:]...)

return true
}
}

return false
}

// marshalExtensions returns the string representation of the candidate extensions.
func (c *candidateBase) marshalExtensions() string {
value := ""
Expand Down
84 changes: 84 additions & 0 deletions candidate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1271,3 +1271,87 @@ func TestBaseCandidateExtensionsEqual(t *testing.T) {
})
}
}

func TestCandidateAddExtension(t *testing.T) {
t.Run("Add extension", func(t *testing.T) {
candidate, err := NewCandidateHost(&CandidateHostConfig{
Network: NetworkTypeUDP4.String(),
Address: "fcd9:e3b8:12ce:9fc5:74a5:c6bb:d8b:e08a",
Port: 53987,
Priority: 500,
Foundation: "750",
})
if err != nil {
t.Error(err)
}

require.NoError(t, candidate.AddExtension(CandidateExtension{"a", "b"}))
require.NoError(t, candidate.AddExtension(CandidateExtension{"c", "d"}))

extensions := candidate.Extensions()
require.Equal(t, []CandidateExtension{{"a", "b"}, {"c", "d"}}, extensions)
})

t.Run("Add extension with existing key", func(t *testing.T) {
candidate, err := NewCandidateHost(&CandidateHostConfig{
Network: NetworkTypeUDP4.String(),
Address: "fcd9:e3b8:12ce:9fc5:74a5:c6bb:d8b:e08a",
Port: 53987,
Priority: 500,
Foundation: "750",
})
if err != nil {
t.Error(err)
}

require.NoError(t, candidate.AddExtension(CandidateExtension{"a", "b"}))
require.NoError(t, candidate.AddExtension(CandidateExtension{"a", "d"}))

extensions := candidate.Extensions()
require.Equal(t, []CandidateExtension{{"a", "d"}}, extensions)
})
}

func TestCandidateRemoveExtension(t *testing.T) {
t.Run("Remove extension", func(t *testing.T) {
candidate, err := NewCandidateHost(&CandidateHostConfig{
Network: NetworkTypeUDP4.String(),
Address: "fcd9:e3b8:12ce:9fc5:74a5:c6bb:d8b:e08a",
Port: 53987,
Priority: 500,
Foundation: "750",
})
if err != nil {
t.Error(err)
}

require.NoError(t, candidate.AddExtension(CandidateExtension{"a", "b"}))
require.NoError(t, candidate.AddExtension(CandidateExtension{"c", "d"}))

require.True(t, candidate.RemoveExtension("a"))

extensions := candidate.Extensions()
require.Equal(t, []CandidateExtension{{"c", "d"}}, extensions)
})

t.Run("Remove extension that does not exist", func(t *testing.T) {
candidate, err := NewCandidateHost(&CandidateHostConfig{
Network: NetworkTypeUDP4.String(),
Address: "fcd9:e3b8:12ce:9fc5:74a5:c6bb:d8b:e08a",
Port: 53987,
Priority: 500,
Foundation: "750",
})
if err != nil {
t.Error(err)
}

require.NoError(t, candidate.AddExtension(CandidateExtension{"a", "b"}))
require.NoError(t, candidate.AddExtension(CandidateExtension{"c", "d"}))

require.False(t, candidate.RemoveExtension("b"))

extensions := candidate.Extensions()
require.Equal(t, []CandidateExtension{{"a", "b"}, {"c", "d"}}, extensions)
})
}

0 comments on commit ed8c6f4

Please sign in to comment.