blob: 518ec6f844e23c262950d3eee0b4dc7e0c879fc7 [file] [log] [blame]
// Copyright (c) 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/third_party/quic/core/quic_stream_id_manager.h"
#include <cstdint>
#include <set>
#include <utility>
#include "net/third_party/quic/core/crypto/crypto_protocol.h"
#include "net/third_party/quic/core/crypto/null_encrypter.h"
#include "net/third_party/quic/core/quic_crypto_stream.h"
#include "net/third_party/quic/core/quic_data_writer.h"
#include "net/third_party/quic/core/quic_packets.h"
#include "net/third_party/quic/core/quic_session.h"
#include "net/third_party/quic/core/quic_stream.h"
#include "net/third_party/quic/core/quic_utils.h"
#include "net/third_party/quic/platform/api/quic_arraysize.h"
#include "net/third_party/quic/platform/api/quic_expect_bug.h"
#include "net/third_party/quic/platform/api/quic_flags.h"
#include "net/third_party/quic/platform/api/quic_map_util.h"
#include "net/third_party/quic/platform/api/quic_ptr_util.h"
#include "net/third_party/quic/platform/api/quic_str_cat.h"
#include "net/third_party/quic/platform/api/quic_string.h"
#include "net/third_party/quic/platform/api/quic_string_piece.h"
#include "net/third_party/quic/platform/api/quic_test.h"
#include "net/third_party/quic/platform/api/quic_test_mem_slice_vector.h"
#include "net/third_party/quic/test_tools/quic_config_peer.h"
#include "net/third_party/quic/test_tools/quic_connection_peer.h"
#include "net/third_party/quic/test_tools/quic_flow_controller_peer.h"
#include "net/third_party/quic/test_tools/quic_session_peer.h"
#include "net/third_party/quic/test_tools/quic_stream_id_manager_peer.h"
#include "net/third_party/quic/test_tools/quic_stream_peer.h"
#include "net/third_party/quic/test_tools/quic_stream_send_buffer_peer.h"
#include "net/third_party/quic/test_tools/quic_test_utils.h"
using testing::_;
using testing::Invoke;
using testing::Return;
using testing::StrictMock;
namespace quic {
namespace test {
namespace {
class TestQuicStream : public QuicStream {
// TestQuicStream exists simply as a place to hang OnDataAvailable().
public:
TestQuicStream(QuicStreamId id, QuicSession* session, StreamType type)
: QuicStream(id, session, /*is_static=*/false, type) {}
void OnDataAvailable() override {}
};
class TestQuicSession : public MockQuicSession {
public:
TestQuicSession(QuicConnection* connection)
: MockQuicSession(connection, /*create_mock_crypto_stream=*/true) {
Initialize();
}
TestQuicStream* CreateIncomingStream(QuicStreamId id) override {
TestQuicStream* stream = new TestQuicStream(
id, this,
DetermineStreamType(id, connection()->transport_version(),
perspective(),
/*is_incoming=*/true, BIDIRECTIONAL));
ActivateStream(QuicWrapUnique(stream));
return stream;
}
bool SaveFrame(const QuicFrame& frame) {
save_frame_ = frame;
DeleteFrame(&const_cast<QuicFrame&>(frame));
return true;
}
const QuicFrame& save_frame() { return save_frame_; }
bool ClearControlFrame(const QuicFrame& frame) {
DeleteFrame(&const_cast<QuicFrame&>(frame));
return true;
}
TestQuicStream* CreateOutgoingBidirectionalStream() {
if (!CanOpenNextOutgoingBidirectionalStream()) {
return nullptr;
}
QuicStreamId id = GetNextOutgoingBidirectionalStreamId();
TestQuicStream* stream = new TestQuicStream(id, this, BIDIRECTIONAL);
ActivateStream(QuicWrapUnique(stream));
return stream;
}
TestQuicStream* CreateOutgoingUnidirectionalStream() {
if (!CanOpenNextOutgoingUnidirectionalStream()) {
return nullptr;
}
QuicStreamId id = GetNextOutgoingUnidirectionalStreamId();
TestQuicStream* stream = new TestQuicStream(id, this, WRITE_UNIDIRECTIONAL);
ActivateStream(QuicWrapUnique(stream));
return stream;
}
private:
QuicFrame save_frame_;
};
class QuicStreamIdManagerTestBase : public QuicTestWithParam<bool> {
protected:
explicit QuicStreamIdManagerTestBase(Perspective perspective)
: connection_(new StrictMock<MockQuicConnection>(
&helper_,
&alarm_factory_,
perspective,
ParsedQuicVersionVector(
{{PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_99}}))) {
connection_->AdvanceTime(QuicTime::Delta::FromSeconds(1));
session_ = QuicMakeUnique<TestQuicSession>(connection_);
stream_id_manager_ =
GetParam() ? QuicSessionPeer::v99_bidirectional_stream_id_manager(
session_.get())
: QuicSessionPeer::v99_unidirectional_stream_id_manager(
session_.get());
}
QuicTransportVersion transport_version() const {
return connection_->transport_version();
}
void CloseStream(QuicStreamId id) { session_->CloseStream(id); }
QuicStreamId GetNthClientInitiatedBidirectionalId(int n) {
return QuicUtils::GetFirstBidirectionalStreamId(
connection_->transport_version(), Perspective::IS_CLIENT) +
kV99StreamIdIncrement * n;
}
QuicStreamId GetNthClientInitiatedUnidirectionalId(int n) {
return QuicUtils::GetFirstUnidirectionalStreamId(
connection_->transport_version(), Perspective::IS_CLIENT) +
kV99StreamIdIncrement * n;
}
QuicStreamId GetNthServerInitiatedBidirectionalId(int n) {
return QuicUtils::GetFirstBidirectionalStreamId(
connection_->transport_version(), Perspective::IS_SERVER) +
kV99StreamIdIncrement * n;
}
QuicStreamId GetNthServerInitiatedUnidirectionalId(int n) {
return QuicUtils::GetFirstUnidirectionalStreamId(
connection_->transport_version(), Perspective::IS_SERVER) +
kV99StreamIdIncrement * n;
}
MockQuicConnectionHelper helper_;
MockAlarmFactory alarm_factory_;
StrictMock<MockQuicConnection>* connection_;
std::unique_ptr<TestQuicSession> session_;
QuicStreamIdManager* stream_id_manager_;
};
// Following tests are either client-specific (they depend, in some way, on
// client-specific attributes, such as the initial stream ID) or are
// server/client independent (arbitrarily all such tests have been placed here).
class QuicStreamIdManagerTestClient : public QuicStreamIdManagerTestBase {
protected:
QuicStreamIdManagerTestClient()
: QuicStreamIdManagerTestBase(Perspective::IS_CLIENT) {}
};
INSTANTIATE_TEST_SUITE_P(Tests, QuicStreamIdManagerTestClient, testing::Bool());
// Check that the parameters used by the stream ID manager are properly
// initialized.
TEST_P(QuicStreamIdManagerTestClient, StreamIdManagerClientInitialization) {
// These fields are inited via the QuicSession constructor to default
// values defined as a constant.
EXPECT_EQ(kDefaultMaxStreamsPerConnection,
stream_id_manager_->max_allowed_incoming_streams());
EXPECT_EQ(kDefaultMaxStreamsPerConnection,
stream_id_manager_->max_allowed_outgoing_streams());
// The window for advertising updates to the MAX STREAM ID is half the number
// of streams allowed.
EXPECT_EQ(kDefaultMaxStreamsPerConnection / kMaxStreamIdWindowDivisor,
stream_id_manager_->max_stream_id_window());
// This test runs as a client, so it initiates (that is to say, outgoing)
// even-numbered stream IDs. Also, our implementation starts allocating
// stream IDs at 0 (for clients) 1 (for servers) -- before taking statically
// allocated streams into account. The -1 in the calculation is
// because the value being tested is the maximum allowed stream ID, not the
// first unallowed stream id.
const QuicStreamId kExpectedMaxOutgoingStreamId =
(GetParam() ? session_->next_outgoing_bidirectional_stream_id()
: session_->next_outgoing_unidirectional_stream_id()) +
((kDefaultMaxStreamsPerConnection - 1) * kV99StreamIdIncrement);
EXPECT_EQ(kExpectedMaxOutgoingStreamId,
stream_id_manager_->max_allowed_outgoing_stream_id());
// Same for IDs of incoming streams...
const QuicStreamId kExpectedMaxIncomingStreamId =
stream_id_manager_->first_incoming_dynamic_stream_id() +
(kDefaultMaxStreamsPerConnection - 1) * kV99StreamIdIncrement;
EXPECT_EQ(kExpectedMaxIncomingStreamId,
stream_id_manager_->actual_max_allowed_incoming_stream_id());
EXPECT_EQ(kExpectedMaxIncomingStreamId,
stream_id_manager_->advertised_max_allowed_incoming_stream_id());
}
// This test checks that the initialization for the maximum allowed outgoing
// stream id is correct.
TEST_P(QuicStreamIdManagerTestClient, CheckMaxAllowedOutgoing) {
const size_t kNumOutgoingStreams = 124;
stream_id_manager_->SetMaxOpenOutgoingStreams(kNumOutgoingStreams);
EXPECT_EQ(kNumOutgoingStreams,
stream_id_manager_->max_allowed_outgoing_streams());
// Check that the maximum available stream is properly set.
size_t expected_max_outgoing_id =
(GetParam() ? session_->next_outgoing_bidirectional_stream_id()
: session_->next_outgoing_unidirectional_stream_id()) +
((kNumOutgoingStreams - 1) * kV99StreamIdIncrement);
EXPECT_EQ(expected_max_outgoing_id,
stream_id_manager_->max_allowed_outgoing_stream_id());
}
// This test checks that the initialization for the maximum allowed incoming
// stream id is correct.
TEST_P(QuicStreamIdManagerTestClient, CheckMaxAllowedIncoming) {
const size_t kStreamCount = 245;
stream_id_manager_->SetMaxOpenIncomingStreams(kStreamCount);
EXPECT_EQ(kStreamCount, stream_id_manager_->max_allowed_incoming_streams());
// Check that the window is 1/2 (integer math) of the stream count.
EXPECT_EQ(kStreamCount / 2, stream_id_manager_->max_stream_id_window());
// Actual- and advertised- maxima start out equal.
EXPECT_EQ(stream_id_manager_->actual_max_allowed_incoming_stream_id(),
stream_id_manager_->advertised_max_allowed_incoming_stream_id());
// Check that the maximum stream ID is properly calculated.
EXPECT_EQ(stream_id_manager_->first_incoming_dynamic_stream_id() +
((kStreamCount - 1) * kV99StreamIdIncrement),
stream_id_manager_->actual_max_allowed_incoming_stream_id());
}
// This test checks that the stream advertisement window is set to 1
// if the number of stream ids is 1. This is a special case in the code.
TEST_P(QuicStreamIdManagerTestClient, CheckMaxStreamIdWindow1) {
stream_id_manager_->SetMaxOpenIncomingStreams(1);
EXPECT_EQ(1u, stream_id_manager_->max_allowed_incoming_streams());
// If streamid_count/2==0 (integer math) force it to 1.
EXPECT_EQ(1u, stream_id_manager_->max_stream_id_window());
}
// Check the case of the stream ID in a STREAM_ID_BLOCKED frame is less than the
// stream ID most recently advertised in a MAX_STREAM_ID frame. This should
// cause a MAX_STREAM_ID frame with the most recently advertised stream id to be
// sent.
TEST_P(QuicStreamIdManagerTestClient, ProcessStreamIdBlockedOk) {
EXPECT_CALL(*connection_, SendControlFrame(_))
.WillRepeatedly(Invoke(session_.get(), &TestQuicSession::SaveFrame));
QuicStreamId stream_id =
stream_id_manager_->advertised_max_allowed_incoming_stream_id() -
kV99StreamIdIncrement;
QuicStreamIdBlockedFrame frame(0, stream_id);
session_->OnStreamIdBlockedFrame(frame);
// We should see a MAX_STREAM_ID frame.
EXPECT_EQ(MAX_STREAM_ID_FRAME, session_->save_frame().type);
// and it should advertise the current max-allowed value.
EXPECT_EQ(stream_id_manager_->actual_max_allowed_incoming_stream_id(),
session_->save_frame().max_stream_id_frame.max_stream_id);
}
// Check the case of the stream ID in a STREAM_ID_BLOCKED frame is equal to
// stream ID most recently advertised in a MAX_STREAM_ID frame. No
// MAX_STREAM_ID should be generated.
TEST_P(QuicStreamIdManagerTestClient, ProcessStreamIdBlockedNoOp) {
EXPECT_CALL(*connection_, SendControlFrame(_)).Times(0);
QuicStreamId stream_id =
stream_id_manager_->advertised_max_allowed_incoming_stream_id();
QuicStreamIdBlockedFrame frame(0, stream_id);
session_->OnStreamIdBlockedFrame(frame);
}
// Check the case of the stream ID in a STREAM_ID_BLOCKED frame is greater than
// the stream ID most recently advertised in a MAX_STREAM_ID frame. Expect a
// connection close with an error.
TEST_P(QuicStreamIdManagerTestClient, ProcessStreamIdBlockedTooBig) {
EXPECT_CALL(*connection_,
CloseConnection(QUIC_STREAM_ID_BLOCKED_ERROR, _, _));
EXPECT_CALL(*connection_, SendControlFrame(_)).Times(0);
QuicStreamId stream_id =
stream_id_manager_->advertised_max_allowed_incoming_stream_id() +
kV99StreamIdIncrement;
QuicStreamIdBlockedFrame frame(0, stream_id);
session_->OnStreamIdBlockedFrame(frame);
}
// Same basic tests as above, but calls
// QuicStreamIdManager::MaybeIncreaseLargestPeerStreamId directly, avoiding the
// call chain. The intent is that if there is a problem, the following tests
// will point to either the stream ID manager or the call chain. They also
// provide specific, small scale, tests of a public QuicStreamIdManager method.
// First test make sure that streams with ids below the limit are accepted.
TEST_P(QuicStreamIdManagerTestClient, IsIncomingStreamIdValidBelowLimit) {
QuicStreamId stream_id =
stream_id_manager_->actual_max_allowed_incoming_stream_id() -
kV99StreamIdIncrement;
EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(0);
EXPECT_TRUE(stream_id_manager_->MaybeIncreaseLargestPeerStreamId(stream_id));
}
// Accept a stream with an ID that equals the limit.
TEST_P(QuicStreamIdManagerTestClient, IsIncomingStreamIdValidAtLimit) {
QuicStreamId stream_id =
stream_id_manager_->actual_max_allowed_incoming_stream_id();
EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(0);
EXPECT_TRUE(stream_id_manager_->MaybeIncreaseLargestPeerStreamId(stream_id));
}
// Close the connection if the id exceeds the limit.
TEST_P(QuicStreamIdManagerTestClient, IsIncomingStreamIdInValidAboveLimit) {
QuicStreamId stream_id =
stream_id_manager_->actual_max_allowed_incoming_stream_id() +
kV99StreamIdIncrement;
QuicString error_details =
GetParam() ? "Stream id 401 above 397" : "Stream id 403 above 399";
EXPECT_CALL(*connection_,
CloseConnection(QUIC_INVALID_STREAM_ID, error_details, _));
EXPECT_FALSE(stream_id_manager_->MaybeIncreaseLargestPeerStreamId(stream_id));
}
// Test that a client will reject a MAX_STREAM_ID that specifies a
// server-initiated stream ID.
TEST_P(QuicStreamIdManagerTestClient, RejectServerMaxStreamId) {
QuicStreamId id = stream_id_manager_->max_allowed_outgoing_stream_id();
// Ensure that the ID that will be in the MAX_STREAM_ID is larger than the
// current MAX.
id += (kV99StreamIdIncrement * 2);
// Make it an odd (server-initiated) ID.
id |= 0x1;
EXPECT_FALSE(QuicUtils::IsClientInitiatedStreamId(QUIC_VERSION_99, id));
// Make the frame and process it; should result in the connection being
// closed.
QuicMaxStreamIdFrame frame(0, id);
EXPECT_CALL(*connection_, CloseConnection(QUIC_MAX_STREAM_ID_ERROR, _, _));
session_->OnMaxStreamIdFrame(frame);
}
// Test that a client will reject a STREAM_ID_BLOCKED that specifies a
// client-initiated stream ID. STREAM_ID_BLOCKED from a server should specify an
// odd (server-initiated_ ID). Generate one with an odd ID and check that the
// connection is closed.
TEST_P(QuicStreamIdManagerTestClient, RejectServerStreamIdBlocked) {
QuicStreamId id = stream_id_manager_->max_allowed_outgoing_stream_id();
// Ensure that the ID that will be in the MAX_STREAM_ID is larger than the
// current MAX.
id += (kV99StreamIdIncrement * 2);
// Make sure it's odd, like a client-initiated ID.
id &= ~0x01;
EXPECT_TRUE(QuicUtils::IsClientInitiatedStreamId(QUIC_VERSION_99, id));
// Generate and process the frame; connection should be closed.
QuicStreamIdBlockedFrame frame(0, id);
EXPECT_CALL(*connection_,
CloseConnection(QUIC_STREAM_ID_BLOCKED_ERROR, _, _));
session_->OnStreamIdBlockedFrame(frame);
}
// Test functionality for reception of a MAX STREAM ID frame. This code is
// client/server-agnostic.
TEST_P(QuicStreamIdManagerTestClient, StreamIdManagerClientOnMaxStreamIdFrame) {
// Get the current maximum allowed outgoing stream ID.
QuicStreamId initial_stream_id =
stream_id_manager_->max_allowed_outgoing_stream_id();
QuicMaxStreamIdFrame frame;
// If the stream ID in the frame is < the current maximum then
// the frame should be ignored.
frame.max_stream_id = initial_stream_id - kV99StreamIdIncrement;
EXPECT_TRUE(stream_id_manager_->OnMaxStreamIdFrame(frame));
EXPECT_EQ(initial_stream_id,
stream_id_manager_->max_allowed_outgoing_stream_id());
// A stream ID greater than the current limit should increase the limit.
frame.max_stream_id = initial_stream_id + kV99StreamIdIncrement;
EXPECT_TRUE(stream_id_manager_->OnMaxStreamIdFrame(frame));
EXPECT_EQ(initial_stream_id + kV99StreamIdIncrement,
stream_id_manager_->max_allowed_outgoing_stream_id());
}
// Test functionality for reception of a STREAM ID BLOCKED frame.
// This code is client/server-agnostic.
TEST_P(QuicStreamIdManagerTestClient, StreamIdManagerOnStreamIdBlockedFrame) {
// Get the current maximum allowed incoming stream ID.
QuicStreamId advertised_stream_id =
stream_id_manager_->advertised_max_allowed_incoming_stream_id();
QuicStreamIdBlockedFrame frame;
// If the peer is saying it's blocked on the stream ID that
// we've advertised, it's a noop since the peer has the correct information.
frame.stream_id = advertised_stream_id;
EXPECT_TRUE(stream_id_manager_->OnStreamIdBlockedFrame(frame));
// If the peer is saying it's blocked on a stream ID that is larger
// than what we've advertised, the connection should get closed.
frame.stream_id = advertised_stream_id + kV99StreamIdIncrement;
EXPECT_CALL(*connection_,
CloseConnection(QUIC_STREAM_ID_BLOCKED_ERROR, _, _));
EXPECT_FALSE(stream_id_manager_->OnStreamIdBlockedFrame(frame));
// If the peer is saying it's blocked on a stream ID that is less than
// what we've advertised, we send a MAX STREAM ID frame and update
// the advertised value.
// First, need to bump up the actual max so there is room for the MAX
// STREAM_ID frame to send a larger ID.
QuicStreamId actual_stream_id =
stream_id_manager_->actual_max_allowed_incoming_stream_id();
stream_id_manager_->OnStreamClosed(
stream_id_manager_->first_incoming_dynamic_stream_id());
EXPECT_EQ(actual_stream_id + kV99StreamIdIncrement,
stream_id_manager_->actual_max_allowed_incoming_stream_id());
EXPECT_GT(stream_id_manager_->actual_max_allowed_incoming_stream_id(),
stream_id_manager_->advertised_max_allowed_incoming_stream_id());
// Now simulate receiving a STTREAM_ID_BLOCKED frame...
// Changing the actual maximum, above, forces a MAX STREAM ID frame to be
// sent, so the logic for that (SendMaxStreamIdFrame(), etc) is tested.
frame.stream_id = advertised_stream_id;
EXPECT_CALL(*connection_, SendControlFrame(_))
.Times(1)
.WillRepeatedly(Invoke(session_.get(), &TestQuicSession::SaveFrame));
EXPECT_TRUE(stream_id_manager_->OnStreamIdBlockedFrame(frame));
EXPECT_EQ(stream_id_manager_->actual_max_allowed_incoming_stream_id(),
stream_id_manager_->advertised_max_allowed_incoming_stream_id());
EXPECT_EQ(MAX_STREAM_ID_FRAME, session_->save_frame().type);
EXPECT_EQ(stream_id_manager_->advertised_max_allowed_incoming_stream_id(),
session_->save_frame().max_stream_id_frame.max_stream_id);
// Ensure a client initiated stream ID is rejected.
frame.stream_id = GetParam() ? GetNthClientInitiatedBidirectionalId(1)
: GetNthClientInitiatedUnidirectionalId(1);
EXPECT_CALL(*connection_,
CloseConnection(QUIC_STREAM_ID_BLOCKED_ERROR, _, _));
EXPECT_FALSE(stream_id_manager_->OnStreamIdBlockedFrame(frame));
}
// Test GetNextOutgoingStream. This is client/server agnostic.
TEST_P(QuicStreamIdManagerTestClient, StreamIdManagerGetNextOutgoingFrame) {
// Number of streams we can open and the first one we should get when
// opening...
int number_of_streams = kDefaultMaxStreamsPerConnection;
QuicStreamId stream_id =
GetParam() ? session_->next_outgoing_bidirectional_stream_id()
: session_->next_outgoing_unidirectional_stream_id();
while (number_of_streams) {
EXPECT_TRUE(stream_id_manager_->CanOpenNextOutgoingStream());
EXPECT_EQ(stream_id, stream_id_manager_->GetNextOutgoingStreamId());
stream_id += kV99StreamIdIncrement;
number_of_streams--;
}
EXPECT_EQ(stream_id - kV99StreamIdIncrement,
stream_id_manager_->max_allowed_outgoing_stream_id());
// If we try to check that the next outgoing stream id is available it should
// A) fail and B) generate a STREAM_ID_BLOCKED frame.
EXPECT_CALL(*connection_, SendControlFrame(_))
.Times(1)
.WillRepeatedly(Invoke(session_.get(), &TestQuicSession::SaveFrame));
EXPECT_FALSE(stream_id_manager_->CanOpenNextOutgoingStream());
EXPECT_EQ(STREAM_ID_BLOCKED_FRAME, session_->save_frame().type);
EXPECT_EQ(stream_id_manager_->max_allowed_outgoing_stream_id(),
session_->save_frame().max_stream_id_frame.max_stream_id);
// If we try to get the next id (above the limit), it should cause a quic-bug.
EXPECT_QUIC_BUG(
stream_id_manager_->GetNextOutgoingStreamId(),
"Attempt allocate a new outgoing stream ID would exceed the limit");
}
// Ensure that MaybeIncreaseLargestPeerStreamId works properly. This is
// server/client agnostic.
TEST_P(QuicStreamIdManagerTestClient,
StreamIdManagerServerMaybeIncreaseLargestPeerStreamId) {
EXPECT_TRUE(stream_id_manager_->MaybeIncreaseLargestPeerStreamId(
stream_id_manager_->actual_max_allowed_incoming_stream_id()));
QuicStreamId server_initiated_stream_id =
GetParam() ? GetNthServerInitiatedBidirectionalId(0)
: GetNthServerInitiatedUnidirectionalId(0);
EXPECT_TRUE(stream_id_manager_->MaybeIncreaseLargestPeerStreamId(
server_initiated_stream_id));
// A bad stream ID results in a closed connection.
EXPECT_CALL(*connection_, CloseConnection(QUIC_INVALID_STREAM_ID, _, _));
EXPECT_FALSE(stream_id_manager_->MaybeIncreaseLargestPeerStreamId(
stream_id_manager_->actual_max_allowed_incoming_stream_id() +
kV99StreamIdIncrement));
}
// Test the MAX STREAM ID Window functionality.
// Free up Stream ID space. Do not expect to see a MAX_STREAM_ID
// until |window| stream ids are available.
TEST_P(QuicStreamIdManagerTestClient, StreamIdManagerServerMaxStreamId) {
// Test that a MAX_STREAM_ID frame is generated when the peer has less than
// |max_stream_id_window_| streams left that it can initiate.
// First, open, and then close, max_stream_id_window_ streams. This will
// max_stream_id_window_ streams available for the peer -- no MAX_STREAM_ID
// should be sent. The -1 is because the check in
// QuicStreamIdManager::MaybeSendMaxStreamIdFrame sends a MAX_STREAM_ID if the
// number of available streams at the peer is <= |max_stream_id_window_|
int stream_count = stream_id_manager_->max_stream_id_window() - 1;
QuicStreamId advertised_max =
stream_id_manager_->advertised_max_allowed_incoming_stream_id();
QuicStreamId expected_actual_max_id =
stream_id_manager_->actual_max_allowed_incoming_stream_id();
// Should not get a control-frame transmission since the peer should have
// "plenty" of stream IDs to use.
EXPECT_CALL(*connection_, SendControlFrame(_)).Times(0);
// This test runs as a client, so the first stream to release is 2, a
// server-initiated stream.
QuicStreamId stream_id = GetParam()
? GetNthServerInitiatedBidirectionalId(0)
: GetNthServerInitiatedUnidirectionalId(0);
size_t old_available_incoming_streams =
stream_id_manager_->available_incoming_streams();
while (stream_count) {
EXPECT_TRUE(
stream_id_manager_->MaybeIncreaseLargestPeerStreamId(stream_id));
old_available_incoming_streams--;
EXPECT_EQ(old_available_incoming_streams,
stream_id_manager_->available_incoming_streams());
stream_count--;
stream_id += kV99StreamIdIncrement;
}
// Now close them, still should get no MAX_STREAM_ID
stream_count = stream_id_manager_->max_stream_id_window();
stream_id = GetParam() ? GetNthServerInitiatedBidirectionalId(0)
: GetNthServerInitiatedUnidirectionalId(0);
while (stream_count) {
stream_id_manager_->OnStreamClosed(stream_id);
stream_count--;
stream_id += kV99StreamIdIncrement;
expected_actual_max_id += kV99StreamIdIncrement;
EXPECT_EQ(expected_actual_max_id,
stream_id_manager_->actual_max_allowed_incoming_stream_id());
// Advertised maximum should remain the same.
EXPECT_EQ(advertised_max,
stream_id_manager_->advertised_max_allowed_incoming_stream_id());
}
// This should not change.
EXPECT_EQ(old_available_incoming_streams,
stream_id_manager_->available_incoming_streams());
// Now whenever we close a stream we should get a MAX_STREAM_ID frame.
// Above code closed all the open streams, so we have to open/close
EXPECT_CALL(*connection_, SendControlFrame(_))
.Times(1)
.WillRepeatedly(Invoke(session_.get(), &TestQuicSession::SaveFrame));
EXPECT_TRUE(stream_id_manager_->MaybeIncreaseLargestPeerStreamId(stream_id));
stream_id_manager_->OnStreamClosed(stream_id);
stream_id += kV99StreamIdIncrement;
// Check that the MAX STREAM ID was sent and has the correct values.
EXPECT_EQ(MAX_STREAM_ID_FRAME, session_->save_frame().type);
EXPECT_EQ(stream_id_manager_->advertised_max_allowed_incoming_stream_id(),
session_->save_frame().max_stream_id_frame.max_stream_id);
}
// Test that registering static stream IDs causes the stream ID limit to rise
// accordingly. This is server/client agnostic.
TEST_P(QuicStreamIdManagerTestClient, TestStaticStreamAdjustment) {
QuicStreamId first_dynamic =
stream_id_manager_->first_incoming_dynamic_stream_id();
QuicStreamId expected_max_incoming =
stream_id_manager_->actual_max_allowed_incoming_stream_id();
// First test will register the first dynamic stream id as being for a static
// stream. This takes one stream ID out of the low-end of the dynamic range
// so therefore the high end should go up by 1 ID.
expected_max_incoming += kV99StreamIdIncrement;
stream_id_manager_->RegisterStaticStream(first_dynamic);
EXPECT_EQ(expected_max_incoming,
stream_id_manager_->actual_max_allowed_incoming_stream_id());
// Now be extreme, increase static by 100 stream ids. A discontinuous
// jump is not allowed; make sure.
first_dynamic += kV99StreamIdIncrement * 100;
expected_max_incoming += kV99StreamIdIncrement * 100;
QuicString bug_detail =
GetParam() ? "allocate 5 got 401" : "allocate 7 got 403";
EXPECT_QUIC_BUG(
stream_id_manager_->RegisterStaticStream(first_dynamic),
"Error in incoming static stream allocation, expected to " + bug_detail);
}
// Following tests all are server-specific. They depend, in some way, on
// server-specific attributes, such as the initial stream ID.
class QuicStreamIdManagerTestServer : public QuicStreamIdManagerTestBase {
protected:
QuicStreamIdManagerTestServer()
: QuicStreamIdManagerTestBase(Perspective::IS_SERVER) {}
};
INSTANTIATE_TEST_SUITE_P(Tests, QuicStreamIdManagerTestServer, testing::Bool());
// This test checks that the initialization for the maximum allowed outgoing
// stream id is correct.
TEST_P(QuicStreamIdManagerTestServer, CheckMaxAllowedOutgoing) {
const size_t kIncomingStreamCount = 123;
stream_id_manager_->SetMaxOpenOutgoingStreams(kIncomingStreamCount);
EXPECT_EQ(kIncomingStreamCount,
stream_id_manager_->max_allowed_outgoing_streams());
// Check that the max outgoing stream id is properly calculated
EXPECT_EQ(stream_id_manager_->GetNextOutgoingStreamId() +
((kIncomingStreamCount - 1) * kV99StreamIdIncrement),
stream_id_manager_->max_allowed_outgoing_stream_id());
}
// This test checks that the initialization for the maximum allowed incoming
// stream id is correct.
TEST_P(QuicStreamIdManagerTestServer, CheckMaxAllowedIncoming) {
const size_t kIncomingStreamCount = 245;
stream_id_manager_->SetMaxOpenIncomingStreams(kIncomingStreamCount);
EXPECT_EQ(kIncomingStreamCount,
stream_id_manager_->max_allowed_incoming_streams());
// Check that the window is 1/2 (integer math) of the stream count.
EXPECT_EQ((kIncomingStreamCount / 2),
stream_id_manager_->max_stream_id_window());
// Actual- and advertised- maxima start out equal.
EXPECT_EQ(stream_id_manager_->actual_max_allowed_incoming_stream_id(),
stream_id_manager_->advertised_max_allowed_incoming_stream_id());
// First stream ID the client should use should be 3, this means that the max
// stream id is 491 -- ((number of stream ids-1) * 2) + first available id.
EXPECT_EQ(stream_id_manager_->first_incoming_dynamic_stream_id() +
((kIncomingStreamCount - 1) * kV99StreamIdIncrement),
stream_id_manager_->actual_max_allowed_incoming_stream_id());
}
// Test that a MAX_STREAM_ID frame is generated when half the stream ids become
// available. This has a useful side effect of testing that when streams are
// closed, the number of available stream ids increases.
TEST_P(QuicStreamIdManagerTestServer, MaxStreamIdSlidingWindow) {
// Ignore OnStreamReset calls.
EXPECT_CALL(*connection_, OnStreamReset(_, _)).WillRepeatedly(Return());
// Capture control frames for analysis.
EXPECT_CALL(*connection_, SendControlFrame(_))
.WillRepeatedly(Invoke(session_.get(), &TestQuicSession::SaveFrame));
// Simulate config being negotiated, causing the limits all to be initialized.
session_->OnConfigNegotiated();
QuicStreamId first_advert =
stream_id_manager_->advertised_max_allowed_incoming_stream_id();
// Open/close enough streams to shrink the window without causing a MAX STREAM
// ID to be generated. The window will open (and a MAX STREAM ID generated)
// when max_stream_id_window() stream IDs have been made available. The loop
// will make that many stream IDs available, so the last CloseStream should
// cause a MAX STREAM ID frame to be generated.
int i = static_cast<int>(stream_id_manager_->max_stream_id_window());
QuicStreamId id = stream_id_manager_->first_incoming_dynamic_stream_id();
while (i) {
QuicStream* stream = session_->GetOrCreateStream(id);
EXPECT_NE(nullptr, stream);
// have to set the stream's fin-received flag to true so that it
// does not go into the has-not-received-byte-offset state, leading
// to the stream being added to the locally_closed_streams_highest_offset_
// map, and therefore not counting as truly being closed. The test requires
// that the stream truly close, so that new streams become available,
// causing the MAX_STREAM_ID to be sent.
stream->set_fin_received(true);
EXPECT_EQ(id, stream->id());
if (GetParam()) {
// Only send reset for incoming bidirectional streams.
EXPECT_CALL(*session_, SendRstStream(_, _, _));
}
CloseStream(stream->id());
i--;
id += kV99StreamIdIncrement;
}
EXPECT_EQ(MAX_STREAM_ID_FRAME, session_->save_frame().type);
QuicStreamId second_advert =
session_->save_frame().max_stream_id_frame.max_stream_id;
EXPECT_EQ(first_advert + (stream_id_manager_->max_stream_id_window() *
kV99StreamIdIncrement),
second_advert);
}
// Tast that an attempt to create an outgoing stream does not exceed the limit
// and that it generates an appropriate STREAM_ID_BLOCKED frame.
TEST_P(QuicStreamIdManagerTestServer, NewStreamDoesNotExceedLimit) {
size_t stream_count = stream_id_manager_->max_allowed_outgoing_streams();
EXPECT_NE(0u, stream_count);
TestQuicStream* stream;
while (stream_count) {
stream = GetParam() ? session_->CreateOutgoingBidirectionalStream()
: session_->CreateOutgoingUnidirectionalStream();
EXPECT_NE(stream, nullptr);
stream_count--;
}
// Quis Custodiet Ipsos Custodes.
EXPECT_EQ(stream->id(), stream_id_manager_->max_allowed_outgoing_stream_id());
// Create another, it should fail. Should also send a STREAM_ID_BLOCKED
// control frame.
EXPECT_CALL(*connection_, SendControlFrame(_));
stream = GetParam() ? session_->CreateOutgoingBidirectionalStream()
: session_->CreateOutgoingUnidirectionalStream();
EXPECT_EQ(nullptr, stream);
}
// Test that a server will reject a MAX_STREAM_ID that specifies a
// client-initiated stream ID.
TEST_P(QuicStreamIdManagerTestServer, RejectClientMaxStreamId) {
QuicStreamId id = stream_id_manager_->max_allowed_outgoing_stream_id();
// Ensure that the ID that will be in the MAX_STREAM_ID is larger than the
// current MAX.
id += (kV99StreamIdIncrement * 2);
// Turn it into a client-initiated ID (even).
id &= ~0x1;
EXPECT_TRUE(QuicUtils::IsClientInitiatedStreamId(QUIC_VERSION_99, id));
// Generate a MAX_STREAM_ID frame and process it; the connection should close.
QuicMaxStreamIdFrame frame(0, id);
EXPECT_CALL(*connection_, CloseConnection(QUIC_MAX_STREAM_ID_ERROR, _, _));
session_->OnMaxStreamIdFrame(frame);
}
// Test that a server will reject a STREAM_ID_BLOCKED that specifies a
// server-initiated stream ID. STREAM_ID_BLOCKED from a client should specify an
// even (client-initiated_ ID) generate one with an odd ID and check that the
// connection is closed.
TEST_P(QuicStreamIdManagerTestServer, RejectClientStreamIdBlocked) {
QuicStreamId id = stream_id_manager_->max_allowed_outgoing_stream_id();
// Ensure that the ID that will be in the MAX_STREAM_ID is larger than the
// current MAX.
id += (kV99StreamIdIncrement * 2);
// Make the ID odd, so it looks like the client is trying to specify a
// server-initiated ID.
id |= 0x1;
EXPECT_FALSE(QuicUtils::IsClientInitiatedStreamId(QUIC_VERSION_99, id));
// Generate a STREAM_ID_BLOCKED frame and process it; the connection should
// close.
QuicStreamIdBlockedFrame frame(0, id);
EXPECT_CALL(*connection_,
CloseConnection(QUIC_STREAM_ID_BLOCKED_ERROR, _, _));
session_->OnStreamIdBlockedFrame(frame);
}
// Check that the parameters used by the stream ID manager are properly
// initialized
TEST_P(QuicStreamIdManagerTestServer, StreamIdManagerServerInitialization) {
// These fields are inited via the QuicSession constructor to default
// values defined as a constant.
EXPECT_EQ(kDefaultMaxStreamsPerConnection,
stream_id_manager_->max_allowed_incoming_streams());
EXPECT_EQ(kDefaultMaxStreamsPerConnection,
stream_id_manager_->max_allowed_outgoing_streams());
// The window for advertising updates to the MAX STREAM ID is half the number
// of stream allowed.
EXPECT_EQ(kDefaultMaxStreamsPerConnection / kMaxStreamIdWindowDivisor,
stream_id_manager_->max_stream_id_window());
// This test runs as a server, so it initiates (that is to say, outgoing)
// even-numbered stream IDs. The -1 in the calculation is because the value
// being tested is the maximum allowed stream ID, not the first unallowed
// stream id.
const QuicStreamId kExpectedMaxOutgoingStreamId =
(GetParam() ? session_->next_outgoing_bidirectional_stream_id()
: session_->next_outgoing_unidirectional_stream_id()) +
((kDefaultMaxStreamsPerConnection - 1) * kV99StreamIdIncrement);
EXPECT_EQ(kExpectedMaxOutgoingStreamId,
stream_id_manager_->max_allowed_outgoing_stream_id());
// Same for IDs of incoming streams... But they are client initiated, so are
// even.
const QuicStreamId kExpectedMaxIncomingStreamId =
GetParam() ? GetNthClientInitiatedBidirectionalId(
kDefaultMaxStreamsPerConnection - 1)
: GetNthClientInitiatedUnidirectionalId(
kDefaultMaxStreamsPerConnection - 1);
EXPECT_EQ(kExpectedMaxIncomingStreamId,
stream_id_manager_->actual_max_allowed_incoming_stream_id());
EXPECT_EQ(kExpectedMaxIncomingStreamId,
stream_id_manager_->advertised_max_allowed_incoming_stream_id());
}
TEST_P(QuicStreamIdManagerTestServer, AvailableStreams) {
stream_id_manager_->MaybeIncreaseLargestPeerStreamId(
GetParam() ? GetNthClientInitiatedBidirectionalId(3)
: GetNthClientInitiatedUnidirectionalId(3));
EXPECT_TRUE(stream_id_manager_->IsAvailableStream(
GetParam() ? GetNthClientInitiatedBidirectionalId(1)
: GetNthClientInitiatedUnidirectionalId(1)));
EXPECT_TRUE(stream_id_manager_->IsAvailableStream(
GetParam() ? GetNthClientInitiatedBidirectionalId(2)
: GetNthClientInitiatedUnidirectionalId(2)));
}
// Tests that if MaybeIncreaseLargestPeerStreamId is given an extremely
// large stream ID (larger than the limit) it is rejected.
// This is a regression for Chromium bugs 909987 and 910040
TEST_P(QuicStreamIdManagerTestServer, ExtremeMaybeIncreaseLargestPeerStreamId) {
QuicStreamId too_big_stream_id =
stream_id_manager_->actual_max_allowed_incoming_stream_id() +
kV99StreamIdIncrement * 20;
QuicString error_details =
GetParam() ? "Stream id 480 above 400" : "Stream id 478 above 398";
EXPECT_CALL(*connection_,
CloseConnection(QUIC_INVALID_STREAM_ID, error_details, _));
EXPECT_FALSE(
stream_id_manager_->MaybeIncreaseLargestPeerStreamId(too_big_stream_id));
}
} // namespace
} // namespace test
} // namespace quic