package config

import (
	
	
	
	
	
	
	

	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	bhost 
	blankhost 
	
	
	rcmgr 
	routed 
	
	tptu 
	
	circuitv2 
	relayv2 
	
	
	
	
	libp2pwebrtc 
	

	ma 
	manet 
	
	
	
)

// AddrsFactory is a function that takes a set of multiaddrs we're listening on and
// returns the set of multiaddrs we should advertise to the network.
type AddrsFactory = bhost.AddrsFactory

// NATManagerC is a NATManager constructor.
type NATManagerC func(network.Network) bhost.NATManager

type RoutingC func(host.Host) (routing.PeerRouting, error)

// AutoNATConfig defines the AutoNAT behavior for the libp2p host.
type AutoNATConfig struct {
	ForceReachability   *network.Reachability
	EnableService       bool
	ThrottleGlobalLimit int
	ThrottlePeerLimit   int
	ThrottleInterval    time.Duration
}

type Security struct {
	ID          protocol.ID
	Constructor interface{}
}

// Config describes a set of settings for a libp2p node
//
// This is *not* a stable interface. Use the options defined in the root
// package.
type Config struct {
	// UserAgent is the identifier this node will send to other peers when
	// identifying itself, e.g. via the identify protocol.
	//
	// Set it via the UserAgent option function.
	UserAgent string

	// ProtocolVersion is the protocol version that identifies the family
	// of protocols used by the peer in the Identify protocol. It is set
	// using the [ProtocolVersion] option.
	ProtocolVersion string

	PeerKey crypto.PrivKey

	QUICReuse          []fx.Option
	Transports         []fx.Option
	Muxers             []tptu.StreamMuxer
	SecurityTransports []Security
	Insecure           bool
	PSK                pnet.PSK

	DialTimeout time.Duration

	RelayCustom bool
	Relay       bool // should the relay transport be used

	EnableRelayService bool // should we run a circuitv2 relay (if publicly reachable)
	RelayServiceOpts   []relayv2.Option

	ListenAddrs     []ma.Multiaddr
	AddrsFactory    bhost.AddrsFactory
	ConnectionGater connmgr.ConnectionGater

	ConnManager     connmgr.ConnManager
	ResourceManager network.ResourceManager

	NATManager NATManagerC
	Peerstore  peerstore.Peerstore
	Reporter   metrics.Reporter

	MultiaddrResolver network.MultiaddrDNSResolver

	DisablePing bool

	Routing RoutingC

	EnableAutoRelay bool
	AutoRelayOpts   []autorelay.Option
	AutoNATConfig

	EnableHolePunching  bool
	HolePunchingOptions []holepunch.Option

	DisableMetrics       bool
	PrometheusRegisterer prometheus.Registerer

	DialRanker network.DialRanker

	SwarmOpts []swarm.Option

	DisableIdentifyAddressDiscovery bool

	EnableAutoNATv2 bool

	UDPBlackHoleSuccessCounter        *swarm.BlackHoleSuccessCounter
	CustomUDPBlackHoleSuccessCounter  bool
	IPv6BlackHoleSuccessCounter       *swarm.BlackHoleSuccessCounter
	CustomIPv6BlackHoleSuccessCounter bool

	UserFxOptions []fx.Option

	ShareTCPListener bool
}

func ( *Config) ( event.Bus,  bool) (*swarm.Swarm, error) {
	if .Peerstore == nil {
		return nil, fmt.Errorf("no peerstore specified")
	}

	// Check this early. Prevents us from even *starting* without verifying this.
	if pnet.ForcePrivateNetwork && len(.PSK) == 0 {
		log.Error("tried to create a libp2p node with no Private" +
			" Network Protector but usage of Private Networks" +
			" is forced by the environment")
		// Note: This is *also* checked the upgrader itself, so it'll be
		// enforced even *if* you don't use the libp2p constructor.
		return nil, pnet.ErrNotInPrivateNetwork
	}

	if .PeerKey == nil {
		return nil, fmt.Errorf("no peer key specified")
	}

	// Obtain Peer ID from public key
	,  := peer.IDFromPublicKey(.PeerKey.GetPublic())
	if  != nil {
		return nil, 
	}

	if  := .Peerstore.AddPrivKey(, .PeerKey);  != nil {
		return nil, 
	}
	if  := .Peerstore.AddPubKey(, .PeerKey.GetPublic());  != nil {
		return nil, 
	}

	 := append(.SwarmOpts,
		swarm.WithUDPBlackHoleSuccessCounter(.UDPBlackHoleSuccessCounter),
		swarm.WithIPv6BlackHoleSuccessCounter(.IPv6BlackHoleSuccessCounter),
	)
	if .Reporter != nil {
		 = append(, swarm.WithMetrics(.Reporter))
	}
	if .ConnectionGater != nil {
		 = append(, swarm.WithConnectionGater(.ConnectionGater))
	}
	if .DialTimeout != 0 {
		 = append(, swarm.WithDialTimeout(.DialTimeout))
	}
	if .ResourceManager != nil {
		 = append(, swarm.WithResourceManager(.ResourceManager))
	}
	if .MultiaddrResolver != nil {
		 = append(, swarm.WithMultiaddrResolver(.MultiaddrResolver))
	}
	if .DialRanker != nil {
		 = append(, swarm.WithDialRanker(.DialRanker))
	}

	if  {
		 = append(,
			swarm.WithMetricsTracer(swarm.NewMetricsTracer(swarm.WithRegisterer(.PrometheusRegisterer))))
	}
	// TODO: Make the swarm implementation configurable.
	return swarm.NewSwarm(, .Peerstore, , ...)
}

