[Unified Plan] Support legacy endpoints that do not use a=mid

These legacy endpoints were supported with Plan B since the SDP
parser would fill in default MID values if a=mid was absent that
happened to match the default offered MIDs.

With Unified Plan, these default MIDs changed so the autofilled
MIDs do not match any more.

This CL adds information to the SessionDescription struct to
indicate whether or not a=mid was present and modified
PeerConnection::SetRemoteDescription to copy MIDs from the local
description if the a=mid lines are not present.

Bug: webrtc:9540
Change-Id: Ibf923b4ad59edb0facd06ddbd01cc10c62fc48e6
Reviewed-on: https://webrtc-review.googlesource.com/c/114820
Commit-Queue: Steve Anton <steveanton@webrtc.org>
Reviewed-by: Amit Hilbuch <amithi@webrtc.org>
Reviewed-by: Seth Hampson <shampson@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#26054}
diff --git a/pc/jsepsessiondescription_unittest.cc b/pc/jsepsessiondescription_unittest.cc
index 94eb47a..04577e2 100644
--- a/pc/jsepsessiondescription_unittest.cc
+++ b/pc/jsepsessiondescription_unittest.cc
@@ -69,16 +69,16 @@
   video->AddCodec(cricket::VideoCodec(120, "VP8"));
   desc->AddContent(cricket::CN_VIDEO, MediaProtocolType::kRtp, video.release());
 
-  EXPECT_TRUE(desc->AddTransportInfo(cricket::TransportInfo(
+  desc->AddTransportInfo(cricket::TransportInfo(
       cricket::CN_AUDIO,
       cricket::TransportDescription(
           std::vector<std::string>(), kCandidateUfragVoice, kCandidatePwdVoice,
-          cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_NONE, NULL))));
-  EXPECT_TRUE(desc->AddTransportInfo(cricket::TransportInfo(
+          cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_NONE, NULL)));
+  desc->AddTransportInfo(cricket::TransportInfo(
       cricket::CN_VIDEO,
       cricket::TransportDescription(
           std::vector<std::string>(), kCandidateUfragVideo, kCandidatePwdVideo,
-          cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_NONE, NULL))));
+          cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_NONE, NULL)));
   return desc;
 }
 
diff --git a/pc/mediasession.cc b/pc/mediasession.cc
index a4837cf..47a84e7 100644
--- a/pc/mediasession.cc
+++ b/pc/mediasession.cc
@@ -1798,14 +1798,12 @@
   std::unique_ptr<TransportDescription> new_tdesc(
       transport_desc_factory_->CreateOffer(transport_options, current_tdesc,
                                            ice_credentials));
-  bool ret =
-      (new_tdesc.get() != NULL &&
-       offer_desc->AddTransportInfo(TransportInfo(content_name, *new_tdesc)));
-  if (!ret) {
+  if (!new_tdesc) {
     RTC_LOG(LS_ERROR) << "Failed to AddTransportOffer, content name="
                       << content_name;
   }
-  return ret;
+  offer_desc->AddTransportInfo(TransportInfo(content_name, *new_tdesc));
+  return true;
 }
 
 std::unique_ptr<TransportDescription>
@@ -1831,12 +1829,7 @@
     const std::string& content_name,
     const TransportDescription& transport_desc,
     SessionDescription* answer_desc) const {
-  if (!answer_desc->AddTransportInfo(
-          TransportInfo(content_name, transport_desc))) {
-    RTC_LOG(LS_ERROR) << "Failed to AddTransportAnswer, content name="
-                      << content_name;
-    return false;
-  }
+  answer_desc->AddTransportInfo(TransportInfo(content_name, transport_desc));
   return true;
 }
 
