//go:build darwin || linux || freebsd

package quic

import (
	
	
	
	
	
	
	
	
	
	

	
	
	

	
	
	
)

const (
	ecnMask       = 0x3
	oobBufferSize = 128
)

// Contrary to what the naming suggests, the ipv{4,6}.Message is not dependent on the IP version.
// They're both just aliases for x/net/internal/socket.Message.
// This means we can use this struct to read from a socket that receives both IPv4 and IPv6 messages.
var _ ipv4.Message = ipv6.Message{}

type batchConn interface {
	ReadBatch(ms []ipv4.Message, flags int) (int, error)
}

func inspectReadBuffer( syscall.RawConn) (int, error) {
	var  int
	var  error
	if  := .Control(func( uintptr) {
		,  = unix.GetsockoptInt(int(), unix.SOL_SOCKET, unix.SO_RCVBUF)
	});  != nil {
		return 0, 
	}
	return , 
}

func inspectWriteBuffer( syscall.RawConn) (int, error) {
	var  int
	var  error
	if  := .Control(func( uintptr) {
		,  = unix.GetsockoptInt(int(), unix.SOL_SOCKET, unix.SO_SNDBUF)
	});  != nil {
		return 0, 
	}
	return , 
}

func isECNDisabledUsingEnv() bool {
	,  := strconv.ParseBool(os.Getenv("QUIC_GO_DISABLE_ECN"))
	return  == nil && 
}

type oobConn struct {
	OOBCapablePacketConn
	batchConn batchConn

	readPos uint8
	// Packets received from the kernel, but not yet returned by ReadPacket().
	messages []ipv4.Message
	buffers  [batchSize]*packetBuffer

	cap connCapabilities
}

var _ rawConn = &oobConn{}

func newConn( OOBCapablePacketConn,  bool) (*oobConn, error) {
	,  := .SyscallConn()
	if  != nil {
		return nil, 
	}
	var  bool
	if ,  := .LocalAddr().(*net.UDPAddr);  && .IP.IsUnspecified() {
		 = true
	}
	// We don't know if this a IPv4-only, IPv6-only or a IPv4-and-IPv6 connection.
	// Try enabling receiving of ECN and packet info for both IP versions.
	// We expect at least one of those syscalls to succeed.
	var , , ,  error
	if  := .Control(func( uintptr) {
		 = unix.SetsockoptInt(int(), unix.IPPROTO_IP, unix.IP_RECVTOS, 1)
		 = unix.SetsockoptInt(int(), unix.IPPROTO_IPV6, unix.IPV6_RECVTCLASS, 1)

		if  {
			 = unix.SetsockoptInt(int(), unix.IPPROTO_IP, ipv4PKTINFO, 1)
			 = unix.SetsockoptInt(int(), unix.IPPROTO_IPV6, unix.IPV6_RECVPKTINFO, 1)
		}
	});  != nil {
		return nil, 
	}
	switch {
	case  == nil &&  == nil:
		utils.DefaultLogger.Debugf("Activating reading of ECN bits for IPv4 and IPv6.")
	case  == nil &&  != nil:
		utils.DefaultLogger.Debugf("Activating reading of ECN bits for IPv4.")
	case  != nil &&  == nil:
		utils.DefaultLogger.Debugf("Activating reading of ECN bits for IPv6.")
	case  != nil &&  != nil:
		return nil, errors.New("activating ECN failed for both IPv4 and IPv6")
	}
	if  {
		switch {
		case  == nil &&  == nil:
			utils.DefaultLogger.Debugf("Activating reading of packet info for IPv4 and IPv6.")
		case  == nil &&  != nil:
			utils.DefaultLogger.Debugf("Activating reading of packet info bits for IPv4.")
		case  != nil &&  == nil:
			utils.DefaultLogger.Debugf("Activating reading of packet info bits for IPv6.")
		case  != nil &&  != nil:
			return nil, errors.New("activating packet info failed for both IPv4 and IPv6")
		}
	}

	// Allows callers to pass in a connection that already satisfies batchConn interface
	// to make use of the optimisation. Otherwise, ipv4.NewPacketConn would unwrap the file descriptor
	// via SyscallConn(), and read it that way, which might not be what the caller wants.
	var  batchConn
	if ,  := .(batchConn);  {
		 = 
	} else {
		 = ipv4.NewPacketConn()
	}

	 := make([]ipv4.Message, batchSize)
	for  := range  {
		// preallocate the [][]byte
		[].Buffers = make([][]byte, 1)
	}
	 := &oobConn{
		OOBCapablePacketConn: ,
		batchConn:            ,
		messages:             ,
		readPos:              batchSize,
		cap: connCapabilities{
			DF:  ,
			GSO: isGSOEnabled(),
			ECN: isECNEnabled(),
		},
	}
	for  := 0;  < batchSize; ++ {
		.messages[].OOB = make([]byte, oobBufferSize)
	}
	return , nil
}

var invalidCmsgOnceV4, invalidCmsgOnceV6 sync.Once

