Skip to content

Commit

Permalink
Use a slice instead
Browse files Browse the repository at this point in the history
  • Loading branch information
joeturki committed Jan 15, 2025
1 parent 6528f3e commit 2b64677
Show file tree
Hide file tree
Showing 5 changed files with 502 additions and 492 deletions.
9 changes: 8 additions & 1 deletion candidate.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,14 @@ type Candidate interface {
// In the order of insertion, *(key value).
// Extension attributes are defined in RFC 5245, Section 15.1:
// https://datatracker.ietf.org/doc/html/rfc5245#section-15.1
Extensions() CandidateExtensions
//.
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 string, ok bool)

String() string
Type() CandidateType
Expand Down
170 changes: 149 additions & 21 deletions candidate_base.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ type candidateBase struct {

remoteCandidateCaches map[AddrPort]Candidate
isLocationTracked bool
extensions *CandidateExtensions
extensions []CandidateExtension
}

// Done implements context.Context
Expand Down Expand Up @@ -418,7 +418,7 @@ func (c *candidateBase) Equal(other Candidate) bool {

// DeepEqual is same as Equal but also compares the extensions
func (c *candidateBase) DeepEqual(other Candidate) bool {
return c.Equal(other) && c.Extensions().Equal(other.Extensions())
return c.Equal(other) && c.extensionsEqual(other.Extensions())
}

// String makes the candidateBase printable
Expand Down Expand Up @@ -510,7 +510,7 @@ func (c *candidateBase) Marshal() string {
r.Port)
}

extensions := c.Extensions().Marshal()
extensions := c.marshalExtensions()