func ( *Config) () (host.Host, error) {
	, ,  := crypto.GenerateEd25519Key(rand.Reader)
	if  != nil {
		return nil, 
	}
	,  := pstoremem.NewPeerstore()
	if  != nil {
		return nil, 
	}

	 := Config{
		Transports:                  .Transports,
		Muxers:                      .Muxers,
		SecurityTransports:          .SecurityTransports,
		Insecure:                    .Insecure,
		PSK:                         .PSK,
		ConnectionGater:             .ConnectionGater,
		Reporter:                    .Reporter,
		PeerKey:                     ,
		Peerstore:                   ,
		DialRanker:                  swarm.NoDelayDialRanker,
		UDPBlackHoleSuccessCounter:  .UDPBlackHoleSuccessCounter,
		IPv6BlackHoleSuccessCounter: .IPv6BlackHoleSuccessCounter,
		ResourceManager:             .ResourceManager,
		SwarmOpts: []swarm.Option{
			// Don't update black hole state for failed autonat dials
			swarm.WithReadOnlyBlackHoleDetector(),
		},
	}
	,  := .addTransports()
	if  != nil {
		return nil, 
	}
	var  host.Host
	 = append(,
		fx.Provide(eventbus.NewBus),
		fx.Provide(func( fx.Lifecycle,  event.Bus) (*swarm.Swarm, error) {
			.Append(fx.Hook{
				OnStop: func(context.Context) error {
					return .Close()
				}})
			,  := .makeSwarm(, false)
			return , 
		}),
		fx.Provide(func( *swarm.Swarm) *blankhost.BlankHost {
			return blankhost.NewBlankHost()
		}),
		fx.Provide(func( *blankhost.BlankHost) host.Host {
			return 
		}),
		fx.Provide(func() crypto.PrivKey { return  }),
		fx.Provide(func( host.Host) peer.ID { return .ID() }),
		fx.Invoke(func( *blankhost.BlankHost) {
			 = 
		}),
	)
	 := fx.New(...)
	if  := .Err();  != nil {
		return nil, 
	}
	 = .Start(context.Background())
	if  != nil {
		return nil, 
	}
	go func() {
		<-.Network().(*swarm.Swarm).Done()
		.Stop(context.Background())
	}()
	return , nil
}

