Make RemoveIceCandidates() return false if no candidates were removed

Simplifying index checks in JsepSessionDescription and adding test.

Bug: webrtc:8395
Change-Id: I7e0a11628b884394d6dcf26b367267a1bde30905
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/395281
Commit-Queue: Tomas Gunnarsson <tommi@webrtc.org>
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#44916}
diff --git a/api/jsep_session_description.h b/api/jsep_session_description.h
index 19dbf99..978a472 100644
--- a/api/jsep_session_description.h
+++ b/api/jsep_session_description.h
@@ -72,8 +72,9 @@
   SdpType type_;
   std::vector<JsepCandidateCollection> candidate_collection_;
 
-  bool GetMediasectionIndex(const IceCandidate* candidate, size_t* index);
-  int GetMediasectionIndex(const Candidate& candidate);
+  bool IsValidMLineIndex(int index) const;
+  bool GetMediasectionIndex(const IceCandidate* candidate, size_t* index) const;
+  int GetMediasectionIndex(absl::string_view mid) const;
 };
 
 }  // namespace webrtc
diff --git a/pc/jsep_session_description.cc b/pc/jsep_session_description.cc
index bded5cd..cf5dfa3 100644
--- a/pc/jsep_session_description.cc
+++ b/pc/jsep_session_description.cc
@@ -10,7 +10,9 @@
 
 #include "api/jsep_session_description.h"
 
+#include <algorithm>
 #include <cstddef>
+#include <iterator>
 #include <memory>
 #include <optional>
 #include <string>
@@ -101,7 +103,6 @@
   }
   media_desc->set_connection_address(connection_addr);
 }
-
 }  // namespace
 
 // TODO(steveanton): Remove this default implementation once Chromium has been
