package swarm

import (
	
	
	
	
	
	
	

	
	
	
	
	

	ma 
	mafmt 
	manet 
)

// The maximum number of addresses we'll return when resolving all of a peer's
// address
const maximumResolvedAddresses = 100

const maximumDNSADDRRecursion = 4

// Diagram of dial sync:
//
//   many callers of Dial()   synched w.  dials many addrs       results to callers
//  ----------------------\    dialsync    use earliest            /--------------
//  -----------------------\              |----------\           /----------------
//  ------------------------>------------<-------     >---------<-----------------
//  -----------------------|              \----x                 \----------------
//  ----------------------|                \-----x                \---------------
//                                         any may fail          if no addr at end
//                                                             retry dialAttempt x

var (
	// ErrDialBackoff is returned by the backoff code when a given peer has
	// been dialed too frequently
	ErrDialBackoff = errors.New("dial backoff")

	// ErrDialRefusedBlackHole is returned when we are in a black holed environment
	ErrDialRefusedBlackHole = errors.New("dial refused because of black hole")

	// ErrDialToSelf is returned if we attempt to dial our own peer
	ErrDialToSelf = errors.New("dial to self attempted")

	// ErrNoTransport is returned when we don't know a transport for the
	// given multiaddr.
	ErrNoTransport = errors.New("no transport for protocol")

	// ErrAllDialsFailed is returned when connecting to a peer has ultimately failed
	ErrAllDialsFailed = errors.New("all dials failed")

	// ErrNoAddresses is returned when we fail to find any addresses for a
	// peer we're trying to dial.
	ErrNoAddresses = errors.New("no addresses")

	// ErrNoGoodAddresses is returned when we find addresses for a peer but
	// can't use any of them.
	ErrNoGoodAddresses = errors.New("no good addresses")

	// ErrGaterDisallowedConnection is returned when the gater prevents us from
	// forming a connection with a peer.
	ErrGaterDisallowedConnection = errors.New("gater disallows connection to peer")
)

// ErrQUICDraft29 wraps ErrNoTransport and provide a more meaningful error message
var ErrQUICDraft29 errQUICDraft29

type errQUICDraft29 struct{}

func (errQUICDraft29) () string {
	return "QUIC draft-29 has been removed, QUIC (RFC 9000) is accessible with /quic-v1"
}

func (errQUICDraft29) () error {
	return ErrNoTransport
}

// DialAttempts governs how many times a goroutine will try to dial a given peer.
// Note: this is down to one, as we have _too many dials_ atm. To add back in,
// add loop back in Dial(.)
const DialAttempts = 1

// ConcurrentFdDials is the number of concurrent outbound dials over transports
// that consume file descriptors
const ConcurrentFdDials = 160

// DefaultPerPeerRateLimit is the number of concurrent outbound dials to make
// per peer
var DefaultPerPeerRateLimit = 8

// DialBackoff is a type for tracking peer dial backoffs. Dialbackoff is used to
// avoid over-dialing the same, dead peers. Whenever we totally time out on all
// addresses of a peer, we add the addresses to DialBackoff. Then, whenever we
// attempt to dial the peer again, we check each address for backoff. If it's on
// backoff, we don't dial the address and exit promptly. If a dial is
// successful, the peer and all its addresses are removed from backoff.
//
// * It's safe to use its zero value.
// * It's thread-safe.
// * It's *not* safe to move this type after using.
type DialBackoff struct {
	entries map[peer.ID]map[string]*backoffAddr
	lock    sync.RWMutex
}

type backoffAddr struct {
	tries int
	until time.Time
}

func ( *DialBackoff) ( context.Context) {
	if .entries == nil {
		.entries = make(map[peer.ID]map[string]*backoffAddr)
	}
	go .background()
}

func ( *DialBackoff) ( context.Context) {
	 := time.NewTicker(BackoffMax)
	defer .Stop()
	for {
		select {
		case <-.Done():
			return
		case <-.C:
			.cleanup()
		}
	}
}

// Backoff returns whether the client should backoff from dialing
// peer p at address addr
func ( *DialBackoff) ( peer.ID,  ma.Multiaddr) ( bool) {
	.lock.RLock()
	defer .lock.RUnlock()

	,  := .entries[][string(.Bytes())]
	return  && time.Now().Before(.until)
}

// BackoffBase is the base amount of time to backoff (default: 5s).
var BackoffBase = time.Second * 5

// BackoffCoef is the backoff coefficient (default: 1s).
var BackoffCoef = time.Second

// BackoffMax is the maximum backoff time (default: 5m).
var BackoffMax = time.Minute * 5