diff --git a/pc/mediasession_unittest.cc b/pc/mediasession_unittest.cc
index fde824d..a2a216b 100644
--- a/pc/mediasession_unittest.cc
+++ b/pc/mediasession_unittest.cc
@@ -427,14 +427,14 @@
     std::unique_ptr<SessionDescription> desc;
     if (has_current_desc) {
       current_desc = absl::make_unique<SessionDescription>();
-      EXPECT_TRUE(current_desc->AddTransportInfo(TransportInfo(
+      current_desc->AddTransportInfo(TransportInfo(
           "audio",
-          TransportDescription(current_audio_ufrag, current_audio_pwd))));
-      EXPECT_TRUE(current_desc->AddTransportInfo(TransportInfo(
+          TransportDescription(current_audio_ufrag, current_audio_pwd)));
+      current_desc->AddTransportInfo(TransportInfo(
           "video",
-          TransportDescription(current_video_ufrag, current_video_pwd))));
-      EXPECT_TRUE(current_desc->AddTransportInfo(TransportInfo(
-          "data", TransportDescription(current_data_ufrag, current_data_pwd))));
+          TransportDescription(current_video_ufrag, current_video_pwd)));
+      current_desc->AddTransportInfo(TransportInfo(
+          "data", TransportDescription(current_data_ufrag, current_data_pwd)));
     }
     if (offer) {
       desc = f1_.CreateOffer(options, current_desc.get());
diff --git a/pc/peerconnection.cc b/pc/peerconnection.cc
index 06fbae1..da34cc2 100644
--- a/pc/peerconnection.cc
+++ b/pc/peerconnection.cc
@@ -649,6 +649,25 @@
   return DataMessageType::kControl;
 }
 
+// Find a new MID that is not already in |used_mids|, then add it to |used_mids|
+// and return a reference to it.
+// Generated MIDs should be no more than 3 bytes long to take up less space in
+// the RTP packet.
+const std::string& AllocateMid(std::set<std::string>* used_mids) {
+  RTC_DCHECK(used_mids);
+  // We're boring: just generate MIDs 0, 1, 2, ...
+  size_t i = 0;
+  std::set<std::string>::iterator it;
+  bool inserted;
+  do {
+    std::string mid = rtc::ToString(i++);
+    auto insert_result = used_mids->insert(mid);
+    it = insert_result.first;
+    inserted = insert_result.second;
+  } while (!inserted);
+  return *it;
+}
+
 }  // namespace
 
 // Upon completion, posts a task to execute the callback of the
@@ -2234,6 +2253,56 @@
   return RTCError::OK();
 }
 
+// The SDP parser used to populate these values by default for the 'content
+// name' if an a=mid line was absent.
+static absl::string_view GetDefaultMidForPlanB(cricket::MediaType media_type) {
+  switch (media_type) {
+    case cricket::MEDIA_TYPE_AUDIO:
+      return cricket::CN_AUDIO;
+    case cricket::MEDIA_TYPE_VIDEO:
+      return cricket::CN_VIDEO;
+    case cricket::MEDIA_TYPE_DATA:
+      return cricket::CN_DATA;
+  }
+  RTC_NOTREACHED();
+  return "";
+}
+
+void PeerConnection::FillInMissingRemoteMids(
+    cricket::SessionDescription* remote_description) {
+  RTC_DCHECK(remote_description);
+  const cricket::ContentInfos& local_contents =
+      (local_description() ? local_description()->description()->contents()
+                           : cricket::ContentInfos());
+  std::set<std::string> used_mids = seen_mids_;
+  for (size_t i = 0; i < remote_description->contents().size(); ++i) {
+    cricket::ContentInfo& content = remote_description->contents()[i];
+    if (!content.name.empty()) {
+      continue;
+    }
+    absl::string_view new_mid;
+    absl::string_view source_explanation;
+    if (IsUnifiedPlan()) {
+      if (i < local_contents.size()) {
+        new_mid = local_contents[i].name;
+        source_explanation = "from the matching local media section";
+      } else {
+        new_mid = AllocateMid(&used_mids);
+        source_explanation = "generated just now";
+      }
+    } else {
+      new_mid = GetDefaultMidForPlanB(content.media_description()->type());
+      source_explanation = "to match pre-existing behavior";
+    }
+    content.name = std::string(new_mid);
+    remote_description->transport_infos()[i].content_name =
+        std::string(new_mid);
+    RTC_LOG(LS_INFO) << "SetRemoteDescription: Remote media section at i=" << i
+                     << " is missing an a=mid line. Filling in the value '"
+                     << new_mid << "' " << source_explanation << ".";
+  }
+}
+
 void PeerConnection::SetRemoteDescription(
     SetSessionDescriptionObserver* observer,
     SessionDescriptionInterface* desc) {
@@ -2274,6 +2343,10 @@
     ReportSdpFormatReceived(*desc);
   }
 
