package socket

import (
	
	
	
	
	
	
	
	
	

	
	
)

// Actor timing constants. These match the Rust reference
// (iroh/src/socket/remote_map/remote_state.rs:52,66,74 and socket.rs).
const (
	// HeartbeatInterval is how often the actor wakes to keep paths alive and
	// re-evaluate path selection. remote_state.rs HEARTBEAT_INTERVAL / socket.rs.
	HeartbeatInterval = 5 * time.Second

	// UpgradeInterval is how often the actor tries to upgrade to a better path
	// even when a working non-relay route exists. remote_state.rs:66.
	UpgradeInterval = 60 * time.Second

	// HolepunchAttemptsInterval throttles hole-punch attempts when the NAT
	// candidate set has not changed. remote_state.rs:52.
	HolepunchAttemptsInterval = 5 * time.Second

	// PathMaxIdleTimeout is the idle timeout for a non-relay path.
	// iroh/src/socket.rs PATH_MAX_IDLE_TIMEOUT.
	PathMaxIdleTimeout = 15 * time.Second

	// RelayPathMaxIdleTimeout is the idle timeout for a relay path.
	// iroh/src/socket.rs RELAY_PATH_MAX_IDLE_TIMEOUT.
	RelayPathMaxIdleTimeout = 30 * time.Second

	// ActorMaxIdleTimeout is how long an actor with no connections stays alive
	// before it exits and deregisters. remote_state.rs:74.
	ActorMaxIdleTimeout = 60 * time.Second
)

// ErrExtensionNotNegotiated is returned by hole-punching and other operations
// that depend on a QUIC extension that is not negotiated on an active
// connection. Endpoint defaults advertise qng multipath; QNT/DISCO
// hole-punching is still gated separately.
var ErrExtensionNotNegotiated = errors.New("socket: QUIC extension not negotiated (qng X1/X2/X3 gate)")

// Connection is the minimal view of a QUIC connection the [RemoteStateActor]
// needs. The iroh package adapts a qng *quic.Conn to it; tests use a fake. It
// stays small on purpose: the actor only reads liveness and RTT.
//
// SmoothedRTT returns the connection's active-path smoothed RTT. Done is closed
// when the connection ends. RemoteAddr reports the path the connection is on,
// so the actor can register it as a candidate path.
type Connection interface {
	// SmoothedRTT returns the smoothed round-trip time of the active path.
	SmoothedRTT() time.Duration
	// Done is closed when the connection is closed.
	Done() <-chan struct{}
	// RemoteAddr returns the transport address the connection is using.
	RemoteAddr() Addr
}

type multipathConnection interface {
	MultipathNegotiated() bool
}

type pathOpeningConnection interface {
	OpenPath(context.Context) error
}

type natTraversalRoundConnection interface {
	AddNATTraversalAddress(netip.AddrPort) error
	InitiateNATTraversalRound(context.Context) ([]netip.AddrPort, error)
}

type natTraversalRemoteAddressConnection interface {
	NATTraversalAddresses() ([]netip.AddrPort, error)
}

type natTraversalRemoteAddressSeedConnection interface {
	AddRemoteNATTraversalAddress(netip.AddrPort) error
}

// PathInfo is qng multipath path state observed through a [Connection]. Addr and
// RTT are set only when qng reports them; socket must not fabricate Addr from
// the connection's original RemoteAddr.
type PathInfo struct {
	// ID is the QUIC multipath PathID.
	ID uint32
	// Validated reports whether the path can carry non-probing application data.
	Validated bool
	// Addr is the path's transport address, when HasAddr is true.
	Addr Addr
	// HasAddr reports whether Addr was observed from qng route metadata.
	HasAddr bool
	// RTT is the path's smoothed round-trip time, when HasRTT is true.
	RTT time.Duration
	// HasRTT reports whether RTT was observed from qng per-path state.
	HasRTT bool
	// BytesInFlight is the path's current application-data bytes in flight,
	// when HasBytesInFlight is true.
	BytesInFlight uint64
	// HasBytesInFlight reports whether BytesInFlight was observed from qng
	// per-path state.
	HasBytesInFlight bool
	// BytesSent is the cumulative application-data bytes sent on this path,
	// when HasBytesSent is true.
	BytesSent uint64
	// HasBytesSent reports whether BytesSent was observed from qng per-path
	// state.
	HasBytesSent bool
	// BytesReceived is the cumulative application-data bytes received on this
	// path, when HasBytesReceived is true.
	BytesReceived uint64
	// HasBytesReceived reports whether BytesReceived was observed from qng
	// per-path state.
	HasBytesReceived bool
	// CongestionWindow is the path's current congestion window, when
	// HasCongestionWindow is true.
	CongestionWindow uint64
	// HasCongestionWindow reports whether CongestionWindow was observed from qng
	// per-path state.
	HasCongestionWindow bool
	// LostPackets is the number of application-data packets declared lost on
	// this path, when HasLoss is true.
	LostPackets uint64
	// LostBytes is the number of application-data bytes declared lost on this
	// path, when HasLoss is true.
	LostBytes uint64
	// HasLoss reports whether LostPackets and LostBytes were observed from qng
	// per-path state.
	HasLoss bool
	// Selected reports whether this path is currently selected for application
	// data transmission.
	Selected bool
}

