/*
 *  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
