// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package ice

import (
	
	
	
	
	
	
	
	
	
	
	

	
)

type candidateBase struct {
	id            string
	networkType   NetworkType
	candidateType CandidateType

	component      uint16
	address        string
	port           int
	relatedAddress *CandidateRelatedAddress
	tcpType        TCPType

	resolvedAddr net.Addr

	lastSent     atomic.Value
	lastReceived atomic.Value
	conn         net.PacketConn

	currAgent *Agent
	closeCh   chan struct{}
	closedCh  chan struct{}

	foundationOverride string
	priorityOverride   uint32

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

// Done implements context.Context.
func ( *candidateBase) () <-chan struct{} {
	return .closeCh
}

// Err implements context.Context.
func ( *candidateBase) () error {
	select {
	case <-.closedCh:
		return ErrRunCanceled
	default:
		return nil
	}
}

// Deadline implements context.Context.
func ( *candidateBase) () ( time.Time,  bool) {
	return time.Time{}, false
}

// Value implements context.Context.
func ( *candidateBase) (interface{}) interface{} {
	return nil
}

// ID returns Candidate ID.
func ( *candidateBase) () string {
	return .id
}

func ( *candidateBase) () string {
	if .foundationOverride != "" {
		return .foundationOverride
	}

	return fmt.Sprintf("%d", crc32.ChecksumIEEE([]byte(.Type().String()+.address+.networkType.String())))
}

// Address returns Candidate Address.
func ( *candidateBase) () string {
	return .address
}

// Port returns Candidate Port.
func ( *candidateBase) () int {
	return .port
}

// Type returns candidate type.
func ( *candidateBase) () CandidateType {
	return .candidateType
}

// NetworkType returns candidate NetworkType.
func ( *candidateBase) () NetworkType {
	return .networkType
}

// Component returns candidate component.
func ( *candidateBase) () uint16 {
	return .component
}

func ( *candidateBase) ( uint16) {
	.component = 
}

// LocalPreference returns the local preference for this candidate.
func ( *candidateBase) () uint16 { //nolint:cyclop
	if .NetworkType().IsTCP() {
		// RFC 6544, section 4.2
		//
		// In Section 4.1.2.1 of [RFC5245], a recommended formula for UDP ICE
		// candidate prioritization is defined.  For TCP candidates, the same
		// formula and candidate type preferences SHOULD be used, and the
		// RECOMMENDED type preferences for the new candidate types defined in
		// this document (see Section 5) are 105 for NAT-assisted candidates and
		// 75 for UDP-tunneled candidates.
		//
		// (...)
		//
		// With TCP candidates, the local preference part of the recommended
		// priority formula is updated to also include the directionality
		// (active, passive, or simultaneous-open) of the TCP connection.  The
		// RECOMMENDED local preference is then defined as:
		//
		//     local preference = (2^13) * direction-pref + other-pref
		//
		// The direction-pref MUST be between 0 and 7 (both inclusive), with 7
		// being the most preferred.  The other-pref MUST be between 0 and 8191
		// (both inclusive), with 8191 being the most preferred.  It is
		// RECOMMENDED that the host, UDP-tunneled, and relayed TCP candidates
		// have the direction-pref assigned as follows: 6 for active, 4 for
		// passive, and 2 for S-O.  For the NAT-assisted and server reflexive
		// candidates, the RECOMMENDED values are: 6 for S-O, 4 for active, and
		// 2 for passive.
		//
		// (...)
		//
		// If any two candidates have the same type-preference and direction-
		// pref, they MUST have a unique other-pref.  With this specification,
		// this usually only happens with multi-homed hosts, in which case
		// other-pref is the preference for the particular IP address from which
		// the candidate was obtained.  When there is only a single IP address,
		// this value SHOULD be set to the maximum allowed value (8191).
		var  uint16 = 8191

		 := func() uint16 {
			switch .Type() {
			case CandidateTypeHost, CandidateTypeRelay:
				switch .tcpType {
				case TCPTypeActive:
					return 6
				case TCPTypePassive:
					return 4
				case TCPTypeSimultaneousOpen:
					return 2
				case TCPTypeUnspecified:
					return 0
				}
			case CandidateTypePeerReflexive, CandidateTypeServerReflexive:
				switch .tcpType {
				case TCPTypeSimultaneousOpen:
					return 6
				case TCPTypeActive:
					return 4
				case TCPTypePassive:
					return 2
				case TCPTypeUnspecified:
					return 0
				}
			case CandidateTypeUnspecified:
				return 0
			}

			return 0
		}()

		return (1<<13)* + 
	}

	return defaultLocalPreference
}

// RelatedAddress returns *CandidateRelatedAddress.
func ( *candidateBase) () *CandidateRelatedAddress {
	return .relatedAddress
}

func ( *candidateBase) () TCPType {
	return .tcpType
}

// start runs the candidate using the provided connection.
func ( *candidateBase) ( *Agent,  net.PacketConn,  <-chan struct{}) {
	if .conn != nil {
		.agent().log.Warn("Can't start already started candidateBase")

		return
	}
	.currAgent = 
	.conn = 
	.closeCh = make(chan struct{})
	.closedCh = make(chan struct{})

	go .recvLoop()
}

var bufferPool = sync.Pool{ // nolint:gochecknoglobals
	New: func() interface{} {
		return make([]byte, receiveMTU)
	},
}

func ( *candidateBase) ( <-chan struct{}) {
	 := .agent()

	defer close(.closedCh)

	select {
	case <-:
	case <-.closeCh:
		return
	}

	 := bufferPool.Get()
	defer bufferPool.Put()
	,  := .([]byte)
	if ! {
		return
	}

	for {
		, ,  := .conn.ReadFrom()
		if  != nil {
			if !(errors.Is(, io.EOF) || errors.Is(, net.ErrClosed)) {
				.log.Warnf("Failed to read from candidate %s: %v", , )
			}

			return
		}

		.handleInboundPacket([:], )
	}
}

func ( *candidateBase) ( net.Addr) bool {
	if ,  := .remoteCandidateCaches[toAddrPort()];  {
		.seen(false)

		return true
	}

	return false
}

func ( *candidateBase) ( Candidate,  net.Addr) {
	if .validateSTUNTrafficCache() {
		return
	}
	.remoteCandidateCaches[toAddrPort()] = 
}

func ( *candidateBase) ( []byte,  net.Addr) {
	 := .agent()

	if stun.IsMessage() {
		 := &stun.Message{
			Raw: make([]byte, len()),
		}

		// Explicitly copy raw buffer so Message can own the memory.
		copy(.Raw, )

		if  := .Decode();  != nil {
			.log.Warnf("Failed to handle decode ICE from %s to %s: %v", .addr(), , )

			return
		}

		if  := .loop.Run(, func( context.Context) {
			// nolint: contextcheck
			.handleInbound(, , )
		});  != nil {
			.log.Warnf("Failed to handle message: %v", )
		}

		return
	}

	if !.validateSTUNTrafficCache() {
		,  := .validateNonSTUNTraffic(, ) //nolint:contextcheck
		if ! {
			.log.Warnf("Discarded message from %s, not a valid remote candidate", .addr())

			return
		}
		.addRemoteCandidateCache(, )
	}

	// Note: This will return packetio.ErrFull if the buffer ever manages to fill up.
	if ,  := .buf.Write();  != nil {
		.log.Warnf("Failed to write packet: %s", )

		return
	}
}

// close stops the recvLoop.
func ( *candidateBase) () error {
	// If conn has never been started will be nil
	if .Done() == nil {
		return nil
	}

	// Assert that conn has not already been closed
	select {
	case <-.Done():
		return nil
	default:
	}

	var  error

	// Unblock recvLoop
	close(.closeCh)
	if  := .conn.SetDeadline(time.Now());  != nil {
		 = 
	}

	// Close the conn
	if  := .conn.Close();  != nil &&  == nil {
		 = 
	}

	if  != nil {
		return 
	}

	// Wait until the recvLoop is closed
	<-.closedCh

	return nil
}

func ( *candidateBase) ( []byte,  Candidate) (int, error) {
	,  := .conn.WriteTo(, .addr())
	if  != nil {
		// If the connection is closed, we should return the error
		if errors.Is(, io.ErrClosedPipe) {
			return , 
		}
		.agent().log.Infof("Failed to send packet: %v", )

		return , nil
	}
	.seen(true)

	return , nil
}

// TypePreference returns the type preference for this candidate.
func ( *candidateBase) () uint16 {
	 := .Type().Preference()
	if  == 0 {
		return 0
	}

	if .NetworkType().IsTCP() {
		var  uint16 = defaultTCPPriorityOffset
		if .agent() != nil {
			 = .agent().tcpPriorityOffset
		}

		 -= 
	}

	return 
}

// Priority computes the priority for this ICE Candidate
// See: https://www.rfc-editor.org/rfc/rfc8445#section-5.1.2.1
func ( *candidateBase) () uint32 {
	if .priorityOverride != 0 {
		return .priorityOverride
	}

	// The local preference MUST be an integer from 0 (lowest preference) to
	// 65535 (highest preference) inclusive.  When there is only a single IP
	// address, this value SHOULD be set to 65535.  If there are multiple
	// candidates for a particular component for a particular data stream
	// that have the same type, the local preference MUST be unique for each
	// one.

	return (1<<24)*uint32(.TypePreference()) +
		(1<<8)*uint32(.LocalPreference()) +
		(1<<0)*uint32(256-.Component())
}

// Equal is used to compare two candidateBases.
func ( *candidateBase) ( Candidate) bool {
	if .addr() != .addr() {
		if .addr() == nil || .addr() == nil {
			return false
		}
		if !addrEqual(.addr(), .addr()) {
			return false
		}
	}

	return .NetworkType() == .NetworkType() &&
		.Type() == .Type() &&
		.Address() == .Address() &&
		.Port() == .Port() &&
		.TCPType() == .TCPType() &&
		.RelatedAddress().Equal(.RelatedAddress())
}

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

// String makes the candidateBase printable.
func ( *candidateBase) () string {
	return fmt.Sprintf(
		"%s %s %s%s (resolved: %v)",
		.NetworkType(),
		.Type(),
		net.JoinHostPort(.Address(), strconv.Itoa(.Port())),
		.relatedAddress,
		.resolvedAddr,
	)
}

// LastReceived returns a time.Time indicating the last time
// this candidate was received.
func ( *candidateBase) () time.Time {
	if ,  := .lastReceived.Load().(time.Time);  {
		return 
	}

	return time.Time{}
}

func ( *candidateBase) ( time.Time) {
	.lastReceived.Store()
}

// LastSent returns a time.Time indicating the last time
// this candidate was sent.
func ( *candidateBase) () time.Time {
	if ,  := .lastSent.Load().(time.Time);  {
		return 
	}

	return time.Time{}
}

func ( *candidateBase) ( time.Time) {
	.lastSent.Store()
}

func ( *candidateBase) ( bool) {
	if  {
		.setLastSent(time.Now())
	} else {
		.setLastReceived(time.Now())
	}
}

func ( *candidateBase) () net.Addr {
	return .resolvedAddr
}

func ( *candidateBase) () bool {
	return .isLocationTracked
}

func ( *candidateBase) () *Agent {
	return .currAgent
}

func ( *candidateBase) () context.Context {
	return 
}

func ( *candidateBase) () (Candidate, error) {
	return UnmarshalCandidate(.Marshal())
}

func removeZoneIDFromAddress( string) string {
	if  := strings.Index(, "%");  != -1 {
		return [:]
	}

	return 
}

// Marshal returns the string representation of the ICECandidate.
func ( *candidateBase) () string {
	 := .Foundation()
	if  == " " {
		 = ""
	}

	 = fmt.Sprintf("%s %d %s %d %s %d typ %s",
		,
		.Component(),
		.NetworkType().NetworkShort(),
		.Priority(),
		removeZoneIDFromAddress(.Address()),
		.Port(),
		.Type())

	if  := .RelatedAddress();  != nil && .Address != "" && .Port != 0 {
		 = fmt.Sprintf("%s raddr %s rport %d",
			,
			.Address,
			.Port)
	}

	 := .marshalExtensions()

	if  != "" {
		 = fmt.Sprintf("%s %s", , )
	}

	return 
}

// 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 ( *candidateBase) () []CandidateExtension {
	 := .TCPType()
	 := 0
	if  != TCPTypeUnspecified {
		 = 1
	}

	 := make([]CandidateExtension, len(.extensions)+)
	// We store the TCPType in c.tcpType, but we need to return it as an extension.
	if  == 1 {
		[0] = CandidateExtension{
			Key:   "tcptype",
			Value: .String(),
		}
	}

	copy([:], .extensions)

	return 
}

