package fasthttp

import (
	
	
	
	
	
	
	
)

// Dial dials the given TCP addr using tcp4.
//
// This function has the following additional features comparing to net.Dial:
//
//   - It reduces load on DNS resolver by caching resolved TCP addressed
//     for DNSCacheDuration.
//   - It dials all the resolved TCP addresses in round-robin manner until
//     connection is established. This may be useful if certain addresses
//     are temporarily unreachable.
//   - It returns ErrDialTimeout if connection cannot be established during
//     DefaultDialTimeout seconds. Use DialTimeout for customizing dial timeout.
//
// This dialer is intended for custom code wrapping before passing
// to Client.Dial or HostClient.Dial.
//
// For instance, per-host counters and/or limits may be implemented
// by such wrappers.
//
// The addr passed to the function must contain port. Example addr values:
//
//   - foobar.baz:443
//   - foo.bar:80
//   - aaa.com:8080
func ( string) (net.Conn, error) {
	return defaultDialer.Dial()
}

// DialTimeout dials the given TCP addr using tcp4 using the given timeout.
//
// This function has the following additional features comparing to net.Dial:
//
//   - It reduces load on DNS resolver by caching resolved TCP addressed
//     for DNSCacheDuration.
//   - It dials all the resolved TCP addresses in round-robin manner until
//     connection is established. This may be useful if certain addresses
//     are temporarily unreachable.
//
// This dialer is intended for custom code wrapping before passing
// to Client.Dial or HostClient.Dial.
//
// For instance, per-host counters and/or limits may be implemented
// by such wrappers.
//
// The addr passed to the function must contain port. Example addr values:
//
//   - foobar.baz:443
//   - foo.bar:80
//   - aaa.com:8080
func ( string,  time.Duration) (net.Conn, error) {
	return defaultDialer.DialTimeout(, )
}

// DialDualStack dials the given TCP addr using both tcp4 and tcp6.
//
// This function has the following additional features comparing to net.Dial:
//
//   - It reduces load on DNS resolver by caching resolved TCP addressed
//     for DNSCacheDuration.
//   - It dials all the resolved TCP addresses in round-robin manner until
//     connection is established. This may be useful if certain addresses
//     are temporarily unreachable.
//   - It returns ErrDialTimeout if connection cannot be established during
//     DefaultDialTimeout seconds. Use DialDualStackTimeout for custom dial
//     timeout.
//
// This dialer is intended for custom code wrapping before passing
// to Client.Dial or HostClient.Dial.
//
// For instance, per-host counters and/or limits may be implemented
// by such wrappers.
//
// The addr passed to the function must contain port. Example addr values:
//
//   - foobar.baz:443
//   - foo.bar:80
//   - aaa.com:8080
func ( string) (net.Conn, error) {
	return defaultDialer.DialDualStack()
}

// DialDualStackTimeout dials the given TCP addr using both tcp4 and tcp6
// using the given timeout.
//
// This function has the following additional features comparing to net.Dial:
//
//   - It reduces load on DNS resolver by caching resolved TCP addressed
//     for DNSCacheDuration.
//   - It dials all the resolved TCP addresses in round-robin manner until
//     connection is established. This may be useful if certain addresses
//     are temporarily unreachable.
//
// This dialer is intended for custom code wrapping before passing
// to Client.Dial or HostClient.Dial.
//
// For instance, per-host counters and/or limits may be implemented
// by such wrappers.
//
// The addr passed to the function must contain port. Example addr values:
//
//   - foobar.baz:443
//   - foo.bar:80
//   - aaa.com:8080
func ( string,  time.Duration) (net.Conn, error) {
	return defaultDialer.DialDualStackTimeout(, )
}

var defaultDialer = &TCPDialer{Concurrency: 1000}

// Resolver represents interface of the tcp resolver.
type Resolver interface {
	LookupIPAddr(context.Context, string) (names []net.IPAddr, err error)
}

// TCPDialer contains options to control a group of Dial calls.
type TCPDialer struct {
	// Concurrency controls the maximum number of concurrent Dials
	// that can be performed using this object.
	// Setting this to 0 means unlimited.
	//
	// WARNING: This can only be changed before the first Dial.
	// Changes made after the first Dial will not affect anything.
	Concurrency int

	// LocalAddr is the local address to use when dialing an
	// address.
	// If nil, a local address is automatically chosen.
	LocalAddr *net.TCPAddr

	// This may be used to override DNS resolving policy, like this:
	// var dialer = &fasthttp.TCPDialer{
	// 	Resolver: &net.Resolver{
	// 		PreferGo:     true,
	// 		StrictErrors: false,
	// 		Dial: func (ctx context.Context, network, address string) (net.Conn, error) {
	// 			d := net.Dialer{}
	// 			return d.DialContext(ctx, "udp", "8.8.8.8:53")
	// 		},
	// 	},
	// }
	Resolver Resolver

	// DNSCacheDuration may be used to override the default DNS cache duration (DefaultDNSCacheDuration)
	DNSCacheDuration time.Duration

	tcpAddrsMap sync.Map

	concurrencyCh chan struct{}

	once sync.Once
}

