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

package vnet

import (
	
	
	
	
	
	
	
	

	
)

const (
	lo0String = "lo0String"
	udp       = "udp"
	udp4      = "udp4"
)

var (
	macAddrCounter                 uint64 = 0xBEEFED910200 //nolint:gochecknoglobals
	errNoInterface                        = errors.New("no interface is available")
	errUnexpectedNetwork                  = errors.New("unexpected network")
	errCantAssignRequestedAddr            = errors.New("can't assign requested address")
	errUnknownNetwork                     = errors.New("unknown network")
	errNoRouterLinked                     = errors.New("no router linked")
	errInvalidPortNumber                  = errors.New("invalid port number")
	errUnexpectedTypeSwitchFailure        = errors.New("unexpected type-switch failure")
	errBindFailedFor                      = errors.New("bind failed for")
	errEndPortLessThanStart               = errors.New("end port is less than the start")
	errPortSpaceExhausted                 = errors.New("port space exhausted")
)

func newMACAddress() net.HardwareAddr {
	 := make([]byte, 8)
	binary.BigEndian.PutUint64(, macAddrCounter)
	macAddrCounter++
	return [2:]
}

// Net represents a local network stack equivalent to a set of layers from NIC
// up to the transport (UDP / TCP) layer.
type Net struct {
	interfaces []*transport.Interface // read-only
	staticIPs  []net.IP               // read-only
	router     *Router                // read-only
	udpConns   *udpConnMap            // read-only
	mutex      sync.RWMutex
}

// Compile-time assertion
var _ transport.Net = &Net{}

func ( *Net) () ([]*transport.Interface, error) {
	if len(.interfaces) == 0 {
		return nil, errNoInterface
	}

	return .interfaces, nil
}

// Interfaces returns a list of the system's network interfaces.
func ( *Net) () ([]*transport.Interface, error) {
	.mutex.RLock()
	defer .mutex.RUnlock()

	return ._getInterfaces()
}

// caller must hold the mutex (read)
func ( *Net) ( string) (*transport.Interface, error) {
	,  := ._getInterfaces()
	if  != nil {
		return nil, 
	}
	for ,  := range  {
		if .Name ==  {
			return , nil
		}
	}

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

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

	return ._getInterface()
}

// InterfaceByIndex returns the interface specified by index.
//
// On Solaris, it returns one of the logical network interfaces
// sharing the logical data link; for more precision use
// InterfaceByName.
func ( *Net) ( int) (*transport.Interface, error) {
	for ,  := range .interfaces {
		if .Index ==  {
			return , nil
		}
	}

	return nil, fmt.Errorf("%w: index=%d", transport.ErrInterfaceNotFound, )
}

// InterfaceByName returns the interface specified by name.
func ( *Net) ( string) (*transport.Interface, error) {
	return .getInterface()
}

// caller must hold the mutex
func ( *Net) ( bool) []net.IP {
	 := []net.IP{}

	for ,  := range .interfaces {
		,  := .Addrs()
		if  != nil {
			continue
		}

		for ,  := range  {
			var  net.IP
			if ,  := .(*net.IPNet);  {
				 = .IP
			} else if ,  := .(*net.IPAddr);  {
				 = .IP
			} else {
				continue
			}

			if ! {
				if .To4() != nil {
					 = append(, )
				}
			}
		}
	}

	return 
}

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

	.router = 
	return nil
}

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

	if .Network() == udp {
		if ,  := .udpConns.find(.DestinationAddr());  {
			.onInboundChunk()
		}
	}
}

// caller must hold the mutex
func ( *Net) ( string, ,  *net.UDPAddr) (transport.UDPConn, error) {
	// validate network
	if  != udp &&  != udp4 {
		return nil, fmt.Errorf("%w: %s", errUnexpectedNetwork, )
	}

	if  == nil {
		 = &net.UDPAddr{
			IP: net.IPv4zero,
		}
	} else if .IP == nil {
		.IP = net.IPv4zero
	}

	// validate address. do we have that address?
	if !.hasIPAddr(.IP) {
		return nil, &net.OpError{
			Op:   "listen",
			Net:  ,
			Addr: ,
			Err:  fmt.Errorf("bind: %w", errCantAssignRequestedAddr),
		}
	}

	if .Port == 0 {
		// choose randomly from the range between 5000 and 5999
		,  := .assignPort(.IP, 5000, 5999)
		if  != nil {
			return nil, &net.OpError{
				Op:   "listen",
				Net:  ,
				Addr: ,
				Err:  ,
			}
		}
		.Port = 
	} else if ,  := .udpConns.find();  {
		return nil, &net.OpError{
			Op:   "listen",
			Net:  ,
			Addr: ,
			Err:  fmt.Errorf("bind: %w", errAddressAlreadyInUse),
		}
	}

	,  := newUDPConn(, , )
	if  != nil {
		return nil, 
	}

	 = .udpConns.insert()
	if  != nil {
		return nil, 
	}

	return , nil
}

