diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet')
72 files changed, 8132 insertions, 0 deletions
diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/app.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/app.cc new file mode 100644 index 0000000000..d5734c6dd5 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/app.cc @@ -0,0 +1,103 @@ +/* + * 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/app.h" + +#include <string.h> + +#include <cstdint> + +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace rtcp { +constexpr uint8_t App::kPacketType; +constexpr size_t App::kMaxDataSize; +// Application-Defined packet (APP) (RFC 3550). +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |V=2|P| subtype | PT=APP=204 | length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 0 | SSRC/CSRC | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 4 | name (ASCII) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 8 | application-dependent data ... +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +App::App() : sub_type_(0), name_(0) {} + +App::~App() = default; + +bool App::Parse(const CommonHeader& packet) { + RTC_DCHECK_EQ(packet.type(), kPacketType); + if (packet.payload_size_bytes() < kAppBaseLength) { + RTC_LOG(LS_WARNING) << "Packet is too small to be a valid APP packet"; + return false; + } + if (packet.payload_size_bytes() % 4 != 0) { + RTC_LOG(LS_WARNING) + << "Packet payload must be 32 bits aligned to make a valid APP packet"; + return false; + } + sub_type_ = packet.fmt(); + SetSenderSsrc(ByteReader<uint32_t>::ReadBigEndian(&packet.payload()[0])); + name_ = ByteReader<uint32_t>::ReadBigEndian(&packet.payload()[4]); + data_.SetData(packet.payload() + kAppBaseLength, + packet.payload_size_bytes() - kAppBaseLength); + return true; +} + +void App::SetSubType(uint8_t subtype) { + RTC_DCHECK_LE(subtype, 0x1f); + sub_type_ = subtype; +} + +void App::SetData(const uint8_t* data, size_t data_length) { + RTC_DCHECK(data); + RTC_DCHECK_EQ(data_length % 4, 0) << "Data must be 32 bits aligned."; + RTC_DCHECK_LE(data_length, kMaxDataSize) + << "App data size " << data_length << " exceed maximum of " + << kMaxDataSize << " bytes."; + data_.SetData(data, data_length); +} + +size_t App::BlockLength() const { + return kHeaderLength + kAppBaseLength + data_.size(); +} + +bool App::Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const { + while (*index + BlockLength() > max_length) { + if (!OnBufferFull(packet, index, callback)) + return false; + } + const size_t index_end = *index + BlockLength(); + CreateHeader(sub_type_, kPacketType, HeaderLength(), packet, index); + + ByteWriter<uint32_t>::WriteBigEndian(&packet[*index + 0], sender_ssrc()); + ByteWriter<uint32_t>::WriteBigEndian(&packet[*index + 4], name_); + if (!data_.empty()) { + memcpy(&packet[*index + 8], data_.data(), data_.size()); + } + *index += (8 + data_.size()); + RTC_DCHECK_EQ(index_end, *index); + return true; +} + +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/app.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/app.h new file mode 100644 index 0000000000..4518792e5a --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/app.h @@ -0,0 +1,67 @@ +/* + * 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. + */ + +#ifndef MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_APP_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_APP_H_ + +#include <stddef.h> +#include <stdint.h> + +#include "modules/rtp_rtcp/source/rtcp_packet.h" +#include "rtc_base/buffer.h" + +namespace webrtc { +namespace rtcp { +class CommonHeader; + +class App : public RtcpPacket { + public: + static constexpr uint8_t kPacketType = 204; + App(); + App(App&&) = default; + ~App() override; + + // Parse assumes header is already parsed and validated. + bool Parse(const CommonHeader& packet); + + void SetSubType(uint8_t subtype); + void SetName(uint32_t name) { name_ = name; } + void SetData(const uint8_t* data, size_t data_length); + + uint8_t sub_type() const { return sub_type_; } + uint32_t name() const { return name_; } + size_t data_size() const { return data_.size(); } + const uint8_t* data() const { return data_.data(); } + + size_t BlockLength() const override; + + bool Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const override; + + static inline constexpr uint32_t NameToInt(const char name[5]) { + return static_cast<uint32_t>(name[0]) << 24 | + static_cast<uint32_t>(name[1]) << 16 | + static_cast<uint32_t>(name[2]) << 8 | static_cast<uint32_t>(name[3]); + } + + private: + static constexpr size_t kAppBaseLength = 8; // Ssrc and Name. + static constexpr size_t kMaxDataSize = 0xffff * 4 - kAppBaseLength; + + uint8_t sub_type_; + uint32_t name_; + rtc::Buffer data_; +}; + +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_APP_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/app_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/app_unittest.cc new file mode 100644 index 0000000000..8690e8e5a0 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/app_unittest.cc @@ -0,0 +1,110 @@ +/* + * 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/app.h" + +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/rtcp_packet_parser.h" + +namespace webrtc { +namespace { + +using ::testing::ElementsAreArray; +using ::testing::make_tuple; +using ::webrtc::rtcp::App; + +constexpr uint32_t kName = ((uint32_t)'n' << 24) | ((uint32_t)'a' << 16) | + ((uint32_t)'m' << 8) | (uint32_t)'e'; +constexpr uint8_t kSubtype = 0x1e; +constexpr uint32_t kSenderSsrc = 0x12345678; +constexpr uint8_t kData[] = {'t', 'e', 's', 't', 'd', 'a', 't', 'a'}; +constexpr uint8_t kVersionBits = 2 << 6; +constexpr uint8_t kPaddingBit = 1 << 5; +// clang-format off +constexpr uint8_t kPacketWithoutData[] = { + kVersionBits | kSubtype, App::kPacketType, 0x00, 0x02, + 0x12, 0x34, 0x56, 0x78, + 'n', 'a', 'm', 'e'}; +constexpr uint8_t kPacketWithData[] = { + kVersionBits | kSubtype, App::kPacketType, 0x00, 0x04, + 0x12, 0x34, 0x56, 0x78, + 'n', 'a', 'm', 'e', + 't', 'e', 's', 't', + 'd', 'a', 't', 'a'}; +constexpr uint8_t kTooSmallPacket[] = { + kVersionBits | kSubtype, App::kPacketType, 0x00, 0x01, + 0x12, 0x34, 0x56, 0x78}; +constexpr uint8_t kPaddingSize = 1; +constexpr uint8_t kPacketWithUnalignedPayload[] = { + kVersionBits | kPaddingBit | kSubtype, App::kPacketType, 0x00, 0x03, + 0x12, 0x34, 0x56, 0x78, + 'n', 'a', 'm', 'e', + 'd', 'a', 't', kPaddingSize}; +// clang-format on +} // namespace + +TEST(RtcpPacketAppTest, CreateWithoutData) { + App app; + app.SetSenderSsrc(kSenderSsrc); + app.SetSubType(kSubtype); + app.SetName(kName); + + rtc::Buffer raw = app.Build(); + + EXPECT_THAT(make_tuple(raw.data(), raw.size()), + ElementsAreArray(kPacketWithoutData)); +} + +TEST(RtcpPacketAppTest, ParseWithoutData) { + App parsed; + EXPECT_TRUE(test::ParseSinglePacket(kPacketWithoutData, &parsed)); + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_EQ(kSubtype, parsed.sub_type()); + EXPECT_EQ(kName, parsed.name()); + EXPECT_EQ(0u, parsed.data_size()); +} + +TEST(RtcpPacketAppTest, CreateWithData) { + App app; + app.SetSenderSsrc(kSenderSsrc); + app.SetSubType(kSubtype); + app.SetName(kName); + app.SetData(kData, sizeof(kData)); + + rtc::Buffer raw = app.Build(); + + EXPECT_THAT(make_tuple(raw.data(), raw.size()), + ElementsAreArray(kPacketWithData)); +} + +TEST(RtcpPacketAppTest, ParseWithData) { + App parsed; + EXPECT_TRUE(test::ParseSinglePacket(kPacketWithData, &parsed)); + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_EQ(kSubtype, parsed.sub_type()); + EXPECT_EQ(kName, parsed.name()); + EXPECT_THAT(make_tuple(parsed.data(), parsed.data_size()), + ElementsAreArray(kData)); +} + +TEST(RtcpPacketAppTest, ParseFailsOnTooSmallPacket) { + App parsed; + EXPECT_FALSE(test::ParseSinglePacket(kTooSmallPacket, &parsed)); +} + +TEST(RtcpPacketAppTest, ParseFailsOnUnalignedPayload) { + App parsed; + EXPECT_FALSE(test::ParseSinglePacket(kPacketWithUnalignedPayload, &parsed)); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/bye.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/bye.cc new file mode 100644 index 0000000000..a6471772b1 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/bye.cc @@ -0,0 +1,141 @@ +/* + * 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/bye.h" + +#include <string.h> + +#include <cstdint> +#include <utility> + +#include "absl/strings/string_view.h" +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace rtcp { +constexpr uint8_t Bye::kPacketType; +// Bye packet (BYE) (RFC 3550). +// +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |V=2|P| SC | PT=BYE=203 | length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC/CSRC | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// : ... : +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// (opt) | length | reason for leaving ... +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +Bye::Bye() = default; + +Bye::~Bye() = default; + +bool Bye::Parse(const CommonHeader& packet) { + RTC_DCHECK_EQ(packet.type(), kPacketType); + + const uint8_t src_count = packet.count(); + // Validate packet. + if (packet.payload_size_bytes() < 4u * src_count) { + RTC_LOG(LS_WARNING) + << "Packet is too small to contain CSRCs it promise to have."; + return false; + } + const uint8_t* const payload = packet.payload(); + bool has_reason = packet.payload_size_bytes() > 4u * src_count; + uint8_t reason_length = 0; + if (has_reason) { + reason_length = payload[4u * src_count]; + if (packet.payload_size_bytes() - 4u * src_count < 1u + reason_length) { + RTC_LOG(LS_WARNING) << "Invalid reason length: " << reason_length; + return false; + } + } + // Once sure packet is valid, copy values. + if (src_count == 0) { // A count value of zero is valid, but useless. + SetSenderSsrc(0); + csrcs_.clear(); + } else { + SetSenderSsrc(ByteReader<uint32_t>::ReadBigEndian(payload)); + csrcs_.resize(src_count - 1); + for (size_t i = 1; i < src_count; ++i) + csrcs_[i - 1] = ByteReader<uint32_t>::ReadBigEndian(&payload[4 * i]); + } + + if (has_reason) { + reason_.assign(reinterpret_cast<const char*>(&payload[4u * src_count + 1]), + reason_length); + } else { + reason_.clear(); + } + + return true; +} + +bool Bye::Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const { + while (*index + BlockLength() > max_length) { + if (!OnBufferFull(packet, index, callback)) + return false; + } + const size_t index_end = *index + BlockLength(); + + CreateHeader(1 + csrcs_.size(), kPacketType, HeaderLength(), packet, index); + // Store srcs of the leaving clients. + ByteWriter<uint32_t>::WriteBigEndian(&packet[*index], sender_ssrc()); + *index += sizeof(uint32_t); + for (uint32_t csrc : csrcs_) { + ByteWriter<uint32_t>::WriteBigEndian(&packet[*index], csrc); + *index += sizeof(uint32_t); + } + // Store the reason to leave. + if (!reason_.empty()) { + uint8_t reason_length = static_cast<uint8_t>(reason_.size()); + packet[(*index)++] = reason_length; + memcpy(&packet[*index], reason_.data(), reason_length); + *index += reason_length; + // Add padding bytes if needed. + size_t bytes_to_pad = index_end - *index; + RTC_DCHECK_LE(bytes_to_pad, 3); + if (bytes_to_pad > 0) { + memset(&packet[*index], 0, bytes_to_pad); + *index += bytes_to_pad; + } + } + RTC_DCHECK_EQ(index_end, *index); + return true; +} + +bool Bye::SetCsrcs(std::vector<uint32_t> csrcs) { + if (csrcs.size() > kMaxNumberOfCsrcs) { + RTC_LOG(LS_WARNING) << "Too many CSRCs for Bye packet."; + return false; + } + csrcs_ = std::move(csrcs); + return true; +} + +void Bye::SetReason(absl::string_view reason) { + RTC_DCHECK_LE(reason.size(), 0xffu); + reason_ = std::string(reason); +} + +size_t Bye::BlockLength() const { + size_t src_count = (1 + csrcs_.size()); + size_t reason_size_in_32bits = reason_.empty() ? 0 : (reason_.size() / 4 + 1); + return kHeaderLength + 4 * (src_count + reason_size_in_32bits); +} + +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/bye.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/bye.h new file mode 100644 index 0000000000..d31205793a --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/bye.h @@ -0,0 +1,57 @@ +/* + * 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. + * + */ + +#ifndef MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_BYE_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_BYE_H_ + +#include <string> +#include <vector> + +#include "absl/strings/string_view.h" +#include "modules/rtp_rtcp/source/rtcp_packet.h" + +namespace webrtc { +namespace rtcp { +class CommonHeader; + +class Bye : public RtcpPacket { + public: + static constexpr uint8_t kPacketType = 203; + + Bye(); + ~Bye() override; + + // Parse assumes header is already parsed and validated. + bool Parse(const CommonHeader& packet); + + bool SetCsrcs(std::vector<uint32_t> csrcs); + void SetReason(absl::string_view reason); + + const std::vector<uint32_t>& csrcs() const { return csrcs_; } + const std::string& reason() const { return reason_; } + + size_t BlockLength() const override; + + bool Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const override; + + private: + static const int kMaxNumberOfCsrcs = 0x1f - 1; // First item is sender SSRC. + + std::vector<uint32_t> csrcs_; + std::string reason_; +}; + +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_BYE_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/bye_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/bye_unittest.cc new file mode 100644 index 0000000000..448c2d4194 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/bye_unittest.cc @@ -0,0 +1,147 @@ +/* + * 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/bye.h" + +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/rtcp_packet_parser.h" + +using ::testing::ElementsAre; +using webrtc::rtcp::Bye; + +namespace webrtc { +namespace { +const uint32_t kSenderSsrc = 0x12345678; +const uint32_t kCsrc1 = 0x22232425; +const uint32_t kCsrc2 = 0x33343536; +} // namespace + +TEST(RtcpPacketByeTest, CreateAndParseWithoutReason) { + Bye bye; + bye.SetSenderSsrc(kSenderSsrc); + + rtc::Buffer raw = bye.Build(); + Bye parsed_bye; + EXPECT_TRUE(test::ParseSinglePacket(raw, &parsed_bye)); + + EXPECT_EQ(kSenderSsrc, parsed_bye.sender_ssrc()); + EXPECT_TRUE(parsed_bye.csrcs().empty()); + EXPECT_TRUE(parsed_bye.reason().empty()); +} + +TEST(RtcpPacketByeTest, CreateAndParseWithCsrcs) { + Bye bye; + bye.SetSenderSsrc(kSenderSsrc); + EXPECT_TRUE(bye.SetCsrcs({kCsrc1, kCsrc2})); + EXPECT_TRUE(bye.reason().empty()); + + rtc::Buffer raw = bye.Build(); + Bye parsed_bye; + EXPECT_TRUE(test::ParseSinglePacket(raw, &parsed_bye)); + + EXPECT_EQ(kSenderSsrc, parsed_bye.sender_ssrc()); + EXPECT_THAT(parsed_bye.csrcs(), ElementsAre(kCsrc1, kCsrc2)); + EXPECT_TRUE(parsed_bye.reason().empty()); +} + +TEST(RtcpPacketByeTest, CreateAndParseWithCsrcsAndAReason) { + Bye bye; + const std::string kReason = "Some Reason"; + + bye.SetSenderSsrc(kSenderSsrc); + EXPECT_TRUE(bye.SetCsrcs({kCsrc1, kCsrc2})); + bye.SetReason(kReason); + + rtc::Buffer raw = bye.Build(); + Bye parsed_bye; + EXPECT_TRUE(test::ParseSinglePacket(raw, &parsed_bye)); + + EXPECT_EQ(kSenderSsrc, parsed_bye.sender_ssrc()); + EXPECT_THAT(parsed_bye.csrcs(), ElementsAre(kCsrc1, kCsrc2)); + EXPECT_EQ(kReason, parsed_bye.reason()); +} + +TEST(RtcpPacketByeTest, CreateWithTooManyCsrcs) { + Bye bye; + bye.SetSenderSsrc(kSenderSsrc); + const int kMaxCsrcs = (1 << 5) - 2; // 5 bit len, first item is sender SSRC. + EXPECT_TRUE(bye.SetCsrcs(std::vector<uint32_t>(kMaxCsrcs, kCsrc1))); + EXPECT_FALSE(bye.SetCsrcs(std::vector<uint32_t>(kMaxCsrcs + 1, kCsrc1))); +} + +TEST(RtcpPacketByeTest, CreateAndParseWithAReason) { + Bye bye; + const std::string kReason = "Some Random Reason"; + + bye.SetSenderSsrc(kSenderSsrc); + bye.SetReason(kReason); + + rtc::Buffer raw = bye.Build(); + Bye parsed_bye; + EXPECT_TRUE(test::ParseSinglePacket(raw, &parsed_bye)); + + EXPECT_EQ(kSenderSsrc, parsed_bye.sender_ssrc()); + EXPECT_TRUE(parsed_bye.csrcs().empty()); + EXPECT_EQ(kReason, parsed_bye.reason()); +} + +TEST(RtcpPacketByeTest, CreateAndParseWithReasons) { + // Test that packet creation/parsing behave with reasons of different length + // both when it require padding and when it does not. + for (size_t reminder = 0; reminder < 4; ++reminder) { + const std::string kReason(4 + reminder, 'a' + reminder); + Bye bye; + bye.SetSenderSsrc(kSenderSsrc); + bye.SetReason(kReason); + + rtc::Buffer raw = bye.Build(); + Bye parsed_bye; + EXPECT_TRUE(test::ParseSinglePacket(raw, &parsed_bye)); + + EXPECT_EQ(kReason, parsed_bye.reason()); + } +} + +TEST(RtcpPacketByeTest, ParseEmptyPacket) { + uint8_t kEmptyPacket[] = {0x80, Bye::kPacketType, 0, 0}; + Bye parsed_bye; + EXPECT_TRUE(test::ParseSinglePacket(kEmptyPacket, &parsed_bye)); + EXPECT_EQ(0u, parsed_bye.sender_ssrc()); + EXPECT_TRUE(parsed_bye.csrcs().empty()); + EXPECT_TRUE(parsed_bye.reason().empty()); +} + +TEST(RtcpPacketByeTest, ParseFailOnInvalidSrcCount) { + Bye bye; + bye.SetSenderSsrc(kSenderSsrc); + + rtc::Buffer raw = bye.Build(); + raw[0]++; // Damage the packet: increase ssrc count by one. + + Bye parsed_bye; + EXPECT_FALSE(test::ParseSinglePacket(raw, &parsed_bye)); +} + +TEST(RtcpPacketByeTest, ParseFailOnInvalidReasonLength) { + Bye bye; + bye.SetSenderSsrc(kSenderSsrc); + bye.SetReason("18 characters long"); + + rtc::Buffer raw = bye.Build(); + // Damage the packet: decrease payload size by 4 bytes + raw[3]--; + raw.SetSize(raw.size() - 4); + + Bye parsed_bye; + EXPECT_FALSE(test::ParseSinglePacket(raw, &parsed_bye)); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/common_header.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/common_header.cc new file mode 100644 index 0000000000..5b54982220 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/common_header.cc @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2016 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/common_header.h" + +#include "modules/rtp_rtcp/source/byte_io.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace rtcp { +constexpr size_t CommonHeader::kHeaderSizeBytes; +// 0 1 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 0 |V=2|P| C/F | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 1 | Packet Type | +// ----------------+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 2 | length | +// --------------------------------+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +// Common header for all RTCP packets, 4 octets. +bool CommonHeader::Parse(const uint8_t* buffer, size_t size_bytes) { + const uint8_t kVersion = 2; + + if (size_bytes < kHeaderSizeBytes) { + RTC_LOG(LS_WARNING) + << "Too little data (" << size_bytes << " byte" + << (size_bytes != 1 ? "s" : "") + << ") remaining in buffer to parse RTCP header (4 bytes)."; + return false; + } + + uint8_t version = buffer[0] >> 6; + if (version != kVersion) { + RTC_LOG(LS_WARNING) << "Invalid RTCP header: Version must be " + << static_cast<int>(kVersion) << " but was " + << static_cast<int>(version); + return false; + } + + bool has_padding = (buffer[0] & 0x20) != 0; + count_or_format_ = buffer[0] & 0x1F; + packet_type_ = buffer[1]; + payload_size_ = ByteReader<uint16_t>::ReadBigEndian(&buffer[2]) * 4; + payload_ = buffer + kHeaderSizeBytes; + padding_size_ = 0; + + if (size_bytes < kHeaderSizeBytes + payload_size_) { + RTC_LOG(LS_WARNING) << "Buffer too small (" << size_bytes + << " bytes) to fit an RtcpPacket with a header and " + << payload_size_ << " bytes."; + return false; + } + + if (has_padding) { + if (payload_size_ == 0) { + RTC_LOG(LS_WARNING) + << "Invalid RTCP header: Padding bit set but 0 payload " + "size specified."; + return false; + } + + padding_size_ = payload_[payload_size_ - 1]; + if (padding_size_ == 0) { + RTC_LOG(LS_WARNING) + << "Invalid RTCP header: Padding bit set but 0 padding " + "size specified."; + return false; + } + if (padding_size_ > payload_size_) { + RTC_LOG(LS_WARNING) << "Invalid RTCP header: Too many padding bytes (" + << padding_size_ << ") for a packet payload size of " + << payload_size_ << " bytes."; + return false; + } + payload_size_ -= padding_size_; + } + return true; +} +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/common_header.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/common_header.h new file mode 100644 index 0000000000..5416406091 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/common_header.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2016 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_COMMON_HEADER_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_COMMON_HEADER_H_ + +#include <stddef.h> +#include <stdint.h> + +namespace webrtc { +namespace rtcp { +class CommonHeader { + public: + static constexpr size_t kHeaderSizeBytes = 4; + + CommonHeader() {} + CommonHeader(const CommonHeader&) = default; + CommonHeader& operator=(const CommonHeader&) = default; + + bool Parse(const uint8_t* buffer, size_t size_bytes); + + uint8_t type() const { return packet_type_; } + // Depending on packet type same header field can be used either as count or + // as feedback message type (fmt). Caller expected to know how it is used. + uint8_t fmt() const { return count_or_format_; } + uint8_t count() const { return count_or_format_; } + size_t payload_size_bytes() const { return payload_size_; } + const uint8_t* payload() const { return payload_; } + size_t packet_size() const { + return kHeaderSizeBytes + payload_size_ + padding_size_; + } + // Returns pointer to the next RTCP packet in compound packet. + const uint8_t* NextPacket() const { + return payload_ + payload_size_ + padding_size_; + } + + private: + uint8_t packet_type_ = 0; + uint8_t count_or_format_ = 0; + uint8_t padding_size_ = 0; + uint32_t payload_size_ = 0; + const uint8_t* payload_ = nullptr; +}; +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_COMMON_HEADER_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/common_header_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/common_header_unittest.cc new file mode 100644 index 0000000000..e8b4c52c68 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/common_header_unittest.cc @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2016 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/common_header.h" + +#include "test/gtest.h" + +using webrtc::rtcp::CommonHeader; + +namespace webrtc { + +TEST(RtcpCommonHeaderTest, TooSmallBuffer) { + uint8_t buffer[] = {0x80, 0x00, 0x00, 0x00}; + CommonHeader header; + // Buffer needs to be able to hold the header. + EXPECT_FALSE(header.Parse(buffer, 0)); + EXPECT_FALSE(header.Parse(buffer, 1)); + EXPECT_FALSE(header.Parse(buffer, 2)); + EXPECT_FALSE(header.Parse(buffer, 3)); + EXPECT_TRUE(header.Parse(buffer, 4)); +} + +TEST(RtcpCommonHeaderTest, Version) { + uint8_t buffer[] = {0x00, 0x00, 0x00, 0x00}; + CommonHeader header; + // Version 2 is the only allowed. + buffer[0] = 0 << 6; + EXPECT_FALSE(header.Parse(buffer, sizeof(buffer))); + buffer[0] = 1 << 6; + EXPECT_FALSE(header.Parse(buffer, sizeof(buffer))); + buffer[0] = 2 << 6; + EXPECT_TRUE(header.Parse(buffer, sizeof(buffer))); + buffer[0] = 3 << 6; + EXPECT_FALSE(header.Parse(buffer, sizeof(buffer))); +} + +TEST(RtcpCommonHeaderTest, PacketSize) { + uint8_t buffer[] = {0x80, 0x00, 0x00, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + CommonHeader header; + EXPECT_FALSE(header.Parse(buffer, sizeof(buffer) - 1)); + EXPECT_TRUE(header.Parse(buffer, sizeof(buffer))); + EXPECT_EQ(8u, header.payload_size_bytes()); + EXPECT_EQ(buffer + sizeof(buffer), header.NextPacket()); + EXPECT_EQ(sizeof(buffer), header.packet_size()); +} + +TEST(RtcpCommonHeaderTest, PaddingAndPayloadSize) { + // Set v = 2, p = 1, but leave fmt, pt as 0. + uint8_t buffer[] = {0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + CommonHeader header; + // Padding bit set, but no byte for padding (can't specify padding length). + EXPECT_FALSE(header.Parse(buffer, 4)); + + buffer[3] = 2; // Set payload size to 2x32bit. + const size_t kPayloadSizeBytes = buffer[3] * 4; + const size_t kPaddingAddress = + CommonHeader::kHeaderSizeBytes + kPayloadSizeBytes - 1; + + // Padding one byte larger than possible. + buffer[kPaddingAddress] = kPayloadSizeBytes + 1; + EXPECT_FALSE(header.Parse(buffer, sizeof(buffer))); + + // Invalid zero padding size. + buffer[kPaddingAddress] = 0; + EXPECT_FALSE(header.Parse(buffer, sizeof(buffer))); + + // Pure padding packet. + buffer[kPaddingAddress] = kPayloadSizeBytes; + EXPECT_TRUE(header.Parse(buffer, sizeof(buffer))); + EXPECT_EQ(0u, header.payload_size_bytes()); + EXPECT_EQ(buffer + sizeof(buffer), header.NextPacket()); + EXPECT_EQ(header.payload(), buffer + CommonHeader::kHeaderSizeBytes); + EXPECT_EQ(header.packet_size(), sizeof(buffer)); + + // Single byte of actual data. + buffer[kPaddingAddress] = kPayloadSizeBytes - 1; + EXPECT_TRUE(header.Parse(buffer, sizeof(buffer))); + EXPECT_EQ(1u, header.payload_size_bytes()); + EXPECT_EQ(buffer + sizeof(buffer), header.NextPacket()); + EXPECT_EQ(header.packet_size(), sizeof(buffer)); +} + +TEST(RtcpCommonHeaderTest, FormatAndPayloadType) { + uint8_t buffer[] = {0x9e, 0xab, 0x00, 0x00}; + CommonHeader header; + EXPECT_TRUE(header.Parse(buffer, sizeof(buffer))); + + EXPECT_EQ(header.count(), 0x1e); + EXPECT_EQ(header.fmt(), 0x1e); + EXPECT_EQ(header.type(), 0xab); + EXPECT_EQ(header.payload_size_bytes(), 0u); + EXPECT_EQ(header.payload(), buffer + CommonHeader::kHeaderSizeBytes); +} +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/compound_packet.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/compound_packet.cc new file mode 100644 index 0000000000..54f3555fc6 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/compound_packet.cc @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2016 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/compound_packet.h" + +#include <memory> +#include <utility> + +#include "rtc_base/checks.h" + +namespace webrtc { +namespace rtcp { + +CompoundPacket::CompoundPacket() = default; + +CompoundPacket::~CompoundPacket() = default; + +void CompoundPacket::Append(std::unique_ptr<RtcpPacket> packet) { + RTC_CHECK(packet); + appended_packets_.push_back(std::move(packet)); +} + +bool CompoundPacket::Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const { + for (const auto& appended : appended_packets_) { + if (!appended->Create(packet, index, max_length, callback)) + return false; + } + return true; +} + +size_t CompoundPacket::BlockLength() const { + size_t block_length = 0; + for (const auto& appended : appended_packets_) { + block_length += appended->BlockLength(); + } + return block_length; +} + +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/compound_packet.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/compound_packet.h new file mode 100644 index 0000000000..d98dbd088d --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/compound_packet.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2016 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_COMPOUND_PACKET_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_COMPOUND_PACKET_H_ + +#include <memory> +#include <vector> + +#include "modules/rtp_rtcp/source/rtcp_packet.h" + +namespace webrtc { +namespace rtcp { + +class CompoundPacket : public RtcpPacket { + public: + CompoundPacket(); + ~CompoundPacket() override; + + CompoundPacket(const CompoundPacket&) = delete; + CompoundPacket& operator=(const CompoundPacket&) = delete; + + void Append(std::unique_ptr<RtcpPacket> packet); + + // Size of this packet in bytes (i.e. total size of nested packets). + size_t BlockLength() const override; + // Returns true if all calls to Create succeeded. + bool Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const override; + + protected: + std::vector<std::unique_ptr<RtcpPacket>> appended_packets_; +}; + +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_COMPOUND_PACKET_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/compound_packet_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/compound_packet_unittest.cc new file mode 100644 index 0000000000..ba7c241215 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/compound_packet_unittest.cc @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2016 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/compound_packet.h" + +#include <memory> +#include <utility> + +#include "modules/rtp_rtcp/source/rtcp_packet.h" +#include "modules/rtp_rtcp/source/rtcp_packet/bye.h" +#include "modules/rtp_rtcp/source/rtcp_packet/fir.h" +#include "modules/rtp_rtcp/source/rtcp_packet/receiver_report.h" +#include "modules/rtp_rtcp/source/rtcp_packet/sender_report.h" +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/rtcp_packet_parser.h" + +using ::testing::_; +using ::testing::Invoke; +using ::testing::MockFunction; +using webrtc::rtcp::Bye; +using webrtc::rtcp::CompoundPacket; +using webrtc::rtcp::Fir; +using webrtc::rtcp::ReceiverReport; +using webrtc::rtcp::ReportBlock; +using webrtc::rtcp::SenderReport; +using webrtc::test::RtcpPacketParser; + +namespace webrtc { + +const uint32_t kSenderSsrc = 0x12345678; +const uint32_t kRemoteSsrc = 0x23456789; +const uint8_t kSeqNo = 13; + +TEST(RtcpCompoundPacketTest, AppendPacket) { + CompoundPacket compound; + auto fir = std::make_unique<Fir>(); + fir->AddRequestTo(kRemoteSsrc, kSeqNo); + ReportBlock rb; + auto rr = std::make_unique<ReceiverReport>(); + rr->SetSenderSsrc(kSenderSsrc); + EXPECT_TRUE(rr->AddReportBlock(rb)); + compound.Append(std::move(rr)); + compound.Append(std::move(fir)); + + rtc::Buffer packet = compound.Build(); + RtcpPacketParser parser; + parser.Parse(packet); + EXPECT_EQ(1, parser.receiver_report()->num_packets()); + EXPECT_EQ(kSenderSsrc, parser.receiver_report()->sender_ssrc()); + EXPECT_EQ(1u, parser.receiver_report()->report_blocks().size()); + EXPECT_EQ(1, parser.fir()->num_packets()); +} + +TEST(RtcpCompoundPacketTest, AppendPacketWithOwnAppendedPacket) { + CompoundPacket root; + auto leaf = std::make_unique<CompoundPacket>(); + + auto fir = std::make_unique<Fir>(); + fir->AddRequestTo(kRemoteSsrc, kSeqNo); + auto bye = std::make_unique<Bye>(); + ReportBlock rb; + + auto rr = std::make_unique<ReceiverReport>(); + EXPECT_TRUE(rr->AddReportBlock(rb)); + leaf->Append(std::move(rr)); + leaf->Append(std::move(fir)); + + auto sr = std::make_unique<SenderReport>(); + root.Append(std::move(sr)); + root.Append(std::move(bye)); + root.Append(std::move(leaf)); + + rtc::Buffer packet = root.Build(); + RtcpPacketParser parser; + parser.Parse(packet); + EXPECT_EQ(1, parser.sender_report()->num_packets()); + EXPECT_EQ(1, parser.receiver_report()->num_packets()); + EXPECT_EQ(1u, parser.receiver_report()->report_blocks().size()); + EXPECT_EQ(1, parser.bye()->num_packets()); + EXPECT_EQ(1, parser.fir()->num_packets()); +} + +TEST(RtcpCompoundPacketTest, BuildWithInputBuffer) { + CompoundPacket compound; + auto fir = std::make_unique<Fir>(); + fir->AddRequestTo(kRemoteSsrc, kSeqNo); + ReportBlock rb; + auto rr = std::make_unique<ReceiverReport>(); + rr->SetSenderSsrc(kSenderSsrc); + EXPECT_TRUE(rr->AddReportBlock(rb)); + compound.Append(std::move(rr)); + compound.Append(std::move(fir)); + + const size_t kRrLength = 8; + const size_t kReportBlockLength = 24; + const size_t kFirLength = 20; + + const size_t kBufferSize = kRrLength + kReportBlockLength + kFirLength; + MockFunction<void(rtc::ArrayView<const uint8_t>)> callback; + EXPECT_CALL(callback, Call(_)) + .WillOnce(Invoke([&](rtc::ArrayView<const uint8_t> packet) { + RtcpPacketParser parser; + parser.Parse(packet); + EXPECT_EQ(1, parser.receiver_report()->num_packets()); + EXPECT_EQ(1u, parser.receiver_report()->report_blocks().size()); + EXPECT_EQ(1, parser.fir()->num_packets()); + })); + + EXPECT_TRUE(compound.Build(kBufferSize, callback.AsStdFunction())); +} + +TEST(RtcpCompoundPacketTest, BuildWithTooSmallBuffer_FragmentedSend) { + CompoundPacket compound; + auto fir = std::make_unique<Fir>(); + fir->AddRequestTo(kRemoteSsrc, kSeqNo); + ReportBlock rb; + auto rr = std::make_unique<ReceiverReport>(); + rr->SetSenderSsrc(kSenderSsrc); + EXPECT_TRUE(rr->AddReportBlock(rb)); + compound.Append(std::move(rr)); + compound.Append(std::move(fir)); + + const size_t kRrLength = 8; + const size_t kReportBlockLength = 24; + + const size_t kBufferSize = kRrLength + kReportBlockLength; + MockFunction<void(rtc::ArrayView<const uint8_t>)> callback; + EXPECT_CALL(callback, Call(_)) + .WillOnce(Invoke([&](rtc::ArrayView<const uint8_t> packet) { + RtcpPacketParser parser; + parser.Parse(packet); + EXPECT_EQ(1, parser.receiver_report()->num_packets()); + EXPECT_EQ(1U, parser.receiver_report()->report_blocks().size()); + EXPECT_EQ(0, parser.fir()->num_packets()); + })) + .WillOnce(Invoke([&](rtc::ArrayView<const uint8_t> packet) { + RtcpPacketParser parser; + parser.Parse(packet); + EXPECT_EQ(0, parser.receiver_report()->num_packets()); + EXPECT_EQ(0U, parser.receiver_report()->report_blocks().size()); + EXPECT_EQ(1, parser.fir()->num_packets()); + })); + + EXPECT_TRUE(compound.Build(kBufferSize, callback.AsStdFunction())); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/dlrr.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/dlrr.cc new file mode 100644 index 0000000000..6863def2fe --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/dlrr.cc @@ -0,0 +1,94 @@ +/* + * 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/dlrr.h" + +#include "modules/rtp_rtcp/source/byte_io.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/numerics/safe_conversions.h" + +namespace webrtc { +namespace rtcp { +// DLRR Report Block (RFC 3611). +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | BT=5 | reserved | block length | +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// | SSRC_1 (SSRC of first receiver) | sub- +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ block +// | last RR (LRR) | 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | delay since last RR (DLRR) | +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// | SSRC_2 (SSRC of second receiver) | sub- +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ block +// : ... : 2 + +Dlrr::Dlrr() = default; + +Dlrr::Dlrr(const Dlrr& other) = default; + +Dlrr::~Dlrr() = default; + +bool Dlrr::Parse(const uint8_t* buffer, uint16_t block_length_32bits) { + RTC_DCHECK(buffer[0] == kBlockType); + // kReserved = buffer[1]; + RTC_DCHECK_EQ(block_length_32bits, + ByteReader<uint16_t>::ReadBigEndian(&buffer[2])); + if (block_length_32bits % 3 != 0) { + RTC_LOG(LS_WARNING) << "Invalid size for dlrr block."; + return false; + } + + size_t blocks_count = block_length_32bits / 3; + const uint8_t* read_at = buffer + kBlockHeaderLength; + sub_blocks_.resize(blocks_count); + for (ReceiveTimeInfo& sub_block : sub_blocks_) { + sub_block.ssrc = ByteReader<uint32_t>::ReadBigEndian(&read_at[0]); + sub_block.last_rr = ByteReader<uint32_t>::ReadBigEndian(&read_at[4]); + sub_block.delay_since_last_rr = + ByteReader<uint32_t>::ReadBigEndian(&read_at[8]); + read_at += kSubBlockLength; + } + return true; +} + +size_t Dlrr::BlockLength() const { + if (sub_blocks_.empty()) + return 0; + return kBlockHeaderLength + kSubBlockLength * sub_blocks_.size(); +} + +void Dlrr::Create(uint8_t* buffer) const { + if (sub_blocks_.empty()) // No subblocks, no need to write header either. + return; + // Create block header. + const uint8_t kReserved = 0; + buffer[0] = kBlockType; + buffer[1] = kReserved; + ByteWriter<uint16_t>::WriteBigEndian( + &buffer[2], rtc::dchecked_cast<uint16_t>(3 * sub_blocks_.size())); + // Create sub blocks. + uint8_t* write_at = buffer + kBlockHeaderLength; + for (const ReceiveTimeInfo& sub_block : sub_blocks_) { + ByteWriter<uint32_t>::WriteBigEndian(&write_at[0], sub_block.ssrc); + ByteWriter<uint32_t>::WriteBigEndian(&write_at[4], sub_block.last_rr); + ByteWriter<uint32_t>::WriteBigEndian(&write_at[8], + sub_block.delay_since_last_rr); + write_at += kSubBlockLength; + } + RTC_DCHECK_EQ(buffer + BlockLength(), write_at); +} + +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/dlrr.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/dlrr.h new file mode 100644 index 0000000000..ad91dfdcc6 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/dlrr.h @@ -0,0 +1,80 @@ +/* + * 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. + * + */ + +#ifndef MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_DLRR_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_DLRR_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <vector> + +namespace webrtc { +namespace rtcp { +struct ReceiveTimeInfo { + // RFC 3611 4.5 + ReceiveTimeInfo() : ssrc(0), last_rr(0), delay_since_last_rr(0) {} + ReceiveTimeInfo(uint32_t ssrc, uint32_t last_rr, uint32_t delay) + : ssrc(ssrc), last_rr(last_rr), delay_since_last_rr(delay) {} + + uint32_t ssrc; + uint32_t last_rr; + uint32_t delay_since_last_rr; +}; + +inline bool operator==(const ReceiveTimeInfo& lhs, const ReceiveTimeInfo& rhs) { + return lhs.ssrc == rhs.ssrc && lhs.last_rr == rhs.last_rr && + lhs.delay_since_last_rr == rhs.delay_since_last_rr; +} + +inline bool operator!=(const ReceiveTimeInfo& lhs, const ReceiveTimeInfo& rhs) { + return !(lhs == rhs); +} + +// DLRR Report Block: Delay since the Last Receiver Report (RFC 3611). +class Dlrr { + public: + static const uint8_t kBlockType = 5; + + Dlrr(); + Dlrr(const Dlrr& other); + ~Dlrr(); + + Dlrr& operator=(const Dlrr& other) = default; + + // Dlrr without items treated same as no dlrr block. + explicit operator bool() const { return !sub_blocks_.empty(); } + + // Second parameter is value read from block header, + // i.e. size of block in 32bits excluding block header itself. + bool Parse(const uint8_t* buffer, uint16_t block_length_32bits); + + size_t BlockLength() const; + // Fills buffer with the Dlrr. + // Consumes BlockLength() bytes. + void Create(uint8_t* buffer) const; + + void ClearItems() { sub_blocks_.clear(); } + void AddDlrrItem(const ReceiveTimeInfo& time_info) { + sub_blocks_.push_back(time_info); + } + + const std::vector<ReceiveTimeInfo>& sub_blocks() const { return sub_blocks_; } + + private: + static const size_t kBlockHeaderLength = 4; + static const size_t kSubBlockLength = 12; + + std::vector<ReceiveTimeInfo> sub_blocks_; +}; +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_DLRR_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/dlrr_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/dlrr_unittest.cc new file mode 100644 index 0000000000..408d0011b8 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/dlrr_unittest.cc @@ -0,0 +1,92 @@ +/* + * 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/dlrr.h" + +#include "modules/rtp_rtcp/source/byte_io.h" +#include "test/gtest.h" + +using webrtc::rtcp::Dlrr; +using webrtc::rtcp::ReceiveTimeInfo; + +namespace webrtc { +namespace { +const uint32_t kSsrc = 0x12345678; +const uint32_t kLastRR = 0x23344556; +const uint32_t kDelay = 0x33343536; +const uint8_t kBlock[] = {0x05, 0x00, 0x00, 0x03, 0x12, 0x34, 0x56, 0x78, + 0x23, 0x34, 0x45, 0x56, 0x33, 0x34, 0x35, 0x36}; +const size_t kBlockSizeBytes = sizeof(kBlock); +} // namespace + +TEST(RtcpPacketDlrrTest, Empty) { + Dlrr dlrr; + + EXPECT_EQ(0u, dlrr.BlockLength()); +} + +TEST(RtcpPacketDlrrTest, Create) { + Dlrr dlrr; + dlrr.AddDlrrItem(ReceiveTimeInfo(kSsrc, kLastRR, kDelay)); + + ASSERT_EQ(kBlockSizeBytes, dlrr.BlockLength()); + uint8_t buffer[kBlockSizeBytes]; + + dlrr.Create(buffer); + EXPECT_EQ(0, memcmp(buffer, kBlock, kBlockSizeBytes)); +} + +TEST(RtcpPacketDlrrTest, Parse) { + Dlrr dlrr; + uint16_t block_length = ByteReader<uint16_t>::ReadBigEndian(&kBlock[2]); + EXPECT_TRUE(dlrr.Parse(kBlock, block_length)); + + EXPECT_EQ(1u, dlrr.sub_blocks().size()); + const ReceiveTimeInfo& block = dlrr.sub_blocks().front(); + EXPECT_EQ(kSsrc, block.ssrc); + EXPECT_EQ(kLastRR, block.last_rr); + EXPECT_EQ(kDelay, block.delay_since_last_rr); +} + +TEST(RtcpPacketDlrrTest, ParseFailsOnBadSize) { + const size_t kBigBufferSize = 0x100; // More than enough. + uint8_t buffer[kBigBufferSize]; + buffer[0] = Dlrr::kBlockType; + buffer[1] = 0; // Reserved. + buffer[2] = 0; // Most significant size byte. + for (uint8_t size = 3; size < 6; ++size) { + buffer[3] = size; + Dlrr dlrr; + // Parse should be successful only when size is multiple of 3. + EXPECT_EQ(size % 3 == 0, dlrr.Parse(buffer, static_cast<uint16_t>(size))); + } +} + +TEST(RtcpPacketDlrrTest, CreateAndParseManySubBlocks) { + const size_t kBufferSize = 0x1000; // More than enough. + const size_t kManyDlrrItems = 50; + uint8_t buffer[kBufferSize]; + + // Create. + Dlrr dlrr; + for (size_t i = 1; i <= kManyDlrrItems; ++i) + dlrr.AddDlrrItem(ReceiveTimeInfo(kSsrc + i, kLastRR + i, kDelay + i)); + size_t used_buffer_size = dlrr.BlockLength(); + ASSERT_LE(used_buffer_size, kBufferSize); + dlrr.Create(buffer); + + // Parse. + Dlrr parsed; + uint16_t block_length = ByteReader<uint16_t>::ReadBigEndian(&buffer[2]); + EXPECT_EQ(used_buffer_size, (block_length + 1) * 4u); + EXPECT_TRUE(parsed.Parse(buffer, block_length)); + EXPECT_EQ(kManyDlrrItems, parsed.sub_blocks().size()); +} +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/extended_reports.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/extended_reports.cc new file mode 100644 index 0000000000..ce57bd5a88 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/extended_reports.cc @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2016 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/extended_reports.h" + +#include <vector> + +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace rtcp { +constexpr uint8_t ExtendedReports::kPacketType; +constexpr size_t ExtendedReports::kMaxNumberOfDlrrItems; +// From RFC 3611: RTP Control Protocol Extended Reports (RTCP XR). +// +// Format for XR packets: +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |V=2|P|reserved | PT=XR=207 | length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// : report blocks : +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +// Extended report block: +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Block Type | reserved | block length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// : type-specific block contents : +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +ExtendedReports::ExtendedReports() = default; +ExtendedReports::ExtendedReports(const ExtendedReports& xr) = default; +ExtendedReports::~ExtendedReports() = default; + +bool ExtendedReports::Parse(const CommonHeader& packet) { + RTC_DCHECK_EQ(packet.type(), kPacketType); + + if (packet.payload_size_bytes() < kXrBaseLength) { + RTC_LOG(LS_WARNING) + << "Packet is too small to be an ExtendedReports packet."; + return false; + } + + SetSenderSsrc(ByteReader<uint32_t>::ReadBigEndian(packet.payload())); + rrtr_block_.reset(); + dlrr_block_.ClearItems(); + target_bitrate_ = absl::nullopt; + + const uint8_t* current_block = packet.payload() + kXrBaseLength; + const uint8_t* const packet_end = + packet.payload() + packet.payload_size_bytes(); + constexpr size_t kBlockHeaderSizeBytes = 4; + while (current_block + kBlockHeaderSizeBytes <= packet_end) { + uint8_t block_type = ByteReader<uint8_t>::ReadBigEndian(current_block); + uint16_t block_length = + ByteReader<uint16_t>::ReadBigEndian(current_block + 2); + const uint8_t* next_block = + current_block + kBlockHeaderSizeBytes + block_length * 4; + if (next_block > packet_end) { + RTC_LOG(LS_WARNING) + << "Report block in extended report packet is too big."; + return false; + } + switch (block_type) { + case Rrtr::kBlockType: + ParseRrtrBlock(current_block, block_length); + break; + case Dlrr::kBlockType: + ParseDlrrBlock(current_block, block_length); + break; + case TargetBitrate::kBlockType: + ParseTargetBitrateBlock(current_block, block_length); + break; + default: + // Unknown block, ignore. + RTC_LOG(LS_WARNING) + << "Unknown extended report block type " << block_type; + break; + } + current_block = next_block; + } + + return true; +} + +void ExtendedReports::SetRrtr(const Rrtr& rrtr) { + if (rrtr_block_) + RTC_LOG(LS_WARNING) << "Rrtr already set, overwriting."; + rrtr_block_.emplace(rrtr); +} + +bool ExtendedReports::AddDlrrItem(const ReceiveTimeInfo& time_info) { + if (dlrr_block_.sub_blocks().size() >= kMaxNumberOfDlrrItems) { + RTC_LOG(LS_WARNING) << "Reached maximum number of DLRR items."; + return false; + } + dlrr_block_.AddDlrrItem(time_info); + return true; +} + +void ExtendedReports::SetTargetBitrate(const TargetBitrate& bitrate) { + if (target_bitrate_) + RTC_LOG(LS_WARNING) << "TargetBitrate already set, overwriting."; + + target_bitrate_ = bitrate; +} + +size_t ExtendedReports::BlockLength() const { + return kHeaderLength + kXrBaseLength + RrtrLength() + DlrrLength() + + TargetBitrateLength(); +} + +bool ExtendedReports::Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const { + while (*index + BlockLength() > max_length) { + if (!OnBufferFull(packet, index, callback)) + return false; + } + size_t index_end = *index + BlockLength(); + const uint8_t kReserved = 0; + CreateHeader(kReserved, kPacketType, HeaderLength(), packet, index); + ByteWriter<uint32_t>::WriteBigEndian(packet + *index, sender_ssrc()); + *index += sizeof(uint32_t); + if (rrtr_block_) { + rrtr_block_->Create(packet + *index); + *index += Rrtr::kLength; + } + if (dlrr_block_) { + dlrr_block_.Create(packet + *index); + *index += dlrr_block_.BlockLength(); + } + if (target_bitrate_) { + target_bitrate_->Create(packet + *index); + *index += target_bitrate_->BlockLength(); + } + RTC_CHECK_EQ(*index, index_end); + return true; +} + +size_t ExtendedReports::TargetBitrateLength() const { + if (target_bitrate_) + return target_bitrate_->BlockLength(); + return 0; +} + +void ExtendedReports::ParseRrtrBlock(const uint8_t* block, + uint16_t block_length) { + if (block_length != Rrtr::kBlockLength) { + RTC_LOG(LS_WARNING) << "Incorrect rrtr block size " << block_length + << " Should be " << Rrtr::kBlockLength; + return; + } + if (rrtr_block_) { + RTC_LOG(LS_WARNING) + << "Two rrtr blocks found in same Extended Report packet"; + return; + } + rrtr_block_.emplace(); + rrtr_block_->Parse(block); +} + +void ExtendedReports::ParseDlrrBlock(const uint8_t* block, + uint16_t block_length) { + if (dlrr_block_) { + RTC_LOG(LS_WARNING) + << "Two Dlrr blocks found in same Extended Report packet"; + return; + } + dlrr_block_.Parse(block, block_length); +} + +void ExtendedReports::ParseTargetBitrateBlock(const uint8_t* block, + uint16_t block_length) { + target_bitrate_.emplace(); + target_bitrate_->Parse(block, block_length); +} +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/extended_reports.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/extended_reports.h new file mode 100644 index 0000000000..6c804bbc7b --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/extended_reports.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2016 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_EXTENDED_REPORTS_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_EXTENDED_REPORTS_H_ + +#include <vector> + +#include "absl/types/optional.h" +#include "modules/rtp_rtcp/source/rtcp_packet.h" +#include "modules/rtp_rtcp/source/rtcp_packet/dlrr.h" +#include "modules/rtp_rtcp/source/rtcp_packet/rrtr.h" +#include "modules/rtp_rtcp/source/rtcp_packet/target_bitrate.h" + +namespace webrtc { +namespace rtcp { +class CommonHeader; + +// From RFC 3611: RTP Control Protocol Extended Reports (RTCP XR). +class ExtendedReports : public RtcpPacket { + public: + static constexpr uint8_t kPacketType = 207; + static constexpr size_t kMaxNumberOfDlrrItems = 50; + + ExtendedReports(); + ExtendedReports(const ExtendedReports& xr); + ~ExtendedReports() override; + + // Parse assumes header is already parsed and validated. + bool Parse(const CommonHeader& packet); + + void SetRrtr(const Rrtr& rrtr); + bool AddDlrrItem(const ReceiveTimeInfo& time_info); + void SetTargetBitrate(const TargetBitrate& target_bitrate); + + const absl::optional<Rrtr>& rrtr() const { return rrtr_block_; } + const Dlrr& dlrr() const { return dlrr_block_; } + const absl::optional<TargetBitrate>& target_bitrate() const { + return target_bitrate_; + } + + size_t BlockLength() const override; + + bool Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const override; + + private: + static constexpr size_t kXrBaseLength = 4; + + size_t RrtrLength() const { return rrtr_block_ ? Rrtr::kLength : 0; } + size_t DlrrLength() const { return dlrr_block_.BlockLength(); } + size_t TargetBitrateLength() const; + + void ParseRrtrBlock(const uint8_t* block, uint16_t block_length); + void ParseDlrrBlock(const uint8_t* block, uint16_t block_length); + void ParseTargetBitrateBlock(const uint8_t* block, uint16_t block_length); + + absl::optional<Rrtr> rrtr_block_; + Dlrr dlrr_block_; // Dlrr without items treated same as no dlrr block. + absl::optional<TargetBitrate> target_bitrate_; +}; +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_EXTENDED_REPORTS_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/extended_reports_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/extended_reports_unittest.cc new file mode 100644 index 0000000000..3d9a2a3408 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/extended_reports_unittest.cc @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2016 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/extended_reports.h" + +#include "rtc_base/random.h" +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/rtcp_packet_parser.h" + +using ::testing::ElementsAre; +using ::testing::ElementsAreArray; +using ::testing::make_tuple; +using ::testing::SizeIs; +using webrtc::rtcp::ExtendedReports; +using webrtc::rtcp::ReceiveTimeInfo; +using webrtc::rtcp::Rrtr; + +namespace webrtc { +namespace { +constexpr uint32_t kSenderSsrc = 0x12345678; +constexpr uint8_t kEmptyPacket[] = {0x80, 207, 0x00, 0x01, + 0x12, 0x34, 0x56, 0x78}; +} // namespace + +class RtcpPacketExtendedReportsTest : public ::testing::Test { + public: + RtcpPacketExtendedReportsTest() : random_(0x123456789) {} + + protected: + template <typename T> + T Rand() { + return random_.Rand<T>(); + } + + private: + Random random_; +}; + +template <> +ReceiveTimeInfo RtcpPacketExtendedReportsTest::Rand<ReceiveTimeInfo>() { + uint32_t ssrc = Rand<uint32_t>(); + uint32_t last_rr = Rand<uint32_t>(); + uint32_t delay_since_last_rr = Rand<uint32_t>(); + return ReceiveTimeInfo(ssrc, last_rr, delay_since_last_rr); +} + +template <> +NtpTime RtcpPacketExtendedReportsTest::Rand<NtpTime>() { + uint32_t secs = Rand<uint32_t>(); + uint32_t frac = Rand<uint32_t>(); + return NtpTime(secs, frac); +} + +template <> +Rrtr RtcpPacketExtendedReportsTest::Rand<Rrtr>() { + Rrtr rrtr; + rrtr.SetNtp(Rand<NtpTime>()); + return rrtr; +} + +TEST_F(RtcpPacketExtendedReportsTest, CreateWithoutReportBlocks) { + ExtendedReports xr; + xr.SetSenderSsrc(kSenderSsrc); + + rtc::Buffer packet = xr.Build(); + + EXPECT_THAT(make_tuple(packet.data(), packet.size()), + ElementsAreArray(kEmptyPacket)); +} + +TEST_F(RtcpPacketExtendedReportsTest, ParseWithoutReportBlocks) { + ExtendedReports parsed; + EXPECT_TRUE(test::ParseSinglePacket(kEmptyPacket, &parsed)); + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_FALSE(parsed.rrtr()); + EXPECT_FALSE(parsed.dlrr()); +} + +TEST_F(RtcpPacketExtendedReportsTest, CreateAndParseWithRrtrBlock) { + const Rrtr kRrtr = Rand<Rrtr>(); + ExtendedReports xr; + xr.SetSenderSsrc(kSenderSsrc); + xr.SetRrtr(kRrtr); + rtc::Buffer packet = xr.Build(); + + ExtendedReports mparsed; + EXPECT_TRUE(test::ParseSinglePacket(packet, &mparsed)); + const ExtendedReports& parsed = mparsed; + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_EQ(kRrtr, parsed.rrtr()); +} + +TEST_F(RtcpPacketExtendedReportsTest, CreateAndParseWithDlrrWithOneSubBlock) { + const ReceiveTimeInfo kTimeInfo = Rand<ReceiveTimeInfo>(); + ExtendedReports xr; + xr.SetSenderSsrc(kSenderSsrc); + xr.AddDlrrItem(kTimeInfo); + + rtc::Buffer packet = xr.Build(); + + ExtendedReports mparsed; + EXPECT_TRUE(test::ParseSinglePacket(packet, &mparsed)); + const ExtendedReports& parsed = mparsed; + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_THAT(parsed.dlrr().sub_blocks(), ElementsAre(kTimeInfo)); +} + +TEST_F(RtcpPacketExtendedReportsTest, CreateAndParseWithDlrrWithTwoSubBlocks) { + const ReceiveTimeInfo kTimeInfo1 = Rand<ReceiveTimeInfo>(); + const ReceiveTimeInfo kTimeInfo2 = Rand<ReceiveTimeInfo>(); + ExtendedReports xr; + xr.SetSenderSsrc(kSenderSsrc); + xr.AddDlrrItem(kTimeInfo1); + xr.AddDlrrItem(kTimeInfo2); + + rtc::Buffer packet = xr.Build(); + + ExtendedReports mparsed; + EXPECT_TRUE(test::ParseSinglePacket(packet, &mparsed)); + const ExtendedReports& parsed = mparsed; + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_THAT(parsed.dlrr().sub_blocks(), ElementsAre(kTimeInfo1, kTimeInfo2)); +} + +TEST_F(RtcpPacketExtendedReportsTest, CreateLimitsTheNumberOfDlrrSubBlocks) { + const ReceiveTimeInfo kTimeInfo = Rand<ReceiveTimeInfo>(); + ExtendedReports xr; + + for (size_t i = 0; i < ExtendedReports::kMaxNumberOfDlrrItems; ++i) + EXPECT_TRUE(xr.AddDlrrItem(kTimeInfo)); + EXPECT_FALSE(xr.AddDlrrItem(kTimeInfo)); + + EXPECT_THAT(xr.dlrr().sub_blocks(), + SizeIs(ExtendedReports::kMaxNumberOfDlrrItems)); +} + +TEST_F(RtcpPacketExtendedReportsTest, CreateAndParseWithMaximumReportBlocks) { + const Rrtr kRrtr = Rand<Rrtr>(); + + ExtendedReports xr; + xr.SetSenderSsrc(kSenderSsrc); + xr.SetRrtr(kRrtr); + for (size_t i = 0; i < ExtendedReports::kMaxNumberOfDlrrItems; ++i) + xr.AddDlrrItem(Rand<ReceiveTimeInfo>()); + + rtc::Buffer packet = xr.Build(); + + ExtendedReports mparsed; + EXPECT_TRUE(test::ParseSinglePacket(packet, &mparsed)); + const ExtendedReports& parsed = mparsed; + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_EQ(kRrtr, parsed.rrtr()); + EXPECT_THAT(parsed.dlrr().sub_blocks(), + ElementsAreArray(xr.dlrr().sub_blocks())); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/fir.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/fir.cc new file mode 100644 index 0000000000..fd4a4c947a --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/fir.cc @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2016 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/fir.h" + +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace rtcp { +constexpr uint8_t Fir::kFeedbackMessageType; +// RFC 4585: Feedback format. +// Common packet format: +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |V=2|P| FMT | PT | length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC of packet sender | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC of media source (unused) = 0 | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// : Feedback Control Information (FCI) : +// : : +// Full intra request (FIR) (RFC 5104). +// The Feedback Control Information (FCI) for the Full Intra Request +// consists of one or more FCI entries. +// FCI: +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Seq nr. | Reserved = 0 | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +Fir::Fir() = default; + +Fir::Fir(const Fir& fir) = default; + +Fir::~Fir() = default; + +bool Fir::Parse(const CommonHeader& packet) { + RTC_DCHECK_EQ(packet.type(), kPacketType); + RTC_DCHECK_EQ(packet.fmt(), kFeedbackMessageType); + + // The FCI field MUST contain one or more FIR entries. + if (packet.payload_size_bytes() < kCommonFeedbackLength + kFciLength) { + RTC_LOG(LS_WARNING) << "Packet is too small to be a valid FIR packet."; + return false; + } + + if ((packet.payload_size_bytes() - kCommonFeedbackLength) % kFciLength != 0) { + RTC_LOG(LS_WARNING) << "Invalid size for a valid FIR packet."; + return false; + } + + ParseCommonFeedback(packet.payload()); + + size_t number_of_fci_items = + (packet.payload_size_bytes() - kCommonFeedbackLength) / kFciLength; + const uint8_t* next_fci = packet.payload() + kCommonFeedbackLength; + items_.resize(number_of_fci_items); + for (Request& request : items_) { + request.ssrc = ByteReader<uint32_t>::ReadBigEndian(next_fci); + request.seq_nr = ByteReader<uint8_t>::ReadBigEndian(next_fci + 4); + next_fci += kFciLength; + } + return true; +} + +size_t Fir::BlockLength() const { + return kHeaderLength + kCommonFeedbackLength + kFciLength * items_.size(); +} + +bool Fir::Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const { + RTC_DCHECK(!items_.empty()); + while (*index + BlockLength() > max_length) { + if (!OnBufferFull(packet, index, callback)) + return false; + } + size_t index_end = *index + BlockLength(); + CreateHeader(kFeedbackMessageType, kPacketType, HeaderLength(), packet, + index); + RTC_DCHECK_EQ(Psfb::media_ssrc(), 0); + CreateCommonFeedback(packet + *index); + *index += kCommonFeedbackLength; + + constexpr uint32_t kReserved = 0; + for (const Request& request : items_) { + ByteWriter<uint32_t>::WriteBigEndian(packet + *index, request.ssrc); + ByteWriter<uint8_t>::WriteBigEndian(packet + *index + 4, request.seq_nr); + ByteWriter<uint32_t, 3>::WriteBigEndian(packet + *index + 5, kReserved); + *index += kFciLength; + } + RTC_CHECK_EQ(*index, index_end); + return true; +} +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/fir.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/fir.h new file mode 100644 index 0000000000..383dc96114 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/fir.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2016 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_FIR_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_FIR_H_ + +#include <vector> + +#include "modules/rtp_rtcp/source/rtcp_packet/psfb.h" + +namespace webrtc { +namespace rtcp { +class CommonHeader; +// Full intra request (FIR) (RFC 5104). +class Fir : public Psfb { + public: + static constexpr uint8_t kFeedbackMessageType = 4; + struct Request { + Request() : ssrc(0), seq_nr(0) {} + Request(uint32_t ssrc, uint8_t seq_nr) : ssrc(ssrc), seq_nr(seq_nr) {} + uint32_t ssrc; + uint8_t seq_nr; + }; + + Fir(); + Fir(const Fir& fir); + ~Fir() override; + + // Parse assumes header is already parsed and validated. + bool Parse(const CommonHeader& packet); + + void AddRequestTo(uint32_t ssrc, uint8_t seq_num) { + items_.emplace_back(ssrc, seq_num); + } + const std::vector<Request>& requests() const { return items_; } + + size_t BlockLength() const override; + + bool Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const override; + + private: + static constexpr size_t kFciLength = 8; + + // SSRC of media source is not used in FIR packet. Shadow base functions. + void SetMediaSsrc(uint32_t ssrc); + uint32_t media_ssrc() const; + + std::vector<Request> items_; +}; +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_FIR_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/fir_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/fir_unittest.cc new file mode 100644 index 0000000000..01593e12ba --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/fir_unittest.cc @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2016 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/fir.h" + +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/rtcp_packet_parser.h" + +using ::testing::AllOf; +using ::testing::ElementsAre; +using ::testing::ElementsAreArray; +using ::testing::Eq; +using ::testing::Field; +using ::testing::make_tuple; +using webrtc::rtcp::Fir; + +namespace webrtc { +namespace { + +constexpr uint32_t kSenderSsrc = 0x12345678; +constexpr uint32_t kRemoteSsrc = 0x23456789; +constexpr uint8_t kSeqNr = 13; +// Manually created Fir packet matching constants above. +constexpr uint8_t kPacket[] = {0x84, 206, 0x00, 0x04, 0x12, 0x34, 0x56, + 0x78, 0x00, 0x00, 0x00, 0x00, 0x23, 0x45, + 0x67, 0x89, 0x0d, 0x00, 0x00, 0x00}; +} // namespace + +TEST(RtcpPacketFirTest, Parse) { + Fir mutable_parsed; + EXPECT_TRUE(test::ParseSinglePacket(kPacket, &mutable_parsed)); + const Fir& parsed = mutable_parsed; // Read values from constant object. + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_THAT(parsed.requests(), + ElementsAre(AllOf(Field(&Fir::Request::ssrc, Eq(kRemoteSsrc)), + Field(&Fir::Request::seq_nr, Eq(kSeqNr))))); +} + +TEST(RtcpPacketFirTest, Create) { + Fir fir; + fir.SetSenderSsrc(kSenderSsrc); + fir.AddRequestTo(kRemoteSsrc, kSeqNr); + + rtc::Buffer packet = fir.Build(); + + EXPECT_THAT(make_tuple(packet.data(), packet.size()), + ElementsAreArray(kPacket)); +} + +TEST(RtcpPacketFirTest, TwoFciEntries) { + Fir fir; + fir.SetSenderSsrc(kSenderSsrc); + fir.AddRequestTo(kRemoteSsrc, kSeqNr); + fir.AddRequestTo(kRemoteSsrc + 1, kSeqNr + 1); + + rtc::Buffer packet = fir.Build(); + Fir parsed; + EXPECT_TRUE(test::ParseSinglePacket(packet, &parsed)); + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_THAT(parsed.requests(), + ElementsAre(AllOf(Field(&Fir::Request::ssrc, Eq(kRemoteSsrc)), + Field(&Fir::Request::seq_nr, Eq(kSeqNr))), + AllOf(Field(&Fir::Request::ssrc, Eq(kRemoteSsrc + 1)), + Field(&Fir::Request::seq_nr, Eq(kSeqNr + 1))))); +} + +TEST(RtcpPacketFirTest, ParseFailsOnZeroFciEntries) { + constexpr uint8_t kPacketWithoutFci[] = {0x84, 206, 0x00, 0x02, 0x12, 0x34, + 0x56, 0x78, 0x00, 0x00, 0x00, 0x00}; + Fir parsed; + EXPECT_FALSE(test::ParseSinglePacket(kPacketWithoutFci, &parsed)); +} + +TEST(RtcpPacketFirTest, ParseFailsOnFractionalFciEntries) { + constexpr uint8_t kPacketWithOneAndHalfFci[] = { + 0x84, 206, 0x00, 0x05, 0x12, 0x34, 0x56, 0x78, 0x00, 0x00, 0x00, 0x00, + 0x23, 0x45, 0x67, 0x89, 0x0d, 0x00, 0x00, 0x00, 'h', 'a', 'l', 'f'}; + + Fir parsed; + EXPECT_FALSE(test::ParseSinglePacket(kPacketWithOneAndHalfFci, &parsed)); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/loss_notification.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/loss_notification.cc new file mode 100644 index 0000000000..0817846f95 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/loss_notification.cc @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2019 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/rtp_rtcp/source/rtcp_packet/loss_notification.h" + +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace rtcp { + +// Loss Notification +// ----------------- +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |V=2|P| FMT=15 | PT=206 | length | +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// 0 | SSRC of packet sender | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 4 | SSRC of media source | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 8 | Unique identifier 'L' 'N' 'T' 'F' | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 12 | Last Decoded Sequence Number | Last Received SeqNum Delta |D| +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +LossNotification::LossNotification() + : last_decoded_(0), last_received_(0), decodability_flag_(false) {} + +LossNotification::LossNotification(uint16_t last_decoded, + uint16_t last_received, + bool decodability_flag) + : last_decoded_(last_decoded), + last_received_(last_received), + decodability_flag_(decodability_flag) {} + +LossNotification::LossNotification(const LossNotification& rhs) = default; + +LossNotification::~LossNotification() = default; + +size_t LossNotification::BlockLength() const { + return kHeaderLength + kCommonFeedbackLength + kLossNotificationPayloadLength; +} + +bool LossNotification::Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const { + while (*index + BlockLength() > max_length) { + if (!OnBufferFull(packet, index, callback)) + return false; + } + + const size_t index_end = *index + BlockLength(); + + // Note: `index` updated by the function below. + CreateHeader(Psfb::kAfbMessageType, kPacketType, HeaderLength(), packet, + index); + + CreateCommonFeedback(packet + *index); + *index += kCommonFeedbackLength; + + ByteWriter<uint32_t>::WriteBigEndian(packet + *index, kUniqueIdentifier); + *index += sizeof(uint32_t); + + ByteWriter<uint16_t>::WriteBigEndian(packet + *index, last_decoded_); + *index += sizeof(uint16_t); + + const uint16_t last_received_delta = last_received_ - last_decoded_; + RTC_DCHECK_LE(last_received_delta, 0x7fff); + const uint16_t last_received_delta_and_decodability = + (last_received_delta << 1) | (decodability_flag_ ? 0x0001 : 0x0000); + + ByteWriter<uint16_t>::WriteBigEndian(packet + *index, + last_received_delta_and_decodability); + *index += sizeof(uint16_t); + + RTC_DCHECK_EQ(index_end, *index); + return true; +} + +bool LossNotification::Parse(const CommonHeader& packet) { + RTC_DCHECK_EQ(packet.type(), kPacketType); + RTC_DCHECK_EQ(packet.fmt(), Psfb::kAfbMessageType); + + if (packet.payload_size_bytes() < + kCommonFeedbackLength + kLossNotificationPayloadLength) { + return false; + } + + const uint8_t* const payload = packet.payload(); + + if (ByteReader<uint32_t>::ReadBigEndian(&payload[8]) != kUniqueIdentifier) { + return false; + } + + ParseCommonFeedback(payload); + + last_decoded_ = ByteReader<uint16_t>::ReadBigEndian(&payload[12]); + + const uint16_t last_received_delta_and_decodability = + ByteReader<uint16_t>::ReadBigEndian(&payload[14]); + last_received_ = last_decoded_ + (last_received_delta_and_decodability >> 1); + decodability_flag_ = (last_received_delta_and_decodability & 0x0001); + + return true; +} + +bool LossNotification::Set(uint16_t last_decoded, + uint16_t last_received, + bool decodability_flag) { + const uint16_t delta = last_received - last_decoded; + if (delta > 0x7fff) { + return false; + } + last_received_ = last_received; + last_decoded_ = last_decoded; + decodability_flag_ = decodability_flag; + return true; +} + +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/loss_notification.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/loss_notification.h new file mode 100644 index 0000000000..0f70cf75c3 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/loss_notification.h @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2019 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_LOSS_NOTIFICATION_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_LOSS_NOTIFICATION_H_ + +#include "absl/base/attributes.h" +#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "modules/rtp_rtcp/source/rtcp_packet/psfb.h" + +namespace webrtc { +namespace rtcp { + +class LossNotification : public Psfb { + public: + LossNotification(); + LossNotification(uint16_t last_decoded, + uint16_t last_received, + bool decodability_flag); + LossNotification(const LossNotification& other); + ~LossNotification() override; + + size_t BlockLength() const override; + + ABSL_MUST_USE_RESULT + bool Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const override; + + // Parse assumes header is already parsed and validated. + ABSL_MUST_USE_RESULT + bool Parse(const CommonHeader& packet); + + // Set all of the values transmitted by the loss notification message. + // If the values may not be represented by a loss notification message, + // false is returned, and no change is made to the object; this happens + // when `last_received` is ahead of `last_decoded` by more than 0x7fff. + // This is because `last_received` is represented on the wire as a delta, + // and only 15 bits are available for that delta. + ABSL_MUST_USE_RESULT + bool Set(uint16_t last_decoded, + uint16_t last_received, + bool decodability_flag); + + // RTP sequence number of the first packet belong to the last decoded + // non-discardable frame. + uint16_t last_decoded() const { return last_decoded_; } + + // RTP sequence number of the last received packet. + uint16_t last_received() const { return last_received_; } + + // A decodability flag, whose specific meaning depends on the last-received + // RTP sequence number. The decodability flag is true if and only if all of + // the frame's dependencies are known to be decodable, and the frame itself + // is not yet known to be unassemblable. + // * Clarification #1: In a multi-packet frame, the first packet's + // dependencies are known, but it is not yet known whether all parts + // of the current frame will be received. + // * Clarification #2: In a multi-packet frame, the dependencies would be + // unknown if the first packet was not received. Then, the packet will + // be known-unassemblable. + bool decodability_flag() const { return decodability_flag_; } + + private: + static constexpr uint32_t kUniqueIdentifier = 0x4C4E5446; // 'L' 'N' 'T' 'F'. + static constexpr size_t kLossNotificationPayloadLength = 8; + + uint16_t last_decoded_; + uint16_t last_received_; + bool decodability_flag_; +}; +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_LOSS_NOTIFICATION_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/loss_notification_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/loss_notification_unittest.cc new file mode 100644 index 0000000000..c38e7f4438 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/loss_notification_unittest.cc @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2019 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/rtp_rtcp/source/rtcp_packet/loss_notification.h" + +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/rtcp_packet_parser.h" + +namespace webrtc { + +using ::testing::ElementsAreArray; +using ::testing::make_tuple; +using ::webrtc::rtcp::LossNotification; + +TEST(RtcpPacketLossNotificationTest, SetWithIllegalValuesFails) { + constexpr uint16_t kLastDecoded = 0x3c7b; + constexpr uint16_t kLastReceived = kLastDecoded + 0x7fff + 1; + constexpr bool kDecodabilityFlag = true; + LossNotification loss_notification; + EXPECT_FALSE( + loss_notification.Set(kLastDecoded, kLastReceived, kDecodabilityFlag)); +} + +TEST(RtcpPacketLossNotificationTest, SetWithLegalValuesSucceeds) { + constexpr uint16_t kLastDecoded = 0x3c7b; + constexpr uint16_t kLastReceived = kLastDecoded + 0x7fff; + constexpr bool kDecodabilityFlag = true; + LossNotification loss_notification; + EXPECT_TRUE( + loss_notification.Set(kLastDecoded, kLastReceived, kDecodabilityFlag)); +} + +TEST(RtcpPacketLossNotificationTest, CreateProducesExpectedWireFormat) { + // Note that (0x6542 >> 1) is used just to make the pattern in kPacket + // more apparent; there's nothing truly special about the value, + // it's only an implementation detail that last-received is represented + // as a delta from last-decoded, and that this delta is shifted before + // it's put on the wire. + constexpr uint16_t kLastDecoded = 0x3c7b; + constexpr uint16_t kLastReceived = kLastDecoded + (0x6542 >> 1); + constexpr bool kDecodabilityFlag = true; + + const uint8_t kPacket[] = {0x8f, 206, 0x00, 0x04, 0x12, 0x34, 0x56, 0x78, // + 0xab, 0xcd, 0xef, 0x01, 'L', 'N', 'T', 'F', // + 0x3c, 0x7b, 0x65, 0x43}; + + LossNotification loss_notification; + loss_notification.SetSenderSsrc(0x12345678); + loss_notification.SetMediaSsrc(0xabcdef01); + ASSERT_TRUE( + loss_notification.Set(kLastDecoded, kLastReceived, kDecodabilityFlag)); + + rtc::Buffer packet = loss_notification.Build(); + + EXPECT_THAT(make_tuple(packet.data(), packet.size()), + ElementsAreArray(kPacket)); +} + +TEST(RtcpPacketLossNotificationTest, + ParseFailsOnTooSmallPacketToBeLossNotification) { + uint8_t packet[] = {0x8f, 206, 0x00, 0x04, 0x12, 0x34, 0x56, 0x78, // + 0xab, 0xcd, 0xef, 0x01, 'L', 'N', 'T', 'F', // + 0x3c, 0x7b, 0x65, 0x43}; + size_t packet_length_bytes = sizeof(packet); + + LossNotification loss_notification; + + // First, prove that the failure we're expecting to see happens because of + // the length, by showing that before the modification to the length, + // the packet was correctly parsed. + ASSERT_TRUE( + test::ParseSinglePacket(packet, packet_length_bytes, &loss_notification)); + + // Show that after shaving off a word, the packet is no longer parsable. + packet[3] -= 1; // Change the `length` field of the RTCP packet. + packet_length_bytes -= 4; // Effectively forget the last 32-bit word. + EXPECT_FALSE( + test::ParseSinglePacket(packet, packet_length_bytes, &loss_notification)); +} + +TEST(RtcpPacketLossNotificationTest, + ParseFailsWhenUniqueIdentifierIsNotLossNotification) { + uint8_t packet[] = {0x8f, 206, 0x00, 0x04, 0x12, 0x34, 0x56, 0x78, // + 0xab, 0xcd, 0xef, 0x01, 'L', 'N', 'T', 'F', // + 0x3c, 0x7b, 0x65, 0x43}; + + LossNotification loss_notification; + + // First, prove that the failure we're expecting to see happens because of + // the identifier, by showing that before the modification to the identifier, + // the packet was correctly parsed. + ASSERT_TRUE(test::ParseSinglePacket(packet, &loss_notification)); + + // Show that after changing the identifier, the packet is no longer parsable. + RTC_DCHECK_EQ(packet[12], 'L'); + RTC_DCHECK_EQ(packet[13], 'N'); + RTC_DCHECK_EQ(packet[14], 'T'); + RTC_DCHECK_EQ(packet[15], 'F'); + packet[14] = 'x'; + EXPECT_FALSE(test::ParseSinglePacket(packet, &loss_notification)); +} + +TEST(RtcpPacketLossNotificationTest, + ParseLegalLossNotificationMessagesCorrectly) { + // Note that (0x6542 >> 1) is used just to make the pattern in kPacket + // more apparent; there's nothing truly special about the value, + // it's only an implementation detail that last-received is represented + // as a delta from last-decoded, and that this delta is shifted before + // it's put on the wire. + constexpr uint16_t kLastDecoded = 0x3c7b; + constexpr uint16_t kLastReceived = kLastDecoded + (0x6542 >> 1); + constexpr bool kDecodabilityFlag = true; + + const uint8_t kPacket[] = {0x8f, 206, 0x00, 0x04, 0x12, 0x34, 0x56, 0x78, // + 0xab, 0xcd, 0xef, 0x01, 'L', 'N', 'T', 'F', // + 0x3c, 0x7b, 0x65, 0x43}; + + LossNotification loss_notification; + EXPECT_TRUE(test::ParseSinglePacket(kPacket, &loss_notification)); + + EXPECT_EQ(loss_notification.sender_ssrc(), 0x12345678u); + EXPECT_EQ(loss_notification.media_ssrc(), 0xabcdef01u); + EXPECT_EQ(loss_notification.last_decoded(), kLastDecoded); + EXPECT_EQ(loss_notification.last_received(), kLastReceived); + EXPECT_EQ(loss_notification.decodability_flag(), kDecodabilityFlag); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/nack.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/nack.cc new file mode 100644 index 0000000000..6fe7eade62 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/nack.cc @@ -0,0 +1,176 @@ +/* + * 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/nack.h" + +#include <algorithm> +#include <cstdint> +#include <utility> + +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace rtcp { +constexpr uint8_t Nack::kFeedbackMessageType; +constexpr size_t Nack::kNackItemLength; +// RFC 4585: Feedback format. +// +// Common packet format: +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |V=2|P| FMT | PT | length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 0 | SSRC of packet sender | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 4 | SSRC of media source | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// : Feedback Control Information (FCI) : +// : : +// +// Generic NACK (RFC 4585). +// +// FCI: +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | PID | BLP | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +Nack::Nack() = default; +Nack::Nack(const Nack& rhs) = default; +Nack::~Nack() = default; + +bool Nack::Parse(const CommonHeader& packet) { + RTC_DCHECK_EQ(packet.type(), kPacketType); + RTC_DCHECK_EQ(packet.fmt(), kFeedbackMessageType); + + if (packet.payload_size_bytes() < kCommonFeedbackLength + kNackItemLength) { + RTC_LOG(LS_WARNING) << "Payload length " << packet.payload_size_bytes() + << " is too small for a Nack."; + return false; + } + size_t nack_items = + (packet.payload_size_bytes() - kCommonFeedbackLength) / kNackItemLength; + + ParseCommonFeedback(packet.payload()); + const uint8_t* next_nack = packet.payload() + kCommonFeedbackLength; + + packet_ids_.clear(); + packed_.resize(nack_items); + for (size_t index = 0; index < nack_items; ++index) { + packed_[index].first_pid = ByteReader<uint16_t>::ReadBigEndian(next_nack); + packed_[index].bitmask = ByteReader<uint16_t>::ReadBigEndian(next_nack + 2); + next_nack += kNackItemLength; + } + Unpack(); + + return true; +} + +size_t Nack::BlockLength() const { + return kHeaderLength + kCommonFeedbackLength + + packed_.size() * kNackItemLength; +} + +bool Nack::Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const { + RTC_DCHECK(!packed_.empty()); + // If nack list can't fit in packet, try to fragment. + constexpr size_t kNackHeaderLength = kHeaderLength + kCommonFeedbackLength; + for (size_t nack_index = 0; nack_index < packed_.size();) { + size_t bytes_left_in_buffer = max_length - *index; + if (bytes_left_in_buffer < kNackHeaderLength + kNackItemLength) { + if (!OnBufferFull(packet, index, callback)) + return false; + continue; + } + size_t num_nack_fields = + std::min((bytes_left_in_buffer - kNackHeaderLength) / kNackItemLength, + packed_.size() - nack_index); + + size_t payload_size_bytes = + kCommonFeedbackLength + (num_nack_fields * kNackItemLength); + size_t payload_size_32bits = + rtc::CheckedDivExact<size_t>(payload_size_bytes, 4); + CreateHeader(kFeedbackMessageType, kPacketType, payload_size_32bits, packet, + index); + + CreateCommonFeedback(packet + *index); + *index += kCommonFeedbackLength; + + size_t nack_end_index = nack_index + num_nack_fields; + for (; nack_index < nack_end_index; ++nack_index) { + const PackedNack& item = packed_[nack_index]; + ByteWriter<uint16_t>::WriteBigEndian(packet + *index + 0, item.first_pid); + ByteWriter<uint16_t>::WriteBigEndian(packet + *index + 2, item.bitmask); + *index += kNackItemLength; + } + RTC_DCHECK_LE(*index, max_length); + } + + return true; +} + +void Nack::SetPacketIds(const uint16_t* nack_list, size_t length) { + RTC_DCHECK(nack_list); + SetPacketIds(std::vector<uint16_t>(nack_list, nack_list + length)); +} + +void Nack::SetPacketIds(std::vector<uint16_t> nack_list) { + RTC_DCHECK(packet_ids_.empty()); + RTC_DCHECK(packed_.empty()); + packet_ids_ = std::move(nack_list); + Pack(); +} + +void Nack::Pack() { + RTC_DCHECK(!packet_ids_.empty()); + RTC_DCHECK(packed_.empty()); + auto it = packet_ids_.begin(); + const auto end = packet_ids_.end(); + while (it != end) { + PackedNack item; + item.first_pid = *it++; + // Bitmask specifies losses in any of the 16 packets following the pid. + item.bitmask = 0; + while (it != end) { + uint16_t shift = static_cast<uint16_t>(*it - item.first_pid - 1); + if (shift <= 15) { + item.bitmask |= (1 << shift); + ++it; + } else { + break; + } + } + packed_.push_back(item); + } +} + +void Nack::Unpack() { + RTC_DCHECK(packet_ids_.empty()); + RTC_DCHECK(!packed_.empty()); + for (const PackedNack& item : packed_) { + packet_ids_.push_back(item.first_pid); + uint16_t pid = item.first_pid + 1; + for (uint16_t bitmask = item.bitmask; bitmask != 0; bitmask >>= 1, ++pid) { + if (bitmask & 1) + packet_ids_.push_back(pid); + } + } +} + +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/nack.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/nack.h new file mode 100644 index 0000000000..9153733fb9 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/nack.h @@ -0,0 +1,59 @@ +/* + * 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. + */ + +#ifndef MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_NACK_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_NACK_H_ + +#include <vector> + +#include "modules/rtp_rtcp/source/rtcp_packet/rtpfb.h" + +namespace webrtc { +namespace rtcp { +class CommonHeader; + +class Nack : public Rtpfb { + public: + static constexpr uint8_t kFeedbackMessageType = 1; + Nack(); + Nack(const Nack&); + ~Nack() override; + + // Parse assumes header is already parsed and validated. + bool Parse(const CommonHeader& packet); + + void SetPacketIds(const uint16_t* nack_list, size_t length); + void SetPacketIds(std::vector<uint16_t> nack_list); + const std::vector<uint16_t>& packet_ids() const { return packet_ids_; } + + size_t BlockLength() const override; + + bool Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const override; + + private: + static constexpr size_t kNackItemLength = 4; + struct PackedNack { + uint16_t first_pid; + uint16_t bitmask; + }; + + void Pack(); // Fills packed_ using packed_ids_. (used in SetPacketIds). + void Unpack(); // Fills packet_ids_ using packed_. (used in Parse). + + std::vector<PackedNack> packed_; + std::vector<uint16_t> packet_ids_; +}; + +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_NACK_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/nack_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/nack_unittest.cc new file mode 100644 index 0000000000..aabae0dc48 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/nack_unittest.cc @@ -0,0 +1,171 @@ +/* + * 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/nack.h" + +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/rtcp_packet_parser.h" + +namespace webrtc { +namespace { + +using ::testing::_; +using ::testing::ElementsAre; +using ::testing::ElementsAreArray; +using ::testing::Invoke; +using ::testing::make_tuple; +using ::testing::MockFunction; +using ::testing::UnorderedElementsAreArray; +using ::webrtc::rtcp::Nack; + +constexpr uint32_t kSenderSsrc = 0x12345678; +constexpr uint32_t kRemoteSsrc = 0x23456789; + +constexpr uint16_t kList[] = {0, 1, 3, 8, 16}; +constexpr size_t kListLength = sizeof(kList) / sizeof(kList[0]); +constexpr uint8_t kVersionBits = 2 << 6; +// clang-format off +constexpr uint8_t kPacket[] = { + kVersionBits | Nack::kFeedbackMessageType, Nack::kPacketType, 0, 3, + 0x12, 0x34, 0x56, 0x78, + 0x23, 0x45, 0x67, 0x89, + 0x00, 0x00, 0x80, 0x85}; + +constexpr uint16_t kWrapList[] = {0xffdc, 0xffec, 0xfffe, 0xffff, 0x0000, + 0x0001, 0x0003, 0x0014, 0x0064}; +constexpr size_t kWrapListLength = sizeof(kWrapList) / sizeof(kWrapList[0]); +constexpr uint8_t kWrapPacket[] = { + kVersionBits | Nack::kFeedbackMessageType, Nack::kPacketType, 0, 6, + 0x12, 0x34, 0x56, 0x78, + 0x23, 0x45, 0x67, 0x89, + 0xff, 0xdc, 0x80, 0x00, + 0xff, 0xfe, 0x00, 0x17, + 0x00, 0x14, 0x00, 0x00, + 0x00, 0x64, 0x00, 0x00}; +constexpr uint8_t kTooSmallPacket[] = { + kVersionBits | Nack::kFeedbackMessageType, Nack::kPacketType, 0, 2, + 0x12, 0x34, 0x56, 0x78, + 0x23, 0x45, 0x67, 0x89}; +// clang-format on +} // namespace + +TEST(RtcpPacketNackTest, Create) { + Nack nack; + nack.SetSenderSsrc(kSenderSsrc); + nack.SetMediaSsrc(kRemoteSsrc); + nack.SetPacketIds(kList, kListLength); + + rtc::Buffer packet = nack.Build(); + + EXPECT_THAT(make_tuple(packet.data(), packet.size()), + ElementsAreArray(kPacket)); +} + +TEST(RtcpPacketNackTest, Parse) { + Nack parsed; + EXPECT_TRUE(test::ParseSinglePacket(kPacket, &parsed)); + const Nack& const_parsed = parsed; + + EXPECT_EQ(kSenderSsrc, const_parsed.sender_ssrc()); + EXPECT_EQ(kRemoteSsrc, const_parsed.media_ssrc()); + EXPECT_THAT(const_parsed.packet_ids(), ElementsAreArray(kList)); +} + +TEST(RtcpPacketNackTest, CreateWrap) { + Nack nack; + nack.SetSenderSsrc(kSenderSsrc); + nack.SetMediaSsrc(kRemoteSsrc); + nack.SetPacketIds(kWrapList, kWrapListLength); + + rtc::Buffer packet = nack.Build(); + + EXPECT_THAT(make_tuple(packet.data(), packet.size()), + ElementsAreArray(kWrapPacket)); +} + +TEST(RtcpPacketNackTest, ParseWrap) { + Nack parsed; + EXPECT_TRUE(test::ParseSinglePacket(kWrapPacket, &parsed)); + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_EQ(kRemoteSsrc, parsed.media_ssrc()); + EXPECT_THAT(parsed.packet_ids(), ElementsAreArray(kWrapList)); +} + +TEST(RtcpPacketNackTest, BadOrder) { + // Does not guarantee optimal packing, but should guarantee correctness. + const uint16_t kUnorderedList[] = {1, 25, 13, 12, 9, 27, 29}; + const size_t kUnorderedListLength = + sizeof(kUnorderedList) / sizeof(kUnorderedList[0]); + Nack nack; + nack.SetSenderSsrc(kSenderSsrc); + nack.SetMediaSsrc(kRemoteSsrc); + nack.SetPacketIds(kUnorderedList, kUnorderedListLength); + + rtc::Buffer packet = nack.Build(); + + Nack parsed; + EXPECT_TRUE(test::ParseSinglePacket(packet, &parsed)); + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_EQ(kRemoteSsrc, parsed.media_ssrc()); + EXPECT_THAT(parsed.packet_ids(), UnorderedElementsAreArray(kUnorderedList)); +} + +TEST(RtcpPacketNackTest, CreateFragmented) { + Nack nack; + const uint16_t kList[] = {1, 100, 200, 300, 400}; + const uint16_t kListLength = sizeof(kList) / sizeof(kList[0]); + nack.SetSenderSsrc(kSenderSsrc); + nack.SetMediaSsrc(kRemoteSsrc); + nack.SetPacketIds(kList, kListLength); + + const size_t kBufferSize = 12 + (3 * 4); // Fits common header + 3 nack items + + MockFunction<void(rtc::ArrayView<const uint8_t>)> callback; + EXPECT_CALL(callback, Call(_)) + .WillOnce(Invoke([&](rtc::ArrayView<const uint8_t> packet) { + Nack nack; + EXPECT_TRUE(test::ParseSinglePacket(packet, &nack)); + EXPECT_EQ(kSenderSsrc, nack.sender_ssrc()); + EXPECT_EQ(kRemoteSsrc, nack.media_ssrc()); + EXPECT_THAT(nack.packet_ids(), ElementsAre(1, 100, 200)); + })) + .WillOnce(Invoke([&](rtc::ArrayView<const uint8_t> packet) { + Nack nack; + EXPECT_TRUE(test::ParseSinglePacket(packet, &nack)); + EXPECT_EQ(kSenderSsrc, nack.sender_ssrc()); + EXPECT_EQ(kRemoteSsrc, nack.media_ssrc()); + EXPECT_THAT(nack.packet_ids(), ElementsAre(300, 400)); + })); + + EXPECT_TRUE(nack.Build(kBufferSize, callback.AsStdFunction())); +} + +TEST(RtcpPacketNackTest, CreateFailsWithTooSmallBuffer) { + const uint16_t kList[] = {1}; + const size_t kMinNackBlockSize = 16; + Nack nack; + nack.SetSenderSsrc(kSenderSsrc); + nack.SetMediaSsrc(kRemoteSsrc); + nack.SetPacketIds(kList, 1); + + MockFunction<void(rtc::ArrayView<const uint8_t>)> callback; + EXPECT_CALL(callback, Call(_)).Times(0); + EXPECT_FALSE(nack.Build(kMinNackBlockSize - 1, callback.AsStdFunction())); +} + +TEST(RtcpPacketNackTest, ParseFailsWithTooSmallBuffer) { + Nack parsed; + EXPECT_FALSE(test::ParseSinglePacket(kTooSmallPacket, &parsed)); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/pli.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/pli.cc new file mode 100644 index 0000000000..5b41aa5c2c --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/pli.cc @@ -0,0 +1,79 @@ +/* + * 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/pli.h" + +#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace rtcp { +constexpr uint8_t Pli::kFeedbackMessageType; +// RFC 4585: Feedback format. +// +// Common packet format: +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |V=2|P| FMT | PT | length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC of packet sender | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC of media source | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// : Feedback Control Information (FCI) : +// : : + +Pli::Pli() = default; + +Pli::Pli(const Pli& pli) = default; + +Pli::~Pli() = default; + +// +// Picture loss indication (PLI) (RFC 4585). +// FCI: no feedback control information. +bool Pli::Parse(const CommonHeader& packet) { + RTC_DCHECK_EQ(packet.type(), kPacketType); + RTC_DCHECK_EQ(packet.fmt(), kFeedbackMessageType); + + if (packet.payload_size_bytes() < kCommonFeedbackLength) { + RTC_LOG(LS_WARNING) << "Packet is too small to be a valid PLI packet"; + return false; + } + + ParseCommonFeedback(packet.payload()); + return true; +} + +size_t Pli::BlockLength() const { + return kHeaderLength + kCommonFeedbackLength; +} + +bool Pli::Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const { + while (*index + BlockLength() > max_length) { + if (!OnBufferFull(packet, index, callback)) + return false; + } + + CreateHeader(kFeedbackMessageType, kPacketType, HeaderLength(), packet, + index); + CreateCommonFeedback(packet + *index); + *index += kCommonFeedbackLength; + return true; +} + +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/pli.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/pli.h new file mode 100644 index 0000000000..b9b9c45a9c --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/pli.h @@ -0,0 +1,39 @@ +/* + * 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. + */ +#ifndef MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_PLI_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_PLI_H_ + +#include "modules/rtp_rtcp/source/rtcp_packet/psfb.h" + +namespace webrtc { +namespace rtcp { +class CommonHeader; +// Picture loss indication (PLI) (RFC 4585). +class Pli : public Psfb { + public: + static constexpr uint8_t kFeedbackMessageType = 1; + + Pli(); + Pli(const Pli& pli); + ~Pli() override; + + bool Parse(const CommonHeader& packet); + + size_t BlockLength() const override; + + bool Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const override; +}; + +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_PLI_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/pli_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/pli_unittest.cc new file mode 100644 index 0000000000..c971e22bc1 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/pli_unittest.cc @@ -0,0 +1,58 @@ +/* + * 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/pli.h" + +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/rtcp_packet_parser.h" + +using ::testing::ElementsAreArray; +using ::testing::make_tuple; +using webrtc::rtcp::Pli; + +namespace webrtc { +namespace { +const uint32_t kSenderSsrc = 0x12345678; +const uint32_t kRemoteSsrc = 0x23456789; +// Manually created Pli packet matching constants above. +const uint8_t kPacket[] = {0x81, 206, 0x00, 0x02, 0x12, 0x34, + 0x56, 0x78, 0x23, 0x45, 0x67, 0x89}; +} // namespace + +TEST(RtcpPacketPliTest, Parse) { + Pli mutable_parsed; + EXPECT_TRUE(test::ParseSinglePacket(kPacket, &mutable_parsed)); + const Pli& parsed = mutable_parsed; // Read values from constant object. + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_EQ(kRemoteSsrc, parsed.media_ssrc()); +} + +TEST(RtcpPacketPliTest, Create) { + Pli pli; + pli.SetSenderSsrc(kSenderSsrc); + pli.SetMediaSsrc(kRemoteSsrc); + + rtc::Buffer packet = pli.Build(); + + EXPECT_THAT(make_tuple(packet.data(), packet.size()), + ElementsAreArray(kPacket)); +} + +TEST(RtcpPacketPliTest, ParseFailsOnTooSmallPacket) { + const uint8_t kTooSmallPacket[] = {0x81, 206, 0x00, 0x01, + 0x12, 0x34, 0x56, 0x78}; + + Pli parsed; + EXPECT_FALSE(test::ParseSinglePacket(kTooSmallPacket, &parsed)); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/psfb.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/psfb.cc new file mode 100644 index 0000000000..384d8ba811 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/psfb.cc @@ -0,0 +1,47 @@ +/* + * 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/psfb.h" + +#include "modules/rtp_rtcp/source/byte_io.h" + +namespace webrtc { +namespace rtcp { +constexpr uint8_t Psfb::kPacketType; +constexpr uint8_t Psfb::kAfbMessageType; +constexpr size_t Psfb::kCommonFeedbackLength; +// RFC 4585: Feedback format. +// +// Common packet format: +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |V=2|P| FMT | PT | length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 0 | SSRC of packet sender | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 4 | SSRC of media source | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// : Feedback Control Information (FCI) : +// : : + +void Psfb::ParseCommonFeedback(const uint8_t* payload) { + SetSenderSsrc(ByteReader<uint32_t>::ReadBigEndian(&payload[0])); + SetMediaSsrc(ByteReader<uint32_t>::ReadBigEndian(&payload[4])); +} + +void Psfb::CreateCommonFeedback(uint8_t* payload) const { + ByteWriter<uint32_t>::WriteBigEndian(&payload[0], sender_ssrc()); + ByteWriter<uint32_t>::WriteBigEndian(&payload[4], media_ssrc()); +} + +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/psfb.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/psfb.h new file mode 100644 index 0000000000..d6b8bca7c4 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/psfb.h @@ -0,0 +1,48 @@ +/* + * 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. + * + */ + +#ifndef MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_PSFB_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_PSFB_H_ + +#include <stddef.h> +#include <stdint.h> + +#include "modules/rtp_rtcp/source/rtcp_packet.h" + +namespace webrtc { +namespace rtcp { + +// PSFB: Payload-specific feedback message. +// RFC 4585, Section 6.3. +class Psfb : public RtcpPacket { + public: + static constexpr uint8_t kPacketType = 206; + static constexpr uint8_t kAfbMessageType = 15; + + Psfb() = default; + ~Psfb() override = default; + + void SetMediaSsrc(uint32_t ssrc) { media_ssrc_ = ssrc; } + + uint32_t media_ssrc() const { return media_ssrc_; } + + protected: + static constexpr size_t kCommonFeedbackLength = 8; + void ParseCommonFeedback(const uint8_t* payload); + void CreateCommonFeedback(uint8_t* payload) const; + + private: + uint32_t media_ssrc_ = 0; +}; + +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_PSFB_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rapid_resync_request.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rapid_resync_request.cc new file mode 100644 index 0000000000..8563c28373 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rapid_resync_request.cc @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2016 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/rapid_resync_request.h" + +#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace rtcp { +constexpr uint8_t RapidResyncRequest::kFeedbackMessageType; +// RFC 4585: Feedback format. +// Rapid Resynchronisation Request (draft-perkins-avt-rapid-rtp-sync-03). +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |V=2|P| FMT=5 | PT=205 | length=2 | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC of packet sender | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC of media source | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +bool RapidResyncRequest::Parse(const CommonHeader& packet) { + RTC_DCHECK_EQ(packet.type(), kPacketType); + RTC_DCHECK_EQ(packet.fmt(), kFeedbackMessageType); + + if (packet.payload_size_bytes() != kCommonFeedbackLength) { + RTC_LOG(LS_WARNING) << "Packet payload size should be " + << kCommonFeedbackLength << " instead of " + << packet.payload_size_bytes() + << " to be a valid Rapid Resynchronisation Request"; + return false; + } + + ParseCommonFeedback(packet.payload()); + return true; +} + +size_t RapidResyncRequest::BlockLength() const { + return kHeaderLength + kCommonFeedbackLength; +} + +bool RapidResyncRequest::Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const { + while (*index + BlockLength() > max_length) { + if (!OnBufferFull(packet, index, callback)) + return false; + } + + CreateHeader(kFeedbackMessageType, kPacketType, HeaderLength(), packet, + index); + CreateCommonFeedback(packet + *index); + *index += kCommonFeedbackLength; + return true; +} +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rapid_resync_request.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rapid_resync_request.h new file mode 100644 index 0000000000..1955b98f5c --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rapid_resync_request.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2016 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_RAPID_RESYNC_REQUEST_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_RAPID_RESYNC_REQUEST_H_ + +#include "modules/rtp_rtcp/source/rtcp_packet/rtpfb.h" + +namespace webrtc { +namespace rtcp { +class CommonHeader; + +// draft-perkins-avt-rapid-rtp-sync-03 +class RapidResyncRequest : public Rtpfb { + public: + static constexpr uint8_t kFeedbackMessageType = 5; + + RapidResyncRequest() {} + ~RapidResyncRequest() override {} + + // Parse assumes header is already parsed and validated. + bool Parse(const CommonHeader& header); + + size_t BlockLength() const override; + + bool Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const override; +}; +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_RAPID_RESYNC_REQUEST_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rapid_resync_request_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rapid_resync_request_unittest.cc new file mode 100644 index 0000000000..d0e40fd83d --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rapid_resync_request_unittest.cc @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2016 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/rapid_resync_request.h" + +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/rtcp_packet_parser.h" + +using ::testing::ElementsAreArray; +using ::testing::make_tuple; +using webrtc::rtcp::RapidResyncRequest; + +namespace webrtc { +namespace { +const uint32_t kSenderSsrc = 0x12345678; +const uint32_t kRemoteSsrc = 0x23456789; +// Manually created packet matching constants above. +const uint8_t kPacket[] = {0x85, 205, 0x00, 0x02, 0x12, 0x34, + 0x56, 0x78, 0x23, 0x45, 0x67, 0x89}; +} // namespace + +TEST(RtcpPacketRapidResyncRequestTest, Parse) { + RapidResyncRequest mutable_parsed; + EXPECT_TRUE(test::ParseSinglePacket(kPacket, &mutable_parsed)); + const RapidResyncRequest& parsed = mutable_parsed; + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_EQ(kRemoteSsrc, parsed.media_ssrc()); +} + +TEST(RtcpPacketRapidResyncRequestTest, Create) { + RapidResyncRequest rrr; + rrr.SetSenderSsrc(kSenderSsrc); + rrr.SetMediaSsrc(kRemoteSsrc); + + rtc::Buffer packet = rrr.Build(); + + EXPECT_THAT(make_tuple(packet.data(), packet.size()), + ElementsAreArray(kPacket)); +} + +TEST(RtcpPacketRapidResyncRequestTest, ParseFailsOnTooSmallPacket) { + const uint8_t kTooSmallPacket[] = {0x85, 205, 0x00, 0x01, + 0x12, 0x34, 0x56, 0x78}; + RapidResyncRequest parsed; + EXPECT_FALSE(test::ParseSinglePacket(kTooSmallPacket, &parsed)); +} + +TEST(RtcpPacketRapidResyncRequestTest, ParseFailsOnTooLargePacket) { + const uint8_t kTooLargePacket[] = {0x85, 205, 0x00, 0x03, 0x12, 0x34, + 0x56, 0x78, 0x32, 0x21, 0x65, 0x87, + 0x23, 0x45, 0x67, 0x89}; + RapidResyncRequest parsed; + EXPECT_FALSE(test::ParseSinglePacket(kTooLargePacket, &parsed)); +} +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/receiver_report.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/receiver_report.cc new file mode 100644 index 0000000000..185011dff1 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/receiver_report.cc @@ -0,0 +1,112 @@ +/* + * 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/receiver_report.h" + +#include <utility> + +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace rtcp { +constexpr uint8_t ReceiverReport::kPacketType; +constexpr size_t ReceiverReport::kMaxNumberOfReportBlocks; +// RTCP receiver report (RFC 3550). +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |V=2|P| RC | PT=RR=201 | length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC of packet sender | +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// | report block(s) | +// | .... | + +ReceiverReport::ReceiverReport() = default; + +ReceiverReport::ReceiverReport(const ReceiverReport& rhs) = default; + +ReceiverReport::~ReceiverReport() = default; + +bool ReceiverReport::Parse(const CommonHeader& packet) { + RTC_DCHECK_EQ(packet.type(), kPacketType); + + const uint8_t report_blocks_count = packet.count(); + + if (packet.payload_size_bytes() < + kRrBaseLength + report_blocks_count * ReportBlock::kLength) { + RTC_LOG(LS_WARNING) << "Packet is too small to contain all the data."; + return false; + } + + SetSenderSsrc(ByteReader<uint32_t>::ReadBigEndian(packet.payload())); + + const uint8_t* next_report_block = packet.payload() + kRrBaseLength; + + report_blocks_.resize(report_blocks_count); + for (ReportBlock& block : report_blocks_) { + block.Parse(next_report_block, ReportBlock::kLength); + next_report_block += ReportBlock::kLength; + } + + RTC_DCHECK_LE(next_report_block - packet.payload(), + static_cast<ptrdiff_t>(packet.payload_size_bytes())); + return true; +} + +size_t ReceiverReport::BlockLength() const { + return kHeaderLength + kRrBaseLength + + report_blocks_.size() * ReportBlock::kLength; +} + +bool ReceiverReport::Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const { + while (*index + BlockLength() > max_length) { + if (!OnBufferFull(packet, index, callback)) + return false; + } + CreateHeader(report_blocks_.size(), kPacketType, HeaderLength(), packet, + index); + ByteWriter<uint32_t>::WriteBigEndian(packet + *index, sender_ssrc()); + *index += kRrBaseLength; + for (const ReportBlock& block : report_blocks_) { + block.Create(packet + *index); + *index += ReportBlock::kLength; + } + return true; +} + +bool ReceiverReport::AddReportBlock(const ReportBlock& block) { + if (report_blocks_.size() >= kMaxNumberOfReportBlocks) { + RTC_LOG(LS_WARNING) << "Max report blocks reached."; + return false; + } + report_blocks_.push_back(block); + return true; +} + +bool ReceiverReport::SetReportBlocks(std::vector<ReportBlock> blocks) { + if (blocks.size() > kMaxNumberOfReportBlocks) { + RTC_LOG(LS_WARNING) << "Too many report blocks (" << blocks.size() + << ") for receiver report."; + return false; + } + report_blocks_ = std::move(blocks); + return true; +} + +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/receiver_report.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/receiver_report.h new file mode 100644 index 0000000000..b9c1c466c7 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/receiver_report.h @@ -0,0 +1,60 @@ +/* + * 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. + */ + +#ifndef MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_RECEIVER_REPORT_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_RECEIVER_REPORT_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <vector> + +#include "modules/rtp_rtcp/source/rtcp_packet.h" +#include "modules/rtp_rtcp/source/rtcp_packet/report_block.h" + +namespace webrtc { +namespace rtcp { +class CommonHeader; + +class ReceiverReport : public RtcpPacket { + public: + static constexpr uint8_t kPacketType = 201; + static constexpr size_t kMaxNumberOfReportBlocks = 0x1f; + + ReceiverReport(); + ReceiverReport(const ReceiverReport&); + ~ReceiverReport() override; + + // Parse assumes header is already parsed and validated. + bool Parse(const CommonHeader& packet); + + bool AddReportBlock(const ReportBlock& block); + bool SetReportBlocks(std::vector<ReportBlock> blocks); + + const std::vector<ReportBlock>& report_blocks() const { + return report_blocks_; + } + + size_t BlockLength() const override; + + bool Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const override; + + private: + static const size_t kRrBaseLength = 4; + + std::vector<ReportBlock> report_blocks_; +}; + +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_RECEIVER_REPORT_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/receiver_report_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/receiver_report_unittest.cc new file mode 100644 index 0000000000..47f8eb13cb --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/receiver_report_unittest.cc @@ -0,0 +1,161 @@ +/* + * 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/receiver_report.h" + +#include <utility> + +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/rtcp_packet_parser.h" + +using ::testing::ElementsAreArray; +using ::testing::IsEmpty; +using ::testing::make_tuple; +using webrtc::rtcp::ReceiverReport; +using webrtc::rtcp::ReportBlock; + +namespace webrtc { +namespace { +const uint32_t kSenderSsrc = 0x12345678; +const uint32_t kRemoteSsrc = 0x23456789; +const uint8_t kFractionLost = 55; +const int32_t kCumulativeLost = 0x111213; +const uint32_t kExtHighestSeqNum = 0x22232425; +const uint32_t kJitter = 0x33343536; +const uint32_t kLastSr = 0x44454647; +const uint32_t kDelayLastSr = 0x55565758; +// Manually created ReceiverReport with one ReportBlock matching constants +// above. +// Having this block allows to test Create and Parse separately. +const uint8_t kPacket[] = {0x81, 201, 0x00, 0x07, 0x12, 0x34, 0x56, 0x78, + 0x23, 0x45, 0x67, 0x89, 55, 0x11, 0x12, 0x13, + 0x22, 0x23, 0x24, 0x25, 0x33, 0x34, 0x35, 0x36, + 0x44, 0x45, 0x46, 0x47, 0x55, 0x56, 0x57, 0x58}; +} // namespace + +TEST(RtcpPacketReceiverReportTest, ParseWithOneReportBlock) { + ReceiverReport rr; + EXPECT_TRUE(test::ParseSinglePacket(kPacket, &rr)); + const ReceiverReport& parsed = rr; + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_EQ(1u, parsed.report_blocks().size()); + const ReportBlock& rb = parsed.report_blocks().front(); + EXPECT_EQ(kRemoteSsrc, rb.source_ssrc()); + EXPECT_EQ(kFractionLost, rb.fraction_lost()); + EXPECT_EQ(kCumulativeLost, rb.cumulative_lost()); + EXPECT_EQ(kExtHighestSeqNum, rb.extended_high_seq_num()); + EXPECT_EQ(kJitter, rb.jitter()); + EXPECT_EQ(kLastSr, rb.last_sr()); + EXPECT_EQ(kDelayLastSr, rb.delay_since_last_sr()); +} + +TEST(RtcpPacketReceiverReportTest, ParseFailsOnIncorrectSize) { + rtc::Buffer damaged_packet(kPacket); + damaged_packet[0]++; // Damage the packet: increase count field. + ReceiverReport rr; + EXPECT_FALSE(test::ParseSinglePacket(damaged_packet, &rr)); +} + +TEST(RtcpPacketReceiverReportTest, CreateWithOneReportBlock) { + ReceiverReport rr; + rr.SetSenderSsrc(kSenderSsrc); + ReportBlock rb; + rb.SetMediaSsrc(kRemoteSsrc); + rb.SetFractionLost(kFractionLost); + rb.SetCumulativeLost(kCumulativeLost); + rb.SetExtHighestSeqNum(kExtHighestSeqNum); + rb.SetJitter(kJitter); + rb.SetLastSr(kLastSr); + rb.SetDelayLastSr(kDelayLastSr); + rr.AddReportBlock(rb); + + rtc::Buffer raw = rr.Build(); + + EXPECT_THAT(make_tuple(raw.data(), raw.size()), ElementsAreArray(kPacket)); +} + +TEST(RtcpPacketReceiverReportTest, CreateAndParseWithoutReportBlocks) { + ReceiverReport rr; + rr.SetSenderSsrc(kSenderSsrc); + + rtc::Buffer raw = rr.Build(); + ReceiverReport parsed; + EXPECT_TRUE(test::ParseSinglePacket(raw, &parsed)); + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_THAT(parsed.report_blocks(), IsEmpty()); +} + +TEST(RtcpPacketReceiverReportTest, CreateAndParseWithTwoReportBlocks) { + ReceiverReport rr; + ReportBlock rb1; + rb1.SetMediaSsrc(kRemoteSsrc); + ReportBlock rb2; + rb2.SetMediaSsrc(kRemoteSsrc + 1); + + rr.SetSenderSsrc(kSenderSsrc); + EXPECT_TRUE(rr.AddReportBlock(rb1)); + EXPECT_TRUE(rr.AddReportBlock(rb2)); + + rtc::Buffer raw = rr.Build(); + ReceiverReport parsed; + EXPECT_TRUE(test::ParseSinglePacket(raw, &parsed)); + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_EQ(2u, parsed.report_blocks().size()); + EXPECT_EQ(kRemoteSsrc, parsed.report_blocks()[0].source_ssrc()); + EXPECT_EQ(kRemoteSsrc + 1, parsed.report_blocks()[1].source_ssrc()); +} + +TEST(RtcpPacketReceiverReportTest, CreateWithTooManyReportBlocks) { + ReceiverReport rr; + rr.SetSenderSsrc(kSenderSsrc); + ReportBlock rb; + for (size_t i = 0; i < ReceiverReport::kMaxNumberOfReportBlocks; ++i) { + rb.SetMediaSsrc(kRemoteSsrc + i); + EXPECT_TRUE(rr.AddReportBlock(rb)); + } + rb.SetMediaSsrc(kRemoteSsrc + ReceiverReport::kMaxNumberOfReportBlocks); + EXPECT_FALSE(rr.AddReportBlock(rb)); +} + +TEST(RtcpPacketReceiverReportTest, SetReportBlocksOverwritesOldBlocks) { + ReceiverReport rr; + ReportBlock report_block; + // Use jitter field of the report blocks to distinguish them. + report_block.SetJitter(1001u); + rr.AddReportBlock(report_block); + ASSERT_EQ(rr.report_blocks().size(), 1u); + ASSERT_EQ(rr.report_blocks()[0].jitter(), 1001u); + + std::vector<ReportBlock> blocks(3u); + blocks[0].SetJitter(2001u); + blocks[1].SetJitter(3001u); + blocks[2].SetJitter(4001u); + EXPECT_TRUE(rr.SetReportBlocks(blocks)); + ASSERT_EQ(rr.report_blocks().size(), 3u); + EXPECT_EQ(rr.report_blocks()[0].jitter(), 2001u); + EXPECT_EQ(rr.report_blocks()[1].jitter(), 3001u); + EXPECT_EQ(rr.report_blocks()[2].jitter(), 4001u); +} + +TEST(RtcpPacketReceiverReportTest, SetReportBlocksMaxLimit) { + ReceiverReport rr; + std::vector<ReportBlock> max_blocks(ReceiverReport::kMaxNumberOfReportBlocks); + EXPECT_TRUE(rr.SetReportBlocks(std::move(max_blocks))); + + std::vector<ReportBlock> one_too_many_blocks( + ReceiverReport::kMaxNumberOfReportBlocks + 1); + EXPECT_FALSE(rr.SetReportBlocks(std::move(one_too_many_blocks))); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remb.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remb.cc new file mode 100644 index 0000000000..1389ca7836 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remb.cc @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2016 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/remb.h" + +#include <cstdint> +#include <utility> + +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace rtcp { +// Receiver Estimated Max Bitrate (REMB) (draft-alvestrand-rmcat-remb). +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |V=2|P| FMT=15 | PT=206 | length | +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// 0 | SSRC of packet sender | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 4 | Unused = 0 | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 8 | Unique identifier 'R' 'E' 'M' 'B' | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 12 | Num SSRC | BR Exp | BR Mantissa | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 16 | SSRC feedback | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// : ... : + +Remb::Remb() : bitrate_bps_(0) {} + +Remb::Remb(const Remb& rhs) = default; + +Remb::~Remb() = default; + +bool Remb::Parse(const CommonHeader& packet) { + RTC_DCHECK(packet.type() == kPacketType); + RTC_DCHECK_EQ(packet.fmt(), Psfb::kAfbMessageType); + + if (packet.payload_size_bytes() < 16) { + RTC_LOG(LS_WARNING) << "Payload length " << packet.payload_size_bytes() + << " is too small for Remb packet."; + return false; + } + const uint8_t* const payload = packet.payload(); + if (kUniqueIdentifier != ByteReader<uint32_t>::ReadBigEndian(&payload[8])) { + return false; + } + uint8_t number_of_ssrcs = payload[12]; + if (packet.payload_size_bytes() != + kCommonFeedbackLength + (2 + number_of_ssrcs) * 4) { + RTC_LOG(LS_WARNING) << "Payload size " << packet.payload_size_bytes() + << " does not match " << number_of_ssrcs << " ssrcs."; + return false; + } + + ParseCommonFeedback(payload); + uint8_t exponent = payload[13] >> 2; + uint64_t mantissa = (static_cast<uint32_t>(payload[13] & 0x03) << 16) | + ByteReader<uint16_t>::ReadBigEndian(&payload[14]); + bitrate_bps_ = (mantissa << exponent); + bool shift_overflow = + (static_cast<uint64_t>(bitrate_bps_) >> exponent) != mantissa; + if (bitrate_bps_ < 0 || shift_overflow) { + RTC_LOG(LS_ERROR) << "Invalid remb bitrate value : " << mantissa << "*2^" + << static_cast<int>(exponent); + return false; + } + + const uint8_t* next_ssrc = payload + 16; + ssrcs_.clear(); + ssrcs_.reserve(number_of_ssrcs); + for (uint8_t i = 0; i < number_of_ssrcs; ++i) { + ssrcs_.push_back(ByteReader<uint32_t>::ReadBigEndian(next_ssrc)); + next_ssrc += sizeof(uint32_t); + } + + return true; +} + +bool Remb::SetSsrcs(std::vector<uint32_t> ssrcs) { + if (ssrcs.size() > kMaxNumberOfSsrcs) { + RTC_LOG(LS_WARNING) << "Not enough space for all given SSRCs."; + return false; + } + ssrcs_ = std::move(ssrcs); + return true; +} + +size_t Remb::BlockLength() const { + return kHeaderLength + kCommonFeedbackLength + (2 + ssrcs_.size()) * 4; +} + +bool Remb::Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const { + while (*index + BlockLength() > max_length) { + if (!OnBufferFull(packet, index, callback)) + return false; + } + size_t index_end = *index + BlockLength(); + CreateHeader(Psfb::kAfbMessageType, kPacketType, HeaderLength(), packet, + index); + RTC_DCHECK_EQ(0, Psfb::media_ssrc()); + CreateCommonFeedback(packet + *index); + *index += kCommonFeedbackLength; + + ByteWriter<uint32_t>::WriteBigEndian(packet + *index, kUniqueIdentifier); + *index += sizeof(uint32_t); + const uint32_t kMaxMantissa = 0x3ffff; // 18 bits. + uint64_t mantissa = bitrate_bps_; + uint8_t exponenta = 0; + while (mantissa > kMaxMantissa) { + mantissa >>= 1; + ++exponenta; + } + packet[(*index)++] = static_cast<uint8_t>(ssrcs_.size()); + packet[(*index)++] = (exponenta << 2) | (mantissa >> 16); + ByteWriter<uint16_t>::WriteBigEndian(packet + *index, mantissa & 0xffff); + *index += sizeof(uint16_t); + + for (uint32_t ssrc : ssrcs_) { + ByteWriter<uint32_t>::WriteBigEndian(packet + *index, ssrc); + *index += sizeof(uint32_t); + } + RTC_DCHECK_EQ(index_end, *index); + return true; +} +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remb.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remb.h new file mode 100644 index 0000000000..b7075c0f23 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remb.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2016 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_REMB_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_REMB_H_ + +#include <vector> + +#include "modules/rtp_rtcp/source/rtcp_packet/psfb.h" + +namespace webrtc { +namespace rtcp { +class CommonHeader; + +// Receiver Estimated Max Bitrate (REMB) (draft-alvestrand-rmcat-remb). +class Remb : public Psfb { + public: + static constexpr size_t kMaxNumberOfSsrcs = 0xff; + + Remb(); + Remb(const Remb&); + ~Remb() override; + + // Parse assumes header is already parsed and validated. + bool Parse(const CommonHeader& packet); + + bool SetSsrcs(std::vector<uint32_t> ssrcs); + void SetBitrateBps(int64_t bitrate_bps) { bitrate_bps_ = bitrate_bps; } + + int64_t bitrate_bps() const { return bitrate_bps_; } + const std::vector<uint32_t>& ssrcs() const { return ssrcs_; } + + size_t BlockLength() const override; + + bool Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const override; + + private: + static constexpr uint32_t kUniqueIdentifier = 0x52454D42; // 'R' 'E' 'M' 'B'. + + // Media ssrc is unused, shadow base class setter and getter. + void SetMediaSsrc(uint32_t); + uint32_t media_ssrc() const; + + int64_t bitrate_bps_; + std::vector<uint32_t> ssrcs_; +}; +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_REMB_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remb_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remb_unittest.cc new file mode 100644 index 0000000000..c439d9c5f6 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remb_unittest.cc @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2016 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/remb.h" + +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/rtcp_packet_parser.h" + +using ::testing::ElementsAreArray; +using ::testing::IsEmpty; +using ::testing::make_tuple; +using webrtc::rtcp::Remb; + +namespace webrtc { +namespace { +const uint32_t kSenderSsrc = 0x12345678; +const uint32_t kRemoteSsrcs[] = {0x23456789, 0x2345678a, 0x2345678b}; +const uint32_t kBitrateBps = 0x3fb93 * 2; // 522022; +const int64_t kBitrateBps64bit = int64_t{0x3fb93} << 30; +const uint8_t kPacket[] = {0x8f, 206, 0x00, 0x07, 0x12, 0x34, 0x56, 0x78, + 0x00, 0x00, 0x00, 0x00, 'R', 'E', 'M', 'B', + 0x03, 0x07, 0xfb, 0x93, 0x23, 0x45, 0x67, 0x89, + 0x23, 0x45, 0x67, 0x8a, 0x23, 0x45, 0x67, 0x8b}; +const size_t kPacketLength = sizeof(kPacket); +} // namespace + +TEST(RtcpPacketRembTest, Create) { + Remb remb; + remb.SetSenderSsrc(kSenderSsrc); + remb.SetSsrcs( + std::vector<uint32_t>(std::begin(kRemoteSsrcs), std::end(kRemoteSsrcs))); + remb.SetBitrateBps(kBitrateBps); + + rtc::Buffer packet = remb.Build(); + + EXPECT_THAT(make_tuple(packet.data(), packet.size()), + ElementsAreArray(kPacket)); +} + +TEST(RtcpPacketRembTest, Parse) { + Remb remb; + EXPECT_TRUE(test::ParseSinglePacket(kPacket, &remb)); + const Remb& parsed = remb; + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_EQ(kBitrateBps, parsed.bitrate_bps()); + EXPECT_THAT(parsed.ssrcs(), ElementsAreArray(kRemoteSsrcs)); +} + +TEST(RtcpPacketRembTest, CreateAndParseWithoutSsrcs) { + Remb remb; + remb.SetSenderSsrc(kSenderSsrc); + remb.SetBitrateBps(kBitrateBps); + rtc::Buffer packet = remb.Build(); + + Remb parsed; + EXPECT_TRUE(test::ParseSinglePacket(packet, &parsed)); + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_EQ(kBitrateBps, parsed.bitrate_bps()); + EXPECT_THAT(parsed.ssrcs(), IsEmpty()); +} + +TEST(RtcpPacketRembTest, CreateAndParse64bitBitrate) { + Remb remb; + remb.SetBitrateBps(kBitrateBps64bit); + rtc::Buffer packet = remb.Build(); + + Remb parsed; + EXPECT_TRUE(test::ParseSinglePacket(packet, &parsed)); + EXPECT_EQ(kBitrateBps64bit, parsed.bitrate_bps()); +} + +TEST(RtcpPacketRembTest, ParseFailsOnTooSmallPacketToBeRemb) { + // Make it too small. + constexpr size_t kTooSmallSize = (1 + 3) * 4; + uint8_t packet[kTooSmallSize]; + memcpy(packet, kPacket, kTooSmallSize); + packet[3] = 3; + + Remb remb; + EXPECT_FALSE(test::ParseSinglePacket(packet, &remb)); +} + +TEST(RtcpPacketRembTest, ParseFailsWhenUniqueIdentifierIsNotRemb) { + uint8_t packet[kPacketLength]; + memcpy(packet, kPacket, kPacketLength); + packet[12] = 'N'; // Swap 'R' -> 'N' in the 'REMB' unique identifier. + + Remb remb; + EXPECT_FALSE(test::ParseSinglePacket(packet, &remb)); +} + +TEST(RtcpPacketRembTest, ParseFailsWhenBitrateDoNotFitIn64bits) { + uint8_t packet[kPacketLength]; + memcpy(packet, kPacket, kPacketLength); + packet[17] |= 0xfc; // Set exponent component to maximum of 63. + packet[19] |= 0x02; // Ensure mantissa is at least 2. + + Remb remb; + EXPECT_FALSE(test::ParseSinglePacket(packet, &remb)); +} + +TEST(RtcpPacketRembTest, ParseFailsWhenBitrateDoNotFitIn63bits) { + uint8_t packet[kPacketLength]; + memcpy(packet, kPacket, kPacketLength); + packet[17] = 56 << 2; // Set exponent component to 56. + packet[18] = 0; // Set mantissa to 200 > 128 + packet[19] = 200; + + // Result value 200 * 2^56 can't be represented with int64_t and thus should + // be rejected. + Remb remb; + EXPECT_FALSE(test::ParseSinglePacket(packet, &remb)); +} + +TEST(RtcpPacketRembTest, ParseFailsWhenSsrcCountMismatchLength) { + uint8_t packet[kPacketLength]; + memcpy(packet, kPacket, kPacketLength); + packet[16]++; // Swap 3 -> 4 in the ssrcs count. + + Remb remb; + EXPECT_FALSE(test::ParseSinglePacket(packet, &remb)); +} + +TEST(RtcpPacketRembTest, TooManySsrcs) { + Remb remb; + EXPECT_FALSE(remb.SetSsrcs( + std::vector<uint32_t>(Remb::kMaxNumberOfSsrcs + 1, kRemoteSsrcs[0]))); + EXPECT_TRUE(remb.SetSsrcs( + std::vector<uint32_t>(Remb::kMaxNumberOfSsrcs, kRemoteSsrcs[0]))); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remote_estimate.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remote_estimate.cc new file mode 100644 index 0000000000..ca59791248 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remote_estimate.cc @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2019 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +#include "modules/rtp_rtcp/source/rtcp_packet/remote_estimate.h" + +#include <algorithm> +#include <cmath> +#include <type_traits> +#include <utility> +#include <vector> + +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace rtcp { +namespace { + +static constexpr int kFieldValueSize = 3; +static constexpr int kFieldSize = 1 + kFieldValueSize; +static constexpr DataRate kDataRateResolution = DataRate::KilobitsPerSec(1); +constexpr int64_t kMaxEncoded = (1 << (kFieldValueSize * 8)) - 1; + +class DataRateSerializer { + public: + DataRateSerializer( + uint8_t id, + std::function<DataRate*(NetworkStateEstimate*)> field_getter) + : id_(id), field_getter_(field_getter) {} + + uint8_t id() const { return id_; } + + void Read(const uint8_t* src, NetworkStateEstimate* target) const { + int64_t scaled = ByteReader<uint32_t, kFieldValueSize>::ReadBigEndian(src); + if (scaled == kMaxEncoded) { + *field_getter_(target) = DataRate::PlusInfinity(); + } else { + *field_getter_(target) = kDataRateResolution * scaled; + } + } + + bool Write(const NetworkStateEstimate& src, uint8_t* target) const { + auto value = *field_getter_(const_cast<NetworkStateEstimate*>(&src)); + if (value.IsMinusInfinity()) { + RTC_LOG(LS_WARNING) << "Trying to serialize MinusInfinity"; + return false; + } + ByteWriter<uint8_t>::WriteBigEndian(target++, id_); + int64_t scaled; + if (value.IsPlusInfinity()) { + scaled = kMaxEncoded; + } else { + scaled = value / kDataRateResolution; + if (scaled >= kMaxEncoded) { + scaled = kMaxEncoded; + RTC_LOG(LS_WARNING) << ToString(value) << " is larger than max (" + << ToString(kMaxEncoded * kDataRateResolution) + << "), encoded as PlusInfinity."; + } + } + ByteWriter<uint32_t, kFieldValueSize>::WriteBigEndian(target, scaled); + return true; + } + + private: + const uint8_t id_; + const std::function<DataRate*(NetworkStateEstimate*)> field_getter_; +}; + +class RemoteEstimateSerializerImpl : public RemoteEstimateSerializer { + public: + explicit RemoteEstimateSerializerImpl(std::vector<DataRateSerializer> fields) + : fields_(fields) {} + + rtc::Buffer Serialize(const NetworkStateEstimate& src) const override { + size_t max_size = fields_.size() * kFieldSize; + size_t size = 0; + rtc::Buffer buf(max_size); + for (const auto& field : fields_) { + if (field.Write(src, buf.data() + size)) { + size += kFieldSize; + } + } + buf.SetSize(size); + return buf; + } + + bool Parse(rtc::ArrayView<const uint8_t> src, + NetworkStateEstimate* target) const override { + if (src.size() % kFieldSize != 0) + return false; + RTC_DCHECK_EQ(src.size() % kFieldSize, 0); + for (const uint8_t* data_ptr = src.data(); data_ptr < src.end(); + data_ptr += kFieldSize) { + uint8_t field_id = ByteReader<uint8_t>::ReadBigEndian(data_ptr); + for (const auto& field : fields_) { + if (field.id() == field_id) { + field.Read(data_ptr + 1, target); + break; + } + } + } + return true; + } + + private: + const std::vector<DataRateSerializer> fields_; +}; + +} // namespace + +const RemoteEstimateSerializer* GetRemoteEstimateSerializer() { + using E = NetworkStateEstimate; + static auto* serializer = new RemoteEstimateSerializerImpl({ + {1, [](E* e) { return &e->link_capacity_lower; }}, + {2, [](E* e) { return &e->link_capacity_upper; }}, + }); + return serializer; +} + +RemoteEstimate::RemoteEstimate() : serializer_(GetRemoteEstimateSerializer()) { + SetSubType(kSubType); + SetName(kName); + SetSenderSsrc(0); +} + +RemoteEstimate::RemoteEstimate(App&& app) + : App(std::move(app)), serializer_(GetRemoteEstimateSerializer()) {} + +bool RemoteEstimate::ParseData() { + return serializer_->Parse({data(), data_size()}, &estimate_); +} + +void RemoteEstimate::SetEstimate(NetworkStateEstimate estimate) { + estimate_ = estimate; + auto buf = serializer_->Serialize(estimate); + SetData(buf.data(), buf.size()); +} + +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remote_estimate.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remote_estimate.h new file mode 100644 index 0000000000..3400274568 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remote_estimate.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2019 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +#ifndef MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_REMOTE_ESTIMATE_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_REMOTE_ESTIMATE_H_ + +#include <memory> +#include <vector> + +#include "api/transport/network_types.h" +#include "modules/rtp_rtcp/source/rtcp_packet/app.h" + +namespace webrtc { +namespace rtcp { + +class CommonHeader; +class RemoteEstimateSerializer { + public: + virtual bool Parse(rtc::ArrayView<const uint8_t> src, + NetworkStateEstimate* target) const = 0; + virtual rtc::Buffer Serialize(const NetworkStateEstimate& src) const = 0; + virtual ~RemoteEstimateSerializer() = default; +}; + +// Using a static global implementation to avoid incurring initialization +// overhead of the serializer every time RemoteEstimate is created. +const RemoteEstimateSerializer* GetRemoteEstimateSerializer(); + +// The RemoteEstimate packet provides network estimation results from the +// receive side. This functionality is experimental and subject to change +// without notice. +class RemoteEstimate : public App { + public: + RemoteEstimate(); + explicit RemoteEstimate(App&& app); + // Note, sub type must be unique among all app messages with "goog" name. + static constexpr uint8_t kSubType = 13; + static constexpr uint32_t kName = NameToInt("goog"); + static TimeDelta GetTimestampPeriod(); + + bool ParseData(); + void SetEstimate(NetworkStateEstimate estimate); + NetworkStateEstimate estimate() const { return estimate_; } + + private: + NetworkStateEstimate estimate_; + const RemoteEstimateSerializer* const serializer_; +}; + +} // namespace rtcp +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_REMOTE_ESTIMATE_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remote_estimate_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remote_estimate_unittest.cc new file mode 100644 index 0000000000..bf0e0e2610 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remote_estimate_unittest.cc @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2019 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +#include "modules/rtp_rtcp/source/rtcp_packet/remote_estimate.h" + +#include "test/gtest.h" + +namespace webrtc { +namespace rtcp { +TEST(RemoteEstimateTest, EncodesCapacityBounds) { + NetworkStateEstimate src; + src.link_capacity_lower = DataRate::KilobitsPerSec(10); + src.link_capacity_upper = DataRate::KilobitsPerSec(1000000); + rtc::Buffer data = GetRemoteEstimateSerializer()->Serialize(src); + NetworkStateEstimate dst; + EXPECT_TRUE(GetRemoteEstimateSerializer()->Parse(data, &dst)); + EXPECT_EQ(src.link_capacity_lower, dst.link_capacity_lower); + EXPECT_EQ(src.link_capacity_upper, dst.link_capacity_upper); +} + +TEST(RemoteEstimateTest, ExpandsToPlusInfinity) { + NetworkStateEstimate src; + // White box testing: We know that the value is stored in an unsigned 24 int + // with kbps resolution. We expected it be represented as plus infinity. + src.link_capacity_lower = DataRate::KilobitsPerSec(2 << 24); + src.link_capacity_upper = DataRate::PlusInfinity(); + rtc::Buffer data = GetRemoteEstimateSerializer()->Serialize(src); + + NetworkStateEstimate dst; + EXPECT_TRUE(GetRemoteEstimateSerializer()->Parse(data, &dst)); + EXPECT_TRUE(dst.link_capacity_lower.IsPlusInfinity()); + EXPECT_TRUE(dst.link_capacity_upper.IsPlusInfinity()); +} + +TEST(RemoteEstimateTest, DoesNotEncodeNegative) { + NetworkStateEstimate src; + src.link_capacity_lower = DataRate::MinusInfinity(); + src.link_capacity_upper = DataRate::MinusInfinity(); + rtc::Buffer data = GetRemoteEstimateSerializer()->Serialize(src); + // Since MinusInfinity can't be represented, the buffer should be empty. + EXPECT_EQ(data.size(), 0u); + NetworkStateEstimate dst; + dst.link_capacity_lower = DataRate::KilobitsPerSec(300); + EXPECT_TRUE(GetRemoteEstimateSerializer()->Parse(data, &dst)); + // The fields will be left unchanged by the parser as they were not encoded. + EXPECT_EQ(dst.link_capacity_lower, DataRate::KilobitsPerSec(300)); + EXPECT_TRUE(dst.link_capacity_upper.IsMinusInfinity()); +} +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/report_block.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/report_block.cc new file mode 100644 index 0000000000..e7e92d2bf1 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/report_block.cc @@ -0,0 +1,91 @@ +/* + * 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/report_block.h" + +#include "modules/rtp_rtcp/source/byte_io.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace rtcp { + +// From RFC 3550, RTP: A Transport Protocol for Real-Time Applications. +// +// RTCP report block (RFC 3550). +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// 0 | SSRC_1 (SSRC of first source) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 4 | fraction lost | cumulative number of packets lost | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 8 | extended highest sequence number received | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 12 | interarrival jitter | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 16 | last SR (LSR) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 20 | delay since last SR (DLSR) | +// 24 +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +ReportBlock::ReportBlock() + : source_ssrc_(0), + fraction_lost_(0), + cumulative_lost_(0), + extended_high_seq_num_(0), + jitter_(0), + last_sr_(0), + delay_since_last_sr_(0) {} + +bool ReportBlock::Parse(const uint8_t* buffer, size_t length) { + RTC_DCHECK(buffer != nullptr); + if (length < ReportBlock::kLength) { + RTC_LOG(LS_ERROR) << "Report Block should be 24 bytes long"; + return false; + } + + source_ssrc_ = ByteReader<uint32_t>::ReadBigEndian(&buffer[0]); + fraction_lost_ = buffer[4]; + cumulative_lost_ = ByteReader<int32_t, 3>::ReadBigEndian(&buffer[5]); + extended_high_seq_num_ = ByteReader<uint32_t>::ReadBigEndian(&buffer[8]); + jitter_ = ByteReader<uint32_t>::ReadBigEndian(&buffer[12]); + last_sr_ = ByteReader<uint32_t>::ReadBigEndian(&buffer[16]); + delay_since_last_sr_ = ByteReader<uint32_t>::ReadBigEndian(&buffer[20]); + + return true; +} + +void ReportBlock::Create(uint8_t* buffer) const { + // Runtime check should be done while setting cumulative_lost. + RTC_DCHECK_LT(cumulative_lost(), (1 << 23)); // Have only 3 bytes for it. + + ByteWriter<uint32_t>::WriteBigEndian(&buffer[0], source_ssrc()); + ByteWriter<uint8_t>::WriteBigEndian(&buffer[4], fraction_lost()); + ByteWriter<int32_t, 3>::WriteBigEndian(&buffer[5], cumulative_lost()); + ByteWriter<uint32_t>::WriteBigEndian(&buffer[8], extended_high_seq_num()); + ByteWriter<uint32_t>::WriteBigEndian(&buffer[12], jitter()); + ByteWriter<uint32_t>::WriteBigEndian(&buffer[16], last_sr()); + ByteWriter<uint32_t>::WriteBigEndian(&buffer[20], delay_since_last_sr()); +} + +bool ReportBlock::SetCumulativeLost(int32_t cumulative_lost) { + // We have only 3 bytes to store it, and it's a signed value. + if (cumulative_lost >= (1 << 23) || cumulative_lost < -(1 << 23)) { + RTC_LOG(LS_WARNING) + << "Cumulative lost is too big to fit into Report Block"; + return false; + } + cumulative_lost_ = cumulative_lost; + return true; +} + +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/report_block.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/report_block.h new file mode 100644 index 0000000000..b49219eceb --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/report_block.h @@ -0,0 +1,70 @@ +/* + * 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. + * + */ + +#ifndef MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_REPORT_BLOCK_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_REPORT_BLOCK_H_ + +#include <stddef.h> +#include <stdint.h> + +namespace webrtc { +namespace rtcp { + +// A ReportBlock represents the Sender Report packet from +// RFC 3550 section 6.4.1. +class ReportBlock { + public: + static const size_t kLength = 24; + + ReportBlock(); + ~ReportBlock() {} + + bool Parse(const uint8_t* buffer, size_t length); + + // Fills buffer with the ReportBlock. + // Consumes ReportBlock::kLength bytes. + void Create(uint8_t* buffer) const; + + void SetMediaSsrc(uint32_t ssrc) { source_ssrc_ = ssrc; } + void SetFractionLost(uint8_t fraction_lost) { + fraction_lost_ = fraction_lost; + } + bool SetCumulativeLost(int32_t cumulative_lost); + void SetExtHighestSeqNum(uint32_t ext_highest_seq_num) { + extended_high_seq_num_ = ext_highest_seq_num; + } + void SetJitter(uint32_t jitter) { jitter_ = jitter; } + void SetLastSr(uint32_t last_sr) { last_sr_ = last_sr; } + void SetDelayLastSr(uint32_t delay_last_sr) { + delay_since_last_sr_ = delay_last_sr; + } + + uint32_t source_ssrc() const { return source_ssrc_; } + uint8_t fraction_lost() const { return fraction_lost_; } + int32_t cumulative_lost() const { return cumulative_lost_; } + uint32_t extended_high_seq_num() const { return extended_high_seq_num_; } + uint32_t jitter() const { return jitter_; } + uint32_t last_sr() const { return last_sr_; } + uint32_t delay_since_last_sr() const { return delay_since_last_sr_; } + + private: + uint32_t source_ssrc_; // 32 bits + uint8_t fraction_lost_; // 8 bits representing a fixed point value 0..1 + int32_t cumulative_lost_; // Signed 24-bit value + uint32_t extended_high_seq_num_; // 32 bits + uint32_t jitter_; // 32 bits + uint32_t last_sr_; // 32 bits + uint32_t delay_since_last_sr_; // 32 bits, units of 1/65536 seconds +}; + +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_REPORT_BLOCK_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/report_block_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/report_block_unittest.cc new file mode 100644 index 0000000000..11031a059a --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/report_block_unittest.cc @@ -0,0 +1,106 @@ +/* + * 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/report_block.h" + +#include <limits> + +#include "rtc_base/random.h" +#include "test/gtest.h" + +using webrtc::rtcp::ReportBlock; + +namespace webrtc { +namespace { + +const uint32_t kRemoteSsrc = 0x23456789; +const uint8_t kFractionLost = 55; +// Use values that are streamed differently LE and BE. +const int32_t kCumulativeLost = 0x111213; +const uint32_t kExtHighestSeqNum = 0x22232425; +const uint32_t kJitter = 0x33343536; +const uint32_t kLastSr = 0x44454647; +const uint32_t kDelayLastSr = 0x55565758; +const size_t kBufferLength = ReportBlock::kLength; + +TEST(RtcpPacketReportBlockTest, ParseChecksLength) { + uint8_t buffer[kBufferLength]; + memset(buffer, 0, sizeof(buffer)); + + ReportBlock rb; + EXPECT_FALSE(rb.Parse(buffer, kBufferLength - 1)); + EXPECT_TRUE(rb.Parse(buffer, kBufferLength)); +} + +TEST(RtcpPacketReportBlockTest, ParseAnyData) { + uint8_t buffer[kBufferLength]; + // Fill buffer with semi-random data. + Random generator(0x256F8A285EC829ull); + for (size_t i = 0; i < kBufferLength; ++i) + buffer[i] = static_cast<uint8_t>(generator.Rand(0, 0xff)); + + ReportBlock rb; + EXPECT_TRUE(rb.Parse(buffer, kBufferLength)); +} + +TEST(RtcpPacketReportBlockTest, ParseMatchCreate) { + ReportBlock rb; + rb.SetMediaSsrc(kRemoteSsrc); + rb.SetFractionLost(kFractionLost); + rb.SetCumulativeLost(kCumulativeLost); + rb.SetExtHighestSeqNum(kExtHighestSeqNum); + rb.SetJitter(kJitter); + rb.SetLastSr(kLastSr); + rb.SetDelayLastSr(kDelayLastSr); + + uint8_t buffer[kBufferLength]; + rb.Create(buffer); + + ReportBlock parsed; + EXPECT_TRUE(parsed.Parse(buffer, kBufferLength)); + + EXPECT_EQ(kRemoteSsrc, parsed.source_ssrc()); + EXPECT_EQ(kFractionLost, parsed.fraction_lost()); + EXPECT_EQ(kCumulativeLost, parsed.cumulative_lost()); + EXPECT_EQ(kExtHighestSeqNum, parsed.extended_high_seq_num()); + EXPECT_EQ(kJitter, parsed.jitter()); + EXPECT_EQ(kLastSr, parsed.last_sr()); + EXPECT_EQ(kDelayLastSr, parsed.delay_since_last_sr()); +} + +TEST(RtcpPacketReportBlockTest, ValidateCumulativeLost) { + // CumulativeLost is a signed 24-bit integer. + const int32_t kMaxCumulativeLost = 0x7fffff; + const int32_t kMinCumulativeLost = -0x800000; + ReportBlock rb; + EXPECT_FALSE(rb.SetCumulativeLost(kMaxCumulativeLost + 1)); + EXPECT_TRUE(rb.SetCumulativeLost(kMaxCumulativeLost)); + EXPECT_FALSE(rb.SetCumulativeLost(kMinCumulativeLost - 1)); + EXPECT_TRUE(rb.SetCumulativeLost(kMinCumulativeLost)); + EXPECT_EQ(rb.cumulative_lost(), kMinCumulativeLost); +} + +TEST(RtcpPacketReportBlockTest, ParseNegativeCumulativeLost) { + // CumulativeLost is a signed 24-bit integer. + const int32_t kNegativeCumulativeLost = -123; + ReportBlock rb; + EXPECT_TRUE(rb.SetCumulativeLost(kNegativeCumulativeLost)); + + uint8_t buffer[kBufferLength]; + rb.Create(buffer); + + ReportBlock parsed; + EXPECT_TRUE(parsed.Parse(buffer, kBufferLength)); + + EXPECT_EQ(kNegativeCumulativeLost, parsed.cumulative_lost()); +} + +} // namespace +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rrtr.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rrtr.cc new file mode 100644 index 0000000000..95fc890b19 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rrtr.cc @@ -0,0 +1,49 @@ +/* + * 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/rrtr.h" + +#include "modules/rtp_rtcp/source/byte_io.h" +#include "rtc_base/checks.h" + +namespace webrtc { +namespace rtcp { +// Receiver Reference Time Report Block (RFC 3611). +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | BT=4 | reserved | block length = 2 | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | NTP timestamp, most significant word | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | NTP timestamp, least significant word | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +void Rrtr::Parse(const uint8_t* buffer) { + RTC_DCHECK(buffer[0] == kBlockType); + // reserved = buffer[1]; + RTC_DCHECK(ByteReader<uint16_t>::ReadBigEndian(&buffer[2]) == kBlockLength); + uint32_t seconds = ByteReader<uint32_t>::ReadBigEndian(&buffer[4]); + uint32_t fraction = ByteReader<uint32_t>::ReadBigEndian(&buffer[8]); + ntp_.Set(seconds, fraction); +} + +void Rrtr::Create(uint8_t* buffer) const { + const uint8_t kReserved = 0; + buffer[0] = kBlockType; + buffer[1] = kReserved; + ByteWriter<uint16_t>::WriteBigEndian(&buffer[2], kBlockLength); + ByteWriter<uint32_t>::WriteBigEndian(&buffer[4], ntp_.seconds()); + ByteWriter<uint32_t>::WriteBigEndian(&buffer[8], ntp_.fractions()); +} + +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rrtr.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rrtr.h new file mode 100644 index 0000000000..827bd74399 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rrtr.h @@ -0,0 +1,59 @@ +/* + * 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. + * + */ + +#ifndef MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_RRTR_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_RRTR_H_ + +#include <stddef.h> +#include <stdint.h> + +#include "system_wrappers/include/ntp_time.h" + +namespace webrtc { +namespace rtcp { + +class Rrtr { + public: + static const uint8_t kBlockType = 4; + static const uint16_t kBlockLength = 2; + static const size_t kLength = 4 * (kBlockLength + 1); // 12 + + Rrtr() {} + Rrtr(const Rrtr&) = default; + ~Rrtr() {} + + Rrtr& operator=(const Rrtr&) = default; + + void Parse(const uint8_t* buffer); + + // Fills buffer with the Rrtr. + // Consumes Rrtr::kLength bytes. + void Create(uint8_t* buffer) const; + + void SetNtp(NtpTime ntp) { ntp_ = ntp; } + + NtpTime ntp() const { return ntp_; } + + private: + NtpTime ntp_; +}; + +inline bool operator==(const Rrtr& rrtr1, const Rrtr& rrtr2) { + return rrtr1.ntp() == rrtr2.ntp(); +} + +inline bool operator!=(const Rrtr& rrtr1, const Rrtr& rrtr2) { + return !(rrtr1 == rrtr2); +} + +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_RRTR_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rrtr_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rrtr_unittest.cc new file mode 100644 index 0000000000..56622ea81a --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rrtr_unittest.cc @@ -0,0 +1,50 @@ +/* + * 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/rrtr.h" + +#include "test/gtest.h" + +using webrtc::rtcp::Rrtr; + +namespace webrtc { +namespace { + +const uint32_t kNtpSec = 0x12345678; +const uint32_t kNtpFrac = 0x23456789; +const uint8_t kBlock[] = {0x04, 0x00, 0x00, 0x02, 0x12, 0x34, + 0x56, 0x78, 0x23, 0x45, 0x67, 0x89}; +const size_t kBlockSizeBytes = sizeof(kBlock); +static_assert( + kBlockSizeBytes == Rrtr::kLength, + "Size of manually created Rrtr block should match class constant"); + +TEST(RtcpPacketRrtrTest, Create) { + uint8_t buffer[Rrtr::kLength]; + Rrtr rrtr; + rrtr.SetNtp(NtpTime(kNtpSec, kNtpFrac)); + + rrtr.Create(buffer); + EXPECT_EQ(0, memcmp(buffer, kBlock, kBlockSizeBytes)); +} + +TEST(RtcpPacketRrtrTest, Parse) { + Rrtr read_rrtr; + read_rrtr.Parse(kBlock); + + // Run checks on const object to ensure all accessors have const modifier. + const Rrtr& parsed = read_rrtr; + + EXPECT_EQ(kNtpSec, parsed.ntp().seconds()); + EXPECT_EQ(kNtpFrac, parsed.ntp().fractions()); +} + +} // namespace +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rtpfb.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rtpfb.cc new file mode 100644 index 0000000000..18097de330 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rtpfb.cc @@ -0,0 +1,45 @@ +/* + * 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/rtpfb.h" + +#include "modules/rtp_rtcp/source/byte_io.h" + +namespace webrtc { +namespace rtcp { +constexpr uint8_t Rtpfb::kPacketType; +// RFC 4585, Section 6.1: Feedback format. +// +// Common packet format: +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |V=2|P| FMT | PT | length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 0 | SSRC of packet sender | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 4 | SSRC of media source | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// : Feedback Control Information (FCI) : +// : : + +void Rtpfb::ParseCommonFeedback(const uint8_t* payload) { + SetSenderSsrc(ByteReader<uint32_t>::ReadBigEndian(&payload[0])); + SetMediaSsrc(ByteReader<uint32_t>::ReadBigEndian(&payload[4])); +} + +void Rtpfb::CreateCommonFeedback(uint8_t* payload) const { + ByteWriter<uint32_t>::WriteBigEndian(&payload[0], sender_ssrc()); + ByteWriter<uint32_t>::WriteBigEndian(&payload[4], media_ssrc()); +} + +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rtpfb.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rtpfb.h new file mode 100644 index 0000000000..973b429a2d --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rtpfb.h @@ -0,0 +1,47 @@ +/* + * 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. + * + */ + +#ifndef MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_RTPFB_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_RTPFB_H_ + +#include <stddef.h> +#include <stdint.h> + +#include "modules/rtp_rtcp/source/rtcp_packet.h" + +namespace webrtc { +namespace rtcp { + +// RTPFB: Transport layer feedback message. +// RFC4585, Section 6.2 +class Rtpfb : public RtcpPacket { + public: + static constexpr uint8_t kPacketType = 205; + + Rtpfb() = default; + ~Rtpfb() override = default; + + void SetMediaSsrc(uint32_t ssrc) { media_ssrc_ = ssrc; } + + uint32_t media_ssrc() const { return media_ssrc_; } + + protected: + static constexpr size_t kCommonFeedbackLength = 8; + void ParseCommonFeedback(const uint8_t* payload); + void CreateCommonFeedback(uint8_t* payload) const; + + private: + uint32_t media_ssrc_ = 0; +}; + +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_RTPFB_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sdes.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sdes.cc new file mode 100644 index 0000000000..f244ec5f37 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sdes.cc @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2016 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/sdes.h" + +#include <string.h> + +#include <utility> + +#include "absl/strings/string_view.h" +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace rtcp { +constexpr uint8_t Sdes::kPacketType; +constexpr size_t Sdes::kMaxNumberOfChunks; +// Source Description (SDES) (RFC 3550). +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// header |V=2|P| SC | PT=SDES=202 | length | +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// chunk | SSRC/CSRC_1 | +// 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SDES items | +// | ... | +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// chunk | SSRC/CSRC_2 | +// 2 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SDES items | +// | ... | +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// Canonical End-Point Identifier SDES Item (CNAME) +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | CNAME=1 | length | user and domain name ... +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +namespace { +const uint8_t kTerminatorTag = 0; +const uint8_t kCnameTag = 1; + +size_t ChunkSize(const Sdes::Chunk& chunk) { + // Chunk: + // SSRC/CSRC (4 bytes) | CNAME=1 (1 byte) | length (1 byte) | cname | padding. + size_t chunk_payload_size = 4 + 1 + 1 + chunk.cname.size(); + size_t padding_size = 4 - (chunk_payload_size % 4); // Minimum 1. + return chunk_payload_size + padding_size; +} +} // namespace + +Sdes::Sdes() : block_length_(RtcpPacket::kHeaderLength) {} + +Sdes::~Sdes() {} + +bool Sdes::Parse(const CommonHeader& packet) { + RTC_DCHECK_EQ(packet.type(), kPacketType); + + uint8_t number_of_chunks = packet.count(); + std::vector<Chunk> chunks; // Read chunk into temporary array, so that in + // case of an error original array would stay + // unchanged. + size_t block_length = kHeaderLength; + + if (packet.payload_size_bytes() % 4 != 0) { + RTC_LOG(LS_WARNING) << "Invalid payload size " + << packet.payload_size_bytes() + << " bytes for a valid Sdes packet. Size should be" + " multiple of 4 bytes"; + } + const uint8_t* const payload_end = + packet.payload() + packet.payload_size_bytes(); + const uint8_t* looking_at = packet.payload(); + chunks.resize(number_of_chunks); + for (size_t i = 0; i < number_of_chunks;) { + // Each chunk consumes at least 8 bytes. + if (payload_end - looking_at < 8) { + RTC_LOG(LS_WARNING) << "Not enough space left for chunk #" << (i + 1); + return false; + } + chunks[i].ssrc = ByteReader<uint32_t>::ReadBigEndian(looking_at); + looking_at += sizeof(uint32_t); + bool cname_found = false; + + uint8_t item_type; + while ((item_type = *(looking_at++)) != kTerminatorTag) { + if (looking_at >= payload_end) { + RTC_LOG(LS_WARNING) + << "Unexpected end of packet while reading chunk #" << (i + 1) + << ". Expected to find size of the text."; + return false; + } + uint8_t item_length = *(looking_at++); + const size_t kTerminatorSize = 1; + if (looking_at + item_length + kTerminatorSize > payload_end) { + RTC_LOG(LS_WARNING) + << "Unexpected end of packet while reading chunk #" << (i + 1) + << ". Expected to find text of size " << item_length; + return false; + } + if (item_type == kCnameTag) { + if (cname_found) { + RTC_LOG(LS_WARNING) + << "Found extra CNAME for same ssrc in chunk #" << (i + 1); + return false; + } + cname_found = true; + chunks[i].cname.assign(reinterpret_cast<const char*>(looking_at), + item_length); + } + looking_at += item_length; + } + if (cname_found) { + // block_length calculates length of the packet that would be generated by + // Build/Create functions. Adjust it same way WithCName function does. + block_length += ChunkSize(chunks[i]); + ++i; + } else { + // RFC states CNAME item is mandatory. + // But same time it allows chunk without items. + // So while parsing, ignore all chunks without cname, + // but do not fail the parse. + RTC_LOG(LS_WARNING) << "CNAME not found for ssrc " << chunks[i].ssrc; + --number_of_chunks; + chunks.resize(number_of_chunks); + } + // Adjust to 32bit boundary. + looking_at += (payload_end - looking_at) % 4; + } + + chunks_ = std::move(chunks); + block_length_ = block_length; + return true; +} + +bool Sdes::AddCName(uint32_t ssrc, absl::string_view cname) { + RTC_DCHECK_LE(cname.length(), 0xffu); + if (chunks_.size() >= kMaxNumberOfChunks) { + RTC_LOG(LS_WARNING) << "Max SDES chunks reached."; + return false; + } + Chunk chunk; + chunk.ssrc = ssrc; + chunk.cname = std::string(cname); + chunks_.push_back(chunk); + block_length_ += ChunkSize(chunk); + return true; +} + +size_t Sdes::BlockLength() const { + return block_length_; +} + +bool Sdes::Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const { + while (*index + BlockLength() > max_length) { + if (!OnBufferFull(packet, index, callback)) + return false; + } + const size_t index_end = *index + BlockLength(); + CreateHeader(chunks_.size(), kPacketType, HeaderLength(), packet, index); + + for (const Sdes::Chunk& chunk : chunks_) { + ByteWriter<uint32_t>::WriteBigEndian(&packet[*index + 0], chunk.ssrc); + ByteWriter<uint8_t>::WriteBigEndian(&packet[*index + 4], kCnameTag); + ByteWriter<uint8_t>::WriteBigEndian( + &packet[*index + 5], static_cast<uint8_t>(chunk.cname.size())); + memcpy(&packet[*index + 6], chunk.cname.data(), chunk.cname.size()); + *index += (6 + chunk.cname.size()); + + // In each chunk, the list of items must be terminated by one or more null + // octets. The next chunk must start on a 32-bit boundary. + // CNAME (1 byte) | length (1 byte) | name | padding. + size_t padding_size = 4 - ((6 + chunk.cname.size()) % 4); + const int kPadding = 0; + memset(packet + *index, kPadding, padding_size); + *index += padding_size; + } + + RTC_CHECK_EQ(*index, index_end); + return true; +} +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sdes.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sdes.h new file mode 100644 index 0000000000..36b63ba29f --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sdes.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2016 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_SDES_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_SDES_H_ + +#include <string> +#include <vector> + +#include "absl/strings/string_view.h" +#include "modules/rtp_rtcp/source/rtcp_packet.h" + +namespace webrtc { +namespace rtcp { +class CommonHeader; +// Source Description (SDES) (RFC 3550). +class Sdes : public RtcpPacket { + public: + struct Chunk { + uint32_t ssrc; + std::string cname; + }; + static constexpr uint8_t kPacketType = 202; + static constexpr size_t kMaxNumberOfChunks = 0x1f; + + Sdes(); + ~Sdes() override; + + // Parse assumes header is already parsed and validated. + bool Parse(const CommonHeader& packet); + + bool AddCName(uint32_t ssrc, absl::string_view cname); + + const std::vector<Chunk>& chunks() const { return chunks_; } + + size_t BlockLength() const override; + + bool Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const override; + + private: + std::vector<Chunk> chunks_; + size_t block_length_; +}; +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_SDES_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sdes_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sdes_unittest.cc new file mode 100644 index 0000000000..15a39efe87 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sdes_unittest.cc @@ -0,0 +1,244 @@ +/* + * Copyright (c) 2016 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/sdes.h" + +#include "rtc_base/strings/string_builder.h" +#include "test/gtest.h" +#include "test/rtcp_packet_parser.h" + +using webrtc::rtcp::Sdes; + +namespace webrtc { +namespace { +const uint32_t kSenderSsrc = 0x12345678; +const uint8_t kPadding = 0; +const uint8_t kTerminatorTag = 0; +const uint8_t kCnameTag = 1; +const uint8_t kNameTag = 2; +const uint8_t kEmailTag = 3; +} // namespace + +TEST(RtcpPacketSdesTest, CreateAndParseWithoutChunks) { + Sdes sdes; + + rtc::Buffer packet = sdes.Build(); + Sdes parsed; + EXPECT_TRUE(test::ParseSinglePacket(packet, &parsed)); + + EXPECT_EQ(0u, parsed.chunks().size()); +} + +TEST(RtcpPacketSdesTest, CreateAndParseWithOneChunk) { + const std::string kCname = "alice@host"; + + Sdes sdes; + EXPECT_TRUE(sdes.AddCName(kSenderSsrc, kCname)); + + rtc::Buffer packet = sdes.Build(); + Sdes sdes_parsed; + EXPECT_TRUE(test::ParseSinglePacket(packet, &sdes_parsed)); + const Sdes& parsed = sdes_parsed; // Ensure accessors are const. + + EXPECT_EQ(1u, parsed.chunks().size()); + EXPECT_EQ(kSenderSsrc, parsed.chunks()[0].ssrc); + EXPECT_EQ(kCname, parsed.chunks()[0].cname); +} + +TEST(RtcpPacketSdesTest, CreateAndParseWithMultipleChunks) { + Sdes sdes; + EXPECT_TRUE(sdes.AddCName(kSenderSsrc + 0, "a")); + EXPECT_TRUE(sdes.AddCName(kSenderSsrc + 1, "ab")); + EXPECT_TRUE(sdes.AddCName(kSenderSsrc + 2, "abc")); + EXPECT_TRUE(sdes.AddCName(kSenderSsrc + 3, "abcd")); + EXPECT_TRUE(sdes.AddCName(kSenderSsrc + 4, "abcde")); + EXPECT_TRUE(sdes.AddCName(kSenderSsrc + 5, "abcdef")); + + rtc::Buffer packet = sdes.Build(); + Sdes parsed; + EXPECT_TRUE(test::ParseSinglePacket(packet, &parsed)); + + EXPECT_EQ(6u, parsed.chunks().size()); + EXPECT_EQ(kSenderSsrc + 5, parsed.chunks()[5].ssrc); + EXPECT_EQ("abcdef", parsed.chunks()[5].cname); +} + +TEST(RtcpPacketSdesTest, CreateWithTooManyChunks) { + const size_t kMaxChunks = (1 << 5) - 1; + Sdes sdes; + for (size_t i = 0; i < kMaxChunks; ++i) { + uint32_t ssrc = kSenderSsrc + i; + rtc::StringBuilder oss; + oss << "cname" << i; + EXPECT_TRUE(sdes.AddCName(ssrc, oss.str())); + } + EXPECT_FALSE(sdes.AddCName(kSenderSsrc + kMaxChunks, "foo")); +} + +TEST(RtcpPacketSdesTest, CreateAndParseCnameItemWithEmptyString) { + Sdes sdes; + EXPECT_TRUE(sdes.AddCName(kSenderSsrc, "")); + + rtc::Buffer packet = sdes.Build(); + Sdes parsed; + EXPECT_TRUE(test::ParseSinglePacket(packet, &parsed)); + + EXPECT_EQ(1u, parsed.chunks().size()); + EXPECT_EQ(kSenderSsrc, parsed.chunks()[0].ssrc); + EXPECT_EQ("", parsed.chunks()[0].cname); +} + +TEST(RtcpPacketSdesTest, ParseSkipsNonCNameField) { + const uint8_t kName[] = "abc"; + const uint8_t kCname[] = "de"; + const uint8_t kValidPacket[] = { + 0x81, 202, 0x00, 0x04, 0x12, 0x34, 0x56, + 0x78, kNameTag, 3, kName[0], kName[1], kName[2], kCnameTag, + 2, kCname[0], kCname[1], kTerminatorTag, kPadding, kPadding}; + // Sanity checks packet was assembled correctly. + ASSERT_EQ(0u, sizeof(kValidPacket) % 4); + ASSERT_EQ(kValidPacket[3] + 1u, sizeof(kValidPacket) / 4); + + Sdes parsed; + EXPECT_TRUE(test::ParseSinglePacket(kValidPacket, &parsed)); + + EXPECT_EQ(1u, parsed.chunks().size()); + EXPECT_EQ(kSenderSsrc, parsed.chunks()[0].ssrc); + EXPECT_EQ("de", parsed.chunks()[0].cname); +} + +TEST(RtcpPacketSdesTest, ParseSkipsChunksWithoutCName) { + const uint8_t kName[] = "ab"; + const uint8_t kEmail[] = "de"; + const uint8_t kCname[] = "def"; + const uint8_t kPacket[] = { + 0x82, 202, 0x00, 0x07, 0x12, + 0x34, 0x56, 0x78, // 1st chunk. + kNameTag, 3, kName[0], kName[1], kName[2], + kEmailTag, 2, kEmail[0], kEmail[1], kTerminatorTag, + kPadding, kPadding, 0x23, 0x45, 0x67, + 0x89, // 2nd chunk. + kCnameTag, 3, kCname[0], kCname[1], kCname[2], + kTerminatorTag, kPadding, kPadding}; + // Sanity checks packet was assembled correctly. + ASSERT_EQ(0u, sizeof(kPacket) % 4); + ASSERT_EQ(kPacket[3] + 1u, sizeof(kPacket) / 4); + + Sdes parsed; + EXPECT_TRUE(test::ParseSinglePacket(kPacket, &parsed)); + ASSERT_EQ(1u, parsed.chunks().size()); + EXPECT_EQ(0x23456789u, parsed.chunks()[0].ssrc); + EXPECT_EQ("def", parsed.chunks()[0].cname); +} + +TEST(RtcpPacketSdesTest, ParseFailsWithoutChunkItemTerminator) { + const uint8_t kName[] = "abc"; + const uint8_t kCname[] = "d"; + // No place for next chunk item. + const uint8_t kInvalidPacket[] = { + 0x81, 202, 0x00, 0x03, 0x12, 0x34, 0x56, 0x78, + kNameTag, 3, kName[0], kName[1], kName[2], kCnameTag, 1, kCname[0]}; + // Sanity checks packet was assembled correctly. + ASSERT_EQ(0u, sizeof(kInvalidPacket) % 4); + ASSERT_EQ(kInvalidPacket[3] + 1u, sizeof(kInvalidPacket) / 4); + + Sdes parsed; + EXPECT_FALSE(test::ParseSinglePacket(kInvalidPacket, &parsed)); +} + +TEST(RtcpPacketSdesTest, ParseFailsWithDamagedChunkItem) { + const uint8_t kName[] = "ab"; + const uint8_t kCname[] = "d"; + // Next chunk item has non-terminator type, but not the size. + const uint8_t kInvalidPacket[] = { + 0x81, 202, 0x00, 0x03, 0x12, 0x34, 0x56, 0x78, + kNameTag, 2, kName[0], kName[1], kCnameTag, 1, kCname[0], kEmailTag}; + // Sanity checks packet was assembled correctly. + ASSERT_EQ(0u, sizeof(kInvalidPacket) % 4); + ASSERT_EQ(kInvalidPacket[3] + 1u, sizeof(kInvalidPacket) / 4); + + Sdes parsed; + EXPECT_FALSE(test::ParseSinglePacket(kInvalidPacket, &parsed)); +} + +TEST(RtcpPacketSdesTest, ParseFailsWithTooLongChunkItem) { + const uint8_t kName[] = "abc"; + const uint8_t kCname[] = "d"; + // Last chunk item has length that goes beyond the buffer end. + const uint8_t kInvalidPacket[] = { + 0x81, 202, 0x00, 0x03, 0x12, 0x34, 0x56, 0x78, + kNameTag, 3, kName[0], kName[1], kName[2], kCnameTag, 2, kCname[0]}; + // Sanity checks packet was assembled correctly. + ASSERT_EQ(0u, sizeof(kInvalidPacket) % 4); + ASSERT_EQ(kInvalidPacket[3] + 1u, sizeof(kInvalidPacket) / 4); + + Sdes parsed; + EXPECT_FALSE(test::ParseSinglePacket(kInvalidPacket, &parsed)); +} + +TEST(RtcpPacketSdesTest, ParseFailsWithTwoCNames) { + const uint8_t kCname1[] = "a"; + const uint8_t kCname2[] = "de"; + const uint8_t kInvalidPacket[] = { + 0x81, 202, 0x00, 0x03, 0x12, 0x34, 0x56, + 0x78, kCnameTag, 1, kCname1[0], kCnameTag, 2, kCname2[0], + kCname2[1], kTerminatorTag}; + // Sanity checks packet was assembled correctly. + ASSERT_EQ(0u, sizeof(kInvalidPacket) % 4); + ASSERT_EQ(kInvalidPacket[3] + 1u, sizeof(kInvalidPacket) / 4); + + Sdes parsed; + EXPECT_FALSE(test::ParseSinglePacket(kInvalidPacket, &parsed)); +} + +TEST(RtcpPacketSdesTest, ParseFailsWithTooLittleSpaceForNextChunk) { + const uint8_t kCname[] = "a"; + const uint8_t kEmail[] = "de"; + // Two chunks are promised in the header, but no place for the second chunk. + const uint8_t kInvalidPacket[] = { + 0x82, 202, 0x00, 0x04, 0x12, 0x34, 0x56, + 0x78, // 1st chunk. + kCnameTag, 1, kCname[0], kEmailTag, 2, kEmail[0], kEmail[1], + kTerminatorTag, 0x23, 0x45, 0x67, 0x89}; // 2nd chunk. + // Sanity checks packet was assembled correctly. + ASSERT_EQ(0u, sizeof(kInvalidPacket) % 4); + ASSERT_EQ(kInvalidPacket[3] + 1u, sizeof(kInvalidPacket) / 4); + + Sdes parsed; + EXPECT_FALSE(test::ParseSinglePacket(kInvalidPacket, &parsed)); +} + +TEST(RtcpPacketSdesTest, ParsedSdesCanBeReusedForBuilding) { + Sdes source; + const std::string kAlice = "alice@host"; + const std::string kBob = "bob@host"; + source.AddCName(kSenderSsrc, kAlice); + + rtc::Buffer packet1 = source.Build(); + Sdes middle; + test::ParseSinglePacket(packet1, &middle); + + EXPECT_EQ(source.BlockLength(), middle.BlockLength()); + + middle.AddCName(kSenderSsrc + 1, kBob); + + rtc::Buffer packet2 = middle.Build(); + Sdes destination; + test::ParseSinglePacket(packet2, &destination); + + EXPECT_EQ(middle.BlockLength(), destination.BlockLength()); + + EXPECT_EQ(2u, destination.chunks().size()); + EXPECT_EQ(kSenderSsrc, destination.chunks()[0].ssrc); + EXPECT_EQ(kAlice, destination.chunks()[0].cname); + EXPECT_EQ(kSenderSsrc + 1, destination.chunks()[1].ssrc); + EXPECT_EQ(kBob, destination.chunks()[1].cname); +} +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sender_report.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sender_report.cc new file mode 100644 index 0000000000..73738376c3 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sender_report.cc @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2016 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/sender_report.h" + +#include <utility> + +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace rtcp { +constexpr uint8_t SenderReport::kPacketType; +constexpr size_t SenderReport::kMaxNumberOfReportBlocks; +constexpr size_t SenderReport::kSenderBaseLength; +// Sender report (SR) (RFC 3550). +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |V=2|P| RC | PT=SR=200 | length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 0 | SSRC of sender | +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// 4 | NTP timestamp, most significant word | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 8 | NTP timestamp, least significant word | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 12 | RTP timestamp | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 16 | sender's packet count | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 20 | sender's octet count | +// 24 +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + +SenderReport::SenderReport() + : rtp_timestamp_(0), sender_packet_count_(0), sender_octet_count_(0) {} + +SenderReport::SenderReport(const SenderReport&) = default; +SenderReport::SenderReport(SenderReport&&) = default; +SenderReport& SenderReport::operator=(const SenderReport&) = default; +SenderReport& SenderReport::operator=(SenderReport&&) = default; +SenderReport::~SenderReport() = default; + +bool SenderReport::Parse(const CommonHeader& packet) { + RTC_DCHECK_EQ(packet.type(), kPacketType); + + const uint8_t report_block_count = packet.count(); + if (packet.payload_size_bytes() < + kSenderBaseLength + report_block_count * ReportBlock::kLength) { + RTC_LOG(LS_WARNING) << "Packet is too small to contain all the data."; + return false; + } + // Read SenderReport header. + const uint8_t* const payload = packet.payload(); + SetSenderSsrc(ByteReader<uint32_t>::ReadBigEndian(&payload[0])); + uint32_t secs = ByteReader<uint32_t>::ReadBigEndian(&payload[4]); + uint32_t frac = ByteReader<uint32_t>::ReadBigEndian(&payload[8]); + ntp_.Set(secs, frac); + rtp_timestamp_ = ByteReader<uint32_t>::ReadBigEndian(&payload[12]); + sender_packet_count_ = ByteReader<uint32_t>::ReadBigEndian(&payload[16]); + sender_octet_count_ = ByteReader<uint32_t>::ReadBigEndian(&payload[20]); + report_blocks_.resize(report_block_count); + const uint8_t* next_block = payload + kSenderBaseLength; + for (ReportBlock& block : report_blocks_) { + bool block_parsed = block.Parse(next_block, ReportBlock::kLength); + RTC_DCHECK(block_parsed); + next_block += ReportBlock::kLength; + } + // Double check we didn't read beyond provided buffer. + RTC_DCHECK_LE(next_block - payload, + static_cast<ptrdiff_t>(packet.payload_size_bytes())); + return true; +} + +size_t SenderReport::BlockLength() const { + return kHeaderLength + kSenderBaseLength + + report_blocks_.size() * ReportBlock::kLength; +} + +bool SenderReport::Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const { + while (*index + BlockLength() > max_length) { + if (!OnBufferFull(packet, index, callback)) + return false; + } + const size_t index_end = *index + BlockLength(); + + CreateHeader(report_blocks_.size(), kPacketType, HeaderLength(), packet, + index); + // Write SenderReport header. + ByteWriter<uint32_t>::WriteBigEndian(&packet[*index + 0], sender_ssrc()); + ByteWriter<uint32_t>::WriteBigEndian(&packet[*index + 4], ntp_.seconds()); + ByteWriter<uint32_t>::WriteBigEndian(&packet[*index + 8], ntp_.fractions()); + ByteWriter<uint32_t>::WriteBigEndian(&packet[*index + 12], rtp_timestamp_); + ByteWriter<uint32_t>::WriteBigEndian(&packet[*index + 16], + sender_packet_count_); + ByteWriter<uint32_t>::WriteBigEndian(&packet[*index + 20], + sender_octet_count_); + *index += kSenderBaseLength; + // Write report blocks. + for (const ReportBlock& block : report_blocks_) { + block.Create(packet + *index); + *index += ReportBlock::kLength; + } + // Ensure bytes written match expected. + RTC_DCHECK_EQ(*index, index_end); + return true; +} + +bool SenderReport::AddReportBlock(const ReportBlock& block) { + if (report_blocks_.size() >= kMaxNumberOfReportBlocks) { + RTC_LOG(LS_WARNING) << "Max report blocks reached."; + return false; + } + report_blocks_.push_back(block); + return true; +} + +bool SenderReport::SetReportBlocks(std::vector<ReportBlock> blocks) { + if (blocks.size() > kMaxNumberOfReportBlocks) { + RTC_LOG(LS_WARNING) << "Too many report blocks (" << blocks.size() + << ") for sender report."; + return false; + } + report_blocks_ = std::move(blocks); + return true; +} + +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sender_report.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sender_report.h new file mode 100644 index 0000000000..66ced31721 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sender_report.h @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2016 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_SENDER_REPORT_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_SENDER_REPORT_H_ + +#include <vector> + +#include "modules/rtp_rtcp/source/rtcp_packet.h" +#include "modules/rtp_rtcp/source/rtcp_packet/report_block.h" +#include "system_wrappers/include/ntp_time.h" + +namespace webrtc { +namespace rtcp { +class CommonHeader; + +class SenderReport : public RtcpPacket { + public: + static constexpr uint8_t kPacketType = 200; + static constexpr size_t kMaxNumberOfReportBlocks = 0x1f; + + SenderReport(); + SenderReport(const SenderReport&); + SenderReport(SenderReport&&); + SenderReport& operator=(const SenderReport&); + SenderReport& operator=(SenderReport&&); + ~SenderReport() override; + + // Parse assumes header is already parsed and validated. + bool Parse(const CommonHeader& packet); + + void SetNtp(NtpTime ntp) { ntp_ = ntp; } + void SetRtpTimestamp(uint32_t rtp_timestamp) { + rtp_timestamp_ = rtp_timestamp; + } + void SetPacketCount(uint32_t packet_count) { + sender_packet_count_ = packet_count; + } + void SetOctetCount(uint32_t octet_count) { + sender_octet_count_ = octet_count; + } + bool AddReportBlock(const ReportBlock& block); + bool SetReportBlocks(std::vector<ReportBlock> blocks); + void ClearReportBlocks() { report_blocks_.clear(); } + + NtpTime ntp() const { return ntp_; } + uint32_t rtp_timestamp() const { return rtp_timestamp_; } + uint32_t sender_packet_count() const { return sender_packet_count_; } + uint32_t sender_octet_count() const { return sender_octet_count_; } + + const std::vector<ReportBlock>& report_blocks() const { + return report_blocks_; + } + + size_t BlockLength() const override; + + bool Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const override; + + private: + static constexpr size_t kSenderBaseLength = 24; + + NtpTime ntp_; + uint32_t rtp_timestamp_; + uint32_t sender_packet_count_; + uint32_t sender_octet_count_; + std::vector<ReportBlock> report_blocks_; +}; + +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_SENDER_REPORT_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sender_report_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sender_report_unittest.cc new file mode 100644 index 0000000000..37f268e6b4 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sender_report_unittest.cc @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2016 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/sender_report.h" + +#include <utility> + +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/rtcp_packet_parser.h" + +using ::testing::ElementsAreArray; +using ::testing::make_tuple; +using webrtc::rtcp::ReportBlock; +using webrtc::rtcp::SenderReport; + +namespace webrtc { +namespace { +const uint32_t kSenderSsrc = 0x12345678; +const uint32_t kRemoteSsrc = 0x23456789; +const NtpTime kNtp(0x11121418, 0x22242628); +const uint32_t kRtpTimestamp = 0x33343536; +const uint32_t kPacketCount = 0x44454647; +const uint32_t kOctetCount = 0x55565758; +const uint8_t kPacket[] = {0x80, 200, 0x00, 0x06, 0x12, 0x34, 0x56, + 0x78, 0x11, 0x12, 0x14, 0x18, 0x22, 0x24, + 0x26, 0x28, 0x33, 0x34, 0x35, 0x36, 0x44, + 0x45, 0x46, 0x47, 0x55, 0x56, 0x57, 0x58}; +} // namespace + +TEST(RtcpPacketSenderReportTest, CreateWithoutReportBlocks) { + SenderReport sr; + sr.SetSenderSsrc(kSenderSsrc); + sr.SetNtp(kNtp); + sr.SetRtpTimestamp(kRtpTimestamp); + sr.SetPacketCount(kPacketCount); + sr.SetOctetCount(kOctetCount); + + rtc::Buffer raw = sr.Build(); + EXPECT_THAT(make_tuple(raw.data(), raw.size()), ElementsAreArray(kPacket)); +} + +TEST(RtcpPacketSenderReportTest, ParseWithoutReportBlocks) { + SenderReport parsed; + EXPECT_TRUE(test::ParseSinglePacket(kPacket, &parsed)); + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_EQ(kNtp, parsed.ntp()); + EXPECT_EQ(kRtpTimestamp, parsed.rtp_timestamp()); + EXPECT_EQ(kPacketCount, parsed.sender_packet_count()); + EXPECT_EQ(kOctetCount, parsed.sender_octet_count()); + EXPECT_TRUE(parsed.report_blocks().empty()); +} + +TEST(RtcpPacketSenderReportTest, CreateAndParseWithOneReportBlock) { + ReportBlock rb; + rb.SetMediaSsrc(kRemoteSsrc); + + SenderReport sr; + sr.SetSenderSsrc(kSenderSsrc); + EXPECT_TRUE(sr.AddReportBlock(rb)); + + rtc::Buffer raw = sr.Build(); + SenderReport parsed; + EXPECT_TRUE(test::ParseSinglePacket(raw, &parsed)); + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_EQ(1u, parsed.report_blocks().size()); + EXPECT_EQ(kRemoteSsrc, parsed.report_blocks()[0].source_ssrc()); +} + +TEST(RtcpPacketSenderReportTest, CreateAndParseWithTwoReportBlocks) { + ReportBlock rb1; + rb1.SetMediaSsrc(kRemoteSsrc); + ReportBlock rb2; + rb2.SetMediaSsrc(kRemoteSsrc + 1); + + SenderReport sr; + sr.SetSenderSsrc(kSenderSsrc); + EXPECT_TRUE(sr.AddReportBlock(rb1)); + EXPECT_TRUE(sr.AddReportBlock(rb2)); + + rtc::Buffer raw = sr.Build(); + SenderReport parsed; + EXPECT_TRUE(test::ParseSinglePacket(raw, &parsed)); + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_EQ(2u, parsed.report_blocks().size()); + EXPECT_EQ(kRemoteSsrc, parsed.report_blocks()[0].source_ssrc()); + EXPECT_EQ(kRemoteSsrc + 1, parsed.report_blocks()[1].source_ssrc()); +} + +TEST(RtcpPacketSenderReportTest, CreateWithTooManyReportBlocks) { + SenderReport sr; + sr.SetSenderSsrc(kSenderSsrc); + ReportBlock rb; + for (size_t i = 0; i < SenderReport::kMaxNumberOfReportBlocks; ++i) { + rb.SetMediaSsrc(kRemoteSsrc + i); + EXPECT_TRUE(sr.AddReportBlock(rb)); + } + rb.SetMediaSsrc(kRemoteSsrc + SenderReport::kMaxNumberOfReportBlocks); + EXPECT_FALSE(sr.AddReportBlock(rb)); +} + +TEST(RtcpPacketSenderReportTest, SetReportBlocksOverwritesOldBlocks) { + SenderReport sr; + ReportBlock report_block; + // Use jitter field of the report blocks to distinguish them. + report_block.SetJitter(1001u); + sr.AddReportBlock(report_block); + ASSERT_EQ(sr.report_blocks().size(), 1u); + ASSERT_EQ(sr.report_blocks()[0].jitter(), 1001u); + + std::vector<ReportBlock> blocks(3u); + blocks[0].SetJitter(2001u); + blocks[1].SetJitter(3001u); + blocks[2].SetJitter(4001u); + EXPECT_TRUE(sr.SetReportBlocks(blocks)); + ASSERT_EQ(sr.report_blocks().size(), 3u); + EXPECT_EQ(sr.report_blocks()[0].jitter(), 2001u); + EXPECT_EQ(sr.report_blocks()[1].jitter(), 3001u); + EXPECT_EQ(sr.report_blocks()[2].jitter(), 4001u); +} + +TEST(RtcpPacketSenderReportTest, SetReportBlocksMaxLimit) { + SenderReport sr; + std::vector<ReportBlock> max_blocks(SenderReport::kMaxNumberOfReportBlocks); + EXPECT_TRUE(sr.SetReportBlocks(std::move(max_blocks))); + + std::vector<ReportBlock> one_too_many_blocks( + SenderReport::kMaxNumberOfReportBlocks + 1); + EXPECT_FALSE(sr.SetReportBlocks(std::move(one_too_many_blocks))); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/target_bitrate.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/target_bitrate.cc new file mode 100644 index 0000000000..601b24fe94 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/target_bitrate.cc @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2016 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/target_bitrate.h" + +#include "modules/rtp_rtcp/source/byte_io.h" +#include "rtc_base/checks.h" +#include "rtc_base/numerics/safe_conversions.h" + +namespace webrtc { +namespace rtcp { +constexpr size_t kTargetBitrateHeaderSizeBytes = 4; +constexpr uint8_t TargetBitrate::kBlockType; +const size_t TargetBitrate::kBitrateItemSizeBytes = 4; + +TargetBitrate::BitrateItem::BitrateItem() + : spatial_layer(0), temporal_layer(0), target_bitrate_kbps(0) {} + +TargetBitrate::BitrateItem::BitrateItem(uint8_t spatial_layer, + uint8_t temporal_layer, + uint32_t target_bitrate_kbps) + : spatial_layer(spatial_layer), + temporal_layer(temporal_layer), + target_bitrate_kbps(target_bitrate_kbps) {} + +// RFC 4585: Feedback format. +// +// Common packet format: +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | BT=42 | reserved | block length | +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// Target bitrate item (repeat as many times as necessary). +// +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | S | T | Target Bitrate | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// : ... : +// +// Spatial Layer (S): 4 bits +// Indicates which temporal layer this bitrate concerns. +// +// Temporal Layer (T): 4 bits +// Indicates which temporal layer this bitrate concerns. +// +// Target Bitrate: 24 bits +// The encoder target bitrate for this layer, in kbps. +// +// As an example of how S and T are intended to be used, VP8 simulcast will +// use a separate TargetBitrate message per stream, since they are transmitted +// on separate SSRCs, with temporal layers grouped by stream. +// If VP9 SVC is used, there will be only one SSRC, so each spatial and +// temporal layer combo used shall be specified in the TargetBitrate packet. + +TargetBitrate::TargetBitrate() = default; +TargetBitrate::TargetBitrate(const TargetBitrate&) = default; +TargetBitrate& TargetBitrate::operator=(const TargetBitrate&) = default; +TargetBitrate::~TargetBitrate() = default; + +void TargetBitrate::Parse(const uint8_t* block, uint16_t block_length) { + // Validate block header (should already have been parsed and checked). + RTC_DCHECK_EQ(block[0], kBlockType); + RTC_DCHECK_EQ(block_length, ByteReader<uint16_t>::ReadBigEndian(&block[2])); + + // Header specifies block length - 1, but since we ignore the header, which + // occupies exactly on block, we can just treat this as payload length. + const size_t payload_bytes = block_length * 4; + const size_t num_items = payload_bytes / kBitrateItemSizeBytes; + size_t index = kTargetBitrateHeaderSizeBytes; + bitrates_.clear(); + for (size_t i = 0; i < num_items; ++i) { + uint8_t layers = block[index]; + uint32_t bitrate_kbps = + ByteReader<uint32_t, 3>::ReadBigEndian(&block[index + 1]); + index += kBitrateItemSizeBytes; + AddTargetBitrate((layers >> 4) & 0x0F, layers & 0x0F, bitrate_kbps); + } +} + +void TargetBitrate::AddTargetBitrate(uint8_t spatial_layer, + uint8_t temporal_layer, + uint32_t target_bitrate_kbps) { + RTC_DCHECK_LE(spatial_layer, 0x0F); + RTC_DCHECK_LE(temporal_layer, 0x0F); + RTC_DCHECK_LE(target_bitrate_kbps, 0x00FFFFFFU); + bitrates_.push_back( + BitrateItem(spatial_layer, temporal_layer, target_bitrate_kbps)); +} + +const std::vector<TargetBitrate::BitrateItem>& +TargetBitrate::GetTargetBitrates() const { + return bitrates_; +} + +size_t TargetBitrate::BlockLength() const { + return kTargetBitrateHeaderSizeBytes + + bitrates_.size() * kBitrateItemSizeBytes; +} + +void TargetBitrate::Create(uint8_t* buffer) const { + buffer[0] = kBlockType; + buffer[1] = 0; // Reserved. + uint16_t block_length_words = + rtc::dchecked_cast<uint16_t>((BlockLength() / 4) - 1); + ByteWriter<uint16_t>::WriteBigEndian(&buffer[2], block_length_words); + + size_t index = kTargetBitrateHeaderSizeBytes; + for (const BitrateItem& item : bitrates_) { + buffer[index] = (item.spatial_layer << 4) | item.temporal_layer; + ByteWriter<uint32_t, 3>::WriteBigEndian(&buffer[index + 1], + item.target_bitrate_kbps); + index += kBitrateItemSizeBytes; + } +} + +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/target_bitrate.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/target_bitrate.h new file mode 100644 index 0000000000..07e5da1a49 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/target_bitrate.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2016 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_TARGET_BITRATE_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_TARGET_BITRATE_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <vector> + +namespace webrtc { +namespace rtcp { + +class TargetBitrate { + public: + // TODO(sprang): This block type is just a place holder. We need to get an + // id assigned by IANA. + static constexpr uint8_t kBlockType = 42; + static const size_t kBitrateItemSizeBytes; + + struct BitrateItem { + BitrateItem(); + BitrateItem(uint8_t spatial_layer, + uint8_t temporal_layer, + uint32_t target_bitrate_kbps); + + uint8_t spatial_layer; + uint8_t temporal_layer; + uint32_t target_bitrate_kbps; + }; + + TargetBitrate(); + TargetBitrate(const TargetBitrate&); + TargetBitrate& operator=(const TargetBitrate&); + ~TargetBitrate(); + + void AddTargetBitrate(uint8_t spatial_layer, + uint8_t temporal_layer, + uint32_t target_bitrate_kbps); + + const std::vector<BitrateItem>& GetTargetBitrates() const; + + void Parse(const uint8_t* block, uint16_t block_length); + + size_t BlockLength() const; + + void Create(uint8_t* buffer) const; + + private: + std::vector<BitrateItem> bitrates_; +}; + +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_TARGET_BITRATE_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/target_bitrate_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/target_bitrate_unittest.cc new file mode 100644 index 0000000000..b16bb5beaa --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/target_bitrate_unittest.cc @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2016 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/target_bitrate.h" + +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtcp_packet/extended_reports.h" +#include "rtc_base/buffer.h" +#include "test/gtest.h" +#include "test/rtcp_packet_parser.h" + +namespace webrtc { +namespace { +using BitrateItem = rtcp::TargetBitrate::BitrateItem; +using rtcp::TargetBitrate; +using test::ParseSinglePacket; + +constexpr uint32_t kSsrc = 0x12345678; + +// clang-format off +const uint8_t kPacket[] = { TargetBitrate::kBlockType, // Block ID. + 0x00, // Reserved. + 0x00, 0x04, // Length = 4 words. + 0x00, 0x01, 0x02, 0x03, // S0T0 0x010203 kbps. + 0x01, 0x02, 0x03, 0x04, // S0T1 0x020304 kbps. + 0x10, 0x03, 0x04, 0x05, // S1T0 0x030405 kbps. + 0x11, 0x04, 0x05, 0x06 }; // S1T1 0x040506 kbps. +constexpr size_t kPacketLengthBlocks = ((sizeof(kPacket) + 3) / 4) - 1; +// clang-format on + +void ExpectBirateItemEquals(const BitrateItem& expected, + const BitrateItem& actual) { + EXPECT_EQ(expected.spatial_layer, actual.spatial_layer); + EXPECT_EQ(expected.temporal_layer, actual.temporal_layer); + EXPECT_EQ(expected.target_bitrate_kbps, actual.target_bitrate_kbps); +} + +void CheckBitrateItems(const std::vector<BitrateItem>& bitrates) { + EXPECT_EQ(4U, bitrates.size()); + ExpectBirateItemEquals(BitrateItem(0, 0, 0x010203), bitrates[0]); + ExpectBirateItemEquals(BitrateItem(0, 1, 0x020304), bitrates[1]); + ExpectBirateItemEquals(BitrateItem(1, 0, 0x030405), bitrates[2]); + ExpectBirateItemEquals(BitrateItem(1, 1, 0x040506), bitrates[3]); +} + +} // namespace + +TEST(TargetBitrateTest, Parse) { + TargetBitrate target_bitrate; + target_bitrate.Parse(kPacket, kPacketLengthBlocks); + CheckBitrateItems(target_bitrate.GetTargetBitrates()); +} + +TEST(TargetBitrateTest, FullPacket) { + const size_t kXRHeaderSize = 8; // RTCP header (4) + SSRC (4). + const size_t kTotalSize = kXRHeaderSize + sizeof(kPacket); + uint8_t kRtcpPacket[kTotalSize] = {2 << 6, 207, 0x00, (kTotalSize / 4) - 1, + 0x12, 0x34, 0x56, 0x78}; // SSRC. + memcpy(&kRtcpPacket[kXRHeaderSize], kPacket, sizeof(kPacket)); + rtcp::ExtendedReports xr; + EXPECT_TRUE(ParseSinglePacket(kRtcpPacket, &xr)); + EXPECT_EQ(kSsrc, xr.sender_ssrc()); + const absl::optional<TargetBitrate>& target_bitrate = xr.target_bitrate(); + ASSERT_TRUE(static_cast<bool>(target_bitrate)); + CheckBitrateItems(target_bitrate->GetTargetBitrates()); +} + +TEST(TargetBitrateTest, Create) { + TargetBitrate target_bitrate; + target_bitrate.AddTargetBitrate(0, 0, 0x010203); + target_bitrate.AddTargetBitrate(0, 1, 0x020304); + target_bitrate.AddTargetBitrate(1, 0, 0x030405); + target_bitrate.AddTargetBitrate(1, 1, 0x040506); + + uint8_t buffer[sizeof(kPacket)] = {}; + ASSERT_EQ(sizeof(kPacket), target_bitrate.BlockLength()); + target_bitrate.Create(buffer); + + EXPECT_EQ(0, memcmp(kPacket, buffer, sizeof(kPacket))); +} + +TEST(TargetBitrateTest, ParseNullBitratePacket) { + const uint8_t kNullPacket[] = {TargetBitrate::kBlockType, 0x00, 0x00, 0x00}; + TargetBitrate target_bitrate; + target_bitrate.Parse(kNullPacket, 0); + EXPECT_TRUE(target_bitrate.GetTargetBitrates().empty()); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmb_item.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmb_item.cc new file mode 100644 index 0000000000..810e1e267a --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmb_item.cc @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2016 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/tmmb_item.h" + +#include "modules/rtp_rtcp/source/byte_io.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace rtcp { +TmmbItem::TmmbItem(uint32_t ssrc, uint64_t bitrate_bps, uint16_t overhead) + : ssrc_(ssrc), bitrate_bps_(bitrate_bps), packet_overhead_(overhead) { + RTC_DCHECK_LE(overhead, 0x1ffu); +} + +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 0 | SSRC | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 4 | MxTBR Exp | MxTBR Mantissa |Measured Overhead| +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +bool TmmbItem::Parse(const uint8_t* buffer) { + ssrc_ = ByteReader<uint32_t>::ReadBigEndian(&buffer[0]); + // Read 4 bytes into 1 block. + uint32_t compact = ByteReader<uint32_t>::ReadBigEndian(&buffer[4]); + // Split 1 block into 3 components. + uint8_t exponent = compact >> 26; // 6 bits. + uint64_t mantissa = (compact >> 9) & 0x1ffff; // 17 bits. + uint16_t overhead = compact & 0x1ff; // 9 bits. + // Combine 3 components into 2 values. + bitrate_bps_ = (mantissa << exponent); + + bool shift_overflow = (bitrate_bps_ >> exponent) != mantissa; + if (shift_overflow) { + RTC_LOG(LS_ERROR) << "Invalid tmmb bitrate value : " << mantissa << "*2^" + << static_cast<int>(exponent); + return false; + } + packet_overhead_ = overhead; + return true; +} + +void TmmbItem::Create(uint8_t* buffer) const { + constexpr uint64_t kMaxMantissa = 0x1ffff; // 17 bits. + uint64_t mantissa = bitrate_bps_; + uint32_t exponent = 0; + while (mantissa > kMaxMantissa) { + mantissa >>= 1; + ++exponent; + } + + ByteWriter<uint32_t>::WriteBigEndian(&buffer[0], ssrc_); + uint32_t compact = (exponent << 26) | (mantissa << 9) | packet_overhead_; + ByteWriter<uint32_t>::WriteBigEndian(&buffer[4], compact); +} + +void TmmbItem::set_packet_overhead(uint16_t overhead) { + RTC_DCHECK_LE(overhead, 0x1ffu); + packet_overhead_ = overhead; +} +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmb_item.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmb_item.h new file mode 100644 index 0000000000..dc5d1b2c2d --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmb_item.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2016 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_TMMB_ITEM_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_TMMB_ITEM_H_ + +#include <stddef.h> +#include <stdint.h> + +namespace webrtc { +namespace rtcp { +// RFC5104, Section 3.5.4 +// Temporary Maximum Media Stream Bitrate Request/Notification. +// Used both by TMMBR and TMMBN rtcp packets. +class TmmbItem { + public: + static const size_t kLength = 8; + + TmmbItem() : ssrc_(0), bitrate_bps_(0), packet_overhead_(0) {} + TmmbItem(uint32_t ssrc, uint64_t bitrate_bps, uint16_t overhead); + + bool Parse(const uint8_t* buffer); + void Create(uint8_t* buffer) const; + + void set_ssrc(uint32_t ssrc) { ssrc_ = ssrc; } + void set_bitrate_bps(uint64_t bitrate_bps) { bitrate_bps_ = bitrate_bps; } + void set_packet_overhead(uint16_t overhead); + + uint32_t ssrc() const { return ssrc_; } + uint64_t bitrate_bps() const { return bitrate_bps_; } + uint16_t packet_overhead() const { return packet_overhead_; } + + private: + // Media stream id. + uint32_t ssrc_; + // Maximum total media bit rate that the media receiver is + // currently prepared to accept for this media stream. + uint64_t bitrate_bps_; + // Per-packet overhead that the media receiver has observed + // for this media stream at its chosen reference protocol layer. + uint16_t packet_overhead_; +}; +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_TMMB_ITEM_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbn.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbn.cc new file mode 100644 index 0000000000..f57e5749c2 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbn.cc @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2016 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/tmmbn.h" + +#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace rtcp { +constexpr uint8_t Tmmbn::kFeedbackMessageType; +// RFC 4585: Feedback format. +// Common packet format: +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |V=2|P| FMT | PT | length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC of packet sender | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC of media source (unused) = 0 | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// : Feedback Control Information (FCI) : +// : : +// Temporary Maximum Media Stream Bit Rate Notification (TMMBN) (RFC 5104). +// The Feedback Control Information (FCI) consists of zero, one, or more +// TMMBN FCI entries. +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | MxTBR Exp | MxTBR Mantissa |Measured Overhead| +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +Tmmbn::Tmmbn() = default; + +Tmmbn::~Tmmbn() = default; + +bool Tmmbn::Parse(const CommonHeader& packet) { + RTC_DCHECK_EQ(packet.type(), kPacketType); + RTC_DCHECK_EQ(packet.fmt(), kFeedbackMessageType); + + if (packet.payload_size_bytes() < kCommonFeedbackLength) { + RTC_LOG(LS_WARNING) << "Payload length " << packet.payload_size_bytes() + << " is too small for TMMBN."; + return false; + } + size_t items_size_bytes = packet.payload_size_bytes() - kCommonFeedbackLength; + if (items_size_bytes % TmmbItem::kLength != 0) { + RTC_LOG(LS_WARNING) << "Payload length " << packet.payload_size_bytes() + << " is not valid for TMMBN."; + return false; + } + ParseCommonFeedback(packet.payload()); + const uint8_t* next_item = packet.payload() + kCommonFeedbackLength; + + size_t number_of_items = items_size_bytes / TmmbItem::kLength; + items_.resize(number_of_items); + for (TmmbItem& item : items_) { + if (!item.Parse(next_item)) + return false; + next_item += TmmbItem::kLength; + } + return true; +} + +void Tmmbn::AddTmmbr(const TmmbItem& item) { + items_.push_back(item); +} + +size_t Tmmbn::BlockLength() const { + return kHeaderLength + kCommonFeedbackLength + + TmmbItem::kLength * items_.size(); +} + +bool Tmmbn::Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const { + while (*index + BlockLength() > max_length) { + if (!OnBufferFull(packet, index, callback)) + return false; + } + const size_t index_end = *index + BlockLength(); + + CreateHeader(kFeedbackMessageType, kPacketType, HeaderLength(), packet, + index); + RTC_DCHECK_EQ(0, Rtpfb::media_ssrc()); + CreateCommonFeedback(packet + *index); + *index += kCommonFeedbackLength; + for (const TmmbItem& item : items_) { + item.Create(packet + *index); + *index += TmmbItem::kLength; + } + RTC_CHECK_EQ(index_end, *index); + return true; +} +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbn.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbn.h new file mode 100644 index 0000000000..ff7779d8ac --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbn.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2016 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_TMMBN_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_TMMBN_H_ + +#include <vector> + +#include "modules/rtp_rtcp/source/rtcp_packet/rtpfb.h" +#include "modules/rtp_rtcp/source/rtcp_packet/tmmb_item.h" + +namespace webrtc { +namespace rtcp { +class CommonHeader; + +// Temporary Maximum Media Stream Bit Rate Notification (TMMBN). +// RFC 5104, Section 4.2.2. +class Tmmbn : public Rtpfb { + public: + static constexpr uint8_t kFeedbackMessageType = 4; + + Tmmbn(); + ~Tmmbn() override; + + // Parse assumes header is already parsed and validated. + bool Parse(const CommonHeader& packet); + + void AddTmmbr(const TmmbItem& item); + + const std::vector<TmmbItem>& items() const { return items_; } + + size_t BlockLength() const override; + + bool Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const override; + + private: + // Media ssrc is unused, shadow base class setter and getter. + void SetMediaSsrc(uint32_t ssrc); + uint32_t media_ssrc() const; + + std::vector<TmmbItem> items_; +}; +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_TMMBN_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbn_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbn_unittest.cc new file mode 100644 index 0000000000..3a37bb1c0e --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbn_unittest.cc @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2016 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/tmmbn.h" + +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/rtcp_packet_parser.h" + +using ::testing::ElementsAreArray; +using ::testing::IsEmpty; +using ::testing::make_tuple; +using webrtc::rtcp::TmmbItem; +using webrtc::rtcp::Tmmbn; + +namespace webrtc { +namespace { +const uint32_t kSenderSsrc = 0x12345678; +const uint32_t kRemoteSsrc = 0x23456789; +const uint32_t kBitrateBps = 312000; +const uint16_t kOverhead = 0x1fe; +const uint8_t kPacket[] = {0x84, 205, 0x00, 0x04, 0x12, 0x34, 0x56, + 0x78, 0x00, 0x00, 0x00, 0x00, 0x23, 0x45, + 0x67, 0x89, 0x0a, 0x61, 0x61, 0xfe}; +} // namespace + +TEST(RtcpPacketTmmbnTest, Create) { + Tmmbn tmmbn; + tmmbn.SetSenderSsrc(kSenderSsrc); + tmmbn.AddTmmbr(TmmbItem(kRemoteSsrc, kBitrateBps, kOverhead)); + + rtc::Buffer packet = tmmbn.Build(); + + EXPECT_THAT(make_tuple(packet.data(), packet.size()), + ElementsAreArray(kPacket)); +} + +TEST(RtcpPacketTmmbnTest, Parse) { + Tmmbn tmmbn; + EXPECT_TRUE(test::ParseSinglePacket(kPacket, &tmmbn)); + + const Tmmbn& parsed = tmmbn; + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + ASSERT_EQ(1u, parsed.items().size()); + EXPECT_EQ(kRemoteSsrc, parsed.items().front().ssrc()); + EXPECT_EQ(kBitrateBps, parsed.items().front().bitrate_bps()); + EXPECT_EQ(kOverhead, parsed.items().front().packet_overhead()); +} + +TEST(RtcpPacketTmmbnTest, CreateAndParseWithoutItems) { + Tmmbn tmmbn; + tmmbn.SetSenderSsrc(kSenderSsrc); + + rtc::Buffer packet = tmmbn.Build(); + Tmmbn parsed; + EXPECT_TRUE(test::ParseSinglePacket(packet, &parsed)); + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_THAT(parsed.items(), IsEmpty()); +} + +TEST(RtcpPacketTmmbnTest, CreateAndParseWithTwoItems) { + Tmmbn tmmbn; + tmmbn.SetSenderSsrc(kSenderSsrc); + tmmbn.AddTmmbr(TmmbItem(kRemoteSsrc, kBitrateBps, kOverhead)); + tmmbn.AddTmmbr(TmmbItem(kRemoteSsrc + 1, 4 * kBitrateBps, 40)); + + rtc::Buffer packet = tmmbn.Build(); + Tmmbn parsed; + EXPECT_TRUE(test::ParseSinglePacket(packet, &parsed)); + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_EQ(2u, parsed.items().size()); + EXPECT_EQ(kRemoteSsrc, parsed.items()[0].ssrc()); + EXPECT_EQ(kBitrateBps, parsed.items()[0].bitrate_bps()); + EXPECT_EQ(kOverhead, parsed.items()[0].packet_overhead()); + EXPECT_EQ(kRemoteSsrc + 1, parsed.items()[1].ssrc()); + EXPECT_EQ(4 * kBitrateBps, parsed.items()[1].bitrate_bps()); + EXPECT_EQ(40U, parsed.items()[1].packet_overhead()); +} + +TEST(RtcpPacketTmmbnTest, ParseFailsOnTooSmallPacket) { + const uint8_t kSmallPacket[] = {0x84, 205, 0x00, 0x01, + 0x12, 0x34, 0x56, 0x78}; + Tmmbn tmmbn; + EXPECT_FALSE(test::ParseSinglePacket(kSmallPacket, &tmmbn)); +} + +TEST(RtcpPacketTmmbnTest, ParseFailsOnUnAlignedPacket) { + const uint8_t kUnalignedPacket[] = {0x84, 205, 0x00, 0x03, 0x12, 0x34, + 0x56, 0x78, 0x00, 0x00, 0x00, 0x00, + 0x23, 0x45, 0x67, 0x89}; + + Tmmbn tmmbn; + EXPECT_FALSE(test::ParseSinglePacket(kUnalignedPacket, &tmmbn)); +} +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbr.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbr.cc new file mode 100644 index 0000000000..9dc745e509 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbr.cc @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2016 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/tmmbr.h" + +#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace rtcp { +constexpr uint8_t Tmmbr::kFeedbackMessageType; +// RFC 4585: Feedback format. +// Common packet format: +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |V=2|P| FMT | PT | length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC of packet sender | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC of media source (unused) = 0 | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// : Feedback Control Information (FCI) : +// : : +// Temporary Maximum Media Stream Bit Rate Request (TMMBR) (RFC 5104). +// The Feedback Control Information (FCI) for the TMMBR +// consists of one or more FCI entries. +// FCI: +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | MxTBR Exp | MxTBR Mantissa |Measured Overhead| +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +Tmmbr::Tmmbr() = default; + +Tmmbr::~Tmmbr() = default; + +bool Tmmbr::Parse(const CommonHeader& packet) { + RTC_DCHECK_EQ(packet.type(), kPacketType); + RTC_DCHECK_EQ(packet.fmt(), kFeedbackMessageType); + + if (packet.payload_size_bytes() < kCommonFeedbackLength + TmmbItem::kLength) { + RTC_LOG(LS_WARNING) << "Payload length " << packet.payload_size_bytes() + << " is too small for a TMMBR."; + return false; + } + size_t items_size_bytes = packet.payload_size_bytes() - kCommonFeedbackLength; + if (items_size_bytes % TmmbItem::kLength != 0) { + RTC_LOG(LS_WARNING) << "Payload length " << packet.payload_size_bytes() + << " is not valid for a TMMBR."; + return false; + } + ParseCommonFeedback(packet.payload()); + + const uint8_t* next_item = packet.payload() + kCommonFeedbackLength; + size_t number_of_items = items_size_bytes / TmmbItem::kLength; + items_.resize(number_of_items); + for (TmmbItem& item : items_) { + if (!item.Parse(next_item)) + return false; + next_item += TmmbItem::kLength; + } + return true; +} + +void Tmmbr::AddTmmbr(const TmmbItem& item) { + items_.push_back(item); +} + +size_t Tmmbr::BlockLength() const { + return kHeaderLength + kCommonFeedbackLength + + TmmbItem::kLength * items_.size(); +} + +bool Tmmbr::Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const { + RTC_DCHECK(!items_.empty()); + while (*index + BlockLength() > max_length) { + if (!OnBufferFull(packet, index, callback)) + return false; + } + const size_t index_end = *index + BlockLength(); + + CreateHeader(kFeedbackMessageType, kPacketType, HeaderLength(), packet, + index); + RTC_DCHECK_EQ(0, Rtpfb::media_ssrc()); + CreateCommonFeedback(packet + *index); + *index += kCommonFeedbackLength; + for (const TmmbItem& item : items_) { + item.Create(packet + *index); + *index += TmmbItem::kLength; + } + RTC_CHECK_EQ(index_end, *index); + return true; +} +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbr.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbr.h new file mode 100644 index 0000000000..7482cb75cc --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbr.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2016 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_TMMBR_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_TMMBR_H_ + +#include <vector> + +#include "modules/rtp_rtcp/source/rtcp_packet/rtpfb.h" +#include "modules/rtp_rtcp/source/rtcp_packet/tmmb_item.h" + +namespace webrtc { +namespace rtcp { +class CommonHeader; + +// Temporary Maximum Media Stream Bit Rate Request (TMMBR). +// RFC 5104, Section 4.2.1. +class Tmmbr : public Rtpfb { + public: + static constexpr uint8_t kFeedbackMessageType = 3; + + Tmmbr(); + ~Tmmbr() override; + + // Parse assumes header is already parsed and validated. + bool Parse(const CommonHeader& packet); + + void AddTmmbr(const TmmbItem& item); + + const std::vector<TmmbItem>& requests() const { return items_; } + + size_t BlockLength() const override; + + bool Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const override; + + private: + // Media ssrc is unused, shadow base class setter. + void SetMediaSsrc(uint32_t ssrc); + + std::vector<TmmbItem> items_; +}; +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_TMMBR_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbr_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbr_unittest.cc new file mode 100644 index 0000000000..1bac808aa9 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbr_unittest.cc @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2016 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/tmmbr.h" + +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/rtcp_packet_parser.h" + +using ::testing::ElementsAreArray; +using ::testing::make_tuple; +using webrtc::rtcp::TmmbItem; +using webrtc::rtcp::Tmmbr; + +namespace webrtc { +namespace { +const uint32_t kSenderSsrc = 0x12345678; +const uint32_t kRemoteSsrc = 0x23456789; +const uint32_t kBitrateBps = 312000; +const uint16_t kOverhead = 0x1fe; +const uint8_t kPacket[] = {0x83, 205, 0x00, 0x04, 0x12, 0x34, 0x56, + 0x78, 0x00, 0x00, 0x00, 0x00, 0x23, 0x45, + 0x67, 0x89, 0x0a, 0x61, 0x61, 0xfe}; +} // namespace + +TEST(RtcpPacketTmmbrTest, Create) { + Tmmbr tmmbr; + tmmbr.SetSenderSsrc(kSenderSsrc); + tmmbr.AddTmmbr(TmmbItem(kRemoteSsrc, kBitrateBps, kOverhead)); + + rtc::Buffer packet = tmmbr.Build(); + + EXPECT_THAT(make_tuple(packet.data(), packet.size()), + ElementsAreArray(kPacket)); +} + +TEST(RtcpPacketTmmbrTest, Parse) { + Tmmbr tmmbr; + EXPECT_TRUE(test::ParseSinglePacket(kPacket, &tmmbr)); + const Tmmbr& parsed = tmmbr; + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + ASSERT_EQ(1u, parsed.requests().size()); + EXPECT_EQ(kRemoteSsrc, parsed.requests().front().ssrc()); + EXPECT_EQ(kBitrateBps, parsed.requests().front().bitrate_bps()); + EXPECT_EQ(kOverhead, parsed.requests().front().packet_overhead()); +} + +TEST(RtcpPacketTmmbrTest, CreateAndParseWithTwoEntries) { + Tmmbr tmmbr; + tmmbr.SetSenderSsrc(kSenderSsrc); + tmmbr.AddTmmbr(TmmbItem(kRemoteSsrc, kBitrateBps, kOverhead)); + tmmbr.AddTmmbr(TmmbItem(kRemoteSsrc + 1, 4 * kBitrateBps, kOverhead + 1)); + + rtc::Buffer packet = tmmbr.Build(); + + Tmmbr parsed; + EXPECT_TRUE(test::ParseSinglePacket(packet, &parsed)); + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_EQ(2u, parsed.requests().size()); + EXPECT_EQ(kRemoteSsrc, parsed.requests()[0].ssrc()); + EXPECT_EQ(kRemoteSsrc + 1, parsed.requests()[1].ssrc()); +} + +TEST(RtcpPacketTmmbrTest, ParseFailsWithoutItems) { + const uint8_t kZeroItemsPacket[] = {0x83, 205, 0x00, 0x02, 0x12, 0x34, + 0x56, 0x78, 0x00, 0x00, 0x00, 0x00}; + + Tmmbr tmmbr; + EXPECT_FALSE(test::ParseSinglePacket(kZeroItemsPacket, &tmmbr)); +} + +TEST(RtcpPacketTmmbrTest, ParseFailsOnUnAlignedPacket) { + const uint8_t kUnalignedPacket[] = { + 0x83, 205, 0x00, 0x05, 0x12, 0x34, 0x56, 0x78, 0x00, 0x00, 0x00, 0x00, + 0x23, 0x45, 0x67, 0x89, 0x0a, 0x61, 0x61, 0xfe, 0x34, 0x56, 0x78, 0x9a}; + + Tmmbr tmmbr; + EXPECT_FALSE(test::ParseSinglePacket(kUnalignedPacket, &tmmbr)); +} +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/transport_feedback.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/transport_feedback.cc new file mode 100644 index 0000000000..003effad29 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/transport_feedback.cc @@ -0,0 +1,737 @@ +/* + * 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 <algorithm> +#include <cstdint> +#include <numeric> +#include <utility> + +#include "absl/algorithm/container.h" +#include "modules/include/module_common_types_public.h" +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/trace_event.h" + +namespace webrtc { +namespace rtcp { +namespace { +// Header size: +// * 4 bytes Common RTCP Packet Header +// * 8 bytes Common Packet Format for RTCP Feedback Messages +// * 8 bytes FeedbackPacket header +constexpr size_t kTransportFeedbackHeaderSizeBytes = 4 + 8 + 8; +constexpr size_t kChunkSizeBytes = 2; +// TODO(sprang): Add support for dynamic max size for easier fragmentation, +// eg. set it to what's left in the buffer or IP_PACKET_SIZE. +// Size constraint imposed by RTCP common header: 16bit size field interpreted +// as number of four byte words minus the first header word. +constexpr size_t kMaxSizeBytes = (1 << 16) * 4; +// Payload size: +// * 8 bytes Common Packet Format for RTCP Feedback Messages +// * 8 bytes FeedbackPacket header. +// * 2 bytes for one chunk. +constexpr size_t kMinPayloadSizeBytes = 8 + 8 + 2; +constexpr TimeDelta kBaseTimeTick = TransportFeedback::kDeltaTick * (1 << 8); +constexpr TimeDelta kTimeWrapPeriod = kBaseTimeTick * (1 << 24); + +// Message format +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |V=2|P| FMT=15 | PT=205 | length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 0 | SSRC of packet sender | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 4 | SSRC of media source | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 8 | base sequence number | packet status count | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 12 | reference time | fb pkt. count | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 16 | packet chunk | packet chunk | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// . . +// . . +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | packet chunk | recv delta | recv delta | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// . . +// . . +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | recv delta | recv delta | zero padding | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +} // namespace +constexpr uint8_t TransportFeedback::kFeedbackMessageType; +constexpr size_t TransportFeedback::kMaxReportedPackets; + +constexpr size_t TransportFeedback::LastChunk::kMaxRunLengthCapacity; +constexpr size_t TransportFeedback::LastChunk::kMaxOneBitCapacity; +constexpr size_t TransportFeedback::LastChunk::kMaxTwoBitCapacity; +constexpr size_t TransportFeedback::LastChunk::kMaxVectorCapacity; + +TransportFeedback::LastChunk::LastChunk() { + Clear(); +} + +bool TransportFeedback::LastChunk::Empty() const { + return size_ == 0; +} + +void TransportFeedback::LastChunk::Clear() { + size_ = 0; + all_same_ = true; + has_large_delta_ = false; +} + +bool TransportFeedback::LastChunk::CanAdd(DeltaSize delta_size) const { + RTC_DCHECK_LE(delta_size, 2); + if (size_ < kMaxTwoBitCapacity) + return true; + if (size_ < kMaxOneBitCapacity && !has_large_delta_ && delta_size != kLarge) + return true; + if (size_ < kMaxRunLengthCapacity && all_same_ && + delta_sizes_[0] == delta_size) + return true; + return false; +} + +void TransportFeedback::LastChunk::Add(DeltaSize delta_size) { + RTC_DCHECK(CanAdd(delta_size)); + if (size_ < kMaxVectorCapacity) + delta_sizes_[size_] = delta_size; + size_++; + all_same_ = all_same_ && delta_size == delta_sizes_[0]; + has_large_delta_ = has_large_delta_ || delta_size == kLarge; +} + +void TransportFeedback::LastChunk::AddMissingPackets(size_t num_missing) { + RTC_DCHECK_EQ(size_, 0); + RTC_DCHECK(all_same_); + RTC_DCHECK(!has_large_delta_); + RTC_DCHECK_LT(num_missing, kMaxRunLengthCapacity); + absl::c_fill(delta_sizes_, DeltaSize(0)); + size_ = num_missing; +} + +uint16_t TransportFeedback::LastChunk::Emit() { + RTC_DCHECK(!CanAdd(0) || !CanAdd(1) || !CanAdd(2)); + if (all_same_) { + uint16_t chunk = EncodeRunLength(); + Clear(); + return chunk; + } + if (size_ == kMaxOneBitCapacity) { + uint16_t chunk = EncodeOneBit(); + Clear(); + return chunk; + } + RTC_DCHECK_GE(size_, kMaxTwoBitCapacity); + uint16_t chunk = EncodeTwoBit(kMaxTwoBitCapacity); + // Remove `kMaxTwoBitCapacity` encoded delta sizes: + // Shift remaining delta sizes and recalculate all_same_ && has_large_delta_. + size_ -= kMaxTwoBitCapacity; + all_same_ = true; + has_large_delta_ = false; + for (size_t i = 0; i < size_; ++i) { + DeltaSize delta_size = delta_sizes_[kMaxTwoBitCapacity + i]; + delta_sizes_[i] = delta_size; + all_same_ = all_same_ && delta_size == delta_sizes_[0]; + has_large_delta_ = has_large_delta_ || delta_size == kLarge; + } + + return chunk; +} + +uint16_t TransportFeedback::LastChunk::EncodeLast() const { + RTC_DCHECK_GT(size_, 0); + if (all_same_) + return EncodeRunLength(); + if (size_ <= kMaxTwoBitCapacity) + return EncodeTwoBit(size_); + return EncodeOneBit(); +} + +// Appends content of the Lastchunk to `deltas`. +void TransportFeedback::LastChunk::AppendTo( + std::vector<DeltaSize>* deltas) const { + if (all_same_) { + deltas->insert(deltas->end(), size_, delta_sizes_[0]); + } else { + deltas->insert(deltas->end(), delta_sizes_.begin(), + delta_sizes_.begin() + size_); + } +} + +void TransportFeedback::LastChunk::Decode(uint16_t chunk, size_t max_size) { + if ((chunk & 0x8000) == 0) { + DecodeRunLength(chunk, max_size); + } else if ((chunk & 0x4000) == 0) { + DecodeOneBit(chunk, max_size); + } else { + DecodeTwoBit(chunk, max_size); + } +} + +// One Bit Status Vector Chunk +// +// 0 1 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |T|S| symbol list | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +// T = 1 +// S = 0 +// Symbol list = 14 entries where 0 = not received, 1 = received 1-byte delta. +uint16_t TransportFeedback::LastChunk::EncodeOneBit() const { + RTC_DCHECK(!has_large_delta_); + RTC_DCHECK_LE(size_, kMaxOneBitCapacity); + uint16_t chunk = 0x8000; + for (size_t i = 0; i < size_; ++i) + chunk |= delta_sizes_[i] << (kMaxOneBitCapacity - 1 - i); + return chunk; +} + +void TransportFeedback::LastChunk::DecodeOneBit(uint16_t chunk, + size_t max_size) { + RTC_DCHECK_EQ(chunk & 0xc000, 0x8000); + size_ = std::min(kMaxOneBitCapacity, max_size); + has_large_delta_ = false; + all_same_ = false; + for (size_t i = 0; i < size_; ++i) + delta_sizes_[i] = (chunk >> (kMaxOneBitCapacity - 1 - i)) & 0x01; +} + +// Two Bit Status Vector Chunk +// +// 0 1 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |T|S| symbol list | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +// T = 1 +// S = 1 +// symbol list = 7 entries of two bits each. +uint16_t TransportFeedback::LastChunk::EncodeTwoBit(size_t size) const { + RTC_DCHECK_LE(size, size_); + uint16_t chunk = 0xc000; + for (size_t i = 0; i < size; ++i) + chunk |= delta_sizes_[i] << 2 * (kMaxTwoBitCapacity - 1 - i); + return chunk; +} + +void TransportFeedback::LastChunk::DecodeTwoBit(uint16_t chunk, + size_t max_size) { + RTC_DCHECK_EQ(chunk & 0xc000, 0xc000); + size_ = std::min(kMaxTwoBitCapacity, max_size); + has_large_delta_ = true; + all_same_ = false; + for (size_t i = 0; i < size_; ++i) + delta_sizes_[i] = (chunk >> 2 * (kMaxTwoBitCapacity - 1 - i)) & 0x03; +} + +// Run Length Status Vector Chunk +// +// 0 1 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |T| S | Run Length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +// T = 0 +// S = symbol +// Run Length = Unsigned integer denoting the run length of the symbol +uint16_t TransportFeedback::LastChunk::EncodeRunLength() const { + RTC_DCHECK(all_same_); + RTC_DCHECK_LE(size_, kMaxRunLengthCapacity); + return (delta_sizes_[0] << 13) | static_cast<uint16_t>(size_); +} + +void TransportFeedback::LastChunk::DecodeRunLength(uint16_t chunk, + size_t max_count) { + RTC_DCHECK_EQ(chunk & 0x8000, 0); + size_ = std::min<size_t>(chunk & 0x1fff, max_count); + DeltaSize delta_size = (chunk >> 13) & 0x03; + has_large_delta_ = delta_size >= kLarge; + all_same_ = true; + // To make it consistent with Add function, populate delta_sizes_ beyond 1st. + for (size_t i = 0; i < std::min<size_t>(size_, kMaxVectorCapacity); ++i) + delta_sizes_[i] = delta_size; +} + +TransportFeedback::TransportFeedback() + : TransportFeedback(/*include_timestamps=*/true) {} + +TransportFeedback::TransportFeedback(bool include_timestamps) + : base_seq_no_(0), + num_seq_no_(0), + base_time_ticks_(0), + feedback_seq_(0), + include_timestamps_(include_timestamps), + last_timestamp_(Timestamp::Zero()), + size_bytes_(kTransportFeedbackHeaderSizeBytes) {} + +TransportFeedback::TransportFeedback(const TransportFeedback&) = default; + +TransportFeedback::TransportFeedback(TransportFeedback&& other) + : base_seq_no_(other.base_seq_no_), + num_seq_no_(other.num_seq_no_), + base_time_ticks_(other.base_time_ticks_), + feedback_seq_(other.feedback_seq_), + include_timestamps_(other.include_timestamps_), + last_timestamp_(other.last_timestamp_), + received_packets_(std::move(other.received_packets_)), + all_packets_(std::move(other.all_packets_)), + encoded_chunks_(std::move(other.encoded_chunks_)), + last_chunk_(other.last_chunk_), + size_bytes_(other.size_bytes_) { + other.Clear(); +} + +TransportFeedback::~TransportFeedback() {} + +void TransportFeedback::SetBase(uint16_t base_sequence, + Timestamp ref_timestamp) { + RTC_DCHECK_EQ(num_seq_no_, 0); + base_seq_no_ = base_sequence; + base_time_ticks_ = + (ref_timestamp.us() % kTimeWrapPeriod.us()) / kBaseTimeTick.us(); + last_timestamp_ = BaseTime(); +} + +void TransportFeedback::SetFeedbackSequenceNumber(uint8_t feedback_sequence) { + feedback_seq_ = feedback_sequence; +} + +bool TransportFeedback::AddReceivedPacket(uint16_t sequence_number, + Timestamp timestamp) { + // Set delta to zero if timestamps are not included, this will simplify the + // encoding process. + int16_t delta = 0; + if (include_timestamps_) { + // Convert to ticks and round. + if (last_timestamp_ > timestamp) { + timestamp += (last_timestamp_ - timestamp).RoundUpTo(kTimeWrapPeriod); + } + RTC_DCHECK_GE(timestamp, last_timestamp_); + int64_t delta_full = + (timestamp - last_timestamp_).us() % kTimeWrapPeriod.us(); + if (delta_full > kTimeWrapPeriod.us() / 2) { + delta_full -= kTimeWrapPeriod.us(); + delta_full -= kDeltaTick.us() / 2; + } else { + delta_full += kDeltaTick.us() / 2; + } + delta_full /= kDeltaTick.us(); + + delta = static_cast<int16_t>(delta_full); + // If larger than 16bit signed, we can't represent it - need new fb packet. + if (delta != delta_full) { + RTC_LOG(LS_WARNING) << "Delta value too large ( >= 2^16 ticks )"; + return false; + } + } + + uint16_t next_seq_no = base_seq_no_ + num_seq_no_; + if (sequence_number != next_seq_no) { + uint16_t last_seq_no = next_seq_no - 1; + if (!IsNewerSequenceNumber(sequence_number, last_seq_no)) + return false; + uint16_t num_missing_packets = sequence_number - next_seq_no; + if (!AddMissingPackets(num_missing_packets)) + return false; + } + + DeltaSize delta_size = (delta >= 0 && delta <= 0xff) ? 1 : 2; + if (!AddDeltaSize(delta_size)) + return false; + + received_packets_.emplace_back(sequence_number, delta); + last_timestamp_ += delta * kDeltaTick; + if (include_timestamps_) { + size_bytes_ += delta_size; + } + return true; +} + +const std::vector<TransportFeedback::ReceivedPacket>& +TransportFeedback::GetReceivedPackets() const { + return received_packets_; +} + +void TransportFeedback::ForAllPackets( + rtc::FunctionView<void(uint16_t, TimeDelta)> handler) const { + TimeDelta delta_since_base = TimeDelta::Zero(); + auto received_it = received_packets_.begin(); + const uint16_t last_seq_num = base_seq_no_ + num_seq_no_; + for (uint16_t seq_num = base_seq_no_; seq_num != last_seq_num; ++seq_num) { + if (received_it != received_packets_.end() && + received_it->sequence_number() == seq_num) { + delta_since_base += received_it->delta(); + handler(seq_num, delta_since_base); + ++received_it; + } else { + handler(seq_num, TimeDelta::PlusInfinity()); + } + } + RTC_DCHECK(received_it == received_packets_.end()); +} + +uint16_t TransportFeedback::GetBaseSequence() const { + return base_seq_no_; +} + +Timestamp TransportFeedback::BaseTime() const { + // Add an extra kTimeWrapPeriod to allow add received packets arrived earlier + // than the first added packet (and thus allow to record negative deltas) + // even when base_time_ticks_ == 0. + return Timestamp::Zero() + kTimeWrapPeriod + + int64_t{base_time_ticks_} * kBaseTimeTick; +} + +TimeDelta TransportFeedback::GetBaseDelta(Timestamp prev_timestamp) const { + TimeDelta delta = BaseTime() - prev_timestamp; + // Compensate for wrap around. + if ((delta - kTimeWrapPeriod).Abs() < delta.Abs()) { + delta -= kTimeWrapPeriod; // Wrap backwards. + } else if ((delta + kTimeWrapPeriod).Abs() < delta.Abs()) { + delta += kTimeWrapPeriod; // Wrap forwards. + } + return delta; +} + +// De-serialize packet. +bool TransportFeedback::Parse(const CommonHeader& packet) { + RTC_DCHECK_EQ(packet.type(), kPacketType); + RTC_DCHECK_EQ(packet.fmt(), kFeedbackMessageType); + TRACE_EVENT0("webrtc", "TransportFeedback::Parse"); + + if (packet.payload_size_bytes() < kMinPayloadSizeBytes) { + RTC_LOG(LS_WARNING) << "Buffer too small (" << packet.payload_size_bytes() + << " bytes) to fit a " + "FeedbackPacket. Minimum size = " + << kMinPayloadSizeBytes; + return false; + } + + const uint8_t* const payload = packet.payload(); + ParseCommonFeedback(payload); + + base_seq_no_ = ByteReader<uint16_t>::ReadBigEndian(&payload[8]); + uint16_t status_count = ByteReader<uint16_t>::ReadBigEndian(&payload[10]); + base_time_ticks_ = ByteReader<uint32_t, 3>::ReadBigEndian(&payload[12]); + feedback_seq_ = payload[15]; + Clear(); + size_t index = 16; + const size_t end_index = packet.payload_size_bytes(); + + if (status_count == 0) { + RTC_LOG(LS_WARNING) << "Empty feedback messages not allowed."; + return false; + } + + std::vector<uint8_t> delta_sizes; + delta_sizes.reserve(status_count); + while (delta_sizes.size() < status_count) { + if (index + kChunkSizeBytes > end_index) { + RTC_LOG(LS_WARNING) << "Buffer overflow while parsing packet."; + Clear(); + return false; + } + + uint16_t chunk = ByteReader<uint16_t>::ReadBigEndian(&payload[index]); + index += kChunkSizeBytes; + encoded_chunks_.push_back(chunk); + last_chunk_.Decode(chunk, status_count - delta_sizes.size()); + last_chunk_.AppendTo(&delta_sizes); + } + // Last chunk is stored in the `last_chunk_`. + encoded_chunks_.pop_back(); + RTC_DCHECK_EQ(delta_sizes.size(), status_count); + num_seq_no_ = status_count; + + uint16_t seq_no = base_seq_no_; + size_t recv_delta_size = absl::c_accumulate(delta_sizes, 0); + + // Determine if timestamps, that is, recv_delta are included in the packet. + if (end_index >= index + recv_delta_size) { + for (size_t delta_size : delta_sizes) { + RTC_DCHECK_LE(index + delta_size, end_index); + switch (delta_size) { + case 0: + break; + case 1: { + int16_t delta = payload[index]; + received_packets_.emplace_back(seq_no, delta); + last_timestamp_ += delta * kDeltaTick; + index += delta_size; + break; + } + case 2: { + int16_t delta = ByteReader<int16_t>::ReadBigEndian(&payload[index]); + received_packets_.emplace_back(seq_no, delta); + last_timestamp_ += delta * kDeltaTick; + index += delta_size; + break; + } + case 3: + Clear(); + RTC_LOG(LS_WARNING) << "Invalid delta_size for seq_no " << seq_no; + + return false; + default: + RTC_DCHECK_NOTREACHED(); + break; + } + ++seq_no; + } + } else { + // The packet does not contain receive deltas. + include_timestamps_ = false; + for (size_t delta_size : delta_sizes) { + // Use delta sizes to detect if packet was received. + if (delta_size > 0) { + received_packets_.emplace_back(seq_no, 0); + } + ++seq_no; + } + } + size_bytes_ = RtcpPacket::kHeaderLength + index; + RTC_DCHECK_LE(index, end_index); + return true; +} + +std::unique_ptr<TransportFeedback> TransportFeedback::ParseFrom( + const uint8_t* buffer, + size_t length) { + CommonHeader header; + if (!header.Parse(buffer, length)) + return nullptr; + if (header.type() != kPacketType || header.fmt() != kFeedbackMessageType) + return nullptr; + std::unique_ptr<TransportFeedback> parsed(new TransportFeedback); + if (!parsed->Parse(header)) + return nullptr; + return parsed; +} + +bool TransportFeedback::IsConsistent() const { + size_t packet_size = kTransportFeedbackHeaderSizeBytes; + std::vector<DeltaSize> delta_sizes; + LastChunk chunk_decoder; + for (uint16_t chunk : encoded_chunks_) { + chunk_decoder.Decode(chunk, kMaxReportedPackets); + chunk_decoder.AppendTo(&delta_sizes); + packet_size += kChunkSizeBytes; + } + if (!last_chunk_.Empty()) { + last_chunk_.AppendTo(&delta_sizes); + packet_size += kChunkSizeBytes; + } + if (num_seq_no_ != delta_sizes.size()) { + RTC_LOG(LS_ERROR) << delta_sizes.size() << " packets encoded. Expected " + << num_seq_no_; + return false; + } + Timestamp timestamp = BaseTime(); + auto packet_it = received_packets_.begin(); + uint16_t seq_no = base_seq_no_; + for (DeltaSize delta_size : delta_sizes) { + if (delta_size > 0) { + if (packet_it == received_packets_.end()) { + RTC_LOG(LS_ERROR) << "Failed to find delta for seq_no " << seq_no; + return false; + } + if (packet_it->sequence_number() != seq_no) { + RTC_LOG(LS_ERROR) << "Expected to find delta for seq_no " << seq_no + << ". Next delta is for " + << packet_it->sequence_number(); + return false; + } + if (delta_size == 1 && + (packet_it->delta_ticks() < 0 || packet_it->delta_ticks() > 0xff)) { + RTC_LOG(LS_ERROR) << "Delta " << packet_it->delta_ticks() + << " for seq_no " << seq_no + << " doesn't fit into one byte"; + return false; + } + timestamp += packet_it->delta(); + ++packet_it; + } + if (include_timestamps_) { + packet_size += delta_size; + } + ++seq_no; + } + if (packet_it != received_packets_.end()) { + RTC_LOG(LS_ERROR) << "Unencoded delta for seq_no " + << packet_it->sequence_number(); + return false; + } + if (timestamp != last_timestamp_) { + RTC_LOG(LS_ERROR) << "Last timestamp mismatch. Calculated: " + << ToLogString(timestamp) + << ". Saved: " << ToLogString(last_timestamp_); + return false; + } + if (size_bytes_ != packet_size) { + RTC_LOG(LS_ERROR) << "Rtcp packet size mismatch. Calculated: " + << packet_size << ". Saved: " << size_bytes_; + return false; + } + return true; +} + +size_t TransportFeedback::BlockLength() const { + // Round size_bytes_ up to multiple of 32bits. + return (size_bytes_ + 3) & (~static_cast<size_t>(3)); +} + +size_t TransportFeedback::PaddingLength() const { + return BlockLength() - size_bytes_; +} + +// Serialize packet. +bool TransportFeedback::Create(uint8_t* packet, + size_t* position, + size_t max_length, + PacketReadyCallback callback) const { + if (num_seq_no_ == 0) + return false; + + while (*position + BlockLength() > max_length) { + if (!OnBufferFull(packet, position, callback)) + return false; + } + const size_t position_end = *position + BlockLength(); + const size_t padding_length = PaddingLength(); + bool has_padding = padding_length > 0; + CreateHeader(kFeedbackMessageType, kPacketType, HeaderLength(), has_padding, + packet, position); + CreateCommonFeedback(packet + *position); + *position += kCommonFeedbackLength; + + ByteWriter<uint16_t>::WriteBigEndian(&packet[*position], base_seq_no_); + *position += 2; + + ByteWriter<uint16_t>::WriteBigEndian(&packet[*position], num_seq_no_); + *position += 2; + + ByteWriter<uint32_t, 3>::WriteBigEndian(&packet[*position], base_time_ticks_); + *position += 3; + + packet[(*position)++] = feedback_seq_; + + for (uint16_t chunk : encoded_chunks_) { + ByteWriter<uint16_t>::WriteBigEndian(&packet[*position], chunk); + *position += 2; + } + if (!last_chunk_.Empty()) { + uint16_t chunk = last_chunk_.EncodeLast(); + ByteWriter<uint16_t>::WriteBigEndian(&packet[*position], chunk); + *position += 2; + } + + if (include_timestamps_) { + for (const auto& received_packet : received_packets_) { + int16_t delta = received_packet.delta_ticks(); + if (delta >= 0 && delta <= 0xFF) { + packet[(*position)++] = delta; + } else { + ByteWriter<int16_t>::WriteBigEndian(&packet[*position], delta); + *position += 2; + } + } + } + + if (padding_length > 0) { + for (size_t i = 0; i < padding_length - 1; ++i) { + packet[(*position)++] = 0; + } + packet[(*position)++] = padding_length; + } + RTC_DCHECK_EQ(*position, position_end); + return true; +} + +void TransportFeedback::Clear() { + num_seq_no_ = 0; + last_timestamp_ = BaseTime(); + received_packets_.clear(); + all_packets_.clear(); + encoded_chunks_.clear(); + last_chunk_.Clear(); + size_bytes_ = kTransportFeedbackHeaderSizeBytes; +} + +bool TransportFeedback::AddDeltaSize(DeltaSize delta_size) { + if (num_seq_no_ == kMaxReportedPackets) + return false; + size_t add_chunk_size = last_chunk_.Empty() ? kChunkSizeBytes : 0; + if (size_bytes_ + delta_size + add_chunk_size > kMaxSizeBytes) + return false; + + if (last_chunk_.CanAdd(delta_size)) { + size_bytes_ += add_chunk_size; + last_chunk_.Add(delta_size); + ++num_seq_no_; + return true; + } + if (size_bytes_ + delta_size + kChunkSizeBytes > kMaxSizeBytes) + return false; + + encoded_chunks_.push_back(last_chunk_.Emit()); + size_bytes_ += kChunkSizeBytes; + last_chunk_.Add(delta_size); + ++num_seq_no_; + return true; +} + +bool TransportFeedback::AddMissingPackets(size_t num_missing_packets) { + size_t new_num_seq_no = num_seq_no_ + num_missing_packets; + if (new_num_seq_no > kMaxReportedPackets) { + return false; + } + + if (!last_chunk_.Empty()) { + while (num_missing_packets > 0 && last_chunk_.CanAdd(0)) { + last_chunk_.Add(0); + --num_missing_packets; + } + if (num_missing_packets == 0) { + num_seq_no_ = new_num_seq_no; + return true; + } + encoded_chunks_.push_back(last_chunk_.Emit()); + } + RTC_DCHECK(last_chunk_.Empty()); + size_t full_chunks = num_missing_packets / LastChunk::kMaxRunLengthCapacity; + size_t partial_chunk = num_missing_packets % LastChunk::kMaxRunLengthCapacity; + size_t num_chunks = full_chunks + (partial_chunk > 0 ? 1 : 0); + if (size_bytes_ + kChunkSizeBytes * num_chunks > kMaxSizeBytes) { + num_seq_no_ = (new_num_seq_no - num_missing_packets); + return false; + } + size_bytes_ += kChunkSizeBytes * num_chunks; + // T = 0, S = 0, run length = kMaxRunLengthCapacity, see EncodeRunLength(). + encoded_chunks_.insert(encoded_chunks_.end(), full_chunks, + LastChunk::kMaxRunLengthCapacity); + last_chunk_.AddMissingPackets(partial_chunk); + num_seq_no_ = new_num_seq_no; + return true; +} +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/transport_feedback.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/transport_feedback.h new file mode 100644 index 0000000000..4d17b54c3a --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/transport_feedback.h @@ -0,0 +1,185 @@ +/* + * 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. + */ + +#ifndef MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_TRANSPORT_FEEDBACK_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_TRANSPORT_FEEDBACK_H_ + +#include <array> +#include <memory> +#include <vector> + +#include "absl/base/attributes.h" +#include "api/function_view.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "modules/rtp_rtcp/source/rtcp_packet/rtpfb.h" + +namespace webrtc { +namespace rtcp { +class CommonHeader; + +class TransportFeedback : public Rtpfb { + public: + class ReceivedPacket { + public: + ReceivedPacket(uint16_t sequence_number, int16_t delta_ticks) + : sequence_number_(sequence_number), delta_ticks_(delta_ticks) {} + ReceivedPacket(const ReceivedPacket&) = default; + ReceivedPacket& operator=(const ReceivedPacket&) = default; + + uint16_t sequence_number() const { return sequence_number_; } + int16_t delta_ticks() const { return delta_ticks_; } + TimeDelta delta() const { return delta_ticks_ * kDeltaTick; } + + private: + uint16_t sequence_number_; + int16_t delta_ticks_; + }; + // TODO(sprang): IANA reg? + static constexpr uint8_t kFeedbackMessageType = 15; + // Convert to multiples of 0.25ms. + static constexpr TimeDelta kDeltaTick = TimeDelta::Micros(250); + // Maximum number of packets (including missing) TransportFeedback can report. + static constexpr size_t kMaxReportedPackets = 0xffff; + + TransportFeedback(); + + // If `include_timestamps` is set to false, the created packet will not + // contain the receive delta block. + explicit TransportFeedback(bool include_timestamps); + TransportFeedback(const TransportFeedback&); + TransportFeedback(TransportFeedback&&); + + ~TransportFeedback() override; + + void SetBase(uint16_t base_sequence, // Seq# of first packet in this msg. + Timestamp ref_timestamp); // Reference timestamp for this msg. + + void SetFeedbackSequenceNumber(uint8_t feedback_sequence); + // NOTE: This method requires increasing sequence numbers (excepting wraps). + bool AddReceivedPacket(uint16_t sequence_number, Timestamp timestamp); + const std::vector<ReceivedPacket>& GetReceivedPackets() const; + + // Calls `handler` for all packets this feedback describes. + // For received packets pass receieve time as `delta_since_base` since the + // `BaseTime()`. For missed packets calls `handler` with `delta_since_base = + // PlusInfinity()`. + void ForAllPackets( + rtc::FunctionView<void(uint16_t sequence_number, + TimeDelta delta_since_base)> handler) const; + + uint16_t GetBaseSequence() const; + + // Returns number of packets (including missing) this feedback describes. + size_t GetPacketStatusCount() const { return num_seq_no_; } + + // Get the reference time including any precision loss. + Timestamp BaseTime() const; + + // Get the unwrapped delta between current base time and `prev_timestamp`. + TimeDelta GetBaseDelta(Timestamp prev_timestamp) const; + + // Does the feedback packet contain timestamp information? + bool IncludeTimestamps() const { return include_timestamps_; } + + bool Parse(const CommonHeader& packet); + static std::unique_ptr<TransportFeedback> ParseFrom(const uint8_t* buffer, + size_t length); + // Pre and postcondition for all public methods. Should always return true. + // This function is for tests. + bool IsConsistent() const; + + size_t BlockLength() const override; + size_t PaddingLength() const; + + bool Create(uint8_t* packet, + size_t* position, + size_t max_length, + PacketReadyCallback callback) const override; + + private: + // Size in bytes of a delta time in rtcp packet. + // Valid values are 0 (packet wasn't received), 1 or 2. + using DeltaSize = uint8_t; + // Keeps DeltaSizes that can be encoded into single chunk if it is last chunk. + class LastChunk { + public: + using DeltaSize = TransportFeedback::DeltaSize; + static constexpr size_t kMaxRunLengthCapacity = 0x1fff; + + LastChunk(); + + bool Empty() const; + void Clear(); + // Return if delta sizes still can be encoded into single chunk with added + // `delta_size`. + bool CanAdd(DeltaSize delta_size) const; + // Add `delta_size`, assumes `CanAdd(delta_size)`, + void Add(DeltaSize delta_size); + // Equivalent to calling Add(0) `num_missing` times. Assumes `Empty()`. + void AddMissingPackets(size_t num_missing); + + // Encode chunk as large as possible removing encoded delta sizes. + // Assume CanAdd() == false for some valid delta_size. + uint16_t Emit(); + // Encode all stored delta_sizes into single chunk, pad with 0s if needed. + uint16_t EncodeLast() const; + + // Decode up to `max_size` delta sizes from `chunk`. + void Decode(uint16_t chunk, size_t max_size); + // Appends content of the Lastchunk to `deltas`. + void AppendTo(std::vector<DeltaSize>* deltas) const; + + private: + static constexpr size_t kMaxOneBitCapacity = 14; + static constexpr size_t kMaxTwoBitCapacity = 7; + static constexpr size_t kMaxVectorCapacity = kMaxOneBitCapacity; + static constexpr DeltaSize kLarge = 2; + + uint16_t EncodeOneBit() const; + void DecodeOneBit(uint16_t chunk, size_t max_size); + + uint16_t EncodeTwoBit(size_t size) const; + void DecodeTwoBit(uint16_t chunk, size_t max_size); + + uint16_t EncodeRunLength() const; + void DecodeRunLength(uint16_t chunk, size_t max_size); + + std::array<DeltaSize, kMaxVectorCapacity> delta_sizes_; + size_t size_; + bool all_same_; + bool has_large_delta_; + }; + + // Reset packet to consistent empty state. + void Clear(); + + bool AddDeltaSize(DeltaSize delta_size); + // Adds `num_missing_packets` deltas of size 0. + bool AddMissingPackets(size_t num_missing_packets); + + uint16_t base_seq_no_; + uint16_t num_seq_no_; + uint32_t base_time_ticks_; + uint8_t feedback_seq_; + bool include_timestamps_; + + Timestamp last_timestamp_; + std::vector<ReceivedPacket> received_packets_; + std::vector<ReceivedPacket> all_packets_; + // All but last encoded packet chunks. + std::vector<uint16_t> encoded_chunks_; + LastChunk last_chunk_; + size_t size_bytes_; +}; + +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_TRANSPORT_FEEDBACK_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/transport_feedback_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/transport_feedback_unittest.cc new file mode 100644 index 0000000000..356d7a2340 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/transport_feedback_unittest.cc @@ -0,0 +1,667 @@ +/* + * 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::InSequence; +using ::testing::MockFunction; +using ::testing::Ne; +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; + EXPECT_TRUE(header.Parse(buffer.data(), buffer.size())); + EXPECT_EQ(header.type(), TransportFeedback::kPacketType); + EXPECT_EQ(header.fmt(), TransportFeedback::kFeedbackMessageType); + TransportFeedback feedback; + EXPECT_TRUE(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; + } + ASSERT_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_CHECK(!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)); + + MockFunction<void(uint16_t, TimeDelta)> handler; + InSequence s; + EXPECT_CALL(handler, Call(kBaseSeqNo + 0, Ne(TimeDelta::PlusInfinity()))); + EXPECT_CALL(handler, Call(kBaseSeqNo + 1, TimeDelta::PlusInfinity())); + EXPECT_CALL(handler, Call(kBaseSeqNo + 2, TimeDelta::PlusInfinity())); + EXPECT_CALL(handler, Call(kBaseSeqNo + 3, Ne(TimeDelta::PlusInfinity()))); + Parse(feedback_builder.Build()).ForAllPackets(handler.AsStdFunction()); +} + +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()); + + MockFunction<void(uint16_t, TimeDelta)> handler; + InSequence s; + EXPECT_CALL(handler, Call(kBaseSeqNo + 0, Ne(TimeDelta::PlusInfinity()))); + EXPECT_CALL(handler, Call(kBaseSeqNo + 1, TimeDelta::PlusInfinity())); + EXPECT_CALL(handler, Call(kBaseSeqNo + 2, TimeDelta::PlusInfinity())); + EXPECT_CALL(handler, Call(kBaseSeqNo + 3, Ne(TimeDelta::PlusInfinity()))); + Parse(feedback_builder.Build()).ForAllPackets(handler.AsStdFunction()); +} +} // namespace +} // namespace webrtc |