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

package ice

import (
	
	
	
	
	
	
	
	

	
	
	stunx 
	
	
	
)

// Close a net.Conn and log if we have a failure.
func closeConnAndLog( io.Closer,  logging.LeveledLogger,  string,  ...interface{}) {
	if  == nil || (reflect.ValueOf().Kind() == reflect.Ptr && reflect.ValueOf().IsNil()) {
		.Warnf("Connection is not allocated: "+, ...)

		return
	}

	.Warnf(, ...)
	if  := .Close();  != nil {
		.Warnf("Failed to close connection: %v", )
	}
}

// GatherCandidates initiates the trickle based gathering process.
func ( *Agent) () error {
	var  error

	if  := .loop.Run(.loop, func( context.Context) {
		if .gatheringState != GatheringStateNew {
			 = ErrMultipleGatherAttempted

			return
		} else if .onCandidateHdlr.Load() == nil {
			 = ErrNoOnCandidateHandler

			return
		}

		.gatherCandidateCancel() // Cancel previous gathering routine
		,  := context.WithCancel()
		.gatherCandidateCancel = 
		 := make(chan struct{})
		.gatherCandidateDone = 

		go .gatherCandidates(, )
	});  != nil {
		return 
	}

	return 
}

func ( *Agent) ( context.Context,  chan struct{}) { //nolint:cyclop
	defer close()
	if  := .setGatheringState(GatheringStateGathering);  != nil { //nolint:contextcheck
		.log.Warnf("Failed to set gatheringState to GatheringStateGathering: %v", )

		return
	}

	var  sync.WaitGroup
	for ,  := range .candidateTypes {
		switch  {
		case CandidateTypeHost:
			.Add(1)
			go func() {
				.gatherCandidatesLocal(, .networkTypes)
				.Done()
			}()
		case CandidateTypeServerReflexive:
			.Add(1)
			go func() {
				if .udpMuxSrflx != nil {
					.gatherCandidatesSrflxUDPMux(, .urls, .networkTypes)
				} else {
					.gatherCandidatesSrflx(, .urls, .networkTypes)
				}
				.Done()
			}()
			if .extIPMapper != nil && .extIPMapper.candidateType == CandidateTypeServerReflexive {
				.Add(1)
				go func() {
					.gatherCandidatesSrflxMapped(, .networkTypes)
					.Done()
				}()
			}
		case CandidateTypeRelay:
			.Add(1)
			go func() {
				.gatherCandidatesRelay(, .urls)
				.Done()
			}()
		case CandidateTypePeerReflexive, CandidateTypeUnspecified:
		}
	}

	// Block until all STUN and TURN URLs have been gathered (or timed out)
	.Wait()

	if  := .setGatheringState(GatheringStateComplete);  != nil { //nolint:contextcheck
		.log.Warnf("Failed to set gatheringState to GatheringStateComplete: %v", )
	}
}

