package identify

import (
	
	
	
	
	
	

	

	ma 
	manet 
)

// ActivationThresh sets how many times an address must be seen as "activated"
// and therefore advertised to other peers as an address that the local peer
// can be contacted on. The "seen" events expire by default after 40 minutes
// (OwnObservedAddressTTL * ActivationThreshold). The are cleaned up during
// the GC rounds set by GCInterval.
var ActivationThresh = 4

// observedAddrManagerWorkerChannelSize defines how many addresses can be enqueued
// for adding to an ObservedAddrManager.
var observedAddrManagerWorkerChannelSize = 16

const maxExternalThinWaistAddrsPerLocalAddr = 3

// thinWaist is a struct that stores the address along with it's thin waist prefix and rest of the multiaddr
type thinWaist struct {
	Addr, TW, Rest ma.Multiaddr
}

// thinWaistWithCount is a thinWaist along with the count of the connection that have it as the local address
type thinWaistWithCount struct {
	thinWaist
	Count int
}

func thinWaistForm( ma.Multiaddr) (thinWaist, error) {
	 := 0
	,  := ma.SplitFunc(, func( ma.Component) bool {
		if  > 1 {
			return true
		}
		switch  {
		case 0:
			if .Protocol().Code == ma.P_IP4 || .Protocol().Code == ma.P_IP6 {
				++
				return false
			}
			return true
		case 1:
			if .Protocol().Code == ma.P_TCP || .Protocol().Code == ma.P_UDP {
				++
				return false
			}
			return true
		}
		return false
	})
	if  <= 1 {
		return thinWaist{}, fmt.Errorf("not a thinwaist address: %s", )
	}
	return thinWaist{Addr: , TW: , Rest: }, nil
}

// getObserver returns the observer for the multiaddress
// For an IPv4 multiaddress the observer is the IP address
// For an IPv6 multiaddress the observer is the first /56 prefix of the IP address
func getObserver( ma.Multiaddr) (string, error) {
	,  := manet.ToIP()
	if  != nil {
		return "", 
	}
	if  := .To4();  != nil {
		return .String(), nil
	}
	// Count /56 prefix as a single observer.
	return .Mask(net.CIDRMask(56, 128)).String(), nil
}

// connMultiaddrs provides IsClosed along with network.ConnMultiaddrs. It is easier to mock this than network.Conn
type connMultiaddrs interface {
	network.ConnMultiaddrs
	IsClosed() bool
}

// observerSetCacheSize is the number of transport sharing the same thinwaist (tcp, ws, wss), (quic, webtransport, webrtc-direct)
// This is 3 in practice right now, but keep a buffer of 3 extra elements
const observerSetCacheSize = 5

// observerSet is the set of observers who have observed ThinWaistAddr
type observerSet struct {
	ObservedTWAddr ma.Multiaddr
	ObservedBy     map[string]int

	mu               sync.RWMutex            // protects following
	cachedMultiaddrs map[string]ma.Multiaddr // cache of localMultiaddr rest(addr - thinwaist) => output multiaddr
}

func ( *observerSet) ( ma.Multiaddr) ma.Multiaddr {
	if  == nil {
		return .ObservedTWAddr
	}
	 := string(.Bytes())
	.mu.RLock()
	,  := .cachedMultiaddrs[]
	.mu.RUnlock()
	if  {
		return 
	}

	.mu.Lock()
	defer .mu.Unlock()
	// Check if some other go routine added this while we were waiting
	,  = .cachedMultiaddrs[]
	if  {
		return 
	}
	if .cachedMultiaddrs == nil {
		.cachedMultiaddrs = make(map[string]ma.Multiaddr, observerSetCacheSize)
	}
	if len(.cachedMultiaddrs) == observerSetCacheSize {
		// remove one entry if we will go over the limit
		for  := range .cachedMultiaddrs {
			delete(.cachedMultiaddrs, )
			break
		}
	}
	.cachedMultiaddrs[] = ma.Join(.ObservedTWAddr, )
	return .cachedMultiaddrs[]
}

type observation struct {
	conn     connMultiaddrs
	observed ma.Multiaddr
}

