Implement Connection::ForgetLearnedState()

This patch adds a new ForgetLearnedState() method on a Connection.
The method, puts the connection into a state similar to
when it was just created.

- write_state = STATE_WRITE_INIT
- receving = false
- throw away all pending request
- reset RttEstimate

All other state is kept unchanged.

Note: It does not trigger SignalStateChange

A subsequent patch will expose the method to the IceController.

BUG: webrtc:11463
Change-Id: I055e8cd067e1bc4fd5ad64dd10f458554dbc87e3
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/171805
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Jonas Oreland <jonaso@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#30916}
diff --git a/p2p/base/connection.cc b/p2p/base/connection.cc
index e503909..282599f 100644
--- a/p2p/base/connection.cc
+++ b/p2p/base/connection.cc
@@ -1334,6 +1334,15 @@
   return false;
 }
 
+void Connection::ForgetLearnedState() {
+  RTC_LOG(LS_INFO) << ToString() << ": Connection forget learned state";
+  requests_.Clear();
+  receiving_ = false;
+  write_state_ = STATE_WRITE_INIT;
+  rtt_estimate_.Reset();
+  pings_since_last_response_.clear();
+}
+
 ProxyConnection::ProxyConnection(Port* port,
                                  size_t index,
                                  const Candidate& remote_candidate)
diff --git a/p2p/base/connection.h b/p2p/base/connection.h
index 0ce2b5d..4b71a7d 100644
--- a/p2p/base/connection.h
+++ b/p2p/base/connection.h
@@ -303,6 +303,20 @@
     return rtt_estimate_;
   }
 
+  // Reset the connection to a state of a newly connected.
+  // - STATE_WRITE_INIT
+  // - receving = false
+  // - throw away all pending request
+  // - reset RttEstimate
+  //
+  // Keep the following unchanged:
+  // - connected
+  // - remote_candidate
+  // - statistics
+  //
+  // Does not trigger SignalStateChange
+  void ForgetLearnedState();
+
   void SendStunBindingResponse(const StunMessage* request);
   void SendGoogPingResponse(const StunMessage* request);
   void SendResponseMessage(const StunMessage& response);
diff --git a/p2p/base/port_unittest.cc b/p2p/base/port_unittest.cc
index c701da2..eaa2545 100644
--- a/p2p/base/port_unittest.cc
+++ b/p2p/base/port_unittest.cc
@@ -13,6 +13,7 @@
 #include <string.h>
 
 #include <cstdint>
+#include <limits>
 #include <list>
 #include <memory>
 #include <string>
@@ -3363,4 +3364,158 @@
   EXPECT_TRUE(port->GetConnection(address) != nullptr);
 }
 
