Add read support of RtpStreamId/RepairedRtpStreamId header extensions.

BUG=webrtc:7433

Review-Url: https://codereview.webrtc.org/2805023002
Cr-Commit-Position: refs/heads/master@{#17759}
diff --git a/webrtc/common_types.cc b/webrtc/common_types.cc
index 17bb265..b9c4737 100644
--- a/webrtc/common_types.cc
+++ b/webrtc/common_types.cc
@@ -10,8 +10,9 @@
 
 #include "webrtc/common_types.h"
 
-#include <limits>
 #include <string.h>
+#include <limits>
+#include <type_traits>
 
 #include "webrtc/base/checks.h"
 #include "webrtc/base/stringutils.h"
@@ -20,6 +21,20 @@
 
 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<StreamId>::value, "");
+
 RTPHeaderExtension::RTPHeaderExtension()
     : hasTransmissionTimeOffset(false),
       transmissionTimeOffset(0),
diff --git a/webrtc/common_types.h b/webrtc/common_types.h
index 7504201..06733be 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 @@
   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<const char> 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<const uint8_t> value) {
+    Set(reinterpret_cast<const char*>(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 @@
   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 56aa9bd..11bc943 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 @@
   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 1d39259..26762b0 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 @@
     CreateExtensionInfo<TransportSequenceNumber>(),
     CreateExtensionInfo<PlayoutDelayLimits>(),
     CreateExtensionInfo<VideoContentTypeExtension>(),
+    CreateExtensionInfo<RtpStreamId>(),
+    CreateExtensionInfo<RepairedRtpStreamId>(),
 };
 
 // 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 8141f02..13f488b 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 @@
   return true;
 }
 
+// RtpStreamId.
+constexpr RTPExtensionType RtpStreamId::kId;
+constexpr uint8_t RtpStreamId::kValueSizeBytes;
+constexpr const char* RtpStreamId::kUri;
+
+bool RtpStreamId::Parse(rtc::ArrayView<const uint8_t> 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<const uint8_t> data, std::string* rsid) {
+  if (data.empty() || data[0] == 0)  // Valid rsid can't be empty.
+    return false;
+  const char* str = reinterpret_cast<const char*>(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<const uint8_t> data,
+                                StreamId* rsid) {
+  return RtpStreamId::Parse(data, rsid);
+}
+
+bool RepairedRtpStreamId::Parse(rtc::ArrayView<const uint8_t> 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 0d30848..c60f290 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 <stdint.h>
+#include <string>
 
 #include "webrtc/api/video/video_content_type.h"
 #include "webrtc/api/video/video_rotation.h"
@@ -111,5 +112,31 @@
   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<const uint8_t> data, StreamId* rid);
+  static bool Parse(rtc::ArrayView<const uint8_t> 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<const uint8_t> data, StreamId* rid);
+  static bool Parse(rtc::ArrayView<const uint8_t> 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 2e87528..19e8984 100644
--- a/webrtc/modules/rtp_rtcp/source/rtp_packet.cc
+++ b/webrtc/modules/rtp_rtcp/source/rtp_packet.cc
@@ -172,6 +172,8 @@
   header->extension.hasVideoContentType =
       GetExtension<VideoContentTypeExtension>(
           &header->extension.videoContentType);
+  GetExtension<RtpStreamId>(&header->extension.stream_id);
+  GetExtension<RepairedRtpStreamId>(&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 b85d98a..bbe4080 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 @@
   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<RtpStreamId>(1);
+  extensions.Register<RepairedRtpStreamId>(2);
+  RtpPacketReceived packet(&extensions);
+  ASSERT_TRUE(packet.Parse(kPacket1, sizeof(kPacket1)));
+
+  std::string rsid;
+  EXPECT_TRUE(packet.GetExtension<RtpStreamId>(&rsid));
+  EXPECT_EQ(rsid, "rtx");
+
+  std::string repaired_rsid;
+  EXPECT_TRUE(packet.GetExtension<RepairedRtpStreamId>(&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<RtpStreamId>(&rsid));
+  EXPECT_EQ(rsid, "HD");
+  EXPECT_FALSE(packet.GetExtension<RepairedRtpStreamId>(&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 1c12c89..d003c0b 100644
--- a/webrtc/modules/rtp_rtcp/source/rtp_utility.cc
+++ b/webrtc/modules/rtp_rtcp/source/rtp_utility.cc
@@ -469,6 +469,15 @@
           }
           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 80f22fc..dcaa242 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 @@
   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 @@
   extensions.Register<TransportSequenceNumber>(8);
   extensions.Register<VideoOrientation>(0xa);
   extensions.Register<PlayoutDelayLimits>(0xb);
+  extensions.Register<RtpStreamId>(0xc);
+  extensions.Register<RepairedRtpStreamId>(0xd);
   RtpUtility::RtpHeaderParser parser(kPacket, sizeof(kPacket));
   RTPHeader header;
 
@@ -199,6 +203,33 @@
             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<RtpStreamId>(0xc);
+  extensions.Register<RepairedRtpStreamId>(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 914a8fb..9e111a8 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<const uint16_t*>(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<RTPExtensionType>(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 7cf65cf..64ab6ec 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<const uint16_t*>(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<RTPExtensionType>(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 @@
         VideoContentType content_type;
         packet.GetExtension<VideoContentTypeExtension>(&content_type);
         break;
+      case kRtpExtensionRtpStreamId: {
+        std::string rsid;
+        packet.GetExtension<RtpStreamId>(&rsid);
+        break;
+      }
+      case kRtpExtensionRepairedRtpStreamId: {
+        std::string rsid;
+        packet.GetExtension<RepairedRtpStreamId>(&rsid);
+        break;
+      }
     }
   }
 }