diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /third_party/libwebrtc/pc/legacy_stats_collector_unittest.cc | |
parent | Initial commit. (diff) | |
download | thunderbird-upstream.tar.xz thunderbird-upstream.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/pc/legacy_stats_collector_unittest.cc')
-rw-r--r-- | third_party/libwebrtc/pc/legacy_stats_collector_unittest.cc | 1902 |
1 files changed, 1902 insertions, 0 deletions
diff --git a/third_party/libwebrtc/pc/legacy_stats_collector_unittest.cc b/third_party/libwebrtc/pc/legacy_stats_collector_unittest.cc new file mode 100644 index 0000000000..3033172e95 --- /dev/null +++ b/third_party/libwebrtc/pc/legacy_stats_collector_unittest.cc @@ -0,0 +1,1902 @@ +/* + * Copyright 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "pc/legacy_stats_collector.h" + +#include <stdio.h> + +#include <cstdint> + +#include "absl/algorithm/container.h" +#include "absl/types/optional.h" +#include "api/audio_codecs/audio_encoder.h" +#include "api/candidate.h" +#include "api/data_channel_interface.h" +#include "api/media_stream_track.h" +#include "api/media_types.h" +#include "api/rtp_sender_interface.h" +#include "api/scoped_refptr.h" +#include "call/call.h" +#include "media/base/media_channel.h" +#include "modules/audio_processing/include/audio_processing_statistics.h" +#include "p2p/base/ice_transport_internal.h" +#include "pc/media_stream.h" +#include "pc/rtp_receiver.h" +#include "pc/rtp_sender.h" +#include "pc/sctp_data_channel.h" +#include "pc/test/fake_peer_connection_for_stats.h" +#include "pc/test/fake_video_track_source.h" +#include "pc/test/mock_rtp_receiver_internal.h" +#include "pc/test/mock_rtp_sender_internal.h" +#include "pc/transport_stats.h" +#include "pc/video_track.h" +#include "rtc_base/fake_ssl_identity.h" +#include "rtc_base/message_digest.h" +#include "rtc_base/net_helper.h" +#include "rtc_base/rtc_certificate.h" +#include "rtc_base/socket_address.h" +#include "rtc_base/ssl_identity.h" +#include "rtc_base/ssl_stream_adapter.h" +#include "rtc_base/string_encode.h" +#include "rtc_base/third_party/base64/base64.h" +#include "rtc_base/thread.h" +#include "test/gmock.h" +#include "test/gtest.h" + +using cricket::ConnectionInfo; +using cricket::SsrcReceiverInfo; +using cricket::TransportChannelStats; +using cricket::VideoMediaInfo; +using cricket::VideoReceiverInfo; +using cricket::VideoSenderInfo; +using cricket::VoiceMediaInfo; +using cricket::VoiceReceiverInfo; +using cricket::VoiceSenderInfo; +using ::testing::_; +using ::testing::AtMost; +using ::testing::Return; +using ::testing::UnorderedElementsAre; + +namespace webrtc { + +namespace internal { +// This value comes from openssl/tls1.h +static const int TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA = 0xC014; +} // namespace internal + +// Error return values +const char kNotFound[] = "NOT FOUND"; + +// Constant names for track identification. +const char kLocalTrackId[] = "local_track_id"; +const char kRemoteTrackId[] = "remote_track_id"; +const uint32_t kSsrcOfTrack = 1234; + +class FakeAudioProcessor : public AudioProcessorInterface { + public: + FakeAudioProcessor() {} + ~FakeAudioProcessor() {} + + private: + AudioProcessorInterface::AudioProcessorStatistics GetStats( + bool has_recv_streams) override { + AudioProcessorStatistics stats; + if (has_recv_streams) { + stats.apm_statistics.echo_return_loss = 2.0; + stats.apm_statistics.echo_return_loss_enhancement = 3.0; + stats.apm_statistics.delay_median_ms = 4; + stats.apm_statistics.delay_standard_deviation_ms = 5; + } + return stats; + } +}; + +class FakeAudioTrack : public MediaStreamTrack<AudioTrackInterface> { + public: + explicit FakeAudioTrack(const std::string& id) + : MediaStreamTrack<AudioTrackInterface>(id), + processor_(rtc::make_ref_counted<FakeAudioProcessor>()) {} + std::string kind() const override { return "audio"; } + AudioSourceInterface* GetSource() const override { return NULL; } + void AddSink(AudioTrackSinkInterface* sink) override {} + void RemoveSink(AudioTrackSinkInterface* sink) override {} + bool GetSignalLevel(int* level) override { + *level = 1; + return true; + } + rtc::scoped_refptr<AudioProcessorInterface> GetAudioProcessor() override { + return processor_; + } + + private: + rtc::scoped_refptr<FakeAudioProcessor> processor_; +}; + +// This fake audio processor is used to verify that the undesired initial values +// (-1) will be filtered out. +class FakeAudioProcessorWithInitValue : public AudioProcessorInterface { + public: + FakeAudioProcessorWithInitValue() {} + ~FakeAudioProcessorWithInitValue() {} + + private: + AudioProcessorInterface::AudioProcessorStatistics GetStats( + bool /*has_recv_streams*/) override { + AudioProcessorStatistics stats; + return stats; + } +}; + +class FakeAudioTrackWithInitValue + : public MediaStreamTrack<AudioTrackInterface> { + public: + explicit FakeAudioTrackWithInitValue(const std::string& id) + : MediaStreamTrack<AudioTrackInterface>(id), + processor_(rtc::make_ref_counted<FakeAudioProcessorWithInitValue>()) {} + std::string kind() const override { return "audio"; } + AudioSourceInterface* GetSource() const override { return NULL; } + void AddSink(AudioTrackSinkInterface* sink) override {} + void RemoveSink(AudioTrackSinkInterface* sink) override {} + bool GetSignalLevel(int* level) override { + *level = 1; + return true; + } + rtc::scoped_refptr<AudioProcessorInterface> GetAudioProcessor() override { + return processor_; + } + + private: + rtc::scoped_refptr<FakeAudioProcessorWithInitValue> processor_; +}; + +bool GetValue(const StatsReport* report, + StatsReport::StatsValueName name, + std::string* value) { + const StatsReport::Value* v = report->FindValue(name); + if (!v) + return false; + *value = v->ToString(); + return true; +} + +std::string ExtractStatsValue(const StatsReport::StatsType& type, + const StatsReports& reports, + StatsReport::StatsValueName name) { + for (const auto* r : reports) { + std::string ret; + if (r->type() == type && GetValue(r, name, &ret)) + return ret; + } + + return kNotFound; +} + +StatsReport::Id TypedIdFromIdString(StatsReport::StatsType type, + const std::string& value) { + EXPECT_FALSE(value.empty()); + StatsReport::Id id; + if (value.empty()) + return id; + + // This has assumptions about how the ID is constructed. As is, this is + // OK since this is for testing purposes only, but if we ever need this + // in production, we should add a generic method that does this. + size_t index = value.find('_'); + EXPECT_NE(index, std::string::npos); + if (index == std::string::npos || index == (value.length() - 1)) + return id; + + id = StatsReport::NewTypedId(type, value.substr(index + 1)); + EXPECT_EQ(id->ToString(), value); + return id; +} + +StatsReport::Id IdFromCertIdString(const std::string& cert_id) { + return TypedIdFromIdString(StatsReport::kStatsReportTypeCertificate, cert_id); +} + +// Finds the `n`-th report of type `type` in `reports`. +// `n` starts from 1 for finding the first report. +const StatsReport* FindNthReportByType(const StatsReports& reports, + const StatsReport::StatsType& type, + int n) { + for (size_t i = 0; i < reports.size(); ++i) { + if (reports[i]->type() == type) { + n--; + if (n == 0) + return reports[i]; + } + } + return nullptr; +} + +// Returns the value of the stat identified by `name` in the `n`-th report of +// type `type` in `reports`. +// `n` starts from 1 for finding the first report. +// If either the `n`-th report is not found, or the stat is not present in that +// report, then nullopt is returned. +absl::optional<std::string> GetValueInNthReportByType( + const StatsReports& reports, + StatsReport::StatsType type, + StatsReport::StatsValueName name, + int n) { + const StatsReport* report = FindNthReportByType(reports, type, n); + if (!report) { + return absl::nullopt; + } + std::string value; + if (!GetValue(report, name, &value)) { + return absl::nullopt; + } + return value; +} + +std::vector<const StatsReport*> GetReportsByType(const StatsReports& reports, + StatsReport::StatsType type) { + std::vector<const StatsReport*> filtered_reports; + for (const StatsReport* report : reports) { + if (report->type() == type) { + filtered_reports.push_back(report); + } + } + return filtered_reports; +} + +const StatsReport* FindReportById(const StatsReports& reports, + const StatsReport::Id& id) { + for (const auto* r : reports) { + if (r->id()->Equals(id)) + return r; + } + return nullptr; +} + +std::string ExtractSsrcStatsValue(const StatsReports& reports, + StatsReport::StatsValueName name) { + return ExtractStatsValue(StatsReport::kStatsReportTypeSsrc, reports, name); +} + +std::string ExtractBweStatsValue(const StatsReports& reports, + StatsReport::StatsValueName name) { + return ExtractStatsValue(StatsReport::kStatsReportTypeBwe, reports, name); +} + +std::string DerToPem(const std::string& der) { + return rtc::SSLIdentity::DerToPem( + rtc::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()); + absl::c_transform(ders, pems.begin(), DerToPem); + return pems; +} + +void CheckCertChainReports(const StatsReports& reports, + const std::vector<std::string>& ders, + const StatsReport::Id& start_id) { + StatsReport::Id cert_id; + const StatsReport::Id* certificate_id = &start_id; + size_t i = 0; + while (true) { + const StatsReport* report = FindReportById(reports, *certificate_id); + ASSERT_TRUE(report != NULL); + + std::string der_base64; + EXPECT_TRUE(GetValue(report, StatsReport::kStatsValueNameDer, &der_base64)); + std::string der = rtc::Base64::Decode(der_base64, rtc::Base64::DO_STRICT); + EXPECT_EQ(ders[i], der); + + std::string fingerprint_algorithm; + EXPECT_TRUE(GetValue(report, + StatsReport::kStatsValueNameFingerprintAlgorithm, + &fingerprint_algorithm)); + // The digest algorithm for a FakeSSLCertificate is always SHA-1. + std::string sha_1_str = rtc::DIGEST_SHA_1; + EXPECT_EQ(sha_1_str, fingerprint_algorithm); + + std::string fingerprint; + EXPECT_TRUE(GetValue(report, StatsReport::kStatsValueNameFingerprint, + &fingerprint)); + EXPECT_FALSE(fingerprint.empty()); + + ++i; + std::string issuer_id; + if (!GetValue(report, StatsReport::kStatsValueNameIssuerId, &issuer_id)) { + break; + } + + cert_id = IdFromCertIdString(issuer_id); + certificate_id = &cert_id; + } + EXPECT_EQ(ders.size(), i); +} + +void VerifyVoiceReceiverInfoReport(const StatsReport* report, + const cricket::VoiceReceiverInfo& info) { + std::string value_in_report; + EXPECT_TRUE(GetValue(report, StatsReport::kStatsValueNameAudioOutputLevel, + &value_in_report)); + EXPECT_EQ(rtc::ToString(info.audio_level), value_in_report); + EXPECT_TRUE(GetValue(report, StatsReport::kStatsValueNameBytesReceived, + &value_in_report)); + EXPECT_EQ(rtc::ToString(info.payload_bytes_rcvd + + info.header_and_padding_bytes_rcvd), + value_in_report); + EXPECT_TRUE(GetValue(report, StatsReport::kStatsValueNameJitterReceived, + &value_in_report)); + EXPECT_EQ(rtc::ToString(info.jitter_ms), value_in_report); + EXPECT_TRUE(GetValue(report, StatsReport::kStatsValueNameJitterBufferMs, + &value_in_report)); + EXPECT_EQ(rtc::ToString(info.jitter_buffer_ms), value_in_report); + EXPECT_TRUE(GetValue(report, + StatsReport::kStatsValueNamePreferredJitterBufferMs, + &value_in_report)); + EXPECT_EQ(rtc::ToString(info.jitter_buffer_preferred_ms), value_in_report); + EXPECT_TRUE(GetValue(report, StatsReport::kStatsValueNameCurrentDelayMs, + &value_in_report)); + EXPECT_EQ(rtc::ToString(info.delay_estimate_ms), value_in_report); + EXPECT_TRUE(GetValue(report, StatsReport::kStatsValueNameExpandRate, + &value_in_report)); + EXPECT_EQ(rtc::ToString(info.expand_rate), value_in_report); + EXPECT_TRUE(GetValue(report, StatsReport::kStatsValueNameSpeechExpandRate, + &value_in_report)); + EXPECT_EQ(rtc::ToString(info.speech_expand_rate), value_in_report); + EXPECT_TRUE(GetValue(report, StatsReport::kStatsValueNameAccelerateRate, + &value_in_report)); + EXPECT_EQ(rtc::ToString(info.accelerate_rate), value_in_report); + EXPECT_TRUE(GetValue(report, StatsReport::kStatsValueNamePreemptiveExpandRate, + &value_in_report)); + EXPECT_EQ(rtc::ToString(info.preemptive_expand_rate), value_in_report); + EXPECT_TRUE(GetValue(report, StatsReport::kStatsValueNameSecondaryDecodedRate, + &value_in_report)); + EXPECT_EQ(rtc::ToString(info.secondary_decoded_rate), value_in_report); + EXPECT_TRUE(GetValue(report, + StatsReport::kStatsValueNameSecondaryDiscardedRate, + &value_in_report)); + EXPECT_EQ(rtc::ToString(info.secondary_discarded_rate), value_in_report); + EXPECT_TRUE(GetValue(report, StatsReport::kStatsValueNamePacketsReceived, + &value_in_report)); + EXPECT_EQ(rtc::ToString(info.packets_rcvd), value_in_report); + EXPECT_TRUE(GetValue(report, StatsReport::kStatsValueNameDecodingCTSG, + &value_in_report)); + EXPECT_EQ(rtc::ToString(info.decoding_calls_to_silence_generator), + value_in_report); + EXPECT_TRUE(GetValue(report, StatsReport::kStatsValueNameDecodingCTN, + &value_in_report)); + EXPECT_EQ(rtc::ToString(info.decoding_calls_to_neteq), value_in_report); + EXPECT_TRUE(GetValue(report, StatsReport::kStatsValueNameDecodingNormal, + &value_in_report)); + EXPECT_EQ(rtc::ToString(info.decoding_normal), value_in_report); + EXPECT_TRUE(GetValue(report, StatsReport::kStatsValueNameDecodingPLC, + &value_in_report)); + EXPECT_EQ(rtc::ToString(info.decoding_plc), value_in_report); + EXPECT_TRUE(GetValue(report, StatsReport::kStatsValueNameDecodingCodecPLC, + &value_in_report)); + EXPECT_EQ(rtc::ToString(info.decoding_codec_plc), value_in_report); + EXPECT_TRUE(GetValue(report, StatsReport::kStatsValueNameDecodingCNG, + &value_in_report)); + EXPECT_EQ(rtc::ToString(info.decoding_cng), value_in_report); + EXPECT_TRUE(GetValue(report, StatsReport::kStatsValueNameDecodingPLCCNG, + &value_in_report)); + EXPECT_EQ(rtc::ToString(info.decoding_plc_cng), value_in_report); + EXPECT_TRUE(GetValue(report, StatsReport::kStatsValueNameDecodingMutedOutput, + &value_in_report)); + EXPECT_EQ(rtc::ToString(info.decoding_muted_output), value_in_report); + EXPECT_TRUE(GetValue(report, StatsReport::kStatsValueNameCodecName, + &value_in_report)); +} + +void VerifyVoiceSenderInfoReport(const StatsReport* report, + const cricket::VoiceSenderInfo& sinfo) { + std::string value_in_report; + EXPECT_TRUE(GetValue(report, StatsReport::kStatsValueNameCodecName, + &value_in_report)); + EXPECT_EQ(sinfo.codec_name, value_in_report); + EXPECT_TRUE(GetValue(report, StatsReport::kStatsValueNameBytesSent, + &value_in_report)); + EXPECT_EQ(rtc::ToString(sinfo.payload_bytes_sent + + sinfo.header_and_padding_bytes_sent), + value_in_report); + EXPECT_TRUE(GetValue(report, StatsReport::kStatsValueNamePacketsSent, + &value_in_report)); + EXPECT_EQ(rtc::ToString(sinfo.packets_sent), value_in_report); + EXPECT_TRUE(GetValue(report, StatsReport::kStatsValueNamePacketsLost, + &value_in_report)); + EXPECT_EQ(rtc::ToString(sinfo.packets_lost), value_in_report); + EXPECT_TRUE( + GetValue(report, StatsReport::kStatsValueNameRtt, &value_in_report)); + EXPECT_EQ(rtc::ToString(sinfo.rtt_ms), value_in_report); + EXPECT_TRUE( + GetValue(report, StatsReport::kStatsValueNameRtt, &value_in_report)); + EXPECT_EQ(rtc::ToString(sinfo.rtt_ms), value_in_report); + EXPECT_TRUE(GetValue(report, StatsReport::kStatsValueNameJitterReceived, + &value_in_report)); + EXPECT_EQ(rtc::ToString(sinfo.jitter_ms), value_in_report); + if (sinfo.apm_statistics.delay_median_ms) { + EXPECT_TRUE(GetValue(report, StatsReport::kStatsValueNameEchoDelayMedian, + &value_in_report)); + EXPECT_EQ(rtc::ToString(*sinfo.apm_statistics.delay_median_ms), + value_in_report); + } else { + EXPECT_FALSE(GetValue(report, StatsReport::kStatsValueNameEchoDelayMedian, + &value_in_report)); + } + if (sinfo.apm_statistics.delay_standard_deviation_ms) { + EXPECT_TRUE(GetValue(report, StatsReport::kStatsValueNameEchoDelayStdDev, + &value_in_report)); + EXPECT_EQ(rtc::ToString(*sinfo.apm_statistics.delay_standard_deviation_ms), + value_in_report); + } else { + EXPECT_FALSE(GetValue(report, StatsReport::kStatsValueNameEchoDelayStdDev, + &value_in_report)); + } + if (sinfo.apm_statistics.echo_return_loss) { + EXPECT_TRUE(GetValue(report, StatsReport::kStatsValueNameEchoReturnLoss, + &value_in_report)); + EXPECT_EQ(rtc::ToString(*sinfo.apm_statistics.echo_return_loss), + value_in_report); + } else { + EXPECT_FALSE(GetValue(report, StatsReport::kStatsValueNameEchoReturnLoss, + &value_in_report)); + } + if (sinfo.apm_statistics.echo_return_loss_enhancement) { + EXPECT_TRUE(GetValue(report, + StatsReport::kStatsValueNameEchoReturnLossEnhancement, + &value_in_report)); + EXPECT_EQ(rtc::ToString(*sinfo.apm_statistics.echo_return_loss_enhancement), + value_in_report); + } else { + EXPECT_FALSE(GetValue(report, + StatsReport::kStatsValueNameEchoReturnLossEnhancement, + &value_in_report)); + } + if (sinfo.apm_statistics.residual_echo_likelihood) { + EXPECT_TRUE(GetValue(report, + StatsReport::kStatsValueNameResidualEchoLikelihood, + &value_in_report)); + EXPECT_EQ(rtc::ToString(*sinfo.apm_statistics.residual_echo_likelihood), + value_in_report); + } else { + EXPECT_FALSE(GetValue(report, + StatsReport::kStatsValueNameResidualEchoLikelihood, + &value_in_report)); + } + if (sinfo.apm_statistics.residual_echo_likelihood_recent_max) { + EXPECT_TRUE(GetValue( + report, StatsReport::kStatsValueNameResidualEchoLikelihoodRecentMax, + &value_in_report)); + EXPECT_EQ(rtc::ToString( + *sinfo.apm_statistics.residual_echo_likelihood_recent_max), + value_in_report); + } else { + EXPECT_FALSE(GetValue( + report, StatsReport::kStatsValueNameResidualEchoLikelihoodRecentMax, + &value_in_report)); + } + EXPECT_TRUE(GetValue(report, StatsReport::kStatsValueNameAudioInputLevel, + &value_in_report)); + EXPECT_EQ(rtc::ToString(sinfo.audio_level), value_in_report); + EXPECT_TRUE(GetValue(report, + StatsReport::kStatsValueNameAnaBitrateActionCounter, + &value_in_report)); + ASSERT_TRUE(sinfo.ana_statistics.bitrate_action_counter); + EXPECT_EQ(rtc::ToString(*sinfo.ana_statistics.bitrate_action_counter), + value_in_report); + EXPECT_TRUE(GetValue(report, + StatsReport::kStatsValueNameAnaChannelActionCounter, + &value_in_report)); + ASSERT_TRUE(sinfo.ana_statistics.channel_action_counter); + EXPECT_EQ(rtc::ToString(*sinfo.ana_statistics.channel_action_counter), + value_in_report); + EXPECT_TRUE(GetValue(report, StatsReport::kStatsValueNameAnaDtxActionCounter, + &value_in_report)); + ASSERT_TRUE(sinfo.ana_statistics.dtx_action_counter); + EXPECT_EQ(rtc::ToString(*sinfo.ana_statistics.dtx_action_counter), + value_in_report); + EXPECT_TRUE(GetValue(report, StatsReport::kStatsValueNameAnaFecActionCounter, + &value_in_report)); + ASSERT_TRUE(sinfo.ana_statistics.fec_action_counter); + EXPECT_EQ(rtc::ToString(*sinfo.ana_statistics.fec_action_counter), + value_in_report); + EXPECT_TRUE(GetValue( + report, StatsReport::kStatsValueNameAnaFrameLengthIncreaseCounter, + &value_in_report)); + ASSERT_TRUE(sinfo.ana_statistics.frame_length_increase_counter); + EXPECT_EQ(rtc::ToString(*sinfo.ana_statistics.frame_length_increase_counter), + value_in_report); + EXPECT_TRUE(GetValue( + report, StatsReport::kStatsValueNameAnaFrameLengthDecreaseCounter, + &value_in_report)); + ASSERT_TRUE(sinfo.ana_statistics.frame_length_decrease_counter); + EXPECT_EQ(rtc::ToString(*sinfo.ana_statistics.frame_length_decrease_counter), + value_in_report); + EXPECT_TRUE(GetValue(report, + StatsReport::kStatsValueNameAnaUplinkPacketLossFraction, + &value_in_report)); + ASSERT_TRUE(sinfo.ana_statistics.uplink_packet_loss_fraction); + EXPECT_EQ(rtc::ToString(*sinfo.ana_statistics.uplink_packet_loss_fraction), + value_in_report); +} + +// Helper methods to avoid duplication of code. +void InitVoiceSenderInfo(cricket::VoiceSenderInfo* voice_sender_info, + uint32_t ssrc = kSsrcOfTrack) { + voice_sender_info->add_ssrc(ssrc); + voice_sender_info->codec_name = "fake_codec"; + voice_sender_info->payload_bytes_sent = 88; + voice_sender_info->header_and_padding_bytes_sent = 12; + voice_sender_info->packets_sent = 101; + voice_sender_info->rtt_ms = 102; + voice_sender_info->fraction_lost = 103; + voice_sender_info->jitter_ms = 104; + voice_sender_info->packets_lost = 105; + voice_sender_info->audio_level = 107; + voice_sender_info->apm_statistics.echo_return_loss = 108; + voice_sender_info->apm_statistics.echo_return_loss_enhancement = 109; + voice_sender_info->apm_statistics.delay_median_ms = 110; + voice_sender_info->apm_statistics.delay_standard_deviation_ms = 111; + voice_sender_info->ana_statistics.bitrate_action_counter = 112; + voice_sender_info->ana_statistics.channel_action_counter = 113; + voice_sender_info->ana_statistics.dtx_action_counter = 114; + voice_sender_info->ana_statistics.fec_action_counter = 115; + voice_sender_info->ana_statistics.frame_length_increase_counter = 116; + voice_sender_info->ana_statistics.frame_length_decrease_counter = 117; + voice_sender_info->ana_statistics.uplink_packet_loss_fraction = 118.0; +} + +void UpdateVoiceSenderInfoFromAudioTrack( + AudioTrackInterface* audio_track, + cricket::VoiceSenderInfo* voice_sender_info, + bool has_remote_tracks) { + audio_track->GetSignalLevel(&voice_sender_info->audio_level); + AudioProcessorInterface::AudioProcessorStatistics audio_processor_stats = + audio_track->GetAudioProcessor()->GetStats(has_remote_tracks); + voice_sender_info->apm_statistics = audio_processor_stats.apm_statistics; +} + +void InitVoiceReceiverInfo(cricket::VoiceReceiverInfo* voice_receiver_info) { + voice_receiver_info->add_ssrc(kSsrcOfTrack); + voice_receiver_info->payload_bytes_rcvd = 98; + voice_receiver_info->header_and_padding_bytes_rcvd = 12; + voice_receiver_info->packets_rcvd = 111; + voice_receiver_info->packets_lost = 114; + voice_receiver_info->jitter_ms = 116; + voice_receiver_info->jitter_buffer_ms = 117; + voice_receiver_info->jitter_buffer_preferred_ms = 118; + voice_receiver_info->delay_estimate_ms = 119; + voice_receiver_info->audio_level = 120; + voice_receiver_info->expand_rate = 121; + voice_receiver_info->speech_expand_rate = 122; + voice_receiver_info->secondary_decoded_rate = 123; + voice_receiver_info->accelerate_rate = 124; + voice_receiver_info->preemptive_expand_rate = 125; + voice_receiver_info->secondary_discarded_rate = 126; + voice_receiver_info->decoding_codec_plc = 127; +} + +class LegacyStatsCollectorForTest : public LegacyStatsCollector { + public: + explicit LegacyStatsCollectorForTest(PeerConnectionInternal* pc) + : LegacyStatsCollector(pc), time_now_(19477) {} + + double GetTimeNow() override { return time_now_; } + + private: + double time_now_; +}; + +class LegacyStatsCollectorTest : public ::testing::Test { + protected: + rtc::scoped_refptr<FakePeerConnectionForStats> CreatePeerConnection() { + return rtc::make_ref_counted<FakePeerConnectionForStats>(); + } + + std::unique_ptr<LegacyStatsCollectorForTest> CreateStatsCollector( + PeerConnectionInternal* pc) { + return std::make_unique<LegacyStatsCollectorForTest>(pc); + } + + void VerifyAudioTrackStats(FakeAudioTrack* audio_track, + LegacyStatsCollectorForTest* stats, + const VoiceMediaInfo& voice_info, + StatsReports* reports) { + stats->UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard); + stats->InvalidateCache(); + stats->GetStats(nullptr, reports); + + // Verify the existence of the track report. + const StatsReport* report = + FindNthReportByType(*reports, StatsReport::kStatsReportTypeSsrc, 1); + ASSERT_TRUE(report); + EXPECT_EQ(stats->GetTimeNow(), report->timestamp()); + std::string track_id = + ExtractSsrcStatsValue(*reports, StatsReport::kStatsValueNameTrackId); + EXPECT_EQ(audio_track->id(), track_id); + std::string ssrc_id = + ExtractSsrcStatsValue(*reports, StatsReport::kStatsValueNameSsrc); + EXPECT_EQ(rtc::ToString(kSsrcOfTrack), ssrc_id); + + std::string media_type = + ExtractSsrcStatsValue(*reports, StatsReport::kStatsValueNameMediaType); + EXPECT_EQ("audio", media_type); + + // Verifies the values in the track report. + if (!voice_info.senders.empty()) { + VerifyVoiceSenderInfoReport(report, voice_info.senders[0]); + } + if (!voice_info.receivers.empty()) { + VerifyVoiceReceiverInfoReport(report, voice_info.receivers[0]); + } + + // Verify we get the same result by passing a track to GetStats(). + StatsReports track_reports; // returned values. + stats->GetStats(audio_track, &track_reports); + const StatsReport* track_report = FindNthReportByType( + track_reports, StatsReport::kStatsReportTypeSsrc, 1); + ASSERT_TRUE(track_report); + EXPECT_EQ(stats->GetTimeNow(), track_report->timestamp()); + track_id = ExtractSsrcStatsValue(track_reports, + StatsReport::kStatsValueNameTrackId); + EXPECT_EQ(audio_track->id(), track_id); + ssrc_id = + ExtractSsrcStatsValue(track_reports, StatsReport::kStatsValueNameSsrc); + EXPECT_EQ(rtc::ToString(kSsrcOfTrack), ssrc_id); + if (!voice_info.senders.empty()) { + VerifyVoiceSenderInfoReport(track_report, voice_info.senders[0]); + } + if (!voice_info.receivers.empty()) { + VerifyVoiceReceiverInfoReport(track_report, voice_info.receivers[0]); + } + } + + void TestCertificateReports(const rtc::FakeSSLIdentity& local_identity, + const std::vector<std::string>& local_ders, + const rtc::FakeSSLIdentity& remote_identity, + const std::vector<std::string>& remote_ders) { + const std::string kTransportName = "transport"; + + auto pc = CreatePeerConnection(); + auto stats = CreateStatsCollector(pc.get()); + + pc->AddVoiceChannel("audio", kTransportName); + + // Fake stats to process. + TransportChannelStats channel_stats; + channel_stats.component = 1; + channel_stats.srtp_crypto_suite = rtc::kSrtpAes128CmSha1_80; + channel_stats.ssl_cipher_suite = + internal::TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA; + pc->SetTransportStats(kTransportName, channel_stats); + + // Fake certificate to report. + rtc::scoped_refptr<rtc::RTCCertificate> local_certificate( + rtc::RTCCertificate::Create(local_identity.Clone())); + pc->SetLocalCertificate(kTransportName, local_certificate); + pc->SetRemoteCertChain(kTransportName, + remote_identity.cert_chain().Clone()); + + stats->UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard); + + StatsReports reports; + stats->GetStats(nullptr, &reports); + + const StatsReport* channel_report = + FindNthReportByType(reports, StatsReport::kStatsReportTypeComponent, 1); + EXPECT_TRUE(channel_report); + + // Check local certificate chain. + std::string local_certificate_id = + ExtractStatsValue(StatsReport::kStatsReportTypeComponent, reports, + StatsReport::kStatsValueNameLocalCertificateId); + if (local_ders.size() > 0) { + EXPECT_NE(kNotFound, local_certificate_id); + StatsReport::Id id(IdFromCertIdString(local_certificate_id)); + CheckCertChainReports(reports, local_ders, id); + } else { + EXPECT_EQ(kNotFound, local_certificate_id); + } + + // Check remote certificate chain. + std::string remote_certificate_id = + ExtractStatsValue(StatsReport::kStatsReportTypeComponent, reports, + StatsReport::kStatsValueNameRemoteCertificateId); + if (remote_ders.size() > 0) { + EXPECT_NE(kNotFound, remote_certificate_id); + StatsReport::Id id(IdFromCertIdString(remote_certificate_id)); + CheckCertChainReports(reports, remote_ders, id); + } else { + EXPECT_EQ(kNotFound, remote_certificate_id); + } + + // Check negotiated ciphers. + std::string dtls_cipher_suite = + ExtractStatsValue(StatsReport::kStatsReportTypeComponent, reports, + StatsReport::kStatsValueNameDtlsCipher); + EXPECT_EQ(rtc::SSLStreamAdapter::SslCipherSuiteToName( + internal::TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA), + dtls_cipher_suite); + std::string srtp_crypto_suite = + ExtractStatsValue(StatsReport::kStatsReportTypeComponent, reports, + StatsReport::kStatsValueNameSrtpCipher); + EXPECT_EQ(rtc::SrtpCryptoSuiteToName(rtc::kSrtpAes128CmSha1_80), + srtp_crypto_suite); + } + + private: + rtc::AutoThread main_thread_; +}; + +static rtc::scoped_refptr<MockRtpSenderInternal> CreateMockSender( + rtc::scoped_refptr<MediaStreamTrackInterface> track, + uint32_t ssrc) { + auto sender = rtc::make_ref_counted<MockRtpSenderInternal>(); + EXPECT_CALL(*sender, track()).WillRepeatedly(Return(track)); + EXPECT_CALL(*sender, ssrc()).WillRepeatedly(Return(ssrc)); + EXPECT_CALL(*sender, media_type()) + .WillRepeatedly( + Return(track->kind() == MediaStreamTrackInterface::kAudioKind + ? cricket::MEDIA_TYPE_AUDIO + : cricket::MEDIA_TYPE_VIDEO)); + EXPECT_CALL(*sender, SetMediaChannel(_)).Times(AtMost(2)); + EXPECT_CALL(*sender, SetTransceiverAsStopped()).Times(AtMost(1)); + EXPECT_CALL(*sender, Stop()); + return sender; +} + +static rtc::scoped_refptr<MockRtpReceiverInternal> CreateMockReceiver( + rtc::scoped_refptr<MediaStreamTrackInterface> track, + uint32_t ssrc) { + auto receiver = rtc::make_ref_counted<MockRtpReceiverInternal>(); + EXPECT_CALL(*receiver, track()).WillRepeatedly(Return(track)); + EXPECT_CALL(*receiver, ssrc()).WillRepeatedly(Return(ssrc)); + EXPECT_CALL(*receiver, media_type()) + .WillRepeatedly( + Return(track->kind() == MediaStreamTrackInterface::kAudioKind + ? cricket::MEDIA_TYPE_AUDIO + : cricket::MEDIA_TYPE_VIDEO)); + EXPECT_CALL(*receiver, SetMediaChannel(_)).WillRepeatedly(Return()); + EXPECT_CALL(*receiver, Stop()).WillRepeatedly(Return()); + return receiver; +} + +class StatsCollectorTrackTest : public LegacyStatsCollectorTest, + public ::testing::WithParamInterface<bool> { + public: + // Adds a outgoing video track with a given SSRC into the stats. + // If GetParam() returns true, the track is also inserted into the local + // stream, which is created if necessary. + void AddOutgoingVideoTrack(FakePeerConnectionForStats* pc, + LegacyStatsCollectorForTest* stats) { + video_track_ = VideoTrack::Create( + kLocalTrackId, FakeVideoTrackSource::Create(), rtc::Thread::Current()); + if (GetParam()) { + if (!stream_) + stream_ = MediaStream::Create("streamid"); + stream_->AddTrack(video_track()); + stats->AddStream(stream_.get()); + } else { + stats->AddTrack(video_track_.get()); + } + pc->AddSender(CreateMockSender(video_track_, kSsrcOfTrack)); + } + + // Adds a incoming video track with a given SSRC into the stats. + void AddIncomingVideoTrack(FakePeerConnectionForStats* pc, + LegacyStatsCollectorForTest* stats) { + video_track_ = VideoTrack::Create( + kRemoteTrackId, FakeVideoTrackSource::Create(), rtc::Thread::Current()); + if (GetParam()) { + stream_ = MediaStream::Create("streamid"); + stream_->AddTrack(video_track()); + stats->AddStream(stream_.get()); + } else { + stats->AddTrack(video_track_.get()); + } + pc->AddReceiver(CreateMockReceiver(video_track_, kSsrcOfTrack)); + } + + // Adds a outgoing audio track with a given SSRC into the stats, + // and register it into the stats object. + // If GetParam() returns true, the track is also inserted into the local + // stream, which is created if necessary. + rtc::scoped_refptr<RtpSenderInterface> AddOutgoingAudioTrack( + FakePeerConnectionForStats* pc, + LegacyStatsCollectorForTest* stats) { + audio_track_ = rtc::make_ref_counted<FakeAudioTrack>(kLocalTrackId); + if (GetParam()) { + if (!stream_) + stream_ = MediaStream::Create("streamid"); + stream_->AddTrack(audio_track()); + stats->AddStream(stream_.get()); + } else { + stats->AddTrack(audio_track_.get()); + } + return pc->AddSender(CreateMockSender(audio_track_, kSsrcOfTrack)); + } + + // Adds a incoming audio track with a given SSRC into the stats. + void AddIncomingAudioTrack(FakePeerConnectionForStats* pc, + LegacyStatsCollectorForTest* stats) { + audio_track_ = rtc::make_ref_counted<FakeAudioTrack>(kRemoteTrackId); + if (GetParam()) { + if (stream_ == nullptr) + stream_ = MediaStream::Create("streamid"); + stream_->AddTrack(audio_track()); + stats->AddStream(stream_.get()); + } else { + stats->AddTrack(audio_track_.get()); + } + pc->AddReceiver(CreateMockReceiver(audio_track_, kSsrcOfTrack)); + } + + rtc::scoped_refptr<AudioTrackInterface> audio_track() { return audio_track_; } + rtc::scoped_refptr<VideoTrackInterface> video_track() { return video_track_; } + + rtc::scoped_refptr<MediaStream> stream_; + rtc::scoped_refptr<VideoTrack> video_track_; + rtc::scoped_refptr<FakeAudioTrack> audio_track_; +}; + +TEST_F(LegacyStatsCollectorTest, FilterOutNegativeDataChannelId) { + auto pc = CreatePeerConnection(); + auto stats = CreateStatsCollector(pc.get()); + + pc->AddSctpDataChannel("hacks"); + + stats->UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard); + StatsReports reports; + stats->GetStats(nullptr, &reports); + + const StatsReport* report = + FindNthReportByType(reports, StatsReport::kStatsReportTypeDataChannel, 1); + + std::string value_in_report; + EXPECT_FALSE(GetValue(report, StatsReport::kStatsValueNameDataChannelId, + &value_in_report)); +} + +// Verify that ExtractDataInfo populates reports. +TEST_F(LegacyStatsCollectorTest, ExtractDataInfo) { + const std::string kDataChannelLabel = "hacks"; + constexpr int kDataChannelId = 31337; + const std::string kConnectingString = DataChannelInterface::DataStateString( + DataChannelInterface::DataState::kConnecting); + + auto pc = CreatePeerConnection(); + auto stats = CreateStatsCollector(pc.get()); + + InternalDataChannelInit init; + init.id = kDataChannelId; + pc->AddSctpDataChannel(kDataChannelLabel, init); + + stats->UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard); + StatsReports reports; + stats->GetStats(nullptr, &reports); + + const StatsReport* report = + FindNthReportByType(reports, StatsReport::kStatsReportTypeDataChannel, 1); + + StatsReport::Id report_id = StatsReport::NewTypedIntId( + StatsReport::kStatsReportTypeDataChannel, kDataChannelId); + + EXPECT_TRUE(report_id->Equals(report->id())); + + EXPECT_EQ(stats->GetTimeNow(), report->timestamp()); + EXPECT_EQ(kDataChannelLabel, + ExtractStatsValue(StatsReport::kStatsReportTypeDataChannel, reports, + StatsReport::kStatsValueNameLabel)); + EXPECT_EQ(rtc::ToString(kDataChannelId), + ExtractStatsValue(StatsReport::kStatsReportTypeDataChannel, reports, + StatsReport::kStatsValueNameDataChannelId)); + EXPECT_EQ(kConnectingString, + ExtractStatsValue(StatsReport::kStatsReportTypeDataChannel, reports, + StatsReport::kStatsValueNameState)); + EXPECT_EQ("", + ExtractStatsValue(StatsReport::kStatsReportTypeDataChannel, reports, + StatsReport::kStatsValueNameProtocol)); +} + +// This test verifies that 64-bit counters are passed successfully. +TEST_P(StatsCollectorTrackTest, BytesCounterHandles64Bits) { + // The number of bytes must be larger than 0xFFFFFFFF for this test. + constexpr int64_t kBytesSent = 12345678901234LL; + + auto pc = CreatePeerConnection(); + auto stats = CreateStatsCollector(pc.get()); + + VideoSenderInfo video_sender_info; + video_sender_info.add_ssrc(1234); + video_sender_info.payload_bytes_sent = kBytesSent; + video_sender_info.header_and_padding_bytes_sent = 0; + VideoMediaInfo video_info; + video_info.aggregated_senders.push_back(video_sender_info); + + pc->AddVideoChannel("video", "transport", video_info); + + AddOutgoingVideoTrack(pc.get(), stats.get()); + + stats->UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard); + StatsReports reports; + stats->GetStats(nullptr, &reports); + + EXPECT_EQ( + rtc::ToString(kBytesSent), + ExtractSsrcStatsValue(reports, StatsReport::kStatsValueNameBytesSent)); +} + +// Test that audio BWE information is reported via stats. +TEST_P(StatsCollectorTrackTest, AudioBandwidthEstimationInfoIsReported) { + // Set up an SSRC just to test that we get both kinds of stats back: SSRC and + // BWE. + constexpr int64_t kBytesSent = 12345678901234LL; + constexpr int kSendBandwidth = 1234567; + constexpr int kRecvBandwidth = 12345678; + constexpr int kPacerDelay = 123; + + auto pc = CreatePeerConnection(); + auto stats = CreateStatsCollector(pc.get()); + + VoiceSenderInfo voice_sender_info; + voice_sender_info.add_ssrc(1234); + voice_sender_info.payload_bytes_sent = kBytesSent - 12; + voice_sender_info.header_and_padding_bytes_sent = 12; + VoiceMediaInfo voice_info; + voice_info.senders.push_back(voice_sender_info); + + auto* voice_media_channel = pc->AddVoiceChannel("audio", "transport"); + voice_media_channel->SetStats(voice_info); + + AddOutgoingAudioTrack(pc.get(), stats.get()); + + Call::Stats call_stats; + call_stats.send_bandwidth_bps = kSendBandwidth; + call_stats.recv_bandwidth_bps = kRecvBandwidth; + call_stats.pacer_delay_ms = kPacerDelay; + pc->SetCallStats(call_stats); + + stats->UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard); + StatsReports reports; + stats->GetStats(nullptr, &reports); + + EXPECT_EQ( + rtc::ToString(kBytesSent), + ExtractSsrcStatsValue(reports, StatsReport::kStatsValueNameBytesSent)); + EXPECT_EQ(rtc::ToString(kSendBandwidth), + ExtractBweStatsValue( + reports, StatsReport::kStatsValueNameAvailableSendBandwidth)); + EXPECT_EQ( + rtc::ToString(kRecvBandwidth), + ExtractBweStatsValue( + reports, StatsReport::kStatsValueNameAvailableReceiveBandwidth)); + EXPECT_EQ( + rtc::ToString(kPacerDelay), + ExtractBweStatsValue(reports, StatsReport::kStatsValueNameBucketDelay)); +} + +// Test that video BWE information is reported via stats. +TEST_P(StatsCollectorTrackTest, VideoBandwidthEstimationInfoIsReported) { + // Set up an SSRC just to test that we get both kinds of stats back: SSRC and + // BWE. + constexpr int64_t kBytesSent = 12345678901234LL; + constexpr int kSendBandwidth = 1234567; + constexpr int kRecvBandwidth = 12345678; + constexpr int kPacerDelay = 123; + + auto pc = CreatePeerConnection(); + auto stats = CreateStatsCollector(pc.get()); + + VideoSenderInfo video_sender_info; + video_sender_info.add_ssrc(1234); + video_sender_info.payload_bytes_sent = kBytesSent - 12; + video_sender_info.header_and_padding_bytes_sent = 12; + + VideoMediaInfo video_info; + video_info.aggregated_senders.push_back(video_sender_info); + + pc->AddVideoChannel("video", "transport", video_info); + + AddOutgoingVideoTrack(pc.get(), stats.get()); + + Call::Stats call_stats; + call_stats.send_bandwidth_bps = kSendBandwidth; + call_stats.recv_bandwidth_bps = kRecvBandwidth; + call_stats.pacer_delay_ms = kPacerDelay; + pc->SetCallStats(call_stats); + + stats->UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard); + StatsReports reports; + stats->GetStats(nullptr, &reports); + + EXPECT_EQ( + rtc::ToString(kBytesSent), + ExtractSsrcStatsValue(reports, StatsReport::kStatsValueNameBytesSent)); + EXPECT_EQ(rtc::ToString(kSendBandwidth), + ExtractBweStatsValue( + reports, StatsReport::kStatsValueNameAvailableSendBandwidth)); + EXPECT_EQ( + rtc::ToString(kRecvBandwidth), + ExtractBweStatsValue( + reports, StatsReport::kStatsValueNameAvailableReceiveBandwidth)); + EXPECT_EQ( + rtc::ToString(kPacerDelay), + ExtractBweStatsValue(reports, StatsReport::kStatsValueNameBucketDelay)); +} + +// This test verifies that an object of type "googSession" always +// exists in the returned stats. +TEST_F(LegacyStatsCollectorTest, SessionObjectExists) { + auto pc = CreatePeerConnection(); + auto stats = CreateStatsCollector(pc.get()); + + stats->UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard); + StatsReports reports; + stats->GetStats(nullptr, &reports); + + EXPECT_TRUE( + FindNthReportByType(reports, StatsReport::kStatsReportTypeSession, 1)); +} + +// This test verifies that only one object of type "googSession" exists +// in the returned stats. +TEST_F(LegacyStatsCollectorTest, OnlyOneSessionObjectExists) { + auto pc = CreatePeerConnection(); + auto stats = CreateStatsCollector(pc.get()); + + stats->UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard); + stats->UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard); + StatsReports reports; + stats->GetStats(nullptr, &reports); + + EXPECT_TRUE( + FindNthReportByType(reports, StatsReport::kStatsReportTypeSession, 1)); + EXPECT_FALSE( + FindNthReportByType(reports, StatsReport::kStatsReportTypeSession, 2)); +} + +// This test verifies that the empty track report exists in the returned stats +// without calling StatsCollector::UpdateStats. +TEST_P(StatsCollectorTrackTest, TrackObjectExistsWithoutUpdateStats) { + auto pc = CreatePeerConnection(); + auto stats = CreateStatsCollector(pc.get()); + + pc->AddVideoChannel("video", "transport"); + AddOutgoingVideoTrack(pc.get(), stats.get()); + + // Verfies the existence of the track report. + StatsReports reports; + stats->GetStats(nullptr, &reports); + ASSERT_EQ(1u, reports.size()); + EXPECT_EQ(StatsReport::kStatsReportTypeTrack, reports[0]->type()); + EXPECT_EQ(0, reports[0]->timestamp()); + + std::string trackValue = + ExtractStatsValue(StatsReport::kStatsReportTypeTrack, reports, + StatsReport::kStatsValueNameTrackId); + EXPECT_EQ(kLocalTrackId, trackValue); +} + +// This test verifies that the empty track report exists in the returned stats +// when StatsCollector::UpdateStats is called with ssrc stats. +TEST_P(StatsCollectorTrackTest, TrackAndSsrcObjectExistAfterUpdateSsrcStats) { + constexpr int64_t kBytesSent = 12345678901234LL; + + auto pc = CreatePeerConnection(); + auto stats = CreateStatsCollector(pc.get()); + + VideoSenderInfo video_sender_info; + video_sender_info.add_ssrc(1234); + video_sender_info.payload_bytes_sent = kBytesSent - 12; + video_sender_info.header_and_padding_bytes_sent = 12; + VideoMediaInfo video_info; + video_info.aggregated_senders.push_back(video_sender_info); + + pc->AddVideoChannel("video", "transport", video_info); + + AddOutgoingVideoTrack(pc.get(), stats.get()); + + stats->UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard); + StatsReports reports; + stats->GetStats(nullptr, &reports); + + // `reports` should contain at least one session report, one track report, + // and one ssrc report. + EXPECT_LE(3u, reports.size()); + const StatsReport* track_report = + FindNthReportByType(reports, StatsReport::kStatsReportTypeTrack, 1); + EXPECT_TRUE(track_report); + + // Get report for the specific `track`. + reports.clear(); + stats->GetStats(video_track_.get(), &reports); + // `reports` should contain at least one session report, one track report, + // and one ssrc report. + EXPECT_LE(3u, reports.size()); + track_report = + FindNthReportByType(reports, StatsReport::kStatsReportTypeTrack, 1); + ASSERT_TRUE(track_report); + EXPECT_EQ(stats->GetTimeNow(), track_report->timestamp()); + + std::string ssrc_id = + ExtractSsrcStatsValue(reports, StatsReport::kStatsValueNameSsrc); + EXPECT_EQ(rtc::ToString(kSsrcOfTrack), ssrc_id); + + std::string track_id = + ExtractSsrcStatsValue(reports, StatsReport::kStatsValueNameTrackId); + EXPECT_EQ(kLocalTrackId, track_id); + + std::string media_type = + ExtractSsrcStatsValue(reports, StatsReport::kStatsValueNameMediaType); + EXPECT_EQ("video", media_type); +} + +// This test verifies that an SSRC object has the identifier of a Transport +// stats object, and that this transport stats object exists in stats. +TEST_P(StatsCollectorTrackTest, TransportObjectLinkedFromSsrcObject) { + constexpr int64_t kBytesSent = 12345678901234LL; + + auto pc = CreatePeerConnection(); + auto stats = CreateStatsCollector(pc.get()); + + VideoSenderInfo video_sender_info; + video_sender_info.add_ssrc(1234); + video_sender_info.payload_bytes_sent = kBytesSent - 12; + video_sender_info.header_and_padding_bytes_sent = 12; + VideoMediaInfo video_info; + video_info.aggregated_senders.push_back(video_sender_info); + + pc->AddVideoChannel("video", "transport", video_info); + + AddOutgoingVideoTrack(pc.get(), stats.get()); + + stats->UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard); + StatsReports reports; + stats->GetStats(nullptr, &reports); + + std::string transport_id = + ExtractStatsValue(StatsReport::kStatsReportTypeSsrc, reports, + StatsReport::kStatsValueNameTransportId); + ASSERT_NE(kNotFound, transport_id); + + // Transport id component ID will always be 1. + // This has assumptions about how the ID is constructed. As is, this is + // OK since this is for testing purposes only, but if we ever need this + // in production, we should add a generic method that does this. + size_t index = transport_id.find('-'); + ASSERT_NE(std::string::npos, index); + std::string content = transport_id.substr(index + 1); + index = content.rfind('-'); + ASSERT_NE(std::string::npos, index); + content = content.substr(0, index); + StatsReport::Id id(StatsReport::NewComponentId(content, 1)); + ASSERT_EQ(transport_id, id->ToString()); + const StatsReport* transport_report = FindReportById(reports, id); + ASSERT_TRUE(transport_report); +} + +// This test verifies that a remote stats object will not be created for +// an outgoing SSRC where remote stats are not returned. +TEST_P(StatsCollectorTrackTest, RemoteSsrcInfoIsAbsent) { + auto pc = CreatePeerConnection(); + auto stats = CreateStatsCollector(pc.get()); + + pc->AddVideoChannel("video", "transport"); + AddOutgoingVideoTrack(pc.get(), stats.get()); + + stats->UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard); + StatsReports reports; + stats->GetStats(nullptr, &reports); + + const StatsReport* remote_report = + FindNthReportByType(reports, StatsReport::kStatsReportTypeRemoteSsrc, 1); + EXPECT_FALSE(remote_report); +} + +// This test verifies that a remote stats object will be created for +// an outgoing SSRC where stats are returned. +TEST_P(StatsCollectorTrackTest, RemoteSsrcInfoIsPresent) { + auto pc = CreatePeerConnection(); + auto stats = CreateStatsCollector(pc.get()); + + SsrcReceiverInfo remote_ssrc_stats; + remote_ssrc_stats.timestamp = 12345.678; + remote_ssrc_stats.ssrc = kSsrcOfTrack; + VideoSenderInfo video_sender_info; + video_sender_info.add_ssrc(kSsrcOfTrack); + video_sender_info.remote_stats.push_back(remote_ssrc_stats); + VideoMediaInfo video_info; + video_info.aggregated_senders.push_back(video_sender_info); + + pc->AddVideoChannel("video", "transport", video_info); + + AddOutgoingVideoTrack(pc.get(), stats.get()); + + stats->UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard); + StatsReports reports; + stats->GetStats(nullptr, &reports); + + const StatsReport* remote_report = + FindNthReportByType(reports, StatsReport::kStatsReportTypeRemoteSsrc, 1); + ASSERT_TRUE(remote_report); + EXPECT_EQ(12345.678, remote_report->timestamp()); +} + +// This test verifies that the empty track report exists in the returned stats +// when StatsCollector::UpdateStats is called with ssrc stats. +TEST_P(StatsCollectorTrackTest, ReportsFromRemoteTrack) { + constexpr int64_t kNumOfPacketsConcealed = 54321; + + auto pc = CreatePeerConnection(); + auto stats = CreateStatsCollector(pc.get()); + + VideoReceiverInfo video_receiver_info; + video_receiver_info.add_ssrc(1234); + video_receiver_info.packets_concealed = kNumOfPacketsConcealed; + VideoMediaInfo video_info; + video_info.receivers.push_back(video_receiver_info); + + pc->AddVideoChannel("video", "transport", video_info); + + AddIncomingVideoTrack(pc.get(), stats.get()); + + stats->UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard); + StatsReports reports; + stats->GetStats(nullptr, &reports); + + // `reports` should contain at least one session report, one track report, + // and one ssrc report. + EXPECT_LE(3u, reports.size()); + const StatsReport* track_report = + FindNthReportByType(reports, StatsReport::kStatsReportTypeTrack, 1); + ASSERT_TRUE(track_report); + EXPECT_EQ(stats->GetTimeNow(), track_report->timestamp()); + + std::string ssrc_id = + ExtractSsrcStatsValue(reports, StatsReport::kStatsValueNameSsrc); + EXPECT_EQ(rtc::ToString(kSsrcOfTrack), ssrc_id); + + std::string track_id = + ExtractSsrcStatsValue(reports, StatsReport::kStatsValueNameTrackId); + EXPECT_EQ(kRemoteTrackId, track_id); +} + +// This test verifies the Ice Candidate report should contain the correct +// information from local/remote candidates. +TEST_F(LegacyStatsCollectorTest, IceCandidateReport) { + const std::string kTransportName = "transport"; + const rtc::AdapterType kNetworkType = rtc::ADAPTER_TYPE_ETHERNET; + constexpr uint32_t kPriority = 1000; + + constexpr int kLocalPort = 2000; + const std::string kLocalIp = "192.168.0.1"; + const rtc::SocketAddress kLocalAddress(kLocalIp, kLocalPort); + + constexpr int kRemotePort = 2001; + const std::string kRemoteIp = "192.168.0.2"; + const rtc::SocketAddress kRemoteAddress(kRemoteIp, kRemotePort); + + auto pc = CreatePeerConnection(); + auto stats = CreateStatsCollector(pc.get()); + + cricket::Candidate local; + EXPECT_GT(local.id().length(), 0u); + local.set_type(cricket::LOCAL_PORT_TYPE); + local.set_protocol(cricket::UDP_PROTOCOL_NAME); + local.set_address(kLocalAddress); + local.set_priority(kPriority); + local.set_network_type(kNetworkType); + + cricket::Candidate remote; + EXPECT_GT(remote.id().length(), 0u); + remote.set_type(cricket::PRFLX_PORT_TYPE); + remote.set_protocol(cricket::UDP_PROTOCOL_NAME); + remote.set_address(kRemoteAddress); + remote.set_priority(kPriority); + remote.set_network_type(kNetworkType); + + ConnectionInfo connection_info; + connection_info.local_candidate = local; + connection_info.remote_candidate = remote; + TransportChannelStats channel_stats; + channel_stats.ice_transport_stats.connection_infos.push_back(connection_info); + + pc->AddVoiceChannel("audio", kTransportName); + pc->SetTransportStats(kTransportName, channel_stats); + + stats->UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard); + StatsReports reports; + stats->GetStats(nullptr, &reports); + + // Verify the local candidate report is populated correctly. + EXPECT_EQ( + "Cand-" + local.id(), + ExtractStatsValue(StatsReport::kStatsReportTypeCandidatePair, reports, + StatsReport::kStatsValueNameLocalCandidateId)); + EXPECT_EQ( + kLocalIp, + ExtractStatsValue(StatsReport::kStatsReportTypeIceLocalCandidate, reports, + StatsReport::kStatsValueNameCandidateIPAddress)); + EXPECT_EQ( + rtc::ToString(kLocalPort), + ExtractStatsValue(StatsReport::kStatsReportTypeIceLocalCandidate, reports, + StatsReport::kStatsValueNameCandidatePortNumber)); + EXPECT_EQ( + cricket::UDP_PROTOCOL_NAME, + ExtractStatsValue(StatsReport::kStatsReportTypeIceLocalCandidate, reports, + StatsReport::kStatsValueNameCandidateTransportType)); + EXPECT_EQ( + rtc::ToString(kPriority), + ExtractStatsValue(StatsReport::kStatsReportTypeIceLocalCandidate, reports, + StatsReport::kStatsValueNameCandidatePriority)); + EXPECT_EQ( + IceCandidateTypeToStatsType(cricket::LOCAL_PORT_TYPE), + ExtractStatsValue(StatsReport::kStatsReportTypeIceLocalCandidate, reports, + StatsReport::kStatsValueNameCandidateType)); + EXPECT_EQ( + AdapterTypeToStatsType(kNetworkType), + ExtractStatsValue(StatsReport::kStatsReportTypeIceLocalCandidate, reports, + StatsReport::kStatsValueNameCandidateNetworkType)); + + // Verify the remote candidate report is populated correctly. + EXPECT_EQ( + "Cand-" + remote.id(), + ExtractStatsValue(StatsReport::kStatsReportTypeCandidatePair, reports, + StatsReport::kStatsValueNameRemoteCandidateId)); + EXPECT_EQ(kRemoteIp, + ExtractStatsValue(StatsReport::kStatsReportTypeIceRemoteCandidate, + reports, + StatsReport::kStatsValueNameCandidateIPAddress)); + EXPECT_EQ(rtc::ToString(kRemotePort), + ExtractStatsValue(StatsReport::kStatsReportTypeIceRemoteCandidate, + reports, + StatsReport::kStatsValueNameCandidatePortNumber)); + EXPECT_EQ(cricket::UDP_PROTOCOL_NAME, + ExtractStatsValue( + StatsReport::kStatsReportTypeIceRemoteCandidate, reports, + StatsReport::kStatsValueNameCandidateTransportType)); + EXPECT_EQ(rtc::ToString(kPriority), + ExtractStatsValue(StatsReport::kStatsReportTypeIceRemoteCandidate, + reports, + StatsReport::kStatsValueNameCandidatePriority)); + EXPECT_EQ( + IceCandidateTypeToStatsType(cricket::PRFLX_PORT_TYPE), + ExtractStatsValue(StatsReport::kStatsReportTypeIceRemoteCandidate, + reports, StatsReport::kStatsValueNameCandidateType)); + EXPECT_EQ(kNotFound, + ExtractStatsValue( + StatsReport::kStatsReportTypeIceRemoteCandidate, reports, + StatsReport::kStatsValueNameCandidateNetworkType)); +} + +// This test verifies that all chained certificates are correctly +// reported +TEST_F(LegacyStatsCollectorTest, 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"; + rtc::FakeSSLIdentity local_identity(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"; + rtc::FakeSSLIdentity remote_identity(DersToPems(remote_ders)); + + TestCertificateReports(local_identity, local_ders, remote_identity, + remote_ders); +} + +// This test verifies that all certificates without chains are correctly +// reported. +TEST_F(LegacyStatsCollectorTest, ChainlessCertificateReportsCreated) { + // Build local certificate. + std::string local_der = "This is the local der."; + rtc::FakeSSLIdentity local_identity(DerToPem(local_der)); + + // Build remote certificate. + std::string remote_der = "This is somebody else's der."; + rtc::FakeSSLIdentity remote_identity(DerToPem(remote_der)); + + TestCertificateReports(local_identity, std::vector<std::string>(1, local_der), + remote_identity, + std::vector<std::string>(1, remote_der)); +} + +// This test verifies that the stats are generated correctly when no +// transport is present. +TEST_F(LegacyStatsCollectorTest, NoTransport) { + auto pc = CreatePeerConnection(); + auto stats = CreateStatsCollector(pc.get()); + + // This will cause the fake PeerConnection to generate a TransportStats entry + // but with only a single dummy TransportChannelStats. + pc->AddVoiceChannel("audio", "transport"); + + stats->UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard); + StatsReports reports; + stats->GetStats(nullptr, &reports); + + // Check that the local certificate is absent. + std::string local_certificate_id = + ExtractStatsValue(StatsReport::kStatsReportTypeComponent, reports, + StatsReport::kStatsValueNameLocalCertificateId); + ASSERT_EQ(kNotFound, local_certificate_id); + + // Check that the remote certificate is absent. + std::string remote_certificate_id = + ExtractStatsValue(StatsReport::kStatsReportTypeComponent, reports, + StatsReport::kStatsValueNameRemoteCertificateId); + ASSERT_EQ(kNotFound, remote_certificate_id); + + // Check that the negotiated ciphers are absent. + std::string dtls_cipher_suite = + ExtractStatsValue(StatsReport::kStatsReportTypeComponent, reports, + StatsReport::kStatsValueNameDtlsCipher); + ASSERT_EQ(kNotFound, dtls_cipher_suite); + std::string srtp_crypto_suite = + ExtractStatsValue(StatsReport::kStatsReportTypeComponent, reports, + StatsReport::kStatsValueNameSrtpCipher); + ASSERT_EQ(kNotFound, srtp_crypto_suite); +} + +// This test verifies that a remote certificate with an unsupported digest +// algorithm is correctly ignored. +TEST_F(LegacyStatsCollectorTest, UnsupportedDigestIgnored) { + // Build a local certificate. + std::string local_der = "This is the local der."; + rtc::FakeSSLIdentity local_identity(DerToPem(local_der)); + + // Build a remote certificate with an unsupported digest algorithm. + std::string remote_der = "This is somebody else's der."; + rtc::FakeSSLCertificate remote_cert(DerToPem(remote_der)); + remote_cert.set_digest_algorithm("foobar"); + rtc::FakeSSLIdentity remote_identity(remote_cert); + + TestCertificateReports(local_identity, std::vector<std::string>(1, local_der), + remote_identity, std::vector<std::string>()); +} + +// This test verifies that the audio/video related stats which are -1 initially +// will be filtered out. +TEST_P(StatsCollectorTrackTest, FilterOutNegativeInitialValues) { + // This test uses streams, but only works for the stream case. + if (!GetParam()) { + return; + } + + auto pc = CreatePeerConnection(); + auto stats = CreateStatsCollector(pc.get()); + + // Create a local stream with a local audio track and adds it to the stats. + stream_ = MediaStream::Create("streamid"); + auto local_track = + rtc::make_ref_counted<FakeAudioTrackWithInitValue>(kLocalTrackId); + stream_->AddTrack(rtc::scoped_refptr<AudioTrackInterface>(local_track.get())); + pc->AddSender(CreateMockSender(local_track, kSsrcOfTrack)); + if (GetParam()) { + stats->AddStream(stream_.get()); + } + stats->AddLocalAudioTrack(local_track.get(), kSsrcOfTrack); + + // Create a remote stream with a remote audio track and adds it to the stats. + rtc::scoped_refptr<MediaStream> remote_stream( + MediaStream::Create("remotestreamid")); + rtc::scoped_refptr<AudioTrackInterface> remote_track = + rtc::make_ref_counted<FakeAudioTrackWithInitValue>(kRemoteTrackId); + remote_stream->AddTrack(remote_track); + pc->AddReceiver(CreateMockReceiver(remote_track, kSsrcOfTrack)); + if (GetParam()) { + stats->AddStream(remote_stream.get()); + } + + VoiceSenderInfo voice_sender_info; + voice_sender_info.add_ssrc(kSsrcOfTrack); + // These values are set to -1 initially in audio_send_stream. + // The voice_sender_info will read the values from audio_send_stream. + voice_sender_info.rtt_ms = -1; + voice_sender_info.packets_lost = -1; + voice_sender_info.jitter_ms = -1; + + // Some of the contents in `voice_sender_info` needs to be updated from the + // `audio_track_`. + UpdateVoiceSenderInfoFromAudioTrack(local_track.get(), &voice_sender_info, + true); + + VoiceReceiverInfo voice_receiver_info; + voice_receiver_info.add_ssrc(kSsrcOfTrack); + voice_receiver_info.capture_start_ntp_time_ms = -1; + voice_receiver_info.audio_level = -1; + + // Constructs an ssrc stats update. + VoiceMediaInfo voice_info; + voice_info.senders.push_back(voice_sender_info); + voice_info.receivers.push_back(voice_receiver_info); + + auto* voice_media_channel = pc->AddVoiceChannel("voice", "transport"); + voice_media_channel->SetStats(voice_info); + + stats->UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard); + + // Get stats for the local track. + StatsReports reports; + stats->GetStats(local_track.get(), &reports); + const StatsReport* report = + FindNthReportByType(reports, StatsReport::kStatsReportTypeSsrc, 1); + ASSERT_TRUE(report); + // The -1 will not be added to the stats report. + std::string value_in_report; + EXPECT_FALSE( + GetValue(report, StatsReport::kStatsValueNameRtt, &value_in_report)); + EXPECT_FALSE(GetValue(report, StatsReport::kStatsValueNamePacketsLost, + &value_in_report)); + EXPECT_FALSE(GetValue(report, StatsReport::kStatsValueNameJitterReceived, + &value_in_report)); + EXPECT_FALSE(GetValue(report, StatsReport::kStatsValueNameEchoDelayMedian, + &value_in_report)); + EXPECT_FALSE(GetValue(report, StatsReport::kStatsValueNameEchoDelayStdDev, + &value_in_report)); + + // Get stats for the remote track. + reports.clear(); + stats->GetStats(remote_track.get(), &reports); + report = FindNthReportByType(reports, StatsReport::kStatsReportTypeSsrc, 1); + ASSERT_TRUE(report); + EXPECT_FALSE(GetValue(report, + StatsReport::kStatsValueNameCaptureStartNtpTimeMs, + &value_in_report)); + EXPECT_FALSE(GetValue(report, StatsReport::kStatsValueNameAudioInputLevel, + &value_in_report)); +} + +// This test verifies that a local stats object can get statistics via +// AudioTrackInterface::GetStats() method. +TEST_P(StatsCollectorTrackTest, GetStatsFromLocalAudioTrack) { + auto pc = CreatePeerConnection(); + auto stats = CreateStatsCollector(pc.get()); + + AddOutgoingAudioTrack(pc.get(), stats.get()); + stats->AddLocalAudioTrack(audio_track_.get(), kSsrcOfTrack); + + VoiceSenderInfo voice_sender_info; + InitVoiceSenderInfo(&voice_sender_info); + UpdateVoiceSenderInfoFromAudioTrack(audio_track_.get(), &voice_sender_info, + false); + VoiceMediaInfo voice_info; + voice_info.senders.push_back(voice_sender_info); + + auto* voice_media_channel = pc->AddVoiceChannel("audio", "transport"); + voice_media_channel->SetStats(voice_info); + + StatsReports reports; // returned values. + VerifyAudioTrackStats(audio_track_.get(), stats.get(), voice_info, &reports); + + // Verify that there is no remote report for the local audio track because + // we did not set it up. + const StatsReport* remote_report = + FindNthReportByType(reports, StatsReport::kStatsReportTypeRemoteSsrc, 1); + EXPECT_TRUE(remote_report == NULL); +} + +// This test verifies that audio receive streams populate stats reports +// correctly. +TEST_P(StatsCollectorTrackTest, GetStatsFromRemoteStream) { + auto pc = CreatePeerConnection(); + auto stats = CreateStatsCollector(pc.get()); + + AddIncomingAudioTrack(pc.get(), stats.get()); + + VoiceReceiverInfo voice_receiver_info; + InitVoiceReceiverInfo(&voice_receiver_info); + voice_receiver_info.codec_name = "fake_codec"; + VoiceMediaInfo voice_info; + voice_info.receivers.push_back(voice_receiver_info); + + auto* voice_media_channel = pc->AddVoiceChannel("audio", "transport"); + voice_media_channel->SetStats(voice_info); + + StatsReports reports; // returned values. + VerifyAudioTrackStats(audio_track_.get(), stats.get(), voice_info, &reports); +} + +// This test verifies that a local stats object won't update its statistics +// after a RemoveLocalAudioTrack() call. +TEST_P(StatsCollectorTrackTest, GetStatsAfterRemoveAudioStream) { + auto pc = CreatePeerConnection(); + auto stats = CreateStatsCollector(pc.get()); + + AddOutgoingAudioTrack(pc.get(), stats.get()); + stats->AddLocalAudioTrack(audio_track_.get(), kSsrcOfTrack); + + VoiceSenderInfo voice_sender_info; + InitVoiceSenderInfo(&voice_sender_info); + VoiceMediaInfo voice_info; + voice_info.senders.push_back(voice_sender_info); + + auto* voice_media_channel = pc->AddVoiceChannel("audio", "transport"); + voice_media_channel->SetStats(voice_info); + + stats->RemoveLocalAudioTrack(audio_track_.get(), kSsrcOfTrack); + + stats->UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard); + StatsReports reports; + stats->GetStats(nullptr, &reports); + + // The report will exist since we don't remove them in RemoveStream(). + const StatsReport* report = + FindNthReportByType(reports, StatsReport::kStatsReportTypeSsrc, 1); + ASSERT_TRUE(report); + EXPECT_EQ(stats->GetTimeNow(), report->timestamp()); + std::string track_id = + ExtractSsrcStatsValue(reports, StatsReport::kStatsValueNameTrackId); + EXPECT_EQ(kLocalTrackId, track_id); + std::string ssrc_id = + ExtractSsrcStatsValue(reports, StatsReport::kStatsValueNameSsrc); + EXPECT_EQ(rtc::ToString(kSsrcOfTrack), ssrc_id); + + // Verifies the values in the track report, no value will be changed by the + // AudioTrackInterface::GetSignalValue() and + // AudioProcessorInterface::GetStats(); + VerifyVoiceSenderInfoReport(report, voice_sender_info); +} + +// This test verifies that when ongoing and incoming audio tracks are using +// the same ssrc, they populate stats reports correctly. +TEST_P(StatsCollectorTrackTest, LocalAndRemoteTracksWithSameSsrc) { + auto pc = CreatePeerConnection(); + auto stats = CreateStatsCollector(pc.get()); + + // Create a local stream with a local audio track and adds it to the stats. + AddOutgoingAudioTrack(pc.get(), stats.get()); + stats->AddLocalAudioTrack(audio_track_.get(), kSsrcOfTrack); + + // Create a remote stream with a remote audio track and adds it to the stats. + rtc::scoped_refptr<MediaStream> remote_stream( + MediaStream::Create("remotestreamid")); + rtc::scoped_refptr<AudioTrackInterface> remote_track = + rtc::make_ref_counted<FakeAudioTrack>(kRemoteTrackId); + pc->AddReceiver(CreateMockReceiver(remote_track, kSsrcOfTrack)); + remote_stream->AddTrack(remote_track); + stats->AddStream(remote_stream.get()); + + VoiceSenderInfo voice_sender_info; + InitVoiceSenderInfo(&voice_sender_info); + // Some of the contents in `voice_sender_info` needs to be updated from the + // `audio_track_`. + UpdateVoiceSenderInfoFromAudioTrack(audio_track_.get(), &voice_sender_info, + true); + + VoiceReceiverInfo voice_receiver_info; + InitVoiceReceiverInfo(&voice_receiver_info); + + // Constructs an ssrc stats update. + VoiceMediaInfo voice_info; + voice_info.senders.push_back(voice_sender_info); + voice_info.receivers.push_back(voice_receiver_info); + + // Instruct the session to return stats containing the transport channel. + auto* voice_media_channel = pc->AddVoiceChannel("audio", "transport"); + voice_media_channel->SetStats(voice_info); + + stats->UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard); + + // Get stats for the local track. + StatsReports reports; // returned values. + stats->GetStats(audio_track_.get(), &reports); + + const StatsReport* track_report = + FindNthReportByType(reports, StatsReport::kStatsReportTypeSsrc, 1); + ASSERT_TRUE(track_report); + EXPECT_EQ(stats->GetTimeNow(), track_report->timestamp()); + std::string track_id = + ExtractSsrcStatsValue(reports, StatsReport::kStatsValueNameTrackId); + EXPECT_EQ(kLocalTrackId, track_id); + VerifyVoiceSenderInfoReport(track_report, voice_sender_info); + + // Get stats for the remote track. + reports.clear(); + stats->GetStats(remote_track.get(), &reports); + track_report = + FindNthReportByType(reports, StatsReport::kStatsReportTypeSsrc, 1); + ASSERT_TRUE(track_report); + EXPECT_EQ(stats->GetTimeNow(), track_report->timestamp()); + track_id = + ExtractSsrcStatsValue(reports, StatsReport::kStatsValueNameTrackId); + EXPECT_EQ(kRemoteTrackId, track_id); + VerifyVoiceReceiverInfoReport(track_report, voice_receiver_info); +} + +// This test verifies that when two outgoing audio tracks are using the same +// ssrc at different times, they populate stats reports correctly. +// TODO(xians): Figure out if it is possible to encapsulate the setup and +// avoid duplication of code in test cases. +TEST_P(StatsCollectorTrackTest, TwoLocalTracksWithSameSsrc) { + // This test only makes sense when we're using streams. + if (!GetParam()) { + return; + } + + auto pc = CreatePeerConnection(); + auto stats = CreateStatsCollector(pc.get()); + + // Create a local stream with a local audio track and adds it to the stats. + auto sender = AddOutgoingAudioTrack(pc.get(), stats.get()); + stats->AddLocalAudioTrack(audio_track_.get(), kSsrcOfTrack); + + VoiceSenderInfo voice_sender_info; + InitVoiceSenderInfo(&voice_sender_info); + UpdateVoiceSenderInfoFromAudioTrack(audio_track_.get(), &voice_sender_info, + false); + voice_sender_info.add_ssrc(kSsrcOfTrack); + VoiceMediaInfo voice_info; + voice_info.senders.push_back(voice_sender_info); + + auto* voice_media_channel = pc->AddVoiceChannel("voice", "transport"); + voice_media_channel->SetStats(voice_info); + + StatsReports reports; // returned values. + VerifyAudioTrackStats(audio_track_.get(), stats.get(), voice_info, &reports); + + // Remove the previous audio track from the stream. + stream_->RemoveTrack(audio_track()); + stats->RemoveLocalAudioTrack(audio_track_.get(), kSsrcOfTrack); + pc->RemoveSender(sender); + + // Create a new audio track and adds it to the stream and stats. + static const std::string kNewTrackId = "new_track_id"; + auto new_audio_track = rtc::make_ref_counted<FakeAudioTrack>(kNewTrackId); + pc->AddSender(CreateMockSender(new_audio_track, kSsrcOfTrack)); + stream_->AddTrack( + rtc::scoped_refptr<AudioTrackInterface>(new_audio_track.get())); + + stats->AddLocalAudioTrack(new_audio_track.get(), kSsrcOfTrack); + stats->InvalidateCache(); + + VoiceSenderInfo new_voice_sender_info; + InitVoiceSenderInfo(&new_voice_sender_info); + UpdateVoiceSenderInfoFromAudioTrack(new_audio_track.get(), + &new_voice_sender_info, false); + VoiceMediaInfo new_voice_info; + new_voice_info.senders.push_back(new_voice_sender_info); + voice_media_channel->SetStats(new_voice_info); + + reports.clear(); + VerifyAudioTrackStats(new_audio_track.get(), stats.get(), new_voice_info, + &reports); +} + +// Test that if there are two local senders with the same track then two SSRC +// reports will be created, one for each sender, with the same track ID and one +// track report will be created for the shared track. +TEST_P(StatsCollectorTrackTest, TwoLocalSendersWithSameTrack) { + constexpr uint32_t kFirstSsrc = 22; + constexpr uint32_t kSecondSsrc = 33; + + auto pc = CreatePeerConnection(); + auto stats = CreateStatsCollector(pc.get()); + + auto local_track = + rtc::make_ref_counted<FakeAudioTrackWithInitValue>(kLocalTrackId); + pc->AddSender(CreateMockSender(local_track, kFirstSsrc)); + stats->AddLocalAudioTrack(local_track.get(), kFirstSsrc); + pc->AddSender(CreateMockSender(local_track, kSecondSsrc)); + stats->AddLocalAudioTrack(local_track.get(), kSecondSsrc); + + VoiceSenderInfo first_sender_info; + InitVoiceSenderInfo(&first_sender_info, kFirstSsrc); + UpdateVoiceSenderInfoFromAudioTrack(local_track.get(), &first_sender_info, + false); + + VoiceSenderInfo second_sender_info; + InitVoiceSenderInfo(&second_sender_info, kSecondSsrc); + UpdateVoiceSenderInfoFromAudioTrack(local_track.get(), &second_sender_info, + false); + + VoiceMediaInfo voice_info; + voice_info.senders.push_back(first_sender_info); + voice_info.senders.push_back(second_sender_info); + + auto* voice_media_channel = pc->AddVoiceChannel("voice", "transport"); + voice_media_channel->SetStats(voice_info); + + stats->UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard); + + StatsReports reports; + stats->GetStats(local_track.get(), &reports); + + // Both SSRC reports have the same track ID. + EXPECT_EQ(kLocalTrackId, GetValueInNthReportByType( + reports, StatsReport::kStatsReportTypeSsrc, + StatsReport::kStatsValueNameTrackId, 1)); + EXPECT_EQ(kLocalTrackId, GetValueInNthReportByType( + reports, StatsReport::kStatsReportTypeSsrc, + StatsReport::kStatsValueNameTrackId, 2)); + + // The SSRC in each SSRC report is different and correspond to the sender + // SSRC. + std::vector<absl::optional<std::string>> ssrcs = { + GetValueInNthReportByType(reports, StatsReport::kStatsReportTypeSsrc, + StatsReport::kStatsValueNameSsrc, 1), + GetValueInNthReportByType(reports, StatsReport::kStatsReportTypeSsrc, + StatsReport::kStatsValueNameSsrc, 2)}; + EXPECT_THAT(ssrcs, UnorderedElementsAre(rtc::ToString(kFirstSsrc), + rtc::ToString(kSecondSsrc))); + + // There is one track report with the same track ID as the SSRC reports. + EXPECT_EQ( + 1u, GetReportsByType(reports, StatsReport::kStatsReportTypeTrack).size()); + EXPECT_EQ(kLocalTrackId, GetValueInNthReportByType( + reports, StatsReport::kStatsReportTypeTrack, + StatsReport::kStatsValueNameTrackId, 1)); +} + +// This test verifies that stats are correctly set in video send ssrc stats. +TEST_P(StatsCollectorTrackTest, VerifyVideoSendSsrcStats) { + auto pc = CreatePeerConnection(); + auto stats = CreateStatsCollector(pc.get()); + + AddOutgoingVideoTrack(pc.get(), stats.get()); + + VideoSenderInfo video_sender_info; + video_sender_info.add_ssrc(1234); + video_sender_info.frames_encoded = 10; + video_sender_info.qp_sum = 11; + VideoMediaInfo video_info; + video_info.aggregated_senders.push_back(video_sender_info); + + pc->AddVideoChannel("video", "transport", video_info); + + stats->UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard); + StatsReports reports; + stats->GetStats(nullptr, &reports); + + EXPECT_EQ(rtc::ToString(video_sender_info.frames_encoded), + ExtractSsrcStatsValue(reports, + StatsReport::kStatsValueNameFramesEncoded)); + EXPECT_EQ(rtc::ToString(*video_sender_info.qp_sum), + ExtractSsrcStatsValue(reports, StatsReport::kStatsValueNameQpSum)); +} + +// This test verifies that stats are correctly set in video receive ssrc stats. +TEST_P(StatsCollectorTrackTest, VerifyVideoReceiveSsrcStatsNew) { + auto pc = CreatePeerConnection(); + auto stats = CreateStatsCollector(pc.get()); + + AddIncomingVideoTrack(pc.get(), stats.get()); + + VideoReceiverInfo video_receiver_info; + video_receiver_info.add_ssrc(1234); + video_receiver_info.frames_decoded = 10; + video_receiver_info.qp_sum = 11; + VideoMediaInfo video_info; + video_info.receivers.push_back(video_receiver_info); + + pc->AddVideoChannel("video", "transport", video_info); + + stats->UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard); + StatsReports reports; + stats->GetStats(nullptr, &reports); + + EXPECT_EQ(rtc::ToString(video_receiver_info.frames_decoded), + ExtractSsrcStatsValue(reports, + StatsReport::kStatsValueNameFramesDecoded)); + EXPECT_EQ(rtc::ToString(*video_receiver_info.qp_sum), + ExtractSsrcStatsValue(reports, StatsReport::kStatsValueNameQpSum)); +} + +INSTANTIATE_TEST_SUITE_P(HasStream, StatsCollectorTrackTest, ::testing::Bool()); + +} // namespace webrtc |