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

package vnet

import (
	
	
	
	
	

	
)

var (
	errNATRequriesMapping            = errors.New("1:1 NAT requires more than one mapping")
	errMismatchLengthIP              = errors.New("length mismtach between mappedIPs and localIPs")
	errNonUDPTranslationNotSupported = errors.New("non-udp translation is not supported yet")
	errNoAssociatedLocalAddress      = errors.New("no associated local address")
	errNoNATBindingFound             = errors.New("no NAT binding found")
	errHasNoPermission               = errors.New("has no permission")
)

// EndpointDependencyType defines a type of behavioral dependendency on the
// remote endpoint's IP address or port number. This is used for the two
// kinds of behaviors:
//   - Port mapping behavior
//   - Filtering behavior
//
// See: https://tools.ietf.org/html/rfc4787
type EndpointDependencyType uint8

const (
	// EndpointIndependent means the behavior is independent of the endpoint's address or port
	EndpointIndependent EndpointDependencyType = iota
	// EndpointAddrDependent means the behavior is dependent on the endpoint's address
	EndpointAddrDependent
	// EndpointAddrPortDependent means the behavior is dependent on the endpoint's address and port
	EndpointAddrPortDependent
)

// NATMode defines basic behavior of the NAT
type NATMode uint8

const (
	// NATModeNormal means the NAT behaves as a standard NAPT (RFC 2663).
	NATModeNormal NATMode = iota
	// NATModeNAT1To1 exhibits 1:1 DNAT where the external IP address is statically mapped to
	// a specific local IP address with port number is preserved always between them.
	// When this mode is selected, MappingBehavior, FilteringBehavior, PortPreservation and
	// MappingLifeTime of NATType are ignored.
	NATModeNAT1To1
)

const (
	defaultNATMappingLifeTime = 30 * time.Second
)

// NATType has a set of parameters that define the behavior of NAT.
type NATType struct {
	Mode              NATMode
	MappingBehavior   EndpointDependencyType
	FilteringBehavior EndpointDependencyType
	Hairpinning       bool // Not implemented yet
	PortPreservation  bool // Not implemented yet
	MappingLifeTime   time.Duration
}

type natConfig struct {
	name          string
	natType       NATType
	mappedIPs     []net.IP // mapped IPv4
	localIPs      []net.IP // local IPv4, required only when the mode is NATModeNAT1To1
	loggerFactory logging.LoggerFactory
}

type mapping struct {
	proto   string              // "udp" or "tcp"
	local   string              // "<local-ip>:<local-port>"
	mapped  string              // "<mapped-ip>:<mapped-port>"
	bound   string              // key: "[<remote-ip>[:<remote-port>]]"
	filters map[string]struct{} // key: "[<remote-ip>[:<remote-port>]]"
	expires time.Time           // time to expire
}

type networkAddressTranslator struct {
	name           string
	natType        NATType
	mappedIPs      []net.IP            // mapped IPv4
	localIPs       []net.IP            // local IPv4, required only when the mode is NATModeNAT1To1
	outboundMap    map[string]*mapping // key: "<proto>:<local-ip>:<local-port>[:remote-ip[:remote-port]]
	inboundMap     map[string]*mapping // key: "<proto>:<mapped-ip>:<mapped-port>"
	udpPortCounter int
	mutex          sync.RWMutex
	log            logging.LeveledLogger
}

func newNAT( *natConfig) (*networkAddressTranslator, error) {
	 := .natType

	if .Mode == NATModeNAT1To1 {
		// 1:1 NAT behavior
		.MappingBehavior = EndpointIndependent
		.FilteringBehavior = EndpointIndependent
		.PortPreservation = true
		.MappingLifeTime = 0

		if len(.mappedIPs) == 0 {
			return nil, errNATRequriesMapping
		}
		if len(.mappedIPs) != len(.localIPs) {
			return nil, errMismatchLengthIP
		}
	} else {
		// Normal (NAPT) behavior
		.Mode = NATModeNormal
		if .MappingLifeTime == 0 {
			.MappingLifeTime = defaultNATMappingLifeTime
		}
	}

	return &networkAddressTranslator{
		name:        .name,
		natType:     ,
		mappedIPs:   .mappedIPs,
		localIPs:    .localIPs,
		outboundMap: map[string]*mapping{},
		inboundMap:  map[string]*mapping{},
		log:         .loggerFactory.NewLogger("vnet"),
	}, nil
}

func ( *networkAddressTranslator) ( net.IP) net.IP {
	for ,  := range .localIPs {
		if .Equal() {
			return .mappedIPs[]
		}
	}
	return nil
}

func ( *networkAddressTranslator) ( net.IP) net.IP {
	for ,  := range .mappedIPs {
		if .Equal() {
			return .localIPs[]
		}
	}
	return nil
}