// Dial dials the given TCP addr using tcp4.
//
// This function has the following additional features comparing to net.Dial:
//
//   - It reduces load on DNS resolver by caching resolved TCP addressed
//     for DNSCacheDuration.
//   - It dials all the resolved TCP addresses in round-robin manner until
//     connection is established. This may be useful if certain addresses
//     are temporarily unreachable.
//   - It returns ErrDialTimeout if connection cannot be established during
//     DefaultDialTimeout seconds. Use DialTimeout for customizing dial timeout.
//
// This dialer is intended for custom code wrapping before passing
// to Client.Dial or HostClient.Dial.
//
// For instance, per-host counters and/or limits may be implemented
// by such wrappers.
//
// The addr passed to the function must contain port. Example addr values:
//
//   - foobar.baz:443
//   - foo.bar:80
//   - aaa.com:8080
func ( *TCPDialer) ( string) (net.Conn, error) {
	return .dial(, false, DefaultDialTimeout)
}

// DialTimeout dials the given TCP addr using tcp4 using the given timeout.
//
// This function has the following additional features comparing to net.Dial:
//
//   - It reduces load on DNS resolver by caching resolved TCP addressed
//     for DNSCacheDuration.
//   - It dials all the resolved TCP addresses in round-robin manner until
//     connection is established. This may be useful if certain addresses
//     are temporarily unreachable.
//
// This dialer is intended for custom code wrapping before passing
// to Client.Dial or HostClient.Dial.
//
// For instance, per-host counters and/or limits may be implemented
// by such wrappers.
//
// The addr passed to the function must contain port. Example addr values:
//
//   - foobar.baz:443
//   - foo.bar:80
//   - aaa.com:8080
func ( *TCPDialer) ( string,  time.Duration) (net.Conn, error) {
	return .dial(, false, )
}

// DialDualStack dials the given TCP addr using both tcp4 and tcp6.
//
// This function has the following additional features comparing to net.Dial:
//
//   - It reduces load on DNS resolver by caching resolved TCP addressed
//     for DNSCacheDuration.
//   - It dials all the resolved TCP addresses in round-robin manner until
//     connection is established. This may be useful if certain addresses
//     are temporarily unreachable.
//   - It returns ErrDialTimeout if connection cannot be established during
//     DefaultDialTimeout seconds. Use DialDualStackTimeout for custom dial
//     timeout.
//
// This dialer is intended for custom code wrapping before passing
// to Client.Dial or HostClient.Dial.
//
// For instance, per-host counters and/or limits may be implemented
// by such wrappers.
//
// The addr passed to the function must contain port. Example addr values:
//
//   - foobar.baz:443
//   - foo.bar:80
//   - aaa.com:8080
func ( *TCPDialer) ( string) (net.Conn, error) {
	return .dial(, true, DefaultDialTimeout)
}

// DialDualStackTimeout dials the given TCP addr using both tcp4 and tcp6
// using the given timeout.
//
// This function has the following additional features comparing to net.Dial:
//
//   - It reduces load on DNS resolver by caching resolved TCP addressed
//     for DNSCacheDuration.
//   - It dials all the resolved TCP addresses in round-robin manner until
//     connection is established. This may be useful if certain addresses
//     are temporarily unreachable.
//
// This dialer is intended for custom code wrapping before passing
// to Client.Dial or HostClient.Dial.
//
// For instance, per-host counters and/or limits may be implemented
// by such wrappers.
//
// The addr passed to the function must contain port. Example addr values:
//
//   - foobar.baz:443
//   - foo.bar:80
//   - aaa.com:8080
func ( *TCPDialer) ( string,  time.Duration) (net.Conn, error) {
	return .dial(, true, )
}

