package http3

import (
	
	
	
	
	

	
	
	
	
)

// FrameType is the frame type of a HTTP/3 frame
type FrameType uint64

type frame any

// The maximum length of an encoded HTTP/3 frame header is 16:
// The frame has a type and length field, both QUIC varints (maximum 8 bytes in length)
const frameHeaderLen = 16

type countingByteReader struct {
	quicvarint.Reader
	NumRead int
}

func ( *countingByteReader) () (byte, error) {
	,  := .Reader.ReadByte()
	if  == nil {
		.NumRead++
	}
	return , 
}

func ( *countingByteReader) ( []byte) (int, error) {
	,  := .Reader.Read()
	.NumRead += 
	return , 
}

func ( *countingByteReader) () {
	.NumRead = 0
}

type frameParser struct {
	r         io.Reader
	streamID  quic.StreamID
	closeConn func(quic.ApplicationErrorCode, string) error
}

func ( *frameParser) ( qlogwriter.Recorder) (frame, error) {
	 := &countingByteReader{Reader: quicvarint.NewReader(.r)}
	for {
		,  := quicvarint.Read()
		if  != nil {
			return nil, 
		}
		,  := quicvarint.Read()
		if  != nil {
			return nil, 
		}

		switch  {
		case 0x0: // DATA
			if  != nil {
				.RecordEvent(qlog.FrameParsed{
					StreamID: .streamID,
					Raw: qlog.RawInfo{
						Length:        int() + .NumRead,
						PayloadLength: int(),
					},
					Frame: qlog.Frame{Frame: qlog.DataFrame{}},
				})
			}
			return &dataFrame{Length: }, nil
		case 0x1: // HEADERS
			return &headersFrame{
				Length:    ,
				headerLen: .NumRead,
			}, nil
		case 0x4: // SETTINGS
			return parseSettingsFrame(, , .streamID, )
		case 0x3: // unsupported: CANCEL_PUSH
			if  != nil {
				.RecordEvent(qlog.FrameParsed{
					StreamID: .streamID,
					Raw:      qlog.RawInfo{Length: .NumRead, PayloadLength: int()},
					Frame:    qlog.Frame{Frame: qlog.CancelPushFrame{}},
				})
			}
		case 0x5: // unsupported: PUSH_PROMISE
			if  != nil {
				.RecordEvent(qlog.FrameParsed{
					StreamID: .streamID,
					Raw:      qlog.RawInfo{Length: .NumRead, PayloadLength: int()},
					Frame:    qlog.Frame{Frame: qlog.PushPromiseFrame{}},
				})
			}
		case 0x7: // GOAWAY
			return parseGoAwayFrame(, , .streamID, )
		case 0xd: // unsupported: MAX_PUSH_ID
			if  != nil {
				.RecordEvent(qlog.FrameParsed{
					StreamID: .streamID,
					Raw:      qlog.RawInfo{Length: .NumRead, PayloadLength: int()},
					Frame:    qlog.Frame{Frame: qlog.MaxPushIDFrame{}},
				})
			}
		case 0x2, 0x6, 0x8, 0x9: // reserved frame types
			if  != nil {
				.RecordEvent(qlog.FrameParsed{
					StreamID: .streamID,
					Raw:      qlog.RawInfo{Length: .NumRead + int(), PayloadLength: int()},
					Frame:    qlog.Frame{Frame: qlog.ReservedFrame{Type: }},
				})
			}
			.closeConn(quic.ApplicationErrorCode(ErrCodeFrameUnexpected), "")
			return nil, fmt.Errorf("http3: reserved frame type: %d", )
		default:
			// unknown frame types
			if  != nil {
				.RecordEvent(qlog.FrameParsed{
					StreamID: .streamID,
					Raw:      qlog.RawInfo{Length: .NumRead, PayloadLength: int()},
					Frame:    qlog.Frame{Frame: qlog.UnknownFrame{Type: }},
				})
			}
		}

		// skip over the payload
		if ,  := io.CopyN(io.Discard, , int64());  != nil {
			return nil, 
		}
		.Reset()
	}
}

type dataFrame struct {
	Length uint64
}

func ( *dataFrame) ( []byte) []byte {
	 = quicvarint.Append(, 0x0)
	return quicvarint.Append(, .Length)
}

type headersFrame struct {
	Length    uint64
	headerLen int // number of bytes read for type and length field
}

func ( *headersFrame) ( []byte) []byte {
	 = quicvarint.Append(, 0x1)
	return quicvarint.Append(, .Length)
}

const (
	// SETTINGS_MAX_FIELD_SECTION_SIZE
	settingMaxFieldSectionSize = 0x6
	// Extended CONNECT, RFC 9220
	settingExtendedConnect = 0x8
	// HTTP Datagrams, RFC 9297
	settingDatagram = 0x33
)

type settingsFrame struct {
	MaxFieldSectionSize int64 // SETTINGS_MAX_FIELD_SECTION_SIZE, -1 if not set

	Datagram        bool              // HTTP Datagrams, RFC 9297
	ExtendedConnect bool              // Extended CONNECT, RFC 9220
	Other           map[uint64]uint64 // all settings that we don't explicitly recognize
}

