Add parsing/serializing for MID RTP header extension.

This is the first in a series of CLs to add support for media
identification as part of unified plan SDP.

Bug: webrtc:4050
Change-Id: I0eb5639d240a9a1412c2b047a33d5112e4901f26
Reviewed-on: https://chromium-review.googlesource.com/576374
Commit-Queue: Steve Anton <steveanton@webrtc.org>
Reviewed-by: Tommi <tommi@webrtc.org>
Reviewed-by: Danil Chapovalov <danilchap@webrtc.org>
Reviewed-by: Peter Thatcher <pthatcher@webrtc.org>
Reviewed-by: Taylor Brandstetter <deadbeef@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#19111}
diff --git a/webrtc/common_types.h b/webrtc/common_types.h
index 03d8ac92..43ae642 100644
--- a/webrtc/common_types.h
+++ b/webrtc/common_types.h
@@ -715,23 +715,26 @@
   int max_ms;
 };
 
-// Class to represent RtpStreamId which is a string.
+// Class to represent the value of RTP header extensions that are
+// variable-length strings (e.g., RtpStreamId and RtpMid).
 // Unlike std::string, it can be copied with memcpy and cleared with memset.
-// Empty value represent unset RtpStreamId.
-class StreamId {
+//
+// Empty value represents unset header extension (use empty() to query).
+class StringRtpHeaderExtension {
  public:
-  // Stream id is limited to 16 bytes because it is the maximum length
-  // that can be encoded with one-byte header extensions.
+  // String RTP header extensions are 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;
 
   static bool IsLegalName(rtc::ArrayView<const char> name);
 
-  StreamId() { value_[0] = 0; }
-  explicit StreamId(rtc::ArrayView<const char> value) {
+  StringRtpHeaderExtension() { value_[0] = 0; }
+  explicit StringRtpHeaderExtension(rtc::ArrayView<const char> value) {
     Set(value.data(), value.size());
   }
-  StreamId(const StreamId&) = default;
-  StreamId& operator=(const StreamId&) = default;
+  StringRtpHeaderExtension(const StringRtpHeaderExtension&) = default;
+  StringRtpHeaderExtension& operator=(const StringRtpHeaderExtension&) =
+      default;
 
   bool empty() const { return value_[0] == 0; }
   const char* data() const { return value_; }
@@ -742,10 +745,12 @@
   }
   void Set(const char* data, size_t size);
 
-  friend bool operator==(const StreamId& lhs, const StreamId& rhs) {
+  friend bool operator==(const StringRtpHeaderExtension& lhs,
+                         const StringRtpHeaderExtension& rhs) {
     return strncmp(lhs.value_, rhs.value_, kMaxSize) == 0;
   }
-  friend bool operator!=(const StreamId& lhs, const StreamId& rhs) {
+  friend bool operator!=(const StringRtpHeaderExtension& lhs,
+                         const StringRtpHeaderExtension& rhs) {
     return !(lhs == rhs);
   }
 
@@ -753,6 +758,12 @@
   char value_[kMaxSize];
 };
 
+// StreamId represents RtpStreamId which is a string.
+typedef StringRtpHeaderExtension StreamId;
+
+// Mid represents RtpMid which is a string.
+typedef StringRtpHeaderExtension Mid;
+
 struct RTPHeaderExtension {
   RTPHeaderExtension();
 
@@ -790,6 +801,10 @@
   // TODO(danilchap): Update url from draft to release version.
   StreamId stream_id;
   StreamId repaired_stream_id;
+
+  // For identifying the media section used to interpret this RTP packet. See
+  // https://tools.ietf.org/html/draft-ietf-mmusic-sdp-bundle-negotiation-38
+  Mid mid;
 };
 
 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 3bf93d8..185f6ba 100644
--- a/webrtc/modules/rtp_rtcp/include/rtp_rtcp_defines.h
+++ b/webrtc/modules/rtp_rtcp/include/rtp_rtcp_defines.h
@@ -80,6 +80,7 @@
   kRtpExtensionVideoTiming,
   kRtpExtensionRtpStreamId,
   kRtpExtensionRepairedRtpStreamId,
+  kRtpExtensionMid,
   kRtpExtensionNumberOfExtensions  // Must be the last entity in the enum.
 };
 
diff --git a/webrtc/modules/rtp_rtcp/source/rtp_header_extension_map.cc b/webrtc/modules/rtp_rtcp/source/rtp_header_extension_map.cc
index 18ee7e0..d65b11d 100644
--- a/webrtc/modules/rtp_rtcp/source/rtp_header_extension_map.cc
+++ b/webrtc/modules/rtp_rtcp/source/rtp_header_extension_map.cc
@@ -42,6 +42,7 @@
     CreateExtensionInfo<VideoTimingExtension>(),
     CreateExtensionInfo<RtpStreamId>(),
     CreateExtensionInfo<RepairedRtpStreamId>(),
+    CreateExtensionInfo<RtpMid>(),
 };
 
 // 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 3a4afef..8b40d04 100644
--- a/webrtc/modules/rtp_rtcp/source/rtp_header_extensions.cc
+++ b/webrtc/modules/rtp_rtcp/source/rtp_header_extensions.cc
@@ -312,72 +312,51 @@
   return true;
 }
 