// AddBackoff adds peer's address to backoff.
//
// Backoff is not exponential, it's quadratic and computed according to the
// following formula:
//
//	BackoffBase + BakoffCoef * PriorBackoffs^2
//
// Where PriorBackoffs is the number of previous backoffs.
func ( *DialBackoff) ( peer.ID,  ma.Multiaddr) {
	 := string(.Bytes())
	.lock.Lock()
	defer .lock.Unlock()
	,  := .entries[]
	if ! {
		 = make(map[string]*backoffAddr, 1)
		.entries[] = 
	}
	,  := []
	if ! {
		[] = &backoffAddr{
			tries: 1,
			until: time.Now().Add(BackoffBase),
		}
		return
	}

	 := BackoffBase + BackoffCoef*time.Duration(.tries*.tries)
	if  > BackoffMax {
		 = BackoffMax
	}
	.until = time.Now().Add()
	.tries++
}

// Clear removes a backoff record. Clients should call this after a
// successful Dial.
func ( *DialBackoff) ( peer.ID) {
	.lock.Lock()
	defer .lock.Unlock()
	delete(.entries, )
}

func ( *DialBackoff) () {
	.lock.Lock()
	defer .lock.Unlock()
	 := time.Now()
	for ,  := range .entries {
		 := false
		for ,  := range  {
			 := BackoffBase + BackoffCoef*time.Duration(.tries*.tries)
			if  > BackoffMax {
				 = BackoffMax
			}
			if .Before(.until.Add()) {
				 = true
				break
			}
		}
		if ! {
			delete(.entries, )
		}
	}
}

// DialPeer connects to a peer. Use network.WithForceDirectDial to force a
// direct connection.
//
// The idea is that the client of Swarm does not need to know what network
// the connection will happen over. Swarm can use whichever it choses.
// This allows us to use various transport protocols, do NAT traversal/relay,
// etc. to achieve connection.
func ( *Swarm) ( context.Context,  peer.ID) (network.Conn, error) {
	// Avoid typed nil issues.
	,  := .dialPeer(, )
	if  != nil {
		return nil, 
	}
	return , nil
}

// internal dial method that returns an unwrapped conn
//
// It is gated by the swarm's dial synchronization systems: dialsync and
// dialbackoff.
func ( *Swarm) ( context.Context,  peer.ID) (*Conn, error) {
	log.Debugw("dialing peer", "from", .local, "to", )
	 := .Validate()
	if  != nil {
		return nil, 
	}

	if  == .local {
		return nil, ErrDialToSelf
	}

	// check if we already have an open (usable) connection.
	 := .bestAcceptableConnToPeer(, )
	if  != nil {
		return , nil
	}

	if .gater != nil && !.gater.InterceptPeerDial() {
		log.Debugf("gater disallowed outbound connection to peer %s", )
		return nil, &DialError{Peer: , Cause: ErrGaterDisallowedConnection}
	}

	// apply the DialPeer timeout
	,  := context.WithTimeout(, network.GetDialPeerTimeout())
	defer ()

	,  = .dsync.Dial(, )
	if  == nil {
		// Ensure we connected to the correct peer.
		// This was most likely already checked by the security protocol, but it doesn't hurt do it again here.
		if .RemotePeer() !=  {
			.Close()
			log.Errorw("Handshake failed to properly authenticate peer", "authenticated", .RemotePeer(), "expected", )
			return nil, fmt.Errorf("unexpected peer")
		}
		return , nil
	}

	log.Debugf("network for %s finished dialing %s", .local, )

	if .Err() != nil {
		// Context error trumps any dial errors as it was likely the ultimate cause.
		return nil, .Err()
	}

	if .ctx.Err() != nil {
		// Ok, so the swarm is shutting down.
		return nil, ErrSwarmClosed
	}

	return nil, 
}

// dialWorkerLoop synchronizes and executes concurrent dials to a single peer
func ( *Swarm) ( peer.ID,  <-chan dialRequest) {
	 := newDialWorker(, , , nil)
	.loop()
}

func ( *Swarm) ( context.Context,  peer.ID) ( []ma.Multiaddr,  []TransportError,  error) {
	 := .peers.Addrs()
	if len() == 0 {
		return nil, nil, ErrNoAddresses
	}

	// Resolve dns or dnsaddrs
	 := .resolveAddrs(, peer.AddrInfo{ID: , Addrs: })

	 = ma.Unique()
	,  = .filterKnownUndialables(, )
	if ,  := network.GetForceDirectDial();  {
		 = ma.FilterAddrs(, .nonProxyAddr)
	}

	if len() == 0 {
		return nil, , ErrNoGoodAddresses
	}

	.peers.AddAddrs(, , peerstore.TempAddrTTL)

	return , , nil
}