// ListenPacket announces on the local network address.
func ( *Net) ( string,  string) (net.PacketConn, error) {
	.mutex.Lock()
	defer .mutex.Unlock()

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

	return ._dialUDP(, , nil)
}

// ListenUDP acts like ListenPacket for UDP networks.
func ( *Net) ( string,  *net.UDPAddr) (transport.UDPConn, error) {
	.mutex.Lock()
	defer .mutex.Unlock()

	return ._dialUDP(, , nil)
}

// DialUDP acts like Dial for UDP networks.
func ( *Net) ( string, ,  *net.UDPAddr) (transport.UDPConn, error) {
	.mutex.Lock()
	defer .mutex.Unlock()

	return ._dialUDP(, , )
}

// Dial connects to the address on the named network.
func ( *Net) ( string,  string) (net.Conn, error) {
	.mutex.Lock()
	defer .mutex.Unlock()

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

	// Determine source address
	 := .determineSourceIP(nil, .IP)

	 := &net.UDPAddr{IP: , Port: 0}

	return ._dialUDP(, , )
}

// ResolveIPAddr returns an address of IP end point.
func ( *Net) (,  string) (*net.IPAddr, error) {
	var  error

	// Check if host is a domain name
	 := net.ParseIP()
	if  == nil {
		 = strings.ToLower()
		if  == "localhost" {
			 = net.IPv4(127, 0, 0, 1)
		} else {
			// host is a domain name. resolve IP address by the name
			if .router == nil {
				return nil, errNoRouterLinked
			}

			,  = .router.resolver.lookUp()
			if  != nil {
				return nil, 
			}
		}
	}

	return &net.IPAddr{
		IP: ,
	}, nil
}

// ResolveUDPAddr returns an address of UDP end point.
func ( *Net) (,  string) (*net.UDPAddr, error) {
	if  != udp &&  != udp4 {
		return nil, fmt.Errorf("%w %s", errUnknownNetwork, )
	}

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

	,  := .ResolveIPAddr("ip", )
	if  != nil {
		return nil, 
	}

	,  := strconv.Atoi()
	if  != nil {
		return nil, errInvalidPortNumber
	}

	 := &net.UDPAddr{
		IP:   .IP,
		Zone: .Zone,
		Port: ,
	}

	return , nil
}

// ResolveTCPAddr returns an address of TCP end point.
func ( *Net) (,  string) (*net.TCPAddr, error) {
	if  != udp &&  != "udp4" {
		return nil, fmt.Errorf("%w %s", errUnknownNetwork, )
	}

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

	,  := .ResolveIPAddr("ip", )
	if  != nil {
		return nil, 
	}

	,  := strconv.Atoi()
	if  != nil {
		return nil, errInvalidPortNumber
	}

	 := &net.TCPAddr{
		IP:   .IP,
		Zone: .Zone,
		Port: ,
	}

	return , nil
}

func ( *Net) ( Chunk) error {
	if .Network() == udp {
		if ,  := .(*chunkUDP);  {
			if .getDestinationIP().IsLoopback() {
				if ,  := .udpConns.find(.DestinationAddr());  {
					.onInboundChunk()
				}
				return nil
			}
		} else {
			return errUnexpectedTypeSwitchFailure
		}
	}

	if .router == nil {
		return errNoRouterLinked
	}

	.router.push()
	return nil
}

func ( *Net) ( net.Addr) {
	if .Network() == udp {
		//nolint:errcheck
		.udpConns.delete() // #nosec
	}
}

// This method determines the srcIP based on the dstIP when locIP
// is any IP address ("0.0.0.0" or "::"). If locIP is a non-any addr,
// this method simply returns locIP.
// caller must hold the mutex
func ( *Net) (,  net.IP) net.IP {
	if  != nil && !.IsUnspecified() {
		return 
	}

	var  net.IP

	if .IsLoopback() {
		 = net.ParseIP("127.0.0.1")
	} else {
		,  := ._getInterface("eth0")
		if  != nil {
			return nil
		}

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

		if len() == 0 {
			return nil
		}

		var  bool
		if  != nil {
			 = (.To4() != nil)
		} else {
			 = (.To4() != nil)
		}

		for ,  := range  {
			 := .(*net.IPNet).IP //nolint:forcetypeassert
			if  {
				if .To4() != nil {
					 = 
					break
				}
			} else {
				if .To4() == nil {
					 = 
					break
				}
			}
		}
	}

	return 
}

