diff --git a/connection.go b/connection.go index 2251af08..e3387535 100644 --- a/connection.go +++ b/connection.go @@ -473,6 +473,7 @@ func (c *Conn) preSetup() { c.frameParser = *wire.NewFrameParser( c.config.EnableDatagrams, c.config.EnableStreamResetPartialDelivery, + false, // ACK_FREQUENCY is not supported yet ) c.rttStats = &utils.RTTStats{} c.connFlowController = flowcontrol.NewConnectionFlowController( diff --git a/fuzzing/frames/fuzz.go b/fuzzing/frames/fuzz.go index c8200265..ce7f2700 100644 --- a/fuzzing/frames/fuzz.go +++ b/fuzzing/frames/fuzz.go @@ -35,7 +35,7 @@ func Fuzz(data []byte) int { encLevel := toEncLevel(data[0]) data = data[PrefixLen:] - parser := wire.NewFrameParser(true, true) + parser := wire.NewFrameParser(true, true, true) parser.SetAckDelayExponent(protocol.DefaultAckDelayExponent) var numFrames int @@ -153,5 +153,9 @@ func validateFrame(frame wire.Frame) { if f.FinalSize < f.ReliableSize { panic("RESET_STREAM frame with a FinalSize smaller than the ReliableSize") } + case *wire.AckFrequencyFrame: + if f.RequestMaxAckDelay < 0 { + panic("ACK_FREQUENCY frame with a negative RequestMaxAckDelay") + } } } diff --git a/internal/ackhandler/ack_eliciting_test.go b/internal/ackhandler/ack_eliciting_test.go index 65cc627e..cf4c6315 100644 --- a/internal/ackhandler/ack_eliciting_test.go +++ b/internal/ackhandler/ack_eliciting_test.go @@ -42,6 +42,7 @@ func TestIsFrameTypeAckEliciting(t *testing.T) { wire.FrameTypeResetStreamAt: true, wire.FrameTypeDatagramNoLength: true, wire.FrameTypeDatagramWithLength: true, + wire.FrameTypeAckFrequency: true, } for ft, expected := range testCases { @@ -61,6 +62,7 @@ func TestAckElicitingFrames(t *testing.T) { &wire.MaxDataFrame{}: true, &wire.MaxStreamDataFrame{}: true, &wire.StopSendingFrame{}: true, + &wire.AckFrequencyFrame{}: true, } for f, expected := range testCases { diff --git a/internal/wire/ack_frequency_frame.go b/internal/wire/ack_frequency_frame.go new file mode 100644 index 00000000..0735d70b --- /dev/null +++ b/internal/wire/ack_frequency_frame.go @@ -0,0 +1,65 @@ +package wire + +import ( + "math" + "time" + + "github.com/quic-go/quic-go/internal/protocol" + "github.com/quic-go/quic-go/quicvarint" +) + +type AckFrequencyFrame struct { + SequenceNumber uint64 + AckElicitingThreshold uint64 + RequestMaxAckDelay time.Duration + ReorderingThreshold protocol.PacketNumber +} + +func parseAckFrequencyFrame(b []byte, _ protocol.Version) (*AckFrequencyFrame, int, error) { + startLen := len(b) + seq, l, err := quicvarint.Parse(b) + if err != nil { + return nil, 0, replaceUnexpectedEOF(err) + } + b = b[l:] + aeth, l, err := quicvarint.Parse(b) + if err != nil { + return nil, 0, replaceUnexpectedEOF(err) + } + b = b[l:] + mad, l, err := quicvarint.Parse(b) + if err != nil { + return nil, 0, replaceUnexpectedEOF(err) + } + // prevents overflows if the peer sends a very large value + maxAckDelay := time.Duration(mad) * time.Microsecond + if maxAckDelay < 0 { + maxAckDelay = math.MaxInt64 + } + b = b[l:] + rth, l, err := quicvarint.Parse(b) + if err != nil { + return nil, 0, replaceUnexpectedEOF(err) + } + b = b[l:] + + return &AckFrequencyFrame{ + SequenceNumber: seq, + AckElicitingThreshold: aeth, + RequestMaxAckDelay: maxAckDelay, + ReorderingThreshold: protocol.PacketNumber(rth), + }, startLen - len(b), nil +} + +func (f *AckFrequencyFrame) Append(b []byte, _ protocol.Version) ([]byte, error) { + b = quicvarint.Append(b, uint64(FrameTypeAckFrequency)) + b = quicvarint.Append(b, f.SequenceNumber) + b = quicvarint.Append(b, f.AckElicitingThreshold) + b = quicvarint.Append(b, uint64(f.RequestMaxAckDelay/time.Microsecond)) + return quicvarint.Append(b, uint64(f.ReorderingThreshold)), nil +} + +func (f *AckFrequencyFrame) Length(_ protocol.Version) protocol.ByteCount { + return protocol.ByteCount(2 + quicvarint.Len(f.SequenceNumber) + quicvarint.Len(f.AckElicitingThreshold) + + quicvarint.Len(uint64(f.RequestMaxAckDelay/time.Microsecond)) + quicvarint.Len(uint64(f.ReorderingThreshold))) +} diff --git a/internal/wire/ack_frequency_frame_test.go b/internal/wire/ack_frequency_frame_test.go new file mode 100644 index 00000000..053f235e --- /dev/null +++ b/internal/wire/ack_frequency_frame_test.go @@ -0,0 +1,71 @@ +package wire + +import ( + "io" + "math" + "testing" + "time" + + "github.com/quic-go/quic-go/internal/protocol" + "github.com/quic-go/quic-go/quicvarint" + + "github.com/stretchr/testify/require" +) + +func TestParseAckFrequency(t *testing.T) { + data := encodeVarInt(0xdeadbeef) // sequence number + data = append(data, encodeVarInt(0xcafe)...) // threshold + data = append(data, encodeVarInt(1337)...) // update max ack delay + data = append(data, encodeVarInt(12345)...) // reordering threshold + frame, l, err := parseAckFrequencyFrame(data, protocol.Version1) + require.NoError(t, err) + require.Equal(t, uint64(0xdeadbeef), frame.SequenceNumber) + require.Equal(t, uint64(0xcafe), frame.AckElicitingThreshold) + require.Equal(t, 1337*time.Microsecond, frame.RequestMaxAckDelay) + require.Equal(t, protocol.PacketNumber(12345), frame.ReorderingThreshold) + require.Equal(t, len(data), l) +} + +func TestParseAckFrequencyMaxAckDelayOverflow(t *testing.T) { + data := encodeVarInt(0xdeadbeef) // sequence number + data = append(data, encodeVarInt(0xcafe)...) // threshold + data = append(data, encodeVarInt(quicvarint.Max)...) // update max ack delay + data = append(data, encodeVarInt(12345)...) // reordering threshold + frame, l, err := parseAckFrequencyFrame(data, protocol.Version1) + require.NoError(t, err) + require.Greater(t, frame.RequestMaxAckDelay, time.Duration(0)) + require.Equal(t, frame.RequestMaxAckDelay, time.Duration(math.MaxInt64)) + require.Equal(t, len(data), l) +} + +func TestParseAckFrequencyErrorsOnEOFs(t *testing.T) { + data := append([]byte{}, encodeVarInt(0xdeadbeef)...) // sequence number + data = append(data, encodeVarInt(0xcafe)...) // threshold + data = append(data, encodeVarInt(1337)...) // update max ack delay + data = append(data, encodeVarInt(12345)...) // reordering threshold + _, l, err := parseAckFrequencyFrame(data, protocol.Version1) + require.NoError(t, err) + require.Equal(t, len(data), l) + for i := range data { + _, _, err := parseAckFrequencyFrame(data[:i], protocol.Version1) + require.Equal(t, io.EOF, err) + } +} + +func TestWriteAckFrequencyFrame(t *testing.T) { + frame := &AckFrequencyFrame{ + SequenceNumber: 0xdecafbad, + AckElicitingThreshold: 0xdeadbeef, + RequestMaxAckDelay: 12345 * time.Microsecond, + ReorderingThreshold: 1337, + } + b, err := frame.Append(nil, protocol.Version1) + require.NoError(t, err) + expected := encodeVarInt(uint64(FrameTypeAckFrequency)) + expected = append(expected, encodeVarInt(0xdecafbad)...) + expected = append(expected, encodeVarInt(0xdeadbeef)...) + expected = append(expected, encodeVarInt(12345)...) + expected = append(expected, encodeVarInt(1337)...) + require.Equal(t, expected, b) + require.Len(t, b, int(frame.Length(protocol.Version1))) +} diff --git a/internal/wire/frame_parser.go b/internal/wire/frame_parser.go index e92e29c8..9ccdc86c 100644 --- a/internal/wire/frame_parser.go +++ b/internal/wire/frame_parser.go @@ -17,6 +17,7 @@ type FrameParser struct { ackDelayExponent uint8 supportsDatagrams bool supportsResetStreamAt bool + supportsAckFrequency bool // To avoid allocating when parsing, keep a single ACK frame struct. // It is used over and over again. @@ -24,10 +25,11 @@ type FrameParser struct { } // NewFrameParser creates a new frame parser. -func NewFrameParser(supportsDatagrams, supportsResetStreamAt bool) *FrameParser { +func NewFrameParser(supportsDatagrams, supportsResetStreamAt, supportsAckFrequency bool) *FrameParser { return &FrameParser{ supportsDatagrams: supportsDatagrams, supportsResetStreamAt: supportsResetStreamAt, + supportsAckFrequency: supportsAckFrequency, ackFrame: &AckFrame{}, } } @@ -52,7 +54,8 @@ func (p *FrameParser) ParseType(b []byte, encLevel protocol.EncryptionLevel) (Fr ft := FrameType(typ) valid := ft.isValidRFC9000() || (p.supportsDatagrams && ft.IsDatagramFrameType()) || - (p.supportsResetStreamAt && ft == FrameTypeResetStreamAt) + (p.supportsResetStreamAt && ft == FrameTypeResetStreamAt) || + (p.supportsAckFrequency && ft == FrameTypeAckFrequency) if !valid { return 0, parsed, &qerr.TransportError{ ErrorCode: qerr.FrameEncodingError, @@ -158,6 +161,8 @@ func (p *FrameParser) ParseLessCommonFrame(frameType FrameType, data []byte, v p frame = &HandshakeDoneFrame{} case FrameTypeResetStreamAt: frame, l, err = parseResetStreamFrame(data, true, v) + case FrameTypeAckFrequency: + frame, l, err = parseAckFrequencyFrame(data, v) default: err = errUnknownFrameType } diff --git a/internal/wire/frame_parser_test.go b/internal/wire/frame_parser_test.go index 821bc5d6..b060c0ca 100644 --- a/internal/wire/frame_parser_test.go +++ b/internal/wire/frame_parser_test.go @@ -11,11 +11,12 @@ import ( "github.com/quic-go/quic-go/internal/protocol" "github.com/quic-go/quic-go/internal/qerr" + "github.com/quic-go/quic-go/quicvarint" "github.com/stretchr/testify/require" ) func TestFrameTypeParsingReturnsNilWhenNothingToRead(t *testing.T) { - parser := NewFrameParser(true, true) + parser := NewFrameParser(true, true, true) frameType, l, err := parser.ParseType(nil, protocol.Encryption1RTT) require.Equal(t, io.EOF, err) require.Zero(t, frameType) @@ -23,7 +24,7 @@ func TestFrameTypeParsingReturnsNilWhenNothingToRead(t *testing.T) { } func TestParseLessCommonFrameReturnsEOFWhenNothingToRead(t *testing.T) { - parser := NewFrameParser(true, true) + parser := NewFrameParser(true, true, true) l, f, err := parser.ParseLessCommonFrame(FrameTypeMaxStreamData, nil, protocol.Version1) require.IsType(t, &qerr.TransportError{}, err) require.Zero(t, l) @@ -31,7 +32,7 @@ func TestParseLessCommonFrameReturnsEOFWhenNothingToRead(t *testing.T) { } func TestFrameParsingSkipsPaddingFrames(t *testing.T) { - parser := NewFrameParser(true, true) + parser := NewFrameParser(true, true, true) b := []byte{0, 0} // 2 PADDING frames b, err := (&PingFrame{}).Append(b, protocol.Version1) require.NoError(t, err) @@ -48,7 +49,7 @@ func TestFrameParsingSkipsPaddingFrames(t *testing.T) { } func TestFrameParsingHandlesPaddingAtEnd(t *testing.T) { - parser := NewFrameParser(true, true) + parser := NewFrameParser(true, true, true) b := []byte{0, 0, 0} _, l, err := parser.ParseType(b, protocol.Encryption1RTT) @@ -57,7 +58,7 @@ func TestFrameParsingHandlesPaddingAtEnd(t *testing.T) { } func TestFrameParsingParsesSingleFrame(t *testing.T) { - parser := NewFrameParser(true, true) + parser := NewFrameParser(true, true, true) var b []byte for range 10 { var err error @@ -76,7 +77,7 @@ func TestFrameParsingParsesSingleFrame(t *testing.T) { } func TestFrameParserACK(t *testing.T) { - parser := NewFrameParser(true, true) + parser := NewFrameParser(true, true, true) f := &AckFrame{AckRanges: []AckRange{{Smallest: 1, Largest: 0x13}}} b, err := f.Append(nil, protocol.Version1) require.NoError(t, err) @@ -102,7 +103,7 @@ func TestFrameParserAckDelay(t *testing.T) { } func testFrameParserAckDelay(t *testing.T, encLevel protocol.EncryptionLevel) { - parser := NewFrameParser(true, true) + parser := NewFrameParser(true, true, true) parser.SetAckDelayExponent(protocol.AckDelayExponent + 2) f := &AckFrame{ AckRanges: []AckRange{{Smallest: 1, Largest: 1}}, @@ -136,7 +137,7 @@ func checkFrameUnsupported(t *testing.T, err error, expectedFrameType uint64) { } func TestFrameParserStreamFrames(t *testing.T) { - parser := NewFrameParser(true, true) + parser := NewFrameParser(true, true, true) f := &StreamFrame{ StreamID: 0x42, Offset: 0x1337, @@ -159,7 +160,7 @@ func TestFrameParserStreamFrames(t *testing.T) { } func TestParseStreamFrameWrapsError(t *testing.T) { - parser := NewFrameParser(true, true) + parser := NewFrameParser(true, true, true) f := &StreamFrame{ StreamID: 0x1234, Offset: 0x1000, @@ -187,7 +188,7 @@ func TestParseStreamFrameWrapsError(t *testing.T) { } func TestParseStreamFrameSuccess(t *testing.T) { - parser := NewFrameParser(true, true) + parser := NewFrameParser(true, true, true) original := &StreamFrame{ StreamID: 0x1234, Offset: 0x1000, @@ -319,23 +320,33 @@ func TestFrameParserFrames(t *testing.T) { frameType: FrameTypeResetStreamAt, frame: &ResetStreamFrame{StreamID: 0x1337, ReliableSize: 0x42, FinalSize: 0xdeadbeef}, }, + { + name: "ACK_FREQUENCY", + frameType: FrameTypeAckFrequency, + frame: &AckFrequencyFrame{ + SequenceNumber: 0x1337, + AckElicitingThreshold: 0x42, + RequestMaxAckDelay: 123 * time.Second, + ReorderingThreshold: 0xcafe, + }, + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - parser := NewFrameParser(true, true) + parser := NewFrameParser(true, true, true) b, err := test.frame.Append(nil, protocol.Version1) require.NoError(t, err) frameType, l, err := parser.ParseType(b, protocol.Encryption1RTT) require.NoError(t, err) require.Equal(t, test.frameType, frameType) - require.Equal(t, 1, l) + require.Equal(t, quicvarint.Len(uint64(test.frameType)), l) frame, l, err := parser.ParseLessCommonFrame(frameType, b[l:], protocol.Version1) require.NoError(t, err) require.Equal(t, test.frame, frame) - require.Equal(t, len(b)-1, l) + require.Equal(t, len(b)-quicvarint.Len(uint64(test.frameType)), l) }) } } @@ -453,7 +464,7 @@ func TestFrameAllowedAtEncLevel(t *testing.T) { allowed = tc.allowedOneRTT } - parser := NewFrameParser(true, true) + parser := NewFrameParser(true, true, true) b, err := tc.frame.Append(nil, protocol.Version1) require.NoError(t, err) frameType, _, err := parser.ParseType(b, encLevel) @@ -472,7 +483,7 @@ func TestFrameAllowedAtEncLevel(t *testing.T) { } func TestFrameParserDatagramFrame(t *testing.T) { - parser := NewFrameParser(true, true) + parser := NewFrameParser(true, true, true) f := &DatagramFrame{ Data: []byte("foobar"), } @@ -496,7 +507,7 @@ func TestFrameParserDatagramFrame(t *testing.T) { } func TestFrameParserDatagramUnsupported(t *testing.T) { - parser := NewFrameParser(false, true) + parser := NewFrameParser(false, true, true) f := &DatagramFrame{Data: []byte("foobar")} b, err := f.Append(nil, protocol.Version1) require.NoError(t, err) @@ -506,7 +517,7 @@ func TestFrameParserDatagramUnsupported(t *testing.T) { } func TestFrameParserResetStreamAtUnsupported(t *testing.T) { - parser := NewFrameParser(true, false) + parser := NewFrameParser(true, false, true) f := &ResetStreamFrame{StreamID: 0x1337, ReliableSize: 0x42, FinalSize: 0xdeadbeef} b, err := f.Append(nil, protocol.Version1) require.NoError(t, err) @@ -516,7 +527,7 @@ func TestFrameParserResetStreamAtUnsupported(t *testing.T) { } func TestFrameParserInvalidFrameType(t *testing.T) { - parser := NewFrameParser(true, true) + parser := NewFrameParser(true, true, true) _, l, err := parser.ParseType(encodeVarInt(0x42), protocol.Encryption1RTT) @@ -529,7 +540,7 @@ func TestFrameParserInvalidFrameType(t *testing.T) { } func TestFrameParsingErrorsOnInvalidFrames(t *testing.T) { - parser := NewFrameParser(true, true) + parser := NewFrameParser(true, true, true) f := &MaxStreamDataFrame{ StreamID: 0x1337, MaximumStreamData: 0xdeadbeef, @@ -711,7 +722,7 @@ func TestFrameParserAllocs(t *testing.T) { func testFrameParserAllocs(t *testing.T, frames []Frame) float64 { buf := writeFrames(t, frames...) - parser := NewFrameParser(true, true) + parser := NewFrameParser(true, true, true) parser.SetAckDelayExponent(3) return testing.AllocsPerRun(100, func() { @@ -779,7 +790,7 @@ func BenchmarkParseDatagramFrame(b *testing.B) { func benchmarkFrames(b *testing.B, frames ...Frame) { buf := writeFrames(b, frames...) - parser := NewFrameParser(true, true) + parser := NewFrameParser(true, true, true) parser.SetAckDelayExponent(3) b.ResetTimer() diff --git a/internal/wire/frame_type.go b/internal/wire/frame_type.go index 0576657f..25a269d0 100644 --- a/internal/wire/frame_type.go +++ b/internal/wire/frame_type.go @@ -31,6 +31,7 @@ const ( 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/ FrameTypeDatagramNoLength FrameType = 0x30 FrameTypeDatagramWithLength FrameType = 0x31 diff --git a/packet_packer_test.go b/packet_packer_test.go index 748c061e..423e506e 100644 --- a/packet_packer_test.go +++ b/packet_packer_test.go @@ -735,7 +735,7 @@ func TestPackLongHeaderPadToAtLeast4Bytes(t *testing.T) { // first bytes should be 2 PADDING frames... require.Equal(t, []byte{0, 0}, data[:2]) // ...followed by the PING frame - frameParser := wire.NewFrameParser(false, false) + frameParser := wire.NewFrameParser(false, false, false) frameType, lt, err := frameParser.ParseType(data[2:], protocol.EncryptionHandshake) require.NoError(t, err) @@ -778,7 +778,7 @@ func TestPackShortHeaderPadToAtLeast4Bytes(t *testing.T) { require.Equal(t, byte(0), payload[0]) // ... followed by the STREAM frame - frameParser := wire.NewFrameParser(false, false) + frameParser := wire.NewFrameParser(false, false, false) frameType, l, err := frameParser.ParseType(payload[1:], protocol.Encryption1RTT) require.NoError(t, err) require.Equal(t, 1, l)