//nolint:gocognit,gocyclo,cyclop
func ( *Agent) ( context.Context,  []NetworkType) {
	 := map[string]struct{}{}
	for ,  := range  {
		if .IsTCP() {
			[tcp] = struct{}{}
		} else {
			[udp] = struct{}{}
		}
	}

	// When UDPMux is enabled, skip other UDP candidates
	if .udpMux != nil {
		if  := .gatherCandidatesLocalUDPMux();  != nil {
			.log.Warnf("Failed to create host candidate for UDPMux: %s", )
		}
		delete(, udp)
	}

	, ,  := localInterfaces(.net, .interfaceFilter, .ipFilter, , .includeLoopback)
	if  != nil {
		.log.Warnf("Failed to iterate local interfaces, host candidates will not be gathered %s", )

		return
	}

	for ,  := range  {
		 := 
		if .mDNSMode != MulticastDNSModeQueryAndGather &&
			.extIPMapper != nil && .extIPMapper.candidateType == CandidateTypeHost {
			if ,  := .extIPMapper.findExternalIP(.String());  == nil {
				,  := netip.AddrFromSlice()
				if ! {
					.log.Warnf("failed to convert mapped external IP to netip.Addr'%s'", .String())

					continue
				}
				// we'd rather have an IPv4-mapped IPv6 become IPv4 so that it is usable
				 = .Unmap()
			} else {
				.log.Warnf("1:1 NAT mapping is enabled but no external IP is found for %s", .String())
			}
		}

		 := .String()
		var  bool
		if .mDNSMode == MulticastDNSModeQueryAndGather {
			 = .mDNSName
		} else {
			// Here, we are not doing multicast gathering, so we will need to skip this address so
			// that we don't accidentally reveal location tracking information. Otherwise, the
			// case above hides the IP behind an mDNS address.
			 = shouldFilterLocationTrackedIP()
		}

		for  := range  {
			type  struct {
				 net.PacketConn
				 int
			}
			var (
				   []
				 TCPType
			)

			switch  {
			case tcp:
				if .tcpMux == nil {
					continue
				}

				// Handle ICE TCP passive mode
				var  []net.PacketConn
				if ,  := .tcpMux.(AllConnsGetter);  {
					.log.Debugf("GetAllConns by ufrag: %s", .localUfrag)
					// Note: this is missing zone for IPv6 by just grabbing the IP slice
					,  = .GetAllConns(.localUfrag, .Is6(), .AsSlice())
					if  != nil {
						.log.Warnf("Failed to get all TCP connections by ufrag: %s %s %s", , , .localUfrag)

						continue
					}
				} else {
					.log.Debugf("GetConn by ufrag: %s", .localUfrag)
					// Note: this is missing zone for IPv6 by just grabbing the IP slice
					,  := .tcpMux.GetConnByUfrag(.localUfrag, .Is6(), .AsSlice())
					if  != nil {
						.log.Warnf("Failed to get TCP connections by ufrag: %s %s %s", , , .localUfrag)

						continue
					}
					 = []net.PacketConn{}
				}

				// Extract the port for each PacketConn we got.
				for ,  := range  {
					if ,  := .LocalAddr().(*net.TCPAddr);  {
						 = append(, {, .Port})
					} else {
						.log.Warnf("Failed to get port of connection from TCPMux: %s %s %s", , , .localUfrag)
					}
				}
				if len() == 0 {
					// Didn't succeed with any, try the next network.
					continue
				}
				 = TCPTypePassive
				// Is there a way to verify that the listen address is even
				// accessible from the current interface.
			case udp:
				,  := listenUDPInPortRange(.net, .log, int(.portMax), int(.portMin), , &net.UDPAddr{
					IP:   .AsSlice(),
					Port: 0,
					Zone: .Zone(),
				})
				if  != nil {
					.log.Warnf("Failed to listen %s %s", , )

					continue
				}

				if ,  := .LocalAddr().(*net.UDPAddr);  {
					 = append(, {, .Port})
				} else {
					.log.Warnf("Failed to get port of UDPAddr from ListenUDPInPortRange: %s %s %s", , , .localUfrag)

					continue
				}
			}

			for ,  := range  {
				 := CandidateHostConfig{
					Network:   ,
					Address:   ,
					Port:      .,
					Component: ComponentRTP,
					TCPType:   ,
					// we will still process this candidate so that we start up the right
					// listeners.
					IsLocationTracked: ,
				}

				,  := NewCandidateHost(&)
				if  != nil {
					closeConnAndLog(
						.,
						.log,
						"failed to create host candidate: %s %s %d: %v",
						, ,
						.,
						,
					)

					continue
				}

				if .mDNSMode == MulticastDNSModeQueryAndGather {
					if  = .setIPAddr();  != nil {
						closeConnAndLog(
							.,
							.log,
							"failed to create host candidate: %s %s %d: %v",
							,
							,
							.,
							,
						)

						continue
					}
				}

				if  := .addCandidate(, , .);  != nil {
					if  := .close();  != nil {
						.log.Warnf("Failed to close candidate: %v", )
					}
					.log.Warnf("Failed to append to localCandidates and run onCandidateHdlr: %v", )
				}
			}
		}
	}
}

