/* * Copyright (c) 2019 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "modules/rtp_rtcp/source/rtp_sequence_number_map.h" #include #include #include #include #include #include #include "absl/memory/memory.h" #include "absl/types/optional.h" #include "rtc_base/checks.h" #include "rtc_base/numerics/sequence_number_util.h" #include "rtc_base/random.h" #include "test/gtest.h" namespace webrtc { namespace { using Info = RtpSequenceNumberMap::Info; constexpr uint16_t kUint16Max = std::numeric_limits::max(); constexpr size_t kMaxPossibleMaxEntries = 1 << 15; // Just a named pair. struct Association final { Association(uint16_t sequence_number, Info info) : sequence_number(sequence_number), info(info) {} uint16_t sequence_number; Info info; }; class RtpSequenceNumberMapTest : public ::testing::Test { protected: RtpSequenceNumberMapTest() : random_(1983) {} ~RtpSequenceNumberMapTest() override = default; Association CreateAssociation(uint16_t sequence_number, uint32_t timestamp) { return Association(sequence_number, {timestamp, random_.Rand(), random_.Rand()}); } void VerifyAssociations(const RtpSequenceNumberMap& uut, const std::vector& associations) { return VerifyAssociations(uut, associations.begin(), associations.end()); } void VerifyAssociations( const RtpSequenceNumberMap& uut, std::vector::const_iterator associations_begin, std::vector::const_iterator associations_end) { RTC_DCHECK(associations_begin < associations_end); ASSERT_EQ(static_cast(associations_end - associations_begin), uut.AssociationCountForTesting()); for (auto association = associations_begin; association != associations_end; ++association) { EXPECT_EQ(uut.Get(association->sequence_number), association->info); } } // Allows several variations of the same test; definition next to the tests. void GetObsoleteSequenceNumberReturnsNullOptMultipleEntriesObsoleted( bool with_wrap_around, bool last_element_kept); // Allows several variations of the same test; definition next to the tests. void RepeatedSequenceNumberInvalidatesAll(size_t index_of_repeated); // Allows several variations of the same test; definition next to the tests. void MaxEntriesReachedAtSameTimeAsObsoletionOfItem(size_t max_entries, size_t obsoleted_count); Random random_; }; class RtpSequenceNumberMapTestWithParams : public RtpSequenceNumberMapTest, public ::testing::WithParamInterface> { protected: RtpSequenceNumberMapTestWithParams() = default; ~RtpSequenceNumberMapTestWithParams() override = default; std::vector ProduceRandomAssociationSequence( size_t association_count, uint16_t first_sequence_number, bool allow_obsoletion) { std::vector associations; associations.reserve(association_count); if (association_count == 0) { return associations; } associations.emplace_back( first_sequence_number, Info(0, random_.Rand(), random_.Rand())); for (size_t i = 1; i < association_count; ++i) { const uint16_t sequence_number = associations[i - 1].sequence_number + random_.Rand(1, 100); RTC_DCHECK(allow_obsoletion || AheadOf(sequence_number, associations[0].sequence_number)); const uint32_t timestamp = associations[i - 1].info.timestamp + random_.Rand(1, 10000); associations.emplace_back( sequence_number, Info(timestamp, random_.Rand(), random_.Rand())); } return associations; } }; INSTANTIATE_TEST_SUITE_P(All, RtpSequenceNumberMapTestWithParams, ::testing::Combine( // Association count. ::testing::Values(1, 2, 100), // First sequence number. ::testing::Values(0, 100, kUint16Max - 100, kUint16Max - 1, kUint16Max))); TEST_F(RtpSequenceNumberMapTest, GetBeforeAssociationsRecordedReturnsNullOpt) { RtpSequenceNumberMap uut(kMaxPossibleMaxEntries); constexpr uint16_t kArbitrarySequenceNumber = 321; EXPECT_FALSE(uut.Get(kArbitrarySequenceNumber)); } // Version #1 - any old unknown sequence number. TEST_F(RtpSequenceNumberMapTest, GetUnknownSequenceNumberReturnsNullOpt1) { RtpSequenceNumberMap uut(kMaxPossibleMaxEntries); constexpr uint16_t kKnownSequenceNumber = 10; constexpr uint32_t kArbitrary = 987; uut.InsertPacket(kKnownSequenceNumber, {kArbitrary, false, false}); constexpr uint16_t kUnknownSequenceNumber = kKnownSequenceNumber + 1; EXPECT_FALSE(uut.Get(kUnknownSequenceNumber)); } // Version #2 - intentionally pick a value in the range of currently held // values, so as to trigger lower_bound / upper_bound. TEST_F(RtpSequenceNumberMapTest, GetUnknownSequenceNumberReturnsNullOpt2) { RtpSequenceNumberMap uut(kMaxPossibleMaxEntries); const std::vector setup = {CreateAssociation(1000, 500), // CreateAssociation(1020, 501)}; for (const Association& association : setup) { uut.InsertPacket(association.sequence_number, association.info); } EXPECT_FALSE(uut.Get(1001)); } TEST_P(RtpSequenceNumberMapTestWithParams, GetKnownSequenceNumberReturnsCorrectValue) { RtpSequenceNumberMap uut(kMaxPossibleMaxEntries); const size_t association_count = std::get<0>(GetParam()); const uint16_t first_sequence_number = std::get<1>(GetParam()); const std::vector associations = ProduceRandomAssociationSequence(association_count, first_sequence_number, /*allow_obsoletion=*/false); for (const Association& association : associations) { uut.InsertPacket(association.sequence_number, association.info); } VerifyAssociations(uut, associations); } TEST_F(RtpSequenceNumberMapTest, InsertFrameOnSinglePacketFrame) { RtpSequenceNumberMap uut(kMaxPossibleMaxEntries); constexpr uint16_t kSequenceNumber = 888; constexpr uint32_t kTimestamp = 98765; uut.InsertFrame(kSequenceNumber, 1, kTimestamp); EXPECT_EQ(uut.Get(kSequenceNumber), Info(kTimestamp, true, true)); } TEST_F(RtpSequenceNumberMapTest, InsertFrameOnMultiPacketFrameNoWrapAround) { RtpSequenceNumberMap uut(kMaxPossibleMaxEntries); constexpr uint16_t kFirstSequenceNumber = 0; constexpr uint32_t kTimestamp = 98765; uut.InsertFrame(kFirstSequenceNumber, 3, kTimestamp); EXPECT_EQ(uut.Get(kFirstSequenceNumber + 0), Info(kTimestamp, true, false)); EXPECT_EQ(uut.Get(kFirstSequenceNumber + 1), Info(kTimestamp, false, false)); EXPECT_EQ(uut.Get(kFirstSequenceNumber + 2), Info(kTimestamp, false, true)); } TEST_F(RtpSequenceNumberMapTest, InsertFrameOnMultiPacketFrameWithWrapAround) { RtpSequenceNumberMap uut(kMaxPossibleMaxEntries); constexpr uint16_t kFirstSequenceNumber = kUint16Max; constexpr uint32_t kTimestamp = 98765; uut.InsertFrame(kFirstSequenceNumber, 3, kTimestamp); // Suppress "truncation of constant value" warning; wrap-around is intended. #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable : 4309) #endif EXPECT_EQ(uut.Get(static_cast(kFirstSequenceNumber + 0u)), Info(kTimestamp, true, false)); EXPECT_EQ(uut.Get(static_cast(kFirstSequenceNumber + 1u)), Info(kTimestamp, false, false)); EXPECT_EQ(uut.Get(static_cast(kFirstSequenceNumber + 2u)), Info(kTimestamp, false, true)); #ifdef _MSC_VER #pragma warning(pop) #endif } TEST_F(RtpSequenceNumberMapTest, GetObsoleteSequenceNumberReturnsNullOptSingleValueObsoleted) { RtpSequenceNumberMap uut(kMaxPossibleMaxEntries); const std::vector associations = { CreateAssociation(0, 10), // CreateAssociation(0x8000, 20), // CreateAssociation(0x8001u, 30)}; uut.InsertPacket(associations[0].sequence_number, associations[0].info); // First association not yet obsolete, and therefore remembered. RTC_DCHECK(AheadOf(associations[1].sequence_number, associations[0].sequence_number)); uut.InsertPacket(associations[1].sequence_number, associations[1].info); VerifyAssociations(uut, {associations[0], associations[1]}); // Test focus - new entry obsoletes first entry. RTC_DCHECK(!AheadOf(associations[2].sequence_number, associations[0].sequence_number)); uut.InsertPacket(associations[2].sequence_number, associations[2].info); VerifyAssociations(uut, {associations[1], associations[2]}); } void RtpSequenceNumberMapTest:: GetObsoleteSequenceNumberReturnsNullOptMultipleEntriesObsoleted( bool with_wrap_around, bool last_element_kept) { RtpSequenceNumberMap uut(kMaxPossibleMaxEntries); std::vector associations; if (with_wrap_around) { associations = {CreateAssociation(kUint16Max - 1, 10), // CreateAssociation(kUint16Max, 20), // CreateAssociation(0, 30), // CreateAssociation(1, 40), // CreateAssociation(2, 50)}; } else { associations = {CreateAssociation(1, 10), // CreateAssociation(2, 20), // CreateAssociation(3, 30), // CreateAssociation(4, 40), // CreateAssociation(5, 50)}; } for (auto association : associations) { uut.InsertPacket(association.sequence_number, association.info); } VerifyAssociations(uut, associations); // Define a new association that will obsolete either all previous entries, // or all previous entries except for the last one, depending on the // parameter instantiation of this test. RTC_DCHECK_EQ( static_cast( associations[associations.size() - 1].sequence_number), static_cast( associations[associations.size() - 2].sequence_number + 1u)); uint16_t new_sequence_number; if (last_element_kept) { new_sequence_number = associations[associations.size() - 1].sequence_number + 0x8000; RTC_DCHECK(AheadOf(new_sequence_number, associations[associations.size() - 1].sequence_number)); } else { new_sequence_number = associations[associations.size() - 1].sequence_number + 0x8001; RTC_DCHECK(!AheadOf(new_sequence_number, associations[associations.size() - 1].sequence_number)); } RTC_DCHECK(!AheadOf(new_sequence_number, associations[associations.size() - 2].sequence_number)); // Record the new association. const Association new_association = CreateAssociation(new_sequence_number, 60); uut.InsertPacket(new_association.sequence_number, new_association.info); // Make sure all obsoleted elements were removed. const size_t obsoleted_count = associations.size() - (last_element_kept ? 1 : 0); for (size_t i = 0; i < obsoleted_count; ++i) { EXPECT_FALSE(uut.Get(associations[i].sequence_number)); } // Make sure the expected elements were not removed, and return the // expected value. if (last_element_kept) { EXPECT_EQ(uut.Get(associations.back().sequence_number), associations.back().info); } EXPECT_EQ(uut.Get(new_association.sequence_number), new_association.info); } TEST_F(RtpSequenceNumberMapTest, GetObsoleteSequenceNumberReturnsNullOptMultipleEntriesObsoleted0) { const bool with_wrap_around = false; const bool last_element_kept = false; GetObsoleteSequenceNumberReturnsNullOptMultipleEntriesObsoleted( with_wrap_around, last_element_kept); } TEST_F(RtpSequenceNumberMapTest, GetObsoleteSequenceNumberReturnsNullOptMultipleEntriesObsoleted1) { const bool with_wrap_around = true; const bool last_element_kept = false; GetObsoleteSequenceNumberReturnsNullOptMultipleEntriesObsoleted( with_wrap_around, last_element_kept); } TEST_F(RtpSequenceNumberMapTest, GetObsoleteSequenceNumberReturnsNullOptMultipleEntriesObsoleted2) { const bool with_wrap_around = false; const bool last_element_kept = true; GetObsoleteSequenceNumberReturnsNullOptMultipleEntriesObsoleted( with_wrap_around, last_element_kept); } TEST_F(RtpSequenceNumberMapTest, GetObsoleteSequenceNumberReturnsNullOptMultipleEntriesObsoleted3) { const bool with_wrap_around = true; const bool last_element_kept = true; GetObsoleteSequenceNumberReturnsNullOptMultipleEntriesObsoleted( with_wrap_around, last_element_kept); } void RtpSequenceNumberMapTest::RepeatedSequenceNumberInvalidatesAll( size_t index_of_repeated) { RtpSequenceNumberMap uut(kMaxPossibleMaxEntries); const std::vector setup = {CreateAssociation(100, 500), // CreateAssociation(101, 501), // CreateAssociation(102, 502)}; RTC_DCHECK_LT(index_of_repeated, setup.size()); for (const Association& association : setup) { uut.InsertPacket(association.sequence_number, association.info); } const Association new_association = CreateAssociation(setup[index_of_repeated].sequence_number, 503); uut.InsertPacket(new_association.sequence_number, new_association.info); // All entries from setup invalidated. // New entry valid and mapped to new value. for (size_t i = 0; i < setup.size(); ++i) { if (i == index_of_repeated) { EXPECT_EQ(uut.Get(new_association.sequence_number), new_association.info); } else { EXPECT_FALSE(uut.Get(setup[i].sequence_number)); } } } TEST_F(RtpSequenceNumberMapTest, RepeatedSequenceNumberInvalidatesAllRepeatFirst) { RepeatedSequenceNumberInvalidatesAll(0); } TEST_F(RtpSequenceNumberMapTest, RepeatedSequenceNumberInvalidatesAllRepeatMiddle) { RepeatedSequenceNumberInvalidatesAll(1); } TEST_F(RtpSequenceNumberMapTest, RepeatedSequenceNumberInvalidatesAllRepeatLast) { RepeatedSequenceNumberInvalidatesAll(2); } TEST_F(RtpSequenceNumberMapTest, SequenceNumberInsideMemorizedRangeInvalidatesAll) { RtpSequenceNumberMap uut(kMaxPossibleMaxEntries); const std::vector setup = {CreateAssociation(1000, 500), // CreateAssociation(1020, 501), // CreateAssociation(1030, 502)}; for (const Association& association : setup) { uut.InsertPacket(association.sequence_number, association.info); } const Association new_association = CreateAssociation(1010, 503); uut.InsertPacket(new_association.sequence_number, new_association.info); // All entries from setup invalidated. // New entry valid and mapped to new value. for (size_t i = 0; i < setup.size(); ++i) { EXPECT_FALSE(uut.Get(setup[i].sequence_number)); } EXPECT_EQ(uut.Get(new_association.sequence_number), new_association.info); } TEST_F(RtpSequenceNumberMapTest, MaxEntriesObserved) { constexpr size_t kMaxEntries = 100; RtpSequenceNumberMap uut(kMaxEntries); std::vector associations; associations.reserve(kMaxEntries); uint32_t timestamp = 789; for (size_t i = 0; i < kMaxEntries; ++i) { associations.push_back(CreateAssociation(i, ++timestamp)); uut.InsertPacket(associations[i].sequence_number, associations[i].info); } VerifyAssociations(uut, associations); // Sanity. const Association new_association = CreateAssociation(kMaxEntries, ++timestamp); uut.InsertPacket(new_association.sequence_number, new_association.info); associations.push_back(new_association); // The +1 is for `new_association`. const size_t kExpectedAssociationCount = 3 * kMaxEntries / 4 + 1; const auto expected_begin = std::prev(associations.end(), kExpectedAssociationCount); VerifyAssociations(uut, expected_begin, associations.end()); } void RtpSequenceNumberMapTest::MaxEntriesReachedAtSameTimeAsObsoletionOfItem( size_t max_entries, size_t obsoleted_count) { RtpSequenceNumberMap uut(max_entries); std::vector associations; associations.reserve(max_entries); uint32_t timestamp = 789; for (size_t i = 0; i < max_entries; ++i) { associations.push_back(CreateAssociation(i, ++timestamp)); uut.InsertPacket(associations[i].sequence_number, associations[i].info); } VerifyAssociations(uut, associations); // Sanity. const uint16_t new_association_sequence_number = static_cast(obsoleted_count) + (1 << 15); const Association new_association = CreateAssociation(new_association_sequence_number, ++timestamp); uut.InsertPacket(new_association.sequence_number, new_association.info); associations.push_back(new_association); // The +1 is for `new_association`. const size_t kExpectedAssociationCount = std::min(3 * max_entries / 4, max_entries - obsoleted_count) + 1; const auto expected_begin = std::prev(associations.end(), kExpectedAssociationCount); VerifyAssociations(uut, expected_begin, associations.end()); } // Version #1 - #(obsoleted entries) < #(entries after paring down below max). TEST_F(RtpSequenceNumberMapTest, MaxEntriesReachedAtSameTimeAsObsoletionOfItem1) { constexpr size_t kMaxEntries = 100; constexpr size_t kObsoletionTarget = (kMaxEntries / 4) - 1; MaxEntriesReachedAtSameTimeAsObsoletionOfItem(kMaxEntries, kObsoletionTarget); } // Version #2 - #(obsoleted entries) == #(entries after paring down below max). TEST_F(RtpSequenceNumberMapTest, MaxEntriesReachedAtSameTimeAsObsoletionOfItem2) { constexpr size_t kMaxEntries = 100; constexpr size_t kObsoletionTarget = kMaxEntries / 4; MaxEntriesReachedAtSameTimeAsObsoletionOfItem(kMaxEntries, kObsoletionTarget); } // Version #3 - #(obsoleted entries) > #(entries after paring down below max). TEST_F(RtpSequenceNumberMapTest, MaxEntriesReachedAtSameTimeAsObsoletionOfItem3) { constexpr size_t kMaxEntries = 100; constexpr size_t kObsoletionTarget = (kMaxEntries / 4) + 1; MaxEntriesReachedAtSameTimeAsObsoletionOfItem(kMaxEntries, kObsoletionTarget); } } // namespace } // namespace webrtc