type pathObservingConnection interface {
	Paths() []PathInfo
}

type natTraversalAddressConnection interface {
	AddNATTraversalAddress(netip.AddrPort) error
	RemoveNATTraversalAddress(netip.AddrPort) error
}

// RemoteInfo is a snapshot of known addresses for a remote endpoint.
type RemoteInfo struct {
	ID    key.EndpointID
	Addrs []TransportAddrInfo
}

// ResolvedAddr is a transport address plus lookup provenance.
type ResolvedAddr struct {
	Addr       netaddr.TransportAddr
	Provenance string
}

// ResolveFunc streams additional transport addresses for a remote endpoint. It
// is supplied by the iroh package (which owns the address-lookup services) so
// the socket package does not import iroh. A nil ResolveFunc disables
// lookup-driven resolution.
//
// It is the hook for the Rust RemoteStateActor::resolve_remote path
// (remote_state.rs:843), wired in slice G's address lookup.
type ResolveFunc func(ctx context.Context, id key.EndpointID) iter.Seq2[ResolvedAddr, error]

// remoteMessage is the actor inbox message. Exactly one field is set.
type remoteMessage struct {
	addConnection *addConnectionMsg
	resolve       *resolveMsg
	resolved      *resolvedMsg
	connClosed    Connection // a registered connection's Done fired
}

// addConnectionMsg registers a new connection with the actor and returns a path
// event subscription.
type addConnectionMsg struct {
	conn  Connection
	reply chan<- (<-chan PathEvent)
}

// resolveMsg asks the actor to resolve more addresses for the remote and add
// them as candidate paths. reply receives nil on success or the lookup error.
type resolveMsg struct {
	addrs netaddr.EndpointAddr
	reply chan<- error
}

type resolvedMsg struct {
	addr ResolvedAddr
}

// connState is the actor's per-connection bookkeeping.
type connState struct {
	conn      Connection
	addr      Addr
	paths     []Addr
	hasDirect bool
	// cancel ends the path-event subscription created for this connection, so
	// its delivery goroutine exits even if the subscriber stopped reading.
	cancel func()
}

// RemoteStateActor manages all connection and path state for a single remote
// endpoint. Exactly one goroutine runs per remote, driven by a single select
// loop over the inbox (which carries add-connection, resolve, and
// connection-closed messages) and timers (heartbeat, upgrade, idle teardown). It
// is the Go analog of the Rust RemoteStateActor
// (iroh/src/socket/remote_map/remote_state.rs).
//
// The actor does not build NAT traversal frames itself. It advertises vetted
// local candidates and asks qng to start traversal rounds; qng owns probe
// timers, response matching, and route-bearing path opening. Path selection is
// driven by qng path observability.
//
// Create an actor with the RemoteMap; do not construct one directly.
type RemoteStateActor struct {
	id       key.EndpointID
	selector PathSelector
	resolve  ResolveFunc
	idle     time.Duration
	metrics  *Metrics
	watcher  *PathWatcher

	// inbox carries messages from the RemoteMap and from per-connection watcher
	// goroutines. It is buffered so callers do not block.
	inbox chan remoteMessage
	// done is closed when the actor goroutine returns.
	done chan struct{}

	// onExit is called once, when the actor goroutine returns, so the RemoteMap
	// can deregister it under the same lock that inserts it (the O12 invariant).
	onExit func()

	// mu guards the fields below, which SendDatagram and SelectedPath read
	// concurrently with the actor loop.
	mu       sync.Mutex
	paths    *RemotePathState
	conns    map[Connection]*connState
	selected *Addr
	localNAT []netip.AddrPort

	// noHolepunch suppresses upgrade-tick NAT traversal and direct-path
	// validation. Set by RemoteMap at spawn (atomically, because the actor
	// loop is already running) for endpoints without IP transports: there is
	// no direct path to punch toward, and a traversal round initiated on a
	// relay-only connection stalls its in-flight relay streams.
	noHolepunch atomic.Bool
}