func startsWithDNSComponent( ma.Multiaddr) bool {
	if  == nil {
		return false
	}
	 := false
	// Using ForEach to avoid allocating
	ma.ForEach(, func( ma.Component) bool {
		switch .Protocol().Code {
		case ma.P_DNS, ma.P_DNS4, ma.P_DNS6:
			 = true
		}

		return false
	})
	return 
}

func stripP2PComponent( []ma.Multiaddr) []ma.Multiaddr {
	for ,  := range  {
		if ,  := peer.IDFromP2PAddr();  != "" {
			[], _ = ma.SplitLast()
		}
	}
	return 
}

type resolver struct {
	canResolve func(ma.Multiaddr) bool
	resolve    func(ctx context.Context, maddr ma.Multiaddr, outputLimit int) ([]ma.Multiaddr, error)
}

type resolveErr struct {
	addr ma.Multiaddr
	err  error
}

func chainResolvers( context.Context,  []ma.Multiaddr,  int,  []resolver) ([]ma.Multiaddr, []resolveErr) {
	 := make([]ma.Multiaddr, 0, len())
	 := make([]resolveErr, 0)
	for ,  := range  {
		for ,  := range  {
			if !.canResolve() {
				 = append(, )
				continue
			}
			if len() >=  {
				 = [:]
				break
			}
			,  := .resolve(, , -len())
			if  != nil {
				 = append(, resolveErr{addr: , err: })
				continue
			}
			 = append(, ...)
		}
		,  = , 
		 = [:0]
	}
	return , 
}

// resolveAddrs resolves DNS/DNSADDR components in the given peer's addresses.
// We want to resolve the DNS components to IP addresses becase we want the
// swarm to manage ranking and dialing multiple connections, and a single DNS
// address can resolve to multiple IP addresses.
func ( *Swarm) ( context.Context,  peer.AddrInfo) []ma.Multiaddr {
	 := resolver{
		canResolve: startsWithDNSADDR,
		resolve: func( context.Context,  ma.Multiaddr,  int) ([]ma.Multiaddr, error) {
			return .multiaddrResolver.ResolveDNSAddr(, .ID, , maximumDNSADDRRecursion, )
		},
	}

	var  []ma.Multiaddr
	 := resolver{
		canResolve: func( ma.Multiaddr) bool {
			 := .TransportForDialing()
			if  == nil {
				return false
			}
			,  := .(transport.SkipResolver)
			return 

		},
		resolve: func( context.Context,  ma.Multiaddr,  int) ([]ma.Multiaddr, error) {
			 := .TransportForDialing()
			,  := .(transport.SkipResolver)
			if ! {
				return []ma.Multiaddr{}, nil
			}
			if .SkipResolve(, ) {
				 = append(, )
				return nil, nil
			}
			return []ma.Multiaddr{}, nil
		},
	}

	 := resolver{
		canResolve: func( ma.Multiaddr) bool {
			 := .TransportForDialing()
			if  == nil {
				return false
			}
			,  := .(transport.Resolver)
			return 
		},
		resolve: func( context.Context,  ma.Multiaddr,  int) ([]ma.Multiaddr, error) {
			 := .TransportForDialing()
			,  := .(transport.Resolver)
			if ! {
				return []ma.Multiaddr{}, nil
			}
			,  := .Resolve(, )
			if  != nil {
				return nil, 
			}
			if len() >  {
				 = [:]
			}
			return , nil
		},
	}

	 := resolver{
		canResolve: startsWithDNSComponent,
		resolve:    .multiaddrResolver.ResolveDNSComponent,
	}
	,  := chainResolvers(, .Addrs, maximumResolvedAddresses, []resolver{, , , })
	for ,  := range  {
		log.Warnf("Failed to resolve addr %s: %v", .addr, .err)
	}
	// Add skipped addresses back to the resolved addresses
	 = append(, ...)
	return stripP2PComponent()
}

func ( *Swarm) ( context.Context,  peer.ID,  ma.Multiaddr,  chan transport.DialUpdate) error {
	// check the dial backoff
	if ,  := network.GetForceDirectDial(); ! {
		if .backf.Backoff(, ) {
			return ErrDialBackoff
		}
	}

	// start the dial
	.limitedDial(, , , )

	return nil
}

func ( *Swarm) ( peer.ID,  ma.Multiaddr) bool {
	,  := .filterKnownUndialables(, []ma.Multiaddr{})
	return len() > 0
}

func ( *Swarm) ( ma.Multiaddr) bool {
	 := .TransportForDialing()
	return !.Proxy()
}