// ObservedAddrManager maps connection's local multiaddrs to their externally observable multiaddress
type ObservedAddrManager struct {
	// Our listen addrs
	listenAddrs func() []ma.Multiaddr
	// Our listen addrs with interface addrs for unspecified addrs
	interfaceListenAddrs func() ([]ma.Multiaddr, error)
	// All host addrs
	hostAddrs func() []ma.Multiaddr
	// Any normalization required before comparing. Useful to remove certhash
	normalize func(ma.Multiaddr) ma.Multiaddr
	// worker channel for new observations
	wch chan observation
	// notified on recording an observation
	addrRecordedNotif chan struct{}

	// for closing
	wg        sync.WaitGroup
	ctx       context.Context
	ctxCancel context.CancelFunc

	mu sync.RWMutex
	// local thin waist => external thin waist => observerSet
	externalAddrs map[string]map[string]*observerSet
	// connObservedTWAddrs maps the connection to the last observed thin waist multiaddr on that connection
	connObservedTWAddrs map[connMultiaddrs]ma.Multiaddr
	// localMultiaddr => thin waist form with the count of the connections the multiaddr
	// was seen on for tracking our local listen addresses
	localAddrs map[string]*thinWaistWithCount
}

// NewObservedAddrManager returns a new address manager using peerstore.OwnObservedAddressTTL as the TTL.
func (,  func() []ma.Multiaddr,
	 func() ([]ma.Multiaddr, error),  func(ma.Multiaddr) ma.Multiaddr) (*ObservedAddrManager, error) {
	if  == nil {
		 = func( ma.Multiaddr) ma.Multiaddr { return  }
	}
	 := &ObservedAddrManager{
		externalAddrs:        make(map[string]map[string]*observerSet),
		connObservedTWAddrs:  make(map[connMultiaddrs]ma.Multiaddr),
		localAddrs:           make(map[string]*thinWaistWithCount),
		wch:                  make(chan observation, observedAddrManagerWorkerChannelSize),
		addrRecordedNotif:    make(chan struct{}, 1),
		listenAddrs:          ,
		interfaceListenAddrs: ,
		hostAddrs:            ,
		normalize:            ,
	}
	.ctx, .ctxCancel = context.WithCancel(context.Background())

	.wg.Add(1)
	go .worker()
	return , nil
}

// AddrsFor return all activated observed addresses associated with the given
// (resolved) listen address.
func ( *ObservedAddrManager) ( ma.Multiaddr) ( []ma.Multiaddr) {
	if  == nil {
		return nil
	}
	.mu.RLock()
	defer .mu.RUnlock()
	,  := thinWaistForm(.normalize())
	if  != nil {
		return nil
	}

	 := .getTopExternalAddrs(string(.TW.Bytes()))
	 := make([]ma.Multiaddr, 0, len())
	for ,  := range  {
		 = append(, .cacheMultiaddr(.Rest))
	}
	return 
}

// appendInferredAddrs infers the external address of other transports that
// share the local thin waist with a transport that we have do observations for.
//
// e.g. If we have observations for a QUIC address on port 9000, and we are
// listening on the same interface and port 9000 for WebTransport, we can infer
// the external WebTransport address.
func ( *ObservedAddrManager) ( map[string][]*observerSet,  []ma.Multiaddr) []ma.Multiaddr {
	if  == nil {
		 = make(map[string][]*observerSet)
		for  := range .externalAddrs {
			[] = append([], .getTopExternalAddrs()...)
		}
	}
	,  := .interfaceListenAddrs()
	if  != nil {
		log.Warnw("failed to get interface resolved listen addrs. Using just the listen addrs", "error", )
		 = nil
	}
	 = append(, .listenAddrs()...)
	 := make(map[string]struct{})
	for ,  := range  {
		if ,  := .localAddrs[string(.Bytes())];  {
			// We already have this address in the list
			continue
		}
		if ,  := [string(.Bytes())];  {
			// We've already added this
			continue
		}
		[string(.Bytes())] = struct{}{}
		 = .normalize()
		,  := thinWaistForm()
		if  != nil {
			continue
		}
		for ,  := range [string(.TW.Bytes())] {
			 = append(, .cacheMultiaddr(.Rest))
		}
	}
	return 
}

// Addrs return all activated observed addresses
func ( *ObservedAddrManager) () []ma.Multiaddr {
	.mu.RLock()
	defer .mu.RUnlock()

	 := make(map[string][]*observerSet)
	for  := range .externalAddrs {
		[] = append([], .getTopExternalAddrs()...)
	}
	 := make([]ma.Multiaddr, 0, maxExternalThinWaistAddrsPerLocalAddr*5) // assume 5 transports
	for ,  := range .localAddrs {
		for ,  := range [string(.TW.Bytes())] {
			 = append(, .cacheMultiaddr(.Rest))
		}
	}

	 = .appendInferredAddrs(, )
	return 
}