// newRemoteStateActor creates and starts an actor for id. The returned actor is
// already running its loop in a goroutine; it stops when ctx is cancelled or it
// idles out, calling onExit on the way out.
func newRemoteStateActor( context.Context,  key.EndpointID,  PathSelector,  ResolveFunc,  time.Duration,  *Metrics,  func()) *RemoteStateActor {
	if  == nil {
		 = BiasedRttPathSelector{}
	}
	if  <= 0 {
		 = ActorMaxIdleTimeout
	}
	 := &RemoteStateActor{
		id:       ,
		selector: ,
		resolve:  ,
		idle:     ,
		metrics:  ,
		watcher:  NewPathWatcher(),
		inbox:    make(chan remoteMessage, 16),
		done:     make(chan struct{}),
		onExit:   ,
		paths:    NewRemotePathState(),
		conns:    make(map[Connection]*connState),
	}
	go .run()
	return 
}

// ID returns the remote endpoint this actor manages.
func ( *RemoteStateActor) () key.EndpointID { return .id }

// donec is closed when the actor goroutine has exited.
func ( *RemoteStateActor) () <-chan struct{} { return .done }

// AddConnection registers conn with the actor and returns a channel of path
// events for it. ok is false if the actor stopped before it could register the
// connection; the caller should retry with a fresh actor. The returned channel
// is closed when the connection closes or the actor stops; the subscription's
// lifetime is managed by the actor, so the caller may simply stop reading.
func ( *RemoteStateActor) ( Connection) ( <-chan PathEvent,  bool) {
	 := make(chan (<-chan PathEvent), 1)
	select {
	case .inbox <- remoteMessage{addConnection: &addConnectionMsg{conn: , reply: }}:
	case <-.done:
		return nil, false
	}
	select {
	case  := <-:
		return , true
	case <-.done:
		return nil, false
	}
}

// ResolveRemote asks the actor to resolve more addresses for addr via the
// [ResolveFunc] and register them as candidate paths as they arrive. It returns
// once the resolver stream has been started. With no resolver and no addrs it
// returns nil immediately.
func ( *RemoteStateActor) ( netaddr.EndpointAddr) error {
	 := make(chan error, 1)
	select {
	case .inbox <- remoteMessage{resolve: &resolveMsg{addrs: , reply: }}:
	case <-.done:
		return context.Canceled
	}
	select {
	case  := <-:
		return 
	case <-.done:
		return context.Canceled
	}
}

// run is the single actor loop. It exits when ctx is cancelled or when the actor
// has had no connections for its idle timeout, deregistering via onExit.
func ( *RemoteStateActor) ( context.Context) {
	defer close(.done)
	defer .watcher.Close()
	// Cancel the per-connection subscriptions before the watcher drain above:
	// their channels may be unread (AddConnection callers can discard them), and
	// Close's drain would wait forever on an abandoned reader.
	defer func() {
		.mu.Lock()
		 := make([]*connState, 0, len(.conns))
		for ,  := range .conns {
			 = append(, )
		}
		.mu.Unlock()
		for ,  := range  {
			.cancel()
		}
	}()
	if .onExit != nil {
		defer .onExit()
	}

	 := time.NewTicker(HeartbeatInterval)
	defer .Stop()
	 := time.NewTicker(UpgradeInterval)
	defer .Stop()
	 := time.NewTimer(.idle)
	defer .Stop()

	for {
		select {
		case <-.Done():
			return
		case  := <-.inbox:
			.handle(, )
		case <-.C:
			// Idle out only when there are no connections. The timer is reset to
			// the full timeout whenever a connection is present, so a stray fire
			// while connections exist is rescheduled here.
			.mu.Lock()
			 := len(.conns)
			.mu.Unlock()
			if  == 0 {
				return
			}
			resetTimer(, .idle)
		case <-.C:
			.reselect()
		case <-.C:
			// Upgrade tick: fallback behind the punch-on-ready trigger.
			// Direct selected: nothing to upgrade toward. Relay selected:
			// only a punch can help — ValidateDirectPath opens a path over
			// the current four-tuple (the relay itself) and just burns its
			// timeout. Off-loop: they block for seconds and the actor must
			// keep processing messages.
			,  := .SelectedPath()
			switch {
			case .noHolepunch.Load():
				// No IP transports: there is no direct path to upgrade
				// toward, and a traversal round on a relay-only connection
				// stalls its in-flight relay streams.
			case  && .Kind() == AddrIP:
			case  && .Kind() == AddrRelay:
				go func() { _ = .TriggerHolepunch() }()
			default:
				go func() {
					_ = .ValidateDirectPath(context.Background())
					_ = .TriggerHolepunch()
				}()
			}
			.reselect()
		}
		// Keep the idle timer disarmed (reset to a fresh full timeout) while
		// connections exist, so an active actor never idles out.
		.mu.Lock()
		 := len(.conns)
		.mu.Unlock()
		if  > 0 {
			resetTimer(, .idle)
		}
	}
}