-// RtpStreamId.
+bool BaseRtpStringExtension::Parse(rtc::ArrayView<const uint8_t> data,
+                                   StringRtpHeaderExtension* str) {
+  if (data.empty() || data[0] == 0)  // Valid string extension can't be empty.
+    return false;
+  str->Set(data);
+  RTC_DCHECK(!str->empty());
+  return true;
+}
+
+bool BaseRtpStringExtension::Write(uint8_t* data,
+                                   const StringRtpHeaderExtension& str) {
+  RTC_DCHECK_GE(str.size(), 1);
+  RTC_DCHECK_LE(str.size(), StringRtpHeaderExtension::kMaxSize);
+  memcpy(data, str.data(), str.size());
+  return true;
+}
+
+bool BaseRtpStringExtension::Parse(rtc::ArrayView<const uint8_t> data,
+                                   std::string* str) {
+  if (data.empty() || data[0] == 0)  // Valid string extension can't be empty.
+    return false;
+  const char* cstr = 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 string extensions shouldn't contain it.
+  str->assign(cstr, strnlen(cstr, data.size()));
+  RTC_DCHECK(!str->empty());
+  return true;
+}
+
+bool BaseRtpStringExtension::Write(uint8_t* data, const std::string& str) {
+  RTC_DCHECK_GE(str.size(), 1);
+  RTC_DCHECK_LE(str.size(), StringRtpHeaderExtension::kMaxSize);
+  memcpy(data, str.data(), str.size());
+  return true;
+}
+
+// Constant declarations for string RTP header extension types.
+
 constexpr RTPExtensionType RtpStreamId::kId;
 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::Write(uint8_t* data, const StreamId& rsid) {
-  RTC_DCHECK_GE(rsid.size(), 1);
-  RTC_DCHECK_LE(rsid.size(), StreamId::kMaxSize);
-  memcpy(data, rsid.data(), rsid.size());
-  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;
-}
-
-bool RtpStreamId::Write(uint8_t* data, const std::string& rsid) {
-  RTC_DCHECK_GE(rsid.size(), 1);
-  RTC_DCHECK_LE(rsid.size(), StreamId::kMaxSize);
-  memcpy(data, rsid.data(), rsid.size());
-  return true;
-}
-
-// RepairedRtpStreamId.
 constexpr RTPExtensionType RepairedRtpStreamId::kId;
 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);
-}
-
-size_t RepairedRtpStreamId::ValueSize(const StreamId& rsid) {
-  return RtpStreamId::ValueSize(rsid);
-}
-
-bool RepairedRtpStreamId::Write(uint8_t* data, const StreamId& rsid) {
-  return RtpStreamId::Write(data, rsid);
-}
-
-bool RepairedRtpStreamId::Parse(rtc::ArrayView<const uint8_t> data,
-                                std::string* rsid) {
-  return RtpStreamId::Parse(data, rsid);
-}
-
-size_t RepairedRtpStreamId::ValueSize(const std::string& rsid) {
-  return RtpStreamId::ValueSize(rsid);
-}
-
-bool RepairedRtpStreamId::Write(uint8_t* data, const std::string& rsid) {
-  return RtpStreamId::Write(data, rsid);
-}
+constexpr RTPExtensionType RtpMid::kId;
+constexpr const char* RtpMid::kUri;
 
 }  // 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 31d49b2..8471cfb 100644
