| // 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_packet_exchanger.h" | |
| #include <utility> | |
| #include "quic/platform/api/quic_test.h" | |
| #include "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()); | |
| } | |
| } // namespace | |
| } // namespace quic |