RTCTransportStats[1] added, supporting all members.

Address TODO in rtcstatscollector_unittest.cc before closing 653873.

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

BUG=chromium:653873, chromium:633550, chromium:627816

Review-Url: https://codereview.webrtc.org/2408363002
Cr-Commit-Position: refs/heads/master@{#14740}
diff --git a/webrtc/api/rtcstatscollector.cc b/webrtc/api/rtcstatscollector.cc
index 71f4725..9052de4 100644
--- a/webrtc/api/rtcstatscollector.cc
+++ b/webrtc/api/rtcstatscollector.cc
@@ -17,14 +17,30 @@
 #include "webrtc/api/peerconnection.h"
 #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/p2pconstants.h"
 #include "webrtc/p2p/base/port.h"
 
 namespace webrtc {
 
 namespace {
 
+std::string RTCCertificateIDFromFingerprint(const std::string& fingerprint) {
+  return "RTCCertificate_" + fingerprint;
+}
+
+std::string RTCIceCandidatePairStatsIDFromConnectionInfo(
+    const cricket::ConnectionInfo& info) {
+  return "RTCIceCandidatePair_" + info.local_candidate.id() + "_" +
+      info.remote_candidate.id();
+}
+
+std::string RTCTransportStatsIDFromTransportChannel(
+    const std::string& transport_name, int channel_component) {
+  return "RTCTransport_" + transport_name + "_" +
+      rtc::ToString<>(channel_component);
+}
+
 const char* CandidateTypeToRTCIceCandidateType(const std::string& type) {
   if (type == cricket::LOCAL_PORT_TYPE)
     return RTCIceCandidateType::kHost;
@@ -128,9 +144,15 @@
 
   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());
+    std::map<std::string, CertificateStatsPair> transport_cert_stats =
+        PrepareTransportCertificateStats_s(session_stats);
+
+    ProduceCertificateStats_s(
+        timestamp_us, transport_cert_stats, report.get());
+    ProduceIceCandidateAndPairStats_s(
+        timestamp_us, session_stats, report.get());
+    ProduceTransportStats_s(
+        timestamp_us, session_stats, transport_cert_stats, report.get());
   }
   ProduceDataChannelStats_s(timestamp_us, report.get());
   ProducePeerConnectionStats_s(timestamp_us, report.get());
@@ -199,44 +221,38 @@
 }
 
 void RTCStatsCollector::ProduceCertificateStats_s(
-    int64_t timestamp_us, const SessionStats& session_stats,
+    int64_t timestamp_us,
+    const std::map<std::string, CertificateStatsPair>& transport_cert_stats,
     RTCStatsReport* report) const {
   RTC_DCHECK(signaling_thread_->IsCurrent());
-  for (const auto& transport_stats : session_stats.transport_stats) {
-    rtc::scoped_refptr<rtc::RTCCertificate> local_certificate;
-    if (pc_->session()->GetLocalCertificate(
-        transport_stats.second.transport_name, &local_certificate)) {
-      ProduceCertificateStatsFromSSLCertificateAndChain_s(
-          timestamp_us, local_certificate->ssl_certificate(), report);
+  for (const auto& kvp : transport_cert_stats) {
+    if (kvp.second.local) {
+      ProduceCertificateStatsFromSSLCertificateStats_s(
+          timestamp_us, *kvp.second.local.get(), report);
     }
-    std::unique_ptr<rtc::SSLCertificate> remote_certificate =
-        pc_->session()->GetRemoteSSLCertificate(
-            transport_stats.second.transport_name);
-    if (remote_certificate) {
-      ProduceCertificateStatsFromSSLCertificateAndChain_s(
-          timestamp_us, *remote_certificate.get(), report);
+    if (kvp.second.remote) {
+      ProduceCertificateStatsFromSSLCertificateStats_s(
+          timestamp_us, *kvp.second.remote.get(), report);
     }
   }
 }
 
-void RTCStatsCollector::ProduceCertificateStatsFromSSLCertificateAndChain_s(
-    int64_t timestamp_us, const rtc::SSLCertificate& certificate,
+void RTCStatsCollector::ProduceCertificateStatsFromSSLCertificateStats_s(
+    int64_t timestamp_us, const rtc::SSLCertificateStats& certificate_stats,
     RTCStatsReport* report) const {
   RTC_DCHECK(signaling_thread_->IsCurrent());
-  std::unique_ptr<rtc::SSLCertificateStats> ssl_stats =
-      certificate.GetStats();
-  RTCCertificateStats* prev_stats = nullptr;
-  for (rtc::SSLCertificateStats* s = ssl_stats.get(); s;
+  RTCCertificateStats* prev_certificate_stats = nullptr;
+  for (const rtc::SSLCertificateStats* s = &certificate_stats; s;
        s = s->issuer.get()) {
-    RTCCertificateStats* stats = new RTCCertificateStats(
-        "RTCCertificate_" + s->fingerprint, timestamp_us);
-    stats->fingerprint = s->fingerprint;
-    stats->fingerprint_algorithm = s->fingerprint_algorithm;
-    stats->base64_certificate = s->base64_certificate;
-    if (prev_stats)
-      prev_stats->issuer_certificate_id = stats->id();
-    report->AddStats(std::unique_ptr<RTCCertificateStats>(stats));
-    prev_stats = stats;
+    RTCCertificateStats* certificate_stats = new RTCCertificateStats(
+        RTCCertificateIDFromFingerprint(s->fingerprint), timestamp_us);
+    certificate_stats->fingerprint = s->fingerprint;
+    certificate_stats->fingerprint_algorithm = s->fingerprint_algorithm;
+    certificate_stats->base64_certificate = s->base64_certificate;
+    if (prev_certificate_stats)
+      prev_certificate_stats->issuer_certificate_id = certificate_stats->id();
+    report->AddStats(std::unique_ptr<RTCCertificateStats>(certificate_stats));
+    prev_certificate_stats = certificate_stats;
   }
 }
 
@@ -270,10 +286,10 @@
     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));
+            new RTCIceCandidatePairStats(
+                RTCIceCandidatePairStatsIDFromConnectionInfo(info),
+                timestamp_us));
 
         // TODO(hbos): Set all of the |RTCIceCandidatePairStats|'s members,
         // crbug.com/633550.
@@ -381,6 +397,98 @@
   report->AddStats(std::move(stats));
 }
 
+void RTCStatsCollector::ProduceTransportStats_s(
+    int64_t timestamp_us, const SessionStats& session_stats,
+    const std::map<std::string, CertificateStatsPair>& transport_cert_stats,
+    RTCStatsReport* report) const {
+  RTC_DCHECK(signaling_thread_->IsCurrent());
+  for (const auto& transport : session_stats.transport_stats) {
+    // Get reference to RTCP channel, if it exists.
+    std::string rtcp_transport_stats_id;
+    for (const auto& channel_stats : transport.second.channel_stats) {
+      if (channel_stats.component ==
+          cricket::ICE_CANDIDATE_COMPONENT_RTCP) {
+        rtcp_transport_stats_id = RTCTransportStatsIDFromTransportChannel(
+            transport.second.transport_name, channel_stats.component);
+        break;
+      }
+    }
+
+    // Get reference to local and remote certificates of this transport, if they
+    // exist.
+    const auto& certificate_stats_it = transport_cert_stats.find(
+        transport.second.transport_name);
+    RTC_DCHECK(certificate_stats_it != transport_cert_stats.cend());
+    std::string local_certificate_id;
+    if (certificate_stats_it->second.local) {
+      local_certificate_id = RTCCertificateIDFromFingerprint(
+          certificate_stats_it->second.local->fingerprint);
+    }
+    std::string remote_certificate_id;
+    if (certificate_stats_it->second.remote) {
+      remote_certificate_id = RTCCertificateIDFromFingerprint(
+          certificate_stats_it->second.remote->fingerprint);
+    }
+
+    // There is one transport stats for each channel.
+    for (const auto& channel_stats : transport.second.channel_stats) {
+      std::unique_ptr<RTCTransportStats> transport_stats(
+          new RTCTransportStats(
+              RTCTransportStatsIDFromTransportChannel(
+                  transport.second.transport_name, channel_stats.component),
+              timestamp_us));
+      transport_stats->bytes_sent = 0;
+      transport_stats->bytes_received = 0;
+      transport_stats->active_connection = false;
+      for (const cricket::ConnectionInfo& info :
+           channel_stats.connection_infos) {
+        *transport_stats->bytes_sent += info.sent_total_bytes;
+        *transport_stats->bytes_received += info.recv_total_bytes;
+        if (info.best_connection) {
+          transport_stats->active_connection = true;
+          transport_stats->selected_candidate_pair_id =
+              RTCIceCandidatePairStatsIDFromConnectionInfo(info);
+        }
+      }
+      if (channel_stats.component != cricket::ICE_CANDIDATE_COMPONENT_RTCP &&
+          !rtcp_transport_stats_id.empty()) {
+        transport_stats->rtcp_transport_stats_id = rtcp_transport_stats_id;
+      }
+      if (!local_certificate_id.empty())
+        transport_stats->local_certificate_id = local_certificate_id;
+      if (!remote_certificate_id.empty())
+        transport_stats->remote_certificate_id = remote_certificate_id;
+      report->AddStats(std::move(transport_stats));
+    }
+  }
+}
+
+std::map<std::string, RTCStatsCollector::CertificateStatsPair>
+RTCStatsCollector::PrepareTransportCertificateStats_s(
+    const SessionStats& session_stats) const {
+  RTC_DCHECK(signaling_thread_->IsCurrent());
+  std::map<std::string, CertificateStatsPair> transport_cert_stats;
+  for (const auto& transport_stats : session_stats.transport_stats) {
+    CertificateStatsPair certificate_stats_pair;
+    rtc::scoped_refptr<rtc::RTCCertificate> local_certificate;
+    if (pc_->session()->GetLocalCertificate(
+        transport_stats.second.transport_name, &local_certificate)) {
+      certificate_stats_pair.local =
+          local_certificate->ssl_certificate().GetStats();
+    }
+    std::unique_ptr<rtc::SSLCertificate> remote_certificate =
+        pc_->session()->GetRemoteSSLCertificate(
+            transport_stats.second.transport_name);
+    if (remote_certificate) {
+      certificate_stats_pair.remote = remote_certificate->GetStats();
+    }
+    transport_cert_stats.insert(
+        std::make_pair(transport_stats.second.transport_name,
+                       std::move(certificate_stats_pair)));
+  }
+  return transport_cert_stats;
+}
+
 const char* CandidateTypeToRTCIceCandidateTypeForTesting(
     const std::string& type) {
   return CandidateTypeToRTCIceCandidateType(type);
diff --git a/webrtc/api/rtcstatscollector.h b/webrtc/api/rtcstatscollector.h
index ae04e61..de374fc 100644
--- a/webrtc/api/rtcstatscollector.h
+++ b/webrtc/api/rtcstatscollector.h
@@ -11,6 +11,7 @@
 #ifndef WEBRTC_API_RTCSTATSCOLLECTOR_H_
 #define WEBRTC_API_RTCSTATSCOLLECTOR_H_
 
+#include <map>
 #include <memory>
 #include <vector>
 
@@ -20,6 +21,7 @@
 #include "webrtc/base/asyncinvoker.h"
 #include "webrtc/base/refcount.h"
 #include "webrtc/base/scoped_ref_ptr.h"
+#include "webrtc/base/sslidentity.h"
 #include "webrtc/base/timeutils.h"
 
 namespace cricket {
@@ -76,15 +78,21 @@
       const rtc::scoped_refptr<RTCStatsReport>& partial_report);
 
  private:
+  struct CertificateStatsPair {
+    std::unique_ptr<rtc::SSLCertificateStats> local;
+    std::unique_ptr<rtc::SSLCertificateStats> remote;
+  };
+
   void AddPartialResults_s(rtc::scoped_refptr<RTCStatsReport> partial_report);
   void DeliverCachedReport();
 
   // Produces |RTCCertificateStats|.
   void ProduceCertificateStats_s(
-      int64_t timestamp_us, const SessionStats& session_stats,
+      int64_t timestamp_us,
+      const std::map<std::string, CertificateStatsPair>& transport_cert_stats,
       RTCStatsReport* report) const;
-  void ProduceCertificateStatsFromSSLCertificateAndChain_s(
-      int64_t timestamp_us, const rtc::SSLCertificate& certificate,
+  void ProduceCertificateStatsFromSSLCertificateStats_s(
+      int64_t timestamp_us, const rtc::SSLCertificateStats& certificate_stats,
       RTCStatsReport* report) const;
   // Produces |RTCDataChannelStats|.
   void ProduceDataChannelStats_s(
@@ -99,6 +107,15 @@
   // Produces |RTCPeerConnectionStats|.
   void ProducePeerConnectionStats_s(
       int64_t timestamp_us, RTCStatsReport* report) const;
+  // Produces |RTCTransportStats|.
+  void ProduceTransportStats_s(
+      int64_t timestamp_us, const SessionStats& session_stats,
+      const std::map<std::string, CertificateStatsPair>& transport_cert_stats,
+      RTCStatsReport* report) const;
+
+  // Helper function to stats-producing functions.
+  std::map<std::string, CertificateStatsPair>
+  PrepareTransportCertificateStats_s(const SessionStats& session_stats) const;
 
   PeerConnection* const pc_;
   rtc::Thread* const signaling_thread_;
diff --git a/webrtc/api/rtcstatscollector_unittest.cc b/webrtc/api/rtcstatscollector_unittest.cc
index fb07e0b..0e7860a 100644
--- a/webrtc/api/rtcstatscollector_unittest.cc
+++ b/webrtc/api/rtcstatscollector_unittest.cc
@@ -31,6 +31,7 @@
 #include "webrtc/base/timeutils.h"
 #include "webrtc/logging/rtc_event_log/rtc_event_log.h"
 #include "webrtc/media/base/fakemediaengine.h"
+#include "webrtc/p2p/base/p2pconstants.h"
 #include "webrtc/p2p/base/port.h"
 
 using testing::_;
@@ -368,7 +369,7 @@
         const std::string& id = "RTCIceCandidatePair_" +
             info.local_candidate.id() + "_" + info.remote_candidate.id();
         const RTCStats* stats = report->Get(id);
-        EXPECT_TRUE(stats);
+        ASSERT_TRUE(stats);
         const RTCIceCandidatePairStats& candidate_pair_stats =
             stats->cast_to<RTCIceCandidatePairStats>();
 
@@ -427,7 +428,7 @@
     for (size_t i = 0; i < cert_info.fingerprints.size(); ++i) {
       const RTCStats* stats = report->Get(
           "RTCCertificate_" + cert_info.fingerprints[i]);
-      EXPECT_TRUE(stats);
+      ASSERT_TRUE(stats);
       const RTCCertificateStats& cert_stats =
           stats->cast_to<const RTCCertificateStats>();
       EXPECT_EQ(*cert_stats.fingerprint, cert_info.fingerprints[i]);
@@ -442,6 +443,72 @@
     }
   }
 
+  void ExpectReportContainsTransportStats(
+      const rtc::scoped_refptr<const RTCStatsReport>& report,
+      const cricket::TransportStats& transport,
+      const CertificateInfo* local_certinfo,
+      const CertificateInfo* remote_certinfo) {
+    std::string rtcp_transport_stats_id;
+    for (const auto& channel_stats : transport.channel_stats) {
+      if (channel_stats.component == cricket::ICE_CANDIDATE_COMPONENT_RTCP) {
+        rtcp_transport_stats_id = "RTCTransport_" + transport.transport_name +
+            "_" + rtc::ToString<>(cricket::ICE_CANDIDATE_COMPONENT_RTCP);
+      }
+    }
+    for (const auto& channel_stats : transport.channel_stats) {
+      const cricket::ConnectionInfo* best_connection_info = nullptr;
+      const RTCStats* stats = report->Get(
+          "RTCTransport_" + transport.transport_name + "_" +
+          rtc::ToString<>(channel_stats.component));
+      ASSERT_TRUE(stats);
+      const RTCTransportStats& transport_stats =
+          stats->cast_to<const RTCTransportStats>();
+      uint64_t bytes_sent = 0;
+      uint64_t bytes_received = 0;
+      for (const cricket::ConnectionInfo& info :
+           channel_stats.connection_infos) {
+        bytes_sent += info.sent_total_bytes;
+        bytes_received += info.recv_total_bytes;
+        if (info.best_connection)
+          best_connection_info = &info;
+      }
+      EXPECT_EQ(*transport_stats.bytes_sent, bytes_sent);
+      EXPECT_EQ(*transport_stats.bytes_received, bytes_received);
+      if (best_connection_info) {
+        EXPECT_EQ(*transport_stats.active_connection, true);
+        // TODO(hbos): Instead of testing how the ID looks, test that the
+        // corresponding pair's IP addresses are equal to the IP addresses of
+        // the |best_connection_info| data. crbug.com/653873
+        EXPECT_EQ(*transport_stats.selected_candidate_pair_id,
+                  "RTCIceCandidatePair_" +
+                  best_connection_info->local_candidate.id() + "_" +
+                  best_connection_info->remote_candidate.id());
+        EXPECT_TRUE(report->Get(*transport_stats.selected_candidate_pair_id));
+      } else {
+        EXPECT_EQ(*transport_stats.active_connection, false);
+        EXPECT_FALSE(transport_stats.selected_candidate_pair_id.is_defined());
+      }
+      if (channel_stats.component != cricket::ICE_CANDIDATE_COMPONENT_RTCP &&
+          !rtcp_transport_stats_id.empty()) {
+        EXPECT_EQ(*transport_stats.rtcp_transport_stats_id,
+                  rtcp_transport_stats_id);
+      } else {
+        EXPECT_FALSE(transport_stats.rtcp_transport_stats_id.is_defined());
+      }
+      if (local_certinfo && remote_certinfo) {
+        EXPECT_EQ(*transport_stats.local_certificate_id,
+                  "RTCCertificate_" + local_certinfo->fingerprints[0]);
+        EXPECT_EQ(*transport_stats.remote_certificate_id,
+                  "RTCCertificate_" + remote_certinfo->fingerprints[0]);
+        EXPECT_TRUE(report->Get(*transport_stats.local_certificate_id));
+        EXPECT_TRUE(report->Get(*transport_stats.remote_certificate_id));
+      } else {
+        EXPECT_FALSE(transport_stats.local_certificate_id.is_defined());
+        EXPECT_FALSE(transport_stats.remote_certificate_id.is_defined());
+      }
+    }
+  }
+
   void ExpectReportContainsDataChannel(
       const rtc::scoped_refptr<const RTCStatsReport>& report,
       const DataChannel& data_channel) {
@@ -849,7 +916,7 @@
   EXPECT_EQ(report->GetStatsOfType<RTCPeerConnectionStats>().size(),
             static_cast<size_t>(1)) << "Expecting 1 RTCPeerConnectionStats.";
   stats = report->Get("RTCPeerConnection");
-  EXPECT_TRUE(stats);
+  ASSERT_TRUE(stats);
   {
     // Expected stats with the above four data channels
     // TODO(hbos): When the |RTCPeerConnectionStats| is the number of data
@@ -863,6 +930,105 @@
   }
 }
 
+TEST_F(RTCStatsCollectorTest, CollectRTCTransportStats) {
+  std::unique_ptr<cricket::Candidate> rtp_local_candidate = CreateFakeCandidate(
+      "42.42.42.42", 42, "protocol", cricket::LOCAL_PORT_TYPE, 42);
+  std::unique_ptr<cricket::Candidate> rtp_remote_candidate =
+      CreateFakeCandidate("42.42.42.42", 42, "protocol",
+                          cricket::LOCAL_PORT_TYPE, 42);
+  std::unique_ptr<cricket::Candidate> rtcp_local_candidate =
+      CreateFakeCandidate("42.42.42.42", 42, "protocol",
+                          cricket::LOCAL_PORT_TYPE, 42);
+  std::unique_ptr<cricket::Candidate> rtcp_remote_candidate =
+      CreateFakeCandidate("42.42.42.42", 42, "protocol",
+                          cricket::LOCAL_PORT_TYPE, 42);
+
+  SessionStats session_stats;
+  session_stats.transport_stats["transport"].transport_name = "transport";
+
+  cricket::ConnectionInfo rtp_connection_info;
+  rtp_connection_info.best_connection = false;
+  rtp_connection_info.local_candidate = *rtp_local_candidate.get();
+  rtp_connection_info.remote_candidate = *rtp_remote_candidate.get();
+  rtp_connection_info.sent_total_bytes = 42;
+  rtp_connection_info.recv_total_bytes = 1337;
+  cricket::TransportChannelStats rtp_transport_channel_stats;
+  rtp_transport_channel_stats.component = cricket::ICE_CANDIDATE_COMPONENT_RTP;
+  rtp_transport_channel_stats.connection_infos.push_back(rtp_connection_info);
+  session_stats.transport_stats["transport"].channel_stats.push_back(
+      rtp_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;
+      }));
+
+  // Get stats without RTCP, an active connection or certificates.
+  rtc::scoped_refptr<const RTCStatsReport> report = GetStatsReport();
+  ExpectReportContainsTransportStats(
+      report, session_stats.transport_stats["transport"], nullptr, nullptr);
+
+  cricket::ConnectionInfo rtcp_connection_info;
+  rtcp_connection_info.best_connection = false;
+  rtcp_connection_info.local_candidate = *rtcp_local_candidate.get();
+  rtcp_connection_info.remote_candidate = *rtcp_remote_candidate.get();
+  rtcp_connection_info.sent_total_bytes = 1337;
+  rtcp_connection_info.recv_total_bytes = 42;
+  cricket::TransportChannelStats rtcp_transport_channel_stats;
+  rtcp_transport_channel_stats.component =
+      cricket::ICE_CANDIDATE_COMPONENT_RTCP;
+  rtcp_transport_channel_stats.connection_infos.push_back(rtcp_connection_info);
+  session_stats.transport_stats["transport"].channel_stats.push_back(
+      rtcp_transport_channel_stats);
+
+  collector_->ClearCachedStatsReport();
+  // Get stats with RTCP and without an active connection or certificates.
+  report = GetStatsReport();
+  ExpectReportContainsTransportStats(
+      report, session_stats.transport_stats["transport"], nullptr, nullptr);
+
+  // Get stats with an active connection.
+  rtcp_connection_info.best_connection = true;
+
+  collector_->ClearCachedStatsReport();
+  report = GetStatsReport();
+  ExpectReportContainsTransportStats(
+      report, session_stats.transport_stats["transport"], nullptr, nullptr);
+
+  // Get stats with certificates.
+  std::unique_ptr<CertificateInfo> local_certinfo =
+      CreateFakeCertificateAndInfoFromDers(
+          std::vector<std::string>({ "(local) local", "(local) chain" }));
+  std::unique_ptr<CertificateInfo> remote_certinfo =
+      CreateFakeCertificateAndInfoFromDers(
+          std::vector<std::string>({ "(remote) local", "(remote) chain" }));
+  EXPECT_CALL(test_->session(), GetLocalCertificate(_, _)).WillRepeatedly(
+    Invoke([this, &local_certinfo](const std::string& transport_name,
+           rtc::scoped_refptr<rtc::RTCCertificate>* certificate) {
+      if (transport_name == "transport") {
+        *certificate = local_certinfo->certificate;
+        return true;
+      }
+      return false;
+    }));
+  EXPECT_CALL(test_->session(),
+      GetRemoteSSLCertificate_ReturnsRawPointer(_)).WillRepeatedly(Invoke(
+      [this, &remote_certinfo](const std::string& transport_name) {
+        if (transport_name == "transport")
+          return remote_certinfo->certificate->ssl_certificate().GetReference();
+        return static_cast<rtc::SSLCertificate*>(nullptr);
+      }));
+
+  collector_->ClearCachedStatsReport();
+  report = GetStatsReport();
+  ExpectReportContainsTransportStats(
+      report, session_stats.transport_stats["transport"],
+      local_certinfo.get(), remote_certinfo.get());
+}
+
 class RTCStatsCollectorTestWithFakeCollector : public testing::Test {
  public:
   RTCStatsCollectorTestWithFakeCollector()
diff --git a/webrtc/api/stats/rtcstats_objects.h b/webrtc/api/stats/rtcstats_objects.h
index ad6a39c..4e8a3ee 100644
--- a/webrtc/api/stats/rtcstats_objects.h
+++ b/webrtc/api/stats/rtcstats_objects.h
@@ -43,6 +43,45 @@
   static const char* kRelay;
 };
 
+// https://w3c.github.io/webrtc-stats/#certificatestats-dict*
+class RTCCertificateStats final : public RTCStats {
+ public:
+  WEBRTC_RTCSTATS_DECL();
+
+  RTCCertificateStats(const std::string& id, int64_t timestamp_us);
+  RTCCertificateStats(std::string&& id, int64_t timestamp_us);
+  RTCCertificateStats(const RTCCertificateStats& other);
+  ~RTCCertificateStats() override;
+
+  RTCStatsMember<std::string> fingerprint;
+  RTCStatsMember<std::string> fingerprint_algorithm;
+  RTCStatsMember<std::string> base64_certificate;
+  RTCStatsMember<std::string> issuer_certificate_id;
+};
+
+// https://w3c.github.io/webrtc-stats/#dcstats-dict*
+class RTCDataChannelStats final : public RTCStats {
+ public:
+  WEBRTC_RTCSTATS_DECL();
+
+  RTCDataChannelStats(const std::string& id, int64_t timestamp_us);
+  RTCDataChannelStats(std::string&& id, int64_t timestamp_us);
+  RTCDataChannelStats(const RTCDataChannelStats& other);
+  ~RTCDataChannelStats() override;
+
+  RTCStatsMember<std::string> label;
+  RTCStatsMember<std::string> protocol;
+  RTCStatsMember<int32_t> datachannelid;
+  // TODO(hbos): Support enum types? "RTCStatsMember<RTCDataChannelState>"?
+  RTCStatsMember<std::string> state;
+  RTCStatsMember<uint32_t> messages_sent;
+  RTCStatsMember<uint64_t> bytes_sent;
+  RTCStatsMember<uint32_t> messages_received;
+  RTCStatsMember<uint64_t> bytes_received;
+};
+
+// https://w3c.github.io/webrtc-stats/#candidatepair-dict*
+// TODO(hbos): Finish implementation. Tracking bug crbug.com/633550
 class RTCIceCandidatePairStats : public RTCStats {
  public:
   WEBRTC_RTCSTATS_DECL();
@@ -81,6 +120,7 @@
 };
 
 // https://w3c.github.io/webrtc-stats/#icecandidate-dict*
+// TODO(hbos): Finish implementation. Tracking bug crbug.com/632723
 class RTCIceCandidateStats : public RTCStats {
  public:
   WEBRTC_RTCSTATS_DECL();
@@ -121,45 +161,8 @@
   const char* type() const override;
 };
 
-// https://w3c.github.io/webrtc-stats/#certificatestats-dict*
-class RTCCertificateStats final : public RTCStats {
- public:
-  WEBRTC_RTCSTATS_DECL();
-
-  RTCCertificateStats(const std::string& id, int64_t timestamp_us);
-  RTCCertificateStats(std::string&& id, int64_t timestamp_us);
-  RTCCertificateStats(const RTCCertificateStats& other);
-  ~RTCCertificateStats() override;
-
-  RTCStatsMember<std::string> fingerprint;
-  RTCStatsMember<std::string> fingerprint_algorithm;
-  RTCStatsMember<std::string> base64_certificate;
-  RTCStatsMember<std::string> issuer_certificate_id;
-};
-
-// https://w3c.github.io/webrtc-stats/#dcstats-dict*
-class RTCDataChannelStats final : public RTCStats {
- public:
-  WEBRTC_RTCSTATS_DECL();
-
-  RTCDataChannelStats(const std::string& id, int64_t timestamp_us);
-  RTCDataChannelStats(std::string&& id, int64_t timestamp_us);
-  RTCDataChannelStats(const RTCDataChannelStats& other);
-  ~RTCDataChannelStats() override;
-
-  RTCStatsMember<std::string> label;
-  RTCStatsMember<std::string> protocol;
-  RTCStatsMember<int32_t> datachannelid;
-  // TODO(hbos): Support enum types? "RTCStatsMember<RTCDataChannelState>"?
-  RTCStatsMember<std::string> state;
-  RTCStatsMember<uint32_t> messages_sent;
-  RTCStatsMember<uint64_t> bytes_sent;
-  RTCStatsMember<uint32_t> messages_received;
-  RTCStatsMember<uint64_t> bytes_received;
-};
-
 // https://w3c.github.io/webrtc-stats/#pcstats-dict*
-// TODO(hbos): Tracking bug crbug.com/636818
+// TODO(hbos): Finish implementation. Tracking bug crbug.com/636818
 class RTCPeerConnectionStats final : public RTCStats {
  public:
   WEBRTC_RTCSTATS_DECL();
@@ -173,6 +176,25 @@
   RTCStatsMember<uint32_t> data_channels_closed;
 };
 
+// https://w3c.github.io/webrtc-stats/#transportstats-dict*
+class RTCTransportStats final : public RTCStats {
+ public:
+  WEBRTC_RTCSTATS_DECL();
+
+  RTCTransportStats(const std::string& id, int64_t timestamp_us);
+  RTCTransportStats(std::string&& id, int64_t timestamp_us);
+  RTCTransportStats(const RTCTransportStats& other);
+  ~RTCTransportStats() override;
+
+  RTCStatsMember<uint64_t> bytes_sent;
+  RTCStatsMember<uint64_t> bytes_received;
+  RTCStatsMember<std::string> rtcp_transport_stats_id;
+  RTCStatsMember<bool> active_connection;
+  RTCStatsMember<std::string> selected_candidate_pair_id;
+  RTCStatsMember<std::string> local_certificate_id;
+  RTCStatsMember<std::string> remote_certificate_id;
+};
+
 }  // namespace webrtc
 
 #endif  // WEBRTC_API_STATS_RTCSTATS_OBJECTS_H_