// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package vnet

import (
	
	
	
	
	
	
	
	

	
	
)

const (
	defaultRouterQueueSize = 0 // unlimited
)

var (
	errInvalidLocalIPinStaticIPs     = errors.New("invalid local IP in StaticIPs")
	errLocalIPBeyondStaticIPsSubset  = errors.New("mapped in StaticIPs is beyond subnet")
	errLocalIPNoStaticsIPsAssociated = errors.New("all StaticIPs must have associated local IPs")
	errRouterAlreadyStarted          = errors.New("router already started")
	errRouterAlreadyStopped          = errors.New("router already stopped")
	errStaticIPisBeyondSubnet        = errors.New("static IP is beyond subnet")
	errAddressSpaceExhausted         = errors.New("address space exhausted")
	errNoIPAddrEth0                  = errors.New("no IP address is assigned for eth0")
)

// Generate a unique router name
var assignRouterName = func() func() string { //nolint:gochecknoglobals
	var  uint64

	return func() string {
		 := atomic.AddUint64(&, 1)
		return fmt.Sprintf("router%d", )
	}
}()

// RouterConfig ...
type RouterConfig struct {
	// Name of router. If not specified, a unique name will be assigned.
	Name string
	// CIDR notation, like "192.0.2.0/24"
	CIDR string
	// StaticIPs is an array of static IP addresses to be assigned for this router.
	// If no static IP address is given, the router will automatically assign
	// an IP address.
	// This will be ignored if this router is the root.
	StaticIPs []string
	// StaticIP is deprecated. Use StaticIPs.
	StaticIP string
	// Internal queue size
	QueueSize int
	// Effective only when this router has a parent router
	NATType *NATType
	// Minimum Delay
	MinDelay time.Duration
	// Max Jitter
	MaxJitter time.Duration
	// Logger factory
	LoggerFactory logging.LoggerFactory
}

// NIC is a network interface controller that interfaces Router
type NIC interface {
	getInterface(ifName string) (*transport.Interface, error)
	onInboundChunk(c Chunk)
	getStaticIPs() []net.IP
	setRouter(r *Router) error
}

// ChunkFilter is a handler users can add to filter chunks.
// If the filter returns false, the packet will be dropped.
type ChunkFilter func(c Chunk) bool

// Router ...
type Router struct {
	name           string                    // read-only
	interfaces     []*transport.Interface    // read-only
	ipv4Net        *net.IPNet                // read-only
	staticIPs      []net.IP                  // read-only
	staticLocalIPs map[string]net.IP         // read-only,
	lastID         byte                      // requires mutex [x], used to assign the last digit of IPv4 address
	queue          *chunkQueue               // read-only
	parent         *Router                   // read-only
	children       []*Router                 // read-only
	natType        *NATType                  // read-only
	nat            *networkAddressTranslator // read-only
	nics           map[string]NIC            // read-only
	stopFunc       func()                    // requires mutex [x]
	resolver       *resolver                 // read-only
	chunkFilters   []ChunkFilter             // requires mutex [x]
	minDelay       time.Duration             // requires mutex [x]
	maxJitter      time.Duration             // requires mutex [x]
	mutex          sync.RWMutex              // thread-safe
	pushCh         chan struct{}             // writer requires mutex
	loggerFactory  logging.LoggerFactory     // read-only
	log            logging.LeveledLogger     // read-only
}

// NewRouter ...
func ( *RouterConfig) (*Router, error) {
	 := .LoggerFactory
	 := .NewLogger("vnet")

	, ,  := net.ParseCIDR(.CIDR)
	if  != nil {
		return nil, 
	}

	 := defaultRouterQueueSize
	if .QueueSize > 0 {
		 = .QueueSize
	}

	// set up network interface, lo0
	 := transport.NewInterface(net.Interface{
		Index:        1,
		MTU:          16384,
		Name:         lo0String,
		HardwareAddr: nil,
		Flags:        net.FlagUp | net.FlagLoopback | net.FlagMulticast,
	})
	.AddAddress(&net.IPAddr{IP: net.ParseIP("127.0.0.1"), Zone: ""})

	// set up network interface, eth0
	 := transport.NewInterface(net.Interface{
		Index:        2,
		MTU:          1500,
		Name:         "eth0",
		HardwareAddr: newMACAddress(),
		Flags:        net.FlagUp | net.FlagMulticast,
	})

	// local host name resolver
	 := newResolver(&resolverConfig{
		LoggerFactory: .LoggerFactory,
	})

	 := .Name
	if len() == 0 {
		 = assignRouterName()
	}

	var  []net.IP
	 := map[string]net.IP{}
	for ,  := range .StaticIPs {
		 := strings.Split(, "/")
		if  := net.ParseIP([0]);  != nil {
			if len() > 1 {
				 := net.ParseIP([1])
				if  == nil {
					return nil, errInvalidLocalIPinStaticIPs
				}
				if !.Contains() {
					return nil, fmt.Errorf("local IP %s %w", .String(), errLocalIPBeyondStaticIPsSubset)
				}
				[.String()] = 
			}
			 = append(, )
		}
	}
	if len(.StaticIP) > 0 {
		.Warn("StaticIP is deprecated. Use StaticIPs instead")
		if  := net.ParseIP(.StaticIP);  != nil {
			 = append(, )
		}
	}

	if  := len();  > 0 {
		if  != len() {
			return nil, errLocalIPNoStaticsIPsAssociated
		}
	}

	return &Router{
		name:           ,
		interfaces:     []*transport.Interface{, },
		ipv4Net:        ,
		staticIPs:      ,
		staticLocalIPs: ,
		queue:          newChunkQueue(, 0),
		natType:        .NATType,
		nics:           map[string]NIC{},
		resolver:       ,
		minDelay:       .MinDelay,
		maxJitter:      .MaxJitter,
		pushCh:         make(chan struct{}, 1),
		loggerFactory:  ,
		log:            ,
	}, nil
}

