Expose negotiated ciphers through stats API.

Use the new internal API to expose the negotiated SRTP/SSL ciphers
through the stats API.
This is a follow-up to https://webrtc-codereview.appspot.com/37209004.

BUG=3976
R=juberti@webrtc.org

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

Cr-Commit-Position: refs/heads/master@{#8584}
git-svn-id: http://webrtc.googlecode.com/svn/trunk@8584 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/talk/app/webrtc/peerconnection_unittest.cc b/talk/app/webrtc/peerconnection_unittest.cc
index d6b9bf2..8e1c43f 100644
--- a/talk/app/webrtc/peerconnection_unittest.cc
+++ b/talk/app/webrtc/peerconnection_unittest.cc
@@ -102,6 +102,15 @@
 static const char kAudioTrackLabelBase[] = "audio_track";
 static const char kDataChannelLabel[] = "data_channel";
 
+// Disable for TSan v2, see
+// https://code.google.com/p/webrtc/issues/detail?id=1205 for details.
+// This declaration is also #ifdef'd as it causes unused-variable errors.
+#if !defined(THREAD_SANITIZER)
+// SRTP cipher name negotiated by the tests. This must be updated if the
+// default changes.
+static const char kDefaultSrtpCipher[] = "AES_CM_128_HMAC_SHA1_32";
+#endif
+
 static void RemoveLinesFromSdp(const std::string& line_start,
                                std::string* sdp) {
   const char kSdpLineEnd[] = "\r\n";
@@ -380,6 +389,24 @@
     return bw;
   }
 
+  std::string GetDtlsCipherStats() {
+    rtc::scoped_refptr<MockStatsObserver>
+        observer(new rtc::RefCountedObject<MockStatsObserver>());
+    EXPECT_TRUE(peer_connection_->GetStats(
+        observer, NULL, PeerConnectionInterface::kStatsOutputLevelStandard));
+    EXPECT_TRUE_WAIT(observer->called(), kMaxWaitMs);
+    return observer->DtlsCipher();
+  }
+
+  std::string GetSrtpCipherStats() {
+    rtc::scoped_refptr<MockStatsObserver>
+        observer(new rtc::RefCountedObject<MockStatsObserver>());
+    EXPECT_TRUE(peer_connection_->GetStats(
+        observer, NULL, PeerConnectionInterface::kStatsOutputLevelStandard));
+    EXPECT_TRUE_WAIT(observer->called(), kMaxWaitMs);
+    return observer->SrtpCipher();
+  }
+
   int rendered_width() {
     EXPECT_FALSE(fake_video_renderers_.empty());
     return fake_video_renderers_.empty() ? 1 :
@@ -1280,6 +1307,22 @@
       kMaxWaitForStatsMs);
 }
 
+// Test that we can get negotiated ciphers.
+TEST_F(JsepPeerConnectionP2PTestClient, GetNegotiatedCiphersStats) {
+  ASSERT_TRUE(CreateTestClients());
+  LocalP2PTest();
+
+  EXPECT_EQ_WAIT(
+      rtc::SSLStreamAdapter::GetDefaultSslCipher(),
+      initializing_client()->GetDtlsCipherStats(),
+      kMaxWaitForStatsMs);
+
+  EXPECT_EQ_WAIT(
+      kDefaultSrtpCipher,
+      initializing_client()->GetSrtpCipherStats(),
+      kMaxWaitForStatsMs);
+}
+
 // This test sets up a call between two parties with audio, video and data.
 TEST_F(JsepPeerConnectionP2PTestClient, LocalP2PTestDataChannel) {
   FakeConstraints setup_constraints;
diff --git a/talk/app/webrtc/statscollector.cc b/talk/app/webrtc/statscollector.cc
index 968fa8f..d434aaf 100644
--- a/talk/app/webrtc/statscollector.cc
+++ b/talk/app/webrtc/statscollector.cc
@@ -715,6 +715,18 @@
               StatsReport::kStatsValueNameRemoteCertificateId,
               remote_cert_report_id);
         }
+        const std::string& srtp_cipher = channel_iter->srtp_cipher;
+        if (!srtp_cipher.empty()) {
+          channel_report->AddValue(
+              StatsReport::kStatsValueNameSrtpCipher,
+              srtp_cipher);
+        }
+        const std::string& ssl_cipher = channel_iter->ssl_cipher;
+        if (!ssl_cipher.empty()) {
+          channel_report->AddValue(
+              StatsReport::kStatsValueNameDtlsCipher,
+              ssl_cipher);
+        }
         for (size_t i = 0;
              i < channel_iter->connection_infos.size();
              ++i) {
@@ -742,8 +754,6 @@
                              info.writable);
           report->AddBoolean(StatsReport::kStatsValueNameReadable,
                              info.readable);