func ( *ObservedAddrManager) ( string) []*observerSet {
	 := make([]*observerSet, 0, len(.externalAddrs[]))
	for ,  := range .externalAddrs[] {
		if len(.ObservedBy) >= ActivationThresh {
			 = append(, )
		}
	}
	slices.SortFunc(, func(,  *observerSet) int {
		 := len(.ObservedBy) - len(.ObservedBy)
		if  != 0 {
			return 
		}
		// In case we have elements with equal counts,
		// keep the address list stable by using the lexicographically smaller address
		 := .ObservedTWAddr.String()
		 := .ObservedTWAddr.String()
		if  <  {
			return -1
		} else if  >  {
			return 1
		} else {
			return 0
		}

	})
	 := len()
	if  > maxExternalThinWaistAddrsPerLocalAddr {
		 = maxExternalThinWaistAddrsPerLocalAddr
	}
	return [:]
}

// Record enqueues an observation for recording
func ( *ObservedAddrManager) ( connMultiaddrs,  ma.Multiaddr) {
	select {
	case .wch <- observation{
		conn:     ,
		observed: ,
	}:
	default:
		log.Debugw("dropping address observation due to full buffer",
			"from", .RemoteMultiaddr(),
			"observed", ,
		)
	}
}

func ( *ObservedAddrManager) () {
	defer .wg.Done()

	for {
		select {
		case  := <-.wch:
			.maybeRecordObservation(.conn, .observed)
		case <-.ctx.Done():
			return
		}
	}
}

func isRelayedAddress( ma.Multiaddr) bool {
	,  := .ValueForProtocol(ma.P_CIRCUIT)
	return  == nil
}

func ( *ObservedAddrManager) ( connMultiaddrs,  ma.Multiaddr) ( bool,  thinWaist,  thinWaist) {
	if  == nil ||  == nil {
		return false, thinWaist{}, thinWaist{}
	}
	// Ignore observations from loopback nodes. We already know our loopback
	// addresses.
	if manet.IsIPLoopback() {
		return false, thinWaist{}, thinWaist{}
	}

	// Provided by NAT64 peers, these addresses are specific to the peer and not publicly routable
	if manet.IsNAT64IPv4ConvertedIPv6Addr() {
		return false, thinWaist{}, thinWaist{}
	}

	// Ignore p2p-circuit addresses. These are the observed address of the relay.
	// Not useful for us.
	if isRelayedAddress() {
		return false, thinWaist{}, thinWaist{}
	}

	// we should only use ObservedAddr when our connection's LocalAddr is one
	// of our ListenAddrs. If we Dial out using an ephemeral addr, knowing that
	// address's external mapping is not very useful because the port will not be
	// the same as the listen addr.
	,  := .interfaceListenAddrs()
	if  != nil {
		log.Infof("failed to get interface listen addrs", )
		return false, thinWaist{}, thinWaist{}
	}

	for ,  := range  {
		[] = .normalize()
	}

	 := .normalize(.LocalMultiaddr())

	 := .listenAddrs()
	for ,  := range  {
		[] = .normalize()
	}

	if !ma.Contains(, ) && !ma.Contains(, ) {
		// not in our list
		return false, thinWaist{}, thinWaist{}
	}

	,  = thinWaistForm()
	if  != nil {
		return false, thinWaist{}, thinWaist{}
	}
	,  = thinWaistForm(.normalize())
	if  != nil {
		return false, thinWaist{}, thinWaist{}
	}

	 := .hostAddrs()
	for ,  := range  {
		[] = .normalize()
	}

	// We should reject the connection if the observation doesn't match the
	// transports of one of our advertised addresses.
	if !HasConsistentTransport(, ) &&
		!HasConsistentTransport(, ) {
		log.Debugw(
			"observed multiaddr doesn't match the transports of any announced addresses",
			"from", .RemoteMultiaddr(),
			"observed", ,
		)
		return false, thinWaist{}, thinWaist{}
	}

	return true, , 
}

func ( *ObservedAddrManager) ( connMultiaddrs,  ma.Multiaddr) {
	, ,  := .shouldRecordObservation(, )
	if ! {
		return
	}
	log.Debugw("added own observed listen addr", "conn", , "observed", )

	.mu.Lock()
	defer .mu.Unlock()
	.recordObservationUnlocked(, , )
	select {
	case .addrRecordedNotif <- struct{}{}:
	default:
	}
}