// handle dispatches one inbox message.
func ( *RemoteStateActor) ( context.Context,  remoteMessage) {
	switch {
	case .addConnection != nil:
		.handleAddConnection(.addConnection)
	case .resolve != nil:
		.handleResolve(, .resolve)
	case .resolved != nil:
		.handleResolved(.resolved)
	case .connClosed != nil:
		.handleConnClosed(.connClosed)
	}
}

// handleAddConnection registers a connection, records its path, subscribes the
// caller to path events, emits an Opened event, and starts a watcher goroutine
// that posts a connClosed message when the connection ends.
func ( *RemoteStateActor) ( *addConnectionMsg) {
	,  := .watcher.Subscribe()

	 := .conn.RemoteAddr()
	 := &connState{conn: .conn, addr: , cancel: }
	 := observeMultipathPaths(.conn)

	.mu.Lock()
	.conns[.conn] = 
	if .metrics != nil {
		.metrics.numConnsOpened.Add(1)
	}
	.paths.SetOpen()
	.recordPathOpenedLocked(, )
	 := .syncMultipathPathsLocked(, )
	.paths.Prune()
	 := append([]netip.AddrPort(nil), .localNAT...)
	.mu.Unlock()

	seedNATTraversalAddresses(.conn, )

	// Watch the connection's lifetime. One goroutine per connection (single-path:
	// usually one); it exits when the connection closes or the actor stops.
	go func( Connection) {
		select {
		case <-.Done():
			select {
			case .inbox <- remoteMessage{connClosed: }:
			case <-.done:
			}
		case <-.done:
		}
	}(.conn)

	.watcher.Send(PathEvent{Kind: PathEventOpened, Addr: })
	for ,  := range  {
		.watcher.Send(PathEvent{Kind: PathEventOpened, Addr: })
	}
	.reselect()
	.reply <- 
}

// handleResolve resolves additional addresses for the remote and adds them as
// candidate paths.
func ( *RemoteStateActor) ( context.Context,  *resolveMsg) {
	// Add any addresses carried directly in the EndpointAddr as candidate paths.
	.mu.Lock()
	for ,  := range .addrs.Addrs() {
		if ,  := transportToAddr(, .id);  {
			.paths.Add()
		}
	}
	.mu.Unlock()

	if .resolve == nil {
		.reply <- nil
		return
	}
	,  := context.WithCancel()
	 := .resolve(, .addrs.ID)
	if  == nil {
		()
		.reply <- nil
		return
	}
	go func() {
		select {
		case <-.done:
			()
		case <-.Done():
		}
	}()
	go .runResolveStream(, , )
	.reply <- nil
}

func ( *RemoteStateActor) ( context.Context,  context.CancelFunc,  iter.Seq2[ResolvedAddr, error]) {
	defer ()
	for ,  := range  {
		if  != nil {
			continue
		}
		select {
		case .inbox <- remoteMessage{resolved: &resolvedMsg{addr: }}:
		case <-.done:
			return
		case <-.Done():
			return
		}
	}
}

func ( *RemoteStateActor) ( *resolvedMsg) {
	.mu.Lock()
	if ,  := transportToAddr(.addr.Addr, .id);  {
		.paths.AddWithProvenance(, .addr.Provenance)
	}
	.paths.Prune()
	.mu.Unlock()
	.reselect()
}

// handleConnClosed handles a connection closing: it removes the connection,
// marks its path inactive, clears the selection if it pointed at that path, and
// emits a Closed event.
func ( *RemoteStateActor) ( Connection) {
	.mu.Lock()
	,  := .conns[]
	if ! {
		.mu.Unlock()
		return
	}
	delete(.conns, )
	 := time.Now()
	 := []Addr{.addr}
	.paths.SetClosed(.addr, )
	.recordPathClosedLocked(.addr)
	for ,  := range .paths {
		if .multipathPathOpenLocked() {
			continue
		}
		.paths.SetClosed(, )
		.recordPathClosedLocked()
		 = appendUniqueAddr(, )
	}
	if .selected != nil && .selected.String() == .addr.String() {
		.selected = nil
	}
	if .selected != nil {
		for ,  := range  {
			if .selected.String() == .String() {
				.selected = nil
				break
			}
		}
	}
	.mu.Unlock()
	for ,  := range  {
		.watcher.Send(PathEvent{Kind: PathEventClosed, Addr: })
	}
	// End the connection's subscription after the Closed events above, so a
	// reader that kept up sees them before its channel closes.
	.cancel()
}

