blob: 3f291e9f4c3628abd43dceb2b5ce133db433d9b4 [file] [log] [blame]
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.21
package quic
import (
"context"
"crypto/tls"
"testing"
)
func TestStreamLimitNewStreamBlocked(t *testing.T) {
// "An endpoint that receives a frame with a stream ID exceeding the limit
// it has sent MUST treat this as a connection error of type STREAM_LIMIT_ERROR [...]"
// https://www.rfc-editor.org/rfc/rfc9000#section-4.6-3
testStreamTypes(t, "", func(t *testing.T, styp streamType) {
ctx := canceledContext()
tc := newTestConn(t, clientSide,
permissiveTransportParameters,
func(p *transportParameters) {
p.initialMaxStreamsBidi = 0
p.initialMaxStreamsUni = 0
})
tc.handshake()
tc.ignoreFrame(frameTypeAck)
opening := runAsync(tc, func(ctx context.Context) (*Stream, error) {
return tc.conn.newLocalStream(ctx, styp)
})
if _, err := opening.result(); err != errNotDone {
t.Fatalf("new stream blocked by limit: %v, want errNotDone", err)
}
tc.writeFrames(packetType1RTT, debugFrameMaxStreams{
streamType: styp,
max: 1,
})
if _, err := opening.result(); err != nil {
t.Fatalf("new stream not created after limit raised: %v", err)
}
if _, err := tc.conn.newLocalStream(ctx, styp); err == nil {
t.Fatalf("new stream blocked by raised limit: %v, want error", err)
}
})
}
func TestStreamLimitMaxStreamsDecreases(t *testing.T) {
// "MAX_STREAMS frames that do not increase the stream limit MUST be ignored."
// https://www.rfc-editor.org/rfc/rfc9000#section-4.6-4
testStreamTypes(t, "", func(t *testing.T, styp streamType) {
ctx := canceledContext()
tc := newTestConn(t, clientSide,
permissiveTransportParameters,
func(p *transportParameters) {
p.initialMaxStreamsBidi = 0
p.initialMaxStreamsUni = 0
})
tc.handshake()
tc.ignoreFrame(frameTypeAck)
tc.writeFrames(packetType1RTT, debugFrameMaxStreams{
streamType: styp,
max: 2,
})
tc.writeFrames(packetType1RTT, debugFrameMaxStreams{
streamType: styp,
max: 1,
})
if _, err := tc.conn.newLocalStream(ctx, styp); err != nil {
t.Fatalf("open stream 1, limit 2, got error: %v", err)
}
if _, err := tc.conn.newLocalStream(ctx, styp); err != nil {
t.Fatalf("open stream 2, limit 2, got error: %v", err)
}
if _, err := tc.conn.newLocalStream(ctx, styp); err == nil {
t.Fatalf("open stream 3, limit 2, got error: %v", err)
}
})
}
func TestStreamLimitViolated(t *testing.T) {
testStreamTypes(t, "", func(t *testing.T, styp streamType) {
tc := newTestConn(t, serverSide,
func(c *Config) {
if styp == bidiStream {
c.MaxBidiRemoteStreams = 10
} else {
c.MaxUniRemoteStreams = 10
}
})
tc.handshake()
tc.ignoreFrame(frameTypeAck)
tc.writeFrames(packetType1RTT, debugFrameStream{
id: newStreamID(clientSide, styp, 9),
})
tc.wantIdle("stream number 9 is within the limit")
tc.writeFrames(packetType1RTT, debugFrameStream{
id: newStreamID(clientSide, styp, 10),
})
tc.wantFrame("stream number 10 is beyond the limit",
packetType1RTT, debugFrameConnectionCloseTransport{
code: errStreamLimit,
},
)
})
}
func TestStreamLimitImplicitStreams(t *testing.T) {
testStreamTypes(t, "", func(t *testing.T, styp streamType) {
tc := newTestConn(t, serverSide,
func(c *Config) {
c.MaxBidiRemoteStreams = 1 << 60
c.MaxUniRemoteStreams = 1 << 60
})
tc.handshake()
tc.ignoreFrame(frameTypeAck)
if got, want := tc.sentTransportParameters.initialMaxStreamsBidi, int64(implicitStreamLimit); got != want {
t.Errorf("sent initial_max_streams_bidi = %v, want %v", got, want)
}
if got, want := tc.sentTransportParameters.initialMaxStreamsUni, int64(implicitStreamLimit); got != want {
t.Errorf("sent initial_max_streams_uni = %v, want %v", got, want)
}
// Create stream 0.
tc.writeFrames(packetType1RTT, debugFrameStream{
id: newStreamID(clientSide, styp, 0),
})
tc.wantIdle("max streams not increased enough to send a new frame")
// Create streams [0, implicitStreamLimit).
tc.writeFrames(packetType1RTT, debugFrameStream{
id: newStreamID(clientSide, styp, implicitStreamLimit-1),
})
tc.wantFrame("max streams increases to implicit stream limit",
packetType1RTT, debugFrameMaxStreams{
streamType: styp,
max: 2 * implicitStreamLimit,
})
// Create a stream past the limit.
tc.writeFrames(packetType1RTT, debugFrameStream{
id: newStreamID(clientSide, styp, 2*implicitStreamLimit),
})
tc.wantFrame("stream is past the limit",
packetType1RTT, debugFrameConnectionCloseTransport{
code: errStreamLimit,
},
)
})
}
func TestStreamLimitMaxStreamsTransportParameterTooLarge(t *testing.T) {
// "If a max_streams transport parameter [...] is received with
// a value greater than 2^60 [...] the connection MUST be closed
// immediately with a connection error of type TRANSPORT_PARAMETER_ERROR [...]"
// https://www.rfc-editor.org/rfc/rfc9000#section-4.6-2
testStreamTypes(t, "", func(t *testing.T, styp streamType) {
tc := newTestConn(t, serverSide,
func(p *transportParameters) {
if styp == bidiStream {
p.initialMaxStreamsBidi = 1<<60 + 1
} else {
p.initialMaxStreamsUni = 1<<60 + 1
}
})
tc.writeFrames(packetTypeInitial, debugFrameCrypto{
data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial],
})
tc.wantFrame("max streams transport parameter is too large",
packetTypeInitial, debugFrameConnectionCloseTransport{
code: errTransportParameter,
},
)
})
}
func TestStreamLimitMaxStreamsFrameTooLarge(t *testing.T) {
// "If [...] a MAX_STREAMS frame is received with a value
// greater than 2^60 [...] the connection MUST be closed immediately
// with a connection error [...] of type FRAME_ENCODING_ERROR [...]"
// https://www.rfc-editor.org/rfc/rfc9000#section-4.6-2
testStreamTypes(t, "", func(t *testing.T, styp streamType) {
tc := newTestConn(t, serverSide)
tc.handshake()
tc.writeFrames(packetTypeInitial,
debugFrameCrypto{
data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial],
})
tc.writeFrames(packetType1RTT, debugFrameMaxStreams{
streamType: styp,
max: 1<<60 + 1,
})
tc.wantFrame("MAX_STREAMS value is too large",
packetType1RTT, debugFrameConnectionCloseTransport{
code: errFrameEncoding,
},
)
})
}
func TestStreamLimitSendUpdatesMaxStreams(t *testing.T) {
testStreamTypes(t, "", func(t *testing.T, styp streamType) {
ctx := canceledContext()
tc := newTestConn(t, serverSide, func(c *Config) {
if styp == uniStream {
c.MaxUniRemoteStreams = 4
c.MaxBidiRemoteStreams = 0
} else {
c.MaxUniRemoteStreams = 0
c.MaxBidiRemoteStreams = 4
}
})
tc.handshake()
tc.ignoreFrame(frameTypeAck)
var streams []*Stream
for i := 0; i < 4; i++ {
tc.writeFrames(packetType1RTT, debugFrameStream{
id: newStreamID(clientSide, styp, int64(i)),
fin: true,
})
s, err := tc.conn.AcceptStream(ctx)
if err != nil {
t.Fatalf("AcceptStream = %v", err)
}
streams = append(streams, s)
}
streams[3].CloseContext(ctx)
if styp == bidiStream {
tc.wantFrame("stream is closed",
packetType1RTT, debugFrameStream{
id: streams[3].id,
fin: true,
data: []byte{},
})
tc.writeAckForAll()
}
tc.wantFrame("closing a stream when peer is at limit immediately extends the limit",
packetType1RTT, debugFrameMaxStreams{
streamType: styp,
max: 5,
})
})
}
func TestStreamLimitStopSendingDoesNotUpdateMaxStreams(t *testing.T) {
tc, s := newTestConnAndRemoteStream(t, serverSide, bidiStream, func(c *Config) {
c.MaxBidiRemoteStreams = 1
})
tc.writeFrames(packetType1RTT, debugFrameStream{
id: s.id,
fin: true,
})
s.CloseRead()
tc.writeFrames(packetType1RTT, debugFrameStopSending{
id: s.id,
})
tc.wantFrame("recieved STOP_SENDING, send RESET_STREAM",
packetType1RTT, debugFrameResetStream{
id: s.id,
})
tc.writeAckForAll()
tc.wantIdle("MAX_STREAMS is not extended until the user fully closes the stream")
s.CloseWrite()
tc.wantFrame("user closing the stream triggers MAX_STREAMS update",
packetType1RTT, debugFrameMaxStreams{
streamType: bidiStream,
max: 2,
})
}