+// TODO(webrtc:11463) : Move Connection tests into separate unit test
+// splitting out shared test code as needed.
+
+class ConnectionTest : public PortTest {
+ public:
+  ConnectionTest() {
+    lport_ = CreateTestPort(kLocalAddr1, "lfrag", "lpass");
+    rport_ = CreateTestPort(kLocalAddr2, "rfrag", "rpass");
+    lport_->SetIceRole(cricket::ICEROLE_CONTROLLING);
+    lport_->SetIceTiebreaker(kTiebreaker1);
+    rport_->SetIceRole(cricket::ICEROLE_CONTROLLED);
+    rport_->SetIceTiebreaker(kTiebreaker2);
+
+    lport_->PrepareAddress();
+    rport_->PrepareAddress();
+  }
+
+  rtc::ScopedFakeClock clock_;
+  int num_state_changes_ = 0;
+
+  Connection* CreateConnection(IceRole role) {
+    Connection* conn;
+    if (role == cricket::ICEROLE_CONTROLLING) {
+      conn = lport_->CreateConnection(rport_->Candidates()[0],
+                                      Port::ORIGIN_MESSAGE);
+    } else {
+      conn = rport_->CreateConnection(lport_->Candidates()[0],
+                                      Port::ORIGIN_MESSAGE);
+    }
+    conn->SignalStateChange.connect(this,
+                                    &ConnectionTest::OnConnectionStateChange);
+    return conn;
+  }
+
+  void SendPingAndCaptureReply(Connection* lconn,
+                               Connection* rconn,
+                               int64_t ms,
+                               rtc::BufferT<uint8_t>* reply) {
+    TestPort* lport =
+        lconn->PortForTest() == lport_.get() ? lport_.get() : rport_.get();
+    TestPort* rport =
+        rconn->PortForTest() == rport_.get() ? rport_.get() : lport_.get();
+    lconn->Ping(rtc::TimeMillis());
+    ASSERT_TRUE_WAIT(lport->last_stun_msg(), kDefaultTimeout);
+    ASSERT_TRUE(lport->last_stun_buf());
+    rconn->OnReadPacket(lport->last_stun_buf()->data<char>(),
+                        lport->last_stun_buf()->size(),
+                        /* packet_time_us */ -1);
+    clock_.AdvanceTime(webrtc::TimeDelta::Millis(ms));
+    ASSERT_TRUE_WAIT(rport->last_stun_msg(), kDefaultTimeout);
+    ASSERT_TRUE(rport->last_stun_buf());
+    *reply = std::move(*rport->last_stun_buf());
+  }
+
+  void SendPingAndReceiveResponse(Connection* lconn,
+                                  Connection* rconn,
+                                  int64_t ms) {
+    rtc::BufferT<uint8_t> reply;
+    SendPingAndCaptureReply(lconn, rconn, ms, &reply);
+    lconn->OnReadPacket(reply.data<char>(), reply.size(),
+                        /* packet_time_us */ -1);
+  }
+
+  void OnConnectionStateChange(Connection* connection) { num_state_changes_++; }
+
+ private:
+  std::unique_ptr<TestPort> lport_;
+  std::unique_ptr<TestPort> rport_;
+};
+
+TEST_F(ConnectionTest, ConnectionForgetLearnedState) {
+  Connection* lconn = CreateConnection(ICEROLE_CONTROLLING);
+  Connection* rconn = CreateConnection(ICEROLE_CONTROLLED);
+
+  EXPECT_FALSE(lconn->writable());
+  EXPECT_FALSE(lconn->receiving());
+  EXPECT_TRUE(std::isnan(lconn->GetRttEstimate().GetAverage()));
+  EXPECT_EQ(lconn->GetRttEstimate().GetVariance(),
+            std::numeric_limits<double>::infinity());
+
+  SendPingAndReceiveResponse(lconn, rconn, 10);
+
+  EXPECT_TRUE(lconn->writable());
+  EXPECT_TRUE(lconn->receiving());
+  EXPECT_EQ(lconn->GetRttEstimate().GetAverage(), 10);
+  EXPECT_EQ(lconn->GetRttEstimate().GetVariance(),
+            std::numeric_limits<double>::infinity());
+
+  SendPingAndReceiveResponse(lconn, rconn, 11);
+
+  EXPECT_TRUE(lconn->writable());
+  EXPECT_TRUE(lconn->receiving());
+  EXPECT_NEAR(lconn->GetRttEstimate().GetAverage(), 10, 0.5);
+  EXPECT_LT(lconn->GetRttEstimate().GetVariance(),
+            std::numeric_limits<double>::infinity());
+
+  lconn->ForgetLearnedState();
+
+  EXPECT_FALSE(lconn->writable());
+  EXPECT_FALSE(lconn->receiving());
+  EXPECT_TRUE(std::isnan(lconn->GetRttEstimate().GetAverage()));
+  EXPECT_EQ(lconn->GetRttEstimate().GetVariance(),
+            std::numeric_limits<double>::infinity());
+}
+
+TEST_F(ConnectionTest, ConnectionForgetLearnedStateDiscardsPendingPings) {
+  Connection* lconn = CreateConnection(ICEROLE_CONTROLLING);
+  Connection* rconn = CreateConnection(ICEROLE_CONTROLLED);
+
+  SendPingAndReceiveResponse(lconn, rconn, 10);
+
+  EXPECT_TRUE(lconn->writable());
+  EXPECT_TRUE(lconn->receiving());
+
+  rtc::BufferT<uint8_t> reply;
+  SendPingAndCaptureReply(lconn, rconn, 10, &reply);
+
+  lconn->ForgetLearnedState();
+
+  EXPECT_FALSE(lconn->writable());
+  EXPECT_FALSE(lconn->receiving());
+
+  lconn->OnReadPacket(reply.data<char>(), reply.size(),
+                      /* packet_time_us */ -1);
+
+  // That reply was discarded due to the ForgetLearnedState() while it was
+  // outstanding.
+  EXPECT_FALSE(lconn->writable());
+  EXPECT_FALSE(lconn->receiving());
+
+  // But sending a new ping and getting a reply works.
+  SendPingAndReceiveResponse(lconn, rconn, 11);
+  EXPECT_TRUE(lconn->writable());
+  EXPECT_TRUE(lconn->receiving());
+}
+
+TEST_F(ConnectionTest, ConnectionForgetLearnedStateDoesNotTriggerStateChange) {
+  Connection* lconn = CreateConnection(ICEROLE_CONTROLLING);
+  Connection* rconn = CreateConnection(ICEROLE_CONTROLLED);
+
+  EXPECT_EQ(num_state_changes_, 0);
+  SendPingAndReceiveResponse(lconn, rconn, 10);
+
+  EXPECT_TRUE(lconn->writable());
+  EXPECT_TRUE(lconn->receiving());
+  EXPECT_EQ(num_state_changes_, 2);
+
+  lconn->ForgetLearnedState();
+
+  EXPECT_FALSE(lconn->writable());
+  EXPECT_FALSE(lconn->receiving());
+  EXPECT_EQ(num_state_changes_, 2);
+}
+
 }  // namespace cricket