func ( *RemoteStateActor) ( *connState,  Addr) {
	if .metrics == nil {
		return
	}
	switch .Kind() {
	case AddrIP:
		.metrics.pathsDirect.Add(1)
		.metrics.transportIPPathsAdded.Add(1)
		if !.hasDirect {
			.hasDirect = true
			.metrics.numConnsDirect.Add(1)
		}
	case AddrRelay:
		.metrics.pathsRelay.Add(1)
		.metrics.transportRelayPathsAdded.Add(1)
	case AddrCustom:
		.metrics.pathsCustom.Add(1)
		.metrics.transportCustomPathsAdded.Add(1)
	}
}

func ( *RemoteStateActor) ( Addr) {
	if .metrics == nil {
		return
	}
	.metrics.numConnsClosed.Add(1)
	switch .Kind() {
	case AddrIP:
		.metrics.transportIPPathsRemoved.Add(1)
	case AddrRelay:
		.metrics.transportRelayPathsRemoved.Add(1)
	case AddrCustom:
		.metrics.transportCustomPathsRemoved.Add(1)
	}
}

type connPathSnapshot struct {
	conn  Connection
	addr  Addr
	rtt   time.Duration
	paths []PathInfo
}

func ( *RemoteStateActor) () []connPathSnapshot {
	.mu.Lock()
	 := make([]connPathSnapshot, 0, len(.conns))
	for ,  := range .conns {
		 = append(, connPathSnapshot{conn: .conn, addr: .addr})
	}
	.mu.Unlock()

	for  := range  {
		[].rtt = [].conn.SmoothedRTT()
		[].paths = observeMultipathPaths([].conn)
	}
	return 
}

func observeMultipathPaths( Connection) []PathInfo {
	,  := .(pathObservingConnection)
	if ! {
		return nil
	}
	return .Paths()
}

func appendCandidate( []PathCandidate,  map[string]struct{},  Addr,  time.Duration) []PathCandidate {
	 := .String()
	if ,  := [];  {
		return 
	}
	[] = struct{}{}
	return append(, PathCandidate{Addr: , RTT: })
}

func appendMultipathCandidates( []PathCandidate,  map[string]struct{},  []PathInfo,  time.Duration) []PathCandidate {
	for ,  := range  {
		if .Validated && .HasAddr {
			 := 
			if .HasRTT {
				 = .RTT
			}
			 = appendCandidate(, , .Addr, )
		}
	}
	return 
}

// syncMultipathPathsLocked records validated qng paths with explicit route
// metadata as open socket paths. a.mu must be held.
func ( *RemoteStateActor) ( *connState,  []PathInfo) []Addr {
	var  []Addr
	for ,  := range  {
		if !.Validated || !.HasAddr {
			continue
		}
		.paths = appendUniqueAddr(.paths, .Addr)
		if ,  := .paths.Status(.Addr);  &&  == PathStatusOpen {
			continue
		}
		.paths.SetOpen(.Addr)
		.recordPathOpenedLocked(, .Addr)
		 = appendUniqueAddr(, .Addr)
	}
	return 
}

// multipathPathOpenLocked reports whether another live connection still owns
// addr as a qng route path. a.mu must be held.
func ( *RemoteStateActor) ( Addr) bool {
	for ,  := range .conns {
		for ,  := range .paths {
			if .String() == .String() {
				return true
			}
		}
	}
	return false
}

func appendUniqueAddr( []Addr,  Addr) []Addr {
	for ,  := range  {
		if .String() == .String() {
			return 
		}
	}
	return append(, )
}

func appendUniqueNATAddr( []netip.AddrPort,  netip.AddrPort) []netip.AddrPort {
	,  := canonicalNATAddr()
	if ! {
		return 
	}
	for ,  := range  {
		if  ==  {
			return 
		}
	}
	return append(, )
}

func containsNATAddr( []netip.AddrPort,  netip.AddrPort) bool {
	for ,  := range  {
		if  ==  {
			return true
		}
	}
	return false
}

func canonicalNATAddr( netip.AddrPort) (netip.AddrPort, bool) {
	if !.IsValid() || .Port() == 0 {
		return netip.AddrPort{}, false
	}
	return netip.AddrPortFrom(.Addr().Unmap(), .Port()), true
}

