forked from quic-go/quic-go
831 lines
24 KiB
Go
831 lines
24 KiB
Go
package wire
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/rand"
|
|
"fmt"
|
|
"io"
|
|
"slices"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.geeks-team.ru/gr1ffon/quic-go/internal/protocol"
|
|
"git.geeks-team.ru/gr1ffon/quic-go/internal/qerr"
|
|
"git.geeks-team.ru/gr1ffon/quic-go/quicvarint"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestFrameTypeParsingReturnsNilWhenNothingToRead(t *testing.T) {
|
|
parser := NewFrameParser(true, true, true)
|
|
frameType, l, err := parser.ParseType(nil, protocol.Encryption1RTT)
|
|
require.Equal(t, io.EOF, err)
|
|
require.Zero(t, frameType)
|
|
require.Zero(t, l)
|
|
}
|
|
|
|
func TestParseLessCommonFrameReturnsEOFWhenNothingToRead(t *testing.T) {
|
|
parser := NewFrameParser(true, true, true)
|
|
l, f, err := parser.ParseLessCommonFrame(FrameTypeMaxStreamData, nil, protocol.Version1)
|
|
require.IsType(t, &qerr.TransportError{}, err)
|
|
require.Zero(t, l)
|
|
require.Zero(t, f)
|
|
}
|
|
|
|
func TestFrameParsingSkipsPaddingFrames(t *testing.T) {
|
|
parser := NewFrameParser(true, true, true)
|
|
b := []byte{0, 0} // 2 PADDING frames
|
|
b, err := (&PingFrame{}).Append(b, protocol.Version1)
|
|
require.NoError(t, err)
|
|
|
|
frameType, l, err := parser.ParseType(b, protocol.Encryption1RTT)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 3, l)
|
|
require.Equal(t, FrameTypePing, frameType)
|
|
|
|
frame, l, err := parser.ParseLessCommonFrame(frameType, b[1:], protocol.Version1)
|
|
require.NoError(t, err)
|
|
require.Zero(t, l)
|
|
require.IsType(t, &PingFrame{}, frame)
|
|
}
|
|
|
|
func TestFrameParsingHandlesPaddingAtEnd(t *testing.T) {
|
|
parser := NewFrameParser(true, true, true)
|
|
b := []byte{0, 0, 0}
|
|
|
|
_, l, err := parser.ParseType(b, protocol.Encryption1RTT)
|
|
require.Equal(t, io.EOF, err)
|
|
require.Equal(t, 3, l)
|
|
}
|
|
|
|
func TestFrameParsingParsesSingleFrame(t *testing.T) {
|
|
parser := NewFrameParser(true, true, true)
|
|
var b []byte
|
|
for range 10 {
|
|
var err error
|
|
b, err = (&PingFrame{}).Append(b, protocol.Version1)
|
|
require.NoError(t, err)
|
|
}
|
|
frameType, l, err := parser.ParseType(b, protocol.Encryption1RTT)
|
|
require.NoError(t, err)
|
|
require.Equal(t, FrameTypePing, frameType)
|
|
require.Equal(t, 1, l)
|
|
|
|
frame, l, err := parser.ParseLessCommonFrame(frameType, b, protocol.Version1)
|
|
require.NoError(t, err)
|
|
require.Zero(t, l)
|
|
require.IsType(t, &PingFrame{}, frame)
|
|
}
|
|
|
|
func TestFrameParserACK(t *testing.T) {
|
|
parser := NewFrameParser(true, true, true)
|
|
f := &AckFrame{AckRanges: []AckRange{{Smallest: 1, Largest: 0x13}}}
|
|
b, err := f.Append(nil, protocol.Version1)
|
|
require.NoError(t, err)
|
|
frameType, l, err := parser.ParseType(b, protocol.Encryption1RTT)
|
|
require.NoError(t, err)
|
|
require.Equal(t, FrameTypeAck, frameType)
|
|
require.Equal(t, 1, l)
|
|
|
|
frame, l, err := parser.ParseAckFrame(frameType, b[l:], protocol.Encryption1RTT, protocol.Version1)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, frame)
|
|
require.Equal(t, protocol.PacketNumber(0x13), frame.LargestAcked())
|
|
require.Equal(t, len(b)-1, l)
|
|
}
|
|
|
|
func TestFrameParserAckDelay(t *testing.T) {
|
|
t.Run("1-RTT", func(t *testing.T) {
|
|
testFrameParserAckDelay(t, protocol.Encryption1RTT)
|
|
})
|
|
t.Run("Handshake", func(t *testing.T) {
|
|
testFrameParserAckDelay(t, protocol.EncryptionHandshake)
|
|
})
|
|
}
|
|
|
|
func testFrameParserAckDelay(t *testing.T, encLevel protocol.EncryptionLevel) {
|
|
parser := NewFrameParser(true, true, true)
|
|
parser.SetAckDelayExponent(protocol.AckDelayExponent + 2)
|
|
f := &AckFrame{
|
|
AckRanges: []AckRange{{Smallest: 1, Largest: 1}},
|
|
DelayTime: time.Second,
|
|
}
|
|
b, err := f.Append(nil, protocol.Version1)
|
|
require.NoError(t, err)
|
|
frameType, l, err := parser.ParseType(b, encLevel)
|
|
require.NoError(t, err)
|
|
require.Equal(t, FrameTypeAck, frameType)
|
|
require.Equal(t, 1, l)
|
|
|
|
frame, l, err := parser.ParseAckFrame(frameType, b[l:], encLevel, protocol.Version1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, len(b)-1, l)
|
|
if encLevel == protocol.Encryption1RTT {
|
|
require.Equal(t, 4*time.Second, frame.DelayTime)
|
|
} else {
|
|
require.Equal(t, time.Second, frame.DelayTime)
|
|
}
|
|
}
|
|
|
|
func checkFrameUnsupported(t *testing.T, err error, expectedFrameType uint64) {
|
|
t.Helper()
|
|
require.ErrorContains(t, err, errUnknownFrameType.Error())
|
|
var transportErr *qerr.TransportError
|
|
require.ErrorAs(t, err, &transportErr)
|
|
require.Equal(t, qerr.FrameEncodingError, transportErr.ErrorCode)
|
|
require.Equal(t, expectedFrameType, transportErr.FrameType)
|
|
require.Equal(t, "unknown frame type", transportErr.ErrorMessage)
|
|
}
|
|
|
|
func TestFrameParserStreamFrames(t *testing.T) {
|
|
parser := NewFrameParser(true, true, true)
|
|
f := &StreamFrame{
|
|
StreamID: 0x42,
|
|
Offset: 0x1337,
|
|
Fin: true,
|
|
Data: []byte("foobar"),
|
|
}
|
|
b, err := f.Append(nil, protocol.Version1)
|
|
require.NoError(t, err)
|
|
frameType, l, err := parser.ParseType(b, protocol.Encryption1RTT)
|
|
require.NoError(t, err)
|
|
require.Equal(t, FrameType(0xd), frameType)
|
|
require.True(t, frameType.IsStreamFrameType())
|
|
require.Equal(t, 1, l)
|
|
|
|
// ParseLessCommonFrame should not handle Stream Frames
|
|
frame, l, err := parser.ParseLessCommonFrame(frameType, b[l:], protocol.Version1)
|
|
checkFrameUnsupported(t, err, 0xd)
|
|
require.Nil(t, frame)
|
|
require.Zero(t, l)
|
|
}
|
|
|
|
func TestParseStreamFrameWrapsError(t *testing.T) {
|
|
parser := NewFrameParser(true, true, true)
|
|
f := &StreamFrame{
|
|
StreamID: 0x1234,
|
|
Offset: 0x1000,
|
|
Data: []byte("hello world"),
|
|
DataLenPresent: true,
|
|
}
|
|
b, err := f.Append(nil, protocol.Version1)
|
|
require.NoError(t, err)
|
|
|
|
// Corrupt the buffer to trigger a parse error
|
|
b = b[:len(b)-2] // Remove last 2 bytes to cause an EOF
|
|
|
|
frameType, l, err := parser.ParseType(b, protocol.Encryption1RTT)
|
|
require.NoError(t, err)
|
|
|
|
frame, n, err := parser.ParseStreamFrame(frameType, b[l:], protocol.Version1)
|
|
require.Nil(t, frame)
|
|
require.Zero(t, n)
|
|
|
|
var transportErr *qerr.TransportError
|
|
require.ErrorAs(t, err, &transportErr)
|
|
require.Equal(t, qerr.FrameEncodingError, transportErr.ErrorCode)
|
|
require.Equal(t, uint64(frameType), transportErr.FrameType)
|
|
require.Contains(t, transportErr.Error(), "EOF")
|
|
}
|
|
|
|
func TestParseStreamFrameSuccess(t *testing.T) {
|
|
parser := NewFrameParser(true, true, true)
|
|
original := &StreamFrame{
|
|
StreamID: 0x1234,
|
|
Offset: 0x1000,
|
|
Fin: true,
|
|
Data: []byte("hello world"),
|
|
DataLenPresent: true,
|
|
}
|
|
b, err := original.Append(nil, protocol.Version1)
|
|
require.NoError(t, err)
|
|
|
|
frameType, l, err := parser.ParseType(b, protocol.Encryption1RTT)
|
|
require.NoError(t, err)
|
|
require.True(t, frameType.IsStreamFrameType())
|
|
require.Equal(t, FrameType(0x0f), frameType) // STREAM | OFF | LEN | FIN
|
|
|
|
parsed, n, err := parser.ParseStreamFrame(frameType, b[l:], protocol.Version1)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, parsed)
|
|
require.Equal(t, len(b)-l, n)
|
|
|
|
require.Equal(t, original.StreamID, parsed.StreamID)
|
|
require.Equal(t, original.Offset, parsed.Offset)
|
|
require.Equal(t, original.Fin, parsed.Fin)
|
|
require.Equal(t, original.DataLenPresent, parsed.DataLenPresent)
|
|
require.Equal(t, original.Data, parsed.Data)
|
|
}
|
|
|
|
func TestFrameParserFrames(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
frameType FrameType
|
|
frame Frame
|
|
}{
|
|
{
|
|
name: "MAX_DATA",
|
|
frameType: FrameTypeMaxData,
|
|
frame: &MaxDataFrame{MaximumData: 0xcafe},
|
|
},
|
|
{
|
|
name: "MAX_STREAM_DATA",
|
|
frameType: FrameTypeMaxStreamData,
|
|
frame: &MaxStreamDataFrame{StreamID: 0xdeadbeef, MaximumStreamData: 0xdecafbad},
|
|
},
|
|
{
|
|
name: "RESET_STREAM",
|
|
frameType: FrameTypeResetStream,
|
|
frame: &ResetStreamFrame{
|
|
StreamID: 0xdeadbeef,
|
|
FinalSize: 0xdecafbad1234,
|
|
ErrorCode: 0x1337,
|
|
},
|
|
},
|
|
{
|
|
name: "STOP_SENDING",
|
|
frameType: FrameTypeStopSending,
|
|
frame: &StopSendingFrame{StreamID: 0x42},
|
|
},
|
|
{
|
|
name: "CRYPTO",
|
|
frameType: FrameTypeCrypto,
|
|
frame: &CryptoFrame{Offset: 0x1337, Data: []byte("lorem ipsum")},
|
|
},
|
|
{
|
|
name: "NEW_TOKEN",
|
|
frameType: FrameTypeNewToken,
|
|
frame: &NewTokenFrame{Token: []byte("foobar")},
|
|
},
|
|
{
|
|
name: "MAX_STREAMS",
|
|
frameType: FrameTypeBidiMaxStreams,
|
|
frame: &MaxStreamsFrame{Type: protocol.StreamTypeBidi, MaxStreamNum: 0x1337},
|
|
},
|
|
{
|
|
name: "DATA_BLOCKED",
|
|
frameType: FrameTypeDataBlocked,
|
|
frame: &DataBlockedFrame{MaximumData: 0x1234},
|
|
},
|
|
{
|
|
name: "STREAM_DATA_BLOCKED",
|
|
frameType: FrameTypeStreamDataBlocked,
|
|
frame: &StreamDataBlockedFrame{StreamID: 0xdeadbeef, MaximumStreamData: 0xdead},
|
|
},
|
|
{
|
|
name: "STREAMS_BLOCKED",
|
|
frameType: FrameTypeBidiStreamBlocked,
|
|
frame: &StreamsBlockedFrame{Type: protocol.StreamTypeBidi, StreamLimit: 0x1234567},
|
|
},
|
|
{
|
|
name: "NEW_CONNECTION_ID",
|
|
frameType: FrameTypeNewConnectionID,
|
|
frame: &NewConnectionIDFrame{
|
|
SequenceNumber: 0x1337,
|
|
ConnectionID: protocol.ParseConnectionID([]byte{0xde, 0xad, 0xbe, 0xef}),
|
|
StatelessResetToken: protocol.StatelessResetToken{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
|
|
},
|
|
},
|
|
{
|
|
name: "RETIRE_CONNECTION_ID",
|
|
frameType: FrameTypeRetireConnectionID,
|
|
frame: &RetireConnectionIDFrame{SequenceNumber: 0x1337},
|
|
},
|
|
{
|
|
name: "PATH_CHALLENGE",
|
|
frameType: FrameTypePathChallenge,
|
|
frame: &PathChallengeFrame{Data: [8]byte{1, 2, 3, 4, 5, 6, 7, 8}},
|
|
},
|
|
{
|
|
name: "PATH_RESPONSE",
|
|
frameType: FrameTypePathResponse,
|
|
frame: &PathResponseFrame{Data: [8]byte{1, 2, 3, 4, 5, 6, 7, 8}},
|
|
},
|
|
{
|
|
name: "CONNECTION_CLOSE",
|
|
frameType: FrameTypeConnectionClose,
|
|
frame: &ConnectionCloseFrame{IsApplicationError: false, ReasonPhrase: "foobar"},
|
|
},
|
|
{
|
|
name: "APPLICATION_CLOSE",
|
|
frameType: FrameTypeApplicationClose,
|
|
frame: &ConnectionCloseFrame{IsApplicationError: true, ReasonPhrase: "foobar"},
|
|
},
|
|
{
|
|
name: "HANDSHAKE_DONE",
|
|
frameType: FrameTypeHandshakeDone,
|
|
frame: &HandshakeDoneFrame{},
|
|
},
|
|
{
|
|
name: "RESET_STREAM_AT",
|
|
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,
|
|
},
|
|
},
|
|
{
|
|
name: "IMMEDIATE_ACK",
|
|
frameType: FrameTypeImmediateAck,
|
|
frame: &ImmediateAckFrame{},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
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, 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)-quicvarint.Len(uint64(test.frameType)), l)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFrameAllowedAtEncLevel(t *testing.T) {
|
|
type testCase struct {
|
|
name string
|
|
frameType FrameType
|
|
frame Frame
|
|
allowedInitial bool
|
|
allowedHandshake bool
|
|
allowedZeroRTT bool
|
|
allowedOneRTT bool
|
|
}
|
|
|
|
for _, tc := range []testCase{
|
|
{
|
|
name: "CRYPTO_FRAME",
|
|
frameType: FrameTypeCrypto,
|
|
frame: &CryptoFrame{Offset: 0, Data: []byte("foo")},
|
|
allowedInitial: true,
|
|
allowedHandshake: true,
|
|
allowedZeroRTT: false,
|
|
allowedOneRTT: true,
|
|
},
|
|
{
|
|
name: "ACK_FRAME",
|
|
frameType: FrameTypeAck,
|
|
frame: &AckFrame{AckRanges: []AckRange{{Smallest: 1, Largest: 1}}},
|
|
allowedInitial: true,
|
|
allowedHandshake: true,
|
|
allowedZeroRTT: false,
|
|
allowedOneRTT: true,
|
|
},
|
|
{
|
|
name: "CONNECTION_CLOSE_FRAME",
|
|
frameType: FrameTypeConnectionClose,
|
|
frame: &ConnectionCloseFrame{IsApplicationError: false, ReasonPhrase: "err"},
|
|
allowedInitial: true,
|
|
allowedHandshake: true,
|
|
allowedZeroRTT: false,
|
|
allowedOneRTT: true,
|
|
},
|
|
{
|
|
name: "PING_FRAME",
|
|
frameType: FrameTypePing,
|
|
frame: &PingFrame{},
|
|
allowedInitial: true,
|
|
allowedHandshake: true,
|
|
allowedZeroRTT: true,
|
|
allowedOneRTT: true,
|
|
},
|
|
{
|
|
name: "NEW_TOKEN_FRAME",
|
|
frameType: FrameTypeNewToken,
|
|
frame: &NewTokenFrame{Token: []byte("tok")},
|
|
allowedInitial: false,
|
|
allowedHandshake: false,
|
|
allowedZeroRTT: false,
|
|
allowedOneRTT: true,
|
|
},
|
|
{
|
|
name: "PATH_RESPONSE_FRAME",
|
|
frameType: FrameTypePathResponse,
|
|
frame: &PathResponseFrame{Data: [8]byte{1, 2, 3, 4, 5, 6, 7, 8}},
|
|
allowedInitial: false,
|
|
allowedHandshake: false,
|
|
allowedZeroRTT: false,
|
|
allowedOneRTT: true,
|
|
},
|
|
{
|
|
name: "RETIRE_CONNECTION_ID_FRAME",
|
|
frameType: FrameTypeRetireConnectionID,
|
|
frame: &RetireConnectionIDFrame{SequenceNumber: 1},
|
|
allowedInitial: false,
|
|
allowedHandshake: false,
|
|
allowedZeroRTT: false,
|
|
allowedOneRTT: true,
|
|
},
|
|
{
|
|
name: "MAX_DATA_FRAME",
|
|
frameType: FrameTypeMaxData,
|
|
frame: &MaxDataFrame{MaximumData: 1},
|
|
allowedInitial: false,
|
|
allowedHandshake: false,
|
|
allowedZeroRTT: true,
|
|
allowedOneRTT: true,
|
|
},
|
|
{
|
|
name: "STREAM_FRAME",
|
|
frameType: FrameType(0x8),
|
|
frame: &StreamFrame{StreamID: 1, Data: []byte("foobar")},
|
|
allowedInitial: false,
|
|
allowedHandshake: false,
|
|
allowedZeroRTT: true,
|
|
allowedOneRTT: true,
|
|
},
|
|
} {
|
|
for _, encLevel := range []protocol.EncryptionLevel{
|
|
protocol.EncryptionInitial,
|
|
protocol.EncryptionHandshake,
|
|
protocol.Encryption0RTT,
|
|
protocol.Encryption1RTT,
|
|
} {
|
|
t.Run(fmt.Sprintf("%s/%v", tc.name, encLevel), func(t *testing.T) {
|
|
var allowed bool
|
|
switch encLevel {
|
|
case protocol.EncryptionInitial:
|
|
allowed = tc.allowedInitial
|
|
case protocol.EncryptionHandshake:
|
|
allowed = tc.allowedHandshake
|
|
case protocol.Encryption0RTT:
|
|
allowed = tc.allowedZeroRTT
|
|
case protocol.Encryption1RTT:
|
|
allowed = tc.allowedOneRTT
|
|
}
|
|
|
|
parser := NewFrameParser(true, true, true)
|
|
b, err := tc.frame.Append(nil, protocol.Version1)
|
|
require.NoError(t, err)
|
|
frameType, _, err := parser.ParseType(b, encLevel)
|
|
if allowed {
|
|
require.NoError(t, err)
|
|
require.Equal(t, tc.frameType, frameType)
|
|
} else {
|
|
require.Error(t, err)
|
|
var transportErr *qerr.TransportError
|
|
require.ErrorAs(t, err, &transportErr)
|
|
require.Equal(t, qerr.FrameEncodingError, transportErr.ErrorCode)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFrameParserDatagramFrame(t *testing.T) {
|
|
parser := NewFrameParser(true, true, true)
|
|
f := &DatagramFrame{
|
|
Data: []byte("foobar"),
|
|
}
|
|
b, err := f.Append(nil, protocol.Version1)
|
|
require.NoError(t, err)
|
|
frameType, l, err := parser.ParseType(b, protocol.Encryption1RTT)
|
|
require.NoError(t, err)
|
|
require.Equal(t, FrameTypeDatagramNoLength, frameType)
|
|
require.Equal(t, 1, l)
|
|
|
|
// ParseLessCommonFrame should not be used to handle DATAGRAM frames
|
|
_, _, err = parser.ParseLessCommonFrame(frameType, b[l:], protocol.Version1)
|
|
require.Error(t, err)
|
|
|
|
// parseDatagramFrame should be used for this type
|
|
datagramFrame, l, err := parser.ParseDatagramFrame(frameType, b[l:], protocol.Version1)
|
|
require.NoError(t, err)
|
|
require.IsType(t, &DatagramFrame{}, datagramFrame)
|
|
require.Equal(t, 6, l)
|
|
require.Equal(t, f.Data, datagramFrame.Data)
|
|
}
|
|
|
|
func TestFrameParserDatagramUnsupported(t *testing.T) {
|
|
parser := NewFrameParser(false, true, true)
|
|
f := &DatagramFrame{Data: []byte("foobar")}
|
|
b, err := f.Append(nil, protocol.Version1)
|
|
require.NoError(t, err)
|
|
|
|
_, _, err = parser.ParseType(b, protocol.Encryption1RTT)
|
|
checkFrameUnsupported(t, err, 0x30)
|
|
}
|
|
|
|
func TestFrameParserResetStreamAtUnsupported(t *testing.T) {
|
|
parser := NewFrameParser(true, false, true)
|
|
f := &ResetStreamFrame{StreamID: 0x1337, ReliableSize: 0x42, FinalSize: 0xdeadbeef}
|
|
b, err := f.Append(nil, protocol.Version1)
|
|
require.NoError(t, err)
|
|
|
|
_, _, err = parser.ParseType(b, protocol.Encryption1RTT)
|
|
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) {
|
|
parser := NewFrameParser(true, true, true)
|
|
|
|
_, l, err := parser.ParseType(encodeVarInt(0x42), protocol.Encryption1RTT)
|
|
|
|
require.Equal(t, 2, l)
|
|
|
|
require.Error(t, err)
|
|
var transportErr *qerr.TransportError
|
|
require.ErrorAs(t, err, &transportErr)
|
|
require.Equal(t, qerr.FrameEncodingError, transportErr.ErrorCode)
|
|
}
|
|
|
|
func TestFrameParsingErrorsOnInvalidFrames(t *testing.T) {
|
|
parser := NewFrameParser(true, true, true)
|
|
f := &MaxStreamDataFrame{
|
|
StreamID: 0x1337,
|
|
MaximumStreamData: 0xdeadbeef,
|
|
}
|
|
b, err := f.Append(nil, protocol.Version1)
|
|
require.NoError(t, err)
|
|
|
|
frameType, l, err := parser.ParseType(b[:len(b)-2], protocol.Encryption1RTT)
|
|
require.NoError(t, err)
|
|
require.Equal(t, FrameTypeMaxStreamData, frameType)
|
|
require.Equal(t, 1, l)
|
|
|
|
_, _, err = parser.ParseLessCommonFrame(frameType, b[1:len(b)-2], protocol.Version1)
|
|
require.Error(t, err)
|
|
var transportErr *qerr.TransportError
|
|
require.ErrorAs(t, err, &transportErr)
|
|
require.Equal(t, qerr.FrameEncodingError, transportErr.ErrorCode)
|
|
}
|
|
|
|
func writeFrames(tb testing.TB, frames ...Frame) []byte {
|
|
var b []byte
|
|
for _, f := range frames {
|
|
var err error
|
|
b, err = f.Append(b, protocol.Version1)
|
|
require.NoError(tb, err)
|
|
}
|
|
return b
|
|
}
|
|
|
|
// This function is used in benchmarks, and also to ensure zero allocation for STREAM frame parsing.
|
|
// We can therefore not use the require framework, as it allocates.
|
|
func parseFrames(tb testing.TB, parser *FrameParser, data []byte, frames ...Frame) {
|
|
for _, expectedFrame := range frames {
|
|
frameType, l, err := parser.ParseType(data, protocol.Encryption1RTT)
|
|
if err != nil {
|
|
tb.Fatal(err)
|
|
}
|
|
data = data[l:]
|
|
|
|
if frameType.IsStreamFrameType() {
|
|
sf := expectedFrame.(*StreamFrame)
|
|
frame, l, err := ParseStreamFrame(data, frameType, protocol.Version1)
|
|
if err != nil {
|
|
tb.Fatal(err)
|
|
}
|
|
if sf.StreamID != frame.StreamID || sf.Offset != frame.Offset {
|
|
tb.Fatalf("STREAM frame does not match: %v vs %v", sf, frame)
|
|
}
|
|
frame.PutBack()
|
|
data = data[l:]
|
|
continue
|
|
}
|
|
|
|
if frameType.IsAckFrameType() {
|
|
af, ok := expectedFrame.(*AckFrame)
|
|
if !ok {
|
|
tb.Fatalf("expected ACK, but got %v", expectedFrame)
|
|
}
|
|
|
|
f, l, err := parser.ParseAckFrame(frameType, data, protocol.Encryption1RTT, protocol.Version1)
|
|
if f.DelayTime != af.DelayTime || f.ECNCE != af.ECNCE || f.ECT0 != af.ECT0 || f.ECT1 != af.ECT1 {
|
|
tb.Fatal(err)
|
|
}
|
|
if f.DelayTime != af.DelayTime {
|
|
tb.Fatalf("ACK frame does not match: %v vs %v", af, f)
|
|
}
|
|
if !slices.Equal(f.AckRanges, af.AckRanges) {
|
|
tb.Fatalf("ACK frame ACK ranges don't match: %v vs %v", af, f)
|
|
}
|
|
data = data[l:]
|
|
continue
|
|
}
|
|
|
|
if frameType.IsDatagramFrameType() {
|
|
df, ok := expectedFrame.(*DatagramFrame)
|
|
if !ok {
|
|
tb.Fatalf("expected DATAGRAM, but got %v", expectedFrame)
|
|
}
|
|
|
|
f, l, err := parser.ParseDatagramFrame(frameType, data, protocol.Version1)
|
|
if err != nil {
|
|
tb.Fatal(err)
|
|
}
|
|
if df.DataLenPresent != f.DataLenPresent || !bytes.Equal(df.Data, f.Data) {
|
|
tb.Fatalf("DATAGRAM frame does not match: %v vs %v", df, f)
|
|
}
|
|
data = data[l:]
|
|
continue
|
|
}
|
|
|
|
f, l, err := parser.ParseLessCommonFrame(frameType, data, protocol.Version1)
|
|
if err != nil {
|
|
tb.Fatal(err)
|
|
}
|
|
data = data[l:]
|
|
|
|
switch frameType {
|
|
case FrameTypeMaxData:
|
|
mdf, ok := expectedFrame.(*MaxDataFrame)
|
|
if !ok {
|
|
tb.Fatalf("expected MAX_DATA, but got %v", expectedFrame)
|
|
}
|
|
if *f.(*MaxDataFrame) != *mdf {
|
|
tb.Fatalf("MAX_DATA frame does not match: %v vs %v", f, mdf)
|
|
}
|
|
case FrameTypeUniMaxStreams:
|
|
msf, ok := expectedFrame.(*MaxStreamsFrame)
|
|
if !ok {
|
|
tb.Fatalf("expected MAX_STREAMS, but got %v", expectedFrame)
|
|
}
|
|
if *f.(*MaxStreamsFrame) != *msf {
|
|
tb.Fatalf("MAX_STREAMS frame does not match: %v vs %v", f, msf)
|
|
}
|
|
case FrameTypeMaxStreamData:
|
|
mdf, ok := expectedFrame.(*MaxStreamDataFrame)
|
|
if !ok {
|
|
tb.Fatalf("expected MAX_STREAM_DATA, but got %v", expectedFrame)
|
|
}
|
|
if *f.(*MaxStreamDataFrame) != *mdf {
|
|
tb.Fatalf("MAX_STREAM_DATA frame does not match: %v vs %v", f, mdf)
|
|
}
|
|
case FrameTypeCrypto:
|
|
cf, ok := expectedFrame.(*CryptoFrame)
|
|
if !ok {
|
|
tb.Fatalf("expected CRYPTO, but got %v", expectedFrame)
|
|
}
|
|
frame := f.(*CryptoFrame)
|
|
if frame.Offset != cf.Offset || !bytes.Equal(frame.Data, cf.Data) {
|
|
tb.Fatalf("CRYPTO frame does not match: %v vs %v", f, cf)
|
|
}
|
|
case FrameTypePing:
|
|
_ = f.(*PingFrame)
|
|
case FrameTypeResetStream:
|
|
rsf, ok := expectedFrame.(*ResetStreamFrame)
|
|
if !ok {
|
|
tb.Fatalf("expected RESET_STREAM, but got %v", expectedFrame)
|
|
}
|
|
if *f.(*ResetStreamFrame) != *rsf {
|
|
tb.Fatalf("RESET_STREAM frame does not match: %v vs %v", f, rsf)
|
|
}
|
|
continue
|
|
default:
|
|
tb.Fatalf("Frame type not supported in benchmark or should not occur: %v", frameType)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFrameParserAllocs(t *testing.T) {
|
|
t.Run("STREAM", func(t *testing.T) {
|
|
var frames []Frame
|
|
for i := range 10 {
|
|
frames = append(frames, &StreamFrame{
|
|
StreamID: protocol.StreamID(1337 + i),
|
|
Offset: protocol.ByteCount(1e7 + i),
|
|
Data: make([]byte, 200+i),
|
|
DataLenPresent: true,
|
|
})
|
|
}
|
|
require.Zero(t, testFrameParserAllocs(t, frames))
|
|
})
|
|
|
|
t.Run("ACK", func(t *testing.T) {
|
|
var frames []Frame
|
|
for i := range 10 {
|
|
frames = append(frames, &AckFrame{
|
|
AckRanges: []AckRange{
|
|
{Smallest: protocol.PacketNumber(5000 + i), Largest: protocol.PacketNumber(5200 + i)},
|
|
{Smallest: protocol.PacketNumber(1 + i), Largest: protocol.PacketNumber(4200 + i)},
|
|
},
|
|
DelayTime: time.Duration(int64(time.Millisecond) * int64(i)),
|
|
ECT0: uint64(5000 + i),
|
|
ECT1: uint64(i),
|
|
ECNCE: uint64(10 + i),
|
|
})
|
|
}
|
|
require.Zero(t, testFrameParserAllocs(t, frames))
|
|
})
|
|
}
|
|
|
|
func testFrameParserAllocs(t *testing.T, frames []Frame) float64 {
|
|
buf := writeFrames(t, frames...)
|
|
parser := NewFrameParser(true, true, true)
|
|
parser.SetAckDelayExponent(3)
|
|
|
|
return testing.AllocsPerRun(100, func() {
|
|
parseFrames(t, parser, buf, frames...)
|
|
})
|
|
}
|
|
|
|
func BenchmarkParseOtherFrames(b *testing.B) {
|
|
frames := []Frame{
|
|
&MaxDataFrame{MaximumData: 123456},
|
|
&MaxStreamsFrame{MaxStreamNum: 10},
|
|
&MaxStreamDataFrame{StreamID: 1337, MaximumStreamData: 1e6},
|
|
&CryptoFrame{Offset: 1000, Data: make([]byte, 128)},
|
|
&PingFrame{},
|
|
&ResetStreamFrame{StreamID: 87654, ErrorCode: 1234, FinalSize: 1e8},
|
|
}
|
|
benchmarkFrames(b, frames...)
|
|
}
|
|
|
|
func BenchmarkParseAckFrame(b *testing.B) {
|
|
var frames []Frame
|
|
for i := range 10 {
|
|
frames = append(frames, &AckFrame{
|
|
AckRanges: []AckRange{
|
|
{Smallest: protocol.PacketNumber(5000 + i), Largest: protocol.PacketNumber(5200 + i)},
|
|
{Smallest: protocol.PacketNumber(1 + i), Largest: protocol.PacketNumber(4200 + i)},
|
|
},
|
|
DelayTime: time.Duration(int64(time.Millisecond) * int64(i)),
|
|
ECT0: uint64(5000 + i),
|
|
ECT1: uint64(i),
|
|
ECNCE: uint64(10 + i),
|
|
})
|
|
}
|
|
benchmarkFrames(b, frames...)
|
|
}
|
|
|
|
func BenchmarkParseStreamFrame(b *testing.B) {
|
|
var frames []Frame
|
|
for i := range 10 {
|
|
data := make([]byte, 200+i)
|
|
rand.Read(data)
|
|
frames = append(frames, &StreamFrame{
|
|
StreamID: protocol.StreamID(1337 + i),
|
|
Offset: protocol.ByteCount(1e7 + i),
|
|
Data: data,
|
|
DataLenPresent: true,
|
|
})
|
|
}
|
|
benchmarkFrames(b, frames...)
|
|
}
|
|
|
|
func BenchmarkParseDatagramFrame(b *testing.B) {
|
|
var frames []Frame
|
|
for i := range 10 {
|
|
data := make([]byte, 200+i)
|
|
rand.Read(data)
|
|
frames = append(frames, &DatagramFrame{
|
|
Data: data,
|
|
DataLenPresent: true,
|
|
})
|
|
}
|
|
benchmarkFrames(b, frames...)
|
|
}
|
|
|
|
func benchmarkFrames(b *testing.B, frames ...Frame) {
|
|
b.ReportAllocs()
|
|
|
|
buf := writeFrames(b, frames...)
|
|
parser := NewFrameParser(true, true, true)
|
|
parser.SetAckDelayExponent(3)
|
|
|
|
for b.Loop() {
|
|
parseFrames(b, parser, buf, frames...)
|
|
}
|
|
}
|