// caller must hold the mutex
func ( *Net) ( net.IP) bool { //nolint:gocognit
	for ,  := range .interfaces {
		if ,  := .Addrs();  == nil {
			for ,  := range  {
				var  net.IP
				if ,  := .(*net.IPNet);  {
					 = .IP
				} else if ,  := .(*net.IPAddr);  {
					 = .IP
				} else {
					continue
				}

				switch .String() {
				case "0.0.0.0":
					if .To4() != nil {
						return true
					}
				case "::":
					if .To4() == nil {
						return true
					}
				default:
					if .Equal() {
						return true
					}
				}
			}
		}
	}

	return false
}

// caller must hold the mutex
func ( *Net) ( net.IP,  int) error {
	// gather local IP addresses to bind
	var  []net.IP
	if .IsUnspecified() {
		 = .getAllIPAddrs(.To4() == nil)
	} else if .hasIPAddr() {
		 = []net.IP{}
	}

	if len() == 0 {
		return fmt.Errorf("%w %s", errBindFailedFor, .String())
	}

	// check if all these transport addresses are not in use
	for ,  := range  {
		 := &net.UDPAddr{
			IP:   ,
			Port: ,
		}
		if ,  := .udpConns.find();  {
			return &net.OpError{
				Op:   "bind",
				Net:  udp,
				Addr: ,
				Err:  fmt.Errorf("bind: %w", errAddressAlreadyInUse),
			}
		}
	}

	return nil
}

// caller must hold the mutex
func ( *Net) ( net.IP, ,  int) (int, error) {
	// choose randomly from the range between start and end (inclusive)
	if  <  {
		return -1, errEndPortLessThanStart
	}

	 :=  + 1 - 
	 := rand.Intn() //nolint:gosec
	for  := 0;  < ; ++ {
		 := (( + ) % ) + 

		 := .allocateLocalAddr(, )
		if  == nil {
			return , nil
		}
	}

	return -1, errPortSpaceExhausted
}

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

// NetConfig is a bag of configuration parameters passed to NewNet().
type NetConfig struct {
	// StaticIPs is an array of static IP addresses to be assigned for this Net.
	// If no static IP address is given, the router will automatically assign
	// an IP address.
	StaticIPs []string

	// StaticIP is deprecated. Use StaticIPs.
	StaticIP string
}

// NewNet creates an instance of a virtual network.
//
// By design, it always have lo0 and eth0 interfaces.
// The lo0 has the address 127.0.0.1 assigned by default.
// IP address for eth0 will be assigned when this Net is added to a router.
func ( *NetConfig) (*Net, error) {
	 := transport.NewInterface(net.Interface{
		Index:        1,
		MTU:          16384,
		Name:         lo0String,
		HardwareAddr: nil,
		Flags:        net.FlagUp | net.FlagLoopback | net.FlagMulticast,
	})
	.AddAddress(&net.IPNet{
		IP:   net.ParseIP("127.0.0.1"),
		Mask: net.CIDRMask(8, 32),
	})

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

	var  []net.IP
	for ,  := range .StaticIPs {
		if  := net.ParseIP();  != nil {
			 = append(, )
		}
	}
	if len(.StaticIP) > 0 {
		if  := net.ParseIP(.StaticIP);  != nil {
			 = append(, )
		}
	}

	return &Net{
		interfaces: []*transport.Interface{, },
		staticIPs:  ,
		udpConns:   newUDPConnMap(),
	}, nil
}

// DialTCP acts like Dial for TCP networks.
func ( *Net) (string, *net.TCPAddr, *net.TCPAddr) (transport.TCPConn, error) {
	return nil, transport.ErrNotSupported
}

// ListenTCP acts like Listen for TCP networks.
func ( *Net) (string, *net.TCPAddr) (transport.TCPListener, error) {
	return nil, transport.ErrNotSupported
}

// CreateDialer creates an instance of vnet.Dialer
func ( *Net) ( *net.Dialer) transport.Dialer {
	return &dialer{
		dialer: ,
		net:    ,
	}
}

type dialer struct {
	dialer *net.Dialer
	net    *Net
}

func ( *dialer) (,  string) (net.Conn, error) {
	return .net.Dial(, )
}