// reselect runs the path selector over the current candidates and, if the
// selection changes, records it and emits a Selected event.
func ( *RemoteStateActor) () {
	 := .connectionPathSnapshots()

	.mu.Lock()
	 := make([]PathCandidate, 0, len())
	 := make(map[string]struct{}, len())
	var  []Addr
	 := time.Now()
	for ,  := range  {
		,  := .conns[.conn]
		if ! {
			continue
		}
		.paths.SetOpenAt(.addr, )
		 = appendCandidate(, , .addr, .rtt)
		 = append(, .syncMultipathPathsLocked(, .paths)...)
		 = appendMultipathCandidates(, , .paths, .rtt)
	}
	 := .paths.ExpireIdle()
	for ,  := range  {
		.recordPathClosedLocked()
	}
	.paths.Prune()
	 := .selected
	,  := .selector.Select(, )
	if ! {
		.mu.Unlock()
		for ,  := range  {
			.watcher.Send(PathEvent{Kind: PathEventOpened, Addr: })
		}
		for ,  := range  {
			.watcher.Send(PathEvent{Kind: PathEventClosed, Addr: })
		}
		return
	}
	if  != nil && .String() == .String() {
		.mu.Unlock()
		for ,  := range  {
			.watcher.Send(PathEvent{Kind: PathEventOpened, Addr: })
		}
		for ,  := range  {
			.watcher.Send(PathEvent{Kind: PathEventClosed, Addr: })
		}
		return
	}
	 := 
	.selected = &
	.mu.Unlock()
	for ,  := range  {
		.watcher.Send(PathEvent{Kind: PathEventOpened, Addr: })
	}
	for ,  := range  {
		.watcher.Send(PathEvent{Kind: PathEventClosed, Addr: })
	}
	.watcher.Send(PathEvent{Kind: PathEventSelected, Addr: })
}

func seedNATTraversalAddresses( Connection,  []netip.AddrPort) {
	if len() == 0 {
		return
	}
	,  := .(multipathConnection)
	if ! || !.MultipathNegotiated() {
		return
	}
	,  := .(natTraversalAddressConnection)
	if ! {
		return
	}
	for ,  := range  {
		_ = .AddNATTraversalAddress()
	}
}

// ValidateDirectPath asks qng to open and validate one ordinary multipath path
// over the current direct socket. This is the RFC 9000 path-validation path used
// before or independent of QNT-discovered NAT candidates.
func ( *RemoteStateActor) ( context.Context) error {
	.mu.Lock()
	 := make([]Connection, 0, len(.conns))
	for  := range .conns {
		 = append(, )
	}
	.mu.Unlock()

	 := false
	var  pathOpeningConnection
	for ,  := range  {
		,  := .(multipathConnection)
		if ! || !.MultipathNegotiated() {
			continue
		}
		 = true
		if ,  := .(pathOpeningConnection);  {
			 = 
			break
		}
	}
	if ! ||  == nil {
		return ErrExtensionNotNegotiated
	}
	,  := context.WithTimeout(, HolepunchAttemptsInterval)
	defer ()
	if  := .OpenPath();  != nil {
		return fmt.Errorf("socket: open direct path: %w", )
	}
	return nil
}

// TriggerHolepunch attempts to open a new direct path by NAT traversal. It is
// gated on an active qng connection with QNT support: socket advertises its
// already-known local candidates and asks qng to initiate one NAT traversal
// round. qng owns QNT frames, probe timers, response matching, and path opening.
func ( *RemoteStateActor) () error {
	.mu.Lock()
	 := make([]Connection, 0, len(.conns))
	for  := range .conns {
		 = append(, )
	}
	 := append([]netip.AddrPort(nil), .localNAT...)
	.mu.Unlock()

	 := false
	var  natTraversalRoundConnection
	for ,  := range  {
		,  := .(multipathConnection)
		if ! || !.MultipathNegotiated() {
			continue
		}
		 = true
		if ,  := .(natTraversalRoundConnection);  {
			 = 
			break
		}
	}
	if ! {
		return ErrExtensionNotNegotiated
	}
	if  == nil {
		return ErrExtensionNotNegotiated
	}
	return .triggerHolepunch(, )
}

// TriggerHolepunchConn attempts NAT traversal on conn. It returns
// [context.Canceled] if conn is no longer registered, or
// [ErrExtensionNotNegotiated] if conn does not support QNT.
func ( *RemoteStateActor) ( Connection) error {
	.mu.Lock()
	,  := .conns[]
	 := append([]netip.AddrPort(nil), .localNAT...)
	.mu.Unlock()
	if ! {
		return context.Canceled
	}
	,  := .(multipathConnection)
	if ! || !.MultipathNegotiated() {
		return ErrExtensionNotNegotiated
	}
	,  := .(natTraversalRoundConnection)
	if ! {
		return ErrExtensionNotNegotiated
	}
	return .triggerHolepunch(, )
}

func ( *RemoteStateActor) ( natTraversalRoundConnection,  []netip.AddrPort) error {
	if .metrics != nil {
		.metrics.holepunchAttempts.Add(1)
	}
	,  := context.WithTimeout(context.Background(), HolepunchAttemptsInterval)
	defer ()
	for ,  := range  {
		if  := .AddNATTraversalAddress();  != nil {
			return fmt.Errorf("socket: add nat traversal address %s: %w", , )
		}
	}
	if ,  := .InitiateNATTraversalRound();  != nil {
		return fmt.Errorf("socket: initiate nat traversal round: %w", )
	}
	return nil
}

