//go:build linux || darwin || (windows && (amd64 || 386))

//nolint:unused
package bridge

import (
	"net/netip"
	"sync"

	"github.com/sagernet/sing-tun/gtcpip"
	"github.com/sagernet/sing-tun/gtcpip/checksum"
	"github.com/sagernet/sing-tun/gtcpip/header"
	E "github.com/sagernet/sing/common/exceptions"
)

const (
	bridgeTunMTU         = 0xffff
	maxPacketLength      = 0xffff
	bridgeMaxInstances   = 254
	bridgeWriteBatchSize = 32
)

var (
	bridgeInet4Base = netip.MustParseAddr("192.0.2.1")
	bridgeInet6Base = netip.MustParseAddr("2001:db8::1")

	bridgeIndexAccess sync.Mutex
	bridgeIndexInUse  [bridgeMaxInstances]bool
)

func allocateBridgeIndex() (uint32, error) {
	bridgeIndexAccess.Lock()
	defer bridgeIndexAccess.Unlock()
	for index := range bridgeMaxInstances {
		if !bridgeIndexInUse[index] {
			bridgeIndexInUse[index] = true
			return uint32(index), nil
		}
	}
	return 0, E.New("too many bridge outbounds: limit is ", bridgeMaxInstances)
}

func releaseBridgeIndex(index uint32) {
	bridgeIndexAccess.Lock()
	defer bridgeIndexAccess.Unlock()
	bridgeIndexInUse[index] = false
}

func addressAt(base netip.Addr, offset uint32) netip.Addr {
	addr := base
	for range offset {
		addr = addr.Next()
	}
	return addr
}

func fixReturnChecksum(packet []byte) {
	switch header.IPVersion(packet) {
	case header.IPv4Version:
		if len(packet) < header.IPv4MinimumSize {
			return
		}
		ipHdr := header.IPv4(packet)
		if !ipHdr.IsValid(len(packet)) {
			return
		}
		if ipHdr.Flags()&header.IPv4FlagMoreFragments != 0 || ipHdr.FragmentOffset() != 0 {
			return
		}
		ipHdr.SetChecksum(0)
		ipHdr.SetChecksum(^ipHdr.CalculateChecksum())
		recomputeTransportChecksum(ipHdr.TransportProtocol(), ipHdr.Payload(), ipHdr.SourceAddressSlice(), ipHdr.DestinationAddressSlice())
	case header.IPv6Version:
		if len(packet) < header.IPv6MinimumSize {
			return
		}
		ipHdr := header.IPv6(packet)
		recomputeTransportChecksum(ipHdr.TransportProtocol(), ipHdr.Payload(), ipHdr.SourceAddressSlice(), ipHdr.DestinationAddressSlice())
	}
}

func recomputeTransportChecksum(protocol tcpip.TransportProtocolNumber, transport []byte, source []byte, destination []byte) {
	switch protocol {
	case header.TCPProtocolNumber:
		if len(transport) < header.TCPMinimumSize {
			return
		}
		tcpHdr := header.TCP(transport)
		tcpHdr.SetChecksum(0)
		payloadChecksum := checksum.Checksum(tcpHdr.Payload(), 0)
		pseudoChecksum := header.PseudoHeaderChecksum(header.TCPProtocolNumber, source, destination, uint16(len(transport)))
		tcpHdr.SetChecksum(^tcpHdr.CalculateChecksum(checksum.Combine(pseudoChecksum, payloadChecksum)))
	case header.UDPProtocolNumber:
		if len(transport) < header.UDPMinimumSize {
			return
		}
		udpHdr := header.UDP(transport)
		udpHdr.SetChecksum(0)
		payloadChecksum := checksum.Checksum(udpHdr.Payload(), 0)
		pseudoChecksum := header.PseudoHeaderChecksum(header.UDPProtocolNumber, source, destination, udpHdr.Length())
		udpChecksum := ^udpHdr.CalculateChecksum(checksum.Combine(pseudoChecksum, payloadChecksum))
		if udpChecksum == 0 {
			udpChecksum = 0xffff
		}
		udpHdr.SetChecksum(udpChecksum)
	case header.ICMPv4ProtocolNumber:
		if len(transport) < header.ICMPv4MinimumSize {
			return
		}
		icmpHdr := header.ICMPv4(transport)
		icmpHdr.SetChecksum(header.ICMPv4Checksum(icmpHdr, 0))
	case header.ICMPv6ProtocolNumber:
		if len(transport) < header.ICMPv6MinimumSize {
			return
		}
		icmpHdr := header.ICMPv6(transport)
		icmpHdr.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
			Header: icmpHdr,
			Src:    source,
			Dst:    destination,
		}))
	}
}
