// Package quicreuse provides `quicreuse.ConnManager`, which provides functionality
// for reusing QUIC transports for various purposes, like listening & dialing, having
// multiple QUIC listeners on the same address with different ALPNs, and sharing the
// same address with non QUIC transports like WebRTC.

package quicreuse

import (
	
	
	
	
	
	

	
	
	ma 
	manet 
	
	
	
	
)

var log = gologshim.Logger("quicreuse")

type QUICListener interface {
	Accept(ctx context.Context) (*quic.Conn, error)
	Close() error
	Addr() net.Addr
}

var _ QUICListener = &quic.Listener{}

type QUICTransport interface {
	Listen(tlsConf *tls.Config, conf *quic.Config) (QUICListener, error)
	Dial(ctx context.Context, addr net.Addr, tlsConf *tls.Config, conf *quic.Config) (*quic.Conn, error)
	WriteTo(b []byte, addr net.Addr) (int, error)
	ReadNonQUICPacket(ctx context.Context, b []byte) (int, net.Addr, error)
	io.Closer
}

// ConnManager enables QUIC and WebTransport transports to listen on the same port, reusing
// listen addresses for dialing, and provides a PacketConn for sharing the listen address
// with other protocols like WebRTC.
// Reusing the listen address for dialing helps with address discovery and hole punching. For details
// of the reuse logic see `ListenQUICAndAssociate` and `DialQUIC`.
// If reuseport is disabled using the `DisableReuseport` option, listen addresses are not used for
// dialing.
type ConnManager struct {
	reuseUDP4       *reuse
	reuseUDP6       *reuse
	enableReuseport bool

	listenUDP          listenUDP
	sourceIPSelectorFn func() (SourceIPSelector, error)

	enableMetrics bool
	registerer    prometheus.Registerer

	serverConfig *quic.Config
	clientConfig *quic.Config

	quicListenersMu sync.Mutex
	quicListeners   map[string]quicListenerEntry

	srk         quic.StatelessResetKey
	tokenKey    quic.TokenGeneratorKey
	connContext connContextFunc

	verifySourceAddress func(addr net.Addr) bool

	qlogTracerDir string
}

type quicListenerEntry struct {
	refCount int
	ln       *quicListener
}

func defaultListenUDP( string,  *net.UDPAddr) (net.PacketConn, error) {
	return net.ListenUDP(, )
}

func defaultSourceIPSelectorFn() (SourceIPSelector, error) {
	,  := netroute.New()
	return &netrouteSourceIPSelector{routes: }, 
}

const (
	unverifiedAddressNewConnectionRPS   = 1000
	unverifiedAddressNewConnectionBurst = 1000
)

// NewConnManager returns a new ConnManager
func ( quic.StatelessResetKey,  quic.TokenGeneratorKey,  ...Option) (*ConnManager, error) {
	 := &ConnManager{
		enableReuseport:    true,
		quicListeners:      make(map[string]quicListenerEntry),
		srk:                ,
		tokenKey:           ,
		registerer:         prometheus.DefaultRegisterer,
		listenUDP:          defaultListenUDP,
		sourceIPSelectorFn: defaultSourceIPSelectorFn,
	}
	for ,  := range  {
		if  := ();  != nil {
			return nil, 
		}
	}

	 := quicConfig.Clone()
	// quic-go takes care of disabling this if QLOGDIR Environment variable isn't present
	.Tracer = qlog.DefaultConnectionTracer
	 := .Clone()

	.clientConfig = 
	.serverConfig = 

	// Verify source addresses when under high load.
	// This is ensures that the number of spoofed/unverified addresses that are passed to downstream rate limiters
	// are limited, which enables IP address based rate limiting.
	 := rate.NewLimiter(unverifiedAddressNewConnectionRPS, unverifiedAddressNewConnectionBurst)
	 := .verifySourceAddress
	.verifySourceAddress = func( net.Addr) bool {
		if .Allow() {
			if  != nil {
				return ()
			}
			return false
		}
		return true
	}
	if .enableReuseport {
		.reuseUDP4 = newReuse(&, &, .listenUDP, .sourceIPSelectorFn, .connContext, .verifySourceAddress)
		.reuseUDP6 = newReuse(&, &, .listenUDP, .sourceIPSelectorFn, .connContext, .verifySourceAddress)
	}
	return , nil
}

func ( *ConnManager) ( string) (*reuse, error) {
	switch  {
	case "udp4":
		return .reuseUDP4, nil
	case "udp6":
		return .reuseUDP6, nil
	default:
		return nil, errors.New("invalid network: must be either udp4 or udp6")
	}
}