@@ -268,7 +269,7 @@
     const std::vector<Candidate>& candidates) {
   size_t num_removed = 0;
   for (auto& candidate : candidates) {
-    int mediasection_index = GetMediasectionIndex(candidate);
+    int mediasection_index = GetMediasectionIndex(candidate.transport_name());
     if (mediasection_index < 0) {
       // Not found.
       continue;
@@ -302,51 +303,33 @@
   return !out->empty();
 }
 
+bool JsepSessionDescription::IsValidMLineIndex(int index) const {
+  RTC_DCHECK(description_);
+  return index >= 0 &&
+         index < static_cast<int>(description_->contents().size());
+}
+
 bool JsepSessionDescription::GetMediasectionIndex(const IceCandidate* candidate,
-                                                  size_t* index) {
-  if (!candidate || !index) {
+                                                  size_t* index) const {
+  if (!candidate || !index || !description_) {
     return false;
   }
 
-  // If the candidate has no valid mline index or sdp_mid, it is impossible
-  // to find a match.
-  if (candidate->sdp_mid().empty() &&
-      (candidate->sdp_mline_index() < 0 ||
-       static_cast<size_t>(candidate->sdp_mline_index()) >=
-           description_->contents().size())) {
-    return false;
-  }
-
-  if (candidate->sdp_mline_index() >= 0)
+  auto mid = candidate->sdp_mid();
+  if (!mid.empty()) {
+    *index = GetMediasectionIndex(mid);
+  } else {
+    // An sdp_mline_index of -1 will be treated as invalid.
     *index = static_cast<size_t>(candidate->sdp_mline_index());
-  if (description_ && !candidate->sdp_mid().empty()) {
-    bool found = false;
-    // Try to match the sdp_mid with content name.
-    for (size_t i = 0; i < description_->contents().size(); ++i) {
-      if (candidate->sdp_mid() == description_->contents().at(i).mid()) {
-        *index = i;
-        found = true;
-        break;
-      }
-    }
-    if (!found) {
-      // If the sdp_mid is presented but we can't find a match, we consider
-      // this as an error.
-      return false;
-    }
   }
-  return true;
+  return IsValidMLineIndex(*index);
 }
 
-int JsepSessionDescription::GetMediasectionIndex(const Candidate& candidate) {
-  // Find the description with a matching transport name of the candidate.
-  const std::string& transport_name = candidate.transport_name();
-  for (size_t i = 0; i < description_->contents().size(); ++i) {
-    if (transport_name == description_->contents().at(i).mid()) {
-      return static_cast<int>(i);
-    }
-  }
-  return -1;
+int JsepSessionDescription::GetMediasectionIndex(absl::string_view mid) const {
+  const auto& contents = description_->contents();
+  auto it =
+      std::find_if(contents.begin(), contents.end(),
+                   [&](const auto& content) { return mid == content.mid(); });
+  return it == contents.end() ? -1 : std::distance(contents.begin(), it);
 }
-
 }  // namespace webrtc
diff --git a/pc/jsep_session_description_unittest.cc b/pc/jsep_session_description_unittest.cc
index 1de2a1a..c00e48d 100644
--- a/pc/jsep_session_description_unittest.cc
+++ b/pc/jsep_session_description_unittest.cc
@@ -166,6 +166,16 @@
   EXPECT_EQ(2u, jsep_desc_->number_of_mediasections());
 }
 
+TEST_F(JsepSessionDescriptionTest, IsValidMLineIndex) {
+  ASSERT_GT(jsep_desc_->number_of_mediasections(), 0u);
+  // Create a candidate with no mid and an illegal index.
+  IceCandidate ice_candidate("", -1, candidate_);
+  EXPECT_FALSE(jsep_desc_->AddCandidate(&ice_candidate));
+  IceCandidate ice_candidate2("", jsep_desc_->number_of_mediasections(),
+                              candidate_);
+  EXPECT_FALSE(jsep_desc_->AddCandidate(&ice_candidate2));
+}
+
 // Test that we can add a candidate to a session description without MID.
 TEST_F(JsepSessionDescriptionTest, AddCandidateWithoutMid) {
   IceCandidate jsep_candidate("", 0, candidate_);
diff --git a/pc/peer_connection_ice_unittest.cc b/pc/peer_connection_ice_unittest.cc
index fbcdb5b..2125883 100644
--- a/pc/peer_connection_ice_unittest.cc
+++ b/pc/peer_connection_ice_unittest.cc
@@ -87,6 +87,7 @@
 
 using ::testing::Combine;
 using ::testing::ElementsAre;
+using ::testing::IsEmpty;
 using ::testing::Pair;
 using ::testing::Values;
 
@@ -595,7 +596,8 @@
   std::unique_ptr<IceCandidate> ice_candidate =
       CreateIceCandidate(audio_content->mid(), 0, candidate);
   EXPECT_TRUE(caller->pc()->AddIceCandidate(ice_candidate.get()));
-  EXPECT_TRUE(caller->pc()->RemoveIceCandidates({candidate}));
+  // This will fail since `candidate.transport_name()` is empty.
+  EXPECT_FALSE(caller->pc()->RemoveIceCandidates({candidate}));
 }
 
 TEST_P(PeerConnectionIceTest, RemoveCandidateRemovesFromRemoteDescription) {
@@ -1598,12 +1600,14 @@
 
   // `candidate.transport_name()` is empty.
   Candidate candidate = CreateLocalUdpCandidate(kCalleeAddress);
+  ASSERT_THAT(candidate.transport_name(), IsEmpty());
   auto* audio_content =
       GetFirstAudioContent(caller->pc()->local_description()->description());
   std::unique_ptr<IceCandidate> ice_candidate =
       CreateIceCandidate(audio_content->mid(), 65535, candidate);
   EXPECT_TRUE(caller->pc()->AddIceCandidate(ice_candidate.get()));
-  EXPECT_TRUE(caller->pc()->RemoveIceCandidates({candidate}));
+  // Removing the candidate will fail because of no transport_name().
+  EXPECT_FALSE(caller->pc()->RemoveIceCandidates({candidate}));
 }
 
 }  // namespace webrtc
diff --git a/pc/sdp_offer_answer.cc b/pc/sdp_offer_answer.cc
index df53177..34102c8 100644
--- a/pc/sdp_offer_answer.cc
+++ b/pc/sdp_offer_answer.cc
@@ -3001,7 +3001,7 @@
     return false;
   }
 
-  size_t number_removed =
+  const size_t number_removed =
       mutable_remote_description()->RemoveCandidates(candidates);
   if (number_removed != candidates.size()) {
     RTC_LOG(LS_ERROR)
@@ -3017,7 +3017,7 @@
         << "RemoveIceCandidates: Error when removing remote candidates: "
         << error.message();
   }
-  return true;
+  return number_removed != 0u;
 }
 
 void SdpOfferAnswerHandler::AddLocalIceCandidate(