Update libjingle to 53920541.

R=mallinath@webrtc.org

Review URL: https://webrtc-codereview.appspot.com/2371004

git-svn-id: http://webrtc.googlecode.com/svn/trunk@4945 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/talk/app/webrtc/statscollector.cc b/talk/app/webrtc/statscollector.cc
index 06c4b44..f5ff708 100644
--- a/talk/app/webrtc/statscollector.cc
+++ b/talk/app/webrtc/statscollector.cc
@@ -30,6 +30,8 @@
 #include <utility>
 #include <vector>
 
+#include "talk/base/base64.h"
+#include "talk/base/scoped_ptr.h"
 #include "talk/session/media/channel.h"
 
 namespace webrtc {
@@ -52,6 +54,7 @@
 const char StatsReport::kStatsValueNameCodecName[] = "googCodecName";
 const char StatsReport::kStatsValueNameComponent[] = "googComponent";
 const char StatsReport::kStatsValueNameContentName[] = "googContentName";
+const char StatsReport::kStatsValueNameDer[] = "googDerBase64";
 // Echo metrics from the audio processing module.
 const char StatsReport::kStatsValueNameEchoCancellationQualityMin[] =
     "googEchoCancellationQualityMin";
@@ -64,6 +67,7 @@
 const char StatsReport::kStatsValueNameEchoReturnLossEnhancement[] =
     "googEchoCancellationReturnLossEnhancement";
 
+const char StatsReport::kStatsValueNameFingerprint[] = "googFingerprint";
 const char StatsReport::kStatsValueNameFirsReceived[] = "googFirsReceived";
 const char StatsReport::kStatsValueNameFirsSent[] = "googFirsSent";
 const char StatsReport::kStatsValueNameFrameHeightReceived[] =
@@ -82,8 +86,11 @@
     "googFrameWidthReceived";
 const char StatsReport::kStatsValueNameFrameWidthSent[] = "googFrameWidthSent";
 const char StatsReport::kStatsValueNameInitiator[] = "googInitiator";
+const char StatsReport::kStatsValueNameIssuerId[] = "googIssuerId";
 const char StatsReport::kStatsValueNameJitterReceived[] = "googJitterReceived";
 const char StatsReport::kStatsValueNameLocalAddress[] = "googLocalAddress";
+const char StatsReport::kStatsValueNameLocalCertificateId[] =
+    "googLocalCertificateId";
 const char StatsReport::kStatsValueNameNacksReceived[] = "googNacksReceived";
 const char StatsReport::kStatsValueNameNacksSent[] = "googNacksSent";
 const char StatsReport::kStatsValueNamePacketsReceived[] = "packetsReceived";
@@ -91,6 +98,8 @@
 const char StatsReport::kStatsValueNamePacketsLost[] = "packetsLost";
 const char StatsReport::kStatsValueNameReadable[] = "googReadable";
 const char StatsReport::kStatsValueNameRemoteAddress[] = "googRemoteAddress";
+const char StatsReport::kStatsValueNameRemoteCertificateId[] =
+    "googRemoteCertificateId";
 const char StatsReport::kStatsValueNameRetransmitBitrate[] =
     "googRetransmitBitrate";
 const char StatsReport::kStatsValueNameRtt[] = "googRtt";
@@ -114,6 +123,7 @@
 const char StatsReport::kStatsReportTypeTransport[] = "googTransport";
 const char StatsReport::kStatsReportTypeComponent[] = "googComponent";
 const char StatsReport::kStatsReportTypeCandidatePair[] = "googCandidatePair";
+const char StatsReport::kStatsReportTypeCertificate[] = "googCertificate";
 
 const char StatsReport::kStatsReportVideoBweId[] = "bweforvideo";
 
@@ -434,6 +444,58 @@
   return report;
 }
 
