package proxyproto

import (
	
	
	
	
	
	
	
	
)

// maxV2HeaderSize is the maximum acceptable size of a V2 header.
//
// A V2 header may be at most 16 bytes + 64KiB large. We enforce a lower limit
// to mitigate memory allocation DoS while allowing real-world legitimate
// headers. PP2_SUBTYPE_SSL_CLIENT_CERT is typically between 1 and 2KiB, so we
// use a 4KiB limit to leave some room for other TLVs.
const maxV2HeaderSize = 4096

var (
	lengthUnspec      = uint16(0)
	lengthV4          = uint16(12)
	lengthV6          = uint16(36)
	lengthUnix        = uint16(216)
	lengthUnspecBytes = func() []byte {
		 := make([]byte, 2)
		binary.BigEndian.PutUint16(, lengthUnspec)
		return 
	}()
	lengthV4Bytes = func() []byte {
		 := make([]byte, 2)
		binary.BigEndian.PutUint16(, lengthV4)
		return 
	}()
	lengthV6Bytes = func() []byte {
		 := make([]byte, 2)
		binary.BigEndian.PutUint16(, lengthV6)
		return 
	}()
	lengthUnixBytes = func() []byte {
		 := make([]byte, 2)
		binary.BigEndian.PutUint16(, lengthUnix)
		return 
	}()
	errUint16Overflow = errors.New("proxyproto: uint16 overflow")
)

type _ports struct {
	SrcPort uint16
	DstPort uint16
}

type _addr4 struct {
	Src     [4]byte
	Dst     [4]byte
	SrcPort uint16
	DstPort uint16
}

type _addr6 struct {
	Src [16]byte
	Dst [16]byte
	_ports
}

type _addrUnix struct {
	Src [108]byte
	Dst [108]byte
}

func parseVersion2( *bufio.Reader) ( *Header,  error) {
	// Skip first 12 bytes (signature)
	for range 12 {
		if _,  = .ReadByte();  != nil {
			return nil, fmt.Errorf("%w: %w", ErrCantReadProtocolVersionAndCommand, )
		}
	}

	 = new(Header)
	.Version = 2

	// Read the 13th byte, protocol version and command
	,  := .ReadByte()
	if  != nil {
		return nil, fmt.Errorf("%w: %w", ErrCantReadProtocolVersionAndCommand, )
	}
	.Command = ProtocolVersionAndCommand()
	if ,  := supportedCommand[.Command]; ! {
		return nil, ErrUnsupportedProtocolVersionAndCommand
	}

	// Read the 14th byte, address family and protocol
	,  := .ReadByte()
	if  != nil {
		return nil, fmt.Errorf("%w: %w", ErrCantReadAddressFamilyAndProtocol, )
	}
	.TransportProtocol = AddressFamilyAndProtocol()
	// UNSPEC is only supported when LOCAL is set.
	if .TransportProtocol == UNSPEC && .Command != LOCAL {
		return nil, ErrUnsupportedAddressFamilyAndProtocol
	}

	// Make sure there are bytes available as specified in length
	var  uint16
	if  := binary.Read(, binary.BigEndian, &);  != nil {
		return nil, fmt.Errorf("%w: %w", ErrCantReadLength, )
	}
	if !.validateLength() {
		return nil, ErrInvalidLength
	}

	// Return early if the length is zero, which means that
	// there's no address information and TLVs present for UNSPEC.
	if  == 0 {
		return , nil
	}

	if  > maxV2HeaderSize {
		return nil, ErrInvalidLength
	}

	// Length-limited reader for payload section
	 := io.LimitReader(, int64()).(*io.LimitedReader)

	// Read addresses and ports for protocols other than UNSPEC.
	// Ignore address information for UNSPEC, and skip straight to read TLVs,
	// since the length is greater than zero.
	if .TransportProtocol != UNSPEC {
		if .TransportProtocol.IsIPv4() {
			var  _addr4
			if  := binary.Read(, binary.BigEndian, &);  != nil {
				return nil, fmt.Errorf("%w: %w", ErrInvalidAddress, )
			}
			.SourceAddr = newIPAddr(.TransportProtocol, .Src[:], .SrcPort)
			.DestinationAddr = newIPAddr(.TransportProtocol, .Dst[:], .DstPort)
		} else if .TransportProtocol.IsIPv6() {
			var  _addr6
			if  := binary.Read(, binary.BigEndian, &);  != nil {
				return nil, fmt.Errorf("%w: %w", ErrInvalidAddress, )
			}
			.SourceAddr = newIPAddr(.TransportProtocol, .Src[:], .SrcPort)
			.DestinationAddr = newIPAddr(.TransportProtocol, .Dst[:], .DstPort)
		} else if .TransportProtocol.IsUnix() {
			var  _addrUnix
			if  := binary.Read(, binary.BigEndian, &);  != nil {
				return nil, fmt.Errorf("%w: %w", ErrInvalidAddress, )
			}

			 := "unix"
			if .TransportProtocol.IsDatagram() {
				 = "unixgram"
			}

			.SourceAddr = &net.UnixAddr{
				Net:  ,
				Name: parseUnixName(.Src[:]),
			}
			.DestinationAddr = &net.UnixAddr{
				Net:  ,
				Name: parseUnixName(.Dst[:]),
			}
		}
	}

	// Copy bytes for optional Type-Length-Value vector
	.rawTLVs = make([]byte, .N) // Allocate minimum size slice
	if _,  = io.ReadFull(, .rawTLVs);  != nil &&  != io.EOF {
		return nil, 
	}

	if .N != 0 {
		return nil, ErrInvalidLength
	}

	return , nil
}

