RTCIceCandidateStats[1] added.

The RTCStatsCollector collects candidates from candidate pairs. Note
that there may be other candidates that are not paired with anything,
stats for these should also be produced before closing crbug.com/632723.

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

BUG=chromium:627816, chromium:632723

Review-Url: https://codereview.webrtc.org/2384143002
Cr-Commit-Position: refs/heads/master@{#14565}
diff --git a/webrtc/api/rtcstatscollector.cc b/webrtc/api/rtcstatscollector.cc
index 084eab1..2d858c0 100644
--- a/webrtc/api/rtcstatscollector.cc
+++ b/webrtc/api/rtcstatscollector.cc
@@ -18,9 +18,24 @@
 #include "webrtc/api/webrtcsession.h"
 #include "webrtc/base/checks.h"
 #include "webrtc/base/sslidentity.h"
+#include "webrtc/p2p/base/candidate.h"
+#include "webrtc/p2p/base/port.h"
 
 namespace webrtc {
 
+const char* CandidateTypeToRTCIceCandidateType(const std::string& type) {
+  if (type == cricket::LOCAL_PORT_TYPE)
+    return RTCIceCandidateType::kHost;
+  if (type == cricket::STUN_PORT_TYPE)
+    return RTCIceCandidateType::kSrflx;
+  if (type == cricket::PRFLX_PORT_TYPE)
+    return RTCIceCandidateType::kPrflx;
+  if (type == cricket::RELAY_PORT_TYPE)
+    return RTCIceCandidateType::kRelay;
+  RTC_NOTREACHED();
+  return nullptr;
+}
+
 rtc::scoped_refptr<RTCStatsCollector> RTCStatsCollector::Create(
     PeerConnection* pc, int64_t cache_lifetime_us) {
   return rtc::scoped_refptr<RTCStatsCollector>(
@@ -93,6 +108,8 @@
   SessionStats session_stats;
   if (pc_->session()->GetTransportStats(&session_stats)) {
     ProduceCertificateStats_s(timestamp_us, session_stats, report.get());
+    ProduceIceCandidateAndPairStats_s(timestamp_us, session_stats,
+                                      report.get());
   }
   ProducePeerConnectionStats_s(timestamp_us, report.get());
 
@@ -201,6 +218,59 @@
   }
 }
 
+void RTCStatsCollector::ProduceIceCandidateAndPairStats_s(
+      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
+        // 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(
+            timestamp_us, info.local_candidate, true, report);
+        ProduceIceCandidateStats_s(
+            timestamp_us, info.remote_candidate, false, report);
+      }
+    }
+  }
+}
+
+const std::string& RTCStatsCollector::ProduceIceCandidateStats_s(
+    int64_t timestamp_us, const cricket::Candidate& candidate, bool is_local,
+    RTCStatsReport* report) const {
+  RTC_DCHECK(signaling_thread_->IsCurrent());
+  const std::string& id = "RTCIceCandidate_" + candidate.id();
+  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));
+    }
+    candidate_stats->ip = candidate.address().ipaddr().ToString();
+    candidate_stats->port = static_cast<int32_t>(candidate.address().port());
+    candidate_stats->protocol = candidate.protocol();
+    candidate_stats->candidate_type = CandidateTypeToRTCIceCandidateType(
+        candidate.type());
+    candidate_stats->priority = static_cast<int32_t>(candidate.priority());
+    // TODO(hbos): Define candidate_stats->url. crbug.com/632723
+
+    stats = candidate_stats.get();
+    report->AddStats(std::move(candidate_stats));
+  }
+  RTC_DCHECK_EQ(stats->type(), is_local ? RTCLocalIceCandidateStats::kType
+                                        : RTCRemoteIceCandidateStats::kType);
+  return stats->id();
+}
+
 void RTCStatsCollector::ProducePeerConnectionStats_s(
     int64_t timestamp_us, RTCStatsReport* report) const {
   RTC_DCHECK(signaling_thread_->IsCurrent());
diff --git a/webrtc/api/rtcstatscollector.h b/webrtc/api/rtcstatscollector.h
index 2f5ed86..6443d13 100644
--- a/webrtc/api/rtcstatscollector.h
+++ b/webrtc/api/rtcstatscollector.h
@@ -21,10 +21,12 @@
 #include "webrtc/base/scoped_ref_ptr.h"
 #include "webrtc/base/timeutils.h"
 
+namespace cricket {
+class Candidate;
+}  // namespace cricket
+
 namespace rtc {
-
 class SSLCertificate;
-
 }  // namespace rtc
 
 namespace webrtc {
@@ -76,12 +78,22 @@
   void AddPartialResults_s(rtc::scoped_refptr<RTCStatsReport> partial_report);
   void DeliverCachedReport();
 
+  // Produces |RTCCertificateStats|.
   void ProduceCertificateStats_s(
       int64_t timestamp_us, const SessionStats& session_stats,
       RTCStatsReport* report) const;
   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
+  void ProduceIceCandidateAndPairStats_s(
+      int64_t timestamp_us, const SessionStats& session_stats,
+      RTCStatsReport* report) const;
+  const std::string& ProduceIceCandidateStats_s(
+      int64_t timestamp_us, const cricket::Candidate& candidate, bool is_local,
+      RTCStatsReport* report) const;
+  // Produces |RTCPeerConnectionStats|.
   void ProducePeerConnectionStats_s(
       int64_t timestamp_us, RTCStatsReport* report) const;
 
@@ -105,6 +117,9 @@
   rtc::scoped_refptr<const RTCStatsReport> cached_report_;
 };
 
+// Helper function, exposed for unittests.
+const char* CandidateTypeToRTCIceCandidateType(const std::string& type);
+
 }  // namespace webrtc
 
 #endif  // WEBRTC_API_RTCSTATSCOLLECTOR_H_
