diff --git a/api/rtpparameters.cc b/api/rtpparameters.cc index af11770995..98ced9b3ea 100644 --- a/api/rtpparameters.cc +++ b/api/rtpparameters.cc @@ -142,7 +142,9 @@ const char RtpExtension::kEncryptHeaderExtensionsUri[] = constexpr int RtpExtension::kMinId; constexpr int RtpExtension::kMaxId; +constexpr int RtpExtension::kMaxValueSize; constexpr int RtpExtension::kOneByteHeaderExtensionMaxId; +constexpr int RtpExtension::kOneByteHeaderExtensionMaxValueSize; bool RtpExtension::IsSupportedForAudio(const std::string& uri) { return uri == webrtc::RtpExtension::kAudioLevelUri || diff --git a/api/rtpparameters.h b/api/rtpparameters.h index f78b644880..678ac02c4b 100644 --- a/api/rtpparameters.h +++ b/api/rtpparameters.h @@ -311,7 +311,9 @@ struct RtpExtension { // header extensions, per RFC8285 Section 4.2-4.3. static constexpr int kMinId = 1; static constexpr int kMaxId = 255; + static constexpr int kMaxValueSize = 255; static constexpr int kOneByteHeaderExtensionMaxId = 14; + static constexpr int kOneByteHeaderExtensionMaxValueSize = 16; std::string uri; int id = 0; diff --git a/modules/rtp_rtcp/include/rtp_header_extension_map.h b/modules/rtp_rtcp/include/rtp_header_extension_map.h index 75a6720bb9..b8f27a1312 100644 --- a/modules/rtp_rtcp/include/rtp_header_extension_map.h +++ b/modules/rtp_rtcp/include/rtp_header_extension_map.h @@ -53,10 +53,18 @@ class RtpHeaderExtensionMap { } int32_t Deregister(RTPExtensionType type); + bool IsMixedOneTwoByteHeaderSupported() const { + return mixed_one_two_byte_header_supported_; + } + void SetMixedOneTwoByteHeaderSupported(bool supported) { + mixed_one_two_byte_header_supported_ = supported; + } + private: bool Register(int id, RTPExtensionType type, const char* uri); uint8_t ids_[kRtpExtensionNumberOfExtensions]; + bool mixed_one_two_byte_header_supported_; }; } // namespace webrtc diff --git a/modules/rtp_rtcp/source/rtp_header_extension_map.cc b/modules/rtp_rtcp/source/rtp_header_extension_map.cc index 338cd9edb8..168665afc4 100644 --- a/modules/rtp_rtcp/source/rtp_header_extension_map.cc +++ b/modules/rtp_rtcp/source/rtp_header_extension_map.cc @@ -56,7 +56,8 @@ static_assert(arraysize(kExtensions) == constexpr RTPExtensionType RtpHeaderExtensionMap::kInvalidType; constexpr int RtpHeaderExtensionMap::kInvalidId; -RtpHeaderExtensionMap::RtpHeaderExtensionMap() { +RtpHeaderExtensionMap::RtpHeaderExtensionMap() + : mixed_one_two_byte_header_supported_(false) { for (auto& id : ids_) id = kInvalidId; } diff --git a/modules/rtp_rtcp/source/rtp_header_extension_size.cc b/modules/rtp_rtcp/source/rtp_header_extension_size.cc index 5edf120b56..f20ca00d71 100644 --- a/modules/rtp_rtcp/source/rtp_header_extension_size.cc +++ b/modules/rtp_rtcp/source/rtp_header_extension_size.cc @@ -18,8 +18,6 @@ int RtpHeaderExtensionSize(rtc::ArrayView extensions, const RtpHeaderExtensionMap& registered_extensions) { // RFC3550 Section 5.3.1 static constexpr int kExtensionBlockHeaderSize = 4; - // RFC8285 Section 4.2-4.3. - static constexpr int kOneByteHeaderExtensionMaxValueSize = 16; int values_size = 0; int num_extensions = 0; @@ -31,7 +29,8 @@ int RtpHeaderExtensionSize(rtc::ArrayView extensions, // All extensions should use same size header. Check if the |extension| // forces to switch to two byte header that allows larger id and value size. if (id > RtpExtension::kOneByteHeaderExtensionMaxId || - extension.value_size > kOneByteHeaderExtensionMaxValueSize) { + extension.value_size > + RtpExtension::kOneByteHeaderExtensionMaxValueSize) { each_extension_header_size = 2; } values_size += extension.value_size; diff --git a/modules/rtp_rtcp/source/rtp_header_extensions.cc b/modules/rtp_rtcp/source/rtp_header_extensions.cc index f5aa38fd59..fc4b7ceb92 100644 --- a/modules/rtp_rtcp/source/rtp_header_extensions.cc +++ b/modules/rtp_rtcp/source/rtp_header_extensions.cc @@ -462,9 +462,11 @@ bool BaseRtpStringExtension::Parse(rtc::ArrayView data, bool BaseRtpStringExtension::Write(rtc::ArrayView data, const std::string& str) { + if (str.size() > StringRtpHeaderExtension::kMaxSize) { + return false; + } RTC_DCHECK_EQ(data.size(), str.size()); RTC_DCHECK_GE(str.size(), 1); - RTC_DCHECK_LE(str.size(), StringRtpHeaderExtension::kMaxSize); memcpy(data.data(), str.data(), str.size()); return true; } diff --git a/modules/rtp_rtcp/source/rtp_packet.cc b/modules/rtp_rtcp/source/rtp_packet.cc index 4df25c3d4d..0b9ed80af5 100644 --- a/modules/rtp_rtcp/source/rtp_packet.cc +++ b/modules/rtp_rtcp/source/rtp_packet.cc @@ -178,10 +178,9 @@ void RtpPacket::SetCsrcs(rtc::ArrayView csrcs) { rtc::ArrayView RtpPacket::AllocateRawExtension(int id, size_t length) { RTC_DCHECK_GE(id, RtpExtension::kMinId); - RTC_DCHECK_LE(id, RtpExtension::kOneByteHeaderExtensionMaxId); + RTC_DCHECK_LE(id, RtpExtension::kMaxId); RTC_DCHECK_GE(length, 1); - RTC_DCHECK_LE(length, 16); - + RTC_DCHECK_LE(length, RtpExtension::kMaxValueSize); const ExtensionInfo* extension_entry = FindExtensionInfo(id); if (extension_entry != nullptr) { // Extension already reserved. Check if same length is used. @@ -205,10 +204,51 @@ rtc::ArrayView RtpPacket::AllocateRawExtension(int id, size_t length) { return nullptr; } - size_t num_csrc = data()[0] & 0x0F; - size_t extensions_offset = kFixedHeaderSize + (num_csrc * 4) + 4; + const size_t num_csrc = data()[0] & 0x0F; + const size_t extensions_offset = kFixedHeaderSize + (num_csrc * 4) + 4; + // Determine if two-byte header is required for the extension based on id and + // length. Please note that a length of 0 also requires two-byte header + // extension. See RFC8285 Section 4.2-4.3. + const bool two_byte_header_required = + id > RtpExtension::kOneByteHeaderExtensionMaxId || + length > RtpExtension::kOneByteHeaderExtensionMaxValueSize || length == 0; + RTC_CHECK(!two_byte_header_required || + extensions_.IsMixedOneTwoByteHeaderSupported()); + + uint16_t profile_id; + if (extensions_size_ > 0) { + profile_id = + ByteReader::ReadBigEndian(data() + extensions_offset - 4); + if (profile_id == kOneByteExtensionProfileId && two_byte_header_required) { + // Is buffer size big enough to fit promotion and new data field? + // The header extension will grow with one byte per already allocated + // extension + the size of the extension that is about to be allocated. + size_t expected_new_extensions_size = + extensions_size_ + extension_entries_.size() + + kTwoByteExtensionHeaderLength + length; + if (extensions_offset + expected_new_extensions_size > capacity()) { + RTC_LOG(LS_ERROR) + << "Extension cannot be registered: Not enough space left in " + "buffer to change to two-byte header extension and add new " + "extension."; + return nullptr; + } + // Promote already written data to two-byte header format. + PromoteToTwoByteHeaderExtension(); + profile_id = kTwoByteExtensionProfileId; + } + } else { + // Profile specific ID, set to OneByteExtensionHeader unless + // TwoByteExtensionHeader is required. + profile_id = two_byte_header_required ? kTwoByteExtensionProfileId + : kOneByteExtensionProfileId; + } + + const size_t extension_header_size = profile_id == kOneByteExtensionProfileId + ? kOneByteExtensionHeaderLength + : kTwoByteExtensionHeaderLength; size_t new_extensions_size = - extensions_size_ + kOneByteExtensionHeaderLength + length; + extensions_size_ + extension_header_size + length; if (extensions_offset + new_extensions_size > capacity()) { RTC_LOG(LS_ERROR) << "Extension cannot be registered: Not enough space left in buffer."; @@ -219,22 +259,76 @@ rtc::ArrayView RtpPacket::AllocateRawExtension(int id, size_t length) { if (extensions_size_ == 0) { RTC_DCHECK_EQ(payload_offset_, kFixedHeaderSize + (num_csrc * 4)); WriteAt(0, data()[0] | 0x10); // Set extension bit. - // Profile specific ID always set to OneByteExtensionHeader. ByteWriter::WriteBigEndian(WriteAt(extensions_offset - 4), - kOneByteExtensionProfileId); + profile_id); } - uint8_t one_byte_header = rtc::dchecked_cast(id) << 4; - one_byte_header |= rtc::dchecked_cast(length - 1); - WriteAt(extensions_offset + extensions_size_, one_byte_header); + if (profile_id == kOneByteExtensionProfileId) { + uint8_t one_byte_header = rtc::dchecked_cast(id) << 4; + one_byte_header |= rtc::dchecked_cast(length - 1); + WriteAt(extensions_offset + extensions_size_, one_byte_header); + } else { + // TwoByteHeaderExtension. + uint8_t extension_id = rtc::dchecked_cast(id); + WriteAt(extensions_offset + extensions_size_, extension_id); + uint8_t extension_length = rtc::dchecked_cast(length); + WriteAt(extensions_offset + extensions_size_ + 1, extension_length); + } const uint16_t extension_info_offset = rtc::dchecked_cast( - extensions_offset + extensions_size_ + kOneByteExtensionHeaderLength); + extensions_offset + extensions_size_ + extension_header_size); const uint8_t extension_info_length = rtc::dchecked_cast(length); extension_entries_.emplace_back(id, extension_info_length, extension_info_offset); + extensions_size_ = new_extensions_size; + uint16_t extensions_size_padded = + SetExtensionLengthMaybeAddZeroPadding(extensions_offset); + payload_offset_ = extensions_offset + extensions_size_padded; + buffer_.SetSize(payload_offset_); + return rtc::MakeArrayView(WriteAt(extension_info_offset), + extension_info_length); +} + +void RtpPacket::PromoteToTwoByteHeaderExtension() { + size_t num_csrc = data()[0] & 0x0F; + size_t extensions_offset = kFixedHeaderSize + (num_csrc * 4) + 4; + + RTC_CHECK_GT(extension_entries_.size(), 0); + RTC_CHECK_EQ(payload_size_, 0); + RTC_CHECK_EQ(kOneByteExtensionProfileId, ByteReader::ReadBigEndian( + data() + extensions_offset - 4)); + // Rewrite data. + // Each extension adds one to the offset. The write-read delta for the last + // extension is therefore the same as the number of extension entries. + size_t write_read_delta = extension_entries_.size(); + for (auto extension_entry = extension_entries_.rbegin(); + extension_entry != extension_entries_.rend(); ++extension_entry) { + size_t read_index = extension_entry->offset; + size_t write_index = read_index + write_read_delta; + // Update offset. + extension_entry->offset = rtc::dchecked_cast(write_index); + // Copy data. Use memmove since read/write regions may overlap. + memmove(WriteAt(write_index), data() + read_index, extension_entry->length); + // Rewrite id and length. + WriteAt(--write_index, extension_entry->length); + WriteAt(--write_index, extension_entry->id); + --write_read_delta; + } + + // Update profile header, extensions length, and zero padding. + ByteWriter::WriteBigEndian(WriteAt(extensions_offset - 4), + kTwoByteExtensionProfileId); + extensions_size_ += extension_entries_.size(); + uint16_t extensions_size_padded = + SetExtensionLengthMaybeAddZeroPadding(extensions_offset); + payload_offset_ = extensions_offset + extensions_size_padded; + buffer_.SetSize(payload_offset_); +} + +uint16_t RtpPacket::SetExtensionLengthMaybeAddZeroPadding( + size_t extensions_offset) { // Update header length field. uint16_t extensions_words = rtc::dchecked_cast( (extensions_size_ + 3) / 4); // Wrap up to 32bit. @@ -244,10 +338,7 @@ rtc::ArrayView RtpPacket::AllocateRawExtension(int id, size_t length) { size_t extension_padding_size = 4 * extensions_words - extensions_size_; memset(WriteAt(extensions_offset + extensions_size_), 0, extension_padding_size); - payload_offset_ = extensions_offset + 4 * extensions_words; - buffer_.SetSize(payload_offset_); - return rtc::MakeArrayView(WriteAt(extension_info_offset), - extension_info_length); + return 4 * extensions_words; } uint8_t* RtpPacket::AllocatePayload(size_t size_bytes) { @@ -463,11 +554,22 @@ rtc::ArrayView RtpPacket::FindExtension( rtc::ArrayView RtpPacket::AllocateExtension(ExtensionType type, size_t length) { + // TODO(webrtc:7990): Add support for empty extensions (length==0). + if (length == 0 || length > RtpExtension::kMaxValueSize || + (!extensions_.IsMixedOneTwoByteHeaderSupported() && + length > RtpExtension::kOneByteHeaderExtensionMaxValueSize)) { + return nullptr; + } + uint8_t id = extensions_.GetId(type); if (id == ExtensionManager::kInvalidId) { // Extension not registered. return nullptr; } + if (!extensions_.IsMixedOneTwoByteHeaderSupported() && + id > RtpExtension::kOneByteHeaderExtensionMaxId) { + return nullptr; + } return AllocateRawExtension(id, length); } diff --git a/modules/rtp_rtcp/source/rtp_packet.h b/modules/rtp_rtcp/source/rtp_packet.h index 9aea1e6d31..15c3865b35 100644 --- a/modules/rtp_rtcp/source/rtp_packet.h +++ b/modules/rtp_rtcp/source/rtp_packet.h @@ -143,6 +143,12 @@ class RtpPacket { // Returns empty arrayview on failure. rtc::ArrayView AllocateRawExtension(int id, size_t length); + // Promotes existing one-byte header extensions to two-byte header extensions + // by rewriting the data and updates the corresponding extension offsets. + void PromoteToTwoByteHeaderExtension(); + + uint16_t SetExtensionLengthMaybeAddZeroPadding(size_t extensions_offset); + // Find or allocate an extension |type|. Returns view of size |length| // to write raw extension to or an empty view on failure. rtc::ArrayView AllocateExtension(ExtensionType type, size_t length); @@ -187,8 +193,6 @@ rtc::ArrayView RtpPacket::GetRawExtension() const { template bool RtpPacket::SetExtension(Values... values) { const size_t value_size = Extension::ValueSize(values...); - if (value_size == 0 || value_size > 16) - return false; auto buffer = AllocateExtension(Extension::kId, value_size); if (buffer.empty()) return false; diff --git a/modules/rtp_rtcp/source/rtp_packet_unittest.cc b/modules/rtp_rtcp/source/rtp_packet_unittest.cc index 0fd500191f..125000d2dc 100644 --- a/modules/rtp_rtcp/source/rtp_packet_unittest.cc +++ b/modules/rtp_rtcp/source/rtp_packet_unittest.cc @@ -64,6 +64,27 @@ constexpr uint8_t kPacketWithTOAndAL[] = { 0x12, 0x00, 0x56, 0xce, 0x90, 0x80|kAudioLevel, 0x00, 0x00}; +constexpr uint8_t kPacketWithTwoByteExtensionIdLast[] = { + 0x90, kPayloadType, kSeqNumFirstByte, kSeqNumSecondByte, + 0x65, 0x43, 0x12, 0x78, + 0x12, 0x34, 0x56, 0x78, + 0x10, 0x00, 0x00, 0x04, + 0x01, 0x03, 0x00, 0x56, + 0xce, 0x09, 0x01, 0x80|kAudioLevel, + kTwoByteExtensionId, 0x03, 0x00, 0x30, // => 0x00 0x30 0x22 + 0x22, 0x00, 0x00, 0x00}; // => Playout delay.min_ms = 3*10 + // => Playout delay.max_ms = 34*10 + +constexpr uint8_t kPacketWithTwoByteExtensionIdFirst[] = { + 0x90, kPayloadType, kSeqNumFirstByte, kSeqNumSecondByte, + 0x65, 0x43, 0x12, 0x78, + 0x12, 0x34, 0x56, 0x78, + 0x10, 0x00, 0x00, 0x04, + kTwoByteExtensionId, 0x03, 0x00, 0x30, // => 0x00 0x30 0x22 + 0x22, 0x01, 0x03, 0x00, // => Playout delay.min_ms = 3*10 + 0x56, 0xce, 0x09, 0x01, // => Playout delay.max_ms = 34*10 + 0x80|kAudioLevel, 0x00, 0x00, 0x00}; + constexpr uint8_t kPacketWithTOAndALInvalidPadding[] = { 0x90, kPayloadType, kSeqNumFirstByte, kSeqNumSecondByte, 0x65, 0x43, 0x12, 0x78, @@ -203,6 +224,51 @@ TEST(RtpPacketTest, CreateWith2Extensions) { ElementsAreArray(packet.data(), packet.size())); } +TEST(RtpPacketTest, CreateWithTwoByteHeaderExtensionFirst) { + RtpPacketToSend::ExtensionManager extensions; + extensions.SetMixedOneTwoByteHeaderSupported(true); + extensions.Register(kRtpExtensionTransmissionTimeOffset, + kTransmissionOffsetExtensionId); + extensions.Register(kRtpExtensionAudioLevel, kAudioLevelExtensionId); + extensions.Register(kRtpExtensionPlayoutDelay, kTwoByteExtensionId); + RtpPacketToSend packet(&extensions); + packet.SetPayloadType(kPayloadType); + packet.SetSequenceNumber(kSeqNum); + packet.SetTimestamp(kTimestamp); + packet.SetSsrc(kSsrc); + // Set extension that requires two-byte header. + PlayoutDelay playoutDelay = {30, 340}; + ASSERT_TRUE(packet.SetExtension(playoutDelay)); + packet.SetExtension(kTimeOffset); + packet.SetExtension(kVoiceActive, kAudioLevel); + EXPECT_THAT(kPacketWithTwoByteExtensionIdFirst, + ElementsAreArray(packet.data(), packet.size())); +} + +TEST(RtpPacketTest, CreateWithTwoByteHeaderExtensionLast) { + // This test will trigger RtpPacket::PromoteToTwoByteHeaderExtension(). + RtpPacketToSend::ExtensionManager extensions; + extensions.SetMixedOneTwoByteHeaderSupported(true); + extensions.Register(kRtpExtensionTransmissionTimeOffset, + kTransmissionOffsetExtensionId); + extensions.Register(kRtpExtensionAudioLevel, kAudioLevelExtensionId); + extensions.Register(kRtpExtensionPlayoutDelay, kTwoByteExtensionId); + RtpPacketToSend packet(&extensions); + packet.SetPayloadType(kPayloadType); + packet.SetSequenceNumber(kSeqNum); + packet.SetTimestamp(kTimestamp); + packet.SetSsrc(kSsrc); + packet.SetExtension(kTimeOffset); + packet.SetExtension(kVoiceActive, kAudioLevel); + EXPECT_THAT(kPacketWithTOAndAL, + ElementsAreArray(packet.data(), packet.size())); + // Set extension that requires two-byte header. + PlayoutDelay playoutDelay = {30, 340}; + ASSERT_TRUE(packet.SetExtension(playoutDelay)); + EXPECT_THAT(kPacketWithTwoByteExtensionIdLast, + ElementsAreArray(packet.data(), packet.size())); +} + TEST(RtpPacketTest, CreateWithDynamicSizedExtensions) { RtpPacketToSend::ExtensionManager extensions; extensions.Register(kRtpStreamIdExtensionId); @@ -247,6 +313,14 @@ TEST(RtpPacketTest, TryToCreateWithLongMid) { EXPECT_FALSE(packet.SetExtension(kLongMid)); } +TEST(RtpPacketTest, TryToCreateTwoByteHeaderNotSupported) { + RtpPacketToSend::ExtensionManager extensions; + extensions.Register(kRtpExtensionAudioLevel, kTwoByteExtensionId); + RtpPacketToSend packet(&extensions); + // Set extension that requires two-byte header. + EXPECT_FALSE(packet.SetExtension(kVoiceActive, kAudioLevel)); +} + TEST(RtpPacketTest, CreateWithMaxSizeHeaderExtension) { const std::string kValue = "123456789abcdef"; RtpPacket::ExtensionManager extensions;