// 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 "quiche/quic/core/uber_quic_stream_id_manager.h" #include "quiche/quic/core/quic_utils.h" #include "quiche/quic/core/quic_versions.h" #include "quiche/quic/platform/api/quic_test.h" #include "quiche/quic/test_tools/quic_stream_id_manager_peer.h" #include "quiche/quic/test_tools/quic_test_utils.h" using testing::_; using testing::StrictMock; namespace quic { namespace test { namespace { struct TestParams { explicit TestParams(ParsedQuicVersion version, Perspective perspective) : version(version), perspective(perspective) {} ParsedQuicVersion version; Perspective perspective; }; // Used by ::testing::PrintToStringParamName(). std::string PrintToString(const TestParams& p) { return absl::StrCat( ParsedQuicVersionToString(p.version), "_", (p.perspective == Perspective::IS_CLIENT ? "client" : "server")); } std::vector GetTestParams() { std::vector params; for (const ParsedQuicVersion& version : AllSupportedVersions()) { if (!version.HasIetfQuicFrames()) { continue; } params.push_back(TestParams(version, Perspective::IS_CLIENT)); params.push_back(TestParams(version, Perspective::IS_SERVER)); } return params; } class MockDelegate : public QuicStreamIdManager::DelegateInterface { public: MOCK_METHOD(void, SendMaxStreams, (QuicStreamCount stream_count, bool unidirectional), (override)); }; class UberQuicStreamIdManagerTest : public QuicTestWithParam { protected: UberQuicStreamIdManagerTest() : manager_(perspective(), version(), &delegate_, 0, 0, kDefaultMaxStreamsPerConnection, kDefaultMaxStreamsPerConnection) {} QuicStreamId GetNthClientInitiatedBidirectionalId(int n) { return QuicUtils::GetFirstBidirectionalStreamId(transport_version(), Perspective::IS_CLIENT) + QuicUtils::StreamIdDelta(transport_version()) * n; } QuicStreamId GetNthClientInitiatedUnidirectionalId(int n) { return QuicUtils::GetFirstUnidirectionalStreamId(transport_version(), Perspective::IS_CLIENT) + QuicUtils::StreamIdDelta(transport_version()) * n; } QuicStreamId GetNthServerInitiatedBidirectionalId(int n) { return QuicUtils::GetFirstBidirectionalStreamId(transport_version(), Perspective::IS_SERVER) + QuicUtils::StreamIdDelta(transport_version()) * n; } QuicStreamId GetNthServerInitiatedUnidirectionalId(int n) { return QuicUtils::GetFirstUnidirectionalStreamId(transport_version(), Perspective::IS_SERVER) + QuicUtils::StreamIdDelta(transport_version()) * n; } QuicStreamId GetNthPeerInitiatedBidirectionalStreamId(int n) { return ((perspective() == Perspective::IS_SERVER) ? GetNthClientInitiatedBidirectionalId(n) : GetNthServerInitiatedBidirectionalId(n)); } QuicStreamId GetNthPeerInitiatedUnidirectionalStreamId(int n) { return ((perspective() == Perspective::IS_SERVER) ? GetNthClientInitiatedUnidirectionalId(n) : GetNthServerInitiatedUnidirectionalId(n)); } QuicStreamId GetNthSelfInitiatedBidirectionalStreamId(int n) { return ((perspective() == Perspective::IS_CLIENT) ? GetNthClientInitiatedBidirectionalId(n) : GetNthServerInitiatedBidirectionalId(n)); } QuicStreamId GetNthSelfInitiatedUnidirectionalStreamId(int n) { return ((perspective() == Perspective::IS_CLIENT) ? GetNthClientInitiatedUnidirectionalId(n) : GetNthServerInitiatedUnidirectionalId(n)); } QuicStreamId StreamCountToId(QuicStreamCount stream_count, Perspective perspective, bool bidirectional) { return ((bidirectional) ? QuicUtils::GetFirstBidirectionalStreamId( transport_version(), perspective) : QuicUtils::GetFirstUnidirectionalStreamId( transport_version(), perspective)) + ((stream_count - 1) * QuicUtils::StreamIdDelta(transport_version())); } ParsedQuicVersion version() { return GetParam().version; } QuicTransportVersion transport_version() { return version().transport_version; } Perspective perspective() { return GetParam().perspective; } testing::StrictMock delegate_; UberQuicStreamIdManager manager_; }; INSTANTIATE_TEST_SUITE_P(Tests, UberQuicStreamIdManagerTest, ::testing::ValuesIn(GetTestParams()), ::testing::PrintToStringParamName()); TEST_P(UberQuicStreamIdManagerTest, Initialization) { EXPECT_EQ(GetNthSelfInitiatedBidirectionalStreamId(0), manager_.next_outgoing_bidirectional_stream_id()); EXPECT_EQ(GetNthSelfInitiatedUnidirectionalStreamId(0), manager_.next_outgoing_unidirectional_stream_id()); } TEST_P(UberQuicStreamIdManagerTest, SetMaxOpenOutgoingStreams) { const size_t kNumMaxOutgoingStream = 123; // Set the uni- and bi- directional limits to different values to ensure // that they are managed separately. EXPECT_TRUE(manager_.MaybeAllowNewOutgoingBidirectionalStreams( kNumMaxOutgoingStream)); EXPECT_TRUE(manager_.MaybeAllowNewOutgoingUnidirectionalStreams( kNumMaxOutgoingStream + 1)); EXPECT_EQ(kNumMaxOutgoingStream, manager_.max_outgoing_bidirectional_streams()); EXPECT_EQ(kNumMaxOutgoingStream + 1, manager_.max_outgoing_unidirectional_streams()); // Check that, for each directionality, we can open the correct number of // streams. int i = kNumMaxOutgoingStream; while (i) { EXPECT_TRUE(manager_.CanOpenNextOutgoingBidirectionalStream()); manager_.GetNextOutgoingBidirectionalStreamId(); EXPECT_TRUE(manager_.CanOpenNextOutgoingUnidirectionalStream()); manager_.GetNextOutgoingUnidirectionalStreamId(); i--; } // One more unidirectional EXPECT_TRUE(manager_.CanOpenNextOutgoingUnidirectionalStream()); manager_.GetNextOutgoingUnidirectionalStreamId(); // Both should be exhausted... EXPECT_FALSE(manager_.CanOpenNextOutgoingUnidirectionalStream()); EXPECT_FALSE(manager_.CanOpenNextOutgoingBidirectionalStream()); } TEST_P(UberQuicStreamIdManagerTest, SetMaxOpenIncomingStreams) { const size_t kNumMaxIncomingStreams = 456; manager_.SetMaxOpenIncomingUnidirectionalStreams(kNumMaxIncomingStreams); // Do +1 for bidirectional to ensure that uni- and bi- get properly set. manager_.SetMaxOpenIncomingBidirectionalStreams(kNumMaxIncomingStreams + 1); EXPECT_EQ(kNumMaxIncomingStreams + 1, manager_.GetMaxAllowdIncomingBidirectionalStreams()); EXPECT_EQ(kNumMaxIncomingStreams, manager_.GetMaxAllowdIncomingUnidirectionalStreams()); EXPECT_EQ(manager_.max_incoming_bidirectional_streams(), manager_.advertised_max_incoming_bidirectional_streams()); EXPECT_EQ(manager_.max_incoming_unidirectional_streams(), manager_.advertised_max_incoming_unidirectional_streams()); // Make sure that we can create kNumMaxIncomingStreams incoming unidirectional // streams and kNumMaxIncomingStreams+1 incoming bidirectional streams. size_t i; for (i = 0; i < kNumMaxIncomingStreams; i++) { EXPECT_TRUE(manager_.MaybeIncreaseLargestPeerStreamId( GetNthPeerInitiatedUnidirectionalStreamId(i), nullptr)); EXPECT_TRUE(manager_.MaybeIncreaseLargestPeerStreamId( GetNthPeerInitiatedBidirectionalStreamId(i), nullptr)); } // Should be able to open the next bidirectional stream EXPECT_TRUE(manager_.MaybeIncreaseLargestPeerStreamId( GetNthPeerInitiatedBidirectionalStreamId(i), nullptr)); // We should have exhausted the counts, the next streams should fail std::string error_details; EXPECT_FALSE(manager_.MaybeIncreaseLargestPeerStreamId( GetNthPeerInitiatedUnidirectionalStreamId(i), &error_details)); EXPECT_EQ(error_details, absl::StrCat( "Stream id ", GetNthPeerInitiatedUnidirectionalStreamId(i), " would exceed stream count limit ", kNumMaxIncomingStreams)); EXPECT_FALSE(manager_.MaybeIncreaseLargestPeerStreamId( GetNthPeerInitiatedBidirectionalStreamId(i + 1), &error_details)); EXPECT_EQ(error_details, absl::StrCat("Stream id ", GetNthPeerInitiatedBidirectionalStreamId(i + 1), " would exceed stream count limit ", kNumMaxIncomingStreams + 1)); } TEST_P(UberQuicStreamIdManagerTest, GetNextOutgoingStreamId) { EXPECT_TRUE(manager_.MaybeAllowNewOutgoingBidirectionalStreams(10)); EXPECT_TRUE(manager_.MaybeAllowNewOutgoingUnidirectionalStreams(10)); EXPECT_EQ(GetNthSelfInitiatedBidirectionalStreamId(0), manager_.GetNextOutgoingBidirectionalStreamId()); EXPECT_EQ(GetNthSelfInitiatedBidirectionalStreamId(1), manager_.GetNextOutgoingBidirectionalStreamId()); EXPECT_EQ(GetNthSelfInitiatedUnidirectionalStreamId(0), manager_.GetNextOutgoingUnidirectionalStreamId()); EXPECT_EQ(GetNthSelfInitiatedUnidirectionalStreamId(1), manager_.GetNextOutgoingUnidirectionalStreamId()); } TEST_P(UberQuicStreamIdManagerTest, AvailableStreams) { EXPECT_TRUE(manager_.MaybeIncreaseLargestPeerStreamId( GetNthPeerInitiatedBidirectionalStreamId(3), nullptr)); EXPECT_TRUE( manager_.IsAvailableStream(GetNthPeerInitiatedBidirectionalStreamId(1))); EXPECT_TRUE( manager_.IsAvailableStream(GetNthPeerInitiatedBidirectionalStreamId(2))); EXPECT_TRUE(manager_.MaybeIncreaseLargestPeerStreamId( GetNthPeerInitiatedUnidirectionalStreamId(3), nullptr)); EXPECT_TRUE( manager_.IsAvailableStream(GetNthPeerInitiatedUnidirectionalStreamId(1))); EXPECT_TRUE( manager_.IsAvailableStream(GetNthPeerInitiatedUnidirectionalStreamId(2))); } TEST_P(UberQuicStreamIdManagerTest, MaybeIncreaseLargestPeerStreamId) { EXPECT_TRUE(manager_.MaybeIncreaseLargestPeerStreamId( StreamCountToId(manager_.max_incoming_bidirectional_streams(), QuicUtils::InvertPerspective(perspective()), /* bidirectional=*/true), nullptr)); EXPECT_TRUE(manager_.MaybeIncreaseLargestPeerStreamId( StreamCountToId(manager_.max_incoming_bidirectional_streams(), QuicUtils::InvertPerspective(perspective()), /* bidirectional=*/false), nullptr)); std::string expected_error_details = perspective() == Perspective::IS_SERVER ? "Stream id 400 would exceed stream count limit 100" : "Stream id 401 would exceed stream count limit 100"; std::string error_details; EXPECT_FALSE(manager_.MaybeIncreaseLargestPeerStreamId( StreamCountToId(manager_.max_incoming_bidirectional_streams() + 1, QuicUtils::InvertPerspective(perspective()), /* bidirectional=*/true), &error_details)); EXPECT_EQ(expected_error_details, error_details); expected_error_details = perspective() == Perspective::IS_SERVER ? "Stream id 402 would exceed stream count limit 100" : "Stream id 403 would exceed stream count limit 100"; EXPECT_FALSE(manager_.MaybeIncreaseLargestPeerStreamId( StreamCountToId(manager_.max_incoming_bidirectional_streams() + 1, QuicUtils::InvertPerspective(perspective()), /* bidirectional=*/false), &error_details)); EXPECT_EQ(expected_error_details, error_details); } TEST_P(UberQuicStreamIdManagerTest, OnStreamsBlockedFrame) { QuicStreamCount stream_count = manager_.advertised_max_incoming_bidirectional_streams() - 1; QuicStreamsBlockedFrame frame(kInvalidControlFrameId, stream_count, /*unidirectional=*/false); EXPECT_CALL(delegate_, SendMaxStreams(manager_.max_incoming_bidirectional_streams(), frame.unidirectional)) .Times(0); EXPECT_TRUE(manager_.OnStreamsBlockedFrame(frame, nullptr)); stream_count = manager_.advertised_max_incoming_unidirectional_streams() - 1; frame.stream_count = stream_count; frame.unidirectional = true; EXPECT_CALL(delegate_, SendMaxStreams(manager_.max_incoming_unidirectional_streams(), frame.unidirectional)) .Times(0); EXPECT_TRUE(manager_.OnStreamsBlockedFrame(frame, nullptr)); } TEST_P(UberQuicStreamIdManagerTest, SetMaxOpenOutgoingStreamsPlusFrame) { const size_t kNumMaxOutgoingStream = 123; // Set the uni- and bi- directional limits to different values to ensure // that they are managed separately. EXPECT_TRUE(manager_.MaybeAllowNewOutgoingBidirectionalStreams( kNumMaxOutgoingStream)); EXPECT_TRUE(manager_.MaybeAllowNewOutgoingUnidirectionalStreams( kNumMaxOutgoingStream + 1)); EXPECT_EQ(kNumMaxOutgoingStream, manager_.max_outgoing_bidirectional_streams()); EXPECT_EQ(kNumMaxOutgoingStream + 1, manager_.max_outgoing_unidirectional_streams()); // Check that, for each directionality, we can open the correct number of // streams. int i = kNumMaxOutgoingStream; while (i) { EXPECT_TRUE(manager_.CanOpenNextOutgoingBidirectionalStream()); manager_.GetNextOutgoingBidirectionalStreamId(); EXPECT_TRUE(manager_.CanOpenNextOutgoingUnidirectionalStream()); manager_.GetNextOutgoingUnidirectionalStreamId(); i--; } // One more unidirectional EXPECT_TRUE(manager_.CanOpenNextOutgoingUnidirectionalStream()); manager_.GetNextOutgoingUnidirectionalStreamId(); // Both should be exhausted... EXPECT_FALSE(manager_.CanOpenNextOutgoingUnidirectionalStream()); EXPECT_FALSE(manager_.CanOpenNextOutgoingBidirectionalStream()); } } // namespace } // namespace test } // namespace quic