+  // Handle remote descriptions missing a=mid lines for interop with legacy end
+  // points.
+  FillInMissingRemoteMids(desc->description());
+
   RTCError error = ValidateSessionDescription(desc.get(), cricket::CS_REMOTE);
   if (!error.ok()) {
     std::string error_message = GetSetDescriptionErrorMessage(
@@ -3919,25 +3992,6 @@
                       offer_answer_options.num_simulcast_layers);
 }
 
-// Find a new MID that is not already in |used_mids|, then add it to |used_mids|
-// and return a reference to it.
-// Generated MIDs should be no more than 3 bytes long to take up less space in
-// the RTP packet.
-static const std::string& AllocateMid(std::set<std::string>* used_mids) {
-  RTC_DCHECK(used_mids);
-  // We're boring: just generate MIDs 0, 1, 2, ...
-  size_t i = 0;
-  std::set<std::string>::iterator it;
-  bool inserted;
-  do {
-    std::string mid = rtc::ToString(i++);
-    auto insert_result = used_mids->insert(mid);
-    it = insert_result.first;
-    inserted = insert_result.second;
-  } while (!inserted);
-  return *it;
-}
-
 static cricket::MediaDescriptionOptions
 GetMediaDescriptionOptionsForTransceiver(
     rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
@@ -6099,6 +6153,17 @@
   return content->media_description()->rtcp_mux();
 }
 
+static RTCError ValidateMids(const cricket::SessionDescription& description) {
+  std::set<std::string> mids;
+  for (const cricket::ContentInfo& content : description.contents()) {
+    if (!mids.insert(content.name).second) {
+      LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER,
+                           "Duplicate a=mid value '" + content.name + "'.");
+    }
+  }
+  return RTCError::OK();
+}
+
 RTCError PeerConnection::ValidateSessionDescription(
     const SessionDescriptionInterface* sdesc,
     cricket::ContentSource source) {
@@ -6118,6 +6183,11 @@
         "Called in wrong state: " + GetSignalingStateString(signaling_state()));
   }
 
+  RTCError error = ValidateMids(*sdesc->description());
+  if (!error.ok()) {
+    return error;
+  }
+
   // Verify crypto settings.
   std::string crypto_error;
   if (webrtc_session_desc_factory_->SdesPolicy() == cricket::SEC_REQUIRED ||
diff --git a/pc/peerconnection.h b/pc/peerconnection.h
index 3105b2f..7529543 100644
--- a/pc/peerconnection.h
+++ b/pc/peerconnection.h
@@ -652,6 +652,12 @@
     return configuration_.sdp_semantics == SdpSemantics::kUnifiedPlan;
   }
 
+  // The offer/answer machinery assumes the media section MID is present and
+  // unique. To support legacy end points that do not supply a=mid lines, this
+  // method will modify the session description to add MIDs generated according
+  // to the SDP semantics.
+  void FillInMissingRemoteMids(cricket::SessionDescription* remote_description);
+
   // Is there an RtpSender of the given type?
   bool HasRtpSender(cricket::MediaType type) const;
 
diff --git a/pc/peerconnection_jsep_unittest.cc b/pc/peerconnection_jsep_unittest.cc
index f0c9111..b860664 100644
--- a/pc/peerconnection_jsep_unittest.cc
+++ b/pc/peerconnection_jsep_unittest.cc
@@ -1604,4 +1604,94 @@
   EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, error.type());
 }
 