var quicDraft29DialMatcher = mafmt.And(mafmt.IP, mafmt.Base(ma.P_UDP), mafmt.Base(ma.P_QUIC))

// filterKnownUndialables takes a list of multiaddrs, and removes those
// that we definitely don't want to dial: addresses configured to be blocked,
// IPv6 link-local addresses, addresses without a dial-capable transport,
// addresses that we know to be our own, and addresses with a better transport
// available. This is an optimization to avoid wasting time on dials that we
// know are going to fail or for which we have a better alternative.
func ( *Swarm) ( peer.ID,  []ma.Multiaddr) ( []ma.Multiaddr,  []TransportError) {
	,  := .InterfaceListenAddresses()
	var  []ma.Multiaddr
	for ,  := range  {
		// we're only sure about filtering out /ip4 and /ip6 addresses, so far
		ma.ForEach(, func( ma.Component) bool {
			if .Protocol().Code == ma.P_IP4 || .Protocol().Code == ma.P_IP6 {
				 = append(, )
			}
			return false
		})
	}

	 = make([]TransportError, 0, len())

	// The order of checking for transport and filtering low priority addrs is important. If we
	// can only dial /webtransport, we don't want to filter /webtransport addresses out because
	// the peer had a /quic-v1 address

	// filter addresses with no transport
	 = ma.FilterAddrs(, func( ma.Multiaddr) bool {
		if .TransportForDialing() == nil {
			 := ErrNoTransport
			// We used to support QUIC draft-29 for a long time.
			// Provide a more useful error when attempting to dial a QUIC draft-29 address.
			if quicDraft29DialMatcher.Matches() {
				 = ErrQUICDraft29
			}
			 = append(, TransportError{Address: , Cause: })
			return false
		}
		return true
	})

	// filter low priority addresses among the addresses we can dial
	// We don't return an error for these addresses
	 = filterLowPriorityAddresses()

	// remove black holed addrs
	,  := .bhd.FilterAddrs()
	for ,  := range  {
		 = append(, TransportError{Address: , Cause: ErrDialRefusedBlackHole})
	}

	return ma.FilterAddrs(,
		// Linux and BSD treat an unspecified address when dialing as a localhost address.
		// Windows doesn't support this. We filter all such addresses out because peers
		// listening on unspecified addresses will advertise more specific addresses.
		// https://unix.stackexchange.com/a/419881
		// https://superuser.com/a/1755455
		func( ma.Multiaddr) bool {
			return !manet.IsIPUnspecified()
		},
		func( ma.Multiaddr) bool {
			if ma.Contains(, ) {
				 = append(, TransportError{Address: , Cause: ErrDialToSelf})
				return false
			}
			return true
		},
		// TODO: Consider allowing link-local addresses
		func( ma.Multiaddr) bool { return !manet.IsIP6LinkLocal() },
		func( ma.Multiaddr) bool {
			if .gater != nil && !.gater.InterceptAddrDial(, ) {
				 = append(, TransportError{Address: , Cause: ErrGaterDisallowedConnection})
				return false
			}
			return true
		},
	), 
}

// limitedDial will start a dial to the given peer when
// it is able, respecting the various different types of rate
// limiting that occur without using extra goroutines per addr
func ( *Swarm) ( context.Context,  peer.ID,  ma.Multiaddr,  chan transport.DialUpdate) {
	 := .dialTimeout
	if manet.IsPrivateAddr() && .dialTimeoutLocal < .dialTimeout {
		 = .dialTimeoutLocal
	}
	.limiter.AddDialJob(&dialJob{
		addr:    ,
		peer:    ,
		resp:    ,
		ctx:     ,
		timeout: ,
	})
}