// caller must hold the mutex
func ( *Router) () ([]*transport.Interface, error) {
	if len(.interfaces) == 0 {
		return nil, fmt.Errorf("%w is available", errNoInterface)
	}

	return .interfaces, nil
}

func ( *Router) ( string) (*transport.Interface, error) {
	.mutex.RLock()
	defer .mutex.RUnlock()

	,  := .getInterfaces()
	if  != nil {
		return nil, 
	}
	for ,  := range  {
		if .Name ==  {
			return , nil
		}
	}

	return nil, fmt.Errorf("%w: %s", transport.ErrInterfaceNotFound, )
}

// Start ...
func ( *Router) () error {
	.mutex.Lock()
	defer .mutex.Unlock()

	if .stopFunc != nil {
		return errRouterAlreadyStarted
	}

	 := make(chan struct{})

	go func() {
	:
		for {
			,  := .processChunks()
			if  != nil {
				.log.Errorf("[%s] %s", .name, .Error())
				break
			}

			if  <= 0 {
				select {
				case <-.pushCh:
				case <-:
					break 
				}
			} else {
				 := time.NewTimer()
				select {
				case <-.C:
				case <-:
					break 
				}
			}
		}
	}()

	.stopFunc = func() {
		close()
	}

	for ,  := range .children {
		if  := .();  != nil {
			return 
		}
	}

	return nil
}

// Stop ...
func ( *Router) () error {
	.mutex.Lock()
	defer .mutex.Unlock()

	if .stopFunc == nil {
		return errRouterAlreadyStopped
	}

	for ,  := range .children {
		.mutex.Unlock()
		 := .()
		.mutex.Lock()

		if  != nil {
			return 
		}
	}

	.stopFunc()
	.stopFunc = nil
	return nil
}

// caller must hold the mutex
func ( *Router) ( NIC) error {
	,  := .getInterface("eth0")
	if  != nil {
		return 
	}

	var  []net.IP

	if  = .getStaticIPs(); len() == 0 {
		// assign an IP address
		,  := .assignIPAddress()
		if  != nil {
			return 
		}
		 = append(, )
	}

	for ,  := range  {
		if !.ipv4Net.Contains() {
			return fmt.Errorf("%w: %s", errStaticIPisBeyondSubnet, .ipv4Net.String())
		}

		.AddAddress(&net.IPNet{
			IP:   ,
			Mask: .ipv4Net.Mask,
		})

		.nics[.String()] = 
	}

	return .setRouter()
}

// AddRouter adds a child Router.
func ( *Router) ( *Router) error {
	.mutex.Lock()
	defer .mutex.Unlock()

	// Router is a NIC. Add it as a NIC so that packets are routed to this child
	// router.
	 := .addNIC()
	if  != nil {
		return 
	}

	if  = .setRouter();  != nil {
		return 
	}

	.children = append(.children, )
	return nil
}

// AddChildRouter is like AddRouter, but does not add the child routers NIC to
// the parent. This has to be done manually by calling AddNet, which allows to
// use a wrapper around the subrouters NIC.
// AddNet MUST be called before AddChildRouter.
func ( *Router) ( *Router) error {
	.mutex.Lock()
	defer .mutex.Unlock()
	if  := .setRouter();  != nil {
		return 
	}

	.children = append(.children, )
	return nil
}

// AddNet ...
func ( *Router) ( NIC) error {
	.mutex.Lock()
	defer .mutex.Unlock()

	return .addNIC()
}

// AddHost adds a mapping of hostname and an IP address to the local resolver.
func ( *Router) ( string,  string) error {
	return .resolver.addHost(, )
}

// AddChunkFilter adds a filter for chunks traversing this router.
// You may add more than one filter. The filters are called in the order of this method call.
// If a chunk is dropped by a filter, subsequent filter will not receive the chunk.
func ( *Router) ( ChunkFilter) {
	.mutex.Lock()
	defer .mutex.Unlock()

	.chunkFilters = append(.chunkFilters, )
}