diff --git a/webrtc/api/rtcstatscollector_unittest.cc b/webrtc/api/rtcstatscollector_unittest.cc
index 14abbd2..7103864 100644
--- a/webrtc/api/rtcstatscollector_unittest.cc
+++ b/webrtc/api/rtcstatscollector_unittest.cc
@@ -25,10 +25,12 @@
 #include "webrtc/base/fakesslidentity.h"
 #include "webrtc/base/gunit.h"
 #include "webrtc/base/logging.h"
+#include "webrtc/base/socketaddress.h"
 #include "webrtc/base/thread_checker.h"
 #include "webrtc/base/timedelta.h"
 #include "webrtc/base/timeutils.h"
 #include "webrtc/media/base/fakemediaengine.h"
+#include "webrtc/p2p/base/port.h"
 
 using testing::_;
 using testing::Invoke;
@@ -91,6 +93,20 @@
   return info;
 }
 
+std::unique_ptr<cricket::Candidate> CreateFakeCandidate(
+    const std::string& hostname,
+    int port,
+    const std::string& protocol,
+    const std::string& candidate_type,
+    uint32_t priority) {
+  std::unique_ptr<cricket::Candidate> candidate(new cricket::Candidate());
+  candidate->set_address(rtc::SocketAddress(hostname, port));
+  candidate->set_protocol(protocol);
+  candidate->set_type(candidate_type);
+  candidate->set_priority(priority);
+  return candidate;
+}
+
 class RTCStatsCollectorTestHelper : public SetSessionDescriptionObserver {
  public:
   RTCStatsCollectorTestHelper()
@@ -111,6 +127,10 @@
     EXPECT_CALL(pc_, sctp_data_channels()).WillRepeatedly(
         ReturnRef(data_channels_));
     EXPECT_CALL(session_, GetTransportStats(_)).WillRepeatedly(Return(false));
+    EXPECT_CALL(session_, GetLocalCertificate(_, _)).WillRepeatedly(
+        Return(false));
+    EXPECT_CALL(session_, GetRemoteSSLCertificate_ReturnsRawPointer(_))
+        .WillRepeatedly(Return(nullptr));
   }
 
   rtc::ScopedFakeClock& fake_clock() { return fake_clock_; }