// shouldFilterLocationTrackedIP returns if this candidate IP should be filtered out from
// any candidate publishing/notification for location tracking reasons.
func shouldFilterLocationTrackedIP( netip.Addr) bool {
	// https://tools.ietf.org/html/rfc8445#section-5.1.1.1
	// Similarly, when host candidates corresponding to
	// an IPv6 address generated using a mechanism that prevents location
	// tracking are gathered, then host candidates corresponding to IPv6
	// link-local addresses [RFC4291] MUST NOT be gathered.
	return .Is6() && (.IsLinkLocalUnicast() || .IsLinkLocalMulticast())
}

// shouldFilterLocationTracked returns if this candidate IP should be filtered out from
// any candidate publishing/notification for location tracking reasons.
func shouldFilterLocationTracked( net.IP) bool {
	,  := netip.AddrFromSlice()
	if ! {
		return false
	}

	return shouldFilterLocationTrackedIP()
}

func ( *Agent) ( context.Context) error { //nolint:gocognit,cyclop
	if .udpMux == nil {
		return errUDPMuxDisabled
	}

	 := .udpMux.GetListenAddresses()
	 := make(map[CandidateHostConfig]struct{})

	for ,  := range  {
		,  := .(*net.UDPAddr)
		if ! {
			return errInvalidAddress
		}
		 := .IP

		if ,  := .udpMux.(*UDPMuxDefault);  && !.includeLoopback && .IsLoopback() {
			// Unlike MultiUDPMux Default, UDPMuxDefault doesn't have
			// a separate param to include loopback, so we respect agent config
			continue
		}

		if .mDNSMode != MulticastDNSModeQueryAndGather &&
			.extIPMapper != nil &&
			.extIPMapper.candidateType == CandidateTypeHost {
			,  := .extIPMapper.findExternalIP(.String())
			if  != nil {
				.log.Warnf("1:1 NAT mapping is enabled but no external IP is found for %s", .String())

				continue
			}

			 = 
		}

		var  string
		var  bool
		if .mDNSMode == MulticastDNSModeQueryAndGather {
			 = .mDNSName
		} else {
			 = .String()
			// Here, we are not doing multicast gathering, so we will need to skip this address so
			// that we don't accidentally reveal location tracking information. Otherwise, the
			// case above hides the IP behind an mDNS address.
			 = shouldFilterLocationTracked()
		}

		 := CandidateHostConfig{
			Network:           udp,
			Address:           ,
			Port:              .Port,
			Component:         ComponentRTP,
			IsLocationTracked: ,
		}

		// Detect a duplicate candidate before calling addCandidate().
		// otherwise, addCandidate() detects the duplicate candidate
		// and close its connection, invalidating all candidates
		// that share the same connection.
		if ,  := [];  {
			continue
		}

		,  := .udpMux.GetConn(.localUfrag, )
		if  != nil {
			return 
		}

		,  := NewCandidateHost(&)
		if  != nil {
			closeConnAndLog(, .log, "failed to create host mux candidate: %s %d: %v", , .Port, )

			continue
		}

		if  := .addCandidate(, , );  != nil {
			if  := .close();  != nil {
				.log.Warnf("Failed to close candidate: %v", )
			}

			closeConnAndLog(, .log, "failed to add candidate: %s %d: %v", , .Port, )

			continue
		}

		[] = struct{}{}
	}

	return nil
}

func ( *Agent) ( context.Context,  []NetworkType) {
	var  sync.WaitGroup
	defer .Wait()

	for ,  := range  {
		if .IsTCP() {
			continue
		}

		 := .String()
		.Add(1)
		go func() {
			defer .Done()

			,  := listenUDPInPortRange(
				.net,
				.log,
				int(.portMax),
				int(.portMin),
				,
				&net.UDPAddr{IP: nil, Port: 0},
			)
			if  != nil {
				.log.Warnf("Failed to listen %s: %v", , )

				return
			}

			,  := .LocalAddr().(*net.UDPAddr)
			if ! {
				closeConnAndLog(, .log, "1:1 NAT mapping is enabled but LocalAddr is not a UDPAddr")

				return
			}

			,  := .extIPMapper.findExternalIP(.IP.String())
			if  != nil {
				closeConnAndLog(, .log, "1:1 NAT mapping is enabled but no external IP is found for %s", .IP.String())

				return
			}

			if shouldFilterLocationTracked() {
				closeConnAndLog(, .log, "external IP is somehow filtered for location tracking reasons %s", )

				return
			}

			 := CandidateServerReflexiveConfig{
				Network:   ,
				Address:   .String(),
				Port:      .Port,
				Component: ComponentRTP,
				RelAddr:   .IP.String(),
				RelPort:   .Port,
			}
			,  := NewCandidateServerReflexive(&)
			if  != nil {
				closeConnAndLog(, .log, "failed to create server reflexive candidate: %s %s %d: %v",
					,
					.String(),
					.Port,
					)

				return
			}

			if  := .addCandidate(, , );  != nil {
				if  := .close();  != nil {
					.log.Warnf("Failed to close candidate: %v", )
				}
				.log.Warnf("Failed to append to localCandidates and run onCandidateHdlr: %v", )
			}
		}()
	}
}

