From 86298f7a84b13308d058c310f4d8058f2da714a5 Mon Sep 17 00:00:00 2001 From: Per K Date: Mon, 22 Apr 2024 13:06:21 +0000 Subject: [PATCH] Implementation of RFC 8888 TranportLayerFeedback RTCP packet This cl adds an implementation of the RTCP feedback packet as specified in https://www.rfc-editor.org/rfc/rfc8888.html Bug: webrtc:15368 Change-Id: I0b9a7fb15512ff9f9e721efd8e03ebe981a8d9bd Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/347901 Reviewed-by: Danil Chapovalov Commit-Queue: Per Kjellander Cr-Commit-Position: refs/heads/main@{#42140} --- modules/rtp_rtcp/BUILD.gn | 4 + .../congestion_control_feedback.cc | 321 ++++++++++++++++++ .../rtcp_packet/congestion_control_feedback.h | 68 ++++ .../congestion_control_feedback_unittest.cc | 223 ++++++++++++ 4 files changed, 616 insertions(+) create mode 100644 modules/rtp_rtcp/source/rtcp_packet/congestion_control_feedback.cc create mode 100644 modules/rtp_rtcp/source/rtcp_packet/congestion_control_feedback.h create mode 100644 modules/rtp_rtcp/source/rtcp_packet/congestion_control_feedback_unittest.cc diff --git a/modules/rtp_rtcp/BUILD.gn b/modules/rtp_rtcp/BUILD.gn index c5b526cd6e..c63603f7d1 100644 --- a/modules/rtp_rtcp/BUILD.gn +++ b/modules/rtp_rtcp/BUILD.gn @@ -29,6 +29,7 @@ rtc_library("rtp_rtcp_format") { "source/rtcp_packet/bye.h", "source/rtcp_packet/common_header.h", "source/rtcp_packet/compound_packet.h", + "source/rtcp_packet/congestion_control_feedback.h", "source/rtcp_packet/dlrr.h", "source/rtcp_packet/extended_reports.h", "source/rtcp_packet/fir.h", @@ -68,6 +69,7 @@ rtc_library("rtp_rtcp_format") { "source/rtcp_packet/bye.cc", "source/rtcp_packet/common_header.cc", "source/rtcp_packet/compound_packet.cc", + "source/rtcp_packet/congestion_control_feedback.cc", "source/rtcp_packet/dlrr.cc", "source/rtcp_packet/extended_reports.cc", "source/rtcp_packet/fir.cc", @@ -575,6 +577,7 @@ if (rtc_include_tests) { "source/rtcp_packet/bye_unittest.cc", "source/rtcp_packet/common_header_unittest.cc", "source/rtcp_packet/compound_packet_unittest.cc", + "source/rtcp_packet/congestion_control_feedback_unittest.cc", "source/rtcp_packet/dlrr_unittest.cc", "source/rtcp_packet/extended_reports_unittest.cc", "source/rtcp_packet/fir_unittest.cc", @@ -706,6 +709,7 @@ if (rtc_include_tests) { "../../rtc_base:task_queue_for_test", "../../rtc_base:threading", "../../rtc_base:timeutils", + "../../rtc_base/network:ecn_marking", "../../system_wrappers", "../../test:explicit_key_value_config", "../../test:mock_transport", diff --git a/modules/rtp_rtcp/source/rtcp_packet/congestion_control_feedback.cc b/modules/rtp_rtcp/source/rtcp_packet/congestion_control_feedback.cc new file mode 100644 index 0000000000..b04d41a370 --- /dev/null +++ b/modules/rtp_rtcp/source/rtcp_packet/congestion_control_feedback.cc @@ -0,0 +1,321 @@ +/* + * Copyright (c) 2024 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/congestion_control_feedback.h" + +#include +#include +#include +#include + +#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 "rtc_base/network/ecn_marking.h" + +namespace webrtc { +namespace rtcp { + +/* + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V=2|P| FMT=11 | PT = 205 | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of RTCP packet sender | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of 1st RTP Stream | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | begin_seq | num_reports | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |R|ECN| Arrival time offset | ... . + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + . . + . . + . . + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of nth RTP Stream | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | begin_seq | num_reports | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |R|ECN| Arrival time offset | ... | + . . + . . + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Report Timestamp (32 bits) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ + +namespace { + +constexpr size_t kSenderSsrcLength = 4; +constexpr size_t kHeaderPerMediaSssrcLength = 8; +constexpr size_t kTimestampLength = 4; + +// RFC-3168, Section 5 +constexpr uint16_t kEcnEct1 = 0x01; +constexpr uint16_t kEcnEct0 = 0x02; +constexpr uint16_t kEcnCe = 0x03; + +// Arrival time offset (ATO, 13 bits): +// The arrival time of the RTP packet at the receiver, as an offset before the +// time represented by the Report Timestamp (RTS) field of this RTCP congestion +// control feedback report. The ATO field is in units of 1/1024 seconds (this +// unit is chosen to give exact offsets from the RTS field) so, for example, an +// ATO value of 512 indicates that the corresponding RTP packet arrived exactly +// half a second before the time instant represented by the RTS field. If the +// measured value is greater than 8189/1024 seconds (the value that would be +// coded as 0x1FFD), the value 0x1FFE MUST be reported to indicate an over-range +// measurement. If the measurement is unavailable or if the arrival time of the +// RTP packet is after the time represented by the RTS field, then an ATO value +// of 0x1FFF MUST be reported for the packet. +uint16_t To13bitAto(TimeDelta arrival_time_offset) { + if (arrival_time_offset < TimeDelta::Zero()) { + return 0x1FFF; + } + return std::min( + static_cast(1024 * arrival_time_offset.seconds()), + int64_t{0x1FFE}); +} + +TimeDelta AtoToTimeDelta(uint16_t receive_info) { + // receive_info + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // |R|ECN| Arrival time offset | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + const uint16_t ato = receive_info & 0x1FFF; + if (ato == 0x1FFE) { + return TimeDelta::PlusInfinity(); + } + if (ato == 0x1FFF) { + return TimeDelta::MinusInfinity(); + } + return TimeDelta::Seconds(ato) / 1024; +} + +uint16_t To2BitEcn(rtc::EcnMarking ecn_marking) { + switch (ecn_marking) { + case rtc::EcnMarking::kNotEct: + return 0; + case rtc::EcnMarking::kEct1: + return kEcnEct1 << 13; + case rtc::EcnMarking::kEct0: + return kEcnEct0 << 13; + case rtc::EcnMarking::kCe: + return kEcnCe << 13; + } +} + +rtc::EcnMarking ToEcnMarking(uint16_t receive_info) { + const uint16_t ecn = (receive_info >> 13) & 0b11; + if (ecn == kEcnEct1) { + return rtc::EcnMarking::kEct1; + } + if (ecn == kEcnEct0) { + return rtc::EcnMarking::kEct0; + } + if (ecn == kEcnCe) { + return rtc::EcnMarking::kCe; + } + return rtc::EcnMarking::kNotEct; +} + +} // namespace + +CongestionControlFeedback ::CongestionControlFeedback( + std::vector packets, + uint32_t compact_ntp_timestamp) + : packets_(std::move(packets)), + report_timestamp_compact_ntp_(compact_ntp_timestamp) {} + +bool CongestionControlFeedback::Create(uint8_t* buffer, + size_t* position, + size_t max_length, + PacketReadyCallback callback) const { + // Ensure there is enough room for this packet. + while (*position + BlockLength() > max_length) { + if (!OnBufferFull(buffer, position, callback)) + return false; + } + const size_t position_end = *position + BlockLength(); + + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // |V=2|P| FMT=11 | PT = 205 | length | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | SSRC of RTCP packet sender | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + CreateHeader(kFeedbackMessageType, kPacketType, HeaderLength(), buffer, + position); + ByteWriter::WriteBigEndian(&buffer[*position], sender_ssrc()); + *position += 4; + + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | SSRC of nth RTP Stream | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | begin_seq | num_reports | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // |R|ECN| Arrival time offset | ... . + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // . . + auto write_report_for_ssrc = [&](rtc::ArrayView packets) { + // SSRC of nth RTP stream. + ByteWriter::WriteBigEndian(&buffer[*position], packets[0].ssrc); + *position += 4; + + // begin_seq + ByteWriter::WriteBigEndian(&buffer[*position], + packets[0].sequence_number); + *position += 2; + // num_reports + uint16_t num_reports = packets[packets.size() - 1].sequence_number - + packets[0].sequence_number + 1; + + // Each report block MUST NOT include more than 16384 packet metric blocks + // (i.e., it MUST NOT report on more than one quarter of the sequence number + // space in a single report). + if (num_reports > 16384) { + RTC_DCHECK_NOTREACHED() << "Unexpected number of reports:" << num_reports; + return; + } + ByteWriter::WriteBigEndian(&buffer[*position], num_reports); + *position += 2; + + int packet_index = 0; + for (int i = 0; i < num_reports; ++i) { + uint16_t sequence_number = packets[0].sequence_number + i; + + bool received = sequence_number == packets[packet_index].sequence_number; + + uint16_t packet_info = 0; + if (received) { + packet_info = 0x8000 | To2BitEcn(packets[packet_index].ecn) | + To13bitAto(packets[packet_index].arrival_time_offset); + + ++packet_index; + } + ByteWriter::WriteBigEndian(&buffer[*position], packet_info); + *position += 2; + } + // 32bit align per SSRC block. + if (num_reports % 2 != 0) { + ByteWriter::WriteBigEndian(&buffer[*position], 0); + *position += 2; + } + }; + + rtc::ArrayView remaining(packets_); + while (!remaining.empty()) { + int number_of_packets_for_ssrc = 0; + uint32_t ssrc = remaining[0].ssrc; + for (const PacketInfo& packet_info : remaining) { + if (packet_info.ssrc != ssrc) { + break; + } + ++number_of_packets_for_ssrc; + } + write_report_for_ssrc(remaining.subview(0, number_of_packets_for_ssrc)); + remaining = remaining.subview(number_of_packets_for_ssrc); + } + + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Report Timestamp (32 bits) | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + ByteWriter::WriteBigEndian(&buffer[*position], + report_timestamp_compact_ntp_); + *position += 4; + + RTC_DCHECK_EQ(*position, position_end); + return true; +} + +size_t CongestionControlFeedback::BlockLength() const { + // Total size of this packet + size_t total_size = kSenderSsrcLength + kHeaderLength + kTimestampLength; + if (packets_.empty()) { + return total_size; + } + + auto increase_size_per_ssrc = [](int number_of_packets_for_ssrc) { + // Each packet report needs two bytes. + size_t packet_block_size = number_of_packets_for_ssrc * 2; + // 32 bit aligned. + return kHeaderPerMediaSssrcLength + packet_block_size + + ((number_of_packets_for_ssrc % 2) != 0 ? 2 : 0); + }; + + uint32_t ssrc = packets_.front().ssrc; + uint16_t first_sequence_number = packets_.front().sequence_number; + for (size_t i = 0; i < packets_.size(); ++i) { + if (packets_[i].ssrc != ssrc) { + uint16_t number_of_packets = + packets_[i - 1].sequence_number - first_sequence_number + 1; + total_size += increase_size_per_ssrc(number_of_packets); + ssrc = packets_[i].ssrc; + first_sequence_number = packets_[i].sequence_number; + } + } + uint16_t number_of_packets = + packets_.back().sequence_number - first_sequence_number + 1; + total_size += increase_size_per_ssrc(number_of_packets); + + return total_size; +} + +bool CongestionControlFeedback::Parse(const rtcp::CommonHeader& packet) { + const uint8_t* payload = packet.payload(); + const uint8_t* payload_end = packet.payload() + packet.payload_size_bytes(); + + if (packet.payload_size_bytes() % 4 != 0 || + packet.payload_size_bytes() < kSenderSsrcLength + kTimestampLength) { + return false; + } + + SetSenderSsrc(ByteReader::ReadBigEndian(payload)); + payload += 4; + + report_timestamp_compact_ntp_ = + ByteReader::ReadBigEndian(payload_end - 4); + payload_end -= 4; + + while (payload + kHeaderPerMediaSssrcLength < payload_end) { + uint32_t ssrc = ByteReader::ReadBigEndian(payload); + payload += 4; + + uint16_t base_seqno = ByteReader::ReadBigEndian(payload); + payload += 2; + uint16_t num_reports = ByteReader::ReadBigEndian(payload); + payload += 2; + + constexpr size_t kPerPacketLength = 2; + if (payload + kPerPacketLength * num_reports > payload_end) { + return false; + } + + for (int i = 0; i < num_reports; ++i) { + uint16_t packet_info = ByteReader::ReadBigEndian(payload); + payload += 2; + + uint16_t seq_no = base_seqno + i; + bool received = (packet_info & 0x8000); + if (received) { + packets_.push_back({.ssrc = ssrc, + .sequence_number = seq_no, + .arrival_time_offset = AtoToTimeDelta(packet_info), + .ecn = ToEcnMarking(packet_info)}); + } + } + if (num_reports % 2) { + // 2 bytes padding + payload += 2; + } + } + return payload == payload_end; +} +} // namespace rtcp +} // namespace webrtc diff --git a/modules/rtp_rtcp/source/rtcp_packet/congestion_control_feedback.h b/modules/rtp_rtcp/source/rtcp_packet/congestion_control_feedback.h new file mode 100644 index 0000000000..ca354458bd --- /dev/null +++ b/modules/rtp_rtcp/source/rtcp_packet/congestion_control_feedback.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2024 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. + */ +#ifndef MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_CONGESTION_CONTROL_FEEDBACK_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_CONGESTION_CONTROL_FEEDBACK_H_ + +#include +#include + +#include "api/array_view.h" +#include "api/units/time_delta.h" +#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "modules/rtp_rtcp/source/rtcp_packet/rtpfb.h" +#include "rtc_base/network/ecn_marking.h" + +namespace webrtc { +namespace rtcp { + +// Congestion control feedback message as specified in +// https://www.rfc-editor.org/rfc/rfc8888.html +class CongestionControlFeedback : public Rtpfb { + public: + struct PacketInfo { + uint32_t ssrc = 0; + uint16_t sequence_number = 0; + // Time offset from report timestamp. + TimeDelta arrival_time_offset = TimeDelta::Zero(); + rtc::EcnMarking ecn = rtc::EcnMarking::kNotEct; + }; + + static constexpr uint8_t kFeedbackMessageType = 11; + + // `Packets` MUST be sorted in sequence_number order per SSRC. + // `Packets` MUST not include duplicate sequence numbers. + CongestionControlFeedback(std::vector packets, + uint32_t report_timestamp_compact_ntp); + CongestionControlFeedback() = default; + + bool Parse(const CommonHeader& packet); + + rtc::ArrayView packets() const { return packets_; } + + uint32_t report_timestamp_compact_ntp() const { + return report_timestamp_compact_ntp_; + } + + // Serialize the packet. + bool Create(uint8_t* packet, + size_t* position, + size_t max_length, + PacketReadyCallback callback) const override; + size_t BlockLength() const override; + + private: + std::vector packets_; + uint32_t report_timestamp_compact_ntp_ = 0; +}; + +} // namespace rtcp +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_CONGESTION_CONTROL_FEEDBACK_H_ diff --git a/modules/rtp_rtcp/source/rtcp_packet/congestion_control_feedback_unittest.cc b/modules/rtp_rtcp/source/rtcp_packet/congestion_control_feedback_unittest.cc new file mode 100644 index 0000000000..05781ff17a --- /dev/null +++ b/modules/rtp_rtcp/source/rtcp_packet/congestion_control_feedback_unittest.cc @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2024 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/congestion_control_feedback.h" + +#include +#include +#include + +#include "api/units/time_delta.h" +#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "modules/rtp_rtcp/source/rtcp_packet/rtpfb.h" +#include "rtc_base/buffer.h" +#include "rtc_base/logging.h" +#include "rtc_base/network/ecn_marking.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { +namespace rtcp { + +using ::testing::IsEmpty; + +// PacketInfo is equal after serializing-deserializing if members are equal +// except for arrival time offset that may differ because of conversion back and +// forth to CompactNtp. +bool PacketInfoEqual(const CongestionControlFeedback::PacketInfo& a, + const CongestionControlFeedback::PacketInfo& b) { + bool equal = a.ssrc == b.ssrc && a.sequence_number == b.sequence_number && + ((a.arrival_time_offset - b.arrival_time_offset).Abs() < + TimeDelta::Seconds(1) / 1024) && + a.ecn == b.ecn; + RTC_LOG_IF(LS_INFO, !equal) + << " Not equal got ssrc: " << a.ssrc << ", seq: " << a.sequence_number + << " arrival_time_offset: " << a.arrival_time_offset.ms() + << " ecn: " << a.ecn << " expected ssrc:" << b.ssrc + << ", seq: " << b.sequence_number + << " arrival_time_offset: " << b.arrival_time_offset.ms() + << " ecn: " << b.ecn; + return equal; +} + +MATCHER_P(PacketInfoEqual, expected_vector, "") { + if (expected_vector.size() != arg.size()) { + RTC_LOG(LS_INFO) << " Wrong size, expected: " << expected_vector.size() + << " got: " << arg.size(); + return false; + } + for (size_t i = 0; i < expected_vector.size(); ++i) { + if (!PacketInfoEqual(arg[i], expected_vector[i])) { + return false; + } + } + return true; +} + +TEST(CongestionControlFeedbackTest, BlockLengthNoPackets) { + CongestionControlFeedback fb({}, /*compact_ntp_timestamp=*/1); + EXPECT_EQ(fb.BlockLength(), + /*common header */ 4u /*sender ssrc*/ + 4u + /*timestamp*/ 4u); +} + +TEST(CongestionControlFeedbackTest, BlockLengthTwoSsrcOnePacketEach) { + std::vector packets = { + {.ssrc = 1, .sequence_number = 1}, {.ssrc = 2, .sequence_number = 1}}; + + CongestionControlFeedback fb(std::move(packets), /*compact_ntp_timestamp=*/1); + EXPECT_EQ(fb.BlockLength(), + /*common header */ 4u + /*sender ssrc*/ + 4u + + /*timestamp*/ 4u + + /*per ssrc header*/ 2 * 8u + + /* padded packet info per ssrc*/ 2 * 4u); +} + +TEST(CongestionControlFeedbackTest, BlockLengthTwoSsrcTwoPacketsEach) { + std::vector packets = { + {.ssrc = 1, .sequence_number = 1}, + {.ssrc = 1, .sequence_number = 2}, + {.ssrc = 2, .sequence_number = 1}, + {.ssrc = 2, .sequence_number = 2}}; + + CongestionControlFeedback fb(std::move(packets), /*compact_ntp_timestamp=*/1); + EXPECT_EQ(fb.BlockLength(), + /*common header */ 4u + /*sender ssrc*/ + 4u + + /*timestamp*/ 4u + + /*per ssrc header*/ 2 * 8u + + /*packet info per ssrc*/ 2 * 4u); +} + +TEST(CongestionControlFeedbackTest, BlockLengthMissingPackets) { + std::vector packets = { + {.ssrc = 1, .sequence_number = 1}, + {.ssrc = 1, .sequence_number = 4}, + }; + + CongestionControlFeedback fb(std::move(packets), /*compact_ntp_timestamp=*/1); + EXPECT_EQ(fb.BlockLength(), + /*common header */ 4u + /*sender ssrc*/ + 4u + + /*timestamp*/ 4u + + /*per ssrc header*/ 1 * 8u + + /*packet info per ssrc*/ 2 * 4u); +} + +TEST(CongestionControlFeedbackTest, CreateReturnsTrueForBasicPacket) { + std::vector packets = { + {.ssrc = 1, + .sequence_number = 1, + .arrival_time_offset = TimeDelta::Millis(1)}, + {.ssrc = 2, + .sequence_number = 2, + .arrival_time_offset = TimeDelta::Millis(2)}}; + CongestionControlFeedback fb(std::move(packets), /*compact_ntp_timestamp=*/1); + + rtc::Buffer buf(fb.BlockLength()); + size_t position = 0; + rtc::FunctionView packet)> callback; + EXPECT_TRUE(fb.Create(buf.data(), &position, buf.capacity(), callback)); +} + +TEST(CongestionControlFeedbackTest, CanCreateAndParseWithoutPackets) { + const std::vector kPackets = {}; + uint32_t kCompactNtp = 1234; + CongestionControlFeedback fb(kPackets, kCompactNtp); + + rtc::Buffer buffer = fb.Build(); + CongestionControlFeedback parsed_fb; + CommonHeader header; + EXPECT_TRUE(header.Parse(buffer.data(), buffer.size())); + EXPECT_TRUE(parsed_fb.Parse(header)); + EXPECT_THAT(parsed_fb.packets(), IsEmpty()); + + EXPECT_EQ(parsed_fb.report_timestamp_compact_ntp(), kCompactNtp); + EXPECT_THAT(parsed_fb.packets(), PacketInfoEqual(kPackets)); +} + +TEST(CongestionControlFeedbackTest, CanCreateAndParsePacketsWithTwoSsrc) { + const std::vector kPackets = { + {.ssrc = 1, + .sequence_number = 1, + .arrival_time_offset = TimeDelta::Millis(1)}, + {.ssrc = 2, + .sequence_number = 1, + .arrival_time_offset = TimeDelta::Millis(3)}}; + uint32_t kCompactNtp = 1234; + CongestionControlFeedback fb(kPackets, kCompactNtp); + + rtc::Buffer buffer = fb.Build(); + CongestionControlFeedback parsed_fb; + CommonHeader header; + EXPECT_TRUE(header.Parse(buffer.data(), buffer.size())); + EXPECT_EQ(header.fmt(), CongestionControlFeedback::kFeedbackMessageType); + EXPECT_EQ(header.type(), Rtpfb::kPacketType); + EXPECT_TRUE(parsed_fb.Parse(header)); + + EXPECT_EQ(parsed_fb.report_timestamp_compact_ntp(), kCompactNtp); + EXPECT_THAT(parsed_fb.packets(), PacketInfoEqual(kPackets)); +} + +TEST(CongestionControlFeedbackTest, CanCreateAndParsePacketWithEcnCe) { + const std::vector kPackets = { + {.ssrc = 1, + .sequence_number = 1, + .arrival_time_offset = TimeDelta::Millis(1), + .ecn = rtc::EcnMarking::kCe}}; + uint32_t kCompactNtp = 1234; + CongestionControlFeedback fb(kPackets, kCompactNtp); + + rtc::Buffer buffer = fb.Build(); + CongestionControlFeedback parsed_fb; + CommonHeader header; + EXPECT_TRUE(header.Parse(buffer.data(), buffer.size())); + EXPECT_TRUE(parsed_fb.Parse(header)); + EXPECT_THAT(parsed_fb.packets(), PacketInfoEqual(kPackets)); +} + +TEST(CongestionControlFeedbackTest, CanCreateAndParsePacketWithEct1) { + const std::vector kPackets = { + {.ssrc = 1, + .sequence_number = 1, + .arrival_time_offset = TimeDelta::Millis(1), + .ecn = rtc::EcnMarking::kEct1}}; + uint32_t kCompactNtp = 1234; + CongestionControlFeedback fb(kPackets, kCompactNtp); + + rtc::Buffer buffer = fb.Build(); + CongestionControlFeedback parsed_fb; + CommonHeader header; + EXPECT_TRUE(header.Parse(buffer.data(), buffer.size())); + EXPECT_TRUE(parsed_fb.Parse(header)); + EXPECT_THAT(parsed_fb.packets(), PacketInfoEqual(kPackets)); +} + +TEST(CongestionControlFeedbackTest, CanCreateAndParseWithMissingPackets) { + const std::vector kPackets = { + {.ssrc = 1, + .sequence_number = 0xFFFE, + .arrival_time_offset = TimeDelta::Millis(1)}, + {.ssrc = 1, + .sequence_number = 1, + .arrival_time_offset = TimeDelta::Millis(1)}}; + uint32_t kCompactNtp = 1234; + CongestionControlFeedback fb(kPackets, kCompactNtp); + + rtc::Buffer buffer = fb.Build(); + CongestionControlFeedback parsed_fb; + CommonHeader header; + EXPECT_TRUE(header.Parse(buffer.data(), buffer.size())); + EXPECT_TRUE(parsed_fb.Parse(header)); + EXPECT_THAT(parsed_fb.packets(), PacketInfoEqual(kPackets)); +} + +} // namespace rtcp +} // namespace webrtc