| // 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 "quiche/quic/qbone/qbone_packet_exchanger.h" |
| |
| #include <list> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "quiche/quic/platform/api/quic_test.h" |
| #include "quiche/quic/qbone/mock_qbone_client.h" |
| |
| namespace quic { |
| namespace { |
| |
| using ::testing::StrEq; |
| using ::testing::StrictMock; |
| |
| const size_t kMaxPendingPackets = 2; |
| |
| class MockVisitor : public QbonePacketExchanger::Visitor { |
| public: |
| MOCK_METHOD(void, OnReadError, (const std::string&), (override)); |
| MOCK_METHOD(void, OnWriteError, (const std::string&), (override)); |
| }; |
| |
| class FakeQbonePacketExchanger : public QbonePacketExchanger { |
| public: |
| using QbonePacketExchanger::QbonePacketExchanger; |
| |
| // Adds a packet to the end of list of packets to be returned by ReadPacket. |
| // When the list is empty, ReadPacket returns nullptr to signify error as |
| // defined by QbonePacketExchanger. If SetReadError is not called or called |
| // with empty error string, ReadPacket sets blocked to true. |
| void AddPacketToBeRead(std::unique_ptr<QuicData> packet) { |
| packets_to_be_read_.push_back(std::move(packet)); |
| } |
| |
| // Sets the error to be returned by ReadPacket when the list of packets is |
| // empty. If error is empty string, blocked is set by ReadPacket. |
| void SetReadError(const std::string& error) { read_error_ = error; } |
| |
| // Force WritePacket to fail with the given status. WritePacket returns true |
| // when blocked == true and error is empty. |
| void ForceWriteFailure(bool blocked, const std::string& error) { |
| write_blocked_ = blocked; |
| write_error_ = error; |
| } |
| |
| // Packets that have been successfully written by WritePacket. |
| const std::vector<std::string>& packets_written() const { |
| return packets_written_; |
| } |
| |
| private: |
| // Implements QbonePacketExchanger::ReadPacket. |
| std::unique_ptr<QuicData> ReadPacket(bool* blocked, |
| std::string* error) override { |
| *blocked = false; |
| |
| if (packets_to_be_read_.empty()) { |
| *blocked = read_error_.empty(); |
| *error = read_error_; |
| return nullptr; |
| } |
| |
| std::unique_ptr<QuicData> packet = std::move(packets_to_be_read_.front()); |
| packets_to_be_read_.pop_front(); |
| return packet; |
| } |
| |
| // Implements QbonePacketExchanger::WritePacket. |
| bool WritePacket(const char* packet, size_t size, bool* blocked, |
| std::string* error) override { |
| *blocked = false; |
| |
| if (write_blocked_ || !write_error_.empty()) { |
| *blocked = write_blocked_; |
| *error = write_error_; |
| return false; |
| } |
| |
| packets_written_.push_back(std::string(packet, size)); |
| return true; |
| } |
| |
| std::string read_error_; |
| std::list<std::unique_ptr<QuicData>> packets_to_be_read_; |
| |
| std::string write_error_; |
| bool write_blocked_ = false; |
| std::vector<std::string> packets_written_; |
| }; |
| |
| TEST(QbonePacketExchangerTest, |
| ReadAndDeliverPacketDeliversPacketToQboneClient) { |
| StrictMock<MockVisitor> visitor; |
| FakeQbonePacketExchanger exchanger(&visitor, kMaxPendingPackets); |
| StrictMock<MockQboneClient> client; |
| |
| std::string packet = "data"; |
| exchanger.AddPacketToBeRead( |
| std::make_unique<QuicData>(packet.data(), packet.length())); |
| EXPECT_CALL(client, ProcessPacketFromNetwork(StrEq("data"))); |
| |
| EXPECT_TRUE(exchanger.ReadAndDeliverPacket(&client)); |
| } |
| |
| TEST(QbonePacketExchangerTest, |
| ReadAndDeliverPacketNotifiesVisitorOnReadFailure) { |
| MockVisitor visitor; |
| FakeQbonePacketExchanger exchanger(&visitor, kMaxPendingPackets); |
| MockQboneClient client; |
| |
| // Force read error. |
| std::string io_error = "I/O error"; |
| exchanger.SetReadError(io_error); |
| EXPECT_CALL(visitor, OnReadError(StrEq(io_error))).Times(1); |
| |
| EXPECT_FALSE(exchanger.ReadAndDeliverPacket(&client)); |
| } |
| |
| TEST(QbonePacketExchangerTest, |
| ReadAndDeliverPacketDoesNotNotifyVisitorOnBlockedIO) { |
| MockVisitor visitor; |
| FakeQbonePacketExchanger exchanger(&visitor, kMaxPendingPackets); |
| MockQboneClient client; |
| |
| // No more packets to read. |
| EXPECT_FALSE(exchanger.ReadAndDeliverPacket(&client)); |
| } |
| |
| TEST(QbonePacketExchangerTest, |
| WritePacketToNetworkWritesDirectlyToNetworkWhenNotBlocked) { |
| MockVisitor visitor; |
| FakeQbonePacketExchanger exchanger(&visitor, kMaxPendingPackets); |
| MockQboneClient client; |
| |
| std::string packet = "data"; |
| exchanger.WritePacketToNetwork(packet.data(), packet.length()); |
| |
| ASSERT_EQ(exchanger.packets_written().size(), 1); |
| EXPECT_THAT(exchanger.packets_written()[0], StrEq(packet)); |
| } |
| |
| TEST(QbonePacketExchangerTest, |
| WritePacketToNetworkQueuesPacketsAndProcessThemLater) { |
| MockVisitor visitor; |
| FakeQbonePacketExchanger exchanger(&visitor, kMaxPendingPackets); |
| MockQboneClient client; |
| |
| // Force write to be blocked so that packets are queued. |
| exchanger.ForceWriteFailure(true, ""); |
| std::vector<std::string> packets = {"packet0", "packet1"}; |
| for (int i = 0; i < packets.size(); i++) { |
| exchanger.WritePacketToNetwork(packets[i].data(), packets[i].length()); |
| } |
| |
| // Nothing should have been written because of blockage. |
| ASSERT_TRUE(exchanger.packets_written().empty()); |
| |
| // Remove blockage and start proccessing queued packets. |
| exchanger.ForceWriteFailure(false, ""); |
| exchanger.SetWritable(); |
| |
| // Queued packets are processed. |
| ASSERT_EQ(exchanger.packets_written().size(), 2); |
| for (int i = 0; i < packets.size(); i++) { |
| EXPECT_THAT(exchanger.packets_written()[i], StrEq(packets[i])); |
| } |
| } |
| |
| TEST(QbonePacketExchangerTest, |
| SetWritableContinuesProcessingPacketIfPreviousCallBlocked) { |
| MockVisitor visitor; |
| FakeQbonePacketExchanger exchanger(&visitor, kMaxPendingPackets); |
| MockQboneClient client; |
| |
| // Force write to be blocked so that packets are queued. |
| exchanger.ForceWriteFailure(true, ""); |
| std::vector<std::string> packets = {"packet0", "packet1"}; |
| for (int i = 0; i < packets.size(); i++) { |
| exchanger.WritePacketToNetwork(packets[i].data(), packets[i].length()); |
| } |
| |
| // Nothing should have been written because of blockage. |
| ASSERT_TRUE(exchanger.packets_written().empty()); |
| |
| // Start processing packets, but since writes are still blocked, nothing |
| // should have been written. |
| exchanger.SetWritable(); |
| ASSERT_TRUE(exchanger.packets_written().empty()); |
| |
| // Remove blockage and start processing packets again. |
| exchanger.ForceWriteFailure(false, ""); |
| exchanger.SetWritable(); |
| |
| ASSERT_EQ(exchanger.packets_written().size(), 2); |
| for (int i = 0; i < packets.size(); i++) { |
| EXPECT_THAT(exchanger.packets_written()[i], StrEq(packets[i])); |
| } |
| } |
| |
| TEST(QbonePacketExchangerTest, WritePacketToNetworkDropsPacketIfQueueIfFull) { |
| std::vector<std::string> packets = {"packet0", "packet1", "packet2"}; |
| size_t queue_size = packets.size() - 1; |
| MockVisitor visitor; |
| // exchanger has smaller queue than number of packets. |
| FakeQbonePacketExchanger exchanger(&visitor, queue_size); |
| MockQboneClient client; |
| |
| exchanger.ForceWriteFailure(true, ""); |
| for (int i = 0; i < packets.size(); i++) { |
| exchanger.WritePacketToNetwork(packets[i].data(), packets[i].length()); |
| } |
| |
| // Blocked writes cause packets to be queued or dropped. |
| ASSERT_TRUE(exchanger.packets_written().empty()); |
| |
| exchanger.ForceWriteFailure(false, ""); |
| exchanger.SetWritable(); |
| |
| ASSERT_EQ(exchanger.packets_written().size(), queue_size); |
| for (int i = 0; i < queue_size; i++) { |
| EXPECT_THAT(exchanger.packets_written()[i], StrEq(packets[i])); |
| } |
| } |
| |
| TEST(QbonePacketExchangerTest, WriteErrorsGetNotified) { |
| MockVisitor visitor; |
| FakeQbonePacketExchanger exchanger(&visitor, kMaxPendingPackets); |
| MockQboneClient client; |
| std::string packet = "data"; |
| |
| // Write error is delivered to visitor during WritePacketToNetwork. |
| std::string io_error = "I/O error"; |
| exchanger.ForceWriteFailure(false, io_error); |
| EXPECT_CALL(visitor, OnWriteError(StrEq(io_error))).Times(1); |
| exchanger.WritePacketToNetwork(packet.data(), packet.length()); |
| ASSERT_TRUE(exchanger.packets_written().empty()); |
| |
| // Write error is delivered to visitor during SetWritable. |
| exchanger.ForceWriteFailure(true, ""); |
| exchanger.WritePacketToNetwork(packet.data(), packet.length()); |
| |
| std::string sys_error = "sys error"; |
| exchanger.ForceWriteFailure(false, sys_error); |
| EXPECT_CALL(visitor, OnWriteError(StrEq(sys_error))).Times(1); |
| exchanger.SetWritable(); |
| ASSERT_TRUE(exchanger.packets_written().empty()); |
| } |
| |
| TEST(QbonePacketExchangerTest, NullVisitorDoesntCrash) { |
| FakeQbonePacketExchanger exchanger(nullptr, kMaxPendingPackets); |
| MockQboneClient client; |
| std::string packet = "data"; |
| |
| // Force read error. |
| std::string io_error = "I/O error"; |
| exchanger.SetReadError(io_error); |
| EXPECT_FALSE(exchanger.ReadAndDeliverPacket(&client)); |
| |
| // Force write error |
| exchanger.ForceWriteFailure(false, io_error); |
| exchanger.WritePacketToNetwork(packet.data(), packet.length()); |
| EXPECT_TRUE(exchanger.packets_written().empty()); |
| } |
| |
| } // namespace |
| } // namespace quic |