func ( *Config) () ([]fx.Option, error) {
	 := []fx.Option{
		fx.WithLogger(func() fxevent.Logger { return getFXLogger() }),
		fx.Provide(fx.Annotate(tptu.New, fx.ParamTags(`name:"security"`))),
		fx.Supply(.Muxers),
		fx.Provide(func() connmgr.ConnectionGater { return .ConnectionGater }),
		fx.Provide(func() pnet.PSK { return .PSK }),
		fx.Provide(func() network.ResourceManager { return .ResourceManager }),
		fx.Provide(func( transport.Upgrader) *tcpreuse.ConnMgr {
			if !.ShareTCPListener {
				return nil
			}
			return tcpreuse.NewConnMgr(tcpreuse.EnvReuseportVal, )
		}),
		fx.Provide(func( *quicreuse.ConnManager,  *swarm.Swarm) libp2pwebrtc.ListenUDPFn {
			 := func( string,  *net.UDPAddr) bool {
				 := map[string]struct{}{}
				for ,  := range .ListenAddresses() {
					if ,  := .ValueForProtocol(ma.P_QUIC_V1);  == nil {
						, ,  := manet.DialArgs()
						if  != nil {
							return false
						}
						[+"_"+] = struct{}{}
					}
				}
				,  := [+"_"+.String()]
				return 
			}

			return func( string,  *net.UDPAddr) (net.PacketConn, error) {
				if (, ) {
					return .SharedNonQUICPacketConn(, )
				}
				return net.ListenUDP(, )
			}
		}),
	}
	 = append(, .Transports...)
	if .Insecure {
		 = append(,
			fx.Provide(
				fx.Annotate(
					func( peer.ID,  crypto.PrivKey) []sec.SecureTransport {
						return []sec.SecureTransport{insecure.NewWithIdentity(insecure.ID, , )}
					},
					fx.ResultTags(`name:"security"`),
				),
			),
		)
	} else {
		// fx groups are unordered, but we need to preserve the order of the security transports
		// First of all, we construct the security transports that are needed,
		// and save them to a group call security_unordered.
		for ,  := range .SecurityTransports {
			 := fmt.Sprintf(`name:"security_%s"`, .ID)
			 = append(, fx.Supply(fx.Annotate(.ID, fx.ResultTags())))
			 = append(,
				fx.Provide(fx.Annotate(
					.Constructor,
					fx.ParamTags(),
					fx.As(new(sec.SecureTransport)),
					fx.ResultTags(`group:"security_unordered"`),
				)),
			)
		}
		// Then we consume the group security_unordered, and order them by the user's preference.
		 = append(, fx.Provide(
			fx.Annotate(
				func( []sec.SecureTransport) ([]sec.SecureTransport, error) {
					if len() != len(.SecurityTransports) {
						return nil, errors.New("inconsistent length for security transports")
					}
					 := make([]sec.SecureTransport, 0, len())
					for ,  := range .SecurityTransports {
						for ,  := range  {
							if .ID != .ID() {
								continue
							}
							 = append(, )
						}
					}
					return , nil
				},
				fx.ParamTags(`group:"security_unordered"`),
				fx.ResultTags(`name:"security"`),
			)))
	}

	 = append(, fx.Provide(PrivKeyToStatelessResetKey))
	 = append(, fx.Provide(PrivKeyToTokenGeneratorKey))
	if .QUICReuse != nil {
		 = append(, .QUICReuse...)
	} else {
		 = append(,
			fx.Provide(func( quic.StatelessResetKey,  quic.TokenGeneratorKey,  network.ResourceManager,  fx.Lifecycle) (*quicreuse.ConnManager, error) {
				 := []quicreuse.Option{
					quicreuse.ConnContext(func( context.Context,  *quic.ClientInfo) (context.Context, error) {
						// even if creating the quic maddr fails, let the rcmgr decide what to do with the connection
						,  := quicreuse.ToQuicMultiaddr(.RemoteAddr, quic.Version1)
						if  != nil {
							 = nil
						}
						,  := .OpenConnection(network.DirInbound, false, )
						if  != nil {
							return , 
						}
						 = network.WithConnManagementScope(, )
						context.AfterFunc(, func() {
							.Done()
						})
						return , nil
					}),
					quicreuse.VerifySourceAddress(func( net.Addr) bool {
						return .VerifySourceAddress()
					}),
				}
				if !.DisableMetrics {
					 = append(, quicreuse.EnableMetrics(.PrometheusRegisterer))
				}
				,  := quicreuse.NewConnManager(, , ...)
				if  != nil {
					return nil, 
				}
				.Append(fx.StopHook(.Close))
				return , nil
			}),
		)
	}

	 = append(, fx.Invoke(
		fx.Annotate(
			func( *swarm.Swarm,  []transport.Transport) error {
				for ,  := range  {
					if  := .AddTransport();  != nil {
						return 
					}
				}
				return nil
			},
			fx.ParamTags("", `group:"transport"`),
		)),
	)
	if .Relay {
		 = append(, fx.Invoke(circuitv2.AddTransport))
	}
	return , nil
}

func ( *Config) ( *swarm.Swarm,  event.Bus,  *autonatv2.AutoNAT) (*bhost.BasicHost, error) {
	,  := bhost.NewHost(, &bhost.HostOpts{
		EventBus:                        ,
		ConnManager:                     .ConnManager,
		AddrsFactory:                    .AddrsFactory,
		NATManager:                      .NATManager,
		EnablePing:                      !.DisablePing,
		UserAgent:                       .UserAgent,
		ProtocolVersion:                 .ProtocolVersion,
		EnableHolePunching:              .EnableHolePunching,
		HolePunchingOptions:             .HolePunchingOptions,
		EnableRelayService:              .EnableRelayService,
		RelayServiceOpts:                .RelayServiceOpts,
		EnableMetrics:                   !.DisableMetrics,
		PrometheusRegisterer:            .PrometheusRegisterer,
		DisableIdentifyAddressDiscovery: .DisableIdentifyAddressDiscovery,
		AutoNATv2:                       ,
	})
	if  != nil {
		return nil, 
	}
	return , nil
}

