package libp2pwebtransport

import (
	
	
	
	
	
	
	

	
	tpt 
	
	
	

	ma 
	
	
	
)

const queueLen = 16
const handshakeTimeout = 10 * time.Second

type connKey struct{}

type listener struct {
	transport       *transport
	isStaticTLSConf bool
	reuseListener   quicreuse.Listener

	server webtransport.Server

	ctx       context.Context
	ctxCancel context.CancelFunc

	serverClosed chan struct{} // is closed when server.Serve returns

	addr      net.Addr
	multiaddr ma.Multiaddr

	queue chan tpt.CapableConn

	mx           sync.Mutex
	pendingConns map[*quic.Conn]*negotiatingConn
}

var _ tpt.Listener = &listener{}

func newListener( quicreuse.Listener,  *transport,  bool) (tpt.Listener, error) {
	,  := toWebtransportMultiaddr(.Addr())
	if  != nil {
		return nil, 
	}

	 := &listener{
		reuseListener:   ,
		transport:       ,
		isStaticTLSConf: ,
		queue:           make(chan tpt.CapableConn, queueLen),
		serverClosed:    make(chan struct{}),
		addr:            .Addr(),
		multiaddr:       ,
		server: webtransport.Server{
			H3: &http3.Server{
				ConnContext: func( context.Context,  *quic.Conn) context.Context {
					return context.WithValue(, connKey{}, )
				},
				EnableDatagrams: true,
			},
			CheckOrigin: func( *http.Request) bool { return true },
		},
		pendingConns: make(map[*quic.Conn]*negotiatingConn),
	}
	.ctx, .ctxCancel = context.WithCancel(context.Background())
	 := http.NewServeMux()
	.HandleFunc(webtransportHTTPEndpoint, .httpHandler)
	.server.H3.Handler = 
	webtransport.ConfigureHTTP3Server(.server.H3)
	go func() {
		defer close(.serverClosed)
		for {
			,  := .reuseListener.Accept(context.Background())
			if  != nil {
				log.Debug("serving failed", "addr", .Addr(), "error", )
				return
			}
			 = .startHandshake()
			if  != nil {
				log.Debug("failed to start handshake", "error", )
				continue
			}
			go .server.ServeQUICConn()
		}
	}()
	return , nil
}

func ( *listener) ( *quic.Conn) error {
	,  := context.WithTimeout(.ctx, handshakeTimeout)
	 := context.AfterFunc(, func() {
		log.Debug("failed to handshake on conn", "remote_addr", .RemoteAddr())
		.CloseWithError(1, "")
		.mx.Lock()
		delete(.pendingConns, )
		.mx.Unlock()
	})
	.mx.Lock()
	defer .mx.Unlock()
	// don't add to map if the context is already cancelled
	if .Err() != nil {
		()
		return .Err()
	}
	.pendingConns[] = &negotiatingConn{
		Conn:                 ,
		ctx:                  ,
		cancel:               ,
		stopHandshakeTimeout: ,
	}
	return nil
}

// negotiatingConn is a wrapper around a *quic.Conn that lets us wrap it in
// our own context for the duration of the upgrade process. Upgrading a quic
// connection to an h3 connection to a webtransport session.
type negotiatingConn struct {
	*quic.Conn
	ctx    context.Context
	cancel context.CancelFunc
	// stopHandshakeTimeout is a function that stops triggering the handshake timeout. Returns true if the handshake timeout was not triggered.
	stopHandshakeTimeout func() bool
	err                  error
}

func ( *negotiatingConn) () error {
	defer .cancel()
	if .stopHandshakeTimeout != nil {
		// cancel the handshake timeout function
		if !.stopHandshakeTimeout() {
			.err = errTimeout
		}
		.stopHandshakeTimeout = nil
	}
	if .err != nil {
		return .err
	}
	return nil
}

var errTimeout = errors.New("timeout")

func ( *listener) ( http.ResponseWriter,  *http.Request) {
	,  := .URL.Query()["type"]
	if ! || len() != 1 || [0] != "noise" {
		.WriteHeader(http.StatusBadRequest)
		return
	}
	,  := stringToWebtransportMultiaddr(.RemoteAddr)
	if  != nil {
		// This should never happen.
		log.Error("converting remote address failed", "remote", .RemoteAddr, "error", )
		.WriteHeader(http.StatusBadRequest)
		return
	}
	if .transport.gater != nil && !.transport.gater.InterceptAccept(&connMultiaddrs{local: .multiaddr, remote: }) {
		.WriteHeader(http.StatusForbidden)
		return
	}
	,  := network.UnwrapConnManagementScope(.Context())
	if  != nil {
		 = nil
		// Don't error here.
		// Setup scope if we don't have scope from quicreuse.
		// This is better than failing so that users that don't use quicreuse.ConnContext option with the resource
		// manager still work correctly.
	}
	if  == nil {
		,  = .transport.rcmgr.OpenConnection(network.DirInbound, false, )
		if  != nil {
			log.Debug("resource manager blocked incoming connection", "addr", .RemoteAddr, "error", )
			.WriteHeader(http.StatusServiceUnavailable)
			return
		}
	}
	 = .httpHandlerWithConnScope(, , )
	if  != nil {
		.Done()
	}
}