@@ -308,6 +328,30 @@
     return callback->report();
   }
 
+  void ExpectReportContainsCandidate(
+      const rtc::scoped_refptr<const RTCStatsReport>& report,
+      const cricket::Candidate& candidate,
+      bool is_local) {
+    const RTCStats* stats =
+        report->Get("RTCIceCandidate_" + candidate.id());
+    EXPECT_TRUE(stats);
+    const RTCIceCandidateStats* candidate_stats;
+    if (is_local)
+        candidate_stats = &stats->cast_to<RTCLocalIceCandidateStats>();
+    else
+        candidate_stats = &stats->cast_to<RTCRemoteIceCandidateStats>();
+    EXPECT_EQ(*candidate_stats->ip, candidate.address().ipaddr().ToString());
+    EXPECT_EQ(*candidate_stats->port,
+              static_cast<int32_t>(candidate.address().port()));
+    EXPECT_EQ(*candidate_stats->protocol, candidate.protocol());
+    EXPECT_EQ(*candidate_stats->candidate_type,
+              CandidateTypeToRTCIceCandidateType(candidate.type()));
+    EXPECT_EQ(*candidate_stats->priority,
+              static_cast<int32_t>(candidate.priority()));
+    // TODO(hbos): Define candidate_stats->url. crbug.com/632723
+    EXPECT_FALSE(candidate_stats->url.is_defined());
+  }
+
   void ExpectReportContainsCertificateInfo(
       const rtc::scoped_refptr<const RTCStatsReport>& report,
       const CertificateInfo& cert_info) {
@@ -534,6 +578,84 @@
   ExpectReportContainsCertificateInfo(report, *remote_certinfo.get());
 }
 