func ( *Config) () error {
	if .EnableAutoRelay && !.Relay {
		return fmt.Errorf("cannot enable autorelay; relay is not enabled")
	}
	// If possible check that the resource manager conn limit is higher than the
	// limit set in the conn manager.
	if ,  := .ResourceManager.(connmgr.GetConnLimiter);  {
		 := .ConnManager.CheckLimit()
		if  != nil {
			log.Warn(fmt.Sprintf("rcmgr limit conflicts with connmgr limit: %v", ))
		}
	}

	if len(.PSK) > 0 && .ShareTCPListener {
		return errors.New("cannot use shared TCP listener with PSK")
	}

	return nil
}

// NewNode constructs a new libp2p Host from the Config.
//
// This function consumes the config. Do not reuse it (really!).
func ( *Config) () (host.Host, error) {

	 := .validate()
	if  != nil {
		if .ResourceManager != nil {
			.ResourceManager.Close()
		}
		if .ConnManager != nil {
			.ConnManager.Close()
		}
		if .Peerstore != nil {
			.Peerstore.Close()
		}

		return nil, 
	}

	if !.DisableMetrics {
		rcmgr.MustRegisterWith(.PrometheusRegisterer)
	}

	 := []fx.Option{
		fx.Provide(func() event.Bus {
			return eventbus.NewBus(eventbus.WithMetricsTracer(eventbus.NewMetricsTracer(eventbus.WithRegisterer(.PrometheusRegisterer))))
		}),
		fx.Provide(func() crypto.PrivKey {
			return .PeerKey
		}),
		// Make sure the swarm constructor depends on the quicreuse.ConnManager.
		// That way, the ConnManager will be started before the swarm, and more importantly,
		// the swarm will be stopped before the ConnManager.
		fx.Provide(func( event.Bus,  *quicreuse.ConnManager,  fx.Lifecycle) (*swarm.Swarm, error) {
			,  := .makeSwarm(, !.DisableMetrics)
			if  != nil {
				return nil, 
			}
			.Append(fx.Hook{
				OnStart: func(context.Context) error {
					// TODO: This method succeeds if listening on one address succeeds. We
					// should probably fail if listening on *any* addr fails.
					return .Listen(.ListenAddrs...)
				},
				OnStop: func(context.Context) error {
					return .Close()
				},
			})
			return , nil
		}),
		fx.Provide(func() (*autonatv2.AutoNAT, error) {
			if !.EnableAutoNATv2 {
				return nil, nil
			}
			,  := .makeAutoNATV2Host()
			if  != nil {
				return nil, 
			}
			var  autonatv2.MetricsTracer
			if !.DisableMetrics {
				 = autonatv2.NewMetricsTracer(.PrometheusRegisterer)
			}
			,  := autonatv2.New(, autonatv2.WithMetricsTracer())
			if  != nil {
				return nil, fmt.Errorf("failed to create autonatv2: %w", )
			}
			return , nil
		}),
		fx.Provide(.newBasicHost),
		fx.Provide(func( *bhost.BasicHost) identify.IDService {
			return .IDService()
		}),
		fx.Provide(func( *bhost.BasicHost) host.Host {
			return 
		}),
		fx.Provide(func( *swarm.Swarm) peer.ID { return .LocalPeer() }),
	}
	,  := .addTransports()
	if  != nil {
		return nil, 
	}
	 = append(, ...)

	// Configure routing
	if .Routing != nil {
		 = append(,
			fx.Provide(.Routing),
			fx.Provide(func( host.Host,  routing.PeerRouting) *routed.RoutedHost {
				return routed.Wrap(, )
			}),
		)
	}

	// enable autorelay
	 = append(,
		fx.Invoke(func( *bhost.BasicHost,  fx.Lifecycle) error {
			if .EnableAutoRelay {
				if !.DisableMetrics {
					 := autorelay.WithMetricsTracer(
						autorelay.NewMetricsTracer(autorelay.WithRegisterer(.PrometheusRegisterer)))
					 := []autorelay.Option{}
					.AutoRelayOpts = append(, .AutoRelayOpts...)
				}

				,  := autorelay.NewAutoRelay(, .AutoRelayOpts...)
				if  != nil {
					return 
				}
				.Append(fx.StartStopHook(.Start, .Close))
				return nil
			}
			return nil
		}),
	)

	var  *bhost.BasicHost
	 = append(, fx.Invoke(func( *bhost.BasicHost) {  =  }))
	 = append(, fx.Invoke(func( *bhost.BasicHost,  fx.Lifecycle) {
		.Append(fx.StartHook(.Start))
	}))

	var  *routed.RoutedHost
	if .Routing != nil {
		 = append(, fx.Invoke(func( *routed.RoutedHost) {  =  }))
	}

	 = append(, .UserFxOptions...)

	 := fx.New(...)
	if  := .Start(context.Background());  != nil {
		return nil, 
	}

	if  := .addAutoNAT();  != nil {
		.Stop(context.Background())
		if .Routing != nil {
			.Close()
		} else {
			.Close()
		}
		return nil, 
	}

	if .Routing != nil {
		return &closableRoutedHost{App: , RoutedHost: }, nil
	}
	return &closableBasicHost{App: , BasicHost: }, nil
}

