Fix the generation mismatch assertion error.

BUG=4860

Review URL: https://codereview.webrtc.org/1248063002

Cr-Commit-Position: refs/heads/master@{#9667}
diff --git a/talk/app/webrtc/webrtcsession.cc b/talk/app/webrtc/webrtcsession.cc
index 2533328..b6a178c 100644
--- a/talk/app/webrtc/webrtcsession.cc
+++ b/talk/app/webrtc/webrtcsession.cc
@@ -432,11 +432,10 @@
     }
   }
 
-  void CheckForRemoteIceRestart(
-      const SessionDescriptionInterface* old_desc,
-      const SessionDescriptionInterface* new_desc) {
+  bool CheckForRemoteIceRestart(const SessionDescriptionInterface* old_desc,
+                                const SessionDescriptionInterface* new_desc) {
     if (!old_desc || new_desc->type() != SessionDescriptionInterface::kOffer) {
-      return;
+      return false;
     }
     const SessionDescription* new_sd = new_desc->description();
     const SessionDescription* old_sd = old_desc->description();
@@ -462,9 +461,10 @@
                                          new_transport_desc->ice_pwd)) {
         LOG(LS_INFO) << "Remote peer request ice restart.";
         ice_restart_ = true;
-        break;
+        return true;
       }
     }
+    return false;
   }
 
  private:
@@ -838,13 +838,19 @@
 
   // Copy all saved candidates.
   CopySavedCandidates(desc);
-  // We retain all received candidates.
-  WebRtcSessionDescriptionFactory::CopyCandidatesFromSessionDescription(
-      remote_desc_.get(), desc);
+
   // Check if this new SessionDescription contains new ice ufrag and password
   // that indicates the remote peer requests ice restart.
-  ice_restart_latch_->CheckForRemoteIceRestart(remote_desc_.get(),
-                                               desc);
+  bool ice_restart =
+      ice_restart_latch_->CheckForRemoteIceRestart(remote_desc_.get(), desc);
+  // We retain all received candidates only if ICE is not restarted.
+  // When ICE is restarted, all previous candidates belong to an old generation
+  // and should not be kept.
+  if (!ice_restart) {
+    WebRtcSessionDescriptionFactory::CopyCandidatesFromSessionDescription(
+        remote_desc_.get(), desc);
+  }
+
   remote_desc_.reset(desc_temp.release());
 
   rtc::SSLRole role;
@@ -1522,7 +1528,6 @@
         }
         continue;
       }
-
       ret = UseCandidate(candidate);
       if (!ret)
         break;
diff --git a/talk/app/webrtc/webrtcsession_unittest.cc b/talk/app/webrtc/webrtcsession_unittest.cc
index de10bc4..a269546 100644
--- a/talk/app/webrtc/webrtcsession_unittest.cc
+++ b/talk/app/webrtc/webrtcsession_unittest.cc
@@ -2580,6 +2580,63 @@
   EXPECT_FALSE(session_->SetRemoteDescription(modified_offer, &error));
 }
 
+// Test that if the remote description indicates the peer requested ICE restart
+// (via a new ufrag or pwd), the old ICE candidates are not copied,
+// and vice versa.
+TEST_F(WebRtcSessionTest, TestSetRemoteDescriptionWithIceRestart) {
+  Init();
+  scoped_ptr<SessionDescriptionInterface> offer(CreateRemoteOffer());
+
+  // Create the first offer.
+  std::string sdp;
+  ModifyIceUfragPwdLines(offer.get(), "0123456789012345",
+                         "abcdefghijklmnopqrstuvwx", &sdp);
+  SessionDescriptionInterface* offer1 =
+      CreateSessionDescription(JsepSessionDescription::kOffer, sdp, NULL);
+  cricket::Candidate candidate1(1, "udp", rtc::SocketAddress("1.1.1.1", 5000),
+                                0, "", "", "relay", 0, "");
+  JsepIceCandidate ice_candidate1(kMediaContentName0, kMediaContentIndex0,
+                                  candidate1);
+  EXPECT_TRUE(offer1->AddCandidate(&ice_candidate1));
+  SetRemoteDescriptionWithoutError(offer1);
+  EXPECT_EQ(1, session_->remote_description()->candidates(0)->count());
+
+  // The second offer has the same ufrag and pwd but different address.
+  sdp.clear();
+  ModifyIceUfragPwdLines(offer.get(), "0123456789012345",
+                         "abcdefghijklmnopqrstuvwx", &sdp);
+  SessionDescriptionInterface* offer2 =
+      CreateSessionDescription(JsepSessionDescription::kOffer, sdp, NULL);
+  candidate1.set_address(rtc::SocketAddress("1.1.1.1", 6000));
+  JsepIceCandidate ice_candidate2(kMediaContentName0, kMediaContentIndex0,
+                                  candidate1);
+  EXPECT_TRUE(offer2->AddCandidate(&ice_candidate2));
+  SetRemoteDescriptionWithoutError(offer2);
+  EXPECT_EQ(2, session_->remote_description()->candidates(0)->count());
+
+  // The third offer has a different ufrag and different address.
+  sdp.clear();
+  ModifyIceUfragPwdLines(offer.get(), "0123456789012333",
+                         "abcdefghijklmnopqrstuvwx", &sdp);
+  SessionDescriptionInterface* offer3 =
+      CreateSessionDescription(JsepSessionDescription::kOffer, sdp, NULL);
+  candidate1.set_address(rtc::SocketAddress("1.1.1.1", 7000));
+  JsepIceCandidate ice_candidate3(kMediaContentName0, kMediaContentIndex0,
+                                  candidate1);
+  EXPECT_TRUE(offer3->AddCandidate(&ice_candidate3));
+  SetRemoteDescriptionWithoutError(offer3);
+  EXPECT_EQ(1, session_->remote_description()->candidates(0)->count());
+
+  // The fourth offer has no candidate but a different ufrag/pwd.
+  sdp.clear();
+  ModifyIceUfragPwdLines(offer.get(), "0123456789012444",
+                         "abcdefghijklmnopqrstuvyz", &sdp);
+  SessionDescriptionInterface* offer4 =
+      CreateSessionDescription(JsepSessionDescription::kOffer, sdp, NULL);
+  SetRemoteDescriptionWithoutError(offer4);
+  EXPECT_EQ(0, session_->remote_description()->candidates(0)->count());
+}
+
 // Test that candidates sent to the "video" transport do not get pushed down to
 // the "audio" transport channel when bundling using TransportProxy.
 TEST_F(WebRtcSessionTest, TestIgnoreCandidatesForUnusedTransportWhenBundling) {
diff --git a/webrtc/p2p/base/p2ptransportchannel.cc b/webrtc/p2p/base/p2ptransportchannel.cc
index 46ed40a..94eb017 100644
--- a/webrtc/p2p/base/p2ptransportchannel.cc
+++ b/webrtc/p2p/base/p2ptransportchannel.cc
@@ -666,6 +666,17 @@
 void P2PTransportChannel::OnCandidate(const Candidate& candidate) {
   ASSERT(worker_thread_ == rtc::Thread::Current());
 
+  uint32 generation = candidate.generation();
+  // Network may not guarantee the order of the candidate delivery. If a
+  // remote candidate with an older generation arrives, drop it.
+  if (generation != 0 && generation < remote_candidate_generation_) {
+    LOG(LS_WARNING) << "Dropping a remote candidate because its generation "
+                    << generation
+                    << " is lower than the current remote generation "
+                    << remote_candidate_generation_;
+    return;
+  }
+
   // Create connections to this remote candidate.
   CreateConnections(candidate, NULL, false);