--- a/webrtc/modules/rtp_rtcp/source/rtp_header_extensions.h
+++ b/webrtc/modules/rtp_rtcp/source/rtp_header_extensions.h
@@ -146,34 +146,40 @@
   static bool Write(uint8_t* data, uint16_t time_delta_ms, uint8_t idx);
 };
 
-class RtpStreamId {
+// Base extension class for RTP header extensions which are strings.
+// Subclasses must defined kId and kUri static constexpr members.
+class BaseRtpStringExtension {
+ public:
+  static bool Parse(rtc::ArrayView<const uint8_t> data,
+                    StringRtpHeaderExtension* str);
+  static size_t ValueSize(const StringRtpHeaderExtension& str) {
+    return str.size();
+  }
+  static bool Write(uint8_t* data, const StringRtpHeaderExtension& str);
+
+  static bool Parse(rtc::ArrayView<const uint8_t> data, std::string* str);
+  static size_t ValueSize(const std::string& str) { return str.size(); }
+  static bool Write(uint8_t* data, const std::string& str);
+};
+
+class RtpStreamId : public BaseRtpStringExtension {
  public:
   static constexpr RTPExtensionType kId = kRtpExtensionRtpStreamId;
   static constexpr const char* kUri =
       "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id";
-
-  static bool Parse(rtc::ArrayView<const uint8_t> data, StreamId* rsid);
-  static size_t ValueSize(const StreamId& rsid) { return rsid.size(); }
-  static bool Write(uint8_t* data, const StreamId& rsid);
-
-  static bool Parse(rtc::ArrayView<const uint8_t> data, std::string* rsid);
-  static size_t ValueSize(const std::string& rsid) { return rsid.size(); }
-  static bool Write(uint8_t* data, const std::string& rsid);
 };
 
-class RepairedRtpStreamId {
+class RepairedRtpStreamId : public BaseRtpStringExtension {
  public:
   static constexpr RTPExtensionType kId = kRtpExtensionRepairedRtpStreamId;
   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* rsid);
-  static size_t ValueSize(const StreamId& rsid);
-  static bool Write(uint8_t* data, const StreamId& rsid);
-
-  static bool Parse(rtc::ArrayView<const uint8_t> data, std::string* rsid);
-  static size_t ValueSize(const std::string& rsid);
-  static bool Write(uint8_t* data, const std::string& rsid);
+class RtpMid : public BaseRtpStringExtension {
+ public:
+  static constexpr RTPExtensionType kId = kRtpExtensionMid;
+  static constexpr const char* kUri = "urn:ietf:params:rtp-hdrext:sdes:mid";
 };
 
 }  // namespace webrtc
diff --git a/webrtc/modules/rtp_rtcp/source/rtp_packet.cc b/webrtc/modules/rtp_rtcp/source/rtp_packet.cc
index 78fa0d2..2c9d0aea 100644
--- a/webrtc/modules/rtp_rtcp/source/rtp_packet.cc
+++ b/webrtc/modules/rtp_rtcp/source/rtp_packet.cc
@@ -178,6 +178,7 @@
       GetExtension<VideoTimingExtension>(&header->extension.video_timing);
   GetExtension<RtpStreamId>(&header->extension.stream_id);
   GetExtension<RepairedRtpStreamId>(&header->extension.repaired_stream_id);
+  GetExtension<RtpMid>(&header->extension.mid);
   GetExtension<PlayoutDelayLimits>(&header->extension.playout_delay);
 }
 
diff --git a/webrtc/modules/rtp_rtcp/source/rtp_packet_unittest.cc b/webrtc/modules/rtp_rtcp/source/rtp_packet_unittest.cc
index 10c94f5..1075d9e 100644
--- a/webrtc/modules/rtp_rtcp/source/rtp_packet_unittest.cc
+++ b/webrtc/modules/rtp_rtcp/source/rtp_packet_unittest.cc
@@ -31,10 +31,12 @@
 constexpr uint8_t kTransmissionOffsetExtensionId = 1;
 constexpr uint8_t kAudioLevelExtensionId = 9;
 constexpr uint8_t kRtpStreamIdExtensionId = 0xa;