func ( *oobConn) () (receivedPacket, error) {
	if len(.messages) == int(.readPos) { // all messages read. Read the next batch of messages.
		.messages = .messages[:batchSize]
		// replace buffers data buffers up to the packet that has been consumed during the last ReadBatch call
		for  := uint8(0);  < .readPos; ++ {
			 := getPacketBuffer()
			.Data = .Data[:protocol.MaxPacketBufferSize]
			.buffers[] = 
			.messages[].Buffers[0] = .buffers[].Data
		}
		.readPos = 0

		,  := .batchConn.ReadBatch(.messages, 0)
		if  == 0 ||  != nil {
			return receivedPacket{}, 
		}
		.messages = .messages[:]
	}

	 := .messages[.readPos]
	 := .buffers[.readPos]
	.readPos++

	 := .OOB[:.NN]
	 := receivedPacket{
		remoteAddr: .Addr,
		rcvTime:    monotime.Now(),
		data:       .Buffers[0][:.N],
		buffer:     ,
	}
	for len() > 0 {
		, , ,  := unix.ParseOneSocketControlMessage()
		if  != nil {
			return receivedPacket{}, 
		}
		if .Level == unix.IPPROTO_IP {
			switch .Type {
			case msgTypeIPTOS:
				if len() != 1 {
					return receivedPacket{}, errors.New("invalid IPTOS size")
				}
				.ecn = protocol.ParseECNHeaderBits([0] & ecnMask)
			case ipv4PKTINFO:
				, ,  := parseIPv4PktInfo()
				if  {
					.info.addr = 
					.info.ifIndex = 
				} else {
					invalidCmsgOnceV4.Do(func() {
						log.Printf("Received invalid IPv4 packet info control message: %+x. "+
							"This should never occur, please open a new issue and include details about the architecture.", )
					})
				}
			}
		}
		if .Level == unix.IPPROTO_IPV6 {
			switch .Type {
			case unix.IPV6_TCLASS:
				if len() != 4 {
					return receivedPacket{}, errors.New("invalid IPV6_TCLASS size")
				}
				 := uint8(binary.NativeEndian.Uint32()) & ecnMask
				.ecn = protocol.ParseECNHeaderBits()
			case unix.IPV6_PKTINFO:
				// struct in6_pktinfo {
				// 	struct in6_addr ipi6_addr;    /* src/dst IPv6 address */
				// 	unsigned int    ipi6_ifindex; /* send/recv interface index */
				// };
				if len() == 20 {
					.info.addr = netip.AddrFrom16(*(*[16]byte)([:16])).Unmap()
					.info.ifIndex = binary.NativeEndian.Uint32([16:])
				} else {
					invalidCmsgOnceV6.Do(func() {
						log.Printf("Received invalid IPv6 packet info control message: %+x. "+
							"This should never occur, please open a new issue and include details about the architecture.", )
					})
				}
			}
		}
		 = 
	}
	return , nil
}

// WritePacket writes a new packet.
func ( *oobConn) ( []byte,  net.Addr,  []byte,  uint16,  protocol.ECN) (int, error) {
	 := 
	if  > 0 {
		if !.capabilities().GSO {
			panic("GSO disabled")
		}
		 = appendUDPSegmentSizeMsg(, )
	}
	if  != protocol.ECNUnsupported {
		if !.capabilities().ECN {
			panic("tried to send an ECN-marked packet although ECN is disabled")
		}
		if ,  := .(*net.UDPAddr);  {
			if .IP.To4() != nil {
				 = appendIPv4ECNMsg(, )
			} else {
				 = appendIPv6ECNMsg(, )
			}
		}
	}
	, ,  := .WriteMsgUDP(, , .(*net.UDPAddr))
	return , 
}

func ( *oobConn) () connCapabilities {
	return .cap
}

type packetInfo struct {
	addr    netip.Addr
	ifIndex uint32
}

func ( *packetInfo) () []byte {
	if  == nil {
		return nil
	}
	if .addr.Is4() {
		 := .addr.As4()
		// struct in_pktinfo {
		// 	unsigned int   ipi_ifindex;  /* Interface index */
		// 	struct in_addr ipi_spec_dst; /* Local address */
		// 	struct in_addr ipi_addr;     /* Header Destination address */
		// };
		 := ipv4.ControlMessage{
			Src:     [:],
			IfIndex: int(.ifIndex),
		}
		return .Marshal()
	} else if .addr.Is6() {
		 := .addr.As16()
		// struct in6_pktinfo {
		// 	struct in6_addr ipi6_addr;    /* src/dst IPv6 address */
		// 	unsigned int    ipi6_ifindex; /* send/recv interface index */
		// };
		 := ipv6.ControlMessage{
			Src:     [:],
			IfIndex: int(.ifIndex),
		}
		return .Marshal()
	}
	return nil
}

func appendIPv4ECNMsg( []byte,  protocol.ECN) []byte {
	 := len()
	 = append(, make([]byte, unix.CmsgSpace(ecnIPv4DataLen))...)
	 := (*unix.Cmsghdr)(unsafe.Pointer(&[]))
	.Level = syscall.IPPROTO_IP
	.Type = unix.IP_TOS
	.SetLen(unix.CmsgLen(ecnIPv4DataLen))

	// UnixRights uses the private `data` method, but I *think* this achieves the same goal.
	 :=  + unix.CmsgSpace(0)
	[] = .ToHeaderBits()
	return 
}

func appendIPv6ECNMsg( []byte,  protocol.ECN) []byte {
	 := len()
	const  = 4
	 = append(, make([]byte, unix.CmsgSpace())...)
	 := (*unix.Cmsghdr)(unsafe.Pointer(&[]))
	.Level = syscall.IPPROTO_IPV6
	.Type = unix.IPV6_TCLASS
	.SetLen(unix.CmsgLen())

	// UnixRights uses the private `data` method, but I *think* this achieves the same goal.
	 :=  + unix.CmsgSpace(0)
	binary.NativeEndian.PutUint32([:+], uint32(.ToHeaderBits()))
	return 
}