+// Removes the RTP header extension associated with the given URI from the media
+// description.
+static void RemoveRtpHeaderExtensionByUri(
+    MediaContentDescription* media_description,
+    absl::string_view uri) {
+  std::vector<RtpExtension> header_extensions =
+      media_description->rtp_header_extensions();
+  header_extensions.erase(std::remove_if(
+      header_extensions.begin(), header_extensions.end(),
+      [uri](const RtpExtension& extension) { return extension.uri == uri; }));
+  media_description->set_rtp_header_extensions(header_extensions);
+}
+
+// Transforms a session description to emulate a legacy endpoint which does not
+// support a=mid, BUNDLE, and the MID header extension.
+static void ClearMids(SessionDescriptionInterface* sdesc) {
+  cricket::SessionDescription* desc = sdesc->description();
+  desc->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE);
+  cricket::ContentInfo* audio_content = cricket::GetFirstAudioContent(desc);
+  if (audio_content) {
+    desc->GetTransportInfoByName(audio_content->name)->content_name = "";
+    audio_content->name = "";
+    RemoveRtpHeaderExtensionByUri(audio_content->media_description(),
+                                  RtpExtension::kMidUri);
+  }
+  cricket::ContentInfo* video_content = cricket::GetFirstVideoContent(desc);
+  if (video_content) {
+    desc->GetTransportInfoByName(video_content->name)->content_name = "";
+    video_content->name = "";
+    RemoveRtpHeaderExtensionByUri(video_content->media_description(),
+                                  RtpExtension::kMidUri);
+  }
+}
+
+// Test that negotiation works with legacy endpoints which do not support a=mid.
+TEST_F(PeerConnectionJsepTest, LegacyNoMidAudioOnlyOffer) {
+  auto caller = CreatePeerConnection();
+  caller->AddAudioTrack("audio");
+  auto callee = CreatePeerConnection();
+  callee->AddAudioTrack("audio");
+
+  auto offer = caller->CreateOffer();
+  ClearMids(offer.get());
+
+  ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer)));
+  EXPECT_TRUE(callee->SetLocalDescription(callee->CreateAnswer()));
+}
+TEST_F(PeerConnectionJsepTest, LegacyNoMidAudioVideoOffer) {
+  auto caller = CreatePeerConnection();
+  caller->AddAudioTrack("audio");
+  caller->AddVideoTrack("video");
+  auto callee = CreatePeerConnection();
+  callee->AddAudioTrack("audio");
+  callee->AddVideoTrack("video");
+
+  auto offer = caller->CreateOffer();
+  ClearMids(offer.get());
+
+  ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer)));
+  EXPECT_TRUE(callee->SetLocalDescription(callee->CreateAnswer()));
+}
+TEST_F(PeerConnectionJsepTest, LegacyNoMidAudioOnlyAnswer) {
+  auto caller = CreatePeerConnection();
+  caller->AddAudioTrack("audio");
+  auto callee = CreatePeerConnection();
+  callee->AddAudioTrack("audio");
+
+  ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
+
+  auto answer = callee->CreateAnswer();
+  ClearMids(answer.get());
+
+  EXPECT_TRUE(caller->SetRemoteDescription(std::move(answer)));
+}
+TEST_F(PeerConnectionJsepTest, LegacyNoMidAudioVideoAnswer) {
+  auto caller = CreatePeerConnection();
+  caller->AddAudioTrack("audio");
+  caller->AddVideoTrack("video");
+  auto callee = CreatePeerConnection();
+  callee->AddAudioTrack("audio");
+  callee->AddVideoTrack("video");
+
+  ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
+
+  auto answer = callee->CreateAnswer();
+  ClearMids(answer.get());
+
+  ASSERT_TRUE(caller->SetRemoteDescription(std::move(answer)));
+}
+
 }  // namespace webrtc
diff --git a/pc/peerconnection_media_unittest.cc b/pc/peerconnection_media_unittest.cc
index 6af0c98..25aea98 100644
--- a/pc/peerconnection_media_unittest.cc
+++ b/pc/peerconnection_media_unittest.cc
@@ -1084,6 +1084,22 @@
             cricket::GetFirstVideoContent(reoffer->description())->name);
 }
 