+constexpr uint8_t kRtpMidExtensionId = 0xb;
 constexpr int32_t kTimeOffset = 0x56ce;
 constexpr bool kVoiceActive = true;
 constexpr uint8_t kAudioLevel = 0x5a;
 constexpr char kStreamId[] = "streamid";
+constexpr char kMid[] = "mid";
 constexpr size_t kMaxPaddingSize = 224u;
 // clang-format off
 constexpr uint8_t kMinimumPacket[] = {
@@ -66,6 +68,13 @@
     'e',  'a',  'm',  'i',
     'd' , 0x00, 0x00, 0x00};
 
+constexpr uint8_t kPacketWithMid[] = {
+    0x90, kPayloadType, kSeqNumFirstByte, kSeqNumSecondByte,
+    0x65, 0x43, 0x12, 0x78,
+    0x12, 0x34, 0x56, 0x78,
+    0xbe, 0xde, 0x00, 0x01,
+    0xb2, 'm', 'i', 'd'};
+
 constexpr uint32_t kCsrcs[] = {0x34567890, 0x32435465};
 constexpr uint8_t kPayload[] = {'p', 'a', 'y', 'l', 'o', 'a', 'd'};
 constexpr uint8_t kPacketPaddingSize = 8;
@@ -159,6 +168,22 @@
   EXPECT_FALSE(packet.SetExtension<RtpStreamId>(kLongStreamId));
 }
 
+TEST(RtpPacketTest, TryToCreateWithEmptyMid) {
+  RtpPacketToSend::ExtensionManager extensions;
+  extensions.Register<RtpMid>(kRtpMidExtensionId);
+  RtpPacketToSend packet(&extensions);
+  EXPECT_FALSE(packet.SetExtension<RtpMid>(""));
+}
+
+TEST(RtpPacketTest, TryToCreateWithLongMid) {
+  RtpPacketToSend::ExtensionManager extensions;
+  constexpr char kLongMid[] = "LoooooooooonogMid";
+  ASSERT_EQ(strlen(kLongMid), 17u);
+  extensions.Register<RtpMid>(kRtpMidExtensionId);
+  RtpPacketToSend packet(&extensions);
+  EXPECT_FALSE(packet.SetExtension<RtpMid>(kLongMid));
+}
+
 TEST(RtpPacketTest, CreateWithExtensionsWithoutManager) {
   RtpPacketToSend packet(nullptr);
   packet.SetPayloadType(kPayloadType);
@@ -445,6 +470,17 @@
   EXPECT_FALSE(packet.GetExtension<RepairedRtpStreamId>(&repaired_rsid));
 }
 
+TEST(RtpPacketTest, ParseWithMid) {
+  RtpPacketReceived::ExtensionManager extensions;
+  extensions.Register<RtpMid>(kRtpMidExtensionId);
+  RtpPacketReceived packet(&extensions);
+  ASSERT_TRUE(packet.Parse(kPacketWithMid, sizeof(kPacketWithMid)));
+
+  std::string mid;
+  EXPECT_TRUE(packet.GetExtension<RtpMid>(&mid));
+  EXPECT_EQ(mid, kMid);
+}
+
 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 8a8be10..9696ff4 100644
--- a/webrtc/modules/rtp_rtcp/source/rtp_utility.cc
+++ b/webrtc/modules/rtp_rtcp/source/rtp_utility.cc
@@ -484,6 +484,10 @@
               rtc::MakeArrayView(ptr, len + 1));
           break;
         }
+        case kRtpExtensionMid: {
+          header->extension.mid.Set(rtc::MakeArrayView(ptr, len + 1));
+          break;
+        }
         case kRtpExtensionNone:
         case kRtpExtensionNumberOfExtensions: {
           RTC_NOTREACHED() << "Invalid extension type: " << type;
diff --git a/webrtc/test/fuzzers/rtp_packet_fuzzer.cc b/webrtc/test/fuzzers/rtp_packet_fuzzer.cc
index fc53216..3e33699 100644
--- a/webrtc/test/fuzzers/rtp_packet_fuzzer.cc
+++ b/webrtc/test/fuzzers/rtp_packet_fuzzer.cc
@@ -104,6 +104,11 @@
         packet.GetExtension<RepairedRtpStreamId>(&rsid);
         break;
       }
+      case kRtpExtensionMid: {
+        std::string mid;
+        packet.GetExtension<RtpMid>(&mid);
+        break;
+      }
     }
   }
 }