// SelectedPath returns the actor's currently selected path and whether one is
// selected. It is safe to call concurrently with the actor loop.
func ( *RemoteStateActor) () (Addr, bool) {
	.mu.Lock()
	defer .mu.Unlock()
	if .selected == nil {
		return Addr{}, false
	}
	return *.selected, true
}

// RemoteInfo returns a snapshot of known addresses for this remote endpoint.
func ( *RemoteStateActor) () RemoteInfo {
	.mu.Lock()
	defer .mu.Unlock()
	return RemoteInfo{
		ID:    .id,
		Addrs: .paths.RemoteAddrs(),
	}
}

// PathInfos returns a snapshot of currently open paths for conn.
func ( *RemoteStateActor) ( Connection) []PathInfo {
	.mu.Lock()
	,  := .conns[]
	if ! {
		.mu.Unlock()
		return nil
	}
	 := append([]Addr{.addr}, .paths...)
	var  *Addr
	if .selected != nil {
		 := *.selected
		 = &
	}
	.mu.Unlock()

	 := make([]PathInfo, 0, len())
	 := make(map[string]int, len())
	for ,  := range  {
		// cs.addr and cs.paths overlap whenever qng validates a path whose
		// address is the connection's own, so open can name one address twice.
		// Reporting it twice yields two entries that both look selected, and
		// only the last one receives the multipath stats merged in below.
		if ,  := [.String()];  {
			continue
		}
		 := PathInfo{
			Validated: true,
			Addr:      ,
			HasAddr:   true,
			Selected:   != nil && .String() == .String(),
		}
		[.String()] = len()
		 = append(, )
	}

	for ,  := range observeMultipathPaths() {
		if .HasAddr {
			if ,  := [.Addr.String()];  {
				[].ID = .ID
				[].Validated = .Validated
				[].RTT = .RTT
				[].HasRTT = .HasRTT
				[].BytesInFlight = .BytesInFlight
				[].HasBytesInFlight = .HasBytesInFlight
				[].BytesSent = .BytesSent
				[].HasBytesSent = .HasBytesSent
				[].BytesReceived = .BytesReceived
				[].HasBytesReceived = .HasBytesReceived
				[].CongestionWindow = .CongestionWindow
				[].HasCongestionWindow = .HasCongestionWindow
				[].LostPackets = .LostPackets
				[].LostBytes = .LostBytes
				[].HasLoss = .HasLoss
				continue
			}
			.Selected =  != nil && .String() == .Addr.String()
			[.Addr.String()] = len()
			 = append(, )
			continue
		}
		 = append(, )
	}

	sort.Slice(, func(,  int) bool {
		if [].Selected != [].Selected {
			return [].Selected
		}
		if [].HasAddr != [].HasAddr {
			return [].HasAddr
		}
		if [].HasAddr && [].HasAddr && [].Addr.String() != [].Addr.String() {
			return [].Addr.String() < [].Addr.String()
		}
		return [].ID < [].ID
	})
	return 
}

// MultipathPaths returns qng multipath path state observed from active
// connections. Paths with explicit qng route metadata are also registered with
// RemotePathState by the actor loop and can produce path events.
func ( *RemoteStateActor) () []PathInfo {
	.mu.Lock()
	 := make([]Connection, 0, len(.conns))
	for  := range .conns {
		 = append(, )
	}
	.mu.Unlock()

	var  []PathInfo
	for ,  := range  {
		,  := .(pathObservingConnection)
		if ! {
			continue
		}
		 = append(, .Paths()...)
	}
	sort.Slice(, func(,  int) bool {
		if [].ID != [].ID {
			return [].ID < [].ID
		}
		return ![].Validated && [].Validated
	})
	return 
}

// NATTraversalAddresses returns the remote QNT ADD_ADDRESS set observed on
// active qng connections. Duplicate addresses are removed in first-seen order.
func ( *RemoteStateActor) () ([]netip.AddrPort, error) {
	.mu.Lock()
	 := make([]Connection, 0, len(.conns))
	for  := range .conns {
		 = append(, )
	}
	.mu.Unlock()

	 := false
	var  []netip.AddrPort
	for ,  := range  {
		,  := .(multipathConnection)
		if ! || !.MultipathNegotiated() {
			continue
		}
		 = true
		,  := .(natTraversalRemoteAddressConnection)
		if ! {
			continue
		}
		,  := .NATTraversalAddresses()
		if  != nil {
			return nil, 
		}
		for ,  := range  {
			 = appendUniqueNATAddr(, )
		}
	}
	if ! {
		return nil, ErrExtensionNotNegotiated
	}
	if  == nil {
		 = []netip.AddrPort{}
	}
	return , nil
}

