From b69abbaf11bbe65c53c5e4ba549305262e210d75 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Mon, 21 Jul 2025 09:56:04 -0400 Subject: [PATCH] wire: implement parsing and writing of the IMMEDIATE_ACK frame (#5265) --- internal/ackhandler/ack_eliciting_test.go | 2 ++ internal/wire/frame_parser.go | 4 ++- internal/wire/frame_parser_test.go | 32 ++++++++++++++++++++++- internal/wire/frame_type.go | 7 +++-- internal/wire/immediate_ack_frame.go | 18 +++++++++++++ internal/wire/immediate_ack_frame_test.go | 22 ++++++++++++++++ 6 files changed, 81 insertions(+), 4 deletions(-) create mode 100644 internal/wire/immediate_ack_frame.go create mode 100644 internal/wire/immediate_ack_frame_test.go diff --git a/internal/ackhandler/ack_eliciting_test.go b/internal/ackhandler/ack_eliciting_test.go index cf4c6315a..7bb839c3a 100644 --- a/internal/ackhandler/ack_eliciting_test.go +++ b/internal/ackhandler/ack_eliciting_test.go @@ -43,6 +43,7 @@ func TestIsFrameTypeAckEliciting(t *testing.T) { wire.FrameTypeDatagramNoLength: true, wire.FrameTypeDatagramWithLength: true, wire.FrameTypeAckFrequency: true, + wire.FrameTypeImmediateAck: true, } for ft, expected := range testCases { @@ -63,6 +64,7 @@ func TestAckElicitingFrames(t *testing.T) { &wire.MaxStreamDataFrame{}: true, &wire.StopSendingFrame{}: true, &wire.AckFrequencyFrame{}: true, + &wire.ImmediateAckFrame{}: true, } for f, expected := range testCases { diff --git a/internal/wire/frame_parser.go b/internal/wire/frame_parser.go index 9ccdc86ce..afc3bedb9 100644 --- a/internal/wire/frame_parser.go +++ b/internal/wire/frame_parser.go @@ -55,7 +55,7 @@ func (p *FrameParser) ParseType(b []byte, encLevel protocol.EncryptionLevel) (Fr valid := ft.isValidRFC9000() || (p.supportsDatagrams && ft.IsDatagramFrameType()) || (p.supportsResetStreamAt && ft == FrameTypeResetStreamAt) || - (p.supportsAckFrequency && ft == FrameTypeAckFrequency) + (p.supportsAckFrequency && (ft == FrameTypeAckFrequency || ft == FrameTypeImmediateAck)) if !valid { return 0, parsed, &qerr.TransportError{ ErrorCode: qerr.FrameEncodingError, @@ -163,6 +163,8 @@ func (p *FrameParser) ParseLessCommonFrame(frameType FrameType, data []byte, v p frame, l, err = parseResetStreamFrame(data, true, v) case FrameTypeAckFrequency: frame, l, err = parseAckFrequencyFrame(data, v) + case FrameTypeImmediateAck: + frame = &ImmediateAckFrame{} default: err = errUnknownFrameType } diff --git a/internal/wire/frame_parser_test.go b/internal/wire/frame_parser_test.go index b060c0cae..b6f84e53f 100644 --- a/internal/wire/frame_parser_test.go +++ b/internal/wire/frame_parser_test.go @@ -330,6 +330,11 @@ func TestFrameParserFrames(t *testing.T) { ReorderingThreshold: 0xcafe, }, }, + { + name: "IMMEDIATE_ACK", + frameType: FrameTypeImmediateAck, + frame: &ImmediateAckFrame{}, + }, } for _, test := range tests { @@ -523,7 +528,32 @@ func TestFrameParserResetStreamAtUnsupported(t *testing.T) { require.NoError(t, err) _, _, err = parser.ParseType(b, protocol.Encryption1RTT) - checkFrameUnsupported(t, err, 0x24) + checkFrameUnsupported(t, err, uint64(FrameTypeResetStreamAt)) +} + +func TestFrameParserAckFrequencyUnsupported(t *testing.T) { + parser := NewFrameParser(true, true, false) + + t.Run("ACK_FREQUENCY", func(t *testing.T) { + f := &AckFrequencyFrame{ + SequenceNumber: 1337, + AckElicitingThreshold: 42, + RequestMaxAckDelay: 42 * time.Millisecond, + ReorderingThreshold: 1234, + } + b, err := f.Append(nil, protocol.Version1) + require.NoError(t, err) + _, _, err = parser.ParseType(b, protocol.Encryption1RTT) + checkFrameUnsupported(t, err, uint64(FrameTypeAckFrequency)) + }) + + t.Run("IMMEDIATE_ACK", func(t *testing.T) { + f := &ImmediateAckFrame{} + b, err := f.Append(nil, protocol.Version1) + require.NoError(t, err) + _, _, err = parser.ParseType(b, protocol.Encryption1RTT) + checkFrameUnsupported(t, err, uint64(FrameTypeImmediateAck)) + }) } func TestFrameParserInvalidFrameType(t *testing.T) { diff --git a/internal/wire/frame_type.go b/internal/wire/frame_type.go index 25a269d04..d3d086d91 100644 --- a/internal/wire/frame_type.go +++ b/internal/wire/frame_type.go @@ -30,8 +30,11 @@ const ( FrameTypeConnectionClose FrameType = 0x1c FrameTypeApplicationClose FrameType = 0x1d FrameTypeHandshakeDone FrameType = 0x1e - FrameTypeResetStreamAt FrameType = 0x24 // https://datatracker.ietf.org/doc/draft-ietf-quic-reliable-stream-reset/06/ - FrameTypeAckFrequency FrameType = 0xaf // https://datatracker.ietf.org/doc/draft-ietf-quic-ack-frequency/11/ + // https://datatracker.ietf.org/doc/draft-ietf-quic-reliable-stream-reset/07/ + FrameTypeResetStreamAt FrameType = 0x24 + // https://datatracker.ietf.org/doc/draft-ietf-quic-ack-frequency/11/ + FrameTypeAckFrequency FrameType = 0xaf + FrameTypeImmediateAck FrameType = 0x1f FrameTypeDatagramNoLength FrameType = 0x30 FrameTypeDatagramWithLength FrameType = 0x31 diff --git a/internal/wire/immediate_ack_frame.go b/internal/wire/immediate_ack_frame.go new file mode 100644 index 000000000..aeff71b4e --- /dev/null +++ b/internal/wire/immediate_ack_frame.go @@ -0,0 +1,18 @@ +package wire + +import ( + "github.com/quic-go/quic-go/internal/protocol" + "github.com/quic-go/quic-go/quicvarint" +) + +// An ImmediateAckFrame is an IMMEDIATE_ACK frame +type ImmediateAckFrame struct{} + +func (f *ImmediateAckFrame) Append(b []byte, _ protocol.Version) ([]byte, error) { + return quicvarint.Append(b, uint64(FrameTypeImmediateAck)), nil +} + +// Length of a written frame +func (f *ImmediateAckFrame) Length(_ protocol.Version) protocol.ByteCount { + return protocol.ByteCount(quicvarint.Len(uint64(FrameTypeImmediateAck))) +} diff --git a/internal/wire/immediate_ack_frame_test.go b/internal/wire/immediate_ack_frame_test.go new file mode 100644 index 000000000..af045a863 --- /dev/null +++ b/internal/wire/immediate_ack_frame_test.go @@ -0,0 +1,22 @@ +package wire + +import ( + "testing" + + "github.com/quic-go/quic-go/internal/protocol" + "github.com/quic-go/quic-go/quicvarint" + "github.com/stretchr/testify/require" +) + +func TestImmediateAckFrame(t *testing.T) { + frame := ImmediateAckFrame{} + b, err := frame.Append(nil, protocol.Version1) + require.NoError(t, err) + + val, l, err := quicvarint.Parse(b) + require.NoError(t, err) + require.Equal(t, uint64(FrameTypeImmediateAck), val) + require.Equal(t, len(b), l) + + require.Len(t, b, int(frame.Length(protocol.Version1))) +}