forked from quic-go/quic-go
* wire: translate ACK frame tests * wire: translate CONNECTION_CLOSE frame tests * wire: translate DATA_BLOCKED frame tests * wire: translate DATAGRAM frame tests * wire: translate HANDSHAKE_DONE frame tests * wire: translate MAX_DATA frame tests * wire: translate MAX_STREAM_DATA frame tests * wire: translate MAX_STREAMS frame tests * wire: translate NEW_CONNECTION_ID frame tests * wire: translate NEW_TOKEN frame tests * wire: translate PATH_CHALLENGE frame tests * wire: translate PATH_RESPONSE frame tests * wire: translate PING frame test * wire: translate RESET_STREAM frame tests * wire: translate RETIRE_CONNECTION_ID frame tests * wire: translate STOP_SENDING frame tests * wire: translate STREAM_DATA_BLOCKED frame tests * wire: translate STREAMS_BLOCKED frame tests * wire: translate CRYPTO frame tests * wire: translate STREAM frame tests * wire: translate version negotiation tests * wire: translate header tests * wire: translate pool tests * wire: translate frame logging tests * wire: translate short header tests * wire: translate framer parser tests * wire: translate transport parameter tests
855 lines
33 KiB
Go
855 lines
33 KiB
Go
package wire
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"math"
|
|
"net/netip"
|
|
"testing"
|
|
"time"
|
|
|
|
"golang.org/x/exp/rand"
|
|
|
|
"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 getRandomValueUpTo(max int64) uint64 {
|
|
maxVals := []int64{math.MaxUint8 / 4, math.MaxUint16 / 4, math.MaxUint32 / 4, math.MaxUint64 / 4}
|
|
m := maxVals[int(rand.Int31n(4))]
|
|
if m > max {
|
|
m = max
|
|
}
|
|
return uint64(rand.Int63n(m))
|
|
}
|
|
|
|
func getRandomValue() uint64 { return getRandomValueUpTo(quicvarint.Max) }
|
|
|
|
func appendInitialSourceConnectionID(b []byte) []byte {
|
|
b = quicvarint.Append(b, uint64(initialSourceConnectionIDParameterID))
|
|
b = quicvarint.Append(b, 6)
|
|
return append(b, []byte("foobar")...)
|
|
}
|
|
|
|
func TestTransportParametersStringRepresentation(t *testing.T) {
|
|
rcid := protocol.ParseConnectionID([]byte{0xde, 0xad, 0xc0, 0xde})
|
|
p := &TransportParameters{
|
|
InitialMaxStreamDataBidiLocal: 1234,
|
|
InitialMaxStreamDataBidiRemote: 2345,
|
|
InitialMaxStreamDataUni: 3456,
|
|
InitialMaxData: 4567,
|
|
MaxBidiStreamNum: 1337,
|
|
MaxUniStreamNum: 7331,
|
|
MaxIdleTimeout: 42 * time.Second,
|
|
OriginalDestinationConnectionID: protocol.ParseConnectionID([]byte{0xde, 0xad, 0xbe, 0xef}),
|
|
InitialSourceConnectionID: protocol.ParseConnectionID([]byte{0xde, 0xca, 0xfb, 0xad}),
|
|
RetrySourceConnectionID: &rcid,
|
|
AckDelayExponent: 14,
|
|
MaxAckDelay: 37 * time.Millisecond,
|
|
StatelessResetToken: &protocol.StatelessResetToken{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00},
|
|
ActiveConnectionIDLimit: 123,
|
|
MaxDatagramFrameSize: 876,
|
|
}
|
|
expected := "&wire.TransportParameters{OriginalDestinationConnectionID: deadbeef, InitialSourceConnectionID: decafbad, RetrySourceConnectionID: deadc0de, InitialMaxStreamDataBidiLocal: 1234, InitialMaxStreamDataBidiRemote: 2345, InitialMaxStreamDataUni: 3456, InitialMaxData: 4567, MaxBidiStreamNum: 1337, MaxUniStreamNum: 7331, MaxIdleTimeout: 42s, AckDelayExponent: 14, MaxAckDelay: 37ms, ActiveConnectionIDLimit: 123, StatelessResetToken: 0x112233445566778899aabbccddeeff00, MaxDatagramFrameSize: 876}"
|
|
require.Equal(t, expected, p.String())
|
|
}
|
|
|
|
func TestTransportParametersStringRepresentationWithoutOptionalFields(t *testing.T) {
|
|
p := &TransportParameters{
|
|
InitialMaxStreamDataBidiLocal: 1234,
|
|
InitialMaxStreamDataBidiRemote: 2345,
|
|
InitialMaxStreamDataUni: 3456,
|
|
InitialMaxData: 4567,
|
|
MaxBidiStreamNum: 1337,
|
|
MaxUniStreamNum: 7331,
|
|
MaxIdleTimeout: 42 * time.Second,
|
|
OriginalDestinationConnectionID: protocol.ParseConnectionID([]byte{0xde, 0xad, 0xbe, 0xef}),
|
|
InitialSourceConnectionID: protocol.ParseConnectionID([]byte{}),
|
|
AckDelayExponent: 14,
|
|
MaxAckDelay: 37 * time.Second,
|
|
ActiveConnectionIDLimit: 89,
|
|
MaxDatagramFrameSize: protocol.InvalidByteCount,
|
|
}
|
|
expected := "&wire.TransportParameters{OriginalDestinationConnectionID: deadbeef, InitialSourceConnectionID: (empty), InitialMaxStreamDataBidiLocal: 1234, InitialMaxStreamDataBidiRemote: 2345, InitialMaxStreamDataUni: 3456, InitialMaxData: 4567, MaxBidiStreamNum: 1337, MaxUniStreamNum: 7331, MaxIdleTimeout: 42s, AckDelayExponent: 14, MaxAckDelay: 37s, ActiveConnectionIDLimit: 89}"
|
|
require.Equal(t, expected, p.String())
|
|
}
|
|
|
|
func TestMarshalAndUnmarshalTransportParameters(t *testing.T) {
|
|
var token protocol.StatelessResetToken
|
|
rand.Read(token[:])
|
|
rcid := protocol.ParseConnectionID([]byte{0xde, 0xad, 0xc0, 0xde})
|
|
params := &TransportParameters{
|
|
InitialMaxStreamDataBidiLocal: protocol.ByteCount(getRandomValue()),
|
|
InitialMaxStreamDataBidiRemote: protocol.ByteCount(getRandomValue()),
|
|
InitialMaxStreamDataUni: protocol.ByteCount(getRandomValue()),
|
|
InitialMaxData: protocol.ByteCount(getRandomValue()),
|
|
MaxIdleTimeout: 0xcafe * time.Second,
|
|
MaxBidiStreamNum: protocol.StreamNum(getRandomValueUpTo(int64(protocol.MaxStreamCount))),
|
|
MaxUniStreamNum: protocol.StreamNum(getRandomValueUpTo(int64(protocol.MaxStreamCount))),
|
|
DisableActiveMigration: true,
|
|
StatelessResetToken: &token,
|
|
OriginalDestinationConnectionID: protocol.ParseConnectionID([]byte{0xde, 0xad, 0xbe, 0xef}),
|
|
InitialSourceConnectionID: protocol.ParseConnectionID([]byte{0xde, 0xca, 0xfb, 0xad}),
|
|
RetrySourceConnectionID: &rcid,
|
|
AckDelayExponent: 13,
|
|
MaxAckDelay: 42 * time.Millisecond,
|
|
ActiveConnectionIDLimit: 2 + getRandomValueUpTo(quicvarint.Max-2),
|
|
MaxUDPPayloadSize: 1200 + protocol.ByteCount(getRandomValueUpTo(quicvarint.Max-1200)),
|
|
MaxDatagramFrameSize: protocol.ByteCount(getRandomValue()),
|
|
}
|
|
data := params.Marshal(protocol.PerspectiveServer)
|
|
|
|
p := &TransportParameters{}
|
|
require.NoError(t, p.Unmarshal(data, protocol.PerspectiveServer))
|
|
require.Equal(t, params.InitialMaxStreamDataBidiLocal, p.InitialMaxStreamDataBidiLocal)
|
|
require.Equal(t, params.InitialMaxStreamDataBidiRemote, p.InitialMaxStreamDataBidiRemote)
|
|
require.Equal(t, params.InitialMaxStreamDataUni, p.InitialMaxStreamDataUni)
|
|
require.Equal(t, params.InitialMaxData, p.InitialMaxData)
|
|
require.Equal(t, params.MaxUniStreamNum, p.MaxUniStreamNum)
|
|
require.Equal(t, params.MaxBidiStreamNum, p.MaxBidiStreamNum)
|
|
require.Equal(t, params.MaxIdleTimeout, p.MaxIdleTimeout)
|
|
require.Equal(t, params.DisableActiveMigration, p.DisableActiveMigration)
|
|
require.Equal(t, params.StatelessResetToken, p.StatelessResetToken)
|
|
require.Equal(t, protocol.ParseConnectionID([]byte{0xde, 0xad, 0xbe, 0xef}), p.OriginalDestinationConnectionID)
|
|
require.Equal(t, protocol.ParseConnectionID([]byte{0xde, 0xca, 0xfb, 0xad}), p.InitialSourceConnectionID)
|
|
require.Equal(t, &rcid, p.RetrySourceConnectionID)
|
|
require.Equal(t, uint8(13), p.AckDelayExponent)
|
|
require.Equal(t, 42*time.Millisecond, p.MaxAckDelay)
|
|
require.Equal(t, params.ActiveConnectionIDLimit, p.ActiveConnectionIDLimit)
|
|
require.Equal(t, params.MaxUDPPayloadSize, p.MaxUDPPayloadSize)
|
|
require.Equal(t, params.MaxDatagramFrameSize, p.MaxDatagramFrameSize)
|
|
}
|
|
|
|
func TestMarshalAdditionalTransportParameters(t *testing.T) {
|
|
origAdditionalTransportParametersClient := AdditionalTransportParametersClient
|
|
t.Cleanup(func() { AdditionalTransportParametersClient = origAdditionalTransportParametersClient })
|
|
AdditionalTransportParametersClient = map[uint64][]byte{1337: []byte("foobar")}
|
|
|
|
result := quicvarint.Append([]byte{}, 1337)
|
|
result = quicvarint.Append(result, 6)
|
|
result = append(result, []byte("foobar")...)
|
|
|
|
params := &TransportParameters{}
|
|
require.True(t, bytes.Contains(params.Marshal(protocol.PerspectiveClient), result))
|
|
require.False(t, bytes.Contains(params.Marshal(protocol.PerspectiveServer), result))
|
|
}
|
|
|
|
func TestMarshalWithoutRetrySourceConnectionID(t *testing.T) {
|
|
data := (&TransportParameters{
|
|
StatelessResetToken: &protocol.StatelessResetToken{},
|
|
ActiveConnectionIDLimit: 2,
|
|
}).Marshal(protocol.PerspectiveServer)
|
|
p := &TransportParameters{}
|
|
require.NoError(t, p.Unmarshal(data, protocol.PerspectiveServer))
|
|
require.Nil(t, p.RetrySourceConnectionID)
|
|
}
|
|
|
|
func TestMarshalZeroLengthRetrySourceConnectionID(t *testing.T) {
|
|
rcid := protocol.ParseConnectionID([]byte{})
|
|
data := (&TransportParameters{
|
|
RetrySourceConnectionID: &rcid,
|
|
StatelessResetToken: &protocol.StatelessResetToken{},
|
|
ActiveConnectionIDLimit: 2,
|
|
}).Marshal(protocol.PerspectiveServer)
|
|
p := &TransportParameters{}
|
|
require.NoError(t, p.Unmarshal(data, protocol.PerspectiveServer))
|
|
require.NotNil(t, p.RetrySourceConnectionID)
|
|
require.Zero(t, p.RetrySourceConnectionID.Len())
|
|
}
|
|
|
|
func TestTransportParameterNoMaxAckDelayIfDefault(t *testing.T) {
|
|
const num = 1000
|
|
var defaultLen, dataLen int
|
|
maxAckDelay := protocol.DefaultMaxAckDelay + time.Millisecond
|
|
for i := 0; i < num; i++ {
|
|
dataDefault := (&TransportParameters{
|
|
MaxAckDelay: protocol.DefaultMaxAckDelay,
|
|
StatelessResetToken: &protocol.StatelessResetToken{},
|
|
}).Marshal(protocol.PerspectiveServer)
|
|
defaultLen += len(dataDefault)
|
|
data := (&TransportParameters{
|
|
MaxAckDelay: maxAckDelay,
|
|
StatelessResetToken: &protocol.StatelessResetToken{},
|
|
}).Marshal(protocol.PerspectiveServer)
|
|
dataLen += len(data)
|
|
}
|
|
entryLen := quicvarint.Len(uint64(ackDelayExponentParameterID)) +
|
|
quicvarint.Len(uint64(quicvarint.Len(uint64(maxAckDelay.Milliseconds())))) +
|
|
quicvarint.Len(uint64(maxAckDelay.Milliseconds()))
|
|
require.InDelta(t, float32(defaultLen)/num+float32(entryLen), float32(dataLen)/num, 1)
|
|
}
|
|
|
|
func TestTransportParameterNoAckDelayExponentIfDefault(t *testing.T) {
|
|
const num = 1000
|
|
var defaultLen, dataLen int
|
|
for i := 0; i < num; i++ {
|
|
dataDefault := (&TransportParameters{
|
|
AckDelayExponent: protocol.DefaultAckDelayExponent,
|
|
StatelessResetToken: &protocol.StatelessResetToken{},
|
|
}).Marshal(protocol.PerspectiveServer)
|
|
defaultLen += len(dataDefault)
|
|
data := (&TransportParameters{
|
|
AckDelayExponent: protocol.DefaultAckDelayExponent + 1,
|
|
StatelessResetToken: &protocol.StatelessResetToken{},
|
|
}).Marshal(protocol.PerspectiveServer)
|
|
dataLen += len(data)
|
|
}
|
|
entryLen := quicvarint.Len(uint64(ackDelayExponentParameterID)) +
|
|
quicvarint.Len(uint64(quicvarint.Len(protocol.DefaultAckDelayExponent+1))) +
|
|
quicvarint.Len(protocol.DefaultAckDelayExponent+1)
|
|
require.InDelta(t, float32(defaultLen)/num+float32(entryLen), float32(dataLen)/num, 1)
|
|
}
|
|
|
|
func TestTransportParameterSetsDefaultValuesWhenNotSent(t *testing.T) {
|
|
data := (&TransportParameters{
|
|
AckDelayExponent: protocol.DefaultAckDelayExponent,
|
|
StatelessResetToken: &protocol.StatelessResetToken{},
|
|
ActiveConnectionIDLimit: protocol.DefaultActiveConnectionIDLimit,
|
|
}).Marshal(protocol.PerspectiveServer)
|
|
p := &TransportParameters{}
|
|
require.NoError(t, p.Unmarshal(data, protocol.PerspectiveServer))
|
|
require.EqualValues(t, protocol.DefaultAckDelayExponent, p.AckDelayExponent)
|
|
require.EqualValues(t, protocol.DefaultActiveConnectionIDLimit, p.ActiveConnectionIDLimit)
|
|
}
|
|
|
|
func TestTransportParameterErrors(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
params *TransportParameters
|
|
perspective protocol.Perspective
|
|
data []byte
|
|
expectedErrMsg string
|
|
}{
|
|
{
|
|
name: "invalid stateless reset token length",
|
|
data: func() []byte {
|
|
b := quicvarint.Append(nil, uint64(statelessResetTokenParameterID))
|
|
b = quicvarint.Append(b, 15)
|
|
return append(b, make([]byte, 15)...)
|
|
}(),
|
|
perspective: protocol.PerspectiveServer,
|
|
expectedErrMsg: "wrong length for stateless_reset_token: 15 (expected 16)",
|
|
},
|
|
{
|
|
name: "small max UDP payload size",
|
|
data: func() []byte {
|
|
b := quicvarint.Append(nil, uint64(maxUDPPayloadSizeParameterID))
|
|
b = quicvarint.Append(b, uint64(quicvarint.Len(1199)))
|
|
return quicvarint.Append(b, 1199)
|
|
}(),
|
|
perspective: protocol.PerspectiveServer,
|
|
expectedErrMsg: "invalid value for max_udp_payload_size: 1199 (minimum 1200)",
|
|
},
|
|
{
|
|
name: "active connection ID limit too small",
|
|
params: &TransportParameters{
|
|
ActiveConnectionIDLimit: 1,
|
|
StatelessResetToken: &protocol.StatelessResetToken{},
|
|
},
|
|
perspective: protocol.PerspectiveServer,
|
|
expectedErrMsg: "invalid value for active_connection_id_limit: 1 (minimum 2)",
|
|
},
|
|
{
|
|
name: "ack delay exponent too large",
|
|
params: &TransportParameters{
|
|
AckDelayExponent: 21,
|
|
StatelessResetToken: &protocol.StatelessResetToken{},
|
|
},
|
|
perspective: protocol.PerspectiveServer,
|
|
expectedErrMsg: "invalid value for ack_delay_exponent: 21 (maximum 20)",
|
|
},
|
|
{
|
|
name: "disable active migration has content",
|
|
data: func() []byte {
|
|
b := quicvarint.Append(nil, uint64(disableActiveMigrationParameterID))
|
|
b = quicvarint.Append(b, 6)
|
|
return append(b, []byte("foobar")...)
|
|
}(),
|
|
perspective: protocol.PerspectiveServer,
|
|
expectedErrMsg: "wrong length for disable_active_migration: 6 (expected empty)",
|
|
},
|
|
{
|
|
name: "server doesn't set original destination connection ID",
|
|
data: func() []byte {
|
|
b := quicvarint.Append(nil, uint64(statelessResetTokenParameterID))
|
|
b = quicvarint.Append(b, 16)
|
|
b = append(b, make([]byte, 16)...)
|
|
return appendInitialSourceConnectionID(b)
|
|
}(),
|
|
perspective: protocol.PerspectiveServer,
|
|
expectedErrMsg: "missing original_destination_connection_id",
|
|
},
|
|
{
|
|
name: "initial source connection ID is missing",
|
|
data: []byte{},
|
|
perspective: protocol.PerspectiveClient,
|
|
expectedErrMsg: "missing initial_source_connection_id",
|
|
},
|
|
{
|
|
name: "max ack delay is too large",
|
|
params: &TransportParameters{
|
|
MaxAckDelay: 1 << 14 * time.Millisecond,
|
|
StatelessResetToken: &protocol.StatelessResetToken{},
|
|
},
|
|
perspective: protocol.PerspectiveServer,
|
|
expectedErrMsg: "invalid value for max_ack_delay: 16384ms (maximum 16383ms)",
|
|
},
|
|
{
|
|
name: "varint value has wrong length",
|
|
data: func() []byte {
|
|
b := quicvarint.Append(nil, uint64(initialMaxStreamDataBidiLocalParameterID))
|
|
b = quicvarint.Append(b, 2)
|
|
val := uint64(0xdeadbeef)
|
|
b = quicvarint.Append(b, val)
|
|
return appendInitialSourceConnectionID(b)
|
|
}(),
|
|
perspective: protocol.PerspectiveServer,
|
|
expectedErrMsg: fmt.Sprintf("inconsistent transport parameter length for transport parameter %#x", initialMaxStreamDataBidiLocalParameterID),
|
|
},
|
|
{
|
|
name: "initial max streams bidi is too large",
|
|
data: func() []byte {
|
|
b := quicvarint.Append(nil, uint64(initialMaxStreamsBidiParameterID))
|
|
b = quicvarint.Append(b, uint64(quicvarint.Len(uint64(protocol.MaxStreamCount+1))))
|
|
b = quicvarint.Append(b, uint64(protocol.MaxStreamCount+1))
|
|
return appendInitialSourceConnectionID(b)
|
|
}(),
|
|
perspective: protocol.PerspectiveServer,
|
|
expectedErrMsg: "initial_max_streams_bidi too large: 1152921504606846977 (maximum 1152921504606846976)",
|
|
},
|
|
{
|
|
name: "initial max streams uni is too large",
|
|
data: func() []byte {
|
|
b := quicvarint.Append(nil, uint64(initialMaxStreamsUniParameterID))
|
|
b = quicvarint.Append(b, uint64(quicvarint.Len(uint64(protocol.MaxStreamCount+1))))
|
|
b = quicvarint.Append(b, uint64(protocol.MaxStreamCount+1))
|
|
return appendInitialSourceConnectionID(b)
|
|
}(),
|
|
perspective: protocol.PerspectiveServer,
|
|
expectedErrMsg: "initial_max_streams_uni too large: 1152921504606846977 (maximum 1152921504606846976)",
|
|
},
|
|
{
|
|
name: "not enough data to read",
|
|
data: func() []byte {
|
|
b := quicvarint.Append(nil, 0x42)
|
|
b = quicvarint.Append(b, 7)
|
|
return append(b, []byte("foobar")...)
|
|
}(),
|
|
perspective: protocol.PerspectiveServer,
|
|
expectedErrMsg: "remaining length (6) smaller than parameter length (7)",
|
|
},
|
|
{
|
|
name: "client sent stateless reset token",
|
|
data: func() []byte {
|
|
b := quicvarint.Append(nil, uint64(statelessResetTokenParameterID))
|
|
b = quicvarint.Append(b, uint64(quicvarint.Len(16)))
|
|
return append(b, make([]byte, 16)...)
|
|
}(),
|
|
perspective: protocol.PerspectiveClient,
|
|
expectedErrMsg: "client sent a stateless_reset_token",
|
|
},
|
|
{
|
|
name: "client sent original destination connection ID",
|
|
data: func() []byte {
|
|
b := quicvarint.Append(nil, uint64(originalDestinationConnectionIDParameterID))
|
|
b = quicvarint.Append(b, 6)
|
|
return append(b, []byte("foobar")...)
|
|
}(),
|
|
perspective: protocol.PerspectiveClient,
|
|
expectedErrMsg: "client sent an original_destination_connection_id",
|
|
},
|
|
{
|
|
name: "huge max ack delay value",
|
|
data: func() []byte {
|
|
val := uint64(math.MaxUint64) / 5
|
|
b := quicvarint.Append(nil, uint64(maxAckDelayParameterID))
|
|
b = quicvarint.Append(b, uint64(quicvarint.Len(val)))
|
|
b = quicvarint.Append(b, val)
|
|
return appendInitialSourceConnectionID(b)
|
|
}(),
|
|
perspective: protocol.PerspectiveClient,
|
|
expectedErrMsg: "invalid value for max_ack_delay: 3689348814741910323ms (maximum 16383ms)",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var err error
|
|
if tt.params != nil {
|
|
data := tt.params.Marshal(tt.perspective)
|
|
err = (&TransportParameters{}).Unmarshal(data, tt.perspective)
|
|
} else {
|
|
err = (&TransportParameters{}).Unmarshal(tt.data, tt.perspective)
|
|
}
|
|
require.Error(t, err)
|
|
transportErr, ok := err.(*qerr.TransportError)
|
|
require.True(t, ok)
|
|
require.Equal(t, qerr.TransportParameterError, transportErr.ErrorCode)
|
|
require.Equal(t, tt.expectedErrMsg, transportErr.ErrorMessage)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTransportParameterUnknownParameters(t *testing.T) {
|
|
// write a known parameter
|
|
b := quicvarint.Append(nil, uint64(initialMaxStreamDataBidiLocalParameterID))
|
|
b = quicvarint.Append(b, uint64(quicvarint.Len(0x1337)))
|
|
b = quicvarint.Append(b, 0x1337)
|
|
// write an unknown parameter
|
|
b = quicvarint.Append(b, 0x42)
|
|
b = quicvarint.Append(b, 6)
|
|
b = append(b, []byte("foobar")...)
|
|
// write a known parameter
|
|
b = quicvarint.Append(b, uint64(initialMaxStreamDataBidiRemoteParameterID))
|
|
b = quicvarint.Append(b, uint64(quicvarint.Len(0x42)))
|
|
b = quicvarint.Append(b, 0x42)
|
|
b = appendInitialSourceConnectionID(b)
|
|
p := &TransportParameters{}
|
|
err := p.Unmarshal(b, protocol.PerspectiveClient)
|
|
require.NoError(t, err)
|
|
require.Equal(t, protocol.ByteCount(0x1337), p.InitialMaxStreamDataBidiLocal)
|
|
require.Equal(t, protocol.ByteCount(0x42), p.InitialMaxStreamDataBidiRemote)
|
|
}
|
|
|
|
func TestTransportParameterRejectsDuplicateParameters(t *testing.T) {
|
|
// write first parameter
|
|
b := quicvarint.Append(nil, uint64(initialMaxStreamDataBidiLocalParameterID))
|
|
b = quicvarint.Append(b, uint64(quicvarint.Len(0x1337)))
|
|
b = quicvarint.Append(b, 0x1337)
|
|
// write a second parameter
|
|
b = quicvarint.Append(b, uint64(initialMaxStreamDataBidiRemoteParameterID))
|
|
b = quicvarint.Append(b, uint64(quicvarint.Len(0x42)))
|
|
b = quicvarint.Append(b, 0x42)
|
|
// write first parameter again
|
|
b = quicvarint.Append(b, uint64(initialMaxStreamDataBidiLocalParameterID))
|
|
b = quicvarint.Append(b, uint64(quicvarint.Len(0x1337)))
|
|
b = quicvarint.Append(b, 0x1337)
|
|
b = appendInitialSourceConnectionID(b)
|
|
err := (&TransportParameters{}).Unmarshal(b, protocol.PerspectiveClient)
|
|
require.Error(t, err)
|
|
transportErr, ok := err.(*qerr.TransportError)
|
|
require.True(t, ok)
|
|
require.Equal(t, qerr.TransportParameterError, transportErr.ErrorCode)
|
|
require.Equal(t, fmt.Sprintf("received duplicate transport parameter %#x", initialMaxStreamDataBidiLocalParameterID), transportErr.ErrorMessage)
|
|
}
|
|
|
|
func TestPreferredAddressMarshalAndUnmarshal(t *testing.T) {
|
|
pa := &PreferredAddress{
|
|
IPv4: netip.AddrPortFrom(netip.AddrFrom4([4]byte{127, 0, 0, 1}), 42),
|
|
IPv6: netip.AddrPortFrom(netip.AddrFrom16([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), 13),
|
|
ConnectionID: protocol.ParseConnectionID([]byte{0xde, 0xad, 0xbe, 0xef}),
|
|
StatelessResetToken: protocol.StatelessResetToken{16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1},
|
|
}
|
|
|
|
data := (&TransportParameters{
|
|
PreferredAddress: pa,
|
|
StatelessResetToken: &protocol.StatelessResetToken{},
|
|
ActiveConnectionIDLimit: 2,
|
|
}).Marshal(protocol.PerspectiveServer)
|
|
p := &TransportParameters{}
|
|
err := p.Unmarshal(data, protocol.PerspectiveServer)
|
|
require.NoError(t, err)
|
|
require.Equal(t, pa.IPv4, p.PreferredAddress.IPv4)
|
|
require.Equal(t, pa.IPv6, p.PreferredAddress.IPv6)
|
|
require.Equal(t, pa.ConnectionID, p.PreferredAddress.ConnectionID)
|
|
require.Equal(t, pa.StatelessResetToken, p.PreferredAddress.StatelessResetToken)
|
|
}
|
|
|
|
func TestPreferredAddressFromClient(t *testing.T) {
|
|
b := quicvarint.Append(nil, uint64(preferredAddressParameterID))
|
|
b = quicvarint.Append(b, 6)
|
|
b = append(b, []byte("foobar")...)
|
|
p := &TransportParameters{}
|
|
err := p.Unmarshal(b, protocol.PerspectiveClient)
|
|
require.Error(t, err)
|
|
require.IsType(t, &qerr.TransportError{}, err)
|
|
transportErr := err.(*qerr.TransportError)
|
|
require.Equal(t, qerr.TransportParameterError, transportErr.ErrorCode)
|
|
require.Equal(t, "client sent a preferred_address", transportErr.ErrorMessage)
|
|
}
|
|
|
|
func TestPreferredAddressZeroLengthConnectionID(t *testing.T) {
|
|
pa := &PreferredAddress{
|
|
IPv4: netip.AddrPortFrom(netip.AddrFrom4([4]byte{127, 0, 0, 1}), 42),
|
|
IPv6: netip.AddrPortFrom(netip.AddrFrom16([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), 13),
|
|
ConnectionID: protocol.ParseConnectionID([]byte{}),
|
|
StatelessResetToken: protocol.StatelessResetToken{16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1},
|
|
}
|
|
data := (&TransportParameters{
|
|
PreferredAddress: pa,
|
|
StatelessResetToken: &protocol.StatelessResetToken{},
|
|
}).Marshal(protocol.PerspectiveServer)
|
|
p := &TransportParameters{}
|
|
err := p.Unmarshal(data, protocol.PerspectiveServer)
|
|
require.Error(t, err)
|
|
require.IsType(t, &qerr.TransportError{}, err)
|
|
transportErr := err.(*qerr.TransportError)
|
|
require.Equal(t, qerr.TransportParameterError, transportErr.ErrorCode)
|
|
require.Equal(t, "invalid connection ID length: 0", transportErr.ErrorMessage)
|
|
}
|
|
|
|
func TestPreferredAddressErrorOnEOF(t *testing.T) {
|
|
raw := []byte{
|
|
127, 0, 0, 1, // IPv4
|
|
0, 42, // IPv4 Port
|
|
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // IPv6
|
|
13, 37, // IPv6 Port,
|
|
4, // conn ID len
|
|
0xde, 0xad, 0xbe, 0xef,
|
|
16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, // stateless reset token
|
|
}
|
|
for i := 1; i < len(raw); i++ {
|
|
b := quicvarint.Append(nil, uint64(preferredAddressParameterID))
|
|
b = append(b, raw[:i]...)
|
|
p := &TransportParameters{}
|
|
err := p.Unmarshal(b, protocol.PerspectiveServer)
|
|
require.Error(t, err)
|
|
}
|
|
}
|
|
|
|
func TestTransportParametersFromSessionTicket(t *testing.T) {
|
|
params := &TransportParameters{
|
|
InitialMaxStreamDataBidiLocal: protocol.ByteCount(getRandomValue()),
|
|
InitialMaxStreamDataBidiRemote: protocol.ByteCount(getRandomValue()),
|
|
InitialMaxStreamDataUni: protocol.ByteCount(getRandomValue()),
|
|
InitialMaxData: protocol.ByteCount(getRandomValue()),
|
|
MaxBidiStreamNum: protocol.StreamNum(getRandomValueUpTo(int64(protocol.MaxStreamCount))),
|
|
MaxUniStreamNum: protocol.StreamNum(getRandomValueUpTo(int64(protocol.MaxStreamCount))),
|
|
ActiveConnectionIDLimit: 2 + getRandomValueUpTo(quicvarint.Max-2),
|
|
MaxDatagramFrameSize: protocol.ByteCount(getRandomValueUpTo(int64(MaxDatagramSize))),
|
|
}
|
|
require.True(t, params.ValidFor0RTT(params))
|
|
b := params.MarshalForSessionTicket(nil)
|
|
var tp TransportParameters
|
|
require.NoError(t, tp.UnmarshalFromSessionTicket(b))
|
|
require.Equal(t, params.InitialMaxStreamDataBidiLocal, tp.InitialMaxStreamDataBidiLocal)
|
|
require.Equal(t, params.InitialMaxStreamDataBidiRemote, tp.InitialMaxStreamDataBidiRemote)
|
|
require.Equal(t, params.InitialMaxStreamDataUni, tp.InitialMaxStreamDataUni)
|
|
require.Equal(t, params.InitialMaxData, tp.InitialMaxData)
|
|
require.Equal(t, params.MaxBidiStreamNum, tp.MaxBidiStreamNum)
|
|
require.Equal(t, params.MaxUniStreamNum, tp.MaxUniStreamNum)
|
|
require.Equal(t, params.ActiveConnectionIDLimit, tp.ActiveConnectionIDLimit)
|
|
require.Equal(t, params.MaxDatagramFrameSize, tp.MaxDatagramFrameSize)
|
|
}
|
|
|
|
func TestSessionTicketInvalidTransportParameters(t *testing.T) {
|
|
var p TransportParameters
|
|
require.Error(t, p.UnmarshalFromSessionTicket([]byte("foobar")))
|
|
}
|
|
|
|
func TestSessionTicketTransportParameterVersionMismatch(t *testing.T) {
|
|
var p TransportParameters
|
|
data := p.MarshalForSessionTicket(nil)
|
|
b := quicvarint.Append(nil, transportParameterMarshalingVersion+1)
|
|
b = append(b, data[quicvarint.Len(transportParameterMarshalingVersion):]...)
|
|
err := p.UnmarshalFromSessionTicket(b)
|
|
require.EqualError(t, err, fmt.Sprintf("unknown transport parameter marshaling version: %d", transportParameterMarshalingVersion+1))
|
|
}
|
|
|
|
func TestTransportParametersValidFor0RTT(t *testing.T) {
|
|
saved := &TransportParameters{
|
|
InitialMaxStreamDataBidiLocal: 1,
|
|
InitialMaxStreamDataBidiRemote: 2,
|
|
InitialMaxStreamDataUni: 3,
|
|
InitialMaxData: 4,
|
|
MaxBidiStreamNum: 5,
|
|
MaxUniStreamNum: 6,
|
|
ActiveConnectionIDLimit: 7,
|
|
MaxDatagramFrameSize: 1000,
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
modify func(*TransportParameters)
|
|
valid bool
|
|
}{
|
|
{
|
|
name: "No Changes",
|
|
modify: func(p *TransportParameters) {},
|
|
valid: true,
|
|
},
|
|
{
|
|
name: "InitialMaxStreamDataBidiLocal reduced",
|
|
modify: func(p *TransportParameters) {
|
|
p.InitialMaxStreamDataBidiLocal = saved.InitialMaxStreamDataBidiLocal - 1
|
|
},
|
|
valid: false,
|
|
},
|
|
{
|
|
name: "InitialMaxStreamDataBidiLocal increased",
|
|
modify: func(p *TransportParameters) {
|
|
p.InitialMaxStreamDataBidiLocal = saved.InitialMaxStreamDataBidiLocal + 1
|
|
},
|
|
valid: true,
|
|
},
|
|
{
|
|
name: "InitialMaxStreamDataBidiRemote reduced",
|
|
modify: func(p *TransportParameters) {
|
|
p.InitialMaxStreamDataBidiRemote = saved.InitialMaxStreamDataBidiRemote - 1
|
|
},
|
|
valid: false,
|
|
},
|
|
{
|
|
name: "InitialMaxStreamDataBidiRemote increased",
|
|
modify: func(p *TransportParameters) {
|
|
p.InitialMaxStreamDataBidiRemote = saved.InitialMaxStreamDataBidiRemote + 1
|
|
},
|
|
valid: true,
|
|
},
|
|
{
|
|
name: "InitialMaxStreamDataUni reduced",
|
|
modify: func(p *TransportParameters) { p.InitialMaxStreamDataUni = saved.InitialMaxStreamDataUni - 1 },
|
|
valid: false,
|
|
},
|
|
{
|
|
name: "InitialMaxStreamDataUni increased",
|
|
modify: func(p *TransportParameters) { p.InitialMaxStreamDataUni = saved.InitialMaxStreamDataUni + 1 },
|
|
valid: true,
|
|
},
|
|
{
|
|
name: "InitialMaxData reduced",
|
|
modify: func(p *TransportParameters) { p.InitialMaxData = saved.InitialMaxData - 1 },
|
|
valid: false,
|
|
},
|
|
{
|
|
name: "InitialMaxData increased",
|
|
modify: func(p *TransportParameters) { p.InitialMaxData = saved.InitialMaxData + 1 },
|
|
valid: true,
|
|
},
|
|
{
|
|
name: "MaxBidiStreamNum reduced",
|
|
modify: func(p *TransportParameters) { p.MaxBidiStreamNum = saved.MaxBidiStreamNum - 1 },
|
|
valid: false,
|
|
},
|
|
{
|
|
name: "MaxBidiStreamNum increased",
|
|
modify: func(p *TransportParameters) { p.MaxBidiStreamNum = saved.MaxBidiStreamNum + 1 },
|
|
valid: true,
|
|
},
|
|
{
|
|
name: "MaxUniStreamNum reduced",
|
|
modify: func(p *TransportParameters) { p.MaxUniStreamNum = saved.MaxUniStreamNum - 1 },
|
|
valid: false,
|
|
},
|
|
{
|
|
name: "MaxUniStreamNum increased",
|
|
modify: func(p *TransportParameters) { p.MaxUniStreamNum = saved.MaxUniStreamNum + 1 },
|
|
valid: true,
|
|
},
|
|
{
|
|
name: "ActiveConnectionIDLimit changed",
|
|
modify: func(p *TransportParameters) { p.ActiveConnectionIDLimit = 0 },
|
|
valid: false,
|
|
},
|
|
{
|
|
name: "MaxDatagramFrameSize increased",
|
|
modify: func(p *TransportParameters) { p.MaxDatagramFrameSize = saved.MaxDatagramFrameSize + 1 },
|
|
valid: true,
|
|
},
|
|
{
|
|
name: "MaxDatagramFrameSize reduced",
|
|
modify: func(p *TransportParameters) { p.MaxDatagramFrameSize = saved.MaxDatagramFrameSize - 1 },
|
|
valid: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
p := *saved
|
|
tt.modify(&p)
|
|
require.Equal(t, tt.valid, p.ValidFor0RTT(saved))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTransportParametersValidAfter0RTT(t *testing.T) {
|
|
saved := &TransportParameters{
|
|
InitialMaxStreamDataBidiLocal: 1,
|
|
InitialMaxStreamDataBidiRemote: 2,
|
|
InitialMaxStreamDataUni: 3,
|
|
InitialMaxData: 4,
|
|
MaxBidiStreamNum: 5,
|
|
MaxUniStreamNum: 6,
|
|
ActiveConnectionIDLimit: 7,
|
|
MaxDatagramFrameSize: 1000,
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
modify func(*TransportParameters)
|
|
reject bool
|
|
}{
|
|
{
|
|
name: "no changes",
|
|
modify: func(p *TransportParameters) {},
|
|
reject: false,
|
|
},
|
|
{
|
|
name: "InitialMaxStreamDataBidiLocal reduced",
|
|
modify: func(p *TransportParameters) {
|
|
p.InitialMaxStreamDataBidiLocal = saved.InitialMaxStreamDataBidiLocal - 1
|
|
},
|
|
reject: true,
|
|
},
|
|
{
|
|
name: "InitialMaxStreamDataBidiLocal increased",
|
|
modify: func(p *TransportParameters) {
|
|
p.InitialMaxStreamDataBidiLocal = saved.InitialMaxStreamDataBidiLocal + 1
|
|
},
|
|
reject: false,
|
|
},
|
|
{
|
|
name: "InitialMaxStreamDataBidiRemote reduced",
|
|
modify: func(p *TransportParameters) {
|
|
p.InitialMaxStreamDataBidiRemote = saved.InitialMaxStreamDataBidiRemote - 1
|
|
},
|
|
reject: true,
|
|
},
|
|
{
|
|
name: "InitialMaxStreamDataBidiRemote increased",
|
|
modify: func(p *TransportParameters) {
|
|
p.InitialMaxStreamDataBidiRemote = saved.InitialMaxStreamDataBidiRemote + 1
|
|
},
|
|
reject: false,
|
|
},
|
|
{
|
|
name: "InitialMaxStreamDataUni reduced",
|
|
modify: func(p *TransportParameters) { p.InitialMaxStreamDataUni = saved.InitialMaxStreamDataUni - 1 },
|
|
reject: true,
|
|
},
|
|
{
|
|
name: "InitialMaxStreamDataUni increased",
|
|
modify: func(p *TransportParameters) { p.InitialMaxStreamDataUni = saved.InitialMaxStreamDataUni + 1 },
|
|
reject: false,
|
|
},
|
|
{
|
|
name: "InitialMaxData reduced",
|
|
modify: func(p *TransportParameters) { p.InitialMaxData = saved.InitialMaxData - 1 },
|
|
reject: true,
|
|
},
|
|
{
|
|
name: "InitialMaxData increased",
|
|
modify: func(p *TransportParameters) { p.InitialMaxData = saved.InitialMaxData + 1 },
|
|
reject: false,
|
|
},
|
|
{
|
|
name: "MaxBidiStreamNum reduced",
|
|
modify: func(p *TransportParameters) { p.MaxBidiStreamNum = saved.MaxBidiStreamNum - 1 },
|
|
reject: true,
|
|
},
|
|
{
|
|
name: "MaxBidiStreamNum increased",
|
|
modify: func(p *TransportParameters) { p.MaxBidiStreamNum = saved.MaxBidiStreamNum + 1 },
|
|
reject: false,
|
|
},
|
|
{
|
|
name: "MaxUniStreamNum reduced",
|
|
modify: func(p *TransportParameters) { p.MaxUniStreamNum = saved.MaxUniStreamNum - 1 },
|
|
reject: true,
|
|
},
|
|
{
|
|
name: "MaxUniStreamNum increased",
|
|
modify: func(p *TransportParameters) { p.MaxUniStreamNum = saved.MaxUniStreamNum + 1 },
|
|
reject: false,
|
|
},
|
|
{
|
|
name: "ActiveConnectionIDLimit reduced",
|
|
modify: func(p *TransportParameters) { p.ActiveConnectionIDLimit = saved.ActiveConnectionIDLimit - 1 },
|
|
reject: true,
|
|
},
|
|
{
|
|
name: "ActiveConnectionIDLimit increased",
|
|
modify: func(p *TransportParameters) { p.ActiveConnectionIDLimit = saved.ActiveConnectionIDLimit + 1 },
|
|
reject: false,
|
|
},
|
|
{
|
|
name: "MaxDatagramFrameSize reduced",
|
|
modify: func(p *TransportParameters) { p.MaxDatagramFrameSize = saved.MaxDatagramFrameSize - 1 },
|
|
reject: true,
|
|
},
|
|
{
|
|
name: "MaxDatagramFrameSize increased",
|
|
modify: func(p *TransportParameters) { p.MaxDatagramFrameSize = saved.MaxDatagramFrameSize + 1 },
|
|
reject: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
p := *saved
|
|
tt.modify(&p)
|
|
if tt.reject {
|
|
require.False(t, p.ValidForUpdate(saved))
|
|
} else {
|
|
require.True(t, p.ValidForUpdate(saved))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func BenchmarkTransportParameters(b *testing.B) {
|
|
b.Run("without preferred address", func(b *testing.B) { benchmarkTransportParameters(b, false) })
|
|
b.Run("with preferred address", func(b *testing.B) { benchmarkTransportParameters(b, true) })
|
|
}
|
|
|
|
func benchmarkTransportParameters(b *testing.B, withPreferredAddress bool) {
|
|
var token protocol.StatelessResetToken
|
|
rand.Read(token[:])
|
|
rcid := protocol.ParseConnectionID([]byte{0xde, 0xad, 0xc0, 0xde})
|
|
params := &TransportParameters{
|
|
InitialMaxStreamDataBidiLocal: protocol.ByteCount(getRandomValue()),
|
|
InitialMaxStreamDataBidiRemote: protocol.ByteCount(getRandomValue()),
|
|
InitialMaxStreamDataUni: protocol.ByteCount(getRandomValue()),
|
|
InitialMaxData: protocol.ByteCount(getRandomValue()),
|
|
MaxIdleTimeout: 0xcafe * time.Second,
|
|
MaxBidiStreamNum: protocol.StreamNum(getRandomValueUpTo(int64(protocol.MaxStreamCount))),
|
|
MaxUniStreamNum: protocol.StreamNum(getRandomValueUpTo(int64(protocol.MaxStreamCount))),
|
|
DisableActiveMigration: true,
|
|
StatelessResetToken: &token,
|
|
OriginalDestinationConnectionID: protocol.ParseConnectionID([]byte{0xde, 0xad, 0xbe, 0xef}),
|
|
InitialSourceConnectionID: protocol.ParseConnectionID([]byte{0xde, 0xca, 0xfb, 0xad}),
|
|
RetrySourceConnectionID: &rcid,
|
|
AckDelayExponent: 13,
|
|
MaxAckDelay: 42 * time.Millisecond,
|
|
ActiveConnectionIDLimit: 2 + getRandomValueUpTo(quicvarint.Max-2),
|
|
MaxDatagramFrameSize: protocol.ByteCount(getRandomValue()),
|
|
}
|
|
var token2 protocol.StatelessResetToken
|
|
rand.Read(token2[:])
|
|
if withPreferredAddress {
|
|
var ip4 [4]byte
|
|
var ip6 [16]byte
|
|
rand.Read(ip4[:])
|
|
rand.Read(ip6[:])
|
|
params.PreferredAddress = &PreferredAddress{
|
|
IPv4: netip.AddrPortFrom(netip.AddrFrom4(ip4), 1234),
|
|
IPv6: netip.AddrPortFrom(netip.AddrFrom16(ip6), 4321),
|
|
ConnectionID: protocol.ParseConnectionID([]byte{0xde, 0xad, 0xbe, 0xef}),
|
|
StatelessResetToken: token2,
|
|
}
|
|
}
|
|
data := params.Marshal(protocol.PerspectiveServer)
|
|
|
|
b.ResetTimer()
|
|
b.ReportAllocs()
|
|
var p TransportParameters
|
|
for i := 0; i < b.N; i++ {
|
|
if err := p.Unmarshal(data, protocol.PerspectiveServer); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
// check a few fields
|
|
if p.DisableActiveMigration != params.DisableActiveMigration ||
|
|
p.InitialMaxStreamDataBidiLocal != params.InitialMaxStreamDataBidiLocal ||
|
|
*p.StatelessResetToken != *params.StatelessResetToken ||
|
|
p.AckDelayExponent != params.AckDelayExponent {
|
|
b.Fatalf("params mismatch: %v vs %v", p, params)
|
|
}
|
|
if withPreferredAddress && *p.PreferredAddress != *params.PreferredAddress {
|
|
b.Fatalf("preferred address mismatch: %v vs %v", p.PreferredAddress, params.PreferredAddress)
|
|
}
|
|
}
|
|
}
|