| // 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/quiche/src/quic/core/quic_stream_id_manager.h" |
| |
| #include <cstdint> |
| #include <set> |
| #include <utility> |
| |
| #include "net/third_party/quiche/src/quic/core/crypto/crypto_protocol.h" |
| #include "net/third_party/quiche/src/quic/core/crypto/null_encrypter.h" |
| #include "net/third_party/quiche/src/quic/core/quic_crypto_stream.h" |
| #include "net/third_party/quiche/src/quic/core/quic_data_writer.h" |
| #include "net/third_party/quiche/src/quic/core/quic_packets.h" |
| #include "net/third_party/quiche/src/quic/core/quic_session.h" |
| #include "net/third_party/quiche/src/quic/core/quic_stream.h" |
| #include "net/third_party/quiche/src/quic/core/quic_utils.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_arraysize.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_expect_bug.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_flags.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_map_util.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_str_cat.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_string.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_test.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_test_mem_slice_vector.h" |
| #include "net/third_party/quiche/src/quic/test_tools/quic_config_peer.h" |
| #include "net/third_party/quiche/src/quic/test_tools/quic_connection_peer.h" |
| #include "net/third_party/quiche/src/quic/test_tools/quic_flow_controller_peer.h" |
| #include "net/third_party/quiche/src/quic/test_tools/quic_session_peer.h" |
| #include "net/third_party/quiche/src/quic/test_tools/quic_stream_id_manager_peer.h" |
| #include "net/third_party/quiche/src/quic/test_tools/quic_stream_peer.h" |
| #include "net/third_party/quiche/src/quic/test_tools/quic_stream_send_buffer_peer.h" |
| #include "net/third_party/quiche/src/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 |