diff --git a/webrtc/test/fuzzers/transport_feedback_packet_loss_tracker_fuzzer.cc b/webrtc/test/fuzzers/transport_feedback_packet_loss_tracker_fuzzer.cc index 837ae883f6..bb2418c81a 100644 --- a/webrtc/test/fuzzers/transport_feedback_packet_loss_tracker_fuzzer.cc +++ b/webrtc/test/fuzzers/transport_feedback_packet_loss_tracker_fuzzer.cc @@ -19,6 +19,29 @@ namespace webrtc { namespace { +template +T FuzzInput(const uint8_t** data, size_t* size) { + RTC_CHECK_GE(*size, sizeof(T)); + T rc = ByteReader::ReadBigEndian(*data); + *data += sizeof(T); + *size -= sizeof(T); + return rc; +} + +size_t FuzzInRange(const uint8_t** data, + size_t* size, + size_t lower, + size_t upper) { + // Achieve a close-to-uniform distribution. + RTC_CHECK_LE(lower, upper); + RTC_CHECK_LT(upper - lower, 1 << (8 * sizeof(uint16_t))); + const size_t range = upper - lower; + const uint16_t fuzzed = FuzzInput(data, size); + const size_t offset = (static_cast(fuzzed) / 0x10000) * (range + 1); + RTC_CHECK_LE(offset, range); // (fuzzed <= 0xffff) -> (offset < range + 1) + return lower + offset; +} + class TransportFeedbackGenerator { public: explicit TransportFeedbackGenerator(rtc::ArrayView data) @@ -63,7 +86,7 @@ class TransportFeedbackGenerator { private: template bool ReadData(T* value) { - RTC_DCHECK(!ended_); + RTC_CHECK(!ended_); if (data_idx_ + sizeof(T) > data_.size()) { ended_ = true; return false; @@ -81,25 +104,25 @@ class TransportFeedbackGenerator { } // namespace void FuzzOneInput(const uint8_t* data, size_t size) { - if (size < sizeof(uint32_t)) { + if (size < 3 * sizeof(uint16_t)) { return; } constexpr size_t kSeqNumHalf = 0x8000u; - const size_t window_size_1 = std::min( - kSeqNumHalf, - std::max(1, ByteReader::ReadBigEndian(data))); - data += sizeof(uint16_t); - const size_t window_size_2 = std::min( - kSeqNumHalf, - std::max(1, ByteReader::ReadBigEndian(data))); - data += sizeof(uint16_t); - size -= 2 * sizeof(uint16_t); + + // 0x8000 >= max_window_size >= plr_min_num_packets > rplr_min_num_pairs >= 1 + // (The distribution isn't uniform, but it's enough; more would be overkill.) + const size_t max_window_size = FuzzInRange(&data, &size, 2, kSeqNumHalf); + const size_t plr_min_num_packets = + FuzzInRange(&data, &size, 2, max_window_size); + const size_t rplr_min_num_pairs = + FuzzInRange(&data, &size, 1, plr_min_num_packets - 1); TransportFeedbackPacketLossTracker tracker( - std::min(window_size_1, window_size_2), - std::max(window_size_1, window_size_2)); + max_window_size, plr_min_num_packets, rplr_min_num_pairs); + TransportFeedbackGenerator feedback_generator( rtc::ArrayView(data, size)); + while (!feedback_generator.ended()) { rtcp::TransportFeedback feedback; feedback_generator.GetNextTransportFeedback(&feedback); diff --git a/webrtc/voice_engine/transport_feedback_packet_loss_tracker.cc b/webrtc/voice_engine/transport_feedback_packet_loss_tracker.cc index becf964609..c05b044814 100644 --- a/webrtc/voice_engine/transport_feedback_packet_loss_tracker.cc +++ b/webrtc/voice_engine/transport_feedback_packet_loss_tracker.cc @@ -21,26 +21,40 @@ namespace { constexpr uint16_t kSeqNumHalf = 0x8000u; constexpr uint16_t kSeqNumQuarter = kSeqNumHalf / 2; constexpr size_t kMaxConsecutiveOldReports = 4; + +void UpdateCounter(size_t* counter, bool increment) { + if (increment) { + RTC_DCHECK_LT(*counter, std::numeric_limits::max()); + ++(*counter); + } else { + RTC_DCHECK_GT(*counter, 0); + --(*counter); + } +} + } // namespace namespace webrtc { TransportFeedbackPacketLossTracker::TransportFeedbackPacketLossTracker( - size_t min_window_size, - size_t max_window_size) - : min_window_size_(min_window_size), - max_window_size_(max_window_size), - ref_packet_status_(packet_status_window_.begin()) { - RTC_DCHECK_GT(min_window_size, 0); - RTC_DCHECK_GE(max_window_size_, min_window_size_); - RTC_DCHECK_LE(max_window_size_, kSeqNumHalf); + size_t max_window_size, + size_t plr_min_num_packets, + size_t rplr_min_num_pairs) + : max_window_size_(max_window_size), + ref_packet_status_(packet_status_window_.begin()), + plr_state_(plr_min_num_packets), + rplr_state_(rplr_min_num_pairs) { + RTC_DCHECK_GT(plr_min_num_packets, 0); + RTC_DCHECK_GE(max_window_size, plr_min_num_packets); + RTC_DCHECK_LE(max_window_size, kSeqNumHalf); + RTC_DCHECK_GT(rplr_min_num_pairs, 0); + RTC_DCHECK_GT(max_window_size, rplr_min_num_pairs); Reset(); } void TransportFeedbackPacketLossTracker::Reset() { - num_received_packets_ = 0; - num_lost_packets_ = 0; - num_consecutive_losses_ = 0; + plr_state_.Reset(); + rplr_state_.Reset(); num_consecutive_old_reports_ = 0; packet_status_window_.clear(); ref_packet_status_ = packet_status_window_.begin(); @@ -104,16 +118,14 @@ void TransportFeedbackPacketLossTracker::OnReceivedTransportFeedback( } } -bool TransportFeedbackPacketLossTracker::GetPacketLossRates( - float* packet_loss_rate, - float* consecutive_packet_loss_rate) const { - const size_t total = num_lost_packets_ + num_received_packets_; - if (total < min_window_size_) - return false; - *packet_loss_rate = static_cast(num_lost_packets_) / total; - *consecutive_packet_loss_rate = - static_cast(num_consecutive_losses_) / total; - return true; +rtc::Optional +TransportFeedbackPacketLossTracker::GetPacketLossRate() const { + return plr_state_.GetMetric(); +} + +rtc::Optional +TransportFeedbackPacketLossTracker::GetRecoverablePacketLossRate() const { + return rplr_state_.GetMetric(); } void TransportFeedbackPacketLossTracker::InsertPacketStatus(uint16_t seq_num, @@ -124,7 +136,7 @@ void TransportFeedbackPacketLossTracker::InsertPacketStatus(uint16_t seq_num, if (!ret.first->second && received) { // If older status said that the packet was lost but newer one says it // is received, we take the newer one. - UndoPacketStatus(ret.first); + UpdateMetrics(ret.first, false); ret.first->second = received; } else { // If the value is unchanged or if older status said that the packet was @@ -132,66 +144,63 @@ void TransportFeedbackPacketLossTracker::InsertPacketStatus(uint16_t seq_num, return; } } - ApplyPacketStatus(ret.first); + UpdateMetrics(ret.first, true); if (packet_status_window_.size() == 1) ref_packet_status_ = ret.first; } void TransportFeedbackPacketLossTracker::RemoveOldestPacketStatus() { - UndoPacketStatus(ref_packet_status_); + UpdateMetrics(ref_packet_status_, false); const auto it = ref_packet_status_; ref_packet_status_ = NextPacketStatus(it); packet_status_window_.erase(it); } -void TransportFeedbackPacketLossTracker::ApplyPacketStatus( - PacketStatusIterator it) { +void TransportFeedbackPacketLossTracker::UpdateMetrics( + PacketStatusIterator it, + bool apply /* false = undo */) { RTC_DCHECK(it != packet_status_window_.end()); + UpdatePlr(it, apply); + UpdateRplr(it, apply); +} + +void TransportFeedbackPacketLossTracker::UpdatePlr( + PacketStatusIterator it, + bool apply /* false = undo */) { + // Record or undo reception status of currently handled packet. if (it->second) { - ++num_received_packets_; + UpdateCounter(&plr_state_.num_received_packets_, apply); } else { - ++num_lost_packets_; - const auto& next = NextPacketStatus(it); - if (next != packet_status_window_.end() && - next->first == static_cast(it->first + 1) && !next->second) { - // Feedback shows that the next packet has been lost. Since this - // packet is lost, we increase the consecutive loss counter. - ++num_consecutive_losses_; - } - if (it != ref_packet_status_) { - const auto& pre = PreviousPacketStatus(it); - if (pre->first == static_cast(it->first - 1) && !pre->second) { - // Feedback shows that the previous packet has been lost. Since this - // packet is lost, we increase the consecutive loss counter. - ++num_consecutive_losses_; - } - } + UpdateCounter(&plr_state_.num_lost_packets_, apply); } } -void TransportFeedbackPacketLossTracker::UndoPacketStatus( - PacketStatusIterator it) { - RTC_DCHECK(it != packet_status_window_.end()); - if (it->second) { - RTC_DCHECK_GT(num_received_packets_, 0); - --num_received_packets_; - } else { - RTC_DCHECK_GT(num_lost_packets_, 0); - --num_lost_packets_; - const auto& next = NextPacketStatus(it); - if (next != packet_status_window_.end() && - next->first == static_cast(it->first + 1) && !next->second) { - RTC_DCHECK_GT(num_consecutive_losses_, 0); - --num_consecutive_losses_; - } - if (it != ref_packet_status_) { - const auto& pre = PreviousPacketStatus(it); - if (pre->first == static_cast(it->first - 1) && !pre->second) { - RTC_DCHECK_GT(num_consecutive_losses_, 0); - --num_consecutive_losses_; +void TransportFeedbackPacketLossTracker::UpdateRplr( + PacketStatusIterator it, + bool apply /* false = undo */) { + // Previous packet and current packet might compose a known pair. + // If so, the RPLR state needs to be updated accordingly. + if (it != ref_packet_status_) { + const auto& prev = PreviousPacketStatus(it); + if (prev->first == static_cast(it->first - 1)) { + UpdateCounter(&rplr_state_.num_known_pairs_, apply); + if (!prev->second && it->second) { + UpdateCounter( + &rplr_state_.num_recoverable_losses_, apply); } } } + + // Current packet and next packet might compose a pair. + // If so, the RPLR state needs to be updated accordingly. + const auto& next = NextPacketStatus(it); + if (next != packet_status_window_.end() && + next->first == static_cast(it->first + 1)) { + UpdateCounter(&rplr_state_.num_known_pairs_, apply); + if (!it->second && next->second) { + UpdateCounter(&rplr_state_.num_recoverable_losses_, apply); + } + } } TransportFeedbackPacketLossTracker::PacketStatusIterator @@ -236,42 +245,70 @@ TransportFeedbackPacketLossTracker::NextPacketStatus(PacketStatusIterator it) { // to unit test. void TransportFeedbackPacketLossTracker::Validate() const { // Testing only! RTC_CHECK_LE(packet_status_window_.size(), max_window_size_); - RTC_CHECK_GE(num_lost_packets_, num_consecutive_losses_); RTC_CHECK_EQ(packet_status_window_.size(), - num_lost_packets_ + num_received_packets_); + plr_state_.num_lost_packets_ + plr_state_.num_received_packets_); + RTC_CHECK_LE(rplr_state_.num_recoverable_losses_, + rplr_state_.num_known_pairs_); + RTC_CHECK_LE(rplr_state_.num_known_pairs_, + packet_status_window_.size() - 1); size_t received_packets = 0; size_t lost_packets = 0; - size_t consecutive_losses = 0; + size_t known_status_pairs = 0; + size_t recoverable_losses = 0; if (!packet_status_window_.empty()) { PacketStatusIterator it = ref_packet_status_; - bool pre_lost = false; - uint16_t pre_seq_num = it->first - 1; do { if (it->second) { ++received_packets; } else { ++lost_packets; - if (pre_lost && pre_seq_num == static_cast(it->first - 1)) - ++consecutive_losses; + } + + auto next = std::next(it); + if (next == packet_status_window_.end()) + next = packet_status_window_.begin(); + + if (next != ref_packet_status_ && + next->first == static_cast(it->first + 1)) { + ++known_status_pairs; + if (!it->second && next->second) + ++recoverable_losses; } RTC_CHECK_LT(ForwardDiff(ReferenceSequenceNumber(), it->first), kSeqNumHalf); - pre_lost = !it->second; - pre_seq_num = it->first; - - ++it; - if (it == packet_status_window_.end()) - it = packet_status_window_.begin(); + it = next; } while (it != ref_packet_status_); } - RTC_CHECK_EQ(num_received_packets_, received_packets); - RTC_CHECK_EQ(num_lost_packets_, lost_packets); - RTC_CHECK_EQ(num_consecutive_losses_, consecutive_losses); + RTC_CHECK_EQ(plr_state_.num_received_packets_, received_packets); + RTC_CHECK_EQ(plr_state_.num_lost_packets_, lost_packets); + RTC_CHECK_EQ(rplr_state_.num_known_pairs_, known_status_pairs); + RTC_CHECK_EQ(rplr_state_.num_recoverable_losses_, recoverable_losses); +} + +rtc::Optional +TransportFeedbackPacketLossTracker::PlrState::GetMetric() const { + const size_t total = num_lost_packets_ + num_received_packets_; + if (total < min_num_packets_) { + return rtc::Optional(); + } else { + return rtc::Optional( + static_cast(num_lost_packets_) / total); + } +} + +rtc::Optional +TransportFeedbackPacketLossTracker::RplrState::GetMetric() const { + if (num_known_pairs_ < min_num_pairs_) { + return rtc::Optional(); + } else { + return rtc::Optional( + static_cast(num_recoverable_losses_) / num_known_pairs_); + } } } // namespace webrtc diff --git a/webrtc/voice_engine/transport_feedback_packet_loss_tracker.h b/webrtc/voice_engine/transport_feedback_packet_loss_tracker.h index e5626eb9b2..77af2fa7da 100644 --- a/webrtc/voice_engine/transport_feedback_packet_loss_tracker.h +++ b/webrtc/voice_engine/transport_feedback_packet_loss_tracker.h @@ -24,21 +24,25 @@ class TransportFeedback; class TransportFeedbackPacketLossTracker final { public: - // Up to |max_window_size| latest packet statuses wil be used for calculating - // the packet loss metrics. When less than |min_window_size| samples are - // available for making a reliable estimation, GetPacketLossRates() will - // return false to indicate packet loss metrics are not ready. - TransportFeedbackPacketLossTracker(size_t min_window_size, - size_t max_window_size); + // * Up to |max_window_size| latest packet statuses will be used for + // calculating the packet loss metrics. + // * PLR (packet-loss-rate) is reliably computable once the statuses of + // |plr_min_num_packets| packets are known. + // * RPLR (recoverable-packet-loss-rate) is reliably computable once the + // statuses of |rplr_min_num_pairs| pairs are known. + TransportFeedbackPacketLossTracker(size_t max_window_size, + size_t plr_min_num_packets, + size_t rplr_min_num_pairs); void OnReceivedTransportFeedback(const rtcp::TransportFeedback& feedback); - // Returns true if packet loss rate and packet loss episode duration are ready - // and assigns respective values to |*packet_loss_rate| and - // |*consecutive_packet_loss_rate|. Continuous packet loss rate is defined as - // the probability of losing two adjacent packets. - bool GetPacketLossRates(float* packet_loss_rate, - float* consecutive_packet_loss_rate) const; + // Returns the packet loss rate, if the window has enough packet statuses to + // reliably compute it. Otherwise, returns empty. + rtc::Optional GetPacketLossRate() const; + + // Returns the first-order-FEC recoverable packet loss rate, if the window has + // enough status pairs to reliably compute it. Otherwise, returns empty. + rtc::Optional GetRecoverablePacketLossRate() const; // Verifies that the internal states are correct. Only used for tests. void Validate() const; @@ -51,6 +55,7 @@ class TransportFeedbackPacketLossTracker final { typedef PacketStatus::const_iterator PacketStatusIterator; void Reset(); + // ReferenceSequenceNumber() provides a sequence number that defines the // order of packet reception info stored in |packet_status_window_|. In // particular, given any sequence number |x|, @@ -60,22 +65,58 @@ class TransportFeedbackPacketLossTracker final { bool IsOldSequenceNumber(uint16_t seq_num) const; void InsertPacketStatus(uint16_t seq_num, bool received); void RemoveOldestPacketStatus(); - void ApplyPacketStatus(PacketStatusIterator it); - void UndoPacketStatus(PacketStatusIterator it); + + void UpdateMetrics(PacketStatusIterator it, bool apply /* false = undo */); + void UpdatePlr(PacketStatusIterator it, bool apply /* false = undo */); + void UpdateRplr(PacketStatusIterator it, bool apply /* false = undo */); + PacketStatusIterator PreviousPacketStatus(PacketStatusIterator it); PacketStatusIterator NextPacketStatus(PacketStatusIterator it); - const size_t min_window_size_; const size_t max_window_size_; PacketStatus packet_status_window_; // |ref_packet_status_| points to the oldest item in |packet_status_window_|. PacketStatusIterator ref_packet_status_; - size_t num_received_packets_; - size_t num_lost_packets_; - size_t num_consecutive_losses_; - size_t num_consecutive_old_reports_; + // Packet-loss-rate calculation (lost / all-known-packets). + struct PlrState { + explicit PlrState(size_t min_num_packets) + : min_num_packets_(min_num_packets) { + Reset(); + } + void Reset() { + num_received_packets_ = 0; + num_lost_packets_ = 0; + } + rtc::Optional GetMetric() const; + const size_t min_num_packets_; + size_t num_received_packets_; + size_t num_lost_packets_; + } plr_state_; + + // Recoverable packet loss calculation (first-order-FEC recoverable). + struct RplrState { + explicit RplrState(size_t min_num_pairs) + : min_num_pairs_(min_num_pairs) { + Reset(); + } + void Reset() { + num_known_pairs_ = 0; + num_recoverable_losses_ = 0; + } + rtc::Optional GetMetric() const; + // Recoverable packets are those which were lost, but immediately followed + // by a properly received packet. If that second packet carried FEC, + // the data from the former (lost) packet could be recovered. + // The RPLR is calculated as the fraction of such pairs (lost-received) out + // of all pairs of consecutive acked packets. + const size_t min_num_pairs_; + size_t num_known_pairs_; + size_t num_recoverable_losses_; + } rplr_state_; + + size_t num_consecutive_old_reports_; // TODO(elad.alon): Upcoming CL removes. }; } // namespace webrtc diff --git a/webrtc/voice_engine/transport_feedback_packet_loss_tracker_unittest.cc b/webrtc/voice_engine/transport_feedback_packet_loss_tracker_unittest.cc index f639923b8c..eb229a09d2 100644 --- a/webrtc/voice_engine/transport_feedback_packet_loss_tracker_unittest.cc +++ b/webrtc/voice_engine/transport_feedback_packet_loss_tracker_unittest.cc @@ -46,175 +46,216 @@ void AddTransportFeedbackAndValidate( test_feedback.AddReceivedPacket(sequence_num, kBaseTimeUs); ++sequence_num; } + + // TransportFeedback imposes some limitations on what constitutes a legal + // status vector. For instance, the vector cannot terminate in a lost + // packet. Make sure all limitations are abided by. + RTC_CHECK_EQ(base_sequence_num, test_feedback.GetBaseSequence()); + const auto& vec = test_feedback.GetStatusVector(); + RTC_CHECK_EQ(reception_status_vec.size(), vec.size()); + for (size_t i = 0; i < reception_status_vec.size(); i++) { + RTC_CHECK_EQ(reception_status_vec[i], + vec[i] != TransportFeedback::StatusSymbol::kNotReceived); + } + tracker->OnReceivedTransportFeedback(test_feedback); tracker->Validate(); } +// Checks that validty is as expected. If valid, checks also that +// value is as expected. +void ValidatePacketLossStatistics( + const TransportFeedbackPacketLossTracker& tracker, + rtc::Optional expected_plr, + rtc::Optional expected_rplr) { + // Comparing the rtc::Optional directly would have given concise code, + // but less readable error messages. + rtc::Optional plr = tracker.GetPacketLossRate(); + EXPECT_EQ(static_cast(expected_plr), static_cast(plr)); + if (expected_plr && plr) { + EXPECT_EQ(*expected_plr, *plr); + } + + rtc::Optional rplr = tracker.GetRecoverablePacketLossRate(); + EXPECT_EQ(static_cast(expected_rplr), static_cast(rplr)); + if (expected_rplr && rplr) { + EXPECT_EQ(*expected_rplr, *rplr); + } +} + +// Convenience function for when both are valid, and explicitly stating +// the rtc::Optional constructor is just cumbersome. +void ValidatePacketLossStatistics( + const TransportFeedbackPacketLossTracker& tracker, + float expected_plr, + float expected_rplr) { + ValidatePacketLossStatistics(tracker, + rtc::Optional(expected_plr), + rtc::Optional(expected_rplr)); +} + } // namespace // Sanity check on an empty window. TEST(TransportFeedbackPacketLossTrackerTest, EmptyWindow) { std::unique_ptr feedback; - float plr = 0.0f; // Packet-loss-rate - float cplr = 0.0f; // Consecutive-packet-loss-rate + TransportFeedbackPacketLossTracker tracker(10, 5, 5); - TransportFeedbackPacketLossTracker tracker(5, 10); - - // PLR and CPLR reported as unknown before reception of first feedback. - EXPECT_FALSE(tracker.GetPacketLossRates(&plr, &cplr)); + // PLR and RPLR reported as unknown before reception of first feedback. + ValidatePacketLossStatistics(tracker, + rtc::Optional(), + rtc::Optional()); } // Sanity check on partially filled window. -TEST(TransportFeedbackPacketLossTrackerTest, PartiallyFilledWindow) { +TEST(TransportFeedbackPacketLossTrackerTest, PlrPartiallyFilledWindow) { for (uint16_t base : kBases) { - float plr = 0.0f; // Packet-loss-rate - float cplr = 0.0f; // Consecutive-packet-loss-rate - TransportFeedbackPacketLossTracker tracker(5, 10); + TransportFeedbackPacketLossTracker tracker(10, 5, 4); - // PLR and CPLR reported as unknown before minimum window size reached. + // PLR unknown before minimum window size reached. + // RPLR unknown before minimum pairs reached. // Expected window contents: [] -> [1001]. AddTransportFeedbackAndValidate(&tracker, base, {true, false, false, true}); - EXPECT_FALSE(tracker.GetPacketLossRates(&plr, &cplr)); + ValidatePacketLossStatistics(tracker, + rtc::Optional(), + rtc::Optional()); } } -// Sanity check on minimum filled window. -TEST(TransportFeedbackPacketLossTrackerTest, MinimumFilledWindow) { +// Sanity check on minimum filled window - PLR known, RPLR unknown. +TEST(TransportFeedbackPacketLossTrackerTest, PlrMinimumFilledWindow) { for (uint16_t base : kBases) { - float plr = 0.0f; // Packet-loss-rate - float cplr = 0.0f; // Consecutive-packet-loss-rate - TransportFeedbackPacketLossTracker tracker(5, 10); + TransportFeedbackPacketLossTracker tracker(10, 5, 5); - // PLR and CPLR correctly calculated after minimum window size reached. + // PLR correctly calculated after minimum window size reached. + // RPLR not necessarily known at that time (not if min-pairs not reached). // Expected window contents: [] -> [10011]. AddTransportFeedbackAndValidate(&tracker, base, {true, false, false, true, true}); - EXPECT_TRUE(tracker.GetPacketLossRates(&plr, &cplr)); - EXPECT_EQ(plr, 2.0f / 5.0f); - EXPECT_EQ(cplr, 1.0f / 5.0f); + ValidatePacketLossStatistics(tracker, + rtc::Optional(2.0f / 5.0f), + rtc::Optional()); } } -// Additional reports update PLR and CPLR. +// Sanity check on minimum filled window - PLR unknown, RPLR known. +TEST(TransportFeedbackPacketLossTrackerTest, RplrMinimumFilledWindow) { + for (uint16_t base : kBases) { + TransportFeedbackPacketLossTracker tracker(10, 6, 4); + + // RPLR correctly calculated after minimum pairs reached. + // PLR not necessarily known at that time (not if min window not reached). + // Expected window contents: [] -> [10011]. + AddTransportFeedbackAndValidate(&tracker, base, + {true, false, false, true, true}); + ValidatePacketLossStatistics(tracker, + rtc::Optional(), + rtc::Optional(1.0f / 4.0f)); + } +} + +// Additional reports update PLR and RPLR. TEST(TransportFeedbackPacketLossTrackerTest, ExtendWindow) { for (uint16_t base : kBases) { - float plr = 0.0f; // Packet-loss-rate - float cplr = 0.0f; // Consecutive-packet-loss-rate - TransportFeedbackPacketLossTracker tracker(5, 20); + TransportFeedbackPacketLossTracker tracker(20, 5, 5); // Expected window contents: [] -> [10011]. AddTransportFeedbackAndValidate(&tracker, base, {true, false, false, true, true}); + ValidatePacketLossStatistics(tracker, + rtc::Optional(2.0f / 5.0f), + rtc::Optional()); - // Expected window contents: [10011] -> [10011-10101]. + // Expected window contents: [10011] -> [1001110101]. AddTransportFeedbackAndValidate(&tracker, base + 5, {true, false, true, false, true}); + ValidatePacketLossStatistics(tracker, 4.0f / 10.0f, 3.0f / 9.0f); - // Expected window contents: [10011-10101] -> [10011-10101-10001]. - AddTransportFeedbackAndValidate(&tracker, base + 10, + // Expected window contents: [1001110101] -> [1001110101-GAP-10001]. + AddTransportFeedbackAndValidate(&tracker, base + 20, {true, false, false, false, true}); - - EXPECT_TRUE(tracker.GetPacketLossRates(&plr, &cplr)); - EXPECT_EQ(plr, 7.0f / 15.0f); - EXPECT_EQ(cplr, 3.0f / 15.0f); + ValidatePacketLossStatistics(tracker, 7.0f / 15.0f, 4.0f / 13.0f); } } +// All packets correctly received. TEST(TransportFeedbackPacketLossTrackerTest, AllReceived) { for (uint16_t base : kBases) { - float plr = 0.0f; // Packet-loss-rate - float cplr = 0.0f; // Consecutive-packet-loss-rate - TransportFeedbackPacketLossTracker tracker(5, 10); + TransportFeedbackPacketLossTracker tracker(10, 5, 4); - // PLR and CPLR correctly calculated after minimum window size reached. + // PLR and RPLR correctly calculated after minimum window size reached. // Expected window contents: [] -> [11111]. AddTransportFeedbackAndValidate(&tracker, base, {true, true, true, true, true}); - EXPECT_TRUE(tracker.GetPacketLossRates(&plr, &cplr)); - EXPECT_EQ(plr, 0.0f); - EXPECT_EQ(cplr, 0.0f); + ValidatePacketLossStatistics(tracker, 0.0f, 0.0f); } } // Repeated reports are ignored. TEST(TransportFeedbackPacketLossTrackerTest, ReportRepetition) { for (uint16_t base : kBases) { - float plr = 0.0f; // Packet-loss-rate - float cplr = 0.0f; // Consecutive-packet-loss-rate - TransportFeedbackPacketLossTracker tracker(5, 10); + TransportFeedbackPacketLossTracker tracker(10, 5, 4); // Expected window contents: [] -> [10011]. AddTransportFeedbackAndValidate(&tracker, base, {true, false, false, true, true}); + ValidatePacketLossStatistics(tracker, 2.0f / 5.0f, 1.0f / 4.0f); // Repeat entire previous feedback // Expected window contents: [10011] -> [10011]. AddTransportFeedbackAndValidate(&tracker, base, {true, false, false, true, true}); - EXPECT_TRUE(tracker.GetPacketLossRates(&plr, &cplr)); - EXPECT_EQ(plr, 2.0f / 5.0f); - EXPECT_EQ(cplr, 1.0f / 5.0f); + ValidatePacketLossStatistics(tracker, 2.0f / 5.0f, 1.0f / 4.0f); } } // Report overlap. TEST(TransportFeedbackPacketLossTrackerTest, ReportOverlap) { for (uint16_t base : kBases) { - float plr = 0.0f; // Packet-loss-rate - float cplr = 0.0f; // Consecutive-packet-loss-rate - TransportFeedbackPacketLossTracker tracker(5, 10); + TransportFeedbackPacketLossTracker tracker(10, 5, 1); // Expected window contents: [] -> [10011]. AddTransportFeedbackAndValidate(&tracker, base, {true, false, false, true, true}); + ValidatePacketLossStatistics(tracker, 2.0f / 5.0f, 1.0f / 4.0f); - // Expected window contents: [10011] -> [10011-01]. + // Expected window contents: [10011] -> [1001101]. AddTransportFeedbackAndValidate(&tracker, base + 3, {true, true, false, true}); - EXPECT_TRUE(tracker.GetPacketLossRates(&plr, &cplr)); - EXPECT_EQ(plr, 3.0f / 7.0f); - EXPECT_EQ(cplr, 1.0f / 7.0f); + ValidatePacketLossStatistics(tracker, 3.0f / 7.0f, 2.0f / 6.0f); } } // Report conflict. TEST(TransportFeedbackPacketLossTrackerTest, ReportConflict) { for (uint16_t base : kBases) { - float plr = 0.0f; // Packet-loss-rate - float cplr = 0.0f; // Consecutive-packet-loss-rate - TransportFeedbackPacketLossTracker tracker(5, 10); + TransportFeedbackPacketLossTracker tracker(10, 5, 4); // Expected window contents: [] -> [01001]. AddTransportFeedbackAndValidate(&tracker, base, {false, true, false, false, true}); + ValidatePacketLossStatistics(tracker, 3.0f / 5.0f, 2.0f / 4.0f); // Expected window contents: [01001] -> [11101]. // While false->true will be applied, true -> false will be ignored. AddTransportFeedbackAndValidate(&tracker, base, {true, false, true}); - - EXPECT_TRUE(tracker.GetPacketLossRates(&plr, &cplr)); - EXPECT_EQ(plr, 1.0f / 5.0f); - EXPECT_EQ(cplr, 0.0f / 5.0f); + ValidatePacketLossStatistics(tracker, 1.0f / 5.0f, 1.0f / 4.0f); } } // Skipped packets treated as unknown (not lost). TEST(TransportFeedbackPacketLossTrackerTest, SkippedPackets) { for (uint16_t base : kBases) { - float plr = 0.0f; // Packet-loss-rate - float cplr = 0.0f; // Consecutive-packet-loss-rate - TransportFeedbackPacketLossTracker tracker(5, 10); + TransportFeedbackPacketLossTracker tracker(10, 5, 1); // Expected window contents: [] -> [10011]. AddTransportFeedbackAndValidate(&tracker, base, {true, false, false, true, true}); + ValidatePacketLossStatistics(tracker, 2.0f / 5.0f, 1.0f / 4.0f); - // Expected window contents: [10011] -> [10011-101]. + // Expected window contents: [10011] -> [10011-GAP-101]. AddTransportFeedbackAndValidate(&tracker, base + 100, {true, false, true}); - - EXPECT_TRUE(tracker.GetPacketLossRates(&plr, &cplr)); - EXPECT_EQ(plr, 3.0f / 8.0f); - EXPECT_EQ(cplr, 1.0f / 8.0f); + ValidatePacketLossStatistics(tracker, 3.0f / 8.0f, 2.0f / 6.0f); } } @@ -222,237 +263,221 @@ TEST(TransportFeedbackPacketLossTrackerTest, SkippedPackets) { // starts discarding after that. TEST(TransportFeedbackPacketLossTrackerTest, MaxWindowSize) { for (uint16_t base : kBases) { - float plr = 0.0f; // Packet-loss-rate - float cplr = 0.0f; // Consecutive-packet-loss-rate - TransportFeedbackPacketLossTracker tracker(10, 10); + TransportFeedbackPacketLossTracker tracker(10, 10, 1); - // Expected window contents: [] -> [10101-00001]. + // Up to max-window-size retained. + // Expected window contents: [] -> [1010100001]. AddTransportFeedbackAndValidate( &tracker, base, {true, false, true, false, true, false, false, false, false, true}); - - // Up to max-window-size retained. - EXPECT_TRUE(tracker.GetPacketLossRates(&plr, &cplr)); - EXPECT_EQ(plr, 6.0f / 10.0f); - EXPECT_EQ(cplr, 3.0f / 10.0f); - - // Expected window contents: [10101-00001] -> [00001-10111]. - AddTransportFeedbackAndValidate(&tracker, base + 10, - {true, false, true, true, true}); + ValidatePacketLossStatistics(tracker, 6.0f / 10.0f, 3.0f / 9.0f); // After max-window-size, older entries discarded to accommodate newer ones. - EXPECT_TRUE(tracker.GetPacketLossRates(&plr, &cplr)); - EXPECT_EQ(plr, 5.0f / 10.0f); - EXPECT_EQ(cplr, 3.0f / 10.0f); + // Expected window contents: [1010100001] -> [0000110111]. + AddTransportFeedbackAndValidate(&tracker, base + 10, + {true, false, true, true, true}); + ValidatePacketLossStatistics(tracker, 5.0f / 10.0f, 2.0f / 9.0f); } } // Inserting into the middle of a full window works correctly. TEST(TransportFeedbackPacketLossTrackerTest, InsertIntoMiddle) { for (uint16_t base : kBases) { - float plr = 0.0f; // Packet-loss-rate - float cplr = 0.0f; // Consecutive-packet-loss-rate - TransportFeedbackPacketLossTracker tracker(10, 10); + TransportFeedbackPacketLossTracker tracker(10, 5, 1); // Expected window contents: [] -> [10101]. AddTransportFeedbackAndValidate(&tracker, base, {true, false, true, false, true}); + ValidatePacketLossStatistics(tracker, 2.0f / 5.0f, 2.0f / 4.0f); - // Expected window contents: [10101] -> [10101-10001]. + // Expected window contents: [10101] -> [10101-GAP-10001]. AddTransportFeedbackAndValidate(&tracker, base + 100, {true, false, false, false, true}); - - // Setup sanity - EXPECT_TRUE(tracker.GetPacketLossRates(&plr, &cplr)); - EXPECT_EQ(plr, 5.0f / 10.0f); - EXPECT_EQ(cplr, 2.0f / 10.0f); + ValidatePacketLossStatistics(tracker, 5.0f / 10.0f, 3.0f / 8.0f); // Insert into the middle of this full window - it discards the older data. - // Expected window contents: [10101-10001] -> [11111-10001]. + // Expected window contents: [10101-GAP-10001] -> [11111-GAP-10001]. AddTransportFeedbackAndValidate(&tracker, base + 50, {true, true, true, true, true}); - EXPECT_TRUE(tracker.GetPacketLossRates(&plr, &cplr)); - EXPECT_EQ(plr, 3.0f / 10.0f); - EXPECT_EQ(cplr, 2.0f / 10.0f); + ValidatePacketLossStatistics(tracker, 3.0f / 10.0f, 1.0f / 8.0f); } } -// Test the behavior of TransportFeedbackPacketLossTracker when there is a gap -// of more than 0x4000 in sequence number, i.e., 1/4 of total sequence numbers. -// Since the sequence number is used in a circular manner, i.e., after 0xffff, -// the sequence number wraps back to 0x0000, we refer to 1/4 of total sequence -// numbers as a quadrant. In this test, e.g., three transport feedbacks are -// added, whereas the 2nd and 3rd lie in the second quadrant w.r.t. the 1st -// feedback. +// Inserting into the middle of a full window works correctly. +TEST(TransportFeedbackPacketLossTrackerTest, InsertionCompletesTwoPairs) { + for (uint16_t base : kBases) { + TransportFeedbackPacketLossTracker tracker(15, 5, 1); + + // Expected window contents: [] -> [10111]. + AddTransportFeedbackAndValidate(&tracker, base, + {true, false, true, true, true}); + ValidatePacketLossStatistics(tracker, 1.0f / 5.0f, 1.0f / 4.0f); + + // Expected window contents: [10111] -> [10111-GAP-10101]. + AddTransportFeedbackAndValidate(&tracker, base + 7, + {true, false, true, false, true}); + ValidatePacketLossStatistics(tracker, 3.0f / 10.0f, 3.0f / 8.0f); + + // Insert in between, closing the gap completely. + // Expected window contents: [10111-GAP-10101] -> [101111010101]. + AddTransportFeedbackAndValidate(&tracker, base + 5, {false, true}); + ValidatePacketLossStatistics(tracker, 4.0f / 12.0f, 4.0f / 11.0f); + } +} + +// Entries in the second quadrant treated like those in the first. +// The sequence number is used in a looped manner. 0xFFFF is followed by 0x0000. +// In many tests, we divide the circle of sequence number into 4 quadrants, and +// verify the behavior of TransportFeedbackPacketLossTracker over them. TEST(TransportFeedbackPacketLossTrackerTest, SecondQuadrant) { for (uint16_t base : kBases) { - float plr = 0.0f; // Packet-loss-rate - float cplr = 0.0f; // Consecutive-packet-loss-rate - TransportFeedbackPacketLossTracker tracker(5, 20); + TransportFeedbackPacketLossTracker tracker(20, 5, 1); // Expected window contents: [] -> [10011]. AddTransportFeedbackAndValidate(&tracker, base, {true, false, false, true, true}); + ValidatePacketLossStatistics(tracker, 2.0f / 5.0f, 1.0f / 4.0f); // Window *does* get updated with inputs from quadrant #2. - // Expected window contents: [10011] -> [10011-1]. + // Expected window contents: [10011] -> [100111]. AddTransportFeedbackAndValidate(&tracker, base + 0x4321, {true}); - EXPECT_TRUE(tracker.GetPacketLossRates(&plr, &cplr)); - EXPECT_EQ(plr, 2.0f / 6.0f); - EXPECT_EQ(cplr, 1.0f / 6.0f); + ValidatePacketLossStatistics(tracker, 2.0f / 6.0f, 1.0f / 4.0f); // Correct recognition of quadrant #2: up to, but not including, base + // 0x8000 - // Expected window contents: [10011-1] -> [10011-11]. + // Expected window contents: [100111] -> [1001111]. AddTransportFeedbackAndValidate(&tracker, base + 0x7fff, {true}); - EXPECT_TRUE(tracker.GetPacketLossRates(&plr, &cplr)); - EXPECT_EQ(plr, 2.0f / 7.0f); - EXPECT_EQ(cplr, 1.0f / 7.0f); + ValidatePacketLossStatistics(tracker, 2.0f / 7.0f, 1.0f / 4.0f); } } // Insertion into the third quadrant moves the base of the window. TEST(TransportFeedbackPacketLossTrackerTest, ThirdQuadrantMovesBase) { for (uint16_t base : kBases) { - float plr = 0.0f; // Packet-loss-rate - float cplr = 0.0f; // Consecutive-packet-loss-rate - TransportFeedbackPacketLossTracker tracker(5, 20); + TransportFeedbackPacketLossTracker tracker(20, 5, 1); // Seed the test. - // Expected window contents: [] -> [10011-01]. + // Expected window contents: [] -> [1001101]. AddTransportFeedbackAndValidate( &tracker, base, {true, false, false, true, true, false, true}); + ValidatePacketLossStatistics(tracker, 3.0f / 7.0f, 2.0f / 6.0f); // Quadrant #3 begins at base + 0x8000. It triggers moving the window so // that // at least one (oldest) report shifts out of window. - // Expected window contents: [10011-01] -> [10110-01]. + // Expected window contents: [1001101] -> [101-GAP-1001]. AddTransportFeedbackAndValidate(&tracker, base + 0x8000, {true, false, false, true}); - EXPECT_TRUE(tracker.GetPacketLossRates(&plr, &cplr)); - EXPECT_EQ(plr, 3.0f / 7.0f); - EXPECT_EQ(cplr, 1.0f / 7.0f); + ValidatePacketLossStatistics(tracker, 3.0f / 7.0f, 2.0f / 5.0f); // The base can move more than once, because the minimum quadrant-1 packets // were dropped out of the window, and some remain. + // Expected window contents: [101-GAP-1001] -> [1-GAP-100111]. AddTransportFeedbackAndValidate(&tracker, base + 0x8000 + 4, {true, true}); - EXPECT_TRUE(tracker.GetPacketLossRates(&plr, &cplr)); - EXPECT_EQ(plr, 2.0f / 7.0f); - EXPECT_EQ(cplr, 1.0f / 7.0f); + ValidatePacketLossStatistics(tracker, 2.0f / 7.0f, 1.0f / 5.0f); } } // After the base has moved due to insertion into the third quadrant, it is // still possible to insert into the middle of the window and obtain the correct -// PLR and CPLR. Insertion into the middle before the max window size has been +// PLR and RPLR. Insertion into the middle before the max window size has been // achieved does not cause older packets to be dropped. TEST(TransportFeedbackPacketLossTrackerTest, InsertIntoMiddleAfterBaseMove) { for (uint16_t base : kBases) { - float plr = 0.0f; // Packet-loss-rate - float cplr = 0.0f; // Consecutive-packet-loss-rate - TransportFeedbackPacketLossTracker tracker(5, 20); + TransportFeedbackPacketLossTracker tracker(20, 5, 1); // Seed the test. - // Expected window contents: [] -> [10011-01]. + // Expected window contents: [] -> [1001101]. AddTransportFeedbackAndValidate( &tracker, base, {true, false, false, true, true, false, true}); + ValidatePacketLossStatistics(tracker, 3.0f / 7.0f, 2.0f / 6.0f); - // Expected window contents: [10011-01] -> [10110-01]. + // Expected window contents: [1001101] -> [101-GAP-1001]. AddTransportFeedbackAndValidate(&tracker, base + 0x8000, {true, false, false, true}); + ValidatePacketLossStatistics(tracker, 3.0f / 7.0f, 2.0f / 5.0f); // Inserting into the middle still works after the base has shifted. - // Expected window contents: [10110-01] -> [10110-01011-001]. + // Expected window contents: + // [101-GAP-1001] -> [101-GAP-100101-GAP-1001] AddTransportFeedbackAndValidate(&tracker, base + 0x5000, {true, false, false, true, false, true}); - EXPECT_TRUE(tracker.GetPacketLossRates(&plr, &cplr)); - EXPECT_EQ(plr, 6.0f / 13.0f); - EXPECT_EQ(cplr, 2.0f / 13.0f); + ValidatePacketLossStatistics(tracker, 6.0f / 13.0f, 4.0f / 10.0f); // The base can keep moving after inserting into the middle. - // Expected window contents: [10110-01011-001] -> [11001-01100-111]. + // Expected window contents: + // [101-GAP-100101-GAP-1001] -> [1-GAP-100101-GAP-100111]. AddTransportFeedbackAndValidate(&tracker, base + 0x8000 + 4, {true, true}); - EXPECT_TRUE(tracker.GetPacketLossRates(&plr, &cplr)); - EXPECT_EQ(plr, 5.0f / 13.0f); - EXPECT_EQ(cplr, 2.0f / 13.0f); + ValidatePacketLossStatistics(tracker, 5.0f / 13.0f, 3.0f / 10.0f); } } // After moving the base of the window, the max window size is still observed. TEST(TransportFeedbackPacketLossTrackerTest, ThirdQuadrantObservesMaxWindow) { for (uint16_t base : kBases) { - float plr = 0.0f; // Packet-loss-rate - float cplr = 0.0f; // Consecutive-packet-loss-rate - TransportFeedbackPacketLossTracker tracker(10, 15); + TransportFeedbackPacketLossTracker tracker(15, 10, 1); - // Expected window contents: [] -> [10011-10101]. + // Expected window contents: [] -> [1001110101]. AddTransportFeedbackAndValidate( &tracker, base, {true, false, false, true, true, true, false, true, false, true}); + ValidatePacketLossStatistics(tracker, 4.0f / 10.0f, 3.0f / 9.0f); - // Expected window contents: [10011-10101] -> [11101-01101]. + // Expected window contents: [1001110101] -> [1110101-GAP-101]. AddTransportFeedbackAndValidate(&tracker, base + 0x8000, {true, false, true}); + ValidatePacketLossStatistics(tracker, 3.0f / 10.0f, 3.0f / 8.0f); // Push into middle until max window is reached. - // Expected window contents: [11101-01101] -> [11101-01100-01101]. + // Expected window contents: + // [1110101-GAP-101] -> [1110101-GAP-10001-GAP-101] AddTransportFeedbackAndValidate(&tracker, base + 0x4000, {true, false, false, false, true}); - - // Setup sanity - EXPECT_TRUE(tracker.GetPacketLossRates(&plr, &cplr)); - EXPECT_EQ(plr, 6.0f / 15.0f); - EXPECT_EQ(cplr, 2.0f / 15.0f); + ValidatePacketLossStatistics(tracker, 6.0f / 15.0f, 4.0f / 12.0f); // Pushing new packets into the middle would discard older packets. - // Expected window contents: [11101-01100-01101] -> [01011-00011-01101]. + // Expected window contents: + // [1110101-GAP-10001-GAP-101] -> [0101-GAP-10001101-GAP-101] AddTransportFeedbackAndValidate(&tracker, base + 0x4000 + 5, {true, false, true}); - EXPECT_TRUE(tracker.GetPacketLossRates(&plr, &cplr)); - EXPECT_EQ(plr, 7.0f / 15.0f); - EXPECT_EQ(cplr, 2.0f / 15.0f); + ValidatePacketLossStatistics(tracker, 7.0f / 15.0f, 5.0f / 12.0f); } } // A new feedback in quadrant #3 might shift enough old feedbacks out of window, -// that we'd go back to an unknown PLR and CPLR. +// that we'd go back to an unknown PLR and RPLR. TEST(TransportFeedbackPacketLossTrackerTest, QuadrantThreeMovedBaseMinWindow) { for (uint16_t base : kBases) { - float plr = 0.0f; // Packet-loss-rate - float cplr = 0.0f; // Consecutive-packet-loss-rate - TransportFeedbackPacketLossTracker tracker(5, 20); + TransportFeedbackPacketLossTracker tracker(20, 5, 1); - // Expected window contents: [] -> [10011-10101]. + // Expected window contents: [] -> [1001110101]. AddTransportFeedbackAndValidate( &tracker, base, {true, false, false, true, true, true, false, true, false, true}); - EXPECT_TRUE( - tracker.GetPacketLossRates(&plr, &cplr)); // Min window reached. + ValidatePacketLossStatistics(tracker, 4.0f / 10.0f, 3.0f / 9.0f); // A new feedback in quadrant #3 might shift enough old feedbacks out of - // window, that we'd go back to an unknown PLR and CPLR. This *doesn't* + // window, that we'd go back to an unknown PLR and RPLR. This *doesn't* // necessarily mean all of the old ones were discarded, though. - // Expected window contents: [10011-10101] -> [0111]. + // Expected window contents: [1001110101] -> [01-GAP-11]. AddTransportFeedbackAndValidate(&tracker, base + 0x8006, {true, true}); - EXPECT_FALSE(tracker.GetPacketLossRates(&plr, &cplr)); + ValidatePacketLossStatistics(tracker, + rtc::Optional(), // Still invalid. + rtc::Optional(1.0f / 2.0f)); // Inserting in the middle shows that though some of the elements were // ejected, some were retained. - // Expected window contents: [] -> [01101-11]. + // Expected window contents: [01-GAP-11] -> [01-GAP-1001-GAP-11]. AddTransportFeedbackAndValidate(&tracker, base + 0x4000, - {true, false, true}); - EXPECT_TRUE(tracker.GetPacketLossRates(&plr, &cplr)); - EXPECT_EQ(plr, 2.0f / 7.0f); - EXPECT_EQ(cplr, 0.0f / 7.0f); + {true, false, false, true}); + ValidatePacketLossStatistics(tracker, 3.0f / 8.0f, 2.0f / 5.0f); } } // Quadrant four reports ignored for up to kMaxConsecutiveOldReports times. TEST(TransportFeedbackPacketLossTrackerTest, QuadrantFourInitiallyIgnored) { for (uint16_t base : kBases) { - float plr = 0.0f; // Packet-loss-rate - float cplr = 0.0f; // Consecutive-packet-loss-rate - TransportFeedbackPacketLossTracker tracker(5, 20); + TransportFeedbackPacketLossTracker tracker(20, 5, 1); // Expected window contents: [] -> [10011]. AddTransportFeedbackAndValidate(&tracker, base, @@ -463,9 +488,7 @@ TEST(TransportFeedbackPacketLossTrackerTest, QuadrantFourInitiallyIgnored) { // Expected window contents: [10011] -> [10011]. for (size_t i = 0; i < kMaxConsecutiveOldReports; i++) { AddTransportFeedbackAndValidate(&tracker, base + 0xc000, {true, true}); - EXPECT_TRUE(tracker.GetPacketLossRates(&plr, &cplr)); - EXPECT_EQ(plr, 2.0f / 5.0f); - EXPECT_EQ(cplr, 1.0f / 5.0f); + ValidatePacketLossStatistics(tracker, 2.0f / 5.0f, 1.0f / 4.0f); } } } @@ -473,9 +496,7 @@ TEST(TransportFeedbackPacketLossTrackerTest, QuadrantFourInitiallyIgnored) { // Receiving a packet from quadrant #1 resets the counter for quadrant #4. TEST(TransportFeedbackPacketLossTrackerTest, QuadrantFourCounterResetByQ1) { for (uint16_t base : kBases) { - float plr = 0.0f; // Packet-loss-rate - float cplr = 0.0f; // Consecutive-packet-loss-rate - TransportFeedbackPacketLossTracker tracker(5, 20); + TransportFeedbackPacketLossTracker tracker(20, 5, 1); // Expected window contents: [] -> [10011]. AddTransportFeedbackAndValidate(&tracker, base, @@ -486,22 +507,29 @@ TEST(TransportFeedbackPacketLossTrackerTest, QuadrantFourCounterResetByQ1) { // Expected window contents: [10011] -> [10011]. for (size_t i = 0; i < kMaxConsecutiveOldReports; i++) { AddTransportFeedbackAndValidate(&tracker, base + 0xc000, {true, true}); - EXPECT_TRUE(tracker.GetPacketLossRates(&plr, &cplr)); - EXPECT_EQ(plr, 2.0f / 5.0f); - EXPECT_EQ(cplr, 1.0f / 5.0f); + ValidatePacketLossStatistics(tracker, 2.0f / 5.0f, 1.0f / 4.0f); } // If we receive a feedback in quadrant #1, the above counter is reset. - // Expected window contents: [10011] -> [10011-1]. - AddTransportFeedbackAndValidate(&tracker, base + 0x000f, {true}); + // Expected window contents: [10011] -> [100111]. + AddTransportFeedbackAndValidate(&tracker, base + 5, {true}); for (size_t i = 0; i < kMaxConsecutiveOldReports; i++) { // Note: though the feedback message reports three packets, it only gets // counted once. AddTransportFeedbackAndValidate(&tracker, base + 0xc000, {true, false, true}); - EXPECT_TRUE(tracker.GetPacketLossRates(&plr, &cplr)); - EXPECT_EQ(plr, 2.0f / 6.0f); - EXPECT_EQ(cplr, 1.0f / 6.0f); + ValidatePacketLossStatistics(tracker, 2.0f / 6.0f, 1.0f / 5.0f); + } + + // The same is true for reports which create a gap - they still reset. + // Expected window contents: [10011] -> [100111-GAP-01]. + AddTransportFeedbackAndValidate(&tracker, base + 0x00ff, {false, true}); + for (size_t i = 0; i < kMaxConsecutiveOldReports; i++) { + // Note: though the feedback message reports three packets, it only gets + // counted once. + AddTransportFeedbackAndValidate(&tracker, base + 0xc000, + {true, false, true}); + ValidatePacketLossStatistics(tracker, 3.0f / 8.0f, 2.0f / 6.0f); } } } @@ -509,9 +537,7 @@ TEST(TransportFeedbackPacketLossTrackerTest, QuadrantFourCounterResetByQ1) { // Receiving a packet from quadrant #2 resets the counter for quadrant #4. TEST(TransportFeedbackPacketLossTrackerTest, QuadrantFourCounterResetByQ2) { for (uint16_t base : kBases) { - float plr = 0.0f; // Packet-loss-rate - float cplr = 0.0f; // Consecutive-packet-loss-rate - TransportFeedbackPacketLossTracker tracker(5, 20); + TransportFeedbackPacketLossTracker tracker(20, 5, 1); // Expected window contents: [] -> [10011]. AddTransportFeedbackAndValidate(&tracker, base, @@ -522,22 +548,18 @@ TEST(TransportFeedbackPacketLossTrackerTest, QuadrantFourCounterResetByQ2) { // Expected window contents: [10011] -> [10011]. for (size_t i = 0; i < kMaxConsecutiveOldReports; i++) { AddTransportFeedbackAndValidate(&tracker, base + 0xc000, {true, true}); - EXPECT_TRUE(tracker.GetPacketLossRates(&plr, &cplr)); - EXPECT_EQ(plr, 2.0f / 5.0f); - EXPECT_EQ(cplr, 1.0f / 5.0f); + ValidatePacketLossStatistics(tracker, 2.0f / 5.0f, 1.0f / 4.0f); } - // If we receive a feedback in quadrant #1, the above counter is reset. - // Expected window contents: [10011] -> [10011-1]. - AddTransportFeedbackAndValidate(&tracker, base + 0x400f, {true}); + // If we receive a feedback in quadrant #2, the above counter is reset. + // Expected window contents: [10011] -> [10011-GAP-11]. + AddTransportFeedbackAndValidate(&tracker, base + 0x400f, {true, true}); for (size_t i = 0; i < kMaxConsecutiveOldReports; i++) { // Note: though the feedback message reports three packets, it only gets // counted once. AddTransportFeedbackAndValidate(&tracker, base + 0xc000, {true, false, true}); - EXPECT_TRUE(tracker.GetPacketLossRates(&plr, &cplr)); - EXPECT_EQ(plr, 2.0f / 6.0f); - EXPECT_EQ(cplr, 1.0f / 6.0f); + ValidatePacketLossStatistics(tracker, 2.0f / 7.0f, 1.0f / 5.0f); } } } @@ -545,27 +567,23 @@ TEST(TransportFeedbackPacketLossTrackerTest, QuadrantFourCounterResetByQ2) { // Receiving a packet from quadrant #3 resets the counter for quadrant #4. TEST(TransportFeedbackPacketLossTrackerTest, QuadrantFourCounterResetByQ3) { for (uint16_t base : kBases) { - float plr = 0.0f; // Packet-loss-rate - float cplr = 0.0f; // Consecutive-packet-loss-rate - TransportFeedbackPacketLossTracker tracker(5, 20); + TransportFeedbackPacketLossTracker tracker(20, 5, 1); - // Expected window contents: [] -> [10011-10001]. + // Expected window contents: [] -> [1001110001]. AddTransportFeedbackAndValidate( &tracker, base, {true, false, false, true, true, true, false, false, false, true}); // Feedbacks in quadrant #4 are discarded (up to kMaxConsecutiveOldReports // consecutive reports). - // Expected window contents: [10011-10001] -> [10011-10001]. + // Expected window contents: [1001110001] -> [1001110001]. for (size_t i = 0; i < kMaxConsecutiveOldReports; i++) { AddTransportFeedbackAndValidate(&tracker, base + 0xc000, {true, true}); - EXPECT_TRUE(tracker.GetPacketLossRates(&plr, &cplr)); - EXPECT_EQ(plr, 5.0f / 10.0f); - EXPECT_EQ(cplr, 3.0f / 10.0f); + ValidatePacketLossStatistics(tracker, 5.0f / 10.0f, 2.0f / 9.0f); } // If we receive a feedback in quadrant #1, the above counter is reset. - // Expected window contents: [10011-10001] -> [11100-01111]. + // Expected window contents: [1001110001] -> [1110001-GAP-111]. AddTransportFeedbackAndValidate(&tracker, base + 0x8000, {true, true, true}); for (size_t i = 0; i < kMaxConsecutiveOldReports; i++) { @@ -573,9 +591,7 @@ TEST(TransportFeedbackPacketLossTrackerTest, QuadrantFourCounterResetByQ3) { // counted once. AddTransportFeedbackAndValidate(&tracker, base + 0xc000 + 10, {true, false, true}); - EXPECT_TRUE(tracker.GetPacketLossRates(&plr, &cplr)); - EXPECT_EQ(plr, 3.0f / 10.0f); - EXPECT_EQ(cplr, 2.0f / 10.0f); + ValidatePacketLossStatistics(tracker, 3.0f / 10.0f, 1.0f / 8.0f); } } } @@ -584,116 +600,96 @@ TEST(TransportFeedbackPacketLossTrackerTest, QuadrantFourCounterResetByQ3) { // After that, the window is reset. TEST(TransportFeedbackPacketLossTrackerTest, QuadrantFourReset) { for (uint16_t base : kBases) { - float plr = 0.0f; // Packet-loss-rate - float cplr = 0.0f; // Consecutive-packet-loss-rate - TransportFeedbackPacketLossTracker tracker(5, 20); + TransportFeedbackPacketLossTracker tracker(20, 5, 1); - // Expected window contents: [] -> [10011-10001]. + // Expected window contents: [] -> [1001110001]. AddTransportFeedbackAndValidate( &tracker, base, {true, false, false, true, true, true, false, false, false, true}); + // Sanity + ValidatePacketLossStatistics(tracker, 5.0f / 10.0f, 2.0f / 9.0f); + // The first kMaxConsecutiveOldReports quadrant #4 reports are ignored. // It doesn't matter that they consist of multiple packets - each report // is only counted once. for (size_t i = 0; i < kMaxConsecutiveOldReports; i++) { - // Expected window contents: [10011-10001] -> [10011-10001]. + // Expected window contents: [1001110001] -> [1001110001]. AddTransportFeedbackAndValidate(&tracker, base + 0xc000, {true, true, false, true}); - EXPECT_TRUE(tracker.GetPacketLossRates(&plr, &cplr)); - EXPECT_EQ(plr, 5.0f / 10.0f); - EXPECT_EQ(cplr, 3.0f / 10.0f); + ValidatePacketLossStatistics(tracker, 5.0f / 10.0f, 2.0f / 9.0f); } // One additional feedback in quadrant #4 brings us over // kMaxConsecutiveOldReports consecutive "old" reports, resetting the // window. - // Note: The report doesn't have to be the same as the previous ones. - // Expected window contents: [10011-10001] -> [10011]. - AddTransportFeedbackAndValidate(&tracker, base + 0xc000, - {true, false, false, true, true}); - EXPECT_TRUE(tracker.GetPacketLossRates(&plr, &cplr)); - // The new window is not completely empty - it's been seeded with the // packets reported in the feedback that has triggered the reset. - EXPECT_EQ(plr, 2.0f / 5.0f); - EXPECT_EQ(cplr, 1.0f / 5.0f); + // Note: The report doesn't have to be the same as the previous ones. + // Expected window contents: [1001110001] -> [10011]. + AddTransportFeedbackAndValidate(&tracker, base + 0xc000, + {true, false, false, true, true}); + ValidatePacketLossStatistics(tracker, 2.0f / 5.0f, 1.0f / 4.0f); } } // Feedbacks spanning multiple quadrant are treated correctly (Q1-Q2). TEST(TransportFeedbackPacketLossTrackerTest, MultiQuadrantQ1Q2) { for (uint16_t base : kBases) { - float plr = 0.0f; // Packet-loss-rate - float cplr = 0.0f; // Consecutive-packet-loss-rate - TransportFeedbackPacketLossTracker tracker(5, 20); + TransportFeedbackPacketLossTracker tracker(20, 5, 1); // Expected window contents: [] -> [10011]. AddTransportFeedbackAndValidate(&tracker, base, {true, false, false, true, true}); - EXPECT_TRUE(tracker.GetPacketLossRates(&plr, &cplr)); + ValidatePacketLossStatistics(tracker, 2.0f / 5.0f, 1.0f / 4.0f); // A feedback with entries in both quadrant #1 and #2 gets both counted: - // Expected window contents: [10011] -> [10011-11]. - AddTransportFeedbackAndValidate(&tracker, base + 0x3fff, {true, true}); - EXPECT_TRUE(tracker.GetPacketLossRates(&plr, &cplr)); - EXPECT_EQ(plr, 2.0f / 7.0f); - EXPECT_EQ(cplr, 1.0f / 7.0f); + // Expected window contents: [10011] -> [10011-GAP-1001]. + AddTransportFeedbackAndValidate(&tracker, base + 0x3ffe, + {true, false, false, true}); + ValidatePacketLossStatistics(tracker, 4.0f / 9.0f, 2.0f / 7.0f); } } // Feedbacks spanning multiple quadrant are treated correctly (Q2-Q3). TEST(TransportFeedbackPacketLossTrackerTest, MultiQuadrantQ2Q3) { for (uint16_t base : kBases) { - float plr = 0.0f; // Packet-loss-rate - float cplr = 0.0f; // Consecutive-packet-loss-rate - TransportFeedbackPacketLossTracker tracker(5, 20); + TransportFeedbackPacketLossTracker tracker(20, 5, 1); - // Expected window contents: [] -> [10011-00001]. + // Expected window contents: [] -> [1001100001]. AddTransportFeedbackAndValidate( &tracker, base, {true, false, false, true, true, false, false, false, false, true}); - EXPECT_TRUE(tracker.GetPacketLossRates(&plr, &cplr)); - EXPECT_EQ(plr, 6.0f / 10.0f); - EXPECT_EQ(cplr, 4.0f / 10.0f); + ValidatePacketLossStatistics(tracker, 6.0f / 10.0f, 2.0f / 9.0f); // A feedback with entries in both quadrant #2 and #3 gets both counted, // but only those from #3 trigger throwing out old entries from quadrant #1: - // Expected window contents: [10011-00001] -> [01100-00110-01]. + // Expected window contents: [1001100001] -> [01100001-GAP-1001]. AddTransportFeedbackAndValidate(&tracker, base + 0x7ffe, {true, false, false, true}); - EXPECT_TRUE(tracker.GetPacketLossRates(&plr, &cplr)); - EXPECT_EQ(plr, 7.0f / 12.0f); - EXPECT_EQ(cplr, 4.0f / 12.0f); + ValidatePacketLossStatistics(tracker, 7.0f / 12.0f, 3.0f / 10.0f); } } -// Feedbacks spanning multiple quadrant are treated correctly (Q2-Q3). +// Feedbacks spanning multiple quadrant are treated correctly (Q3-Q4). TEST(TransportFeedbackPacketLossTrackerTest, MultiQuadrantQ3Q4) { for (uint16_t base : kBases) { - float plr = 0.0f; // Packet-loss-rate - float cplr = 0.0f; // Consecutive-packet-loss-rate + TransportFeedbackPacketLossTracker tracker(20, 5, 1); - TransportFeedbackPacketLossTracker tracker(5, 20); - - // Expected window contents: [] -> [10011-00001]. + // Expected window contents: [] -> [1001100001]. AddTransportFeedbackAndValidate( &tracker, base, {true, false, false, true, true, false, false, false, false, true}); - EXPECT_TRUE(tracker.GetPacketLossRates(&plr, &cplr)); - EXPECT_EQ(plr, 6.0f / 10.0f); - EXPECT_EQ(cplr, 4.0f / 10.0f); + ValidatePacketLossStatistics(tracker, 6.0f / 10.0f, 2.0f / 9.0f); // A feedback with entries in both quadrant #3 and #4 would have the entries // from quadrant #3 shift enough quadrant #1 entries out of window, that // by the time the #4 packets are examined, the moving baseline has made // them into quadrant #3 packets. - // Expected window contents: [10011-00001] -> [10011]. + // Expected window contents: [1001100001] -> [10011]. AddTransportFeedbackAndValidate(&tracker, base + 0xbfff, {true, false, false, true, true}); - EXPECT_TRUE(tracker.GetPacketLossRates(&plr, &cplr)); - EXPECT_EQ(plr, 2.0f / 5.0f); - EXPECT_EQ(cplr, 1.0f / 5.0f); + ValidatePacketLossStatistics(tracker, 2.0f / 5.0f, 1.0f / 4.0f); } }