func TestTransportParametersStringRepresentation(t *testing.T) {
rcid := protocol.ParseConnectionID([]byte{0xde, 0xad, 0xc0, 0xde})
+ minAckDelay := 42 * time.Millisecond
p := &TransportParameters{
InitialMaxStreamDataBidiLocal: 1234,
InitialMaxStreamDataBidiRemote: 2345,
ActiveConnectionIDLimit: 123,
MaxDatagramFrameSize: 876,
EnableResetStreamAt: true,
+ MinAckDelay: &minAckDelay,
}
- 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, EnableResetStreamAt: true}"
+ 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, EnableResetStreamAt: true, MinAckDelay: 42ms}"
require.Equal(t, expected, p.String())
}
var token protocol.StatelessResetToken
rand.Read(token[:])
rcid := protocol.ParseConnectionID([]byte{0xde, 0xad, 0xc0, 0xde})
+ minAckDelay := 42 * time.Millisecond
params := &TransportParameters{
InitialMaxStreamDataBidiLocal: protocol.ByteCount(getRandomValue()),
InitialMaxStreamDataBidiRemote: protocol.ByteCount(getRandomValue()),
MaxUDPPayloadSize: 1200 + protocol.ByteCount(getRandomValueUpTo(quicvarint.Max-1200)),
MaxDatagramFrameSize: protocol.ByteCount(getRandomValue()),
EnableResetStreamAt: getRandomValue()%2 == 0,
+ MinAckDelay: &minAckDelay,
}
data := params.Marshal(protocol.PerspectiveServer)
require.Equal(t, params.MaxUDPPayloadSize, p.MaxUDPPayloadSize)
require.Equal(t, params.MaxDatagramFrameSize, p.MaxDatagramFrameSize)
require.Equal(t, params.EnableResetStreamAt, p.EnableResetStreamAt)
+ require.NotNil(t, p.MinAckDelay)
+ require.Equal(t, minAckDelay, *p.MinAckDelay)
}
func TestMarshalAdditionalTransportParameters(t *testing.T) {
origAdditionalTransportParametersClient := AdditionalTransportParametersClient
- t.Cleanup(func() { AdditionalTransportParametersClient = origAdditionalTransportParametersClient })
+ t.Cleanup(func() {
+ AdditionalTransportParametersClient = origAdditionalTransportParametersClient
+ })
AdditionalTransportParametersClient = map[uint64][]byte{1337: []byte("foobar")}
result := quicvarint.Append([]byte{}, 1337)
require.False(t, bytes.Contains(params.Marshal(protocol.PerspectiveServer), result))
}
-func TestMarshalWithoutRetrySourceConnectionID(t *testing.T) {
+func TestMarshalRetrySourceConnectionID(t *testing.T) {
+ // no retry source connection ID
data := (&TransportParameters{
StatelessResetToken: &protocol.StatelessResetToken{},
ActiveConnectionIDLimit: 2,
}).Marshal(protocol.PerspectiveServer)
- p := &TransportParameters{}
+ var p TransportParameters
require.NoError(t, p.Unmarshal(data, protocol.PerspectiveServer))
require.Nil(t, p.RetrySourceConnectionID)
-}
-func TestMarshalZeroLengthRetrySourceConnectionID(t *testing.T) {
+ // zero-length retry source connection ID
rcid := protocol.ParseConnectionID([]byte{})
- data := (&TransportParameters{
+ data = (&TransportParameters{
RetrySourceConnectionID: &rcid,
StatelessResetToken: &protocol.StatelessResetToken{},
ActiveConnectionIDLimit: 2,
}).Marshal(protocol.PerspectiveServer)
- p := &TransportParameters{}
+ p = TransportParameters{}
require.NoError(t, p.Unmarshal(data, protocol.PerspectiveServer))
require.NotNil(t, p.RetrySourceConnectionID)
require.Zero(t, p.RetrySourceConnectionID.Len())
const num = 1000
var defaultLen, dataLen int
maxAckDelay := protocol.DefaultMaxAckDelay + time.Millisecond
- for i := 0; i < num; i++ {
+ for range num {
dataDefault := (&TransportParameters{
MaxAckDelay: protocol.DefaultMaxAckDelay,
StatelessResetToken: &protocol.StatelessResetToken{},
func TestTransportParameterNoAckDelayExponentIfDefault(t *testing.T) {
const num = 1000
var defaultLen, dataLen int
- for i := 0; i < num; i++ {
+ for range num {
dataDefault := (&TransportParameters{
AckDelayExponent: protocol.DefaultAckDelayExponent,
StatelessResetToken: &protocol.StatelessResetToken{},
perspective: protocol.PerspectiveClient,
expectedErrMsg: "wrong length for reset_stream_at: 1 (expected empty)",
},
+ {
+ name: "min ack delay is greater than max ack delay",
+ data: func() []byte {
+ b := quicvarint.Append(nil, uint64(minAckDelayParameterID))
+ b = quicvarint.Append(b, uint64(quicvarint.Len(42001)))
+ b = quicvarint.Append(b, 42001) // 42001 microseconds
+ b = quicvarint.Append(b, uint64(maxAckDelayParameterID))
+ b = quicvarint.Append(b, uint64(quicvarint.Len(42)))
+ b = quicvarint.Append(b, 42) // 42 microseconds
+ return appendInitialSourceConnectionID(b)
+ }(),
+ perspective: protocol.PerspectiveClient,
+ expectedErrMsg: "min_ack_delay (42.001ms) is greater than max_ack_delay (42ms)",
+ },
+ {
+ name: "huge min ack delay value",
+ data: func() []byte {
+ b := quicvarint.Append(nil, uint64(minAckDelayParameterID))
+ b = quicvarint.Append(b, uint64(quicvarint.Len(quicvarint.Max)))
+ b = quicvarint.Append(b, quicvarint.Max)
+ b = quicvarint.Append(b, uint64(maxAckDelayParameterID))
+ b = quicvarint.Append(b, uint64(quicvarint.Len(42)))
+ b = quicvarint.Append(b, 42) // 42 microseconds
+ return appendInitialSourceConnectionID(b)
+ }(),
+ perspective: protocol.PerspectiveClient,
+ expectedErrMsg: "min_ack_delay (2562047h47m16.854775807s) is greater than max_ack_delay (42ms)",
+ },
}
for _, tt := range tests {
"errors"
"fmt"
"io"
+ "math"
"net/netip"
"slices"
"time"
maxDatagramFrameSizeParameterID transportParameterID = 0x20
// https://datatracker.ietf.org/doc/draft-ietf-quic-reliable-stream-reset/06/
resetStreamAtParameterID transportParameterID = 0x17f7586d2cb571
+ // https://datatracker.ietf.org/doc/draft-ietf-quic-ack-frequency/11/
+ minAckDelayParameterID transportParameterID = 0xff04de1b
)
// PreferredAddress is the value encoding in the preferred_address transport parameter
MaxDatagramFrameSize protocol.ByteCount // RFC 9221
EnableResetStreamAt bool // https://datatracker.ietf.org/doc/draft-ietf-quic-reliable-stream-reset/06/
+ MinAckDelay *time.Duration
}
// Unmarshal the transport parameters
var (
readOriginalDestinationConnectionID bool
readInitialSourceConnectionID bool
- readActiveConnectionIDLimit bool
)
p.AckDelayExponent = protocol.DefaultAckDelayExponent
p.MaxAckDelay = protocol.DefaultMaxAckDelay
p.MaxDatagramFrameSize = protocol.InvalidByteCount
+ p.ActiveConnectionIDLimit = protocol.DefaultActiveConnectionIDLimit
for len(b) > 0 {
paramIDInt, l, err := quicvarint.Parse(b)
}
parameterIDs = append(parameterIDs, paramID)
switch paramID {
- case activeConnectionIDLimitParameterID:
- readActiveConnectionIDLimit = true
- fallthrough
case maxIdleTimeoutParameterID,
maxUDPPayloadSizeParameterID,
initialMaxDataParameterID,
initialMaxStreamsUniParameterID,
maxAckDelayParameterID,
maxDatagramFrameSizeParameterID,
- ackDelayExponentParameterID:
+ ackDelayExponentParameterID,
+ activeConnectionIDLimitParameterID,
+ minAckDelayParameterID:
if err := p.readNumericTransportParameter(b, paramID, int(paramLen)); err != nil {
return err
}
}
}
- if !readActiveConnectionIDLimit {
- p.ActiveConnectionIDLimit = protocol.DefaultActiveConnectionIDLimit
+ // min_ack_delay must be less or equal to max_ack_delay
+ if p.MinAckDelay != nil && *p.MinAckDelay > p.MaxAckDelay {
+ return fmt.Errorf("min_ack_delay (%s) is greater than max_ack_delay (%s)", *p.MinAckDelay, p.MaxAckDelay)
}
if !fromSessionTicket {
if sentBy == protocol.PerspectiveServer && !readOriginalDestinationConnectionID {
p.ActiveConnectionIDLimit = val
case maxDatagramFrameSizeParameterID:
p.MaxDatagramFrameSize = protocol.ByteCount(val)
+ case minAckDelayParameterID:
+ mad := time.Duration(val) * time.Microsecond
+ if mad < 0 {
+ mad = math.MaxInt64
+ }
+ p.MinAckDelay = &mad
default:
return fmt.Errorf("TransportParameter BUG: transport parameter %d not found", paramID)
}
b = quicvarint.Append(b, uint64(resetStreamAtParameterID))
b = quicvarint.Append(b, 0)
}
+ if p.MinAckDelay != nil {
+ b = p.marshalVarintParam(b, minAckDelayParameterID, uint64(*p.MinAckDelay/time.Microsecond))
+ }
if pers == protocol.PerspectiveClient && len(AdditionalTransportParametersClient) > 0 {
for k, v := range AdditionalTransportParametersClient {
}
logString += ", EnableResetStreamAt: %t"
logParams = append(logParams, p.EnableResetStreamAt)
+ if p.MinAckDelay != nil {
+ logString += ", MinAckDelay: %s"
+ logParams = append(logParams, *p.MinAckDelay)
+ }
logString += "}"
return fmt.Sprintf(logString, logParams...)
}