//nolint:gocognit,cyclop
func ( *Agent) ( context.Context,  []*stun.URI,  []NetworkType) {
	var  sync.WaitGroup
	defer .Wait()

	for ,  := range  {
		if .IsTCP() {
			continue
		}

		for  := range  {
			for ,  := range .udpMuxSrflx.GetListenAddresses() {
				,  := .(*net.UDPAddr)
				if ! {
					.log.Warn("Failed to cast udpMuxSrflx listen address to UDPAddr")

					continue
				}
				.Add(1)
				go func( stun.URI,  string,  *net.UDPAddr) {
					defer .Done()

					 := fmt.Sprintf("%s:%d", .Host, .Port)
					,  := .net.ResolveUDPAddr(, )
					if  != nil {
						.log.Debugf("Failed to resolve STUN host: %s %s: %v", , , )

						return
					}

					if shouldFilterLocationTracked(.IP) {
						.log.Warnf("STUN host %s is somehow filtered for location tracking reasons", )

						return
					}

					,  := .udpMuxSrflx.GetXORMappedAddr(, .stunGatherTimeout)
					if  != nil {
						.log.Warnf("Failed get server reflexive address %s %s: %v", , , )

						return
					}

					,  := .udpMuxSrflx.GetConnForURL(.localUfrag, .String(), )
					if  != nil {
						.log.Warnf("Failed to find connection in UDPMuxSrflx %s %s: %v", , , )

						return
					}

					 := .IP
					 := .Port

					 := CandidateServerReflexiveConfig{
						Network:   ,
						Address:   .String(),
						Port:      ,
						Component: ComponentRTP,
						RelAddr:   .IP.String(),
						RelPort:   .Port,
					}
					,  := NewCandidateServerReflexive(&)
					if  != nil {
						closeConnAndLog(, .log, "failed to create server reflexive candidate: %s %s %d: %v", , , , )

						return
					}

					if  := .addCandidate(, , );  != nil {
						if  := .close();  != nil {
							.log.Warnf("Failed to close candidate: %v", )
						}
						.log.Warnf("Failed to append to localCandidates and run onCandidateHdlr: %v", )
					}
				}(*[], .String(), )
			}
		}
	}
}

//nolint:cyclop,gocognit
func ( *Agent) ( context.Context,  []*stun.URI,  []NetworkType) {
	var  sync.WaitGroup
	defer .Wait()

	for ,  := range  {
		if .IsTCP() {
			continue
		}

		for  := range  {
			.Add(1)
			go func( stun.URI,  string) {
				defer .Done()

				 := fmt.Sprintf("%s:%d", .Host, .Port)
				,  := .net.ResolveUDPAddr(, )
				if  != nil {
					.log.Debugf("Failed to resolve STUN host: %s %s: %v", , , )

					return
				}

				if shouldFilterLocationTracked(.IP) {
					.log.Warnf("STUN host %s is somehow filtered for location tracking reasons", )

					return
				}

				,  := listenUDPInPortRange(
					.net,
					.log,
					int(.portMax),
					int(.portMin),
					,
					&net.UDPAddr{IP: nil, Port: 0},
				)
				if  != nil {
					closeConnAndLog(, .log, "failed to listen for %s: %v", .String(), )

					return
				}
				// If the agent closes midway through the connection
				// we end it early to prevent close delay.
				,  := context.WithCancel()
				defer ()
				go func() {
					select {
					case <-.Done():
						return
					case <-.loop.Done():
						_ = .Close()
					}
				}()

				,  := stunx.GetXORMappedAddr(, , .stunGatherTimeout)
				if  != nil {
					closeConnAndLog(, .log, "failed to get server reflexive address %s %s: %v", , , )

					return
				}

				 := .IP
				 := .Port

				 := .LocalAddr().(*net.UDPAddr) //nolint:forcetypeassert
				 := CandidateServerReflexiveConfig{
					Network:   ,
					Address:   .String(),
					Port:      ,
					Component: ComponentRTP,
					RelAddr:   .IP.String(),
					RelPort:   .Port,
				}
				,  := NewCandidateServerReflexive(&)
				if  != nil {
					closeConnAndLog(, .log, "failed to create server reflexive candidate: %s %s %d: %v", , , , )

					return
				}

				if  := .addCandidate(, , );  != nil {
					if  := .close();  != nil {
						.log.Warnf("Failed to close candidate: %v", )
					}
					.log.Warnf("Failed to append to localCandidates and run onCandidateHdlr: %v", )
				}
			}(*[], .String())
		}
	}
}