+// Test that SetRemoteDescription returns an error if there are two m= sections
+// with the same MID value.
+TEST_P(PeerConnectionMediaTest, SetRemoteDescriptionFailsWithDuplicateMids) {
+  auto caller = CreatePeerConnectionWithAudioVideo();
+  auto callee = CreatePeerConnectionWithAudioVideo();
+
+  auto offer = caller->CreateOffer();
+  RenameContent(offer->description(), cricket::MEDIA_TYPE_AUDIO, "same");
+  RenameContent(offer->description(), cricket::MEDIA_TYPE_VIDEO, "same");
+
+  std::string error;
+  EXPECT_FALSE(callee->SetRemoteDescription(std::move(offer), &error));
+  EXPECT_EQ(error,
+            "Failed to set remote offer sdp: Duplicate a=mid value 'same'.");
+}
+
 TEST_P(PeerConnectionMediaTest,
        CombinedAudioVideoBweConfigPropagatedToMediaEngine) {
   RTCConfiguration config;
diff --git a/pc/sessiondescription.cc b/pc/sessiondescription.cc
index aefe148..dd56eaf 100644
--- a/pc/sessiondescription.cc
+++ b/pc/sessiondescription.cc
@@ -199,12 +199,8 @@
   return false;
 }
 
-bool SessionDescription::AddTransportInfo(const TransportInfo& transport_info) {
-  if (GetTransportInfoByName(transport_info.content_name) != NULL) {
-    return false;
-  }
+void SessionDescription::AddTransportInfo(const TransportInfo& transport_info) {
   transport_infos_.push_back(transport_info);
-  return true;
 }
 
 bool SessionDescription::RemoveTransportInfoByName(const std::string& name) {
diff --git a/pc/sessiondescription.h b/pc/sessiondescription.h
index ceb9fd7..9998356 100644
--- a/pc/sessiondescription.h
+++ b/pc/sessiondescription.h
@@ -483,8 +483,7 @@
     transport_infos_ = transport_infos;
   }
   // Adds a TransportInfo to this description.
-  // Returns false if a TransportInfo with the same name already exists.
-  bool AddTransportInfo(const TransportInfo& transport_info);
+  void AddTransportInfo(const TransportInfo& transport_info);
   bool RemoveTransportInfoByName(const std::string& name);
 
   // Group accessors.
diff --git a/pc/webrtcsdp.cc b/pc/webrtcsdp.cc
index 87ee35f..0a0d659 100644
--- a/pc/webrtcsdp.cc
+++ b/pc/webrtcsdp.cc
@@ -2550,20 +2550,6 @@
     std::vector<std::unique_ptr<JsepIceCandidate>>* candidates,
     webrtc::SdpParseError* error) {
   auto media_desc = absl::make_unique<C>();
-  switch (media_type) {
-    case cricket::MEDIA_TYPE_AUDIO:
-      *content_name = cricket::CN_AUDIO;
-      break;
-    case cricket::MEDIA_TYPE_VIDEO:
-      *content_name = cricket::CN_VIDEO;
-      break;
-    case cricket::MEDIA_TYPE_DATA:
-      *content_name = cricket::CN_DATA;
-      break;
-    default:
-      RTC_NOTREACHED();
-      break;
-  }
   if (!ParseContent(message, media_type, mline_index, protocol, payload_types,
                     pos, content_name, bundle_only, msid_signaling,
                     media_desc.get(), transport, candidates, error)) {
@@ -2747,14 +2733,7 @@
                                           : MediaProtocolType::kRtp,
                      content_rejected, bundle_only, content.release());
     // Create TransportInfo with the media level "ice-pwd" and "ice-ufrag".
-    TransportInfo transport_info(content_name, transport);
-
-    if (!desc->AddTransportInfo(transport_info)) {
-      rtc::StringBuilder description;
-      description << "Failed to AddTransportInfo with content name: "
-                  << content_name;
-      return ParseFailed("", description.str(), error);
-    }
+    desc->AddTransportInfo(TransportInfo(content_name, transport));
   }
 
   desc->set_msid_signaling(msid_signaling);
diff --git a/pc/webrtcsdp_unittest.cc b/pc/webrtcsdp_unittest.cc
index 0ecba4c..849f6c5 100644
--- a/pc/webrtcsdp_unittest.cc
+++ b/pc/webrtcsdp_unittest.cc
@@ -38,6 +38,7 @@
 #include "rtc_base/sslfingerprint.h"
 #include "rtc_base/stringencode.h"
 #include "rtc_base/stringutils.h"