func ( *Header) () ([]byte, error) {
	var  bytes.Buffer
	.Write(SIGV2)
	.WriteByte(.Command.toByte())
	.WriteByte(.TransportProtocol.toByte())
	if .TransportProtocol.IsUnspec() {
		// For UNSPEC, write no addresses and ports but only TLVs if they are present
		,  := addTLVLen(lengthUnspecBytes, len(.rawTLVs))
		if  != nil {
			return nil, 
		}
		.Write()
	} else {
		var ,  []byte
		if .TransportProtocol.IsIPv4() {
			,  := addTLVLen(lengthV4Bytes, len(.rawTLVs))
			if  != nil {
				return nil, 
			}
			.Write()
			, ,  := .IPs()
			 = .To4()
			 = .To4()
		} else if .TransportProtocol.IsIPv6() {
			,  := addTLVLen(lengthV6Bytes, len(.rawTLVs))
			if  != nil {
				return nil, 
			}
			.Write()
			, ,  := .IPs()
			 = .To16()
			 = .To16()
		} else if .TransportProtocol.IsUnix() {
			.Write(lengthUnixBytes)
			, ,  := .UnixAddrs()
			if ! {
				return nil, ErrInvalidAddress
			}
			 = formatUnixName(.Name)
			 = formatUnixName(.Name)
		}

		if  == nil ||  == nil {
			return nil, ErrInvalidAddress
		}
		.Write()
		.Write()

		if , ,  := .Ports();  {
			if  < 0 ||  > math.MaxUint16 ||  < 0 ||  > math.MaxUint16 {
				return nil, ErrInvalidPortNumber
			}
			 := make([]byte, 2)

			//nolint:gosec // Bounds are checked above.
			binary.BigEndian.PutUint16(, uint16())
			.Write()

			//nolint:gosec // Bounds are checked above.
			binary.BigEndian.PutUint16(, uint16())
			.Write()
		}
	}

	if len(.rawTLVs) > 0 {
		.Write(.rawTLVs)
	}

	return .Bytes(), nil
}

func ( *Header) ( uint16) bool {
	if .TransportProtocol.IsIPv4() {
		return  >= lengthV4
	} else if .TransportProtocol.IsIPv6() {
		return  >= lengthV6
	} else if .TransportProtocol.IsUnix() {
		return  >= lengthUnix
	} else if .TransportProtocol.IsUnspec() {
		return  >= lengthUnspec
	}
	return false
}

// addTLVLen adds the length of the TLV to the header length or errors on uint16 overflow.
func addTLVLen( []byte,  int) ([]byte, error) {
	if  == 0 {
		return , nil
	}
	 := binary.BigEndian.Uint16()
	 := int() + 
	if  >= 1<<16 {
		return nil, errUint16Overflow
	}
	 := make([]byte, 2)
	//nolint:gosec // newLen bounds are validated above.
	binary.BigEndian.PutUint16(, uint16())
	return , nil
}

func newIPAddr( AddressFamilyAndProtocol,  net.IP,  uint16) net.Addr {
	if .IsStream() {
		return &net.TCPAddr{IP: , Port: int()}
	}
	if .IsDatagram() {
		return &net.UDPAddr{IP: , Port: int()}
	}
	return nil
}

func parseUnixName( []byte) string {
	, ,  := bytes.Cut(, []byte{0})
	if ! {
		return string()
	}
	return string()
}

func formatUnixName( string) []byte {
	 := int(lengthUnix) / 2
	if len() >=  {
		return []byte([:])
	}
	 := make([]byte, -len())
	return append([]byte(), ...)
}