// Get returns the value of the given key if it exists.
func ( *candidateBase) ( string) (CandidateExtension, bool) {
	 := CandidateExtension{Key: }

	for  := range .extensions {
		if .extensions[].Key ==  {
			.Value = .extensions[].Value

			return , true
		}
	}

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

		return , true
	}

	return , false
}

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

		.tcpType = 

		return nil
	}

	if .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  := range .extensions {
		if .extensions[].Key == .Key {
			.extensions[] = 

			return nil
		}
	}

	.extensions = append(.extensions, )

	return nil
}

func ( *candidateBase) ( string) ( bool) {
	if  == "tcptype" {
		.tcpType = TCPTypeUnspecified
		 = true
	}

	for  := range .extensions {
		if .extensions[].Key ==  {
			.extensions = append(.extensions[:], .extensions[+1:]...)
			 = true

			break
		}
	}

	return 
}

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

	for  := range  {
		if  != "" {
			 += " "
		}

		 += [].Key + " " + [].Value
	}

	return 
}

// Equal returns true if the candidate extensions are equal.
func ( *candidateBase) ( []CandidateExtension) bool {
	 := make(map[CandidateExtension]int)
	 := make(map[CandidateExtension]int)

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

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

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

	for  := range .extensions {
		[.extensions[]]++
		[[]]++
	}

	for ,  := range  {
		if [] !=  {
			return false
		}
	}

	return true
}

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