// AddRemoteNATTraversalAddresses seeds remote QNT candidates from an
// authenticated endpoint address, such as the address used to dial the peer.
func ( *RemoteStateActor) ( []netip.AddrPort) error {
	.mu.Lock()
	 := make([]Connection, 0, len(.conns))
	for  := range .conns {
		 = append(, )
	}
	.mu.Unlock()

	 := false
	var  natTraversalRemoteAddressSeedConnection
	for ,  := range  {
		,  := .(multipathConnection)
		if ! || !.MultipathNegotiated() {
			continue
		}
		 = true
		if ,  := .(natTraversalRemoteAddressSeedConnection);  {
			 = 
			break
		}
	}
	if ! ||  == nil {
		return ErrExtensionNotNegotiated
	}
	for ,  := range  {
		if  := .AddRemoteNATTraversalAddress();  != nil {
			return fmt.Errorf("socket: add remote nat traversal address %s: %w", , )
		}
	}
	return nil
}

// AddNATTraversalAddresses reconciles the full local QNT candidate set for
// active qng connections. Candidate discovery stays outside this method:
// callers must pass already-vetted local candidates, such as endpoint-bound
// direct addresses and QAD reflexive addresses. qng owns QNT state, wire frames,
// probe timers, and eventual path opening.
func ( *RemoteStateActor) ( []netip.AddrPort) error {
	.mu.Lock()
	var  []netip.AddrPort
	for ,  := range  {
		,  := canonicalNATAddr()
		if ! {
			continue
		}
		 = appendUniqueNATAddr(, )
	}
	var  []netip.AddrPort
	for ,  := range .localNAT {
		if !containsNATAddr(, ) {
			 = append(, )
		}
	}
	var  []netip.AddrPort
	for ,  := range  {
		if !containsNATAddr(.localNAT, ) {
			 = append(, )
		}
	}
	.localNAT = 
	 := make([]Connection, 0, len(.conns))
	for  := range .conns {
		 = append(, )
	}
	.mu.Unlock()

	 := false
	var  natTraversalAddressConnection
	for ,  := range  {
		,  := .(multipathConnection)
		if ! || !.MultipathNegotiated() {
			continue
		}
		 = true
		if ,  := .(natTraversalAddressConnection);  {
			 = 
			break
		}
	}
	if ! {
		return ErrExtensionNotNegotiated
	}
	if  == nil {
		return ErrExtensionNotNegotiated
	}
	if len() != 0 || len() != 0 {
		if .metrics != nil {
			.metrics.updateDirectAddrs.Add(1)
		}
	}
	for ,  := range  {
		if  := .RemoveNATTraversalAddress();  != nil {
			return fmt.Errorf("socket: remove nat traversal address %s: %w", , )
		}
	}
	for ,  := range  {
		if  := .AddNATTraversalAddress();  != nil {
			return fmt.Errorf("socket: add nat traversal address %s: %w", , )
		}
	}
	return nil
}

// SendDatagram routes a datagram toward the remote via send. If a path is
// selected it sends there; otherwise it sends to every known path. It NEVER
// returns an error for an unreachable path: an unroutable datagram is treated as
// lost so QUIC loss recovery handles it (the socket-core blackhole invariant,
// iroh/src/socket/remote_map/remote_state.rs:782). send's bool result is
// advisory only.
//
// qng addresses datagrams to a concrete path directly through the MagicConn, so
// this method backs the Mixed-EndpointID send path, which is exercised by unit
// tests rather than the QUIC data plane in this slice.
func ( *RemoteStateActor) ( []byte,  func(Addr, []byte) bool) error {
	.mu.Lock()
	var  []Addr
	if .selected != nil {
		 = []Addr{*.selected}
	} else {
		 = .paths.Addrs()
	}
	.mu.Unlock()
	for ,  := range  {
		(, ) // result advisory; blackhole on failure
	}
	return nil
}

// PathEvents returns a fresh subscription to this actor's path events and a
// function to cancel it.
func ( *RemoteStateActor) () (<-chan PathEvent, func()) {
	return .watcher.Subscribe()
}

// resetTimer drains and resets a timer to fire after d.
func resetTimer( *time.Timer,  time.Duration) {
	if !.Stop() {
		select {
		case <-.C:
		default:
		}
	}
	.Reset()
}

// transportToAddr converts a netaddr.TransportAddr (the public address type) to the
// socket package's internal [Addr], pairing relay addresses with the remote id.
// It returns ok=false for address kinds the magic socket cannot route.
func transportToAddr( netaddr.TransportAddr,  key.EndpointID) (Addr, bool) {
	switch v := .(type) {
	case netaddr.IPAddr:
		return IPAddr(.Addr), true
	case netaddr.RelayAddr:
		return RelayAddr(.URL, ), true
	case netaddr.CustomAddr:
		return CustomAddr(), true
	default:
		return Addr{}, false
	}
}