func ( *TCPDialer) ( string,  bool,  time.Duration) (net.Conn, error) {
	.once.Do(func() {
		if .Concurrency > 0 {
			.concurrencyCh = make(chan struct{}, .Concurrency)
		}

		if .DNSCacheDuration == 0 {
			.DNSCacheDuration = DefaultDNSCacheDuration
		}

		go .tcpAddrsClean()
	})

	 := time.Now().Add()
	, ,  := .getTCPAddrs(, , )
	if  != nil {
		return nil, 
	}
	 := "tcp4"
	if  {
		 = "tcp"
	}

	var  net.Conn
	 := uint32(len())
	for  > 0 {
		,  = .tryDial(, &[%], , .concurrencyCh)
		if  == nil {
			return , nil
		}
		if  == ErrDialTimeout {
			return nil, 
		}
		++
		--
	}
	return nil, 
}

func ( *TCPDialer) ( string,  *net.TCPAddr,  time.Time,  chan struct{}) (net.Conn, error) {
	 := time.Until()
	if  <= 0 {
		return nil, ErrDialTimeout
	}

	if  != nil {
		select {
		case  <- struct{}{}:
		default:
			 := AcquireTimer()
			 := false
			select {
			case  <- struct{}{}:
			case <-.C:
				 = true
			}
			ReleaseTimer()
			if  {
				return nil, ErrDialTimeout
			}
		}
		defer func() { <- }()
	}

	 := net.Dialer{}
	if .LocalAddr != nil {
		.LocalAddr = .LocalAddr
	}

	,  := context.WithDeadline(context.Background(), )
	defer ()
	,  := .DialContext(, , .String())
	if  != nil && .Err() == context.DeadlineExceeded {
		return nil, ErrDialTimeout
	}
	return , 
}

// ErrDialTimeout is returned when TCP dialing is timed out.
var ErrDialTimeout = errors.New("dialing to the given TCP address timed out")

// DefaultDialTimeout is timeout used by Dial and DialDualStack
// for establishing TCP connections.
const DefaultDialTimeout = 3 * time.Second

type tcpAddrEntry struct {
	addrs    []net.TCPAddr
	addrsIdx uint32

	pending     int32
	resolveTime time.Time
}

// DefaultDNSCacheDuration is the duration for caching resolved TCP addresses
// by Dial* functions.
const DefaultDNSCacheDuration = time.Minute

func ( *TCPDialer) () {
	 := 2 * .DNSCacheDuration
	for {
		time.Sleep(time.Second)
		 := time.Now()
		.tcpAddrsMap.Range(func(,  interface{}) bool {
			if ,  := .(*tcpAddrEntry);  && .Sub(.resolveTime) >  {
				.tcpAddrsMap.Delete()
			}
			return true
		})

	}
}

func ( *TCPDialer) ( string,  bool,  time.Time) ([]net.TCPAddr, uint32, error) {
	,  := .tcpAddrsMap.Load()
	,  := .(*tcpAddrEntry)
	if  &&  &&  != nil && time.Since(.resolveTime) > .DNSCacheDuration {
		// Only let one goroutine re-resolve at a time.
		if atomic.SwapInt32(&.pending, 1) == 0 {
			 = nil
		}
	}

	if  == nil {
		,  := resolveTCPAddrs(, , .Resolver, )
		if  != nil {
			,  := .tcpAddrsMap.Load()
			,  = .(*tcpAddrEntry)
			if  &&  &&  != nil {
				// Set pending to 0 so another goroutine can retry.
				atomic.StoreInt32(&.pending, 0)
			}
			return nil, 0, 
		}

		 = &tcpAddrEntry{
			addrs:       ,
			resolveTime: time.Now(),
		}
		.tcpAddrsMap.Store(, )
	}

	 := atomic.AddUint32(&.addrsIdx, 1)
	return .addrs, , nil
}

func resolveTCPAddrs( string,  bool,  Resolver,  time.Time) ([]net.TCPAddr, error) {
	, ,  := net.SplitHostPort()
	if  != nil {
		return nil, 
	}
	,  := strconv.Atoi()
	if  != nil {
		return nil, 
	}

	if  == nil {
		 = net.DefaultResolver
	}

	,  := context.WithDeadline(context.Background(), )
	defer ()
	,  := .LookupIPAddr(, )
	if  != nil {
		return nil, 
	}

	 := len()
	 := make([]net.TCPAddr, 0, )
	for  := 0;  < ; ++ {
		 := []
		if ! && .IP.To4() == nil {
			continue
		}
		 = append(, net.TCPAddr{
			IP:   .IP,
			Port: ,
			Zone: .Zone,
		})
	}
	if len() == 0 {
		return nil, errNoDNSEntries
	}
	return , nil
}

var errNoDNSEntries = errors.New("couldn't find DNS entries for the given domain. Try using DialDualStack")