RTCIceCandidatePairStats[1] added.

Note: In this initial CL most stats members are missing. This needs to
be addressed before closing the RTCIceCandidatePairStats bug
(crbug.com/633550).

[1] https://w3c.github.io/webrtc-stats/#candidatepair-dict*

BUG=chromium:633550, chromium:627816

Review-Url: https://codereview.webrtc.org/2390693003
Cr-Commit-Position: refs/heads/master@{#14604}
diff --git a/webrtc/api/rtcstatscollector.cc b/webrtc/api/rtcstatscollector.cc
index 2d858c0..5f60ea1 100644
--- a/webrtc/api/rtcstatscollector.cc
+++ b/webrtc/api/rtcstatscollector.cc
@@ -180,16 +180,16 @@
     int64_t timestamp_us, const SessionStats& session_stats,
     RTCStatsReport* report) const {
   RTC_DCHECK(signaling_thread_->IsCurrent());
-  for (const auto& transport : session_stats.transport_stats) {
+  for (const auto& transport_stats : session_stats.transport_stats) {
     rtc::scoped_refptr<rtc::RTCCertificate> local_certificate;
     if (pc_->session()->GetLocalCertificate(
-        transport.second.transport_name, &local_certificate)) {
+        transport_stats.second.transport_name, &local_certificate)) {
       ProduceCertificateStatsFromSSLCertificateAndChain_s(
           timestamp_us, local_certificate->ssl_certificate(), report);
     }
     std::unique_ptr<rtc::SSLCertificate> remote_certificate =
         pc_->session()->GetRemoteSSLCertificate(
-            transport.second.transport_name);
+            transport_stats.second.transport_name);
     if (remote_certificate) {
       ProduceCertificateStatsFromSSLCertificateAndChain_s(
           timestamp_us, *remote_certificate.get(), report);
@@ -222,19 +222,65 @@
       int64_t timestamp_us, const SessionStats& session_stats,
       RTCStatsReport* report) const {
   RTC_DCHECK(signaling_thread_->IsCurrent());
-  for (const auto& transport : session_stats.transport_stats) {
-    for (const auto& channel : transport.second.channel_stats) {
-      for (const cricket::ConnectionInfo& info : channel.connection_infos) {
-        // TODO(hbos): Produce |RTCIceCandidatePairStats| referencing the
-        // resulting |RTCIceCandidateStats| pair. crbug.com/633550
+  for (const auto& transport_stats : session_stats.transport_stats) {
+    for (const auto& channel_stats : transport_stats.second.channel_stats) {
+      for (const cricket::ConnectionInfo& info :
+           channel_stats.connection_infos) {
+        const std::string& id = "RTCIceCandidatePair_" +
+            info.local_candidate.id() + "_" + info.remote_candidate.id();
+        std::unique_ptr<RTCIceCandidatePairStats> candidate_pair_stats(
+            new RTCIceCandidatePairStats(id, timestamp_us));
+
+        // TODO(hbos): Set all of the |RTCIceCandidatePairStats|'s members,
+        // crbug.com/633550.
+
+        // TODO(hbos): Set candidate_pair_stats->transport_id. Should be ID to
+        // RTCTransportStats which does not exist yet: crbug.com/653873.
+
         // TODO(hbos): There could be other candidates that are not paired with
         // anything. We don't have a complete list. Local candidates come from
         // Port objects, and prflx candidates (both local and remote) are only
         // stored in candidate pairs. crbug.com/632723
-        ProduceIceCandidateStats_s(
+        candidate_pair_stats->local_candidate_id = ProduceIceCandidateStats_s(
             timestamp_us, info.local_candidate, true, report);
-        ProduceIceCandidateStats_s(
+        candidate_pair_stats->remote_candidate_id = ProduceIceCandidateStats_s(
             timestamp_us, info.remote_candidate, false, report);
+
+        // TODO(hbos): Set candidate_pair_stats->state.
+        // TODO(hbos): Set candidate_pair_stats->priority.
+        // TODO(hbos): Set candidate_pair_stats->nominated.
+        // TODO(hbos): This writable is different than the spec. It goes to
+        // false after a certain amount of time without a response passes.
+        // crbug.com/633550
+        candidate_pair_stats->writable = info.writable;
+        // TODO(hbos): Set candidate_pair_stats->readable.
+        candidate_pair_stats->bytes_sent =
+            static_cast<uint64_t>(info.sent_total_bytes);
+        candidate_pair_stats->bytes_received =
+            static_cast<uint64_t>(info.recv_total_bytes);
+        // TODO(hbos): Set candidate_pair_stats->total_rtt.
+        // TODO(hbos): The |info.rtt| measurement is smoothed. It shouldn't be
+        // smoothed according to the spec. crbug.com/633550. See
+        // https://w3c.github.io/webrtc-stats/#dom-rtcicecandidatepairstats-currentrtt
+        candidate_pair_stats->current_rtt =
+            static_cast<double>(info.rtt) / 1000.0;
+        // TODO(hbos): Set candidate_pair_stats->available_outgoing_bitrate.
+        // TODO(hbos): Set candidate_pair_stats->available_incoming_bitrate.
+        // TODO(hbos): Set candidate_pair_stats->requests_received.
+        candidate_pair_stats->requests_sent =
+            static_cast<uint64_t>(info.sent_ping_requests_total);
+        candidate_pair_stats->responses_received =
+            static_cast<uint64_t>(info.recv_ping_responses);
+        candidate_pair_stats->responses_sent =
+            static_cast<uint64_t>(info.sent_ping_responses);
+        // TODO(hbos): Set candidate_pair_stats->retransmissions_received.
+        // TODO(hbos): Set candidate_pair_stats->retransmissions_sent.
+        // TODO(hbos): Set candidate_pair_stats->consent_requests_received.
+        // TODO(hbos): Set candidate_pair_stats->consent_requests_sent.
+        // TODO(hbos): Set candidate_pair_stats->consent_responses_received.
+        // TODO(hbos): Set candidate_pair_stats->consent_responses_sent.
+
+        report->AddStats(std::move(candidate_pair_stats));
       }
     }
   }
@@ -248,13 +294,10 @@
   const RTCStats* stats = report->Get(id);
   if (!stats) {
     std::unique_ptr<RTCIceCandidateStats> candidate_stats;
-    if (is_local) {
-      candidate_stats.reset(
-          new RTCLocalIceCandidateStats(id, timestamp_us));
-    } else {
-      candidate_stats.reset(
-          new RTCRemoteIceCandidateStats(id, timestamp_us));
-    }
+    if (is_local)
+      candidate_stats.reset(new RTCLocalIceCandidateStats(id, timestamp_us));
+    else
+      candidate_stats.reset(new RTCRemoteIceCandidateStats(id, timestamp_us));
     candidate_stats->ip = candidate.address().ipaddr().ToString();
     candidate_stats->port = static_cast<int32_t>(candidate.address().port());
     candidate_stats->protocol = candidate.protocol();
diff --git a/webrtc/api/rtcstatscollector.h b/webrtc/api/rtcstatscollector.h
index 6443d13..712f9e2 100644
--- a/webrtc/api/rtcstatscollector.h
+++ b/webrtc/api/rtcstatscollector.h
@@ -85,8 +85,7 @@
   void ProduceCertificateStatsFromSSLCertificateAndChain_s(
       int64_t timestamp_us, const rtc::SSLCertificate& certificate,
       RTCStatsReport* report) const;
-  // Produces |RTCIceCandidateStats|. TODO(hbos): Should also produce
-  // |RTCIceCandidatePairStats|. crbug.com/633550
+  // Produces |RTCIceCandidatePairStats| and |RTCIceCandidateStats|.
   void ProduceIceCandidateAndPairStats_s(
       int64_t timestamp_us, const SessionStats& session_stats,
       RTCStatsReport* report) const;
diff --git a/webrtc/api/rtcstatscollector_unittest.cc b/webrtc/api/rtcstatscollector_unittest.cc
index 0da772f..c8eed9f 100644
--- a/webrtc/api/rtcstatscollector_unittest.cc
+++ b/webrtc/api/rtcstatscollector_unittest.cc
@@ -331,12 +331,11 @@
     return callback->report();
   }
 
-  void ExpectReportContainsCandidate(
+  const RTCIceCandidateStats* ExpectReportContainsCandidate(
       const rtc::scoped_refptr<const RTCStatsReport>& report,
       const cricket::Candidate& candidate,
       bool is_local) {
-    const RTCStats* stats =
-        report->Get("RTCIceCandidate_" + candidate.id());
+    const RTCStats* stats = report->Get("RTCIceCandidate_" + candidate.id());
     EXPECT_TRUE(stats);
     const RTCIceCandidateStats* candidate_stats;
     if (is_local)
@@ -353,6 +352,69 @@
               static_cast<int32_t>(candidate.priority()));
     // TODO(hbos): Define candidate_stats->url. crbug.com/632723
     EXPECT_FALSE(candidate_stats->url.is_defined());
+    return candidate_stats;
+  }
+
+  void ExpectReportContainsCandidatePair(
+      const rtc::scoped_refptr<const RTCStatsReport>& report,
+      const cricket::TransportStats& transport_stats) {
+    for (const auto& channel_stats : transport_stats.channel_stats) {
+      for (const cricket::ConnectionInfo& info :
+           channel_stats.connection_infos) {
+        const std::string& id = "RTCIceCandidatePair_" +
+            info.local_candidate.id() + "_" + info.remote_candidate.id();
+        const RTCStats* stats = report->Get(id);
+        EXPECT_TRUE(stats);
+        const RTCIceCandidatePairStats& candidate_pair_stats =
+            stats->cast_to<RTCIceCandidatePairStats>();
+
+        // TODO(hbos): Define all the undefined |candidate_pair_stats| stats.
+        // The EXPECT_FALSE are for the undefined stats, see also todos listed
+        // in rtcstatscollector.cc. crbug.com/633550
+        EXPECT_FALSE(candidate_pair_stats.transport_id.is_defined());
+        const RTCIceCandidateStats* local_candidate =
+            ExpectReportContainsCandidate(report, info.local_candidate, true);
+        EXPECT_EQ(*candidate_pair_stats.local_candidate_id,
+                  local_candidate->id());
+        const RTCIceCandidateStats* remote_candidate =
+            ExpectReportContainsCandidate(report, info.remote_candidate, false);
+        EXPECT_EQ(*candidate_pair_stats.remote_candidate_id,
+                  remote_candidate->id());
+
+        EXPECT_FALSE(candidate_pair_stats.state.is_defined());
+        EXPECT_FALSE(candidate_pair_stats.priority.is_defined());
+        EXPECT_FALSE(candidate_pair_stats.nominated.is_defined());
+        EXPECT_EQ(*candidate_pair_stats.writable, info.writable);
+        EXPECT_FALSE(candidate_pair_stats.readable.is_defined());
+        EXPECT_EQ(*candidate_pair_stats.bytes_sent,
+                  static_cast<uint64_t>(info.sent_total_bytes));
+        EXPECT_EQ(*candidate_pair_stats.bytes_received,
+                  static_cast<uint64_t>(info.recv_total_bytes));
+        EXPECT_FALSE(candidate_pair_stats.total_rtt.is_defined());
+        EXPECT_EQ(*candidate_pair_stats.current_rtt,
+                  static_cast<double>(info.rtt) / 1000.0);
+        EXPECT_FALSE(
+            candidate_pair_stats.available_outgoing_bitrate.is_defined());
+        EXPECT_FALSE(
+            candidate_pair_stats.available_incoming_bitrate.is_defined());
+        EXPECT_FALSE(candidate_pair_stats.requests_received.is_defined());
+        EXPECT_EQ(*candidate_pair_stats.requests_sent,
+                  static_cast<uint64_t>(info.sent_ping_requests_total));
+        EXPECT_EQ(*candidate_pair_stats.responses_received,
+                  static_cast<uint64_t>(info.recv_ping_responses));
+        EXPECT_EQ(*candidate_pair_stats.responses_sent,
+                  static_cast<uint64_t>(info.sent_ping_responses));
+        EXPECT_FALSE(
+            candidate_pair_stats.retransmissions_received.is_defined());
+        EXPECT_FALSE(candidate_pair_stats.retransmissions_sent.is_defined());
+        EXPECT_FALSE(
+            candidate_pair_stats.consent_requests_received.is_defined());
+        EXPECT_FALSE(candidate_pair_stats.consent_requests_sent.is_defined());
+        EXPECT_FALSE(
+            candidate_pair_stats.consent_responses_received.is_defined());
+        EXPECT_FALSE(candidate_pair_stats.consent_responses_sent.is_defined());
+      }
+    }
   }
 
   void ExpectReportContainsCertificateInfo(
@@ -659,6 +721,43 @@
   ExpectReportContainsCandidate(report, *b_remote.get(), false);
 }
 
+TEST_F(RTCStatsCollectorTest, CollectRTCIceCandidatePairStats) {
+  std::unique_ptr<cricket::Candidate> local_candidate = CreateFakeCandidate(
+      "42.42.42.42", 42, "protocol", cricket::LOCAL_PORT_TYPE, 42);
+  std::unique_ptr<cricket::Candidate> remote_candidate = CreateFakeCandidate(
+      "42.42.42.42", 42, "protocol", cricket::LOCAL_PORT_TYPE, 42);
+
+  SessionStats session_stats;
+
+  cricket::ConnectionInfo connection_info;
+  connection_info.local_candidate = *local_candidate.get();
+  connection_info.remote_candidate = *remote_candidate.get();
+  connection_info.writable = true;
+  connection_info.sent_total_bytes = 42;
+  connection_info.recv_total_bytes = 1234;
+  connection_info.rtt = 1337;
+  connection_info.sent_ping_requests_total = 1010;
+  connection_info.recv_ping_responses = 4321;
+  connection_info.sent_ping_responses = 1000;
+
+  cricket::TransportChannelStats transport_channel_stats;
+  transport_channel_stats.connection_infos.push_back(connection_info);
+  session_stats.transport_stats["transport"].transport_name = "transport";
+  session_stats.transport_stats["transport"].channel_stats.push_back(
+      transport_channel_stats);
+
+  // Mock the session to return the desired candidates.
+  EXPECT_CALL(test_->session(), GetTransportStats(_)).WillRepeatedly(Invoke(
+      [this, &session_stats](SessionStats* stats) {
+        *stats = session_stats;
+        return true;
+      }));
+
+  rtc::scoped_refptr<const RTCStatsReport> report = GetStatsReport();
+  ExpectReportContainsCandidatePair(
+      report, session_stats.transport_stats["transport"]);
+}
+
 TEST_F(RTCStatsCollectorTest, CollectRTCPeerConnectionStats) {
   int64_t before = rtc::TimeUTCMicros();
   rtc::scoped_refptr<const RTCStatsReport> report = GetStatsReport();
diff --git a/webrtc/api/stats/rtcstats_objects.h b/webrtc/api/stats/rtcstats_objects.h
index e827d01..4738fe8 100644
--- a/webrtc/api/stats/rtcstats_objects.h
+++ b/webrtc/api/stats/rtcstats_objects.h
@@ -17,6 +17,16 @@
 
 namespace webrtc {
 
+// https://w3c.github.io/webrtc-stats/#dom-rtcstatsicecandidatepairstate
+struct RTCStatsIceCandidatePairState {
+  static const char* kFrozen;
+  static const char* kWaiting;
+  static const char* kInProgress;
+  static const char* kFailed;
+  static const char* kSucceeded;
+  static const char* kCancelled;
+};
+
 // https://www.w3.org/TR/webrtc/#rtcicecandidatetype-enum
 struct RTCIceCandidateType {
   static const char* kHost;
@@ -25,6 +35,43 @@
   static const char* kRelay;
 };
 
+class RTCIceCandidatePairStats : public RTCStats {
+ public:
+  WEBRTC_RTCSTATS_DECL();
+
+  RTCIceCandidatePairStats(const std::string& id, int64_t timestamp_us);
+  RTCIceCandidatePairStats(std::string&& id, int64_t timestamp_us);
+  RTCIceCandidatePairStats(const RTCIceCandidatePairStats& other);
+  ~RTCIceCandidatePairStats() override;
+
+  RTCStatsMember<std::string> transport_id;
+  RTCStatsMember<std::string> local_candidate_id;
+  RTCStatsMember<std::string> remote_candidate_id;
+  // TODO(hbos): Support enum types?
+  // "RTCStatsMember<RTCStatsIceCandidatePairState>"?
+  RTCStatsMember<std::string> state;
+  RTCStatsMember<uint64_t> priority;
+  RTCStatsMember<bool> nominated;
+  RTCStatsMember<bool> writable;
+  RTCStatsMember<bool> readable;
+  RTCStatsMember<uint64_t> bytes_sent;
+  RTCStatsMember<uint64_t> bytes_received;
+  RTCStatsMember<double> total_rtt;
+  RTCStatsMember<double> current_rtt;
+  RTCStatsMember<double> available_outgoing_bitrate;
+  RTCStatsMember<double> available_incoming_bitrate;
+  RTCStatsMember<uint64_t> requests_received;
+  RTCStatsMember<uint64_t> requests_sent;
+  RTCStatsMember<uint64_t> responses_received;
+  RTCStatsMember<uint64_t> responses_sent;
+  RTCStatsMember<uint64_t> retransmissions_received;
+  RTCStatsMember<uint64_t> retransmissions_sent;
+  RTCStatsMember<uint64_t> consent_requests_received;
+  RTCStatsMember<uint64_t> consent_requests_sent;
+  RTCStatsMember<uint64_t> consent_responses_received;
+  RTCStatsMember<uint64_t> consent_responses_sent;
+};
+
 // https://w3c.github.io/webrtc-stats/#icecandidate-dict*
 class RTCIceCandidateStats : public RTCStats {
  public: