Reject the descriptions that attempt to change the order of m= sections
in current local description.

When setting the descriptions, the order of m= sections would be compared
against existing m= sections and an error would be returned if the order
doesn't match.

Previously reviewed on: https://codereview.webrtc.org/3012313002/

BUG=chromium:763842
TBR=deadbeef@webrtc.org

Change-Id: I577e3424830b0a4c5ecd5524923873e30ad23d43
Reviewed-on: https://webrtc-review.googlesource.com/1200
Commit-Queue: Zhi Huang <zhihuang@webrtc.org>
Reviewed-by: Zhi Huang <zhihuang@webrtc.org>
Cr-Original-Commit-Position: refs/heads/master@{#19842}
Cr-Mirrored-From: https://webrtc.googlesource.com/src
Cr-Mirrored-Commit: 2a5e4268f821ef7e3a0fb59bc4d40b8af04ec4f9
diff --git a/pc/peerconnectioninterface_unittest.cc b/pc/peerconnectioninterface_unittest.cc
index 565704e..ccf64d2 100644
--- a/pc/peerconnectioninterface_unittest.cc
+++ b/pc/peerconnectioninterface_unittest.cc
@@ -8,6 +8,7 @@
  *  be found in the AUTHORS file in the root of the source tree.
  */
 
+#include <algorithm>
 #include <memory>
 #include <sstream>
 #include <string>
@@ -3831,6 +3832,39 @@
   EXPECT_TRUE(DoCreateAnswer(&answer, nullptr));
 }
 