+#include "test/gmock.h"
 #include "test/gtest.h"
 
 #ifdef WEBRTC_ANDROID
@@ -48,20 +49,20 @@
 using cricket::AudioCodec;
 using cricket::AudioContentDescription;
 using cricket::Candidate;
+using cricket::ContentGroup;
 using cricket::ContentInfo;
 using cricket::CryptoParams;
-using cricket::ContentGroup;
 using cricket::DataCodec;
 using cricket::DataContentDescription;
 using cricket::ICE_CANDIDATE_COMPONENT_RTCP;
 using cricket::ICE_CANDIDATE_COMPONENT_RTP;
 using cricket::kFecSsrcGroupSemantics;
 using cricket::LOCAL_PORT_TYPE;
-using cricket::RELAY_PORT_TYPE;
-using cricket::SessionDescription;
 using cricket::MediaProtocolType;
+using cricket::RELAY_PORT_TYPE;
 using cricket::RidDescription;
 using cricket::RidDirection;
+using cricket::SessionDescription;
 using cricket::SimulcastDescription;
 using cricket::SimulcastLayer;
 using cricket::StreamParams;
@@ -70,6 +71,8 @@
 using cricket::TransportInfo;
 using cricket::VideoCodec;
 using cricket::VideoContentDescription;
+using ::testing::ElementsAre;
+using ::testing::Field;
 using webrtc::IceCandidateCollection;
 using webrtc::IceCandidateInterface;
 using webrtc::JsepIceCandidate;
@@ -1032,10 +1035,10 @@
     desc_.AddContent(kVideoContentName, MediaProtocolType::kRtp, video_desc_);
 
     // TransportInfo
-    EXPECT_TRUE(desc_.AddTransportInfo(TransportInfo(
-        kAudioContentName, TransportDescription(kUfragVoice, kPwdVoice))));
-    EXPECT_TRUE(desc_.AddTransportInfo(TransportInfo(
-        kVideoContentName, TransportDescription(kUfragVideo, kPwdVideo))));
+    desc_.AddTransportInfo(TransportInfo(
+        kAudioContentName, TransportDescription(kUfragVoice, kPwdVoice)));
+    desc_.AddTransportInfo(TransportInfo(
+        kVideoContentName, TransportDescription(kUfragVideo, kPwdVideo)));
 
     // v4 host
     int port = 1234;
@@ -1231,8 +1234,8 @@
     }
     audio_desc_2->AddStream(audio_track_2);
     desc_.AddContent(kAudioContentName2, MediaProtocolType::kRtp, audio_desc_2);
-    EXPECT_TRUE(desc_.AddTransportInfo(TransportInfo(
-        kAudioContentName2, TransportDescription(kUfragVoice2, kPwdVoice2))));
+    desc_.AddTransportInfo(TransportInfo(
+        kAudioContentName2, TransportDescription(kUfragVoice2, kPwdVoice2)));
     // Video track 2, in stream 2.
     VideoContentDescription* video_desc_2 = CreateVideoContentDescription();
     StreamParams video_track_2;
@@ -1244,8 +1247,8 @@
     }
     video_desc_2->AddStream(video_track_2);
     desc_.AddContent(kVideoContentName2, MediaProtocolType::kRtp, video_desc_2);
-    EXPECT_TRUE(desc_.AddTransportInfo(TransportInfo(
-        kVideoContentName2, TransportDescription(kUfragVideo2, kPwdVideo2))));
+    desc_.AddTransportInfo(TransportInfo(
+        kVideoContentName2, TransportDescription(kUfragVideo2, kPwdVideo2)));
 
     // Video track 3, in stream 2.
     VideoContentDescription* video_desc_3 = CreateVideoContentDescription();
@@ -1258,8 +1261,8 @@
     }
     video_desc_3->AddStream(video_track_3);
     desc_.AddContent(kVideoContentName3, MediaProtocolType::kRtp, video_desc_3);