-          report->AddBoolean(StatsReport::kStatsValueNameActiveConnection,
-                             info.best_connection);
           report->AddValue(StatsReport::kStatsValueNameLocalCandidateId,
                            AddCandidateReport(info.local_candidate, true));
           report->AddValue(
@@ -760,6 +770,13 @@
                            info.local_candidate.type());
           report->AddValue(StatsReport::kStatsValueNameRemoteCandidateType,
                            info.remote_candidate.type());
+          report->AddBoolean(StatsReport::kStatsValueNameActiveConnection,
+                             info.best_connection);
+          if (info.best_connection) {
+            channel_report->AddValue(
+                StatsReport::kStatsValueNameSelectedCandidatePairId,
+                report->id().ToString());
+          }
         }
       }
     }
diff --git a/talk/app/webrtc/statscollector_unittest.cc b/talk/app/webrtc/statscollector_unittest.cc
index 8a1dc77..8fd59e9 100644
--- a/talk/app/webrtc/statscollector_unittest.cc
+++ b/talk/app/webrtc/statscollector_unittest.cc
@@ -644,6 +644,8 @@
     // Fake stats to process.
     cricket::TransportChannelStats channel_stats;
     channel_stats.component = 1;
+    channel_stats.srtp_cipher = "the-srtp-cipher";
+    channel_stats.ssl_cipher = "the-ssl-cipher";
 
     cricket::TransportStats transport_stats;
     transport_stats.content_name = "audio";
@@ -713,6 +715,18 @@
     } else {
       EXPECT_EQ(kNotFound, remote_certificate_id);
     }
+
+    // Check negotiated ciphers.
+    std::string dtls_cipher = ExtractStatsValue(
+        StatsReport::kStatsReportTypeComponent,
+        reports,
+        StatsReport::kStatsValueNameDtlsCipher);
+    EXPECT_EQ("the-ssl-cipher", dtls_cipher);
+    std::string srtp_cipher = ExtractStatsValue(
+        StatsReport::kStatsReportTypeComponent,
+        reports,
+        StatsReport::kStatsValueNameSrtpCipher);
+    EXPECT_EQ("the-srtp-cipher", srtp_cipher);
   }
 
   cricket::FakeMediaEngine* media_engine_;
@@ -1318,6 +1332,18 @@
       reports,
       StatsReport::kStatsValueNameRemoteCertificateId);
   ASSERT_EQ(kNotFound, remote_certificate_id);
+
+  // Check that the negotiated ciphers are absent.
+  std::string dtls_cipher = ExtractStatsValue(
+      StatsReport::kStatsReportTypeComponent,
+      reports,
+      StatsReport::kStatsValueNameDtlsCipher);
+  ASSERT_EQ(kNotFound, dtls_cipher);
+  std::string srtp_cipher = ExtractStatsValue(
+      StatsReport::kStatsReportTypeComponent,
+      reports,
+      StatsReport::kStatsValueNameSrtpCipher);
+  ASSERT_EQ(kNotFound, srtp_cipher);
 }
 
 // This test verifies that the stats are generated correctly when the transport
diff --git a/talk/app/webrtc/statstypes.cc b/talk/app/webrtc/statstypes.cc
index f41bf7a..e88583f 100644
--- a/talk/app/webrtc/statstypes.cc
+++ b/talk/app/webrtc/statstypes.cc
@@ -54,7 +54,7 @@
     case StatsReport::kStatsReportTypeIceRemoteCandidate:
       return "remotecandidate";
     case StatsReport::kStatsReportTypeTransport:
-      return "googTransport";
+      return "transport";
     case StatsReport::kStatsReportTypeComponent:
       return "googComponent";
     case StatsReport::kStatsReportTypeCandidatePair:
@@ -244,6 +244,8 @@
       return "protocol";
     case kStatsValueNameTransportId:
       return "transportId";
+    case kStatsValueNameSelectedCandidatePairId:
+      return "selectedCandidatePairId";
     case kStatsValueNameSsrc:
       return "ssrc";
     case kStatsValueNameState:
@@ -310,6 +312,8 @@
       return "googDecodingPLCCNG";
     case kStatsValueNameDer:
       return "googDerBase64";
+    case kStatsValueNameDtlsCipher:
+      return "dtlsCipher";
     case kStatsValueNameEchoCancellationQualityMin:
       return "googEchoCancellationQualityMin";
     case kStatsValueNameEchoDelayMedian:
@@ -383,7 +387,7 @@
     case kStatsValueNameLocalCandidateType:
       return "googLocalCandidateType";
     case kStatsValueNameLocalCertificateId:
-      return "googLocalCertificateId";
+      return "localCertificateId";
     case kStatsValueNameAdaptationChanges:
       return "googAdaptationChanges";
     case kStatsValueNameNacksReceived:
@@ -411,7 +415,7 @@
     case kStatsValueNameRemoteCandidateType:
       return "googRemoteCandidateType";
     case kStatsValueNameRemoteCertificateId:
-      return "googRemoteCertificateId";
+      return "remoteCertificateId";
     case kStatsValueNameRetransmitBitrate:
       return "googRetransmitBitrate";
     case kStatsValueNameRtt:
@@ -422,6 +426,8 @@
       return "packetsDiscardedOnSend";
     case kStatsValueNameSpeechExpandRate:
       return "googSpeechExpandRate";
+    case kStatsValueNameSrtpCipher:
+      return "srtpCipher";
     case kStatsValueNameTargetEncBitrate:
       return "googTargetEncBitrate";
     case kStatsValueNameTransmitBitrate:
diff --git a/talk/app/webrtc/statstypes.h b/talk/app/webrtc/statstypes.h
index 65549df..9fe9c11 100644
--- a/talk/app/webrtc/statstypes.h
+++ b/talk/app/webrtc/statstypes.h
@@ -125,6 +125,7 @@
     kStatsValueNamePacketsSent,
     kStatsValueNameProtocol,
     kStatsValueNameReadable,
+    kStatsValueNameSelectedCandidatePairId,
     kStatsValueNameSsrc,
     kStatsValueNameState,
     kStatsValueNameTransportId,
@@ -160,6 +161,7 @@
     kStatsValueNameDecodingPLC,
     kStatsValueNameDecodingPLCCNG,
     kStatsValueNameDer,
+    kStatsValueNameDtlsCipher,
     kStatsValueNameEchoCancellationQualityMin,
     kStatsValueNameEchoDelayMedian,
     kStatsValueNameEchoDelayStdDev,
@@ -211,6 +213,7 @@
     kStatsValueNameSecondaryDecodedRate,
     kStatsValueNameSendPacketsDiscarded,
     kStatsValueNameSpeechExpandRate,
+    kStatsValueNameSrtpCipher,
     kStatsValueNameTargetDelayMs,
     kStatsValueNameTargetEncBitrate,
     kStatsValueNameTrackId,
diff --git a/talk/app/webrtc/test/mockpeerconnectionobservers.h b/talk/app/webrtc/test/mockpeerconnectionobservers.h
index 9b06c2f..4ab6e8e 100644
--- a/talk/app/webrtc/test/mockpeerconnectionobservers.h
+++ b/talk/app/webrtc/test/mockpeerconnectionobservers.h
@@ -123,7 +123,7 @@
   virtual void OnComplete(const StatsReports& reports) {
     ASSERT(!called_);
     called_ = true;
-    memset(&stats_, 0, sizeof(stats_));
+    stats_.Clear();
     stats_.number_of_reports = reports.size();
     for (const auto* r : reports) {
       if (r->type() == StatsReport::kStatsReportTypeSsrc) {
@@ -138,6 +138,11 @@
       } else if (r->type() == StatsReport::kStatsReportTypeBwe) {
         GetIntValue(r, StatsReport::kStatsValueNameAvailableReceiveBandwidth,
             &stats_.available_receive_bandwidth);
+      } else if (r->type() == StatsReport::kStatsReportTypeComponent) {
+        GetStringValue(r, StatsReport::kStatsValueNameDtlsCipher,
+            &stats_.dtls_cipher);
+        GetStringValue(r, StatsReport::kStatsValueNameSrtpCipher,
+            &stats_.srtp_cipher);
       }
     }
   }
@@ -170,6 +175,16 @@
     return stats_.available_receive_bandwidth;
   }
 
+  std::string DtlsCipher() const {
+    ASSERT(called_);
+    return stats_.dtls_cipher;
+  }
+
+  std::string SrtpCipher() const {
+    ASSERT(called_);
+    return stats_.srtp_cipher;
+  }
+
  private:
   bool GetIntValue(const StatsReport* report,
                    StatsReport::StatsValueName name,
@@ -182,15 +197,39 @@
     }
     return false;
   }
+  bool GetStringValue(const StatsReport* report,
+                      StatsReport::StatsValueName name,
+                      std::string* value) {
+    for (const auto& v : report->values()) {
+      if (v->name == name) {
+        *value = v->value;
+        return true;
+      }
+    }
+    return false;
+  }
 
   bool called_;
   struct {
+    void Clear() {
+      number_of_reports = 0;
+      audio_output_level = 0;
+      audio_input_level = 0;
+      bytes_received = 0;
+      bytes_sent = 0;
+      available_receive_bandwidth = 0;
+      dtls_cipher.clear();
+      srtp_cipher.clear();
+    }
+
     size_t number_of_reports;
     int audio_output_level;
     int audio_input_level;
     int bytes_received;
     int bytes_sent;
     int available_receive_bandwidth;
+    std::string dtls_cipher;
+    std::string srtp_cipher;
   } stats_;
 };