// UnmarshalCandidate Parses a candidate from a string
// https://datatracker.ietf.org/doc/html/rfc5245#section-15.1
func ( string) (Candidate, error) { //nolint:cyclop
	// Handle candidates with the "candidate:" prefix as defined in RFC 5245 section 15.1.
	 = strings.TrimPrefix(, "candidate:")

	 := 0
	// foundation ( 1*32ice-char ) But we allow for empty foundation,
	, ,  := readCandidateCharToken(, , 32)
	if  != nil {
		return nil, fmt.Errorf("%w: %v in %s", errParseFoundation, , ) //nolint:errorlint // we wrap the error
	}

	// Empty foundation, not RFC 8445 compliant but seen in the wild
	if  == "" {
		 = " "
	}

	if  >= len() {
		return nil, fmt.Errorf("%w: expected component in %s", errAttributeTooShortICECandidate, )
	}

	// component-id ( 1*5DIGIT )
	, ,  := readCandidateDigitToken(, , 5)
	if  != nil {
		return nil, fmt.Errorf("%w: %v in %s", errParseComponent, , ) //nolint:errorlint // we wrap the error
	}

	if  >= len() {
		return nil, fmt.Errorf("%w: expected transport in %s", errAttributeTooShortICECandidate, )
	}

	// transport ( "UDP" / transport-extension ; from RFC 3261 ) SP
	,  := readCandidateStringToken(, )

	if  >= len() {
		return nil, fmt.Errorf("%w: expected priority in %s", errAttributeTooShortICECandidate, )
	}

	// priority ( 1*10DIGIT ) SP
	, ,  := readCandidateDigitToken(, , 10)
	if  != nil {
		return nil, fmt.Errorf("%w: %v in %s", errParsePriority, , ) //nolint:errorlint // we wrap the error
	}

	if  >= len() {
		return nil, fmt.Errorf("%w: expected address in %s", errAttributeTooShortICECandidate, )
	}

	// connection-address SP     ;from RFC 4566
	,  := readCandidateStringToken(, )

	// Remove IPv6 ZoneID: https://github.com/pion/ice/pull/704
	 = removeZoneIDFromAddress()

	if  >= len() {
		return nil, fmt.Errorf("%w: expected port in %s", errAttributeTooShortICECandidate, )
	}

	// port from RFC 4566
	, ,  := readCandidatePort(, )
	if  != nil {
		return nil, fmt.Errorf("%w: %v in %s", errParsePort, , ) //nolint:errorlint // we wrap the error
	}

	// "typ" SP
	,  := readCandidateStringToken(, )
	if  != "typ" {
		return nil, fmt.Errorf("%w (%s)", ErrUnknownCandidateTyp, )
	}

	if  >= len() {
		return nil, fmt.Errorf("%w: expected candidate type in %s", errAttributeTooShortICECandidate, )
	}

	// SP cand-type ("host" / "srflx" / "prflx" / "relay")
	,  := readCandidateStringToken(, )

	, , ,  := tryReadRelativeAddrs(, )
	if  != nil {
		return nil, 
	}

	 := TCPTypeUnspecified
	var  []CandidateExtension
	var  string

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

		if  != "" {
			 = NewTCPType()
			if  == TCPTypeUnspecified {
				return nil, fmt.Errorf("%w: invalid or unsupported TCPtype %s", errParseTCPType, )
			}
		}
	}

	// this code is ugly because we can't break backwards compatibility
	// with the old way of parsing candidates
	switch  {
	case "host":
		,  := NewCandidateHost(&CandidateHostConfig{
			"",
			,
			,
			,
			uint16(), //nolint:gosec // G115 no overflow we read 5 digits
			uint32(),  //nolint:gosec // G115 no overflow we read 5 digits
			,
			,
			false,
		})
		if  != nil {
			return nil, 
		}

		.setExtensions()

		return , nil
	case "srflx":
		,  := NewCandidateServerReflexive(&CandidateServerReflexiveConfig{
			"",
			,
			,
			,
			uint16(), //nolint:gosec // G115 no overflow we read 5 digits
			uint32(),  //nolint:gosec // G115 no overflow we read 5 digits
			,
			,
			,
		})
		if  != nil {
			return nil, 
		}

		.setExtensions()

		return , nil
	case "prflx":
		,  := NewCandidatePeerReflexive(&CandidatePeerReflexiveConfig{
			"",
			,
			,
			,
			uint16(), //nolint:gosec // G115 no overflow we read 5 digits
			uint32(),  //nolint:gosec // G115 no overflow we read 5 digits
			,
			,
			,
		})
		if  != nil {
			return nil, 
		}

		.setExtensions()

		return , nil
	case "relay":
		,  := NewCandidateRelay(&CandidateRelayConfig{
			"",
			,
			,
			,
			uint16(), //nolint:gosec // G115 no overflow we read 5 digits
			uint32(),  //nolint:gosec // G115 no overflow we read 5 digits
			,
			,
			,
			"",
			nil,
		})
		if  != nil {
			return nil, 
		}

		.setExtensions()

		return , nil
	default:
		return nil, fmt.Errorf("%w (%s)", ErrUnknownCandidateTyp, )
	}
}