// LendTransport is an advanced method used to lend an existing QUICTransport
// to the ConnManager. The ConnManager will close the returned channel when it
// is done with the transport, so that the owner may safely close the transport.
func ( *ConnManager) ( string,  QUICTransport,  net.PacketConn) (<-chan struct{}, error) {
	.quicListenersMu.Lock()
	defer .quicListenersMu.Unlock()

	,  := .LocalAddr().(*net.UDPAddr)
	if ! {
		return nil, errors.New("expected a conn.LocalAddr() to return a *net.UDPAddr")
	}

	 := &refcountedTransport{
		QUICTransport:    ,
		packetConn:       ,
		borrowDoneSignal: make(chan struct{}),
	}

	var  *reuse
	,  := .getReuse()
	if  != nil {
		return nil, 
	}
	return .borrowDoneSignal, .AddTransport(, )
}

// ListenQUIC listens for quic connections with the provided `tlsConf.NextProtos` ALPNs on `addr`. The same addr can be shared between
// different ALPNs.
func ( *ConnManager) ( ma.Multiaddr,  *tls.Config,  func( *quic.Conn,  uint64) bool) (Listener, error) {
	return .ListenQUICAndAssociate(nil, , , )
}

// ListenQUICAndAssociate listens for quic connections with the provided `tlsConf.NextProtos` ALPNs on `addr`. The same addr can be shared between
// different ALPNs.
// The QUIC Transport used for listening is tagged with the `association`. Any subsequent `TransportWithAssociationForDial`,
// or `DialQUIC` calls with the same `association` will reuse the QUIC Transport used by this method.
// A common use of associations is to ensure /quic dials use the quic listening address and /webtransport dials use the
// WebTransport listening address.
func ( *ConnManager) ( any,  ma.Multiaddr,  *tls.Config,  func( *quic.Conn,  uint64) bool) (Listener, error) {
	, ,  := manet.DialArgs()
	if  != nil {
		return nil, 
	}
	,  := net.ResolveUDPAddr(, )
	if  != nil {
		return nil, 
	}

	.quicListenersMu.Lock()
	defer .quicListenersMu.Unlock()

	 := .String()
	,  := .quicListeners[]
	if ! {
		,  := .transportForListen(, )
		if  != nil {
			return nil, 
		}
		,  := newQuicListener(, .serverConfig)
		if  != nil {
			return nil, 
		}
		 = .LocalAddr().String()
		 = quicListenerEntry{ln: }
	}
	if .enableReuseport &&  != nil {
		if ,  := .ln.transport.(*refcountedTransport); ! {
			log.Warn("reuseport is enabled, association is non-nil, but the transport is not a refcountedTransport.")
		}
	}
	,  := .ln.Add(, , , func() {
		.onListenerClosed()
	})
	if  != nil {
		if .refCount <= 0 {
			.ln.Close()
		}
		return nil, 
	}
	.refCount++
	.quicListeners[] = 
	return , nil
}

func ( *ConnManager) ( string) {
	.quicListenersMu.Lock()
	defer .quicListenersMu.Unlock()

	 := .quicListeners[]
	.refCount = .refCount - 1
	if .refCount <= 0 {
		delete(.quicListeners, )
		.ln.Close()
	} else {
		.quicListeners[] = 
	}
}

// SharedNonQUICPacketConn returns a `net.PacketConn` for `laddr` for non QUIC uses.
func ( *ConnManager) ( string,  *net.UDPAddr) (net.PacketConn, error) {
	.quicListenersMu.Lock()
	defer .quicListenersMu.Unlock()
	 := .String()
	,  := .quicListeners[]
	if ! {
		return nil, errors.New("expected to be able to share with a QUIC listener, but no QUIC listener found. The QUIC listener should start first")
	}
	 := .ln.transport
	if ,  := .(*refcountedTransport);  {
		.IncreaseCount()
		,  := context.WithCancel(context.Background())
		return &nonQUICPacketConn{
			ctx:             ,
			ctxCancel:       ,
			owningTransport: ,
			tr:              .QUICTransport,
		}, nil
	}
	return nil, errors.New("expected to be able to share with a QUIC listener, but the QUIC listener is not using a refcountedTransport. `DisableReuseport` should not be set")
}

func ( *ConnManager) ( string,  *net.UDPAddr) (RefCountedQUICTransport, error) {
	if .enableReuseport {
		,  := .getReuse()
		if  != nil {
			return nil, 
		}
		,  := .TransportForListen(, )
		if  != nil {
			return nil, 
		}
		return , nil
	}

	,  := .listenUDP(, )
	if  != nil {
		return nil, 
	}
	return .newSingleOwnerTransport(), nil
}

