blob: b3b117530506b9c72d0918df019523e4d6d9a900 [file] [log] [blame]
/*
* Copyright (c) 2015 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/rtcp_packet/transport_feedback.h"
#include <limits>
#include <memory>
#include <utility>
#include "api/array_view.h"
#include "api/units/time_delta.h"
#include "api/units/timestamp.h"
#include "modules/rtp_rtcp/source/byte_io.h"
#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h"
#include "test/gmock.h"
#include "test/gtest.h"
namespace webrtc {
namespace {
using rtcp::TransportFeedback;
using ::testing::AllOf;
using ::testing::Each;
using ::testing::ElementsAreArray;
using ::testing::Eq;
using ::testing::Property;
using ::testing::SizeIs;
constexpr int kHeaderSize = 20;
constexpr int kStatusChunkSize = 2;
constexpr int kSmallDeltaSize = 1;
constexpr int kLargeDeltaSize = 2;
constexpr TimeDelta kDeltaLimit = 0xFF * TransportFeedback::kDeltaTick;
constexpr TimeDelta kBaseTimeTick = TransportFeedback::kDeltaTick * (1 << 8);
constexpr TimeDelta kBaseTimeWrapPeriod = kBaseTimeTick * (1 << 24);
MATCHER_P2(Near, value, max_abs_error, "") {
return value - max_abs_error <= arg && arg <= value + max_abs_error;
}
MATCHER(IsValidFeedback, "") {
rtcp::CommonHeader rtcp_header;
TransportFeedback feedback;
return rtcp_header.Parse(std::data(arg), std::size(arg)) &&
rtcp_header.type() == TransportFeedback::kPacketType &&
rtcp_header.fmt() == TransportFeedback::kFeedbackMessageType &&
feedback.Parse(rtcp_header);
}
TransportFeedback Parse(rtc::ArrayView<const uint8_t> buffer) {
rtcp::CommonHeader header;
RTC_DCHECK(header.Parse(buffer.data(), buffer.size()));
RTC_DCHECK_EQ(header.type(), TransportFeedback::kPacketType);
RTC_DCHECK_EQ(header.fmt(), TransportFeedback::kFeedbackMessageType);
TransportFeedback feedback;
RTC_DCHECK(feedback.Parse(header));
return feedback;
}
class FeedbackTester {
public:
FeedbackTester() : FeedbackTester(true) {}
explicit FeedbackTester(bool include_timestamps)
: expected_size_(kAnySize),
default_delta_(TransportFeedback::kDeltaTick * 4),
include_timestamps_(include_timestamps) {}
void WithExpectedSize(size_t expected_size) {
expected_size_ = expected_size;
}
void WithDefaultDelta(TimeDelta delta) { default_delta_ = delta; }
void WithInput(rtc::ArrayView<const uint16_t> received_seq,
rtc::ArrayView<const Timestamp> received_ts = {}) {
std::vector<Timestamp> temp_timestamps;
if (received_ts.empty()) {
temp_timestamps = GenerateReceiveTimestamps(received_seq);
received_ts = temp_timestamps;
}
RTC_DCHECK_EQ(received_seq.size(), received_ts.size());
expected_deltas_.clear();
feedback_.emplace(include_timestamps_);
feedback_->SetBase(received_seq[0], received_ts[0]);
ASSERT_TRUE(feedback_->IsConsistent());
// First delta is special: it doesn't represent the delta between two times,
// but a compensation for the reduced precision of the base time.
EXPECT_TRUE(feedback_->AddReceivedPacket(received_seq[0], received_ts[0]));
// GetBaseDelta suppose to return balanced diff between base time of the new
// feedback message (stored internally) and base time of the old feedback
// message (passed as parameter), but first delta is the difference between
// 1st timestamp (passed as parameter) and base time (stored internally),
// thus to get the first delta need to negate whatever GetBaseDelta returns.
expected_deltas_.push_back(-feedback_->GetBaseDelta(received_ts[0]));
for (size_t i = 1; i < received_ts.size(); ++i) {
EXPECT_TRUE(
feedback_->AddReceivedPacket(received_seq[i], received_ts[i]));
expected_deltas_.push_back(received_ts[i] - received_ts[i - 1]);
}
ASSERT_TRUE(feedback_->IsConsistent());
expected_seq_.assign(received_seq.begin(), received_seq.end());
}
void VerifyPacket() {
ASSERT_TRUE(feedback_->IsConsistent());
serialized_ = feedback_->Build();
VerifyInternal();
feedback_.emplace(Parse(serialized_));
ASSERT_TRUE(feedback_->IsConsistent());
EXPECT_EQ(include_timestamps_, feedback_->IncludeTimestamps());
VerifyInternal();
}
static constexpr size_t kAnySize = static_cast<size_t>(0) - 1;
private:
void VerifyInternal() {
if (expected_size_ != kAnySize) {
// Round up to whole 32-bit words.
size_t expected_size_words = (expected_size_ + 3) / 4;
size_t expected_size_bytes = expected_size_words * 4;
EXPECT_EQ(expected_size_bytes, serialized_.size());
}
std::vector<uint16_t> actual_seq_nos;
std::vector<TimeDelta> actual_deltas;
for (const auto& packet : feedback_->GetReceivedPackets()) {
actual_seq_nos.push_back(packet.sequence_number());
actual_deltas.push_back(packet.delta());
}
EXPECT_THAT(actual_seq_nos, ElementsAreArray(expected_seq_));
if (include_timestamps_) {
EXPECT_THAT(actual_deltas, ElementsAreArray(expected_deltas_));
}
}
std::vector<Timestamp> GenerateReceiveTimestamps(
rtc::ArrayView<const uint16_t> seq_nums) {
RTC_DCHECK(!seq_nums.empty());
uint16_t last_seq = seq_nums[0];
Timestamp time = Timestamp::Zero();
std::vector<Timestamp> result;
for (uint16_t seq : seq_nums) {
if (seq < last_seq)
time += 0x10000 * default_delta_;
last_seq = seq;
result.push_back(time + last_seq * default_delta_);
}
return result;
}
std::vector<uint16_t> expected_seq_;
std::vector<TimeDelta> expected_deltas_;
size_t expected_size_;
TimeDelta default_delta_;
absl::optional<TransportFeedback> feedback_;
rtc::Buffer serialized_;
bool include_timestamps_;
};
// The following tests use FeedbackTester that simulates received packets as
// specified by the parameters `received_seq[]` and `received_ts[]` (optional).
// The following is verified in these tests:
// - Expected size of serialized packet.
// - Expected sequence numbers and receive deltas.
// - Sequence numbers and receive deltas are persistent after serialization
// followed by parsing.
// - The internal state of a feedback packet is consistent.
TEST(RtcpPacketTest, TransportFeedbackOneBitVector) {
const uint16_t kReceived[] = {1, 2, 7, 8, 9, 10, 13};
const size_t kLength = sizeof(kReceived) / sizeof(uint16_t);
const size_t kExpectedSizeBytes =
kHeaderSize + kStatusChunkSize + (kLength * kSmallDeltaSize);
FeedbackTester test;
test.WithExpectedSize(kExpectedSizeBytes);
test.WithInput(kReceived);
test.VerifyPacket();
}
TEST(RtcpPacketTest, TransportFeedbackOneBitVectorNoRecvDelta) {
const uint16_t kReceived[] = {1, 2, 7, 8, 9, 10, 13};
const size_t kExpectedSizeBytes = kHeaderSize + kStatusChunkSize;
FeedbackTester test(/*include_timestamps=*/false);
test.WithExpectedSize(kExpectedSizeBytes);
test.WithInput(kReceived);
test.VerifyPacket();
}
TEST(RtcpPacketTest, TransportFeedbackFullOneBitVector) {
const uint16_t kReceived[] = {1, 2, 7, 8, 9, 10, 13, 14};
const size_t kLength = sizeof(kReceived) / sizeof(uint16_t);
const size_t kExpectedSizeBytes =
kHeaderSize + kStatusChunkSize + (kLength * kSmallDeltaSize);
FeedbackTester test;
test.WithExpectedSize(kExpectedSizeBytes);
test.WithInput(kReceived);
test.VerifyPacket();
}
TEST(RtcpPacketTest, TransportFeedbackOneBitVectorWrapReceived) {
const uint16_t kMax = 0xFFFF;
const uint16_t kReceived[] = {kMax - 2, kMax - 1, kMax, 0, 1, 2};
const size_t kLength = sizeof(kReceived) / sizeof(uint16_t);
const size_t kExpectedSizeBytes =
kHeaderSize + kStatusChunkSize + (kLength * kSmallDeltaSize);
FeedbackTester test;
test.WithExpectedSize(kExpectedSizeBytes);
test.WithInput(kReceived);
test.VerifyPacket();
}
TEST(RtcpPacketTest, TransportFeedbackOneBitVectorWrapMissing) {
const uint16_t kMax = 0xFFFF;
const uint16_t kReceived[] = {kMax - 2, kMax - 1, 1, 2};
const size_t kLength = sizeof(kReceived) / sizeof(uint16_t);
const size_t kExpectedSizeBytes =
kHeaderSize + kStatusChunkSize + (kLength * kSmallDeltaSize);
FeedbackTester test;
test.WithExpectedSize(kExpectedSizeBytes);
test.WithInput(kReceived);
test.VerifyPacket();
}
TEST(RtcpPacketTest, TransportFeedbackTwoBitVector) {
const uint16_t kReceived[] = {1, 2, 6, 7};
const size_t kLength = sizeof(kReceived) / sizeof(uint16_t);
const size_t kExpectedSizeBytes =
kHeaderSize + kStatusChunkSize + (kLength * kLargeDeltaSize);
FeedbackTester test;
test.WithExpectedSize(kExpectedSizeBytes);
test.WithDefaultDelta(kDeltaLimit + TransportFeedback::kDeltaTick);
test.WithInput(kReceived);
test.VerifyPacket();
}
TEST(RtcpPacketTest, TransportFeedbackTwoBitVectorFull) {
const uint16_t kReceived[] = {1, 2, 6, 7, 8};
const size_t kLength = sizeof(kReceived) / sizeof(uint16_t);
const size_t kExpectedSizeBytes =
kHeaderSize + (2 * kStatusChunkSize) + (kLength * kLargeDeltaSize);
FeedbackTester test;
test.WithExpectedSize(kExpectedSizeBytes);
test.WithDefaultDelta(kDeltaLimit + TransportFeedback::kDeltaTick);
test.WithInput(kReceived);
test.VerifyPacket();
}
TEST(RtcpPacketTest, TransportFeedbackWithLargeBaseTimeIsConsistent) {
TransportFeedback tb;
constexpr Timestamp kTimestamp =
Timestamp::Zero() + int64_t{0x7fff'ffff} * TransportFeedback::kDeltaTick;
tb.SetBase(/*base_sequence=*/0, /*ref_timestamp=*/kTimestamp);
tb.AddReceivedPacket(/*base_sequence=*/0, /*ref_timestamp=*/kTimestamp);
EXPECT_TRUE(tb.IsConsistent());
}
TEST(RtcpPacketTest, TransportFeedbackLargeAndNegativeDeltas) {
const uint16_t kReceived[] = {1, 2, 6, 7, 8};
const Timestamp kReceiveTimes[] = {
Timestamp::Millis(2), Timestamp::Millis(1), Timestamp::Millis(4),
Timestamp::Millis(3),
Timestamp::Millis(3) + TransportFeedback::kDeltaTick * (1 << 8)};
const size_t kExpectedSizeBytes =
kHeaderSize + kStatusChunkSize + (3 * kLargeDeltaSize) + kSmallDeltaSize;
FeedbackTester test;
test.WithExpectedSize(kExpectedSizeBytes);
test.WithInput(kReceived, kReceiveTimes);
test.VerifyPacket();
}
TEST(RtcpPacketTest, TransportFeedbackMaxRle) {
// Expected chunks created:
// * 1-bit vector chunk (1xreceived + 13xdropped)
// * RLE chunk of max length for dropped symbol
// * 1-bit vector chunk (1xreceived + 13xdropped)
const size_t kPacketCount = (1 << 13) - 1 + 14;
const uint16_t kReceived[] = {0, kPacketCount};
const Timestamp kReceiveTimes[] = {Timestamp::Millis(1),
Timestamp::Millis(2)};
const size_t kLength = sizeof(kReceived) / sizeof(uint16_t);
const size_t kExpectedSizeBytes =
kHeaderSize + (3 * kStatusChunkSize) + (kLength * kSmallDeltaSize);
FeedbackTester test;
test.WithExpectedSize(kExpectedSizeBytes);
test.WithInput(kReceived, kReceiveTimes);
test.VerifyPacket();
}
TEST(RtcpPacketTest, TransportFeedbackMinRle) {
// Expected chunks created:
// * 1-bit vector chunk (1xreceived + 13xdropped)
// * RLE chunk of length 15 for dropped symbol
// * 1-bit vector chunk (1xreceived + 13xdropped)
const uint16_t kReceived[] = {0, (14 * 2) + 1};
const Timestamp kReceiveTimes[] = {Timestamp::Millis(1),
Timestamp::Millis(2)};
const size_t kLength = sizeof(kReceived) / sizeof(uint16_t);
const size_t kExpectedSizeBytes =
kHeaderSize + (3 * kStatusChunkSize) + (kLength * kSmallDeltaSize);
FeedbackTester test;
test.WithExpectedSize(kExpectedSizeBytes);
test.WithInput(kReceived, kReceiveTimes);
test.VerifyPacket();
}
TEST(RtcpPacketTest, TransportFeedbackOneToTwoBitVector) {
const size_t kTwoBitVectorCapacity = 7;
const uint16_t kReceived[] = {0, kTwoBitVectorCapacity - 1};
const Timestamp kReceiveTimes[] = {
Timestamp::Zero(),
Timestamp::Zero() + kDeltaLimit + TransportFeedback::kDeltaTick};
const size_t kExpectedSizeBytes =
kHeaderSize + kStatusChunkSize + kSmallDeltaSize + kLargeDeltaSize;
FeedbackTester test;
test.WithExpectedSize(kExpectedSizeBytes);
test.WithInput(kReceived, kReceiveTimes);
test.VerifyPacket();
}
TEST(RtcpPacketTest, TransportFeedbackOneToTwoBitVectorSimpleSplit) {
const size_t kTwoBitVectorCapacity = 7;
const uint16_t kReceived[] = {0, kTwoBitVectorCapacity};
const Timestamp kReceiveTimes[] = {
Timestamp::Zero(),
Timestamp::Zero() + kDeltaLimit + TransportFeedback::kDeltaTick};
const size_t kExpectedSizeBytes =
kHeaderSize + (kStatusChunkSize * 2) + kSmallDeltaSize + kLargeDeltaSize;
FeedbackTester test;
test.WithExpectedSize(kExpectedSizeBytes);
test.WithInput(kReceived, kReceiveTimes);
test.VerifyPacket();
}
TEST(RtcpPacketTest, TransportFeedbackOneToTwoBitVectorSplit) {
// With received small delta = S, received large delta = L, use input
// SSSSSSSSLSSSSSSSSSSSS. This will cause a 1:2 split at the L.
// After split there will be two symbols in symbol_vec: SL.
const TimeDelta kLargeDelta = TransportFeedback::kDeltaTick * (1 << 8);
const size_t kNumPackets = (3 * 7) + 1;
const size_t kExpectedSizeBytes = kHeaderSize + (kStatusChunkSize * 3) +
(kSmallDeltaSize * (kNumPackets - 1)) +
(kLargeDeltaSize * 1);
uint16_t kReceived[kNumPackets];
for (size_t i = 0; i < kNumPackets; ++i)
kReceived[i] = i;
std::vector<Timestamp> receive_times;
receive_times.reserve(kNumPackets);
receive_times.push_back(Timestamp::Millis(1));
for (size_t i = 1; i < kNumPackets; ++i) {
TimeDelta delta = (i == 8) ? kLargeDelta : TimeDelta::Millis(1);
receive_times.push_back(receive_times.back() + delta);
}
FeedbackTester test;
test.WithExpectedSize(kExpectedSizeBytes);
test.WithInput(kReceived, receive_times);
test.VerifyPacket();
}
TEST(RtcpPacketTest, TransportFeedbackAliasing) {
TransportFeedback feedback;
feedback.SetBase(0, Timestamp::Zero());
const int kSamples = 100;
const TimeDelta kTooSmallDelta = TransportFeedback::kDeltaTick / 3;
for (int i = 0; i < kSamples; ++i)
feedback.AddReceivedPacket(i, Timestamp::Zero() + i * kTooSmallDelta);
feedback.Build();
TimeDelta accumulated_delta = TimeDelta::Zero();
int num_samples = 0;
for (const auto& packet : feedback.GetReceivedPackets()) {
accumulated_delta += packet.delta();
TimeDelta expected_time = num_samples * kTooSmallDelta;
++num_samples;
EXPECT_THAT(accumulated_delta,
Near(expected_time, TransportFeedback::kDeltaTick / 2));
}
}
TEST(RtcpPacketTest, TransportFeedbackLimits) {
// Sequence number wrap above 0x8000.
std::unique_ptr<TransportFeedback> packet(new TransportFeedback());
packet->SetBase(0, Timestamp::Zero());
EXPECT_TRUE(packet->AddReceivedPacket(0x0, Timestamp::Zero()));
EXPECT_TRUE(packet->AddReceivedPacket(0x8000, Timestamp::Millis(1)));
packet.reset(new TransportFeedback());
packet->SetBase(0, Timestamp::Zero());
EXPECT_TRUE(packet->AddReceivedPacket(0x0, Timestamp::Zero()));
EXPECT_FALSE(packet->AddReceivedPacket(0x8000 + 1, Timestamp::Millis(1)));
// Packet status count max 0xFFFF.
packet.reset(new TransportFeedback());
packet->SetBase(0, Timestamp::Zero());
EXPECT_TRUE(packet->AddReceivedPacket(0x0, Timestamp::Zero()));
EXPECT_TRUE(packet->AddReceivedPacket(0x8000, Timestamp::Millis(1)));
EXPECT_TRUE(packet->AddReceivedPacket(0xFFFE, Timestamp::Millis(2)));
EXPECT_FALSE(packet->AddReceivedPacket(0xFFFF, Timestamp::Millis(3)));
// Too large delta.
packet.reset(new TransportFeedback());
packet->SetBase(0, Timestamp::Zero());
TimeDelta kMaxPositiveTimeDelta =
std::numeric_limits<int16_t>::max() * TransportFeedback::kDeltaTick;
EXPECT_FALSE(packet->AddReceivedPacket(1, Timestamp::Zero() +
kMaxPositiveTimeDelta +
TransportFeedback::kDeltaTick));
EXPECT_TRUE(
packet->AddReceivedPacket(1, Timestamp::Zero() + kMaxPositiveTimeDelta));
// Too large negative delta.
packet.reset(new TransportFeedback());
TimeDelta kMaxNegativeTimeDelta =
std::numeric_limits<int16_t>::min() * TransportFeedback::kDeltaTick;
// Use larger base time to avoid kBaseTime + kNegativeDelta to be negative.
Timestamp kBaseTime = Timestamp::Seconds(1'000'000);
packet->SetBase(0, kBaseTime);
EXPECT_FALSE(packet->AddReceivedPacket(
1, kBaseTime + kMaxNegativeTimeDelta - TransportFeedback::kDeltaTick));
EXPECT_TRUE(packet->AddReceivedPacket(1, kBaseTime + kMaxNegativeTimeDelta));
// TODO(sprang): Once we support max length lower than RTCP length limit,
// add back test for max size in bytes.
}
TEST(RtcpPacketTest, BaseTimeIsConsistentAcrossMultiplePackets) {
constexpr Timestamp kMaxBaseTime =
Timestamp::Zero() + kBaseTimeWrapPeriod - kBaseTimeTick;
TransportFeedback packet1;
packet1.SetBase(0, kMaxBaseTime);
packet1.AddReceivedPacket(0, kMaxBaseTime);
// Build and parse packet to simulate sending it over the wire.
TransportFeedback parsed_packet1 = Parse(packet1.Build());
TransportFeedback packet2;
packet2.SetBase(1, kMaxBaseTime + kBaseTimeTick);
packet2.AddReceivedPacket(1, kMaxBaseTime + kBaseTimeTick);
TransportFeedback parsed_packet2 = Parse(packet2.Build());
EXPECT_EQ(parsed_packet2.GetBaseDelta(parsed_packet1.BaseTime()),
kBaseTimeTick);
}
TEST(RtcpPacketTest, SupportsMaximumNumberOfNegativeDeltas) {
TransportFeedback feedback;
// Use large base time to avoid hitting zero limit while filling the feedback,
// but use multiple of kBaseTimeWrapPeriod to hit edge case where base time
// is recorded as zero in the raw rtcp packet.
Timestamp time = Timestamp::Zero() + 1'000 * kBaseTimeWrapPeriod;
feedback.SetBase(0, time);
static constexpr TimeDelta kMinDelta =
TransportFeedback::kDeltaTick * std::numeric_limits<int16_t>::min();
uint16_t num_received_rtp_packets = 0;
time += kMinDelta;
while (feedback.AddReceivedPacket(++num_received_rtp_packets, time)) {
ASSERT_GE(time, Timestamp::Zero() - kMinDelta);
time += kMinDelta;
}
// Subtract one last packet that failed to add.
--num_received_rtp_packets;
EXPECT_TRUE(feedback.IsConsistent());
TransportFeedback parsed = Parse(feedback.Build());
EXPECT_EQ(parsed.GetReceivedPackets().size(), num_received_rtp_packets);
EXPECT_THAT(parsed.GetReceivedPackets(),
AllOf(SizeIs(num_received_rtp_packets),
Each(Property(&TransportFeedback::ReceivedPacket::delta,
Eq(kMinDelta)))));
EXPECT_GE(parsed.BaseTime(),
Timestamp::Zero() - kMinDelta * num_received_rtp_packets);
}
TEST(RtcpPacketTest, TransportFeedbackPadding) {
const size_t kExpectedSizeBytes =
kHeaderSize + kStatusChunkSize + kSmallDeltaSize;
const size_t kExpectedSizeWords = (kExpectedSizeBytes + 3) / 4;
const size_t kExpectedPaddingSizeBytes =
4 * kExpectedSizeWords - kExpectedSizeBytes;
TransportFeedback feedback;
feedback.SetBase(0, Timestamp::Zero());
EXPECT_TRUE(feedback.AddReceivedPacket(0, Timestamp::Zero()));
rtc::Buffer packet = feedback.Build();
EXPECT_EQ(kExpectedSizeWords * 4, packet.size());
ASSERT_GT(kExpectedSizeWords * 4, kExpectedSizeBytes);
for (size_t i = kExpectedSizeBytes; i < (kExpectedSizeWords * 4 - 1); ++i)
EXPECT_EQ(0u, packet[i]);
EXPECT_EQ(kExpectedPaddingSizeBytes, packet[kExpectedSizeWords * 4 - 1]);
// Modify packet by adding 4 bytes of padding at the end. Not currently used
// when we're sending, but need to be able to handle it when receiving.
const int kPaddingBytes = 4;
const size_t kExpectedSizeWithPadding =
(kExpectedSizeWords * 4) + kPaddingBytes;
uint8_t mod_buffer[kExpectedSizeWithPadding];
memcpy(mod_buffer, packet.data(), kExpectedSizeWords * 4);
memset(&mod_buffer[kExpectedSizeWords * 4], 0, kPaddingBytes - 1);
mod_buffer[kExpectedSizeWithPadding - 1] =
kPaddingBytes + kExpectedPaddingSizeBytes;
const uint8_t padding_flag = 1 << 5;
mod_buffer[0] |= padding_flag;
ByteWriter<uint16_t>::WriteBigEndian(
&mod_buffer[2], ByteReader<uint16_t>::ReadBigEndian(&mod_buffer[2]) +
((kPaddingBytes + 3) / 4));
EXPECT_THAT(mod_buffer, IsValidFeedback());
}
TEST(RtcpPacketTest, TransportFeedbackPaddingBackwardsCompatibility) {
const size_t kExpectedSizeBytes =
kHeaderSize + kStatusChunkSize + kSmallDeltaSize;
const size_t kExpectedSizeWords = (kExpectedSizeBytes + 3) / 4;
const size_t kExpectedPaddingSizeBytes =
4 * kExpectedSizeWords - kExpectedSizeBytes;
TransportFeedback feedback;
feedback.SetBase(0, Timestamp::Zero());
EXPECT_TRUE(feedback.AddReceivedPacket(0, Timestamp::Zero()));
rtc::Buffer packet = feedback.Build();
EXPECT_EQ(kExpectedSizeWords * 4, packet.size());
ASSERT_GT(kExpectedSizeWords * 4, kExpectedSizeBytes);
for (size_t i = kExpectedSizeBytes; i < (kExpectedSizeWords * 4 - 1); ++i)
EXPECT_EQ(0u, packet[i]);
EXPECT_GT(kExpectedPaddingSizeBytes, 0u);
EXPECT_EQ(kExpectedPaddingSizeBytes, packet[kExpectedSizeWords * 4 - 1]);
// Modify packet by removing padding bit and writing zero at the last padding
// byte to verify that we can parse packets from old clients, where zero
// padding of up to three bytes was used without the padding bit being set.
uint8_t mod_buffer[kExpectedSizeWords * 4];
memcpy(mod_buffer, packet.data(), kExpectedSizeWords * 4);
mod_buffer[kExpectedSizeWords * 4 - 1] = 0;
const uint8_t padding_flag = 1 << 5;
mod_buffer[0] &= ~padding_flag; // Unset padding flag.
EXPECT_THAT(mod_buffer, IsValidFeedback());
}
TEST(RtcpPacketTest, TransportFeedbackCorrectlySplitsVectorChunks) {
const int kOneBitVectorCapacity = 14;
const TimeDelta kLargeTimeDelta = TransportFeedback::kDeltaTick * (1 << 8);
// Test that a number of small deltas followed by a large delta results in a
// correct split into multiple chunks, as needed.
for (int deltas = 0; deltas <= kOneBitVectorCapacity + 1; ++deltas) {
TransportFeedback feedback;
feedback.SetBase(0, Timestamp::Zero());
for (int i = 0; i < deltas; ++i)
feedback.AddReceivedPacket(i, Timestamp::Millis(i));
feedback.AddReceivedPacket(deltas,
Timestamp::Millis(deltas) + kLargeTimeDelta);
EXPECT_THAT(feedback.Build(), IsValidFeedback());
}
}
TEST(RtcpPacketTest, TransportFeedbackMoveConstructor) {
const int kSamples = 100;
const uint16_t kBaseSeqNo = 7531;
const Timestamp kBaseTimestamp = Timestamp::Micros(123'456'789);
const uint8_t kFeedbackSeqNo = 90;
TransportFeedback feedback;
feedback.SetBase(kBaseSeqNo, kBaseTimestamp);
feedback.SetFeedbackSequenceNumber(kFeedbackSeqNo);
for (int i = 0; i < kSamples; ++i) {
feedback.AddReceivedPacket(
kBaseSeqNo + i, kBaseTimestamp + i * TransportFeedback::kDeltaTick);
}
EXPECT_TRUE(feedback.IsConsistent());
TransportFeedback feedback_copy(feedback);
EXPECT_TRUE(feedback_copy.IsConsistent());
EXPECT_TRUE(feedback.IsConsistent());
EXPECT_EQ(feedback_copy.Build(), feedback.Build());
TransportFeedback moved(std::move(feedback));
EXPECT_TRUE(moved.IsConsistent());
EXPECT_TRUE(feedback.IsConsistent());
EXPECT_EQ(moved.Build(), feedback_copy.Build());
}
TEST(TransportFeedbackTest, ReportsMissingPackets) {
const uint16_t kBaseSeqNo = 1000;
const Timestamp kBaseTimestamp = Timestamp::Millis(10);
const uint8_t kFeedbackSeqNo = 90;
TransportFeedback feedback_builder(/*include_timestamps*/ true);
feedback_builder.SetBase(kBaseSeqNo, kBaseTimestamp);
feedback_builder.SetFeedbackSequenceNumber(kFeedbackSeqNo);
feedback_builder.AddReceivedPacket(kBaseSeqNo + 0, kBaseTimestamp);
// Packet losses indicated by jump in sequence number.
feedback_builder.AddReceivedPacket(kBaseSeqNo + 3,
kBaseTimestamp + TimeDelta::Millis(2));
EXPECT_THAT(
Parse(feedback_builder.Build()).GetAllPackets(),
ElementsAre(
Property(&TransportFeedback::ReceivedPacket::received, true),
Property(&TransportFeedback::ReceivedPacket::received, false),
Property(&TransportFeedback::ReceivedPacket::received, false),
Property(&TransportFeedback::ReceivedPacket::received, true)));
}
TEST(TransportFeedbackTest, ReportsMissingPacketsWithoutTimestamps) {
const uint16_t kBaseSeqNo = 1000;
const uint8_t kFeedbackSeqNo = 90;
TransportFeedback feedback_builder(/*include_timestamps*/ false);
feedback_builder.SetBase(kBaseSeqNo, Timestamp::Millis(10));
feedback_builder.SetFeedbackSequenceNumber(kFeedbackSeqNo);
feedback_builder.AddReceivedPacket(kBaseSeqNo + 0, Timestamp::Zero());
// Packet losses indicated by jump in sequence number.
feedback_builder.AddReceivedPacket(kBaseSeqNo + 3, Timestamp::Zero());
EXPECT_THAT(
Parse(feedback_builder.Build()).GetAllPackets(),
ElementsAre(
Property(&TransportFeedback::ReceivedPacket::received, true),
Property(&TransportFeedback::ReceivedPacket::received, false),
Property(&TransportFeedback::ReceivedPacket::received, false),
Property(&TransportFeedback::ReceivedPacket::received, true)));
}
} // namespace
} // namespace webrtc