//nolint:maintidx,gocognit,gocyclo,cyclop
func ( *Agent) ( context.Context,  []*stun.URI) {
	var  sync.WaitGroup
	defer .Wait()

	 := NetworkTypeUDP4.String()
	for  := range  {
		switch {
		case [].Scheme != stun.SchemeTypeTURN && [].Scheme != stun.SchemeTypeTURNS:
			continue
		case [].Username == "":
			.log.Errorf("Failed to gather relay candidates: %v", ErrUsernameEmpty)

			return
		case [].Password == "":
			.log.Errorf("Failed to gather relay candidates: %v", ErrPasswordEmpty)

			return
		}

		.Add(1)
		go func( stun.URI) {
			defer .Done()
			 := fmt.Sprintf("%s:%d", .Host, .Port)
			var (
				       net.PacketConn
				           error
				       string
				       int
				 string
			)

			switch {
			case .Proto == stun.ProtoTypeUDP && .Scheme == stun.SchemeTypeTURN:
				if ,  = .net.ListenPacket(, "0.0.0.0:0");  != nil {
					.log.Warnf("Failed to listen %s: %v", , )

					return
				}

				 = .LocalAddr().(*net.UDPAddr).IP.String() //nolint:forcetypeassert
				 = .LocalAddr().(*net.UDPAddr).Port        //nolint:forcetypeassert
				 = udp
			case .proxyDialer != nil && .Proto == stun.ProtoTypeTCP &&
				(.Scheme == stun.SchemeTypeTURN || .Scheme == stun.SchemeTypeTURNS):
				,  := .proxyDialer.Dial(NetworkTypeTCP4.String(), )
				if  != nil {
					.log.Warnf("Failed to dial TCP address %s via proxy dialer: %v", , )

					return
				}

				 = .LocalAddr().(*net.TCPAddr).IP.String() //nolint:forcetypeassert
				 = .LocalAddr().(*net.TCPAddr).Port        //nolint:forcetypeassert
				if .Scheme == stun.SchemeTypeTURN {
					 = tcp
				} else if .Scheme == stun.SchemeTypeTURNS {
					 = "tls"
				}
				 = turn.NewSTUNConn()

			case .Proto == stun.ProtoTypeTCP && .Scheme == stun.SchemeTypeTURN:
				,  := .net.ResolveTCPAddr(NetworkTypeTCP4.String(), )
				if  != nil {
					.log.Warnf("Failed to resolve TCP address %s: %v", , )

					return
				}

				,  := .net.DialTCP(NetworkTypeTCP4.String(), nil, )
				if  != nil {
					.log.Warnf("Failed to dial TCP address %s: %v", , )

					return
				}

				 = .LocalAddr().(*net.TCPAddr).IP.String() //nolint:forcetypeassert
				 = .LocalAddr().(*net.TCPAddr).Port        //nolint:forcetypeassert
				 = tcp
				 = turn.NewSTUNConn()
			case .Proto == stun.ProtoTypeUDP && .Scheme == stun.SchemeTypeTURNS:
				,  := .net.ResolveUDPAddr(, )
				if  != nil {
					.log.Warnf("Failed to resolve UDP address %s: %v", , )

					return
				}

				,  := .net.DialUDP("udp", nil, )
				if  != nil {
					.log.Warnf("Failed to dial DTLS address %s: %v", , )

					return
				}

				,  := dtls.Client(&fakenet.PacketConn{Conn: }, .RemoteAddr(), &dtls.Config{
					ServerName:         .Host,
					InsecureSkipVerify: .insecureSkipVerify, //nolint:gosec
					LoggerFactory:      .loggerFactory,
				})
				if  != nil {
					.log.Warnf("Failed to create DTLS client: %v", , )

					return
				}

				if  = .HandshakeContext();  != nil {
					.log.Warnf("Failed to create DTLS client: %v", , )

					return
				}

				 = .LocalAddr().(*net.UDPAddr).IP.String() //nolint:forcetypeassert
				 = .LocalAddr().(*net.UDPAddr).Port        //nolint:forcetypeassert
				 = relayProtocolDTLS
				 = &fakenet.PacketConn{Conn: }
			case .Proto == stun.ProtoTypeTCP && .Scheme == stun.SchemeTypeTURNS:
				,  := .net.ResolveTCPAddr(NetworkTypeTCP4.String(), )
				if  != nil {
					.log.Warnf("Failed to resolve relay address %s: %v", , )

					return
				}

				,  := .net.DialTCP(NetworkTypeTCP4.String(), nil, )
				if  != nil {
					.log.Warnf("Failed to connect to relay: %v", )

					return
				}

				 := tls.Client(, &tls.Config{
					ServerName:         .Host,
					InsecureSkipVerify: .insecureSkipVerify, //nolint:gosec
				})

				if  := .HandshakeContext();  != nil {
					if  := .Close();  != nil {
						.log.Errorf("Failed to close relay connection: %v", )
					}
					.log.Warnf("Failed to connect to relay: %v", )

					return
				}

				 = .LocalAddr().(*net.TCPAddr).IP.String() //nolint:forcetypeassert
				 = .LocalAddr().(*net.TCPAddr).Port        //nolint:forcetypeassert
				 = relayProtocolTLS
				 = turn.NewSTUNConn()
			default:
				.log.Warnf("Unable to handle URL in gatherCandidatesRelay %v", )

				return
			}

			,  := turn.NewClient(&turn.ClientConfig{
				TURNServerAddr: ,
				Conn:           ,
				Username:       .Username,
				Password:       .Password,
				LoggerFactory:  .loggerFactory,
				Net:            .net,
			})
			if  != nil {
				closeConnAndLog(, .log, "failed to create new TURN client %s %s", , )

				return
			}

			if  = .Listen();  != nil {
				.Close()
				closeConnAndLog(, .log, "failed to listen on TURN client %s %s", , )

				return
			}

			,  := .Allocate()
			if  != nil {
				.Close()
				closeConnAndLog(, .log, "failed to allocate on TURN client %s %s", , )

				return
			}

			 := .LocalAddr().(*net.UDPAddr) //nolint:forcetypeassert

			if shouldFilterLocationTracked(.IP) {
				.log.Warnf("TURN address %s is somehow filtered for location tracking reasons", .IP)

				return
			}

			 := CandidateRelayConfig{
				Network:       ,
				Component:     ComponentRTP,
				Address:       .IP.String(),
				Port:          .Port,
				RelAddr:       ,
				RelPort:       ,
				RelayProtocol: ,
				OnClose: func() error {
					.Close()

					return .Close()
				},
			}
			 := func() {
				if  := .Close();  != nil {
					.log.Warnf("Failed to close relay %v", )
				}
			}
			,  := NewCandidateRelay(&)
			if  != nil {
				()

				.Close()
				closeConnAndLog(, .log, "failed to create relay candidate: %s %s: %v", , .String(), )

				return
			}

			if  := .addCandidate(, , );  != nil {
				()

				if  := .close();  != nil {
					.log.Warnf("Failed to close candidate: %v", )
				}
				.log.Warnf("Failed to append to localCandidates and run onCandidateHdlr: %v", )
			}
		}(*[])
	}
}