wire: add support for the reset_stream_at transport parameter (#5158)

This commit is contained in:
Marten Seemann
2025-05-28 12:30:02 +08:00
committed by GitHub
parent cb5c3f2aad
commit 5d7ee61f5f
2 changed files with 45 additions and 7 deletions

View File

@@ -48,8 +48,9 @@ func TestTransportParametersStringRepresentation(t *testing.T) {
StatelessResetToken: &protocol.StatelessResetToken{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00},
ActiveConnectionIDLimit: 123,
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}"
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}"
require.Equal(t, expected, p.String())
}
@@ -69,7 +70,7 @@ func TestTransportParametersStringRepresentationWithoutOptionalFields(t *testing
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}"
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, EnableResetStreamAt: false}"
require.Equal(t, expected, p.String())
}
@@ -95,6 +96,7 @@ func TestMarshalAndUnmarshalTransportParameters(t *testing.T) {
ActiveConnectionIDLimit: 2 + getRandomValueUpTo(quicvarint.Max-2),
MaxUDPPayloadSize: 1200 + protocol.ByteCount(getRandomValueUpTo(quicvarint.Max-1200)),
MaxDatagramFrameSize: protocol.ByteCount(getRandomValue()),
EnableResetStreamAt: getRandomValue()%2 == 0,
}
data := params.Marshal(protocol.PerspectiveServer)
@@ -117,6 +119,7 @@ func TestMarshalAndUnmarshalTransportParameters(t *testing.T) {
require.Equal(t, params.ActiveConnectionIDLimit, p.ActiveConnectionIDLimit)
require.Equal(t, params.MaxUDPPayloadSize, p.MaxUDPPayloadSize)
require.Equal(t, params.MaxDatagramFrameSize, p.MaxDatagramFrameSize)
require.Equal(t, params.EnableResetStreamAt, p.EnableResetStreamAt)
}
func TestMarshalAdditionalTransportParameters(t *testing.T) {
@@ -369,6 +372,17 @@ func TestTransportParameterErrors(t *testing.T) {
perspective: protocol.PerspectiveClient,
expectedErrMsg: "invalid value for max_ack_delay: 3689348814741910323ms (maximum 16383ms)",
},
{
name: "invalid value for reset_stream_at",
data: func() []byte {
b := quicvarint.Append(nil, uint64(resetStreamAtParameterID))
b = quicvarint.Append(b, 1)
b = quicvarint.Append(b, 1)
return appendInitialSourceConnectionID(b)
}(),
perspective: protocol.PerspectiveClient,
expectedErrMsg: "wrong length for reset_stream_at: 1 (expected empty)",
},
}
for _, tt := range tests {
@@ -550,6 +564,7 @@ func TestTransportParametersFromSessionTicket(t *testing.T) {
MaxUniStreamNum: protocol.StreamNum(getRandomValueUpTo(uint64(protocol.MaxStreamCount))),
ActiveConnectionIDLimit: 2 + getRandomValueUpTo(quicvarint.Max-2),
MaxDatagramFrameSize: protocol.ByteCount(getRandomValueUpTo(uint64(MaxDatagramSize))),
EnableResetStreamAt: getRandomValue()%2 == 0,
}
require.True(t, params.ValidFor0RTT(params))
b := params.MarshalForSessionTicket(nil)
@@ -563,6 +578,7 @@ func TestTransportParametersFromSessionTicket(t *testing.T) {
require.Equal(t, params.MaxUniStreamNum, tp.MaxUniStreamNum)
require.Equal(t, params.ActiveConnectionIDLimit, tp.ActiveConnectionIDLimit)
require.Equal(t, params.MaxDatagramFrameSize, tp.MaxDatagramFrameSize)
require.Equal(t, params.EnableResetStreamAt, tp.EnableResetStreamAt)
}
func TestSessionTicketInvalidTransportParameters(t *testing.T) {

View File

@@ -45,6 +45,8 @@ const (
retrySourceConnectionIDParameterID transportParameterID = 0x10
// RFC 9221
maxDatagramFrameSizeParameterID transportParameterID = 0x20
// https://datatracker.ietf.org/doc/draft-ietf-quic-reliable-stream-reset/06/
resetStreamAtParameterID transportParameterID = 0x17f7586d2cb571
)
// PreferredAddress is the value encoding in the preferred_address transport parameter
@@ -82,7 +84,8 @@ type TransportParameters struct {
StatelessResetToken *protocol.StatelessResetToken
ActiveConnectionIDLimit uint64
MaxDatagramFrameSize protocol.ByteCount
MaxDatagramFrameSize protocol.ByteCount // RFC 9221
EnableResetStreamAt bool // https://datatracker.ietf.org/doc/draft-ietf-quic-reliable-stream-reset/06/
}
// Unmarshal the transport parameters
@@ -199,6 +202,11 @@ func (p *TransportParameters) unmarshal(b []byte, sentBy protocol.Perspective, f
connID := protocol.ParseConnectionID(b[:paramLen])
b = b[paramLen:]
p.RetrySourceConnectionID = &connID
case resetStreamAtParameterID:
if paramLen != 0 {
return fmt.Errorf("wrong length for reset_stream_at: %d (expected empty)", paramLen)
}
p.EnableResetStreamAt = true
default:
b = b[paramLen:]
}
@@ -428,9 +436,15 @@ func (p *TransportParameters) Marshal(pers protocol.Perspective) []byte {
b = quicvarint.Append(b, uint64(p.RetrySourceConnectionID.Len()))
b = append(b, p.RetrySourceConnectionID.Bytes()...)
}
// QUIC datagrams
if p.MaxDatagramFrameSize != protocol.InvalidByteCount {
b = p.marshalVarintParam(b, maxDatagramFrameSizeParameterID, uint64(p.MaxDatagramFrameSize))
}
// QUIC Stream Resets with Partial Delivery
if p.EnableResetStreamAt {
b = quicvarint.Append(b, uint64(resetStreamAtParameterID))
b = quicvarint.Append(b, 0)
}
if pers == protocol.PerspectiveClient && len(AdditionalTransportParametersClient) > 0 {
for k, v := range AdditionalTransportParametersClient {
@@ -472,12 +486,18 @@ func (p *TransportParameters) MarshalForSessionTicket(b []byte) []byte {
b = p.marshalVarintParam(b, initialMaxStreamsBidiParameterID, uint64(p.MaxBidiStreamNum))
// initial_max_uni_streams
b = p.marshalVarintParam(b, initialMaxStreamsUniParameterID, uint64(p.MaxUniStreamNum))
// active_connection_id_limit
b = p.marshalVarintParam(b, activeConnectionIDLimitParameterID, p.ActiveConnectionIDLimit)
// max_datagram_frame_size
if p.MaxDatagramFrameSize != protocol.InvalidByteCount {
b = p.marshalVarintParam(b, maxDatagramFrameSizeParameterID, uint64(p.MaxDatagramFrameSize))
}
// active_connection_id_limit
return p.marshalVarintParam(b, activeConnectionIDLimitParameterID, p.ActiveConnectionIDLimit)
// reset_stream_at
if p.EnableResetStreamAt {
b = quicvarint.Append(b, uint64(resetStreamAtParameterID))
b = quicvarint.Append(b, 0)
}
return b
}
// UnmarshalFromSessionTicket unmarshals transport parameters from a session ticket.
@@ -524,13 +544,13 @@ func (p *TransportParameters) ValidForUpdate(saved *TransportParameters) bool {
// String returns a string representation, intended for logging.
func (p *TransportParameters) String() string {
logString := "&wire.TransportParameters{OriginalDestinationConnectionID: %s, InitialSourceConnectionID: %s, "
logParams := []interface{}{p.OriginalDestinationConnectionID, p.InitialSourceConnectionID}
logParams := []any{p.OriginalDestinationConnectionID, p.InitialSourceConnectionID}
if p.RetrySourceConnectionID != nil {
logString += "RetrySourceConnectionID: %s, "
logParams = append(logParams, p.RetrySourceConnectionID)
}
logString += "InitialMaxStreamDataBidiLocal: %d, InitialMaxStreamDataBidiRemote: %d, InitialMaxStreamDataUni: %d, InitialMaxData: %d, MaxBidiStreamNum: %d, MaxUniStreamNum: %d, MaxIdleTimeout: %s, AckDelayExponent: %d, MaxAckDelay: %s, ActiveConnectionIDLimit: %d"
logParams = append(logParams, []interface{}{p.InitialMaxStreamDataBidiLocal, p.InitialMaxStreamDataBidiRemote, p.InitialMaxStreamDataUni, p.InitialMaxData, p.MaxBidiStreamNum, p.MaxUniStreamNum, p.MaxIdleTimeout, p.AckDelayExponent, p.MaxAckDelay, p.ActiveConnectionIDLimit}...)
logParams = append(logParams, []any{p.InitialMaxStreamDataBidiLocal, p.InitialMaxStreamDataBidiRemote, p.InitialMaxStreamDataUni, p.InitialMaxData, p.MaxBidiStreamNum, p.MaxUniStreamNum, p.MaxIdleTimeout, p.AckDelayExponent, p.MaxAckDelay, p.ActiveConnectionIDLimit}...)
if p.StatelessResetToken != nil { // the client never sends a stateless reset token
logString += ", StatelessResetToken: %#x"
logParams = append(logParams, *p.StatelessResetToken)
@@ -539,6 +559,8 @@ func (p *TransportParameters) String() string {
logString += ", MaxDatagramFrameSize: %d"
logParams = append(logParams, p.MaxDatagramFrameSize)
}
logString += ", EnableResetStreamAt: %t"
logParams = append(logParams, p.EnableResetStreamAt)
logString += "}"
return fmt.Sprintf(logString, logParams...)
}