| // Copyright (c) 2012 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/quic/quic_connection_helper.h" |
| |
| #include <vector> |
| |
| #include "net/base/net_errors.h" |
| #include "net/quic/test_tools/mock_clock.h" |
| #include "net/quic/test_tools/quic_test_utils.h" |
| #include "net/quic/test_tools/test_task_runner.h" |
| #include "net/socket/socket_test_util.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using testing::_; |
| |
| namespace net { |
| |
| class QuicConnectionPeer { |
| public: |
| static void SendAck(QuicConnection* connection) { |
| connection->SendAck(); |
| } |
| |
| static void SetScheduler(QuicConnection* connection, |
| QuicSendScheduler* scheduler) { |
| connection->scheduler_.reset(scheduler); |
| } |
| }; |
| |
| namespace test { |
| |
| const char kData[] = "foo"; |
| |
| class TestConnection : public QuicConnection { |
| public: |
| TestConnection(QuicGuid guid, |
| IPEndPoint address, |
| QuicConnectionHelper* helper) |
| : QuicConnection(guid, address, helper) { |
| } |
| |
| void SendAck() { |
| QuicConnectionPeer::SendAck(this); |
| } |
| |
| void SetScheduler(QuicSendScheduler* scheduler) { |
| QuicConnectionPeer::SetScheduler(this, scheduler); |
| } |
| }; |
| |
| class QuicConnectionHelperTest : public ::testing::Test { |
| protected: |
| // Holds a packet to be written to the wire, and the IO mode that should |
| // be used by the mock socket when performing the write. |
| struct PacketToWrite { |
| PacketToWrite(IoMode mode, QuicEncryptedPacket* packet) |
| : mode(mode), |
| packet(packet) { |
| } |
| IoMode mode; |
| QuicEncryptedPacket* packet; |
| }; |
| |
| QuicConnectionHelperTest() |
| : guid_(2), |
| framer_(QuicDecrypter::Create(kNULL), QuicEncrypter::Create(kNULL)), |
| creator_(guid_, &framer_), |
| net_log_(BoundNetLog()), |
| frame_(1, false, 0, kData) { |
| Initialize(); |
| } |
| |
| ~QuicConnectionHelperTest() { |
| for (size_t i = 0; i < writes_.size(); i++) { |
| delete writes_[i].packet; |
| } |
| } |
| |
| // Adds a packet to the list of expected writes. |
| void AddWrite(IoMode mode, QuicEncryptedPacket* packet) { |
| writes_.push_back(PacketToWrite(mode, packet)); |
| } |
| |
| // Returns the packet to be written at position |pos|. |
| QuicEncryptedPacket* GetWrite(size_t pos) { |
| return writes_[pos].packet; |
| } |
| |
| bool AtEof() { |
| return socket_data_->at_read_eof() && socket_data_->at_write_eof(); |
| } |
| |
| // Configures the test fixture to use the list of expected writes. |
| void Initialize() { |
| mock_writes_.reset(new MockWrite[writes_.size()]); |
| for (size_t i = 0; i < writes_.size(); i++) { |
| mock_writes_[i] = MockWrite(writes_[i].mode, |
| writes_[i].packet->data(), |
| writes_[i].packet->length()); |
| }; |
| |
| socket_data_.reset(new StaticSocketDataProvider(NULL, 0, mock_writes_.get(), |
| writes_.size())); |
| |
| MockUDPClientSocket* socket = |
| new MockUDPClientSocket(socket_data_.get(), net_log_.net_log()); |
| socket->Connect(IPEndPoint()); |
| runner_ = new TestTaskRunner(&clock_); |
| helper_.reset(new QuicConnectionHelper(runner_.get(), &clock_, socket)); |
| scheduler_ = new testing::StrictMock<MockScheduler>(); |
| EXPECT_CALL(*scheduler_, TimeUntilSend(_)). |
| WillRepeatedly(testing::Return(QuicTime::Delta())); |
| connection_.reset(new TestConnection(guid_, IPEndPoint(), helper_.get())); |
| connection_->set_visitor(&visitor_); |
| connection_->SetScheduler(scheduler_); |
| } |
| |
| // Returns a newly created packet to send kData on stream 1. |
| QuicEncryptedPacket* ConstructDataPacket( |
| QuicPacketSequenceNumber sequence_number) { |
| InitializeHeader(sequence_number); |
| |
| return ConstructPacket(header_, QuicFrame(&frame_)); |
| } |
| |
| // Returns a newly created packet to send ack data. |
| QuicEncryptedPacket* ConstructAckPacket( |
| QuicPacketSequenceNumber sequence_number) { |
| InitializeHeader(sequence_number); |
| |
| QuicAckFrame ack(0, sequence_number); |
| return ConstructPacket(header_, QuicFrame(&ack)); |
| } |
| |
| |
| // Returns a newly created packet to send congestion feedback data. |
| QuicEncryptedPacket* ConstructFeedbackPacket( |
| QuicPacketSequenceNumber sequence_number) { |
| InitializeHeader(sequence_number); |
| |
| QuicCongestionFeedbackFrame frame; |
| frame.type = kTCP; |
| frame.tcp.accumulated_number_of_lost_packets = 0; |
| frame.tcp.receive_window = 16000; |
| |
| return ConstructPacket(header_, QuicFrame(&frame)); |
| } |
| |
| // Returns a newly created packet to send a connection close frame. |
| QuicEncryptedPacket* ConstructClosePacket( |
| QuicPacketSequenceNumber sequence_number, |
| QuicPacketSequenceNumber least_waiting) { |
| InitializeHeader(sequence_number); |
| |
| QuicFrames frames; |
| QuicAckFrame ack(0, least_waiting); |
| QuicConnectionCloseFrame close; |
| close.error_code = QUIC_CONNECTION_TIMED_OUT; |
| close.ack_frame = ack; |
| |
| return ConstructPacket(header_, QuicFrame(&close)); |
| } |
| |
| testing::StrictMock<MockScheduler>* scheduler_; |
| scoped_refptr<TestTaskRunner> runner_; |
| scoped_ptr<QuicConnectionHelper> helper_; |
| scoped_array<MockWrite> mock_writes_; |
| MockClock clock_; |
| scoped_ptr<TestConnection> connection_; |
| testing::StrictMock<MockConnectionVisitor> visitor_; |
| |
| private: |
| void InitializeHeader(QuicPacketSequenceNumber sequence_number) { |
| header_.guid = guid_; |
| header_.packet_sequence_number = sequence_number; |
| header_.flags = PACKET_FLAGS_NONE; |
| header_.fec_group = 0; |
| } |
| |
| QuicEncryptedPacket* ConstructPacket(const QuicPacketHeader& header, |
| const QuicFrame& frame) { |
| QuicFrames frames; |
| frames.push_back(frame); |
| scoped_ptr<QuicPacket> packet( |
| framer_.ConstructFrameDataPacket(header_, frames)); |
| return framer_.EncryptPacket(*packet); |
| } |
| |
| QuicGuid guid_; |
| QuicFramer framer_; |
| QuicPacketCreator creator_; |
| QuicPacketHeader header_; |
| BoundNetLog net_log_; |
| QuicStreamFrame frame_; |
| scoped_ptr<StaticSocketDataProvider> socket_data_; |
| std::vector<PacketToWrite> writes_; |
| }; |
| |
| TEST_F(QuicConnectionHelperTest, GetClock) { |
| EXPECT_EQ(&clock_, helper_->GetClock()); |
| } |
| |
| TEST_F(QuicConnectionHelperTest, IsSendAlarmSet) { |
| EXPECT_FALSE(helper_->IsSendAlarmSet()); |
| } |
| |
| TEST_F(QuicConnectionHelperTest, SetSendAlarm) { |
| helper_->SetSendAlarm(QuicTime::Delta()); |
| EXPECT_TRUE(helper_->IsSendAlarmSet()); |
| } |
| |
| TEST_F(QuicConnectionHelperTest, UnregisterSendAlarmIfRegistered) { |
| helper_->SetSendAlarm(QuicTime::Delta()); |
| helper_->UnregisterSendAlarmIfRegistered() ; |
| EXPECT_FALSE(helper_->IsSendAlarmSet()); |
| } |
| |
| TEST_F(QuicConnectionHelperTest, TestResend) { |
| AddWrite(SYNCHRONOUS, ConstructDataPacket(1)); |
| AddWrite(SYNCHRONOUS, ConstructDataPacket(2)); |
| Initialize(); |
| |
| QuicTime::Delta kDefaultResendTime = QuicTime::Delta::FromMilliseconds(500); |
| QuicTime start = clock_.Now(); |
| |
| EXPECT_CALL(*scheduler_, SentPacket(1, _, false)); |
| // Send a packet. |
| connection_->SendStreamData(1, kData, 0, false, NULL); |
| EXPECT_CALL(*scheduler_, SentPacket(2, _, true)); |
| // Since no ack was received, the resend alarm will fire and resend it. |
| runner_->RunNextTask(); |
| |
| EXPECT_EQ(kDefaultResendTime, clock_.Now().Subtract(start)); |
| EXPECT_TRUE(AtEof()); |
| } |
| |
| TEST_F(QuicConnectionHelperTest, InitialTimeout) { |
| AddWrite(SYNCHRONOUS, ConstructClosePacket(1, 0)); |
| Initialize(); |
| |
| // Verify that a single task was posted. |
| EXPECT_EQ(1u, runner_->tasks()->size()); |
| EXPECT_EQ(base::TimeDelta::FromMicroseconds(kDefaultTimeoutUs), |
| runner_->GetTask(0).delta); |
| |
| EXPECT_CALL(*scheduler_, SentPacket(1, _, false)); |
| // After we run the next task, we should close the connection. |
| EXPECT_CALL(visitor_, ConnectionClose(QUIC_CONNECTION_TIMED_OUT, false)); |
| |
| runner_->RunNextTask(); |
| EXPECT_EQ(QuicTime::FromMicroseconds(kDefaultTimeoutUs), clock_.Now()); |
| EXPECT_FALSE(connection_->connected()); |
| EXPECT_TRUE(AtEof()); |
| } |
| |
| TEST_F(QuicConnectionHelperTest, WritePacketToWire) { |
| AddWrite(SYNCHRONOUS, ConstructDataPacket(1)); |
| Initialize(); |
| |
| int len = GetWrite(0)->length(); |
| int error = 0; |
| EXPECT_EQ(len, helper_->WritePacketToWire(*GetWrite(0), &error)); |
| EXPECT_EQ(0, error); |
| EXPECT_TRUE(AtEof()); |
| } |
| |
| TEST_F(QuicConnectionHelperTest, WritePacketToWireAsync) { |
| AddWrite(ASYNC, ConstructClosePacket(1, 0)); |
| Initialize(); |
| |
| EXPECT_CALL(visitor_, OnCanWrite()).WillOnce(testing::Return(true)); |
| int error = 0; |
| EXPECT_EQ(ERR_IO_PENDING, |
| helper_->WritePacketToWire(*GetWrite(0), &error)); |
| EXPECT_EQ(0, error); |
| MessageLoop::current()->RunUntilIdle(); |
| EXPECT_TRUE(AtEof()); |
| } |
| |
| TEST_F(QuicConnectionHelperTest, TimeoutAfterSend) { |
| AddWrite(SYNCHRONOUS, ConstructAckPacket(1)); |
| AddWrite(SYNCHRONOUS, ConstructFeedbackPacket(2)); |
| AddWrite(SYNCHRONOUS, ConstructClosePacket(3, 1)); |
| Initialize(); |
| |
| EXPECT_TRUE(connection_->connected()); |
| EXPECT_EQ(0u, clock_.Now().ToMicroseconds()); |
| |
| // When we send a packet, the timeout will change to 5000 + kDefaultTimeout. |
| clock_.AdvanceTime(QuicTime::Delta::FromMicroseconds(5000)); |
| EXPECT_EQ(5000u, clock_.Now().ToMicroseconds()); |
| EXPECT_CALL(*scheduler_, SentPacket(1, _, false)); |
| EXPECT_CALL(*scheduler_, SentPacket(2, _, false)); |
| |
| // Send an ack so we don't set the resend alarm. |
| connection_->SendAck(); |
| |
| // The original alarm will fire. We should not time out because we had a |
| // network event at t=5000. The alarm will reregister. |
| runner_->RunNextTask(); |
| |
| EXPECT_EQ(QuicTime::FromMicroseconds(kDefaultTimeoutUs), clock_.Now()); |
| EXPECT_TRUE(connection_->connected()); |
| |
| // This time, we should time out. |
| EXPECT_CALL(visitor_, ConnectionClose(QUIC_CONNECTION_TIMED_OUT, false)); |
| EXPECT_CALL(*scheduler_, SentPacket(3, _, false)); |
| runner_->RunNextTask(); |
| EXPECT_EQ(kDefaultTimeoutUs + 5000, clock_.Now().ToMicroseconds()); |
| EXPECT_FALSE(connection_->connected()); |
| EXPECT_TRUE(AtEof()); |
| } |
| |
| TEST_F(QuicConnectionHelperTest, SendSchedulerDelayThenSend) { |
| AddWrite(SYNCHRONOUS, ConstructDataPacket(1)); |
| Initialize(); |
| |
| // Test that if we send a packet with a delay, it ends up queued. |
| EXPECT_CALL(*scheduler_, TimeUntilSend(true)).WillOnce(testing::Return( |
| QuicTime::Delta::FromMicroseconds(1))); |
| |
| connection_->SendStreamData(1, kData, 0, false, NULL); |
| EXPECT_CALL(*scheduler_, SentPacket(1, _, false)); |
| EXPECT_EQ(1u, connection_->NumQueuedPackets()); |
| |
| // Advance the clock to fire the alarm, and configure the scheduler |
| // to permit the packet to be sent. |
| EXPECT_CALL(*scheduler_, TimeUntilSend(true)).WillOnce(testing::Return( |
| QuicTime::Delta())); |
| EXPECT_CALL(visitor_, OnCanWrite()).WillOnce(testing::Return(true)); |
| runner_->RunNextTask(); |
| EXPECT_EQ(0u, connection_->NumQueuedPackets()); |
| EXPECT_TRUE(AtEof()); |
| } |
| |
| } // namespace test |
| |
| } // namespace net |