// Read an ice-char token from the raw string
// ice-char = ALPHA / DIGIT / "+" / "/"
// stop reading when a space is encountered or the end of the string.
func readCandidateCharToken( string,  int,  int) (string, int, error) { //nolint:cyclop
	for ,  := range [:] {
		if  == 0x20 { // SP
			return [ : +],  +  + 1, nil
		}

		if  ==  {
			//nolint: err113 // handled by caller
			return "", 0, fmt.Errorf("token too long: %s expected 1x%d", [:+], )
		}

		if !( >= 'A' &&  <= 'Z' ||
			 >= 'a' &&  <= 'z' ||
			 >= '0' &&  <= '9' ||
			 == '+' ||  == '/') {
			return "", 0, fmt.Errorf("invalid ice-char token: %c", ) //nolint: err113 // handled by caller
		}
	}

	return [:], len(), nil
}

// Read an ice string token from the raw string until a space is encountered
// Or the end of the string, we imply that ice string are UTF-8 encoded.
func readCandidateStringToken( string,  int) (string, int) {
	for ,  := range [:] {
		if  == 0x20 { // SP
			return [ : +],  +  + 1
		}
	}

	return [:], len()
}

// Read a digit token from the raw string
// stop reading when a space is encountered or the end of the string.
func readCandidateDigitToken( string, ,  int) (int, int, error) {
	var  int
	for ,  := range [:] {
		if  == 0x20 { // SP
			return ,  +  + 1, nil
		}

		if  ==  {
			//nolint: err113 // handled by caller
			return 0, 0, fmt.Errorf("token too long: %s expected 1x%d", [:+], )
		}

		if !( >= '0' &&  <= '9') {
			return 0, 0, fmt.Errorf("invalid digit token: %c", ) //nolint: err113 // handled by caller
		}

		 = *10 + int(-'0')
	}

	return , len(), nil
}

