package rcmgr

import (
	
	
	
	
	

	

	
	manet 
)

type Allowlist struct {
	mu sync.RWMutex
	// a simple structure of lists of networks. There is probably a faster way
	// to check if an IP address is in this network than iterating over this
	// list, but this is good enough for small numbers of networks (<1_000).
	// Analyze the benchmark before trying to optimize this.

	// Any peer with these IPs are allowed
	allowedNetworks []*net.IPNet

	// Only the specified peers can use these IPs
	allowedPeerByNetwork map[peer.ID][]*net.IPNet
}

// WithAllowlistedMultiaddrs sets the multiaddrs to be in the allowlist
func ( []multiaddr.Multiaddr) Option {
	return func( *resourceManager) error {
		for ,  := range  {
			 := .allowlist.Add()
			if  != nil {
				return 
			}
		}
		return nil
	}
}

func newAllowlist() Allowlist {
	return Allowlist{
		allowedPeerByNetwork: make(map[peer.ID][]*net.IPNet),
	}
}

func toIPNet( multiaddr.Multiaddr) (*net.IPNet, peer.ID, error) {
	var  string
	var  string
	var  string
	var  peer.ID
	var  bool

	multiaddr.ForEach(, func( multiaddr.Component) bool {
		if .Protocol().Code == multiaddr.P_IP4 || .Protocol().Code == multiaddr.P_IP6 {
			 = .Protocol().Code == multiaddr.P_IP4
			 = .Value()
		}
		if .Protocol().Code == multiaddr.P_IPCIDR {
			 = .Value()
		}
		if .Protocol().Code == multiaddr.P_P2P {
			 = .Value()
		}
		return  == "" ||  == "" ||  == ""
	})

	if  == "" {
		return nil, , errors.New("missing ip address")
	}

	if  != "" {
		var  error
		,  = peer.Decode()
		if  != nil {
			return nil, , fmt.Errorf("failed to decode allowed peer: %w", )
		}
	}

	if  == "" {
		 := net.ParseIP()
		if  == nil {
			return nil, , errors.New("invalid ip address")
		}
		var  net.IPMask
		if  {
			 = net.CIDRMask(32, 32)
		} else {
			 = net.CIDRMask(128, 128)
		}

		 := &net.IPNet{IP: , Mask: }
		return , , nil
	}

	, ,  := net.ParseCIDR( + "/" + )
	return , , 

}

// Add takes a multiaddr and adds it to the allowlist. The multiaddr should be
// an ip address of the peer with or without a `/p2p` protocol.
// e.g. /ip4/1.2.3.4/p2p/QmFoo, /ip4/1.2.3.4, and /ip4/1.2.3.0/ipcidr/24 are valid.
// /p2p/QmFoo is not valid.
func ( *Allowlist) ( multiaddr.Multiaddr) error {
	, ,  := toIPNet()
	if  != nil {
		return 
	}
	.mu.Lock()
	defer .mu.Unlock()

	if  != peer.ID("") {
		// We have a peerID constraint
		if .allowedPeerByNetwork == nil {
			.allowedPeerByNetwork = make(map[peer.ID][]*net.IPNet)
		}
		.allowedPeerByNetwork[] = append(.allowedPeerByNetwork[], )
	} else {
		.allowedNetworks = append(.allowedNetworks, )
	}
	return nil
}

func ( *Allowlist) ( multiaddr.Multiaddr) error {
	, ,  := toIPNet()
	if  != nil {
		return 
	}
	.mu.Lock()
	defer .mu.Unlock()

	 := .allowedNetworks

	if  != "" {
		// We have a peerID constraint
		 = .allowedPeerByNetwork[]
	}

	if  == nil {
		return nil
	}

	 := len()
	for  > 0 {
		--
		if [].IP.Equal(.IP) && bytes.Equal([].Mask, .Mask) {
			// swap remove
			[] = [len()-1]
			 = [:len()-1]
			// We only remove one thing
			break
		}
	}

	if  != "" {
		.allowedPeerByNetwork[] = 
	} else {
		.allowedNetworks = 
	}

	return nil
}

func ( *Allowlist) ( multiaddr.Multiaddr) bool {
	,  := manet.ToIP()
	if  != nil {
		return false
	}
	.mu.RLock()
	defer .mu.RUnlock()

	for ,  := range .allowedNetworks {
		if .Contains() {
			return true
		}
	}

	for ,  := range .allowedPeerByNetwork {
		for ,  := range  {
			if .Contains() {
				return true
			}
		}
	}

	return false
}

func ( *Allowlist) ( peer.ID,  multiaddr.Multiaddr) bool {
	,  := manet.ToIP()
	if  != nil {
		return false
	}
	.mu.RLock()
	defer .mu.RUnlock()

	for ,  := range .allowedNetworks {
		if .Contains() {
			// We found a match that isn't constrained by a peerID
			return true
		}
	}

	if ,  := .allowedPeerByNetwork[];  {
		for ,  := range  {
			if .Contains() {
				return true
			}
		}
	}

	return false
}