// dialAddr is the actual dial for an addr, indirectly invoked through the limiter
func ( *Swarm) ( context.Context,  peer.ID,  ma.Multiaddr,  chan<- transport.DialUpdate) (transport.CapableConn, error) {
	// Just to double check. Costs nothing.
	if .local ==  {
		return nil, ErrDialToSelf
	}
	// Check before we start work
	if  := .Err();  != nil {
		log.Debugf("%s swarm not dialing. Context cancelled: %v. %s %s", .local, , , )
		return nil, 
	}
	log.Debugf("%s swarm dialing %s %s", .local, , )

	 := .TransportForDialing()
	if  == nil {
		return nil, ErrNoTransport
	}

	 := time.Now()
	var  transport.CapableConn
	var  error
	if ,  := .(transport.DialUpdater);  {
		,  = .DialWithUpdates(, , , )
	} else {
		,  = .Dial(, , )
	}

	// We're recording any error as a failure here.
	// Notably, this also applies to cancellations (i.e. if another dial attempt was faster).
	// This is ok since the black hole detector uses a very low threshold (5%).
	.bhd.RecordResult(,  == nil)

	if  != nil {
		if .metricsTracer != nil {
			.metricsTracer.FailedDialing(, , context.Cause())
		}
		return nil, 
	}
	canonicallog.LogPeerStatus(100, .RemotePeer(), .RemoteMultiaddr(), "connection_status", "established", "dir", "outbound")
	if .metricsTracer != nil {
		 := wrapWithMetrics(, .metricsTracer, , network.DirOutbound)
		.completedHandshake()
		 = 
	}

	// Trust the transport? Yeah... right.
	if .RemotePeer() !=  {
		.Close()
		 = fmt.Errorf("BUG in transport %T: tried to dial %s, dialed %s", , , .RemotePeer())
		log.Error()
		return nil, 
	}

	// success! we got one!
	return , nil
}

// TODO We should have a `IsFdConsuming() bool` method on the `Transport` interface in go-libp2p/core/transport.
// This function checks if any of the transport protocols in the address requires a file descriptor.
// For now:
// A Non-circuit address which has the TCP/UNIX protocol is deemed FD consuming.
// For a circuit-relay address, we look at the address of the relay server/proxy
// and use the same logic as above to decide.
func isFdConsumingAddr( ma.Multiaddr) bool {
	,  := ma.SplitFunc(, func( ma.Component) bool {
		return .Protocol().Code == ma.P_CIRCUIT
	})

	// for safety
	if  == nil {
		return true
	}

	,  := .ValueForProtocol(ma.P_TCP)
	,  := .ValueForProtocol(ma.P_UNIX)
	return  == nil ||  == nil
}

func isRelayAddr( ma.Multiaddr) bool {
	,  := .ValueForProtocol(ma.P_CIRCUIT)
	return  == nil
}

// filterLowPriorityAddresses removes addresses inplace for which we have a better alternative
//  1. If a /quic-v1 address is present, filter out /quic and /webtransport address on the same 2-tuple:
//     QUIC v1 is preferred over the deprecated QUIC draft-29, and given the choice, we prefer using
//     raw QUIC over using WebTransport.
//  2. If a /tcp address is present, filter out /ws or /wss addresses on the same 2-tuple:
//     We prefer using raw TCP over using WebSocket.
func filterLowPriorityAddresses( []ma.Multiaddr) []ma.Multiaddr {
	// make a map of QUIC v1 and TCP AddrPorts.
	 := make(map[netip.AddrPort]struct{})
	 := make(map[netip.AddrPort]struct{})
	for ,  := range  {
		switch {
		case isProtocolAddr(, ma.P_WEBTRANSPORT):
		case isProtocolAddr(, ma.P_QUIC_V1):
			,  := addrPort(, ma.P_UDP)
			if  != nil {
				continue
			}
			[] = struct{}{}
		case isProtocolAddr(, ma.P_WS) || isProtocolAddr(, ma.P_WSS):
		case isProtocolAddr(, ma.P_TCP):
			,  := addrPort(, ma.P_TCP)
			if  != nil {
				continue
			}
			[] = struct{}{}
		}
	}

	 := 0
	for ,  := range  {
		switch {
		case isProtocolAddr(, ma.P_WEBTRANSPORT) || isProtocolAddr(, ma.P_QUIC):
			,  := addrPort(, ma.P_UDP)
			if  != nil {
				break
			}
			if ,  := [];  {
				continue
			}
		case isProtocolAddr(, ma.P_WS) || isProtocolAddr(, ma.P_WSS):
			,  := addrPort(, ma.P_TCP)
			if  != nil {
				break
			}
			if ,  := [];  {
				continue
			}
		}
		[] = 
		++
	}
	return [:]
}

// addrPort returns the ip and port for a. p should be either ma.P_TCP or ma.P_UDP.
// a must be an (ip, TCP) or (ip, udp) address.
func addrPort( ma.Multiaddr,  int) (netip.AddrPort, error) {
	,  := manet.ToIP()
	if  != nil {
		return netip.AddrPort{}, 
	}
	,  := .ValueForProtocol()
	if  != nil {
		return netip.AddrPort{}, 
	}
	,  := strconv.Atoi()
	if  != nil {
		return netip.AddrPort{}, 
	}
	,  := netip.AddrFromSlice()
	if ! {
		return netip.AddrPort{}, fmt.Errorf("failed to parse IP %s", )
	}
	return netip.AddrPortFrom(, uint16()), nil
}