| // Copyright (c) 2019 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 "quic/qbone/qbone_stream.h" |
| |
| #include <utility> |
| |
| #include "absl/strings/string_view.h" |
| #include "quic/core/crypto/quic_random.h" |
| #include "quic/core/quic_session.h" |
| #include "quic/core/quic_simple_buffer_allocator.h" |
| #include "quic/core/quic_utils.h" |
| #include "quic/platform/api/quic_test.h" |
| #include "quic/platform/api/quic_test_loopback.h" |
| #include "quic/qbone/qbone_constants.h" |
| #include "quic/qbone/qbone_session_base.h" |
| #include "quic/test_tools/mock_clock.h" |
| #include "quic/test_tools/quic_test_utils.h" |
| #include "spdy/core/spdy_protocol.h" |
| |
| namespace quic { |
| |
| namespace { |
| |
| using ::testing::_; |
| using ::testing::StrictMock; |
| |
| // MockQuicSession that does not create streams and writes data from |
| // QuicStream to a string. |
| class MockQuicSession : public QboneSessionBase { |
| public: |
| MockQuicSession(QuicConnection* connection, const QuicConfig& config) |
| : QboneSessionBase(connection, |
| nullptr /*visitor*/, |
| config, |
| CurrentSupportedVersions(), |
| nullptr /*writer*/) {} |
| |
| ~MockQuicSession() override {} |
| |
| // Writes outgoing data from QuicStream to a string. |
| QuicConsumedData WritevData(QuicStreamId id, size_t write_length, |
| QuicStreamOffset offset, StreamSendingState state, |
| TransmissionType type, |
| EncryptionLevel level) override { |
| if (!writable_) { |
| return QuicConsumedData(0, false); |
| } |
| |
| return QuicConsumedData(write_length, state != StreamSendingState::NO_FIN); |
| } |
| |
| QboneReadOnlyStream* CreateIncomingStream(QuicStreamId id) override { |
| return nullptr; |
| } |
| |
| // Called by QuicStream when they want to close stream. |
| MOCK_METHOD(void, |
| MaybeSendRstStreamFrame, |
| (QuicStreamId stream_id, |
| QuicRstStreamErrorCode error, |
| QuicStreamOffset bytes_written), |
| (override)); |
| MOCK_METHOD(void, |
| MaybeSendStopSendingFrame, |
| (QuicStreamId stream_id, QuicRstStreamErrorCode error), |
| (override)); |
| |
| // Sets whether data is written to buffer, or else if this is write blocked. |
| void set_writable(bool writable) { writable_ = writable; } |
| |
| // Tracks whether the stream is write blocked and its priority. |
| void RegisterReliableStream(QuicStreamId stream_id) { |
| // The priority effectively does not matter. Put all streams on the same |
| // priority. |
| write_blocked_streams()->RegisterStream( |
| stream_id, |
| /*is_static_stream=*/false, |
| /* precedence= */ spdy::SpdyStreamPrecedence(3)); |
| } |
| |
| // The session take ownership of the stream. |
| void ActivateReliableStream(std::unique_ptr<QuicStream> stream) { |
| ActivateStream(std::move(stream)); |
| } |
| |
| std::unique_ptr<QuicCryptoStream> CreateCryptoStream() override { |
| return std::make_unique<test::MockQuicCryptoStream>(this); |
| } |
| |
| MOCK_METHOD(void, ProcessPacketFromPeer, (absl::string_view), (override)); |
| MOCK_METHOD(void, ProcessPacketFromNetwork, (absl::string_view), (override)); |
| |
| private: |
| // Whether data is written to write_buffer_. |
| bool writable_ = true; |
| }; |
| |
| // Packet writer that does nothing. This is required for QuicConnection but |
| // isn't used for writing data. |
| class DummyPacketWriter : public QuicPacketWriter { |
| public: |
| DummyPacketWriter() {} |
| |
| // QuicPacketWriter overrides. |
| WriteResult WritePacket(const char* buffer, |
| size_t buf_len, |
| const QuicIpAddress& self_address, |
| const QuicSocketAddress& peer_address, |
| PerPacketOptions* options) override { |
| return WriteResult(WRITE_STATUS_ERROR, 0); |
| } |
| |
| bool IsWriteBlocked() const override { return false; }; |
| |
| void SetWritable() override {} |
| |
| QuicByteCount GetMaxPacketSize( |
| const QuicSocketAddress& peer_address) const override { |
| return 0; |
| } |
| |
| bool SupportsReleaseTime() const override { return false; } |
| |
| bool IsBatchMode() const override { return false; } |
| |
| QuicPacketBuffer GetNextWriteLocation( |
| const QuicIpAddress& self_address, |
| const QuicSocketAddress& peer_address) override { |
| return {nullptr, nullptr}; |
| } |
| |
| WriteResult Flush() override { return WriteResult(WRITE_STATUS_OK, 0); } |
| }; |
| |
| class QboneReadOnlyStreamTest : public ::testing::Test, |
| public QuicConnectionHelperInterface { |
| public: |
| void CreateReliableQuicStream() { |
| // Arbitrary values for QuicConnection. |
| Perspective perspective = Perspective::IS_SERVER; |
| bool owns_writer = true; |
| |
| alarm_factory_ = std::make_unique<test::MockAlarmFactory>(); |
| |
| connection_.reset(new QuicConnection( |
| test::TestConnectionId(0), QuicSocketAddress(TestLoopback(), 0), |
| QuicSocketAddress(TestLoopback(), 0), |
| this /*QuicConnectionHelperInterface*/, alarm_factory_.get(), |
| new DummyPacketWriter(), owns_writer, perspective, |
| ParsedVersionOfIndex(CurrentSupportedVersions(), 0))); |
| clock_.AdvanceTime(QuicTime::Delta::FromSeconds(1)); |
| session_ = std::make_unique<StrictMock<MockQuicSession>>(connection_.get(), |
| QuicConfig()); |
| session_->Initialize(); |
| stream_ = new QboneReadOnlyStream(kStreamId, session_.get()); |
| session_->ActivateReliableStream( |
| std::unique_ptr<QboneReadOnlyStream>(stream_)); |
| } |
| |
| ~QboneReadOnlyStreamTest() override {} |
| |
| const QuicClock* GetClock() const override { return &clock_; } |
| |
| QuicRandom* GetRandomGenerator() override { |
| return QuicRandom::GetInstance(); |
| } |
| |
| QuicBufferAllocator* GetStreamSendBufferAllocator() override { |
| return &buffer_allocator_; |
| } |
| |
| protected: |
| // The QuicSession will take the ownership. |
| QboneReadOnlyStream* stream_; |
| std::unique_ptr<StrictMock<MockQuicSession>> session_; |
| std::unique_ptr<QuicAlarmFactory> alarm_factory_; |
| std::unique_ptr<QuicConnection> connection_; |
| // Used to implement the QuicConnectionHelperInterface. |
| SimpleBufferAllocator buffer_allocator_; |
| MockClock clock_; |
| const QuicStreamId kStreamId = QuicUtils::GetFirstUnidirectionalStreamId( |
| CurrentSupportedVersions()[0].transport_version, |
| Perspective::IS_CLIENT); |
| }; |
| |
| // Read an entire string. |
| TEST_F(QboneReadOnlyStreamTest, ReadDataWhole) { |
| std::string packet = "Stuff"; |
| CreateReliableQuicStream(); |
| QuicStreamFrame frame(kStreamId, true, 0, packet); |
| EXPECT_CALL(*session_, ProcessPacketFromPeer("Stuff")); |
| stream_->OnStreamFrame(frame); |
| } |
| |
| // Test buffering. |
| TEST_F(QboneReadOnlyStreamTest, ReadBuffered) { |
| CreateReliableQuicStream(); |
| std::string packet = "Stuf"; |
| { |
| QuicStreamFrame frame(kStreamId, false, 0, packet); |
| stream_->OnStreamFrame(frame); |
| } |
| // We didn't write 5 bytes yet... |
| |
| packet = "f"; |
| EXPECT_CALL(*session_, ProcessPacketFromPeer("Stuff")); |
| { |
| QuicStreamFrame frame(kStreamId, true, 4, packet); |
| stream_->OnStreamFrame(frame); |
| } |
| } |
| |
| TEST_F(QboneReadOnlyStreamTest, ReadOutOfOrder) { |
| CreateReliableQuicStream(); |
| std::string packet = "f"; |
| { |
| QuicStreamFrame frame(kStreamId, true, 4, packet); |
| stream_->OnStreamFrame(frame); |
| } |
| |
| packet = "S"; |
| { |
| QuicStreamFrame frame(kStreamId, false, 0, packet); |
| stream_->OnStreamFrame(frame); |
| } |
| |
| packet = "tuf"; |
| EXPECT_CALL(*session_, ProcessPacketFromPeer("Stuff")); |
| { |
| QuicStreamFrame frame(kStreamId, false, 1, packet); |
| stream_->OnStreamFrame(frame); |
| } |
| } |
| |
| // Test buffering too many bytes. |
| TEST_F(QboneReadOnlyStreamTest, ReadBufferedTooLarge) { |
| CreateReliableQuicStream(); |
| std::string packet = "0123456789"; |
| int iterations = (QboneConstants::kMaxQbonePacketBytes / packet.size()) + 2; |
| EXPECT_CALL(*session_, MaybeSendStopSendingFrame( |
| kStreamId, QUIC_BAD_APPLICATION_PAYLOAD)); |
| EXPECT_CALL(*session_, MaybeSendRstStreamFrame( |
| kStreamId, QUIC_BAD_APPLICATION_PAYLOAD, _)); |
| for (int i = 0; i < iterations; ++i) { |
| QuicStreamFrame frame(kStreamId, i == (iterations - 1), i * packet.size(), |
| packet); |
| if (!stream_->reading_stopped()) { |
| stream_->OnStreamFrame(frame); |
| } |
| } |
| // We should have nothing written to the network and the stream |
| // should have stopped reading. |
| EXPECT_TRUE(stream_->reading_stopped()); |
| } |
| |
| } // namespace |
| |
| } // namespace quic |