func pointer[ any]( ) * {
	return &
}

func parseSettingsFrame( *countingByteReader,  uint64,  quic.StreamID,  qlogwriter.Recorder) (*settingsFrame, error) {
	if  > 8*(1<<10) {
		return nil, fmt.Errorf("unexpected size for SETTINGS frame: %d", )
	}
	 := make([]byte, )
	if ,  := io.ReadFull(, );  != nil {
		if  == io.ErrUnexpectedEOF {
			return nil, io.EOF
		}
		return nil, 
	}
	 := &settingsFrame{MaxFieldSectionSize: -1}
	 := bytes.NewReader()
	 := qlog.SettingsFrame{MaxFieldSectionSize: -1}
	var , ,  bool
	for .Len() > 0 {
		,  := quicvarint.Read()
		if  != nil { // should not happen. We allocated the whole frame already.
			return nil, 
		}
		,  := quicvarint.Read()
		if  != nil { // should not happen. We allocated the whole frame already.
			return nil, 
		}

		switch  {
		case settingMaxFieldSectionSize:
			if  {
				return nil, fmt.Errorf("duplicate setting: %d", )
			}
			 = true
			.MaxFieldSectionSize = int64()
			.MaxFieldSectionSize = int64()
		case settingExtendedConnect:
			if  {
				return nil, fmt.Errorf("duplicate setting: %d", )
			}
			 = true
			if  != 0 &&  != 1 {
				return nil, fmt.Errorf("invalid value for SETTINGS_ENABLE_CONNECT_PROTOCOL: %d", )
			}
			.ExtendedConnect =  == 1
			if  != nil {
				.ExtendedConnect = pointer(.ExtendedConnect)
			}
		case settingDatagram:
			if  {
				return nil, fmt.Errorf("duplicate setting: %d", )
			}
			 = true
			if  != 0 &&  != 1 {
				return nil, fmt.Errorf("invalid value for SETTINGS_H3_DATAGRAM: %d", )
			}
			.Datagram =  == 1
			if  != nil {
				.Datagram = pointer(.Datagram)
			}
		default:
			if ,  := .Other[];  {
				return nil, fmt.Errorf("duplicate setting: %d", )
			}
			if .Other == nil {
				.Other = make(map[uint64]uint64)
			}
			.Other[] = 
		}
	}
	if  != nil {
		.Other = maps.Clone(.Other)

		.RecordEvent(qlog.FrameParsed{
			StreamID: ,
			Raw: qlog.RawInfo{
				Length:        .NumRead,
				PayloadLength: int(),
			},
			Frame: qlog.Frame{Frame: },
		})
	}
	return , nil
}

func ( *settingsFrame) ( []byte) []byte {
	 = quicvarint.Append(, 0x4)
	var  int
	if .MaxFieldSectionSize >= 0 {
		 += quicvarint.Len(settingMaxFieldSectionSize) + quicvarint.Len(uint64(.MaxFieldSectionSize))
	}
	for ,  := range .Other {
		 += quicvarint.Len() + quicvarint.Len()
	}
	if .Datagram {
		 += quicvarint.Len(settingDatagram) + quicvarint.Len(1)
	}
	if .ExtendedConnect {
		 += quicvarint.Len(settingExtendedConnect) + quicvarint.Len(1)
	}
	 = quicvarint.Append(, uint64())
	if .MaxFieldSectionSize >= 0 {
		 = quicvarint.Append(, settingMaxFieldSectionSize)
		 = quicvarint.Append(, uint64(.MaxFieldSectionSize))
	}
	if .Datagram {
		 = quicvarint.Append(, settingDatagram)
		 = quicvarint.Append(, 1)
	}
	if .ExtendedConnect {
		 = quicvarint.Append(, settingExtendedConnect)
		 = quicvarint.Append(, 1)
	}
	for ,  := range .Other {
		 = quicvarint.Append(, )
		 = quicvarint.Append(, )
	}
	return 
}

type goAwayFrame struct {
	StreamID quic.StreamID
}

func parseGoAwayFrame( *countingByteReader,  uint64,  quic.StreamID,  qlogwriter.Recorder) (*goAwayFrame, error) {
	 := &goAwayFrame{}
	 := .NumRead
	,  := quicvarint.Read()
	if  != nil {
		return nil, 
	}
	if .NumRead- != int() {
		return nil, errors.New("GOAWAY frame: inconsistent length")
	}
	.StreamID = quic.StreamID()
	if  != nil {
		.RecordEvent(qlog.FrameParsed{
			StreamID: ,
			Raw:      qlog.RawInfo{Length: .NumRead, PayloadLength: int()},
			Frame:    qlog.Frame{Frame: qlog.GoAwayFrame{StreamID: .StreamID}},
		})
	}
	return , nil
}

func ( *goAwayFrame) ( []byte) []byte {
	 = quicvarint.Append(, 0x7)
	 = quicvarint.Append(, uint64(quicvarint.Len(uint64(.StreamID))))
	return quicvarint.Append(, uint64(.StreamID))
}