Reject attempts to change the media kind for a m-line with a previously used mid

which can happen if the remote end reuses a mid.

BUG=webrtc:15471

Change-Id: I38da7dced712400002bc61d616e481a1255aa896
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/319460
Reviewed-by: Florent Castelli <orphis@webrtc.org>
Commit-Queue: Philipp Hancke <phancke@microsoft.com>
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#40776}
diff --git a/pc/media_session.cc b/pc/media_session.cc
index a2ea39f..53c422f 100644
--- a/pc/media_session.cc
+++ b/pc/media_session.cc
@@ -2341,7 +2341,14 @@
     // recycled.
     if (current_content && !current_content->rejected &&
         current_content->name == media_description_options.mid) {
-      RTC_CHECK(IsMediaContentOfType(current_content, MEDIA_TYPE_AUDIO));
+      if (!IsMediaContentOfType(current_content, MEDIA_TYPE_AUDIO)) {
+        // TODO(bugs.webrtc.org/15471): add a unit test for this since
+        // it is not clear how this can happen for offers.
+        RTC_LOG(LS_ERROR) << "Media type for content with mid='"
+                          << current_content->name
+                          << "' does not match previous type.";
+        return false;
+      }
       const AudioContentDescription* acd =
           current_content->media_description()->as_audio();
       for (const AudioCodec& codec : acd->codecs()) {
@@ -2434,7 +2441,15 @@
     // recycled.
     if (current_content && !current_content->rejected &&
         current_content->name == media_description_options.mid) {
-      RTC_CHECK(IsMediaContentOfType(current_content, MEDIA_TYPE_VIDEO));
+      if (!IsMediaContentOfType(current_content, MEDIA_TYPE_VIDEO)) {
+        // TODO(bugs.webrtc.org/15471): add a unit test for this since
+        // it is not clear how this can happen for offers.
+        RTC_LOG(LS_ERROR) << "Media type for content with mid='"
+                          << current_content->name
+                          << "' does not match previous type.";
+        return false;
+      }
+
       const VideoContentDescription* vcd =
           current_content->media_description()->as_video();
       for (const VideoCodec& codec : vcd->codecs()) {
@@ -2645,7 +2660,13 @@
     // recycled.
     if (current_content && !current_content->rejected &&
         current_content->name == media_description_options.mid) {
-      RTC_CHECK(IsMediaContentOfType(current_content, MEDIA_TYPE_AUDIO));
+      if (!IsMediaContentOfType(current_content, MEDIA_TYPE_AUDIO)) {
+        // Can happen if the remote side re-uses a MID while recycling.
+        RTC_LOG(LS_ERROR) << "Media type for content with mid='"
+                          << current_content->name
+                          << "' does not match previous type.";
+        return false;
+      }
       const AudioContentDescription* acd =
           current_content->media_description()->as_audio();
       for (const AudioCodec& codec : acd->codecs()) {
@@ -2770,7 +2791,13 @@
     // recycled.
     if (current_content && !current_content->rejected &&
         current_content->name == media_description_options.mid) {
-      RTC_CHECK(IsMediaContentOfType(current_content, MEDIA_TYPE_VIDEO));
+      if (!IsMediaContentOfType(current_content, MEDIA_TYPE_VIDEO)) {
+        // Can happen if the remote side re-uses a MID while recycling.
+        RTC_LOG(LS_ERROR) << "Media type for content with mid='"
+                          << current_content->name
+                          << "' does not match previous type.";
+        return false;
+      }
       const VideoContentDescription* vcd =
           current_content->media_description()->as_video();
       for (const VideoCodec& codec : vcd->codecs()) {
diff --git a/pc/sdp_offer_answer_unittest.cc b/pc/sdp_offer_answer_unittest.cc
index 2537a41..448f85a 100644
--- a/pc/sdp_offer_answer_unittest.cc
+++ b/pc/sdp_offer_answer_unittest.cc
@@ -1006,4 +1006,55 @@
   }
 }
 
+// Test variant with boolean order for audio-video and video-audio.
+class SdpOfferAnswerShuffleMediaTypes
+    : public SdpOfferAnswerTest,
+      public testing::WithParamInterface<bool> {
+ public:
+  SdpOfferAnswerShuffleMediaTypes() : SdpOfferAnswerTest() {}
+};
+
+TEST_P(SdpOfferAnswerShuffleMediaTypes,
+       RecyclingWithDifferentKindAndSameMidFails) {
+  bool audio_first = GetParam();
+  auto pc1 = CreatePeerConnection();
+  auto pc2 = CreatePeerConnection();
+  if (audio_first) {
+    pc1->AddAudioTrack("audio_track", {});
+    pc2->AddVideoTrack("video_track", {});
+  } else {
+    pc2->AddAudioTrack("audio_track", {});
+    pc1->AddVideoTrack("video_track", {});
+  }
+
+  auto initial_offer = pc1->CreateOfferAndSetAsLocal();
+  ASSERT_EQ(initial_offer->description()->contents().size(), 1u);
+  auto mid1 = initial_offer->description()->contents()[0].mid();
+  std::string rejected_answer_sdp =
+      "v=0\r\n"
+      "o=- 8621259572628890423 2 IN IP4 127.0.0.1\r\n"
+      "s=-\r\n"
+      "t=0 0\r\n"
+      "m=" +
+      std::string(audio_first ? "audio" : "video") +
+      " 0 UDP/TLS/RTP/SAVPF 111\r\n"
+      "c=IN IP4 0.0.0.0\r\n";
+  auto rejected_answer =
+      CreateSessionDescription(SdpType::kAnswer, rejected_answer_sdp);
+  EXPECT_TRUE(pc1->SetRemoteDescription(std::move(rejected_answer)));
+
+  auto offer =
+      pc2->CreateOfferAndSetAsLocal();  // This will generate a mid=0 too
+  ASSERT_EQ(offer->description()->contents().size(), 1u);
+  auto mid2 = offer->description()->contents()[0].mid();
+  EXPECT_EQ(mid1, mid2);  // Check that the mids collided.
+  EXPECT_TRUE(pc1->SetRemoteDescription(std::move(offer)));
+  auto answer = pc1->CreateAnswer();
+  EXPECT_FALSE(pc1->SetLocalDescription(std::move(answer)));
+}
+
+INSTANTIATE_TEST_SUITE_P(SdpOfferAnswerShuffleMediaTypes,
+                         SdpOfferAnswerShuffleMediaTypes,
+                         ::testing::Values(true, false));
+
 }  // namespace webrtc