-    EXPECT_TRUE(desc_.AddTransportInfo(TransportInfo(
-        kVideoContentName3, TransportDescription(kUfragVideo3, kPwdVideo3))));
+    desc_.AddTransportInfo(TransportInfo(
+        kVideoContentName3, TransportDescription(kUfragVideo3, kPwdVideo3)));
     desc_.set_msid_signaling(cricket::kMsidSignalingMediaSection);
 
     ASSERT_TRUE(jdesc_.Initialize(desc_.Copy(), jdesc_.session_id(),
@@ -1302,8 +1305,8 @@
     audio_track_2.ssrcs.push_back(kAudioTrack2Ssrc);
     audio_desc_2->AddStream(audio_track_2);
     desc_.AddContent(kAudioContentName2, MediaProtocolType::kRtp, audio_desc_2);
-    EXPECT_TRUE(desc_.AddTransportInfo(TransportInfo(
-        kAudioContentName2, TransportDescription(kUfragVoice2, kPwdVoice2))));
+    desc_.AddTransportInfo(TransportInfo(
+        kAudioContentName2, TransportDescription(kUfragVoice2, kPwdVoice2)));
 
     // Audio track 3 has no stream ids.
     AudioContentDescription* audio_desc_3 = CreateAudioContentDescription();
@@ -1314,8 +1317,8 @@
     audio_track_3.ssrcs.push_back(kAudioTrack3Ssrc);
     audio_desc_3->AddStream(audio_track_3);
     desc_.AddContent(kAudioContentName3, MediaProtocolType::kRtp, audio_desc_3);
-    EXPECT_TRUE(desc_.AddTransportInfo(TransportInfo(
-        kAudioContentName3, TransportDescription(kUfragVoice3, kPwdVoice3))));
+    desc_.AddTransportInfo(TransportInfo(
+        kAudioContentName3, TransportDescription(kUfragVoice3, kPwdVoice3)));
     desc_.set_msid_signaling(msid_signaling);
     ASSERT_TRUE(jdesc_.Initialize(desc_.Copy(), jdesc_.session_id(),
                                   jdesc_.session_version()));
@@ -1614,7 +1617,7 @@
     SessionDescription* desc =
         const_cast<SessionDescription*>(jdesc->description());
     desc->RemoveTransportInfoByName(content_name);
-    EXPECT_TRUE(desc->AddTransportInfo(transport_info));
+    desc->AddTransportInfo(transport_info);
     for (size_t i = 0; i < jdesc_.number_of_mediasections(); ++i) {
       const IceCandidateCollection* cc = jdesc_.candidates(i);
       for (size_t j = 0; j < cc->count(); ++j) {
@@ -1653,16 +1656,16 @@
     desc_.RemoveTransportInfoByName(kAudioContentName);
     desc_.RemoveTransportInfoByName(kVideoContentName);
     rtc::SSLFingerprint fingerprint(rtc::DIGEST_SHA_1, kIdentityDigest);
-    EXPECT_TRUE(desc_.AddTransportInfo(TransportInfo(
+    desc_.AddTransportInfo(TransportInfo(
         kAudioContentName,
         TransportDescription(std::vector<std::string>(), kUfragVoice, kPwdVoice,
                              cricket::ICEMODE_FULL,
-                             cricket::CONNECTIONROLE_NONE, &fingerprint))));
-    EXPECT_TRUE(desc_.AddTransportInfo(TransportInfo(
+                             cricket::CONNECTIONROLE_NONE, &fingerprint)));
+    desc_.AddTransportInfo(TransportInfo(
         kVideoContentName,
         TransportDescription(std::vector<std::string>(), kUfragVideo, kPwdVideo,
                              cricket::ICEMODE_FULL,
-                             cricket::CONNECTIONROLE_NONE, &fingerprint))));
+                             cricket::CONNECTIONROLE_NONE, &fingerprint)));
   }
 
   void AddExtmap(bool encrypted) {
@@ -1779,8 +1782,8 @@
     data_desc_->AddCodec(codec);
     desc_.AddContent(kDataContentName, MediaProtocolType::kSctp,
                      data.release());
-    EXPECT_TRUE(desc_.AddTransportInfo(TransportInfo(
-        kDataContentName, TransportDescription(kUfragData, kPwdData))));
+    desc_.AddTransportInfo(TransportInfo(
+        kDataContentName, TransportDescription(kUfragData, kPwdData)));
   }
 
   void AddRtpDataChannel() {
@@ -1799,8 +1802,8 @@
                      "inline:FvLcvU2P3ZWmQxgPAgcDu7Zl9vftYElFOjEzhWs5", ""));
     data_desc_->set_protocol(cricket::kMediaProtocolSavpf);
     desc_.AddContent(kDataContentName, MediaProtocolType::kRtp, data.release());
