package upgrader

import (
	
	
	
	

	
	
	

	tec 
	logging 
	manet 
)

var log = logging.Logger("upgrader")

type listener struct {
	transport.GatedMaListener

	transport transport.Transport
	upgrader  *upgrader
	rcmgr     network.ResourceManager

	incoming chan transport.CapableConn
	err      error

	// Used for backpressure
	threshold *threshold

	// Canceling this context isn't sufficient to tear down the listener.
	// Call close.
	ctx    context.Context
	cancel func()
}

var _ transport.Listener = (*listener)(nil)

// Close closes the listener.
func ( *listener) () error {
	// Do this first to try to get any relevant errors.
	 := .GatedMaListener.Close()

	.cancel()
	// Drain and wait.
	for  := range .incoming {
		.Close()
	}
	return 
}

// handles inbound connections.
//
// This function does a few interesting things that should be noted:
//
//  1. It logs and discards temporary/transient errors (errors with a Temporary()
//     function that returns true).
//  2. It stops accepting new connections once AcceptQueueLength connections have
//     been fully negotiated but not accepted. This gives us a basic backpressure
//     mechanism while still allowing us to negotiate connections in parallel.
func ( *listener) () {
	var  sync.WaitGroup
	defer func() {
		// make sure we're closed
		.GatedMaListener.Close()
		if .err == nil {
			.err = fmt.Errorf("listener closed")
		}

		.Wait()
		close(.incoming)
	}()

	var  tec.TempErrCatcher
	for .ctx.Err() == nil {
		, ,  := .GatedMaListener.Accept()
		if  != nil {
			// Note: function may pause the accept loop.
			if .IsTemporary() {
				log.Info("temporary accept error", "err", )
				continue
			}
			.err = 
			return
		}
		.Reset()

		if  == nil {
			log.Error("BUG: got nil connScope for incoming connection", "remote_multiaddr", .RemoteMultiaddr())
			.Close()
			continue
		}

		// The go routine below calls Release when the context is
		// canceled so there's no need to wait on it here.
		.threshold.Wait()

		log.Debug("listener got connection",
			"listener", ,
			"local_multiaddr", .LocalMultiaddr(),
			"remote_multiaddr", .RemoteMultiaddr())

		.Add(1)
		go func() {
			defer .Done()

			,  := context.WithTimeout(.ctx, .upgrader.acceptTimeout)
			defer ()

			,  := .upgrader.Upgrade(, .transport, , network.DirInbound, "", )
			if  != nil {
				// Don't bother bubbling this up. We just failed
				// to completely negotiate the connection.
				log.Debug("accept upgrade error",
					"err", ,
					"local_multiaddr", .LocalMultiaddr(),
					"remote_multiaddr", .RemoteMultiaddr())
				.Done()
				return
			}

			log.Debug("listener accepted connection",
				"listener", ,
				"connection", )

			// This records the fact that the connection has been
			// setup and is waiting to be accepted. This call
			// *never* blocks, even if we go over the threshold. It
			// simply ensures that calls to Wait block while we're
			// over the threshold.
			.threshold.Acquire()
			defer .threshold.Release()

			select {
			case .incoming <- :
			case <-.Done():
				// Listener not closed but the accept timeout expired.
				if .ctx.Err() == nil {
					log.Warn("listener dropped connection due to slow accept", "remote_multiaddr", .RemoteMultiaddr(), "peer", .RemotePeer())
				}
				.CloseWithError(network.ConnRateLimited)
			}
		}()
	}
}

// Accept accepts a connection.
func ( *listener) () (transport.CapableConn, error) {
	for  := range .incoming {
		// Could have been sitting there for a while.
		if !.IsClosed() {
			return , nil
		}
	}
	if strings.Contains(.err.Error(), "use of closed network connection") {
		return nil, transport.ErrListenerClosed
	}
	return nil, .err
}

func ( *listener) () string {
	if ,  := .transport.(fmt.Stringer);  {
		return fmt.Sprintf("<stream.Listener[%s] %s>", , .Multiaddr())
	}
	return fmt.Sprintf("<stream.Listener %s>", .Multiaddr())
}

type gatedMaListener struct {
	manet.Listener
	rcmgr     network.ResourceManager
	connGater connmgr.ConnectionGater
}

var _ transport.GatedMaListener = &gatedMaListener{}

func ( *gatedMaListener) () (manet.Conn, network.ConnManagementScope, error) {
	for {
		,  := .Listener.Accept()
		if  != nil {
			return nil, nil, 
		}
		// gate the connection if applicable
		if .connGater != nil && !.connGater.InterceptAccept() {
			log.Debug("gater blocked incoming connection",
				"local_multiaddr", .LocalMultiaddr(),
				"remote_multiaddr", .RemoteMultiaddr())
			if  := .Close();  != nil {
				log.Warn("failed to close incoming connection rejected by gater", "err", )
			}
			continue
		}

		,  := .rcmgr.OpenConnection(network.DirInbound, true, .RemoteMultiaddr())
		if  != nil {
			log.Debug("resource manager blocked accept of new connection", "err", )
			if  := .Close();  != nil {
				log.Warn("failed to open incoming connection. Rejected by resource manager", "err", )
			}
			continue
		}
		return , , nil
	}
}