| /* |
| * 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 <algorithm> |
| #include <iterator> |
| #include <limits> |
| #include <memory> |
| #include <optional> |
| #include <tuple> |
| #include <vector> |
| |
| #include "absl/memory/memory.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<uint16_t>::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<bool>(), random_.Rand<bool>()}); |
| } |
| |
| void VerifyAssociations(const RtpSequenceNumberMap& uut, |
| const std::vector<Association>& associations) { |
| return VerifyAssociations(uut, associations.begin(), associations.end()); |
| } |
| |
| void VerifyAssociations( |
| const RtpSequenceNumberMap& uut, |
| std::vector<Association>::const_iterator associations_begin, |
| std::vector<Association>::const_iterator associations_end) { |
| RTC_DCHECK(associations_begin < associations_end); |
| ASSERT_EQ(static_cast<size_t>(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<std::tuple<size_t, uint16_t>> { |
| protected: |
| RtpSequenceNumberMapTestWithParams() = default; |
| ~RtpSequenceNumberMapTestWithParams() override = default; |
| |
| std::vector<Association> ProduceRandomAssociationSequence( |
| size_t association_count, |
| uint16_t first_sequence_number, |
| bool allow_obsoletion) { |
| std::vector<Association> associations; |
| associations.reserve(association_count); |
| |
| if (association_count == 0) { |
| return associations; |
| } |
| |
| associations.emplace_back( |
| first_sequence_number, |
| Info(0, random_.Rand<bool>(), random_.Rand<bool>())); |
| |
| 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<bool>(), random_.Rand<bool>())); |
| } |
| |
| 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<Association> 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<Association> 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<uint16_t>(kFirstSequenceNumber + 0u)), |
| Info(kTimestamp, true, false)); |
| EXPECT_EQ(uut.Get(static_cast<uint16_t>(kFirstSequenceNumber + 1u)), |
| Info(kTimestamp, false, false)); |
| EXPECT_EQ(uut.Get(static_cast<uint16_t>(kFirstSequenceNumber + 2u)), |
| Info(kTimestamp, false, true)); |
| #ifdef _MSC_VER |
| #pragma warning(pop) |
| #endif |
| } |
| |
| TEST_F(RtpSequenceNumberMapTest, |
| GetObsoleteSequenceNumberReturnsNullOptSingleValueObsoleted) { |
| RtpSequenceNumberMap uut(kMaxPossibleMaxEntries); |
| |
| const std::vector<Association> 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<Association> 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<uint16_t>( |
| associations[associations.size() - 1].sequence_number), |
| static_cast<uint16_t>( |
| 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<Association> 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<Association> 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<Association> 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<Association> 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<uint16_t>(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 |