| // 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_fec_group.h" |
| |
| #include <algorithm> |
| #include <vector> |
| |
| #include "base/logging.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| |
| using ::testing::_; |
| using base::StringPiece; |
| using std::string; |
| |
| namespace net { |
| |
| namespace { |
| |
| // kData[] and kEntropyFlag[] are indexed by packet numbers, which |
| // start at 1, so their first elements are dummy. |
| const char* kData[] = { |
| "", // dummy |
| // kData[1] must be at least as long as every element of kData[], because |
| // it is used to calculate kDataMaxLen. |
| "abc12345678", "987defg", "ghi12345", "987jlkmno", "mno4567890", |
| "789pqrstuvw", |
| }; |
| // The maximum length of an element of kData. |
| const size_t kDataMaxLen = strlen(kData[1]); |
| // A suitable test data string, whose length is kDataMaxLen. |
| const char* kDataSingle = kData[1]; |
| |
| const bool kEntropyFlag[] = { |
| false, // dummy |
| false, true, true, false, true, true, |
| }; |
| |
| } // namespace |
| |
| class QuicFecGroupTest : public ::testing::Test { |
| protected: |
| void RunTest(size_t num_packets, size_t lost_packet, bool out_of_order) { |
| // kData[] and kEntropyFlag[] are indexed by packet numbers, which |
| // start at 1. |
| DCHECK_GE(arraysize(kData), num_packets); |
| scoped_ptr<char[]> redundancy(new char[kDataMaxLen]); |
| for (size_t i = 0; i < kDataMaxLen; i++) { |
| redundancy[i] = 0x00; |
| } |
| // XOR in the packets. |
| for (size_t packet = 1; packet <= num_packets; ++packet) { |
| for (size_t i = 0; i < kDataMaxLen; i++) { |
| uint8_t byte = i > strlen(kData[packet]) ? 0x00 : kData[packet][i]; |
| redundancy[i] = redundancy[i] ^ byte; |
| } |
| } |
| |
| QuicFecGroup group(1); |
| |
| // If we're out of order, send the FEC packet in the position of the |
| // lost packet. Otherwise send all (non-missing) packets, then FEC. |
| if (out_of_order) { |
| // Update the FEC state for each non-lost packet. |
| for (size_t packet = 1; packet <= num_packets; packet++) { |
| if (packet == lost_packet) { |
| QuicPacketHeader header; |
| header.packet_number = num_packets + 1; |
| header.fec_group = 1; |
| ASSERT_FALSE(group.IsFinished()); |
| ASSERT_TRUE( |
| group.UpdateFec(ENCRYPTION_FORWARD_SECURE, header, |
| StringPiece(redundancy.get(), kDataMaxLen))); |
| } else { |
| QuicPacketHeader header; |
| header.packet_number = packet; |
| header.fec_group = 1; |
| ASSERT_TRUE( |
| group.Update(ENCRYPTION_FORWARD_SECURE, header, kData[packet])); |
| } |
| ASSERT_TRUE(group.CanRevive() == (packet == num_packets)); |
| } |
| } else { |
| // Update the FEC state for each non-lost packet. |
| for (size_t packet = 1; packet <= num_packets; packet++) { |
| if (packet == lost_packet) { |
| continue; |
| } |
| |
| QuicPacketHeader header; |
| header.packet_number = packet; |
| header.fec_group = 1; |
| header.entropy_flag = kEntropyFlag[packet]; |
| ASSERT_TRUE( |
| group.Update(ENCRYPTION_FORWARD_SECURE, header, kData[packet])); |
| ASSERT_FALSE(group.CanRevive()); |
| } |
| |
| ASSERT_FALSE(group.IsFinished()); |
| QuicPacketHeader header; |
| header.packet_number = num_packets + 1; |
| header.fec_group = 1; |
| // Attempt to revive the missing packet. |
| ASSERT_TRUE(group.UpdateFec(ENCRYPTION_FORWARD_SECURE, header, |
| StringPiece(redundancy.get(), kDataMaxLen))); |
| } |
| QuicPacketHeader header; |
| char recovered[kMaxPacketSize]; |
| ASSERT_TRUE(group.CanRevive()); |
| size_t len = group.Revive(&header, recovered, arraysize(recovered)); |
| ASSERT_NE(0u, len) << "Failed to revive packet " << lost_packet |
| << " out of " << num_packets; |
| EXPECT_EQ(lost_packet, header.packet_number) << "Failed to revive packet " |
| << lost_packet << " out of " |
| << num_packets; |
| // Revived packets have an unknown entropy. |
| EXPECT_FALSE(header.entropy_flag); |
| ASSERT_GE(len, strlen(kData[lost_packet])) << "Incorrect length"; |
| for (size_t i = 0; i < strlen(kData[lost_packet]); i++) { |
| EXPECT_EQ(kData[lost_packet][i], recovered[i]); |
| } |
| ASSERT_TRUE(group.IsFinished()); |
| } |
| }; |
| |
| TEST_F(QuicFecGroupTest, UpdateAndRevive) { |
| RunTest(2, 1, false); |
| RunTest(2, 2, false); |
| |
| RunTest(3, 1, false); |
| RunTest(3, 2, false); |
| RunTest(3, 3, false); |
| } |
| |
| TEST_F(QuicFecGroupTest, UpdateAndReviveOutOfOrder) { |
| RunTest(2, 1, true); |
| RunTest(2, 2, true); |
| |
| RunTest(3, 1, true); |
| RunTest(3, 2, true); |
| RunTest(3, 3, true); |
| } |
| |
| TEST_F(QuicFecGroupTest, UpdateFecIfReceivedPacketIsNotCovered) { |
| char data1[] = "abc123"; |
| char redundancy[arraysize(data1)]; |
| for (size_t i = 0; i < arraysize(data1); i++) { |
| redundancy[i] = data1[i]; |
| } |
| |
| QuicFecGroup group(1); |
| |
| QuicPacketHeader header; |
| header.fec_group = 1; |
| header.packet_number = 3; |
| group.Update(ENCRYPTION_FORWARD_SECURE, header, data1); |
| |
| header.packet_number = 2; |
| ASSERT_FALSE(group.UpdateFec(ENCRYPTION_FORWARD_SECURE, header, redundancy)); |
| } |
| |
| TEST_F(QuicFecGroupTest, IsWaitingForPacketBefore) { |
| QuicPacketHeader header; |
| header.fec_group = 3; |
| header.packet_number = 3; |
| |
| QuicFecGroup group(3); |
| ASSERT_TRUE(group.Update(ENCRYPTION_FORWARD_SECURE, header, kDataSingle)); |
| |
| EXPECT_FALSE(group.IsWaitingForPacketBefore(1)); |
| EXPECT_FALSE(group.IsWaitingForPacketBefore(2)); |
| EXPECT_FALSE(group.IsWaitingForPacketBefore(3)); |
| EXPECT_FALSE(group.IsWaitingForPacketBefore(4)); |
| EXPECT_TRUE(group.IsWaitingForPacketBefore(5)); |
| EXPECT_TRUE(group.IsWaitingForPacketBefore(50)); |
| } |
| |
| TEST_F(QuicFecGroupTest, IsWaitingForPacketBeforeWithSeveralPackets) { |
| QuicPacketHeader header; |
| header.fec_group = 3; |
| header.packet_number = 3; |
| |
| QuicFecGroup group(3); |
| ASSERT_TRUE(group.Update(ENCRYPTION_FORWARD_SECURE, header, kDataSingle)); |
| |
| header.packet_number = 7; |
| ASSERT_TRUE(group.Update(ENCRYPTION_FORWARD_SECURE, header, kDataSingle)); |
| |
| header.packet_number = 5; |
| ASSERT_TRUE(group.Update(ENCRYPTION_FORWARD_SECURE, header, kDataSingle)); |
| |
| EXPECT_FALSE(group.IsWaitingForPacketBefore(1)); |
| EXPECT_FALSE(group.IsWaitingForPacketBefore(2)); |
| EXPECT_FALSE(group.IsWaitingForPacketBefore(3)); |
| EXPECT_FALSE(group.IsWaitingForPacketBefore(4)); |
| EXPECT_TRUE(group.IsWaitingForPacketBefore(5)); |
| EXPECT_TRUE(group.IsWaitingForPacketBefore(6)); |
| EXPECT_TRUE(group.IsWaitingForPacketBefore(7)); |
| EXPECT_TRUE(group.IsWaitingForPacketBefore(8)); |
| EXPECT_TRUE(group.IsWaitingForPacketBefore(9)); |
| EXPECT_TRUE(group.IsWaitingForPacketBefore(50)); |
| } |
| |
| TEST_F(QuicFecGroupTest, IsWaitingForPacketBeforeWithFecData1) { |
| QuicFecGroup group(3); |
| |
| QuicPacketHeader header; |
| header.fec_group = 3; |
| header.packet_number = 4; |
| ASSERT_TRUE(group.UpdateFec(ENCRYPTION_FORWARD_SECURE, header, kDataSingle)); |
| |
| EXPECT_FALSE(group.IsWaitingForPacketBefore(1)); |
| EXPECT_FALSE(group.IsWaitingForPacketBefore(2)); |
| EXPECT_FALSE(group.IsWaitingForPacketBefore(3)); |
| EXPECT_TRUE(group.IsWaitingForPacketBefore(4)); |
| EXPECT_TRUE(group.IsWaitingForPacketBefore(5)); |
| EXPECT_TRUE(group.IsWaitingForPacketBefore(6)); |
| EXPECT_TRUE(group.IsWaitingForPacketBefore(50)); |
| } |
| |
| TEST_F(QuicFecGroupTest, IsWaitingForPacketBeforeWithFecData2) { |
| QuicFecGroup group(3); |
| |
| QuicPacketHeader header; |
| header.fec_group = 3; |
| header.packet_number = 3; |
| ASSERT_TRUE(group.Update(ENCRYPTION_FORWARD_SECURE, header, kDataSingle)); |
| |
| header.packet_number = 5; |
| ASSERT_TRUE(group.UpdateFec(ENCRYPTION_FORWARD_SECURE, header, kDataSingle)); |
| |
| EXPECT_FALSE(group.IsWaitingForPacketBefore(1)); |
| EXPECT_FALSE(group.IsWaitingForPacketBefore(2)); |
| EXPECT_FALSE(group.IsWaitingForPacketBefore(3)); |
| EXPECT_FALSE(group.IsWaitingForPacketBefore(4)); |
| EXPECT_TRUE(group.IsWaitingForPacketBefore(5)); |
| EXPECT_TRUE(group.IsWaitingForPacketBefore(6)); |
| EXPECT_TRUE(group.IsWaitingForPacketBefore(50)); |
| } |
| |
| TEST_F(QuicFecGroupTest, EffectiveEncryptionLevel) { |
| QuicFecGroup group(1); |
| EXPECT_EQ(NUM_ENCRYPTION_LEVELS, group.EffectiveEncryptionLevel()); |
| |
| QuicPacketHeader header; |
| header.fec_group = 1; |
| header.packet_number = 5; |
| ASSERT_TRUE(group.Update(ENCRYPTION_INITIAL, header, kDataSingle)); |
| EXPECT_EQ(ENCRYPTION_INITIAL, group.EffectiveEncryptionLevel()); |
| |
| header.packet_number = 7; |
| ASSERT_TRUE(group.UpdateFec(ENCRYPTION_FORWARD_SECURE, header, kDataSingle)); |
| EXPECT_EQ(ENCRYPTION_INITIAL, group.EffectiveEncryptionLevel()); |
| |
| header.packet_number = 3; |
| ASSERT_TRUE(group.Update(ENCRYPTION_NONE, header, kDataSingle)); |
| EXPECT_EQ(ENCRYPTION_NONE, group.EffectiveEncryptionLevel()); |
| } |
| |
| // Test the code assuming it is going to be operating in 128-bit chunks (which |
| // is something that can happen if it is compiled with full vectorization). |
| const QuicByteCount kWordSize = 128 / 8; |
| |
| // A buffer which stores the data with the specified offset with respect to word |
| // alignment boundary. |
| class MisalignedBuffer { |
| public: |
| MisalignedBuffer(const string& original, size_t offset); |
| |
| char* buffer() { return buffer_; } |
| size_t size() { return size_; } |
| |
| StringPiece AsStringPiece() { return StringPiece(buffer_, size_); } |
| |
| private: |
| char* buffer_; |
| size_t size_; |
| |
| scoped_ptr<char[]> allocation_; |
| }; |
| |
| MisalignedBuffer::MisalignedBuffer(const string& original, size_t offset) { |
| CHECK_LT(offset, kWordSize); |
| size_ = original.size(); |
| |
| // Allocate aligned buffer two words larger than needed. |
| const size_t aligned_buffer_size = size_ + 2 * kWordSize; |
| allocation_.reset(new char[aligned_buffer_size]); |
| char* aligned_buffer = |
| allocation_.get() + |
| (kWordSize - reinterpret_cast<uintptr_t>(allocation_.get()) % kWordSize); |
| CHECK_EQ(0u, reinterpret_cast<uintptr_t>(aligned_buffer) % kWordSize); |
| |
| buffer_ = aligned_buffer + offset; |
| CHECK_EQ(offset, reinterpret_cast<uintptr_t>(buffer_) % kWordSize); |
| memcpy(buffer_, original.data(), size_); |
| } |
| |
| // Checks whether XorBuffers works correctly with buffers aligned in various |
| // ways. |
| TEST(XorBuffersTest, XorBuffers) { |
| const string longer_data = |
| "Having to care about memory alignment can be incredibly frustrating."; |
| const string shorter_data = "strict aliasing"; |
| |
| // Compute the reference XOR using simpler slow way. |
| string output_reference; |
| for (size_t i = 0; i < longer_data.size(); i++) { |
| char shorter_byte = i < shorter_data.size() ? shorter_data[i] : 0; |
| output_reference.push_back(longer_data[i] ^ shorter_byte); |
| } |
| |
| // Check whether XorBuffers works correctly for all possible misalignments. |
| for (size_t offset_shorter = 0; offset_shorter < kWordSize; |
| offset_shorter++) { |
| for (size_t offset_longer = 0; offset_longer < kWordSize; offset_longer++) { |
| // Prepare the misaligned buffer. |
| MisalignedBuffer longer(longer_data, offset_longer); |
| MisalignedBuffer shorter(shorter_data, offset_shorter); |
| |
| // XOR the buffers and compare the result with the reference. |
| QuicFecGroup::XorBuffers(shorter.buffer(), shorter.size(), |
| longer.buffer()); |
| EXPECT_EQ(output_reference, longer.AsStringPiece()); |
| } |
| } |
| } |
| |
| } // namespace net |