diff --git a/webrtc/common_types.cc b/webrtc/common_types.cc index 17bb265674..b9c4737aeb 100644 --- a/webrtc/common_types.cc +++ b/webrtc/common_types.cc @@ -10,8 +10,9 @@ #include "webrtc/common_types.h" -#include #include +#include +#include #include "webrtc/base/checks.h" #include "webrtc/base/stringutils.h" @@ -20,6 +21,20 @@ namespace webrtc { StreamDataCounters::StreamDataCounters() : first_packet_time_ms(-1) {} +constexpr size_t StreamId::kMaxSize; + +void StreamId::Set(const char* data, size_t size) { + // If |data| contains \0, the stream id size might become less than |size|. + RTC_DCHECK_LE(size, kMaxSize); + memcpy(value_, data, size); + if (size < kMaxSize) + value_[size] = 0; +} + +// StreamId is used as member of RTPHeader that is sometimes copied with memcpy +// and thus assume trivial destructibility. +static_assert(std::is_trivially_destructible::value, ""); + RTPHeaderExtension::RTPHeaderExtension() : hasTransmissionTimeOffset(false), transmissionTimeOffset(0), diff --git a/webrtc/common_types.h b/webrtc/common_types.h index 750420196d..06733be168 100644 --- a/webrtc/common_types.h +++ b/webrtc/common_types.h @@ -20,6 +20,7 @@ #include "webrtc/api/video/video_content_type.h" #include "webrtc/api/video/video_rotation.h" +#include "webrtc/base/array_view.h" #include "webrtc/base/checks.h" #include "webrtc/base/optional.h" #include "webrtc/typedefs.h" @@ -695,6 +696,42 @@ struct PlayoutDelay { int max_ms; }; +// Class to represent RtpStreamId which is a string. +// Unlike std::string, it can be copied with memcpy and cleared with memset. +// Empty value represent unset RtpStreamId. +class StreamId { + public: + // Stream id is limited to 16 bytes because it is the maximum length + // that can be encoded with one-byte header extensions. + static constexpr size_t kMaxSize = 16; + + StreamId() { value_[0] = 0; } + explicit StreamId(rtc::ArrayView value) { + Set(value.data(), value.size()); + } + StreamId(const StreamId&) = default; + StreamId& operator=(const StreamId&) = default; + + bool empty() const { return value_[0] == 0; } + const char* data() const { return value_; } + size_t size() const { return strnlen(value_, kMaxSize); } + + void Set(rtc::ArrayView value) { + Set(reinterpret_cast(value.data()), value.size()); + } + void Set(const char* data, size_t size); + + friend bool operator==(const StreamId& lhs, const StreamId& rhs) { + return strncmp(lhs.value_, rhs.value_, kMaxSize) == 0; + } + friend bool operator!=(const StreamId& lhs, const StreamId& rhs) { + return !(lhs == rhs); + } + + private: + char value_[kMaxSize]; +}; + struct RTPHeaderExtension { RTPHeaderExtension(); @@ -723,6 +760,12 @@ struct RTPHeaderExtension { VideoContentType videoContentType; PlayoutDelay playout_delay = {-1, -1}; + + // For identification of a stream when ssrc is not signaled. See + // https://tools.ietf.org/html/draft-ietf-avtext-rid-09 + // TODO(danilchap): Update url from draft to release version. + StreamId stream_id; + StreamId repaired_stream_id; }; struct RTPHeader { diff --git a/webrtc/modules/rtp_rtcp/include/rtp_rtcp_defines.h b/webrtc/modules/rtp_rtcp/include/rtp_rtcp_defines.h index 56aa9bd777..11bc9433a4 100644 --- a/webrtc/modules/rtp_rtcp/include/rtp_rtcp_defines.h +++ b/webrtc/modules/rtp_rtcp/include/rtp_rtcp_defines.h @@ -77,6 +77,8 @@ enum RTPExtensionType { kRtpExtensionTransportSequenceNumber, kRtpExtensionPlayoutDelay, kRtpExtensionVideoContentType, + kRtpExtensionRtpStreamId, + kRtpExtensionRepairedRtpStreamId, kRtpExtensionNumberOfExtensions // Must be the last entity in the enum. }; diff --git a/webrtc/modules/rtp_rtcp/source/rtp_header_extension.cc b/webrtc/modules/rtp_rtcp/source/rtp_header_extension.cc index 1d3925931a..26762b0e96 100644 --- a/webrtc/modules/rtp_rtcp/source/rtp_header_extension.cc +++ b/webrtc/modules/rtp_rtcp/source/rtp_header_extension.cc @@ -40,6 +40,8 @@ constexpr ExtensionInfo kExtensions[] = { CreateExtensionInfo(), CreateExtensionInfo(), CreateExtensionInfo(), + CreateExtensionInfo(), + CreateExtensionInfo(), }; // Because of kRtpExtensionNone, NumberOfExtension is 1 bigger than the actual diff --git a/webrtc/modules/rtp_rtcp/source/rtp_header_extensions.cc b/webrtc/modules/rtp_rtcp/source/rtp_header_extensions.cc index 8141f02290..13f488b09a 100644 --- a/webrtc/modules/rtp_rtcp/source/rtp_header_extensions.cc +++ b/webrtc/modules/rtp_rtcp/source/rtp_header_extensions.cc @@ -244,4 +244,44 @@ bool VideoContentTypeExtension::Write(uint8_t* data, return true; } +// RtpStreamId. +constexpr RTPExtensionType RtpStreamId::kId; +constexpr uint8_t RtpStreamId::kValueSizeBytes; +constexpr const char* RtpStreamId::kUri; + +bool RtpStreamId::Parse(rtc::ArrayView data, StreamId* rsid) { + if (data.empty() || data[0] == 0) // Valid rsid can't be empty. + return false; + rsid->Set(data); + RTC_DCHECK(!rsid->empty()); + return true; +} + +bool RtpStreamId::Parse(rtc::ArrayView data, std::string* rsid) { + if (data.empty() || data[0] == 0) // Valid rsid can't be empty. + return false; + const char* str = reinterpret_cast(data.data()); + // If there is a \0 character in the middle of the |data|, treat it as end of + // the string. Well-formed rsid shouldn't contain it. + rsid->assign(str, strnlen(str, data.size())); + RTC_DCHECK(!rsid->empty()); + return true; +} + +// RepairedRtpStreamId. +constexpr RTPExtensionType RepairedRtpStreamId::kId; +constexpr uint8_t RepairedRtpStreamId::kValueSizeBytes; +constexpr const char* RepairedRtpStreamId::kUri; + +// RtpStreamId and RepairedRtpStreamId use the same format to store rsid. +bool RepairedRtpStreamId::Parse(rtc::ArrayView data, + StreamId* rsid) { + return RtpStreamId::Parse(data, rsid); +} + +bool RepairedRtpStreamId::Parse(rtc::ArrayView data, + std::string* rsid) { + return RtpStreamId::Parse(data, rsid); +} + } // namespace webrtc diff --git a/webrtc/modules/rtp_rtcp/source/rtp_header_extensions.h b/webrtc/modules/rtp_rtcp/source/rtp_header_extensions.h index 0d30848f96..c60f29050d 100644 --- a/webrtc/modules/rtp_rtcp/source/rtp_header_extensions.h +++ b/webrtc/modules/rtp_rtcp/source/rtp_header_extensions.h @@ -11,6 +11,7 @@ #define WEBRTC_MODULES_RTP_RTCP_SOURCE_RTP_HEADER_EXTENSIONS_H_ #include +#include #include "webrtc/api/video/video_content_type.h" #include "webrtc/api/video/video_rotation.h" @@ -111,5 +112,31 @@ class VideoContentTypeExtension { static bool Write(uint8_t* data, VideoContentType content_type); }; +class RtpStreamId { + public: + static constexpr RTPExtensionType kId = kRtpExtensionRtpStreamId; + // TODO(danilchap): Implement write support of dynamic size extension that + // allows to remove the ValueSize constant. + static constexpr uint8_t kValueSizeBytes = 1; + static constexpr const char* kUri = + "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id"; + + static bool Parse(rtc::ArrayView data, StreamId* rid); + static bool Parse(rtc::ArrayView data, std::string* rid); +}; + +class RepairedRtpStreamId { + public: + static constexpr RTPExtensionType kId = kRtpExtensionRepairedRtpStreamId; + // TODO(danilchap): Implement write support of dynamic size extension that + // allows to remove the ValueSize constant. + static constexpr uint8_t kValueSizeBytes = 1; + static constexpr const char* kUri = + "urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id"; + + static bool Parse(rtc::ArrayView data, StreamId* rid); + static bool Parse(rtc::ArrayView data, std::string* rid); +}; + } // namespace webrtc #endif // WEBRTC_MODULES_RTP_RTCP_SOURCE_RTP_HEADER_EXTENSIONS_H_ diff --git a/webrtc/modules/rtp_rtcp/source/rtp_packet.cc b/webrtc/modules/rtp_rtcp/source/rtp_packet.cc index 2e875289de..19e898422f 100644 --- a/webrtc/modules/rtp_rtcp/source/rtp_packet.cc +++ b/webrtc/modules/rtp_rtcp/source/rtp_packet.cc @@ -172,6 +172,8 @@ void Packet::GetHeader(RTPHeader* header) const { header->extension.hasVideoContentType = GetExtension( &header->extension.videoContentType); + GetExtension(&header->extension.stream_id); + GetExtension(&header->extension.repaired_stream_id); } size_t Packet::headers_size() const { diff --git a/webrtc/modules/rtp_rtcp/source/rtp_packet_unittest.cc b/webrtc/modules/rtp_rtcp/source/rtp_packet_unittest.cc index b85d98ae09..bbe40808ae 100644 --- a/webrtc/modules/rtp_rtcp/source/rtp_packet_unittest.cc +++ b/webrtc/modules/rtp_rtcp/source/rtp_packet_unittest.cc @@ -364,6 +364,45 @@ TEST(RtpPacketTest, ParseWithoutExtensionManager) { EXPECT_EQ(time_offset, kTimeOffset); } +TEST(RtpPacketTest, ParseDynamicSizeExtension) { + // clang-format off + const uint8_t kPacket1[] = { + 0x90, kPayloadType, 0x00, kSeqNum, + 0x65, 0x43, 0x12, 0x78, // Timestamp. + 0x12, 0x34, 0x56, 0x78, // Ssrc. + 0xbe, 0xde, 0x00, 0x02, // Extensions block of size 2x32bit words. + 0x21, 'H', 'D', // Extension with id = 2, size = (1+1). + 0x12, 'r', 't', 'x', // Extension with id = 1, size = (2+1). + 0x00}; // Extension padding. + const uint8_t kPacket2[] = { + 0x90, kPayloadType, 0x00, kSeqNum, + 0x65, 0x43, 0x12, 0x78, // Timestamp. + 0x12, 0x34, 0x56, 0x79, // Ssrc. + 0xbe, 0xde, 0x00, 0x01, // Extensions block of size 1x32bit words. + 0x11, 'H', 'D', // Extension with id = 1, size = (1+1). + 0x00}; // Extension padding. + // clang-format on + RtpPacketReceived::ExtensionManager extensions; + extensions.Register(1); + extensions.Register(2); + RtpPacketReceived packet(&extensions); + ASSERT_TRUE(packet.Parse(kPacket1, sizeof(kPacket1))); + + std::string rsid; + EXPECT_TRUE(packet.GetExtension(&rsid)); + EXPECT_EQ(rsid, "rtx"); + + std::string repaired_rsid; + EXPECT_TRUE(packet.GetExtension(&repaired_rsid)); + EXPECT_EQ(repaired_rsid, "HD"); + + // Parse another packet with RtpStreamId extension of different size. + ASSERT_TRUE(packet.Parse(kPacket2, sizeof(kPacket2))); + EXPECT_TRUE(packet.GetExtension(&rsid)); + EXPECT_EQ(rsid, "HD"); + EXPECT_FALSE(packet.GetExtension(&repaired_rsid)); +} + TEST(RtpPacketTest, RawExtensionFunctionsAcceptZeroIdAndReturnFalse) { RtpPacketReceived::ExtensionManager extensions; RtpPacketReceived packet(&extensions); diff --git a/webrtc/modules/rtp_rtcp/source/rtp_utility.cc b/webrtc/modules/rtp_rtcp/source/rtp_utility.cc index 1c12c89c92..d003c0bdb0 100644 --- a/webrtc/modules/rtp_rtcp/source/rtp_utility.cc +++ b/webrtc/modules/rtp_rtcp/source/rtp_utility.cc @@ -469,6 +469,15 @@ void RtpHeaderParser::ParseOneByteExtensionHeader( } break; } + case kRtpExtensionRtpStreamId: { + header->extension.stream_id.Set(rtc::MakeArrayView(ptr, len + 1)); + break; + } + case kRtpExtensionRepairedRtpStreamId: { + header->extension.repaired_stream_id.Set( + rtc::MakeArrayView(ptr, len + 1)); + break; + } case kRtpExtensionNone: case kRtpExtensionNumberOfExtensions: { RTC_NOTREACHED() << "Invalid extension type: " << type; diff --git a/webrtc/modules/rtp_rtcp/source/rtp_utility_unittest.cc b/webrtc/modules/rtp_rtcp/source/rtp_utility_unittest.cc index 80f22fc613..dcaa242148 100644 --- a/webrtc/modules/rtp_rtcp/source/rtp_utility_unittest.cc +++ b/webrtc/modules/rtp_rtcp/source/rtp_utility_unittest.cc @@ -148,21 +148,23 @@ TEST(RtpHeaderParser, ParseWithOverSizedExtension) { EXPECT_EQ(sizeof(kPacket), header.headerLength); } -TEST(RtpHeaderParser, ParseAll6Extensions) { +TEST(RtpHeaderParser, ParseAll8Extensions) { const uint8_t kAudioLevel = 0x5a; // clang-format off const uint8_t kPacket[] = { 0x90, kPayloadType, 0x00, kSeqNum, 0x65, 0x43, 0x12, 0x78, // kTimestamp. 0x12, 0x34, 0x56, 0x78, // kSsrc. - 0xbe, 0xde, 0x00, 0x05, // Extension of size 5x32bit word. + 0xbe, 0xde, 0x00, 0x08, // Extension of size 8x32bit words. 0x40, 0x80|kAudioLevel, // AudioLevel. 0x22, 0x01, 0x56, 0xce, // TransmissionOffset. 0x62, 0x12, 0x34, 0x56, // AbsoluteSendTime. 0x81, 0xce, 0xab, // TransportSequenceNumber. 0xa0, 0x03, // VideoRotation. 0xb2, 0x12, 0x48, 0x76, // PlayoutDelayLimits. - 0x00, // Padding to 32bit boundary. + 0xc2, 'r', 't', 'x', // RtpStreamId + 0xd5, 's', 't', 'r', 'e', 'a', 'm', // RepairedRtpStreamId + 0x00, 0x00, // Padding to 32bit boundary. }; // clang-format on ASSERT_EQ(sizeof(kPacket) % 4, 0u); @@ -174,6 +176,8 @@ TEST(RtpHeaderParser, ParseAll6Extensions) { extensions.Register(8); extensions.Register(0xa); extensions.Register(0xb); + extensions.Register(0xc); + extensions.Register(0xd); RtpUtility::RtpHeaderParser parser(kPacket, sizeof(kPacket)); RTPHeader header; @@ -199,6 +203,33 @@ TEST(RtpHeaderParser, ParseAll6Extensions) { header.extension.playout_delay.min_ms); EXPECT_EQ(0x876 * PlayoutDelayLimits::kGranularityMs, header.extension.playout_delay.max_ms); + EXPECT_EQ(header.extension.stream_id, StreamId("rtx")); + EXPECT_EQ(header.extension.repaired_stream_id, StreamId("stream")); +} + +TEST(RtpHeaderParser, ParseMalformedRsidExtensions) { + // clang-format off + const uint8_t kPacket[] = { + 0x90, kPayloadType, 0x00, kSeqNum, + 0x65, 0x43, 0x12, 0x78, // kTimestamp. + 0x12, 0x34, 0x56, 0x78, // kSsrc. + 0xbe, 0xde, 0x00, 0x03, // Extension of size 3x32bit words. + 0xc2, '\0', 't', 'x', // empty RtpStreamId + 0xd5, 's', 't', 'r', '\0', 'a', 'm', // RepairedRtpStreamId + 0x00, // Padding to 32bit boundary. + }; + // clang-format on + ASSERT_EQ(sizeof(kPacket) % 4, 0u); + + RtpHeaderExtensionMap extensions; + extensions.Register(0xc); + extensions.Register(0xd); + RtpUtility::RtpHeaderParser parser(kPacket, sizeof(kPacket)); + RTPHeader header; + + EXPECT_TRUE(parser.Parse(&header, &extensions)); + EXPECT_TRUE(header.extension.stream_id.empty()); + EXPECT_EQ(header.extension.repaired_stream_id, StreamId("str")); } TEST(RtpHeaderParser, ParseWithCsrcsExtensionAndPadding) { diff --git a/webrtc/test/fuzzers/rtp_header_fuzzer.cc b/webrtc/test/fuzzers/rtp_header_fuzzer.cc index 914a8fb949..9e111a80f2 100644 --- a/webrtc/test/fuzzers/rtp_header_fuzzer.cc +++ b/webrtc/test/fuzzers/rtp_header_fuzzer.cc @@ -15,30 +15,31 @@ #include "webrtc/modules/rtp_rtcp/source/rtp_utility.h" namespace webrtc { -// We decide which header extensions to register by reading one byte +// We decide which header extensions to register by reading two bytes // from the beginning of |data| and interpreting it as a bitmask over -// the RTPExtensionType enum. This assert ensures one byte is enough. -static_assert(kRtpExtensionNumberOfExtensions <= 8, +// the RTPExtensionType enum. This assert ensures two bytes are enough. +static_assert(kRtpExtensionNumberOfExtensions <= 16, "Insufficient bits read to configure all header extensions. Add " "an extra byte and update the switches."); void FuzzOneInput(const uint8_t* data, size_t size) { - if (size <= 1) + if (size <= 2) return; // Don't use the configuration byte as part of the packet. - std::bitset<8> extensionMask(data[0]); - data++; - size--; + std::bitset<16> extensionMask(*reinterpret_cast(data)); + data += 2; + size -= 2; RtpPacketReceived::ExtensionManager extensions; + // Skip i = 0 since it maps to ExtensionNone and extension id = 0 is invalid. for (int i = 0; i < kRtpExtensionNumberOfExtensions; i++) { RTPExtensionType extension_type = static_cast(i); if (extensionMask[i] && extension_type != kRtpExtensionNone) { // Extensions are registered with an ID, which you signal to the // peer so they know what to expect. This code only cares about // parsing so the value of the ID isn't relevant; we use i. - extensions.Register(extension_type, i); + extensions.RegisterByType(i, extension_type); } } diff --git a/webrtc/test/fuzzers/rtp_packet_fuzzer.cc b/webrtc/test/fuzzers/rtp_packet_fuzzer.cc index 7cf65cf655..64ab6ecd9e 100644 --- a/webrtc/test/fuzzers/rtp_packet_fuzzer.cc +++ b/webrtc/test/fuzzers/rtp_packet_fuzzer.cc @@ -14,30 +14,31 @@ namespace webrtc { -// We decide which header extensions to register by reading one byte +// We decide which header extensions to register by reading two bytes // from the beginning of |data| and interpreting it as a bitmask over -// the RTPExtensionType enum. This assert ensures one byte is enough. -static_assert(kRtpExtensionNumberOfExtensions <= 8, +// the RTPExtensionType enum. This assert ensures two bytes are enough. +static_assert(kRtpExtensionNumberOfExtensions <= 16, "Insufficient bits read to configure all header extensions. Add " "an extra byte and update the switches."); void FuzzOneInput(const uint8_t* data, size_t size) { - if (size <= 1) + if (size <= 2) return; - // Don't use the configuration byte as part of the packet. - std::bitset<8> extensionMask(data[0]); - data++; - size--; + // Don't use the configuration bytes as part of the packet. + std::bitset<16> extensionMask(*reinterpret_cast(data)); + data += 2; + size -= 2; RtpPacketReceived::ExtensionManager extensions; - for (int i = 0; i < kRtpExtensionNumberOfExtensions; i++) { + // Skip i = 0 since it maps to ExtensionNone and extension id = 0 is invalid. + for (int i = 1; i < kRtpExtensionNumberOfExtensions; i++) { RTPExtensionType extension_type = static_cast(i); if (extensionMask[i] && extension_type != kRtpExtensionNone) { // Extensions are registered with an ID, which you signal to the // peer so they know what to expect. This code only cares about // parsing so the value of the ID isn't relevant; we use i. - extensions.Register(extension_type, i); + extensions.RegisterByType(i, extension_type); } } @@ -89,6 +90,16 @@ void FuzzOneInput(const uint8_t* data, size_t size) { VideoContentType content_type; packet.GetExtension(&content_type); break; + case kRtpExtensionRtpStreamId: { + std::string rsid; + packet.GetExtension(&rsid); + break; + } + case kRtpExtensionRepairedRtpStreamId: { + std::string rsid; + packet.GetExtension(&rsid); + break; + } } } }