-    EXPECT_TRUE(desc_.AddTransportInfo(TransportInfo(
-        kDataContentName, TransportDescription(kUfragData, kPwdData))));
+    desc_.AddTransportInfo(TransportInfo(
+        kDataContentName, TransportDescription(kUfragData, kPwdData)));
   }
 
   bool TestDeserializeDirection(RtpTransceiverDirection direction) {
@@ -2450,9 +2453,7 @@
   JsepSessionDescription jdesc(kDummyType);
   EXPECT_TRUE(SdpDeserialize(kSdpNoRtpmapString, &jdesc));
   cricket::AudioContentDescription* audio =
-      jdesc.description()
-          ->GetContentDescriptionByName(cricket::CN_AUDIO)
-          ->as_audio();
+      cricket::GetFirstAudioContentDescription(jdesc.description());
   AudioCodecs ref_codecs;
   // The codecs in the AudioContentDescription should be in the same order as
   // the payload types (<fmt>s) on the m= line.
@@ -2475,9 +2476,7 @@
   JsepSessionDescription jdesc(kDummyType);
   EXPECT_TRUE(SdpDeserialize(kSdpNoRtpmapString, &jdesc));
   cricket::AudioContentDescription* audio =
-      jdesc.description()
-          ->GetContentDescriptionByName(cricket::CN_AUDIO)
-          ->as_audio();
+      cricket::GetFirstAudioContentDescription(jdesc.description());
 
   cricket::AudioCodec g729 = audio->codecs()[0];
   EXPECT_EQ("G729", g729.name);
@@ -3074,15 +3073,11 @@
 
   // Verify
   cricket::AudioContentDescription* audio =
-      jdesc.description()
-          ->GetContentDescriptionByName(cricket::CN_AUDIO)
-          ->as_audio();
+      cricket::GetFirstAudioContentDescription(jdesc.description());
   EXPECT_TRUE(audio->conference_mode());
 
   cricket::VideoContentDescription* video =
-      jdesc.description()
-          ->GetContentDescriptionByName(cricket::CN_VIDEO)
-          ->as_video();
+      cricket::GetFirstVideoContentDescription(jdesc.description());
   EXPECT_TRUE(video->conference_mode());
 }
 
@@ -3097,15 +3092,11 @@
 
   // Verify.
   cricket::AudioContentDescription* audio =
-      jdesc.description()
-          ->GetContentDescriptionByName(cricket::CN_AUDIO)
-          ->as_audio();
+      cricket::GetFirstAudioContentDescription(jdesc.description());
   EXPECT_TRUE(audio->conference_mode());
 
   cricket::VideoContentDescription* video =
-      jdesc.description()
-          ->GetContentDescriptionByName(cricket::CN_VIDEO)
-          ->as_video();
+      cricket::GetFirstVideoContentDescription(jdesc.description());
   EXPECT_TRUE(video->conference_mode());
 }
 
@@ -4318,3 +4309,19 @@
 
   TestSerialize(jdesc_);
 }
+
+// Test that the content name is empty if the media section does not have an
+// a=mid line.
+TEST_F(WebRtcSdpTest, ParseNoMid) {
+  std::string sdp = kSdpString;
+  Replace("a=mid:audio_content_name\r\n", "", &sdp);
+  Replace("a=mid:video_content_name\r\n", "", &sdp);
+
+  JsepSessionDescription output(kDummyType);
+  SdpParseError error;
+  ASSERT_TRUE(webrtc::SdpDeserialize(sdp, &output, &error));
+
+  EXPECT_THAT(output.description()->contents(),
+              ElementsAre(Field("name", &cricket::ContentInfo::name, ""),
+                          Field("name", &cricket::ContentInfo::name, "")));
+}