func ( *Config) ( *bhost.BasicHost) error {
	// Only use public addresses for autonat
	 := func() []ma.Multiaddr {
		return slices.DeleteFunc(.AllAddrs(), func( ma.Multiaddr) bool { return !manet.IsPublicAddr() })
	}
	if .AddrsFactory != nil {
		 = func() []ma.Multiaddr {
			return slices.DeleteFunc(
				slices.Clone(.AddrsFactory(.AllAddrs())),
				func( ma.Multiaddr) bool { return !manet.IsPublicAddr() })
		}
	}
	 := []autonat.Option{
		autonat.UsingAddresses(),
	}
	if !.DisableMetrics {
		 = append(, autonat.WithMetricsTracer(
			autonat.NewMetricsTracer(autonat.WithRegisterer(.PrometheusRegisterer)),
		))
	}
	if .AutoNATConfig.ThrottleInterval != 0 {
		 = append(,
			autonat.WithThrottling(.AutoNATConfig.ThrottleGlobalLimit, .AutoNATConfig.ThrottleInterval),
			autonat.WithPeerThrottling(.AutoNATConfig.ThrottlePeerLimit))
	}
	if .AutoNATConfig.EnableService {
		, ,  := crypto.GenerateEd25519Key(rand.Reader)
		if  != nil {
			return 
		}
		,  := pstoremem.NewPeerstore()
		if  != nil {
			return 
		}

		// Pull out the pieces of the config that we _actually_ care about.
		// Specifically, don't set up things like listeners, identify, etc.
		 := Config{
			Transports:         .Transports,
			Muxers:             .Muxers,
			SecurityTransports: .SecurityTransports,
			Insecure:           .Insecure,
			PSK:                .PSK,
			ConnectionGater:    .ConnectionGater,
			Reporter:           .Reporter,
			PeerKey:            ,
			Peerstore:          ,
			DialRanker:         swarm.NoDelayDialRanker,
			ResourceManager:    .ResourceManager,
			SwarmOpts: []swarm.Option{
				swarm.WithUDPBlackHoleSuccessCounter(nil),
				swarm.WithIPv6BlackHoleSuccessCounter(nil),
			},
		}

		,  := .addTransports()
		if  != nil {
			return 
		}
		var  *swarm.Swarm

		 = append(,
			fx.Provide(eventbus.NewBus),
			fx.Provide(func( fx.Lifecycle,  event.Bus) (*swarm.Swarm, error) {
				.Append(fx.Hook{
					OnStop: func(context.Context) error {
						return .Close()
					}})
				var  error
				,  = .makeSwarm(, false)
				return , 

			}),
			fx.Provide(func( *swarm.Swarm) peer.ID { return .LocalPeer() }),
			fx.Provide(func() crypto.PrivKey { return  }),
		)
		 := fx.New(...)
		if  := .Err();  != nil {
			return 
		}
		 = .Start(context.Background())
		if  != nil {
			return 
		}
		go func() {
			<-.Done() // The swarm used for autonat has closed, we can cleanup now
			.Stop(context.Background())
		}()
		 = append(, autonat.EnableService())
	}
	if .AutoNATConfig.ForceReachability != nil {
		 = append(, autonat.WithReachability(*.AutoNATConfig.ForceReachability))
	}

	,  := autonat.New(, ...)
	if  != nil {
		return fmt.Errorf("autonat init failed: %w", )
	}
	.SetAutoNat()
	return nil
}

// Option is a libp2p config option that can be given to the libp2p constructor
// (`libp2p.New`).
type Option func(cfg *Config) error

// Apply applies the given options to the config, returning the first error
// encountered (if any).
func ( *Config) ( ...Option) error {
	for ,  := range  {
		if  == nil {
			continue
		}
		if  := ();  != nil {
			return 
		}
	}
	return nil
}