+// Test that an error is returned if a description is applied that doesn't
+// respect the order of existing media sections.
+TEST_F(PeerConnectionInterfaceTest,
+       MediaSectionOrderEnforcedForSubsequentOffers) {
+  CreatePeerConnection();
+  FakeConstraints constraints;
+  constraints.SetMandatoryReceiveAudio(true);
+  constraints.SetMandatoryReceiveVideo(true);
+  std::unique_ptr<SessionDescriptionInterface> offer;
+  ASSERT_TRUE(DoCreateOffer(&offer, &constraints));
+  EXPECT_TRUE(DoSetRemoteDescription(std::move(offer)));
+
+  std::unique_ptr<SessionDescriptionInterface> answer;
+  ASSERT_TRUE(DoCreateAnswer(&answer, nullptr));
+  EXPECT_TRUE(DoSetLocalDescription(std::move(answer)));
+
+  // A remote offer with different m=line order should be rejected.
+  ASSERT_TRUE(DoCreateOffer(&offer, &constraints));
+  std::reverse(offer->description()->contents().begin(),
+               offer->description()->contents().end());
+  std::reverse(offer->description()->transport_infos().begin(),
+               offer->description()->transport_infos().end());
+  EXPECT_FALSE(DoSetRemoteDescription(std::move(offer)));
+
+  // A subsequent local offer with different m=line order should be rejected.
+  ASSERT_TRUE(DoCreateOffer(&offer, &constraints));
+  std::reverse(offer->description()->contents().begin(),
+               offer->description()->contents().end());
+  std::reverse(offer->description()->transport_infos().begin(),
+               offer->description()->transport_infos().end());
+  EXPECT_FALSE(DoSetLocalDescription(std::move(offer)));
+}
+
 class PeerConnectionMediaConfigTest : public testing::Test {
  protected:
   void SetUp() override {
diff --git a/pc/webrtcsession.cc b/pc/webrtcsession.cc
index c78dbc4..0753081 100644
--- a/pc/webrtcsession.cc
+++ b/pc/webrtcsession.cc
@@ -61,8 +61,12 @@
 const char kCreateChannelFailed[] = "Failed to create channels.";
 const char kInvalidCandidates[] = "Description contains invalid candidates.";
 const char kInvalidSdp[] = "Invalid session description.";
-const char kMlineMismatch[] =
-    "Offer and answer descriptions m-lines are not matching. Rejecting answer.";
+const char kMlineMismatchInAnswer[] =
+    "The order of m-lines in answer doesn't match order in offer. Rejecting "
+    "answer.";
+const char kMlineMismatchInSubsequentOffer[] =
+    "The order of m-lines in subsequent offer doesn't match order from "
+    "previous offer/answer.";
 const char kPushDownTDFailed[] =
     "Failed to push down transport description:";
 const char kSdpWithoutDtlsFingerprint[] =
@@ -136,31 +140,39 @@
   return kIceCandidatePairMax;
 }
 
-// Compares |answer| against |offer|. Comparision is done
-// for number of m-lines in answer against offer. If matches true will be
-// returned otherwise false.
-static bool VerifyMediaDescriptions(
-    const SessionDescription* answer, const SessionDescription* offer) {
-  if (offer->contents().size() != answer->contents().size())
+// Verify that the order of media sections in |desc1| matches |desc2|. The
+// number of m= sections could be different.
+static bool MediaSectionsInSameOrder(const SessionDescription* desc1,
+                                     const SessionDescription* desc2) {
+  if (!desc1 || !desc2) {
     return false;
-
-  for (size_t i = 0; i < offer->contents().size(); ++i) {
-    if ((offer->contents()[i].name) != answer->contents()[i].name) {
+  }
+  for (size_t i = 0;
+       i < desc1->contents().size() && i < desc2->contents().size(); ++i) {
+    if ((desc2->contents()[i].name) != desc1->contents()[i].name) {
       return false;
     }
-    const MediaContentDescription* offer_mdesc =
+    const MediaContentDescription* desc2_mdesc =
         static_cast<const MediaContentDescription*>(
-            offer->contents()[i].description);
-    const MediaContentDescription* answer_mdesc =
+            desc2->contents()[i].description);
+    const MediaContentDescription* desc1_mdesc =
         static_cast<const MediaContentDescription*>(
-            answer->contents()[i].description);
-    if (offer_mdesc->type() != answer_mdesc->type()) {
+            desc1->contents()[i].description);
+    if (desc2_mdesc->type() != desc1_mdesc->type()) {
       return false;
     }
   }
   return true;
 }
 
+static bool MediaSectionsHaveSameCount(const SessionDescription* desc1,
+                                       const SessionDescription* desc2) {
+  if (!desc1 || !desc2) {
+    return false;
+  }
+  return desc1->contents().size() == desc2->contents().size();
+}
+
 // Checks that each non-rejected content has SDES crypto keys or a DTLS
 // fingerprint, unless it's in a BUNDLE group, in which case only the
 // BUNDLE-tag section (first media section/description in the BUNDLE group)
@@ -2153,12 +2165,21 @@
   // m-lines that do not rtcp-mux enabled.
 
   // Verify m-lines in Answer when compared against Offer.
-  if (action == kAnswer) {
+  if (action == kAnswer || action == kPrAnswer) {
     const cricket::SessionDescription* offer_desc =
         (source == cricket::CS_LOCAL) ? remote_description()->description()
                                       : local_description()->description();
-    if (!VerifyMediaDescriptions(sdesc->description(), offer_desc)) {
-      return BadAnswerSdp(source, kMlineMismatch, err_desc);
+    if (!MediaSectionsHaveSameCount(sdesc->description(), offer_desc) ||
+        !MediaSectionsInSameOrder(sdesc->description(), offer_desc)) {
+      return BadAnswerSdp(source, kMlineMismatchInAnswer, err_desc);
+    }
+  } else {
+    // The re-offers should respect the order of m= sections in current local
+    // description. See RFC3264 Section 8 paragraph 4 for more details.
+    if (local_description() &&
+        !MediaSectionsInSameOrder(sdesc->description(),
+                                  local_description()->description())) {
+      return BadOfferSdp(source, kMlineMismatchInSubsequentOffer, err_desc);
     }
   }
 
diff --git a/pc/webrtcsession.h b/pc/webrtcsession.h
index 686ac1e..8be828f 100644
--- a/pc/webrtcsession.h
+++ b/pc/webrtcsession.h
@@ -61,7 +61,8 @@
 extern const char kCreateChannelFailed[];
 extern const char kInvalidCandidates[];
 extern const char kInvalidSdp[];
-extern const char kMlineMismatch[];
+extern const char kMlineMismatchInAnswer[];
+extern const char kMlineMismatchInSubsequentOffer[];
 extern const char kPushDownTDFailed[];
 extern const char kSdpWithoutDtlsFingerprint[];
 extern const char kSdpWithoutSdesCrypto[];
diff --git a/pc/webrtcsession_unittest.cc b/pc/webrtcsession_unittest.cc
index 8621965..497daff 100644
--- a/pc/webrtcsession_unittest.cc
+++ b/pc/webrtcsession_unittest.cc
@@ -70,7 +70,7 @@
 using webrtc::kBundleWithoutRtcpMux;
 using webrtc::kCreateChannelFailed;
 using webrtc::kInvalidSdp;
-using webrtc::kMlineMismatch;
+using webrtc::kMlineMismatchInAnswer;
 using webrtc::kPushDownTDFailed;
 using webrtc::kSdpWithoutIceUfragPwd;
 using webrtc::kSdpWithoutDtlsFingerprint;
@@ -3747,7 +3747,8 @@
   EXPECT_TRUE(modified_answer->Initialize(answer_copy,
                                           answer->session_id(),
                                           answer->session_version()));
-  SetRemoteDescriptionAnswerExpectError(kMlineMismatch, modified_answer);
+  SetRemoteDescriptionAnswerExpectError(kMlineMismatchInAnswer,
+                                        modified_answer);
 
   // Different content names.
   std::string sdp;
@@ -3760,7 +3761,8 @@
                              &sdp);
   SessionDescriptionInterface* modified_answer1 =
       CreateSessionDescription(JsepSessionDescription::kAnswer, sdp, NULL);
-  SetRemoteDescriptionAnswerExpectError(kMlineMismatch, modified_answer1);
+  SetRemoteDescriptionAnswerExpectError(kMlineMismatchInAnswer,
+                                        modified_answer1);
 
   // Different media types.
   EXPECT_TRUE(answer->ToString(&sdp));
@@ -3772,7 +3774,8 @@
                              &sdp);
   SessionDescriptionInterface* modified_answer2 =
       CreateSessionDescription(JsepSessionDescription::kAnswer, sdp, NULL);
-  SetRemoteDescriptionAnswerExpectError(kMlineMismatch, modified_answer2);
+  SetRemoteDescriptionAnswerExpectError(kMlineMismatchInAnswer,
+                                        modified_answer2);
 
   SetRemoteDescriptionWithoutError(answer.release());
 }
@@ -3794,7 +3797,7 @@
   EXPECT_TRUE(modified_answer->Initialize(answer_copy,
                                           answer->session_id(),
                                           answer->session_version()));
-  SetLocalDescriptionAnswerExpectError(kMlineMismatch, modified_answer);
+  SetLocalDescriptionAnswerExpectError(kMlineMismatchInAnswer, modified_answer);
   SetLocalDescriptionWithoutError(answer);
 }