+TEST_F(RTCStatsCollectorTest, CollectRTCIceCandidateStats) {
+  // Candidates in the first transport stats.
+  std::unique_ptr<cricket::Candidate> a_local_host = CreateFakeCandidate(
+      "1.2.3.4", 5,
+      "a_local_host's protocol",
+      cricket::LOCAL_PORT_TYPE,
+      0);
+  std::unique_ptr<cricket::Candidate> a_remote_srflx = CreateFakeCandidate(
+      "6.7.8.9", 10,
+      "remote_srflx's protocol",
+      cricket::STUN_PORT_TYPE,
+      1);
+  std::unique_ptr<cricket::Candidate> a_local_prflx = CreateFakeCandidate(
+      "11.12.13.14", 15,
+      "a_local_prflx's protocol",
+      cricket::PRFLX_PORT_TYPE,
+      2);
+  std::unique_ptr<cricket::Candidate> a_remote_relay = CreateFakeCandidate(
+      "16.17.18.19", 20,
+      "a_remote_relay's protocol",
+      cricket::RELAY_PORT_TYPE,
+      3);
+  // Candidates in the second transport stats.
+  std::unique_ptr<cricket::Candidate> b_local = CreateFakeCandidate(
+      "42.42.42.42", 42,
+      "b_local's protocol",
+      cricket::LOCAL_PORT_TYPE,
+      42);
+  std::unique_ptr<cricket::Candidate> b_remote = CreateFakeCandidate(
+      "42.42.42.42", 42,
+      "b_remote's protocol",
+      cricket::LOCAL_PORT_TYPE,
+      42);
+
+  SessionStats session_stats;
+
+  cricket::TransportChannelStats a_transport_channel_stats;
+  a_transport_channel_stats.connection_infos.push_back(
+      cricket::ConnectionInfo());
+  a_transport_channel_stats.connection_infos[0].local_candidate =
+      *a_local_host.get();
+  a_transport_channel_stats.connection_infos[0].remote_candidate =
+      *a_remote_srflx.get();
+  a_transport_channel_stats.connection_infos.push_back(
+      cricket::ConnectionInfo());
+  a_transport_channel_stats.connection_infos[1].local_candidate =
+      *a_local_prflx.get();
+  a_transport_channel_stats.connection_infos[1].remote_candidate =
+      *a_remote_relay.get();
+  session_stats.transport_stats["a"].channel_stats.push_back(
+      a_transport_channel_stats);
+
+  cricket::TransportChannelStats b_transport_channel_stats;
+  b_transport_channel_stats.connection_infos.push_back(
+      cricket::ConnectionInfo());
+  b_transport_channel_stats.connection_infos[0].local_candidate =
+      *b_local.get();
+  b_transport_channel_stats.connection_infos[0].remote_candidate =
+      *b_remote.get();
+  session_stats.transport_stats["b"].channel_stats.push_back(
+      b_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();
+  ExpectReportContainsCandidate(report, *a_local_host.get(), true);
+  ExpectReportContainsCandidate(report, *a_remote_srflx.get(), false);
+  ExpectReportContainsCandidate(report, *a_local_prflx.get(), true);
+  ExpectReportContainsCandidate(report, *a_remote_relay.get(), false);
+  ExpectReportContainsCandidate(report, *b_local.get(), true);
+  ExpectReportContainsCandidate(report, *b_remote.get(), false);
+}
+
 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 f816d48..e827d01 100644
--- a/webrtc/api/stats/rtcstats_objects.h
+++ b/webrtc/api/stats/rtcstats_objects.h
@@ -17,8 +17,57 @@
 
 namespace webrtc {
 
+// https://www.w3.org/TR/webrtc/#rtcicecandidatetype-enum
+struct RTCIceCandidateType {
+  static const char* kHost;
+  static const char* kSrflx;
+  static const char* kPrflx;
+  static const char* kRelay;
+};
+
+// https://w3c.github.io/webrtc-stats/#icecandidate-dict*
+class RTCIceCandidateStats : public RTCStats {
+ public:
+  WEBRTC_RTCSTATS_DECL();
+
+  RTCIceCandidateStats(const RTCIceCandidateStats& other);
+  ~RTCIceCandidateStats() override;
+
+  RTCStatsMember<std::string> ip;
+  RTCStatsMember<int32_t> port;
+  RTCStatsMember<std::string> protocol;
+  // TODO(hbos): Support enum types? "RTCStatsMember<RTCIceCandidateType>"?
+  RTCStatsMember<std::string> candidate_type;
+  RTCStatsMember<int32_t> priority;
+  RTCStatsMember<std::string> url;
+
+ protected:
+  RTCIceCandidateStats(const std::string& id, int64_t timestamp_us);
+  RTCIceCandidateStats(std::string&& id, int64_t timestamp_us);
+};
+
+// In the spec both local and remote varieties are of type RTCIceCandidateStats.
+// But here we define them as subclasses of |RTCIceCandidateStats| because the
+// |kType| need to be different ("RTCStatsType type") in the local/remote case.
+// https://w3c.github.io/webrtc-stats/#rtcstatstype-str*
+class RTCLocalIceCandidateStats final : public RTCIceCandidateStats {
+ public:
+  static const char kType[];
+  RTCLocalIceCandidateStats(const std::string& id, int64_t timestamp_us);
+  RTCLocalIceCandidateStats(std::string&& id, int64_t timestamp_us);
+  const char* type() const override;
+};
+
+class RTCRemoteIceCandidateStats final : public RTCIceCandidateStats {
+ public:
+  static const char kType[];
+  RTCRemoteIceCandidateStats(const std::string& id, int64_t timestamp_us);
+  RTCRemoteIceCandidateStats(std::string&& id, int64_t timestamp_us);
+  const char* type() const override;
+};
+
 // https://w3c.github.io/webrtc-stats/#certificatestats-dict*
-class RTCCertificateStats : public RTCStats {
+class RTCCertificateStats final : public RTCStats {
  public:
   WEBRTC_RTCSTATS_DECL();
 
@@ -35,7 +84,7 @@
 
 // https://w3c.github.io/webrtc-stats/#pcstats-dict*
 // TODO(hbos): Tracking bug crbug.com/636818
-class RTCPeerConnectionStats : public RTCStats {
+class RTCPeerConnectionStats final : public RTCStats {
  public:
   WEBRTC_RTCSTATS_DECL();