/* * Copyright 2026 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. */ #ifndef PC_TEST_LEGACY_STATS_COLLECTOR_TEST_HELPERS_H_ #define PC_TEST_LEGACY_STATS_COLLECTOR_TEST_HELPERS_H_ #include #include #include #include #include #include "absl/strings/str_cat.h" #include "api/legacy_stats_types.h" #include "api/make_ref_counted.h" #include "api/media_stream_interface.h" #include "api/peer_connection_interface.h" #include "api/scoped_refptr.h" #include "api/units/time_delta.h" #include "api/units/timestamp.h" #include "media/base/media_channel.h" #include "pc/legacy_stats_collector.h" #include "pc/peer_connection_internal.h" #include "pc/test/fake_peer_connection_for_stats.h" #include "pc/test/mock_rtp_receiver_internal.h" #include "pc/test/mock_rtp_sender_internal.h" #include "pc/transport_stats.h" #include "rtc_base/fake_ssl_identity.h" #include "rtc_base/rtc_certificate.h" #include "rtc_base/ssl_stream_adapter.h" #include "rtc_base/system/plan_b_only.h" #include "system_wrappers/include/clock.h" #include "system_wrappers/include/ntp_time.h" #include "test/create_test_environment.h" #include "test/gtest.h" #include "test/run_loop.h" namespace webrtc { // Error return values inline constexpr char kNotFound[] = "NOT FOUND"; // Constant names for track identification. inline constexpr char kLocalTrackId[] = "local_track_id"; inline constexpr char kRemoteTrackId[] = "remote_track_id"; inline constexpr uint32_t kSsrcOfTrack = 1234; // Returns a FakeAudioTrack with the given ID and configuration. scoped_refptr CreateFakeAudioTrack(const std::string& id, bool return_stats); bool GetValue(const StatsReport* report, StatsReport::StatsValueName name, std::string* value); std::string ExtractStatsValue(const StatsReport::StatsType& type, const StatsReports& reports, StatsReport::StatsValueName name); StatsReport::Id TypedIdFromIdString(StatsReport::StatsType type, const std::string& value); StatsReport::Id IdFromCertIdString(const std::string& 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); // 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. std::optional GetValueInNthReportByType( const StatsReports& reports, StatsReport::StatsType type, StatsReport::StatsValueName name, int n); std::vector GetReportsByType(const StatsReports& reports, StatsReport::StatsType type); const StatsReport* FindReportById(const StatsReports& reports, const StatsReport::Id& id); std::string ExtractSsrcStatsValue(const StatsReports& reports, StatsReport::StatsValueName name); std::string ExtractBweStatsValue(const StatsReports& reports, StatsReport::StatsValueName name); std::string DerToPem(const std::string& der); std::vector DersToPems(const std::vector& ders); void CheckCertChainReports(const StatsReports& reports, const std::vector& ders, const StatsReport::Id& start_id); void VerifyVoiceReceiverInfoReport(const StatsReport* report, const VoiceReceiverInfo& info); void VerifyVoiceSenderInfoReport(const StatsReport* report, const VoiceSenderInfo& sinfo); // Helper methods to avoid duplication of code. void InitVoiceSenderInfo(VoiceSenderInfo* voice_sender_info, uint32_t ssrc = kSsrcOfTrack); void UpdateVoiceSenderInfoFromAudioTrack(AudioTrackInterface* audio_track, VoiceSenderInfo* voice_sender_info, bool has_remote_tracks); void InitVoiceReceiverInfo(VoiceReceiverInfo* voice_receiver_info); class LegacyStatsCollectorTest : public ::testing::Test { protected: scoped_refptr CreatePeerConnection() { return make_ref_counted( CreateTestEnvironment({.time = &clock_})); } std::unique_ptr CreateStatsCollector( PeerConnectionInternal* pc) { return std::make_unique( pc, clock_, [this]() { return GetUtcTimeNowMs(); }); } void VerifyAudioTrackStats(AudioTrackInterface* audio_track, LegacyStatsCollector* 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(GetUtcTimeNowMs(), 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(absl::StrCat(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(GetUtcTimeNowMs(), 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(absl::StrCat(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 FakeSSLIdentity& local_identity, const std::vector& local_ders, const FakeSSLIdentity& remote_identity, const std::vector& remote_ders) { const std::string kTransportName = "audio"; // Same as the mid. auto pc = CreatePeerConnection(); auto stats = CreateStatsCollector(pc.get()); RTC_ALLOW_PLAN_B_DEPRECATION_BEGIN(); pc->AddVoiceChannel(/*mid=*/"audio", kTransportName); RTC_ALLOW_PLAN_B_DEPRECATION_END(); // Fake stats to process. TransportChannelStats channel_stats; channel_stats.component = 1; channel_stats.srtp_crypto_suite = kSrtpAes128CmSha1_80; channel_stats.tls_cipher_suite_name = "cipher_suite_for_test"; pc->SetTransportStats(kTransportName, channel_stats); // Fake certificate to report. scoped_refptr local_certificate( 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.empty()) { 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.empty()) { 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(dtls_cipher_suite, "cipher_suite_for_test"); std::string srtp_crypto_suite = ExtractStatsValue(StatsReport::kStatsReportTypeComponent, reports, StatsReport::kStatsValueNameSrtpCipher); EXPECT_EQ(SrtpCryptoSuiteToName(kSrtpAes128CmSha1_80), srtp_crypto_suite); } int64_t GetUtcTimeNowMs() { return Clock::NtpToUtc(clock_.CurrentNtpTime()).ms(); } protected: // Slight enhancement to SimulatedClock. // SimulatedClock uses a simplified model where its internal monotonic time is // assumed to be the Wall Clock time (relative to the Unix Epoch). It maps // CurrentTime() directly to NTP time by just adding the 1900-1970 offset. // Therefore, converting Ntp back to Utc just cancels the offset, ending up // returning the same value as CurrentTime(). This utility class makes sure // that these values are not the same so that we can accurately test when the // UTC time is being used. class OffsetSimulatedClock : public SimulatedClock { public: OffsetSimulatedClock(Timestamp start_time, TimeDelta utc_offset) : SimulatedClock(start_time), offset_(utc_offset) {} // Shifts the NTP calculation by the offset, simulating a Wall Clock // that is ahead of the system monotonic clock. NtpTime ConvertTimestampToNtpTime(Timestamp timestamp) override { return SimulatedClock::ConvertTimestampToNtpTime(timestamp + offset_); } private: const TimeDelta offset_; }; OffsetSimulatedClock clock_{ Timestamp::Millis(1337), TimeDelta::Seconds(int64_t{365} * 50 * 86400)}; // 50 years offset. private: test::RunLoop main_thread_; }; scoped_refptr CreateMockSender( scoped_refptr track, uint32_t ssrc); scoped_refptr CreateMockReceiver( scoped_refptr track, uint32_t ssrc); } // namespace webrtc #endif // PC_TEST_LEGACY_STATS_COLLECTOR_TEST_HELPERS_H_