+std::string StatsCollector::AddOneCertificateReport(
+    const talk_base::SSLCertificate* cert, const std::string& issuer_id) {
+  // TODO(bemasc): Move this computation to a helper class that caches these
+  // values to reduce CPU use in GetStats.  This will require adding a fast
+  // SSLCertificate::Equals() method to detect certificate changes.
+  talk_base::scoped_ptr<talk_base::SSLFingerprint> ssl_fingerprint(
+      talk_base::SSLFingerprint::Create(talk_base::DIGEST_SHA_256, cert));
+  std::string fingerprint = ssl_fingerprint->GetRfc4572Fingerprint();
+
+  talk_base::Buffer der_buffer;
+  cert->ToDER(&der_buffer);
+  std::string der_base64;
+  talk_base::Base64::EncodeFromArray(
+      der_buffer.data(), der_buffer.length(), &der_base64);
+
+  StatsReport report;
+  report.type = StatsReport::kStatsReportTypeCertificate;
+  report.id = StatsId(report.type, fingerprint);
+  report.timestamp = stats_gathering_started_;
+  report.AddValue(StatsReport::kStatsValueNameFingerprint, fingerprint);
+  report.AddValue(StatsReport::kStatsValueNameDer, der_base64);
+  if (!issuer_id.empty())
+    report.AddValue(StatsReport::kStatsValueNameIssuerId, issuer_id);
+  reports_[report.id] = report;
+  return report.id;
+}
+
+std::string StatsCollector::AddCertificateReports(
+    const talk_base::SSLCertificate* cert) {
+  // Produces a chain of StatsReports representing this certificate and the rest
+  // of its chain, and adds those reports to |reports_|.  The return value is
+  // the id of the leaf report.  The provided cert must be non-null, so at least
+  // one report will always be provided and the returned string will never be
+  // empty.
+  ASSERT(cert != NULL);
+
+  std::string issuer_id;
+  talk_base::scoped_ptr<talk_base::SSLCertChain> chain;
+  if (cert->GetChain(chain.accept())) {
+    // This loop runs in reverse, i.e. from root to leaf, so that each
+    // certificate's issuer's report ID is known before the child certificate's
+    // report is generated.  The root certificate does not have an issuer ID
+    // value.
+    for (ptrdiff_t i = chain->GetSize() - 1; i >= 0; --i) {
+      const talk_base::SSLCertificate& cert_i = chain->Get(i);
+      issuer_id = AddOneCertificateReport(&cert_i, issuer_id);
+    }
+  }
+  // Add the leaf certificate.
+  return AddOneCertificateReport(cert, issuer_id);
+}
+
 void StatsCollector::ExtractSessionInfo() {
   // Extract information from the base session.
   StatsReport report;
@@ -454,6 +516,22 @@
     for (cricket::TransportStatsMap::iterator transport_iter
              = stats.transport_stats.begin();
          transport_iter != stats.transport_stats.end(); ++transport_iter) {
+      // Attempt to get a copy of the certificates from the transport and
+      // expose them in stats reports.  All channels in a transport share the
+      // same local and remote certificates.
+      std::string local_cert_report_id, remote_cert_report_id;
+      cricket::Transport* transport =
+          session_->GetTransport(transport_iter->second.content_name);
+      if (transport) {
+        talk_base::scoped_ptr<talk_base::SSLIdentity> identity;
+        if (transport->GetIdentity(identity.accept()))
+          local_cert_report_id = AddCertificateReports(
+              &(identity->certificate()));
+
+        talk_base::scoped_ptr<talk_base::SSLCertificate> cert;
+        if (transport->GetRemoteCertificate(cert.accept()))
+          remote_cert_report_id = AddCertificateReports(cert.get());
+      }
       for (cricket::TransportChannelStatsList::iterator channel_iter
                = transport_iter->second.channel_stats.begin();
            channel_iter != transport_iter->second.channel_stats.end();
@@ -467,6 +545,14 @@
         channel_report.timestamp = stats_gathering_started_;
         channel_report.AddValue(StatsReport::kStatsValueNameComponent,
                                 channel_iter->component);
+        if (!local_cert_report_id.empty())
+          channel_report.AddValue(
+              StatsReport::kStatsValueNameLocalCertificateId,
+              local_cert_report_id);
+        if (!remote_cert_report_id.empty())
+          channel_report.AddValue(
+              StatsReport::kStatsValueNameRemoteCertificateId,
+              remote_cert_report_id);
         reports_[channel_report.id] = channel_report;
         for (size_t i = 0;
              i < channel_iter->connection_infos.size();
diff --git a/talk/app/webrtc/statscollector.h b/talk/app/webrtc/statscollector.h
index 03a32c4..c34b5a0 100644
--- a/talk/app/webrtc/statscollector.h
+++ b/talk/app/webrtc/statscollector.h
@@ -75,6 +75,14 @@
  private:
   bool CopySelectedReports(const std::string& selector, StatsReports* reports);
 
+  // Helper method for AddCertificateReports.
+  std::string AddOneCertificateReport(
+      const talk_base::SSLCertificate* cert, const std::string& issuer_id);
+
+  // Adds a report for this certificate and every certificate in its chain, and
+  // returns the leaf certificate's report's ID.
+  std::string AddCertificateReports(const talk_base::SSLCertificate* cert);
+
   void ExtractSessionInfo();
   void ExtractVoiceInfo();
   void ExtractVideoInfo();
diff --git a/talk/app/webrtc/statscollector_unittest.cc b/talk/app/webrtc/statscollector_unittest.cc
index cce1645bc..9829832 100644
--- a/talk/app/webrtc/statscollector_unittest.cc
+++ b/talk/app/webrtc/statscollector_unittest.cc
@@ -30,6 +30,8 @@
 
 #include "talk/app/webrtc/mediastream.h"
 #include "talk/app/webrtc/videotrack.h"
+#include "talk/base/base64.h"
+#include "talk/base/fakesslidentity.h"
 #include "talk/base/gunit.h"
 #include "talk/media/base/fakemediaengine.h"
 #include "talk/media/devices/fakedevicemanager.h"
@@ -60,11 +62,12 @@
  public:
   explicit MockWebRtcSession(cricket::ChannelManager* channel_manager)
     : WebRtcSession(channel_manager, talk_base::Thread::Current(),
-                    NULL, NULL, NULL) {
+                    talk_base::Thread::Current(), NULL, NULL) {
   }
   MOCK_METHOD0(video_channel, cricket::VideoChannel*());
   MOCK_METHOD2(GetTrackIdBySsrc, bool(uint32, std::string*));
   MOCK_METHOD1(GetStats, bool(cricket::SessionStats*));
+  MOCK_METHOD1(GetTransport, cricket::Transport*(const std::string&));
 };
 
 class MockVideoMediaChannel : public cricket::FakeVideoMediaChannel {
@@ -76,8 +79,21 @@
   MOCK_METHOD1(GetStats, bool(cricket::VideoMediaInfo*));
 };
 
+bool GetValue(const webrtc::StatsReport* report,
+              const std::string& name,
+              std::string* value) {
+  webrtc::StatsReport::Values::const_iterator it = report->values.begin();
+  for (; it != report->values.end(); ++it) {
+    if (it->name == name) {
+      *value = it->value;
+      return true;
+    }
+  }
+  return false;
+}
+
 std::string ExtractStatsValue(const std::string& type,
-                              webrtc::StatsReports reports,
+                              const webrtc::StatsReports& reports,
                               const std::string name) {
   if (reports.empty()) {
     return kNoReports;
@@ -85,12 +101,9 @@
   for (size_t i = 0; i < reports.size(); ++i) {
     if (reports[i].type != type)
       continue;
-    webrtc::StatsReport::Values::const_iterator it =
-        reports[i].values.begin();
-    for (; it != reports[i].values.end(); ++it) {
-      if (it->name == name) {
-        return it->value;
-      }
+    std::string ret;
+    if (GetValue(&reports[i], name, &ret)) {
+      return ret;
     }
   }
 
@@ -99,9 +112,8 @@
 
 // Finds the |n|-th report of type |type| in |reports|.
 // |n| starts from 1 for finding the first report.
-const webrtc::StatsReport* FindNthReportByType(webrtc::StatsReports reports,
-                                               const std::string& type,
-                                               int n) {
+const webrtc::StatsReport* FindNthReportByType(
+    const webrtc::StatsReports& reports, const std::string& type, int n) {
   for (size_t i = 0; i < reports.size(); ++i) {
     if (reports[i].type == type) {
       n--;
@@ -112,7 +124,7 @@
   return NULL;
 }
 
-const webrtc::StatsReport* FindReportById(webrtc::StatsReports reports,
+const webrtc::StatsReport* FindReportById(const webrtc::StatsReports& reports,
                                           const std::string& id) {
   for (size_t i = 0; i < reports.size(); ++i) {
     if (reports[i].id == id) {
@@ -134,6 +146,42 @@
       webrtc::StatsReport::kStatsReportTypeBwe, reports, name);
 }
 
+std::string DerToPem(const std::string& der) {
+  return talk_base::SSLIdentity::DerToPem(
+        talk_base::kPemTypeCertificate,
+        reinterpret_cast<const unsigned char*>(der.c_str()),
+        der.length());
+}
+
+std::vector<std::string> DersToPems(
+    const std::vector<std::string>& ders) {
+  std::vector<std::string> pems(ders.size());
+  std::transform(ders.begin(), ders.end(), pems.begin(), DerToPem);
+  return pems;
+}
+
+void CheckCertChainReports(const webrtc::StatsReports& reports,
+                           const std::vector<std::string>& ders,
+                           const std::string& start_id) {
+  std::string certificate_id = start_id;
+  size_t i = 0;
+  while (true) {
+    const webrtc::StatsReport* report = FindReportById(reports, certificate_id);
+    ASSERT_TRUE(report != NULL);
+    std::string der_base64;
+    EXPECT_TRUE(GetValue(
+        report, webrtc::StatsReport::kStatsValueNameDer, &der_base64));
+    std::string der = talk_base::Base64::Decode(der_base64,
+                                                talk_base::Base64::DO_STRICT);
+    EXPECT_EQ(ders[i], der);
+    ++i;
+    if (!GetValue(
+        report, webrtc::StatsReport::kStatsValueNameIssuerId, &certificate_id))
+      break;
+  }
+  EXPECT_EQ(ders.size(), i);
+}
+
 class StatsCollectorTest : public testing::Test {
  protected:
   StatsCollectorTest()
@@ -147,6 +195,77 @@
     EXPECT_CALL(session_, GetStats(_)).WillRepeatedly(Return(false));
   }
 
+  void TestCertificateReports(const talk_base::FakeSSLCertificate& local_cert,
+                              const std::vector<std::string>& local_ders,
+                              const talk_base::FakeSSLCertificate& remote_cert,
+                              const std::vector<std::string>& remote_ders) {
+    webrtc::StatsCollector stats;  // Implementation under test.
+    webrtc::StatsReports reports;  // returned values.
+    stats.set_session(&session_);
+
+    // Fake stats to process.
+    cricket::TransportChannelStats channel_stats;
+    channel_stats.component = 1;
+
+    cricket::TransportStats transport_stats;
+    transport_stats.content_name = "audio";
+    transport_stats.channel_stats.push_back(channel_stats);
+
+    cricket::SessionStats session_stats;
+    session_stats.transport_stats[transport_stats.content_name] =
+        transport_stats;
+
+    // Fake certificates to report.
+    talk_base::FakeSSLIdentity local_identity(local_cert);
+    talk_base::scoped_ptr<talk_base::FakeSSLCertificate> remote_cert_copy(
+        remote_cert.GetReference());
+
+    // Fake transport object.
+    talk_base::scoped_ptr<cricket::FakeTransport> transport(
+        new cricket::FakeTransport(
+            session_.signaling_thread(),
+            session_.worker_thread(),
+            transport_stats.content_name));
+    transport->SetIdentity(&local_identity);
+    cricket::FakeTransportChannel* channel =
+        static_cast<cricket::FakeTransportChannel*>(
+            transport->CreateChannel(channel_stats.component));
+    EXPECT_FALSE(channel == NULL);
+    channel->SetRemoteCertificate(remote_cert_copy.get());
+
+    // Configure MockWebRtcSession
+    EXPECT_CALL(session_, GetTransport(transport_stats.content_name))
+      .WillOnce(Return(transport.get()));
+    EXPECT_CALL(session_, GetStats(_))
+      .WillOnce(DoAll(SetArgPointee<0>(session_stats),
+                      Return(true)));
+    EXPECT_CALL(session_, video_channel())
+      .WillRepeatedly(ReturnNull());
+
+    stats.UpdateStats();
+
+    stats.GetStats(NULL, &reports);
+
+    const webrtc::StatsReport* channel_report = FindNthReportByType(
+        reports, webrtc::StatsReport::kStatsReportTypeComponent, 1);
+    EXPECT_TRUE(channel_report != NULL);
+
+    // Check local certificate chain.
+    std::string local_certificate_id = ExtractStatsValue(
+        webrtc::StatsReport::kStatsReportTypeComponent,
+        reports,
+        webrtc::StatsReport::kStatsValueNameLocalCertificateId);
+    EXPECT_NE(kNotFound, local_certificate_id);
+    CheckCertChainReports(reports, local_ders, local_certificate_id);
+
+    // Check remote certificate chain.
+    std::string remote_certificate_id = ExtractStatsValue(
+        webrtc::StatsReport::kStatsReportTypeComponent,
+        reports,
+        webrtc::StatsReport::kStatsValueNameRemoteCertificateId);
+    EXPECT_NE(kNotFound, remote_certificate_id);
+    CheckCertChainReports(reports, remote_ders, remote_certificate_id);
+  }
   cricket::FakeMediaEngine* media_engine_;
   talk_base::scoped_ptr<cricket::ChannelManager> channel_manager_;
   MockWebRtcSession session_;
@@ -439,4 +558,142 @@
   ASSERT_FALSE(transport_report == NULL);
 }
 
+// This test verifies that all chained certificates are correctly
+// reported
+TEST_F(StatsCollectorTest, DISABLED_ChainedCertificateReportsCreated) {
+  // Build local certificate chain.
+  std::vector<std::string> local_ders(5);
+  local_ders[0] = "These";
+  local_ders[1] = "are";
+  local_ders[2] = "some";
+  local_ders[3] = "der";
+  local_ders[4] = "values";
+  talk_base::FakeSSLCertificate local_cert(DersToPems(local_ders));
+
+  // Build remote certificate chain
+  std::vector<std::string> remote_ders(4);
+  remote_ders[0] = "A";
+  remote_ders[1] = "non-";
+  remote_ders[2] = "intersecting";
+  remote_ders[3] = "set";
+  talk_base::FakeSSLCertificate remote_cert(DersToPems(remote_ders));
+
+  TestCertificateReports(local_cert, local_ders, remote_cert, remote_ders);
+}
+
+// This test verifies that all certificates without chains are correctly
+// reported.
+TEST_F(StatsCollectorTest, DISABLED_ChainlessCertificateReportsCreated) {
+  // Build local certificate.
+  std::string local_der = "This is the local der.";
+  talk_base::FakeSSLCertificate local_cert(DerToPem(local_der));
+
+  // Build remote certificate.
+  std::string remote_der = "This is somebody else's der.";
+  talk_base::FakeSSLCertificate remote_cert(DerToPem(remote_der));
+
+  TestCertificateReports(local_cert, std::vector<std::string>(1, local_der),
+                         remote_cert, std::vector<std::string>(1, remote_der));
+}
+
+// This test verifies that the stats are generated correctly when no
+// transport is present.
+TEST_F(StatsCollectorTest, DISABLED_NoTransport) {
+  webrtc::StatsCollector stats;  // Implementation under test.
+  webrtc::StatsReports reports;  // returned values.
+  stats.set_session(&session_);
+
+  // Fake stats to process.
+  cricket::TransportChannelStats channel_stats;
+  channel_stats.component = 1;
+
+  cricket::TransportStats transport_stats;
+  transport_stats.content_name = "audio";
+  transport_stats.channel_stats.push_back(channel_stats);
+
+  cricket::SessionStats session_stats;
+  session_stats.transport_stats[transport_stats.content_name] =
+      transport_stats;
+
+  // Configure MockWebRtcSession
+  EXPECT_CALL(session_, GetTransport(transport_stats.content_name))
+    .WillOnce(ReturnNull());
+  EXPECT_CALL(session_, GetStats(_))
+    .WillOnce(DoAll(SetArgPointee<0>(session_stats),
+                    Return(true)));
+  EXPECT_CALL(session_, video_channel())
+    .WillRepeatedly(ReturnNull());
+
+  stats.UpdateStats();
+  stats.GetStats(NULL, &reports);
+
+  // Check that the local certificate is absent.
+  std::string local_certificate_id = ExtractStatsValue(
+      webrtc::StatsReport::kStatsReportTypeComponent,
+      reports,
+      webrtc::StatsReport::kStatsValueNameLocalCertificateId);
+  ASSERT_EQ(kNotFound, local_certificate_id);
+
+  // Check that the remote certificate is absent.
+  std::string remote_certificate_id = ExtractStatsValue(
+      webrtc::StatsReport::kStatsReportTypeComponent,
+      reports,
+      webrtc::StatsReport::kStatsValueNameRemoteCertificateId);
+  ASSERT_EQ(kNotFound, remote_certificate_id);
+}
+
+// This test verifies that the stats are generated correctly when the transport
+// does not have any certificates.
+TEST_F(StatsCollectorTest, DISABLED_NoCertificates) {
+  webrtc::StatsCollector stats;  // Implementation under test.
+  webrtc::StatsReports reports;  // returned values.
+  stats.set_session(&session_);
+
+  // Fake stats to process.
+  cricket::TransportChannelStats channel_stats;
+  channel_stats.component = 1;
+
+  cricket::TransportStats transport_stats;
+  transport_stats.content_name = "audio";
+  transport_stats.channel_stats.push_back(channel_stats);
+
+  cricket::SessionStats session_stats;
+  session_stats.transport_stats[transport_stats.content_name] =
+      transport_stats;
+
+  // Fake transport object.
+  talk_base::scoped_ptr<cricket::FakeTransport> transport(
+      new cricket::FakeTransport(
+          session_.signaling_thread(),
+          session_.worker_thread(),
+          transport_stats.content_name));
+
+  // Configure MockWebRtcSession
+  EXPECT_CALL(session_, GetTransport(transport_stats.content_name))
+    .WillOnce(Return(transport.get()));
+  EXPECT_CALL(session_, GetStats(_))
+    .WillOnce(DoAll(SetArgPointee<0>(session_stats),
+                    Return(true)));
+  EXPECT_CALL(session_, video_channel())
+    .WillRepeatedly(ReturnNull());
+
+  stats.UpdateStats();
+  stats.GetStats(NULL, &reports);
+
+  // Check that the local certificate is absent.
+  std::string local_certificate_id = ExtractStatsValue(
+      webrtc::StatsReport::kStatsReportTypeComponent,
+      reports,
+      webrtc::StatsReport::kStatsValueNameLocalCertificateId);
+  ASSERT_EQ(kNotFound, local_certificate_id);
+
+  // Check that the remote certificate is absent.
+  std::string remote_certificate_id = ExtractStatsValue(
+      webrtc::StatsReport::kStatsReportTypeComponent,
+      reports,
+      webrtc::StatsReport::kStatsValueNameRemoteCertificateId);
+  ASSERT_EQ(kNotFound, remote_certificate_id);
+}
+
+
 }  // namespace
diff --git a/talk/app/webrtc/statstypes.h b/talk/app/webrtc/statstypes.h
index 30a8b84..fe36885 100644
--- a/talk/app/webrtc/statstypes.h
+++ b/talk/app/webrtc/statstypes.h
@@ -99,6 +99,14 @@
   // The id of StatsReport of type VideoBWE.
   static const char kStatsReportVideoBweId[];
 
+  // A StatsReport of |type| = "googCertificate" contains an SSL certificate
+  // transmitted by one of the endpoints of this connection.  The |id| is
+  // controlled by the fingerprint, and is used to identify the certificate in
+  // the Channel stats (as "googLocalCertificateId" or
+  // "googRemoteCertificateId") and in any child certificates (as
+  // "googIssuerId").
+  static const char kStatsReportTypeCertificate[];
+
   // StatsValue names
   static const char kStatsValueNameAudioOutputLevel[];
   static const char kStatsValueNameAudioInputLevel[];
@@ -152,6 +160,11 @@
   static const char kStatsValueNameTrackId[];
   static const char kStatsValueNameSsrc[];
   static const char kStatsValueNameTypingNoiseState[];
+  static const char kStatsValueNameDer[];
+  static const char kStatsValueNameFingerprint[];
+  static const char kStatsValueNameIssuerId[];
+  static const char kStatsValueNameLocalCertificateId[];
+  static const char kStatsValueNameRemoteCertificateId[];
 };
 
 typedef std::vector<StatsReport> StatsReports;