func ( *networkAddressTranslator) ( Chunk) (Chunk, error) {
	.mutex.Lock()
	defer .mutex.Unlock()

	 := .Clone()

	if .Network() == udp {
		if .natType.Mode == NATModeNAT1To1 {
			// 1:1 NAT behavior
			 := .SourceAddr().(*net.UDPAddr) //nolint:forcetypeassert
			 := .getPairedMappedIP(.IP)
			if  == nil {
				.log.Debugf("[%s] drop outbound chunk %s with not route", .name, .String())
				return nil, nil // nolint:nilnil
			}
			 := .Port
			if  := .setSourceAddr(fmt.Sprintf("%s:%d", .String(), ));  != nil {
				return nil, 
			}
		} else {
			// Normal (NAPT) behavior
			var ,  string
			switch .natType.MappingBehavior {
			case EndpointIndependent:
				 = ""
			case EndpointAddrDependent:
				 = .getDestinationIP().String()
			case EndpointAddrPortDependent:
				 = .DestinationAddr().String()
			}

			switch .natType.FilteringBehavior {
			case EndpointIndependent:
				 = ""
			case EndpointAddrDependent:
				 = .getDestinationIP().String()
			case EndpointAddrPortDependent:
				 = .DestinationAddr().String()
			}

			 := fmt.Sprintf("udp:%s:%s", .SourceAddr().String(), )

			 := .findOutboundMapping()
			if  == nil {
				// Create a new mapping
				 := 0xC000 + .udpPortCounter
				.udpPortCounter++

				 = &mapping{
					proto:   .SourceAddr().Network(),
					local:   .SourceAddr().String(),
					bound:   ,
					mapped:  fmt.Sprintf("%s:%d", .mappedIPs[0].String(), ),
					filters: map[string]struct{}{},
					expires: time.Now().Add(.natType.MappingLifeTime),
				}

				.outboundMap[] = 

				 := fmt.Sprintf("udp:%s", .mapped)

				.log.Debugf("[%s] created a new NAT binding oKey=%s iKey=%s",
					.name,
					,
					)

				.filters[] = struct{}{}
				.log.Debugf("[%s] permit access from %s to %s", .name, , .mapped)
				.inboundMap[] = 
			} else if ,  := .filters[]; ! {
				.log.Debugf("[%s] permit access from %s to %s", .name, , .mapped)
				.filters[] = struct{}{}
			}

			if  := .setSourceAddr(.mapped);  != nil {
				return nil, 
			}
		}

		.log.Debugf("[%s] translate outbound chunk from %s to %s", .name, .String(), .String())

		return , nil
	}

	return nil, errNonUDPTranslationNotSupported
}

func ( *networkAddressTranslator) ( Chunk) (Chunk, error) {
	.mutex.Lock()
	defer .mutex.Unlock()

	 := .Clone()

	if .Network() == udp {
		if .natType.Mode == NATModeNAT1To1 {
			// 1:1 NAT behavior
			 := .DestinationAddr().(*net.UDPAddr) //nolint:forcetypeassert
			 := .getPairedLocalIP(.IP)
			if  == nil {
				return nil, fmt.Errorf("drop %s as %w", .String(), errNoAssociatedLocalAddress)
			}
			 := .DestinationAddr().(*net.UDPAddr).Port //nolint:forcetypeassert
			if  := .setDestinationAddr(fmt.Sprintf("%s:%d", , ));  != nil {
				return nil, 
			}
		} else {
			// Normal (NAPT) behavior
			 := fmt.Sprintf("udp:%s", .DestinationAddr().String())
			 := .findInboundMapping()
			if  == nil {
				return nil, fmt.Errorf("drop %s as %w", .String(), errNoNATBindingFound)
			}

			var  string
			switch .natType.FilteringBehavior {
			case EndpointIndependent:
				 = ""
			case EndpointAddrDependent:
				 = .getSourceIP().String()
			case EndpointAddrPortDependent:
				 = .SourceAddr().String()
			}

			if ,  := .filters[]; ! {
				return nil, fmt.Errorf("drop %s as the remote %s %w", .String(), , errHasNoPermission)
			}

			// See RFC 4847 Section 4.3.  Mapping Refresh
			// a) Inbound refresh may be useful for applications with no outgoing
			//   UDP traffic.  However, allowing inbound refresh may allow an
			//   external attacker or misbehaving application to keep a mapping
			//   alive indefinitely.  This may be a security risk.  Also, if the
			//   process is repeated with different ports, over time, it could
			//   use up all the ports on the NAT.

			if  := .setDestinationAddr(.local);  != nil {
				return nil, 
			}
		}

		.log.Debugf("[%s] translate inbound chunk from %s to %s", .name, .String(), .String())

		return , nil
	}

	return nil, errNonUDPTranslationNotSupported
}

// caller must hold the mutex
func ( *networkAddressTranslator) ( string) *mapping {
	 := time.Now()

	,  := .outboundMap[]
	if  {
		// check if this mapping is expired
		if .After(.expires) {
			.removeMapping()
			 = nil // expired
		} else {
			.expires = time.Now().Add(.natType.MappingLifeTime)
		}
	}

	return 
}

// caller must hold the mutex
func ( *networkAddressTranslator) ( string) *mapping {
	 := time.Now()
	,  := .inboundMap[]
	if ! {
		return nil
	}

	// check if this mapping is expired
	if .After(.expires) {
		.removeMapping()
		return nil
	}

	return 
}

// caller must hold the mutex
func ( *networkAddressTranslator) ( *mapping) {
	 := fmt.Sprintf("%s:%s:%s", .proto, .local, .bound)
	 := fmt.Sprintf("%s:%s", .proto, .mapped)

	delete(.outboundMap, )
	delete(.inboundMap, )
}