func ( *ObservedAddrManager) ( connMultiaddrs, ,  thinWaist) {
	if .IsClosed() {
		// dont record if the connection is already closed. Any previous observations will be removed in
		// the disconnected callback
		return
	}
	 := string(.TW.Bytes())
	 := string(.TW.Bytes())
	,  := getObserver(.RemoteMultiaddr())
	if  != nil {
		return
	}

	,  := .connObservedTWAddrs[]
	if ! {
		,  := .localAddrs[string(.Addr.Bytes())]
		if ! {
			 = &thinWaistWithCount{
				thinWaist: ,
			}
			.localAddrs[string(.Addr.Bytes())] = 
		}
		.Count++
	} else {
		if .Equal(.TW) {
			// we have received the same observation again, nothing to do
			return
		}
		// if we have a previous entry remove it from externalAddrs
		.removeExternalAddrsUnlocked(, , string(.Bytes()))
		// no need to change the localAddrs map here
	}
	.connObservedTWAddrs[] = .TW
	.addExternalAddrsUnlocked(.TW, , , )
}

func ( *ObservedAddrManager) (, ,  string) {
	,  := .externalAddrs[][]
	if ! {
		return
	}
	.ObservedBy[]--
	if .ObservedBy[] <= 0 {
		delete(.ObservedBy, )
	}
	if len(.ObservedBy) == 0 {
		delete(.externalAddrs[], )
	}
	if len(.externalAddrs[]) == 0 {
		delete(.externalAddrs, )
	}
}

func ( *ObservedAddrManager) ( ma.Multiaddr, , ,  string) {
	,  := .externalAddrs[][]
	if ! {
		 = &observerSet{
			ObservedTWAddr: ,
			ObservedBy:     make(map[string]int),
		}
		if ,  := .externalAddrs[]; ! {
			.externalAddrs[] = make(map[string]*observerSet)
		}
		.externalAddrs[][] = 
	}
	.ObservedBy[]++
}

func ( *ObservedAddrManager) ( connMultiaddrs) {
	if  == nil {
		return
	}
	.mu.Lock()
	defer .mu.Unlock()

	,  := .connObservedTWAddrs[]
	if ! {
		return
	}
	delete(.connObservedTWAddrs, )

	// normalize before obtaining the thinWaist so that we are always dealing
	// with the normalized form of the address
	,  := thinWaistForm(.normalize(.LocalMultiaddr()))
	if  != nil {
		return
	}
	,  := .localAddrs[string(.Addr.Bytes())]
	if ! {
		return
	}
	.Count--
	if .Count <= 0 {
		delete(.localAddrs, string(.Addr.Bytes()))
	}

	,  := getObserver(.RemoteMultiaddr())
	if  != nil {
		return
	}

	.removeExternalAddrsUnlocked(, string(.TW.Bytes()), string(.Bytes()))
	select {
	case .addrRecordedNotif <- struct{}{}:
	default:
	}
}

func ( *ObservedAddrManager) () (,  network.NATDeviceType) {
	.mu.RLock()
	defer .mu.RUnlock()

	var ,  []int
	var ,  int
	for ,  := range .externalAddrs {
		 := false
		for ,  := range  {
			if ,  := .ObservedTWAddr.ValueForProtocol(ma.P_TCP);  == nil {
				 = true
			}
			break
		}
		for ,  := range  {
			if  {
				 = append(, len(.ObservedBy))
				 += len(.ObservedBy)
			} else {
				 = append(, len(.ObservedBy))
				 += len(.ObservedBy)
			}
		}
	}

	sort.Sort(sort.Reverse(sort.IntSlice()))
	sort.Sort(sort.Reverse(sort.IntSlice()))

	,  := 0, 0
	for  := 0;  < maxExternalThinWaistAddrsPerLocalAddr &&  < len(); ++ {
		 += []
	}
	for  := 0;  < maxExternalThinWaistAddrsPerLocalAddr &&  < len(); ++ {
		 += []
	}

	// If the top elements cover more than 1/2 of all the observations, there's a > 50% chance that
	// hole punching based on outputs of observed address manager will succeed
	if  >= 3*maxExternalThinWaistAddrsPerLocalAddr {
		if  >= /2 {
			 = network.NATDeviceTypeCone
		} else {
			 = network.NATDeviceTypeSymmetric
		}
	}
	if  >= 3*maxExternalThinWaistAddrsPerLocalAddr {
		if  >= /2 {
			 = network.NATDeviceTypeCone
		} else {
			 = network.NATDeviceTypeSymmetric
		}
	}
	return
}

func ( *ObservedAddrManager) () error {
	.ctxCancel()
	.wg.Wait()
	return nil
}