func ( *listener) ( http.ResponseWriter,  *http.Request,  network.ConnManagementScope) error {
	,  := .server.Upgrade(, )
	if  != nil {
		log.Debug("upgrade failed", "error", )
		// TODO: think about the status code to use here
		.WriteHeader(500)
		return 
	}
	,  := context.WithTimeout(.ctx, handshakeTimeout)
	,  := .handshake(, )
	if  != nil {
		()
		log.Debug("handshake failed", "error", )
		.CloseWithError(1, "")
		return 
	}
	()

	if .transport.gater != nil && !.transport.gater.InterceptSecured(network.DirInbound, .RemotePeer(), ) {
		// TODO: can we close with a specific error here?
		.CloseWithError(errorCodeConnectionGating, "")
		return errors.New("gater blocked connection")
	}

	if  := .SetPeer(.RemotePeer());  != nil {
		log.Debug("resource manager blocked incoming connection for peer", "peer", .RemotePeer(), "addr", .RemoteAddr, "error", )
		.CloseWithError(1, "")
		return 
	}

	 := .Context().Value(connKey{})
	if  == nil {
		log.Error("missing conn from context")
		.CloseWithError(1, "")
		return errors.New("invalid context")
	}
	 := .(*quic.Conn)

	.mx.Lock()
	,  := .pendingConns[]
	delete(.pendingConns, )
	.mx.Unlock()
	if ! {
		log.Debug("handshake timed out", "remote_addr", .RemoteAddr)
		.CloseWithError(1, "")
		return errTimeout
	}
	if  := .StopHandshakeTimeout();  != nil {
		log.Debug("handshake timed out", "remote_addr", .RemoteAddr)
		.CloseWithError(1, "")
		return 
	}

	 := newConn(.transport, , , , )
	.transport.addConn(, )
	select {
	case .queue <- :
	default:
		log.Debug("accept queue full, dropping incoming connection", "peer", .RemotePeer(), "addr", .RemoteAddr, "error", )
		.Close()
		return errors.New("accept queue full")
	}

	return nil
}

func ( *listener) () (tpt.CapableConn, error) {
	select {
	case <-.ctx.Done():
		return nil, tpt.ErrListenerClosed
	case  := <-.queue:
		return , nil
	}
}

func ( *listener) ( context.Context,  *webtransport.Session) (*connSecurityMultiaddrs, error) {
	,  := toWebtransportMultiaddr(.LocalAddr())
	if  != nil {
		return nil, fmt.Errorf("error determiniting local addr: %w", )
	}
	,  := toWebtransportMultiaddr(.RemoteAddr())
	if  != nil {
		return nil, fmt.Errorf("error determiniting remote addr: %w", )
	}

	,  := .AcceptStream()
	if  != nil {
		return nil, 
	}
	var  [][]byte
	if !.isStaticTLSConf {
		 = .transport.certManager.SerializedCertHashes()
	}

	,  := .transport.noise.WithSessionOptions(noise.EarlyData(
		nil,
		newEarlyDataSender(&pb.NoiseExtensions{WebtransportCerthashes: }),
	))
	if  != nil {
		return nil, fmt.Errorf("failed to initialize Noise session: %w", )
	}
	,  := .SecureInbound(, webtransportStream{Stream: , wsess: }, "")
	if  != nil {
		return nil, 
	}

	return &connSecurityMultiaddrs{
		ConnSecurity:   ,
		ConnMultiaddrs: &connMultiaddrs{local: , remote: },
	}, nil
}

func ( *listener) () net.Addr {
	return .addr
}

func ( *listener) () ma.Multiaddr {
	if .transport.certManager == nil {
		return .multiaddr
	}
	return .multiaddr.Encapsulate(.transport.certManager.AddrComponent())
}

func ( *listener) () error {
	.ctxCancel()
	.reuseListener.Close()
	 := .server.Close()
	<-.serverClosed
:
	for {
		select {
		case  := <-.queue:
			.Close()
		default:
			break 
		}
	}
	return 
}