type associationKey struct{}

// WithAssociation returns a new context with the given association. Used in
// DialQUIC to prefer a transport that has the given association.
func ( context.Context,  any) context.Context {
	return context.WithValue(, associationKey{}, )
}

// DialQUIC dials `raddr`. Use `WithAssociation` to select a specific transport that was previously used for listening.
// see the documentation for `ListenQUICAndAssociate` for details on associate.
// The priority order for reusing the transport is as follows:
// - Listening transport with the same association
// - Any other listening transport
// - Any transport previously used for dialing
// If none of these are available, it'll create a new transport.
func ( *ConnManager) ( context.Context,  ma.Multiaddr,  *tls.Config,  func( *quic.Conn,  uint64) bool) (*quic.Conn, error) {
	, ,  := FromQuicMultiaddr()
	if  != nil {
		return nil, 
	}
	, ,  := manet.DialArgs()
	if  != nil {
		return nil, 
	}

	 := .clientConfig.Clone()
	.AllowConnectionWindowIncrease = 

	if  == quic.Version1 {
		// The endpoint has explicit support for QUIC v1, so we'll only use that version.
		.Versions = []quic.Version{quic.Version1}
	} else {
		return nil, errors.New("unknown QUIC version")
	}

	var  RefCountedQUICTransport
	 := .Value(associationKey{})
	,  = .TransportWithAssociationForDial(, , )
	if  != nil {
		return nil, 
	}
	,  := .Dial(, , , )
	if  != nil {
		.DecreaseCount()
		return nil, 
	}
	return , nil
}

// TransportForDial returns a transport for dialing `raddr`.
// If reuseport is enabled, it attempts to reuse the QUIC Transport used for
// previous listens or dials.
func ( *ConnManager) ( string,  *net.UDPAddr) (RefCountedQUICTransport, error) {
	return .TransportWithAssociationForDial(nil, , )
}

// TransportWithAssociationForDial returns a transport for dialing `raddr`.
// If reuseport is enabled, it attempts to reuse the QUIC Transport previously used for listening with `ListenQuicAndAssociate`
// with the same `association`. If it fails to do so, it uses any other previously used transport.
func ( *ConnManager) ( any,  string,  *net.UDPAddr) (RefCountedQUICTransport, error) {
	if .enableReuseport {
		,  := .getReuse()
		if  != nil {
			return nil, 
		}
		return .TransportWithAssociationForDial(, , )
	}

	var  *net.UDPAddr
	switch  {
	case "udp4":
		 = &net.UDPAddr{IP: net.IPv4zero, Port: 0}
	case "udp6":
		 = &net.UDPAddr{IP: net.IPv6zero, Port: 0}
	}
	,  := .listenUDP(, )
	if  != nil {
		return nil, 
	}
	return .newSingleOwnerTransport(), nil
}

func ( *ConnManager) ( net.PacketConn) *singleOwnerTransport {
	return &singleOwnerTransport{
		Transport: &wrappedQUICTransport{
			Transport: newQUICTransport(
				,
				&.tokenKey,
				&.srk,
				.connContext,
				.verifySourceAddress,
			),
		},
		packetConn: }
}

// Protocols returns the supported QUIC protocols. The only supported protocol at the moment is /quic-v1.
func ( *ConnManager) () []int {
	return []int{ma.P_QUIC_V1}
}

func ( *ConnManager) () error {
	if !.enableReuseport {
		return nil
	}
	if  := .reuseUDP6.Close();  != nil {
		return 
	}
	return .reuseUDP4.Close()
}

func ( *ConnManager) () *quic.Config {
	return .clientConfig
}

// wrappedQUICTransport wraps a `quic.Transport` to confirm to `QUICTransport`
type wrappedQUICTransport struct {
	*quic.Transport
}

var _ QUICTransport = (*wrappedQUICTransport)(nil)

func ( *wrappedQUICTransport) ( *tls.Config,  *quic.Config) (QUICListener, error) {
	return .Transport.Listen(, )
}

func newQUICTransport(
	 net.PacketConn,
	 *quic.TokenGeneratorKey,
	 *quic.StatelessResetKey,
	 connContextFunc,
	 func( net.Addr) bool,
) *quic.Transport {
	return &quic.Transport{
		Conn:                ,
		TokenGeneratorKey:   ,
		StatelessResetKey:   ,
		ConnContext:         ,
		VerifySourceAddress: ,
	}
}