if extensions != "" {
val = fmt.Sprintf("%s %s", val, extensions)
Expand All @@ -519,27 +519,94 @@ func (c *candidateBase) Marshal() string {
return val
}

func (c *candidateBase) Extensions() CandidateExtensions {
if c.extensions == nil {
ext := CandidateExtensions{}
// CandidateExtension represents a single candidate extension
// as defined in https://tools.ietf.org/html/rfc5245#section-15.1
// .
type CandidateExtension struct {
key string
value string
}

func (c *candidateBase) Extensions() []CandidateExtension {
// IF Extensions were not parsed using UnmarshalCandidate
// For backwards compatibility when the TCPType is set manually
if len(c.extensions) == 0 && c.TCPType() != TCPTypeUnspecified {
return []CandidateExtension{{
key: "tcptype",
value: c.TCPType().String(),
}}
}

extensions := make([]CandidateExtension, len(c.extensions))
copy(extensions, c.extensions)

return extensions
}

// Get returns the value of the given key if it exists.
func (c candidateBase) GetExtension(key string) (string, bool) {
for i := range c.extensions {
if c.extensions[i].key == key {
return c.extensions[i].value, true
}
}

// TCPType was manually set.
if key == "tcptype" && c.TCPType() != TCPTypeUnspecified {
return c.TCPType().String(), true
}

// Extensions were not parsed using UnmarshalCandidate
// For backwards compatibility we set TCPType value
return "", false
}

// marshalExtensions returns the string representation of the candidate extensions.
func (c candidateBase) marshalExtensions() string {
value := ""
exts := c.Extensions()

if c.TCPType() != TCPTypeUnspecified {
ext.extensions = append(ext.extensions, CandidateExtension{
key: "tcptype",
value: c.TCPType().String(),
})
for i := range exts {
if value != "" {
value += " "
}

return ext
value += exts[i].key + " " + exts[i].value
}

return *c.extensions
return value
}

func (c *candidateBase) setExtensions(extensions *CandidateExtensions) {
// Equal returns true if the candidate extensions are equal.
func (c candidateBase) extensionsEqual(other []CandidateExtension) bool {
freq1 := make(map[CandidateExtension]int)
freq2 := make(map[CandidateExtension]int)

if len(c.extensions) != len(other) {
return false
}

if len(c.extensions) == 0 {
return true
}

if len(c.extensions) == 1 {
return c.extensions[0] == other[0]
}

for i := range c.extensions {
freq1[c.extensions[i]]++
freq2[other[i]]++
}

for k, v := range freq1 {
if freq2[k] != v {
return false
}
}

return true
}

func (c *candidateBase) setExtensions(extensions []CandidateExtension) {
c.extensions = extensions
}

Expand Down Expand Up @@ -627,17 +694,16 @@ func UnmarshalCandidate(raw string) (Candidate, error) {
}

tcpType := TCPTypeUnspecified
var extensions *CandidateExtensions
var extensions []CandidateExtension
var tcpTypeRaw string

if pos < len(raw) {
extensions, err = UnmarshalCandidateExtensions(raw[pos:])
extensions, tcpTypeRaw, err = unmarshalCandidateExtensions(raw[pos:])
if err != nil {
return nil, fmt.Errorf("%w: %v", errParseExtension, err) //nolint:errorlint // we wrap the error
}

tcpTypeRaw, ok := extensions.Get("tcptype")

if ok {
if tcpTypeRaw != "" {
tcpType = NewTCPType(tcpTypeRaw)
if tcpType == TCPTypeUnspecified {
return nil, fmt.Errorf("%w: invalid or unsupported TCPtype %s", errParseTCPType, tcpTypeRaw)
Expand Down Expand Up @@ -804,6 +870,26 @@ func readCandidatePort(raw string, start int) (int, int, error) {
return port, pos, nil
}

// Read a byte-string token from the raw string
// As defined in RFC 4566 1*(%x01-09/%x0B-0C/%x0E-FF) ;any byte except NUL, CR, or LF
// we imply that extensions byte-string are UTF-8 encoded
func readCandidateByteString(raw string, start int) (string, int, error) {
for i, char := range raw[start:] {
if char == 0x20 { // SP
return raw[start : start+i], start + i + 1, nil
}

// 1*(%x01-09/%x0B-0C/%x0E-FF)
if !(char >= 0x01 && char <= 0x09 ||
char >= 0x0B && char <= 0x0C ||
char >= 0x0E && char <= 0xFF) {
return "", 0, fmt.Errorf("invalid byte-string character: %c", char) //nolint: err113 // handled by caller
}
}

return raw[start:], len(raw), nil
}

// Read and validate raddr and rport from the raw string
// [SP rel-addr] [SP rel-port]
// defined in https://datatracker.ietf.org/doc/html/rfc5245#section-15.1
Expand Down Expand Up @@ -841,3 +927,45 @@ func tryReadRelativeAddrs(raw string, start int) (raddr string, rport, pos int,

return raddr, rport, pos, nil
}

// UnmarshalCandidateExtensions parses the candidate extensions from the raw string.
// *(SP extension-att-name SP extension-att-value)
// Where extension-att-name, and extension-att-value are byte-strings
// as defined in https://tools.ietf.org/html/rfc5245#section-15.1
func unmarshalCandidateExtensions(raw string) (extensions []CandidateExtension, rawTCPTypeRaw string, err error) {
extensions = make([]CandidateExtension, 0)

if raw == "" {
return extensions, "", nil
}

if raw[0] == 0x20 { // SP
return extensions, "", fmt.Errorf("%w: unexpected space %s", errParseExtension, raw)
}

for i := 0; i < len(raw); {
key, next, err := readCandidateByteString(raw, i)
if err != nil {
return extensions, "", fmt.Errorf("%w: failed to read key %v", errParseExtension, err) //nolint: errorlint // we wrap the error
}
i = next

if i >= len(raw) {
return extensions, "", fmt.Errorf("%w: missing value for %s in %s", errParseExtension, key, raw)
}

value, next, err := readCandidateByteString(raw, i)
if err != nil {
return extensions, "", fmt.Errorf("%w: failed to read value %v", errParseExtension, err) //nolint: errorlint // we are wrapping the error
}
i = next

if key == "tcptype" {
rawTCPTypeRaw = value
}

extensions = append(extensions, CandidateExtension{key, value})
}

return
}
Loading

0 comments on commit 2b64677

Please sign in to comment.