// caller should hold the mutex
func ( *Router) () (net.IP, error) {
	// See: https://stackoverflow.com/questions/14915188/ip-address-ending-with-zero

	if .lastID == 0xfe {
		return nil, errAddressSpaceExhausted
	}

	 := make(net.IP, 4)
	copy(, .ipv4Net.IP[:3])
	.lastID++
	[3] = .lastID
	return , nil
}

func ( *Router) ( Chunk) {
	.mutex.Lock()
	defer .mutex.Unlock()

	.log.Debugf("[%s] route %s", .name, .String())
	if .stopFunc != nil {
		.setTimestamp()
		if .queue.push() {
			select {
			case .pushCh <- struct{}{}:
			default:
			}
		} else {
			.log.Warnf("[%s] queue was full. dropped a chunk", .name)
		}
	}
}

func ( *Router) () (time.Duration, error) {
	.mutex.Lock()
	defer .mutex.Unlock()

	// Introduce jitter by delaying the processing of chunks.
	if .maxJitter > 0 {
		 := time.Duration(rand.Int63n(int64(.maxJitter))) //nolint:gosec
		time.Sleep()
	}

	//      cutOff
	//         v min delay
	//         |<--->|
	//  +------------:--
	//  |OOOOOOXXXXX :   --> time
	//  +------------:--
	//  |<--->|     now
	//    due

	 := time.Now()
	 := .Add(-.minDelay)

	var  time.Duration // the next sleep duration

	for {
		 = 0

		 := .queue.peek()
		if  == nil {
			break // no more chunk in the queue
		}

		// check timestamp to find if the chunk is due
		if .getTimestamp().After() {
			// There is one or more chunk in the queue but none of them are due.
			// Calculate the next sleep duration here.
			 := .getTimestamp().Add(.minDelay)
			 = .Sub()
			break
		}

		var  bool
		if ,  = .queue.pop(); ! {
			break // no more chunk in the queue
		}

		 := false
		for  := 0;  < len(.chunkFilters); ++ {
			 := .chunkFilters[]
			if !() {
				 = true
				break
			}
		}
		if  {
			continue // discard
		}

		 := .getDestinationIP()

		// check if the destination is in our subnet
		if .ipv4Net.Contains() {
			// search for the destination NIC
			var  NIC
			if ,  = .nics[.String()]; ! {
				// NIC not found. drop it.
				.log.Debugf("[%s] %s unreachable", .name, .String())
				continue
			}

			// found the NIC, forward the chunk to the NIC.
			// call to NIC must unlock mutex
			.mutex.Unlock()
			.onInboundChunk()
			.mutex.Lock()
			continue
		}

		// the destination is outside of this subnet
		// is this WAN?
		if .parent == nil {
			// this WAN. No route for this chunk
			.log.Debugf("[%s] no route found for %s", .name, .String())
			continue
		}

		// Pass it to the parent via NAT
		,  := .nat.translateOutbound()
		if  != nil {
			return 0, 
		}

		if  == nil {
			continue
		}

		//nolint:godox
		/* FIXME: this implementation would introduce a duplicate packet!
		if r.nat.natType.Hairpinning {
			hairpinned, err := r.nat.translateInbound(toParent)
			if err != nil {
				r.log.Warnf("[%s] %s", r.name, err.Error())
			} else {
				go func() {
					r.push(hairpinned)
				}()
			}
		}
		*/

		// call to parent router mutex unlock mutex
		.mutex.Unlock()
		.parent.push()
		.mutex.Lock()
	}

	return , nil
}

// caller must hold the mutex
func ( *Router) ( *Router) error {
	.parent = 
	.resolver.setParent(.resolver)

	// when this method is called, one or more IP address has already been assigned by
	// the parent router.
	,  := .getInterface("eth0")
	if  != nil {
		return 
	}

	,  := .Addrs()
	if len() == 0 {
		return errNoIPAddrEth0
	}

	 := []net.IP{}
	 := []net.IP{}

	for ,  := range  {
		var  net.IP
		switch addr := .(type) {
		case *net.IPNet:
			 = .IP
		case *net.IPAddr: // Do we really need this case?
			 = .IP
		default:
		}

		if  == nil {
			continue
		}

		 = append(, )

		if  := .staticLocalIPs[.String()];  != nil {
			 = append(, )
		}
	}

	// Set up NAT here
	if .natType == nil {
		.natType = &NATType{
			MappingBehavior:   EndpointIndependent,
			FilteringBehavior: EndpointAddrPortDependent,
			Hairpinning:       false,
			PortPreservation:  false,
			MappingLifeTime:   30 * time.Second,
		}
	}
	.nat,  = newNAT(&natConfig{
		name:          .name,
		natType:       *.natType,
		mappedIPs:     ,
		localIPs:      ,
		loggerFactory: .loggerFactory,
	})
	if  != nil {
		return 
	}

	return nil
}

func ( *Router) ( Chunk) {
	,  := .nat.translateInbound()
	if  != nil {
		.log.Warnf("[%s] %s", .name, .Error())
		return
	}

	.push()
}

func ( *Router) () []net.IP {
	return .staticIPs
}