// Read and validate RFC 4566 port from the raw string.
func readCandidatePort( string,  int) (int, int, error) {
	, ,  := readCandidateDigitToken(, , 5)
	if  != nil {
		return 0, 0, 
	}

	if  > 65535 {
		return 0, 0, fmt.Errorf("invalid RFC 4566 port %d", ) //nolint: err113 // handled by caller
	}

	return , , 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( string,  int) (string, int, error) {
	for ,  := range [:] {
		if  == 0x20 { // SP
			return [ : +],  +  + 1, nil
		}

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

	return [:], len(), 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
// .
func tryReadRelativeAddrs( string,  int) ( string, ,  int,  error) {
	,  := readCandidateStringToken(, )

	if  != "raddr" {
		return "", 0, , nil
	}

	if  >= len() {
		return "", 0, 0, fmt.Errorf("%w: expected raddr value in %s", errParseRelatedAddr, )
	}

	,  = readCandidateStringToken(, )

	if  >= len() {
		return "", 0, 0, fmt.Errorf("%w: expected rport in %s", errParseRelatedAddr, )
	}

	,  = readCandidateStringToken(, )
	if  != "rport" {
		return "", 0, 0, fmt.Errorf("%w: expected rport in %s", errParseRelatedAddr, )
	}

	if  >= len() {
		return "", 0, 0, fmt.Errorf("%w: expected rport value in %s", errParseRelatedAddr, )
	}

	, ,  = readCandidatePort(, )
	if  != nil {
		return "", 0, 0, fmt.Errorf("%w: %v", errParseRelatedAddr, ) //nolint:errorlint // we wrap the error
	}

	return , , , 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( string) ( []CandidateExtension,  string,  error) {
	 = make([]CandidateExtension, 0)

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

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

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

		// while not spec-compliant, we allow for empty values, as seen in the wild
		var  string
		if  < len() {
			, ,  = readCandidateByteString(, )
			if  != nil {
				return , "", fmt.Errorf(
					"%w: failed to read value %v", errParseExtension, , //nolint: errorlint // we are wrapping the error
				)
			}
			 = 
		}

		if  == "tcptype" {
			 = 

			continue
		}

		 = append(, CandidateExtension{, })
	}

	return , , nil
}