| /* | |
| * Copyright 2017 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 <array> | |
| #include <memory> | |
| #include <optional> | |
| #include <set> | |
| #include <string> | |
| #include <string_view> | |
| #include <utility> | |
| #include <vector> | |
| #include "absl/strings/str_format.h" | |
| #include "absl/strings/string_view.h" | |
| #include "api/create_modular_peer_connection_factory.h" | |
| #include "api/jsep.h" | |
| #include "api/peer_connection_interface.h" | |
| #include "api/rtc_error.h" | |
| #include "api/scoped_refptr.h" | |
| #include "api/test/mock_async_dns_resolver.h" | |
| #include "api/test/rtc_error_matchers.h" | |
| #include "api/units/time_delta.h" | |
| #include "api/webrtc_sdp.h" | |
| #include "pc/peer_connection.h" | |
| #include "pc/peer_connection_wrapper.h" | |
| #include "pc/test/enable_fake_media.h" | |
| #include "pc/test/mock_peer_connection_observers.h" | |
| #include "pc/usage_pattern.h" | |
| #include "rtc_base/checks.h" | |
| #include "rtc_base/fake_mdns_responder.h" | |
| #include "rtc_base/fake_network.h" | |
| #include "rtc_base/ip_address.h" | |
| #include "rtc_base/network.h" | |
| #include "rtc_base/socket_address.h" | |
| #include "rtc_base/thread.h" | |
| #include "rtc_base/virtual_socket_server.h" | |
| #include "system_wrappers/include/metrics.h" | |
| #include "test/gmock.h" | |
| #include "test/gtest.h" | |
| #include "test/run_loop.h" | |
| #include "test/wait_until.h" | |
| namespace webrtc { | |
| namespace { | |
| using ::testing::NiceMock; | |
| using RTCConfiguration = PeerConnectionInterface::RTCConfiguration; | |
| using RTCOfferAnswerOptions = PeerConnectionInterface::RTCOfferAnswerOptions; | |
| class PeerConnectionWrapperForUsageHistogramTest; | |
| typedef PeerConnectionWrapperForUsageHistogramTest* RawWrapperPtr; | |
| constexpr const char kBasicRemoteDescription[] = R"(v=0 | |
| o=- 0 0 IN IP4 127.0.0.1 | |
| s=- | |
| t=0 0 | |
| m=audio 9 UDP/TLS/RTP/SAVPF 101 | |
| c=IN IP4 0.0.0.0 | |
| a=ice-ufrag:fooUfrag | |
| a=ice-pwd:someRemotePasswordGeneratedString | |
| a=fingerprint:sha-256 0A:B1:C2:D3:E4:F5:06:07:08:09:0A:0B:0C:0D:0E:0F:10:11:12:13:14:15:16:17:18:19:1A:1B:1C:1D:1E:1F | |
| a=candidate:1 1 UDP 2130706431 %s 57892 typ host generation 0 | |
| a=setup:active | |
| a=mid:0 | |
| a=sendrecv | |
| a=rtcp-mux | |
| a=rtpmap:101 fake_audio_codec/8000 | |
| )"; | |
| constexpr char kUsagePatternMetric[] = "WebRTC.PeerConnection.UsagePattern"; | |
| constexpr TimeDelta kDefaultTimeout = TimeDelta::Millis(10000); | |
| const std::array kLocalAddrs{SocketAddress("1.1.1.1", 0), | |
| SocketAddress("2.2.2.2", 0)}; | |
| const SocketAddress kPrivateLocalAddress("10.1.1.1", 0); | |
| const SocketAddress kPrivateIpv6LocalAddress("fd12:3456:789a:1::1", 0); | |
| int MakeUsageFingerprint(std::set<UsageEvent> events) { | |
| int signature = 0; | |
| for (const auto it : events) { | |
| signature |= static_cast<int>(it); | |
| } | |
| return signature; | |
| } | |
| class ObserverForUsageHistogramTest : public MockPeerConnectionObserver { | |
| public: | |
| void OnIceCandidate(const IceCandidate* candidate) override; | |
| void OnInterestingUsage(int usage_pattern) override { | |
| interesting_usage_detected_ = usage_pattern; | |
| } | |
| void PrepareToExchangeCandidates(RawWrapperPtr other) { | |
| candidate_target_ = other; | |
| } | |
| bool HaveDataChannel() { return last_datachannel_ != nullptr; } | |
| std::optional<int> interesting_usage_detected() { | |
| return interesting_usage_detected_; | |
| } | |
| void ClearInterestingUsageDetector() { | |
| interesting_usage_detected_ = std::optional<int>(); | |
| } | |
| bool candidate_gathered() const { return candidate_gathered_; } | |
| private: | |
| std::optional<int> interesting_usage_detected_; | |
| bool candidate_gathered_ = false; | |
| RawWrapperPtr candidate_target_; // Note: Not thread-safe against deletions. | |
| }; | |
| class PeerConnectionWrapperForUsageHistogramTest | |
| : public PeerConnectionWrapper { | |
| public: | |
| using PeerConnectionWrapper::PeerConnectionWrapper; | |
| // Override with different return type | |
| ObserverForUsageHistogramTest* observer() { | |
| return static_cast<ObserverForUsageHistogramTest*>( | |
| PeerConnectionWrapper::observer()); | |
| } | |
| void PrepareToExchangeCandidates( | |
| PeerConnectionWrapperForUsageHistogramTest* other) { | |
| observer()->PrepareToExchangeCandidates(other); | |
| other->observer()->PrepareToExchangeCandidates(this); | |
| } | |
| bool IsConnected() { | |
| return pc()->ice_connection_state() == | |
| PeerConnectionInterface::kIceConnectionConnected || | |
| pc()->ice_connection_state() == | |
| PeerConnectionInterface::kIceConnectionCompleted; | |
| } | |
| bool HaveDataChannel() { | |
| return static_cast<ObserverForUsageHistogramTest*>(observer()) | |
| ->HaveDataChannel(); | |
| } | |
| void BufferIceCandidate(const IceCandidate* candidate) { | |
| std::string sdp = candidate->ToString(); | |
| std::unique_ptr<IceCandidate> candidate_copy(CreateIceCandidate( | |
| candidate->sdp_mid(), candidate->sdp_mline_index(), sdp, nullptr)); | |
| buffered_candidates_.push_back(std::move(candidate_copy)); | |
| } | |
| void AddBufferedIceCandidates() { | |
| for (const auto& candidate : buffered_candidates_) { | |
| EXPECT_TRUE(pc()->AddIceCandidate(candidate.get())); | |
| } | |
| buffered_candidates_.clear(); | |
| } | |
| // This method performs the following actions in sequence: | |
| // 1. Exchange Offer and Answer. | |
| // 2. Exchange ICE candidates after both caller and callee complete | |
| // gathering. | |
| // 3. Wait for ICE to connect. | |
| // | |
| // This guarantees a deterministic sequence of events and also rules out the | |
| // occurrence of prflx candidates if the offer/answer signaling and the | |
| // candidate trickling race in order. In case prflx candidates need to be | |
| // simulated, see the approach used by tests below for that. | |
| bool ConnectTo(PeerConnectionWrapperForUsageHistogramTest* callee) { | |
| PrepareToExchangeCandidates(callee); | |
| if (!ExchangeOfferAnswerWith(callee)) { | |
| return false; | |
| } | |
| // Wait until the gathering completes before we signal the candidate. | |
| EXPECT_TRUE(WaitUntil([&] { return observer()->ice_gathering_complete_; }, | |
| {.timeout = kDefaultTimeout})); | |
| EXPECT_TRUE( | |
| WaitUntil([&] { return callee->observer()->ice_gathering_complete_; }, | |
| {.timeout = kDefaultTimeout})); | |
| AddBufferedIceCandidates(); | |
| callee->AddBufferedIceCandidates(); | |
| EXPECT_TRUE( | |
| WaitUntil([&] { return IsConnected(); }, {.timeout = kDefaultTimeout})); | |
| EXPECT_TRUE(WaitUntil([&] { return callee->IsConnected(); }, | |
| {.timeout = kDefaultTimeout})); | |
| return IsConnected() && callee->IsConnected(); | |
| } | |
| bool GenerateOfferAndCollectCandidates() { | |
| std::unique_ptr<SessionDescriptionInterface> offer = | |
| CreateOffer(RTCOfferAnswerOptions()); | |
| if (!offer) { | |
| return false; | |
| } | |
| bool set_local_offer = SetLocalDescription(offer->Clone()); | |
| EXPECT_TRUE(set_local_offer); | |
| if (!set_local_offer) { | |
| return false; | |
| } | |
| EXPECT_THAT(WaitUntil([&] { return observer()->ice_gathering_complete_; }, | |
| ::testing::IsTrue()), | |
| IsRtcOk()); | |
| return true; | |
| } | |
| PeerConnectionInterface::IceGatheringState ice_gathering_state() { | |
| return pc()->ice_gathering_state(); | |
| } | |
| private: | |
| // Candidates that have been sent but not yet configured | |
| std::vector<std::unique_ptr<IceCandidate>> buffered_candidates_; | |
| }; | |
| // Buffers candidates until we add them via AddBufferedIceCandidates. | |
| void ObserverForUsageHistogramTest::OnIceCandidate( | |
| const IceCandidate* candidate) { | |
| // If target is not set, ignore. This happens in one-ended unit tests. | |
| if (candidate_target_) { | |
| this->candidate_target_->BufferIceCandidate(candidate); | |
| } | |
| candidate_gathered_ = true; | |
| } | |
| class PeerConnectionUsageHistogramTest : public ::testing::Test { | |
| protected: | |
| typedef std::unique_ptr<PeerConnectionWrapperForUsageHistogramTest> | |
| WrapperPtr; | |
| PeerConnectionUsageHistogramTest() : main_(&vss_) { metrics::Reset(); } | |
| WrapperPtr CreatePeerConnection() { | |
| RTCConfiguration config; | |
| config.sdp_semantics = SdpSemantics::kUnifiedPlan; | |
| return CreatePeerConnection(config); | |
| } | |
| WrapperPtr CreatePeerConnection(const RTCConfiguration& config) { | |
| return CreatePeerConnection(config, | |
| PeerConnectionFactoryInterface::Options(), | |
| PeerConnectionDependencies(nullptr), nullptr); | |
| } | |
| WrapperPtr CreatePeerConnectionWithMdns(const RTCConfiguration& config) { | |
| PeerConnectionDependencies deps(nullptr /* observer_in */); | |
| deps.async_dns_resolver_factory = | |
| std::make_unique<NiceMock<MockAsyncDnsResolverFactory>>(); | |
| auto fake_network = std::make_unique<FakeNetworkManager>(Thread::Current()); | |
| fake_network->set_mdns_responder( | |
| std::make_unique<FakeMdnsResponder>(Thread::Current())); | |
| fake_network->AddInterface(NextLocalAddress()); | |
| return CreatePeerConnection(config, | |
| PeerConnectionFactoryInterface::Options(), | |
| std::move(deps), std::move(fake_network)); | |
| } | |
| WrapperPtr CreatePeerConnectionWithImmediateReport() { | |
| RTCConfiguration configuration; | |
| configuration.sdp_semantics = SdpSemantics::kUnifiedPlan; | |
| configuration.report_usage_pattern_delay_ms = 0; | |
| return CreatePeerConnection(configuration); | |
| } | |
| WrapperPtr CreatePeerConnectionWithPrivateLocalAddresses() { | |
| auto fake_network = std::make_unique<FakeNetworkManager>(Thread::Current()); | |
| fake_network->AddInterface(NextLocalAddress()); | |
| fake_network->AddInterface(kPrivateLocalAddress); | |
| RTCConfiguration config; | |
| config.sdp_semantics = SdpSemantics::kUnifiedPlan; | |
| return CreatePeerConnection( | |
| config, PeerConnectionFactoryInterface::Options(), | |
| PeerConnectionDependencies(nullptr), std::move(fake_network)); | |
| } | |
| WrapperPtr CreatePeerConnectionWithPrivateIpv6LocalAddresses() { | |
| auto fake_network = std::make_unique<FakeNetworkManager>(Thread::Current()); | |
| fake_network->AddInterface(NextLocalAddress()); | |
| fake_network->AddInterface(kPrivateIpv6LocalAddress); | |
| RTCConfiguration config; | |
| config.sdp_semantics = SdpSemantics::kUnifiedPlan; | |
| return CreatePeerConnection( | |
| config, PeerConnectionFactoryInterface::Options(), | |
| PeerConnectionDependencies(nullptr), std::move(fake_network)); | |
| } | |
| WrapperPtr CreatePeerConnection( | |
| const RTCConfiguration& config, | |
| const PeerConnectionFactoryInterface::Options factory_options, | |
| PeerConnectionDependencies deps, | |
| std::unique_ptr<NetworkManager> network_manager) { | |
| PeerConnectionFactoryDependencies pcf_deps; | |
| pcf_deps.network_thread = Thread::Current(); | |
| pcf_deps.worker_thread = Thread::Current(); | |
| pcf_deps.signaling_thread = Thread::Current(); | |
| pcf_deps.socket_factory = &vss_; | |
| if (network_manager != nullptr) { | |
| pcf_deps.network_manager = std::move(network_manager); | |
| } else { | |
| // If no network manager is provided, one will be created that uses the | |
| // host network. This doesn't work on all trybots. | |
| auto fake_network = | |
| std::make_unique<FakeNetworkManager>(pcf_deps.network_thread); | |
| fake_network->AddInterface(NextLocalAddress()); | |
| pcf_deps.network_manager = std::move(fake_network); | |
| } | |
| EnableFakeMedia(pcf_deps); | |
| auto pc_factory = CreateModularPeerConnectionFactory(std::move(pcf_deps)); | |
| pc_factory->SetOptions(factory_options); | |
| auto observer = std::make_unique<ObserverForUsageHistogramTest>(); | |
| deps.observer = observer.get(); | |
| auto result = | |
| pc_factory->CreatePeerConnectionOrError(config, std::move(deps)); | |
| if (!result.ok()) { | |
| return nullptr; | |
| } | |
| observer->SetPeerConnectionInterface(result.value().get()); | |
| auto wrapper = std::make_unique<PeerConnectionWrapperForUsageHistogramTest>( | |
| pc_factory, result.MoveValue(), std::move(observer)); | |
| return wrapper; | |
| } | |
| int ObservedFingerprint() { | |
| // This works correctly only if there is only one sample value | |
| // that has been counted. | |
| // Returns -1 for "not found". | |
| return metrics::MinSample(kUsagePatternMetric); | |
| } | |
| SocketAddress NextLocalAddress() { | |
| RTC_DCHECK_LT(next_local_address_, std::ssize(kLocalAddrs)); | |
| return kLocalAddrs[next_local_address_++]; | |
| } | |
| int next_local_address_ = 0; | |
| VirtualSocketServer vss_; | |
| test::RunLoop main_; | |
| }; | |
| TEST_F(PeerConnectionUsageHistogramTest, UsageFingerprintHistogramFromTimeout) { | |
| auto pc = CreatePeerConnectionWithImmediateReport(); | |
| int expected_fingerprint = MakeUsageFingerprint({}); | |
| EXPECT_THAT( | |
| WaitUntil([&] { return metrics::NumSamples(kUsagePatternMetric); }, | |
| ::testing::Eq(1)), | |
| IsRtcOk()); | |
| EXPECT_METRIC_EQ( | |
| 1, metrics::NumEvents(kUsagePatternMetric, expected_fingerprint)); | |
| } | |
| #ifndef WEBRTC_ANDROID | |
| // These tests do not work on Android. Why is unclear. | |
| // https://bugs.webrtc.org/9461 | |
| // Test getting the usage fingerprint for an audio/video connection. | |
| TEST_F(PeerConnectionUsageHistogramTest, FingerprintAudioVideo) { | |
| auto caller = CreatePeerConnection(); | |
| auto callee = CreatePeerConnection(); | |
| caller->AddAudioTrack("audio"); | |
| caller->AddVideoTrack("video"); | |
| ASSERT_TRUE(caller->ConnectTo(callee.get())); | |
| caller->pc()->Close(); | |
| callee->pc()->Close(); | |
| int expected_fingerprint = MakeUsageFingerprint( | |
| {UsageEvent::AUDIO_ADDED, UsageEvent::VIDEO_ADDED, | |
| UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED, | |
| UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED, | |
| UsageEvent::CANDIDATE_COLLECTED, UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED, | |
| UsageEvent::ICE_STATE_CONNECTED, UsageEvent::REMOTE_CANDIDATE_ADDED, | |
| UsageEvent::DIRECT_CONNECTION_SELECTED, UsageEvent::CLOSE_CALLED}); | |
| // In this case, we may or may not have PRIVATE_CANDIDATE_COLLECTED, | |
| // depending on the machine configuration. | |
| EXPECT_METRIC_EQ(2, metrics::NumSamples(kUsagePatternMetric)); | |
| EXPECT_METRIC_TRUE( | |
| metrics::NumEvents(kUsagePatternMetric, expected_fingerprint) == 2 || | |
| metrics::NumEvents( | |
| kUsagePatternMetric, | |
| expected_fingerprint | | |
| static_cast<int>(UsageEvent::PRIVATE_CANDIDATE_COLLECTED)) == 2); | |
| } | |
| // Test getting the usage fingerprint when the caller collects an mDNS | |
| // candidate. | |
| TEST_F(PeerConnectionUsageHistogramTest, FingerprintWithMdnsCaller) { | |
| RTCConfiguration config; | |
| config.sdp_semantics = SdpSemantics::kUnifiedPlan; | |
| // Enable hostname candidates with mDNS names. | |
| auto caller = CreatePeerConnectionWithMdns(config); | |
| auto callee = CreatePeerConnection(config); | |
| caller->AddAudioTrack("audio"); | |
| caller->AddVideoTrack("video"); | |
| ASSERT_TRUE(caller->ConnectTo(callee.get())); | |
| caller->pc()->Close(); | |
| callee->pc()->Close(); | |
| int expected_fingerprint_caller = MakeUsageFingerprint( | |
| {UsageEvent::AUDIO_ADDED, UsageEvent::VIDEO_ADDED, | |
| UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED, | |
| UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED, | |
| UsageEvent::CANDIDATE_COLLECTED, UsageEvent::MDNS_CANDIDATE_COLLECTED, | |
| UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED, UsageEvent::ICE_STATE_CONNECTED, | |
| UsageEvent::REMOTE_CANDIDATE_ADDED, | |
| UsageEvent::DIRECT_CONNECTION_SELECTED, UsageEvent::CLOSE_CALLED}); | |
| // Without a resolver, the callee cannot resolve the received mDNS candidate | |
| // but can still connect with the caller via a prflx candidate. As a result, | |
| // the bit for the direct connection should not be logged. | |
| int expected_fingerprint_callee = MakeUsageFingerprint( | |
| {UsageEvent::AUDIO_ADDED, UsageEvent::VIDEO_ADDED, | |
| UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED, | |
| UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED, | |
| UsageEvent::CANDIDATE_COLLECTED, UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED, | |
| UsageEvent::REMOTE_MDNS_CANDIDATE_ADDED, UsageEvent::ICE_STATE_CONNECTED, | |
| UsageEvent::REMOTE_CANDIDATE_ADDED, UsageEvent::CLOSE_CALLED}); | |
| EXPECT_METRIC_EQ(2, metrics::NumSamples(kUsagePatternMetric)); | |
| EXPECT_METRIC_EQ( | |
| 1, metrics::NumEvents(kUsagePatternMetric, expected_fingerprint_caller)); | |
| EXPECT_METRIC_EQ( | |
| 1, metrics::NumEvents(kUsagePatternMetric, expected_fingerprint_callee)); | |
| } | |
| // Test getting the usage fingerprint when the callee collects an mDNS | |
| // candidate. | |
| TEST_F(PeerConnectionUsageHistogramTest, FingerprintWithMdnsCallee) { | |
| RTCConfiguration config; | |
| config.sdp_semantics = SdpSemantics::kUnifiedPlan; | |
| // Enable hostname candidates with mDNS names. | |
| auto caller = CreatePeerConnection(config); | |
| auto callee = CreatePeerConnectionWithMdns(config); | |
| caller->AddAudioTrack("audio"); | |
| caller->AddVideoTrack("video"); | |
| ASSERT_TRUE(caller->ConnectTo(callee.get())); | |
| caller->pc()->Close(); | |
| callee->pc()->Close(); | |
| // Similar to the test above, the caller connects with the callee via a prflx | |
| // candidate. | |
| int expected_fingerprint_caller = MakeUsageFingerprint( | |
| {UsageEvent::AUDIO_ADDED, UsageEvent::VIDEO_ADDED, | |
| UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED, | |
| UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED, | |
| UsageEvent::CANDIDATE_COLLECTED, UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED, | |
| UsageEvent::REMOTE_MDNS_CANDIDATE_ADDED, UsageEvent::ICE_STATE_CONNECTED, | |
| UsageEvent::REMOTE_CANDIDATE_ADDED, UsageEvent::CLOSE_CALLED}); | |
| int expected_fingerprint_callee = MakeUsageFingerprint( | |
| {UsageEvent::AUDIO_ADDED, UsageEvent::VIDEO_ADDED, | |
| UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED, | |
| UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED, | |
| UsageEvent::CANDIDATE_COLLECTED, UsageEvent::MDNS_CANDIDATE_COLLECTED, | |
| UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED, UsageEvent::ICE_STATE_CONNECTED, | |
| UsageEvent::REMOTE_CANDIDATE_ADDED, | |
| UsageEvent::DIRECT_CONNECTION_SELECTED, UsageEvent::CLOSE_CALLED}); | |
| EXPECT_METRIC_EQ(2, metrics::NumSamples(kUsagePatternMetric)); | |
| EXPECT_METRIC_EQ( | |
| 1, metrics::NumEvents(kUsagePatternMetric, expected_fingerprint_caller)); | |
| EXPECT_METRIC_EQ( | |
| 1, metrics::NumEvents(kUsagePatternMetric, expected_fingerprint_callee)); | |
| } | |
| #ifdef WEBRTC_HAVE_SCTP | |
| TEST_F(PeerConnectionUsageHistogramTest, FingerprintDataOnly) { | |
| auto caller = CreatePeerConnection(); | |
| auto callee = CreatePeerConnection(); | |
| caller->CreateDataChannel("foodata"); | |
| ASSERT_TRUE(caller->ConnectTo(callee.get())); | |
| ASSERT_THAT( | |
| WaitUntil([&] { return callee->HaveDataChannel(); }, ::testing::IsTrue()), | |
| IsRtcOk()); | |
| caller->pc()->Close(); | |
| callee->pc()->Close(); | |
| int expected_fingerprint = MakeUsageFingerprint( | |
| {UsageEvent::DATA_ADDED, UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED, | |
| UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED, | |
| UsageEvent::CANDIDATE_COLLECTED, UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED, | |
| UsageEvent::ICE_STATE_CONNECTED, UsageEvent::REMOTE_CANDIDATE_ADDED, | |
| UsageEvent::DIRECT_CONNECTION_SELECTED, UsageEvent::CLOSE_CALLED}); | |
| EXPECT_METRIC_EQ(2, metrics::NumSamples(kUsagePatternMetric)); | |
| EXPECT_METRIC_TRUE( | |
| metrics::NumEvents(kUsagePatternMetric, expected_fingerprint) == 2 || | |
| metrics::NumEvents( | |
| kUsagePatternMetric, | |
| expected_fingerprint | | |
| static_cast<int>(UsageEvent::PRIVATE_CANDIDATE_COLLECTED)) == 2); | |
| } | |
| #endif // WEBRTC_HAVE_SCTP | |
| #endif // WEBRTC_ANDROID | |
| TEST_F(PeerConnectionUsageHistogramTest, FingerprintStunTurn) { | |
| RTCConfiguration configuration; | |
| configuration.sdp_semantics = SdpSemantics::kUnifiedPlan; | |
| PeerConnection::IceServer server; | |
| server.urls = {"stun:dummy.stun.server"}; | |
| configuration.servers.push_back(server); | |
| server.urls = {"turn:dummy.turn.server"}; | |
| server.username = "username"; | |
| server.password = "password"; | |
| configuration.servers.push_back(server); | |
| auto caller = CreatePeerConnection(configuration); | |
| ASSERT_TRUE(caller); | |
| caller->pc()->Close(); | |
| int expected_fingerprint = MakeUsageFingerprint( | |
| {UsageEvent::STUN_SERVER_ADDED, UsageEvent::TURN_SERVER_ADDED, | |
| UsageEvent::CLOSE_CALLED}); | |
| EXPECT_METRIC_EQ(1, metrics::NumSamples(kUsagePatternMetric)); | |
| EXPECT_METRIC_EQ( | |
| 1, metrics::NumEvents(kUsagePatternMetric, expected_fingerprint)); | |
| } | |
| TEST_F(PeerConnectionUsageHistogramTest, FingerprintStunTurnInReconfiguration) { | |
| RTCConfiguration configuration; | |
| configuration.sdp_semantics = SdpSemantics::kUnifiedPlan; | |
| PeerConnection::IceServer server; | |
| server.urls = {"stun:dummy.stun.server"}; | |
| configuration.servers.push_back(server); | |
| server.urls = {"turn:dummy.turn.server"}; | |
| server.username = "username"; | |
| server.password = "password"; | |
| configuration.servers.push_back(server); | |
| auto caller = CreatePeerConnection(); | |
| ASSERT_TRUE(caller); | |
| ASSERT_TRUE(caller->pc()->SetConfiguration(configuration).ok()); | |
| caller->pc()->Close(); | |
| int expected_fingerprint = MakeUsageFingerprint( | |
| {UsageEvent::STUN_SERVER_ADDED, UsageEvent::TURN_SERVER_ADDED, | |
| UsageEvent::CLOSE_CALLED}); | |
| EXPECT_METRIC_EQ(1, metrics::NumSamples(kUsagePatternMetric)); | |
| EXPECT_METRIC_EQ( | |
| 1, metrics::NumEvents(kUsagePatternMetric, expected_fingerprint)); | |
| } | |
| TEST_F(PeerConnectionUsageHistogramTest, FingerprintWithPrivateIPCaller) { | |
| auto caller = CreatePeerConnectionWithPrivateLocalAddresses(); | |
| auto callee = CreatePeerConnection(); | |
| caller->AddAudioTrack("audio"); | |
| ASSERT_TRUE(caller->ConnectTo(callee.get())); | |
| caller->pc()->Close(); | |
| callee->pc()->Close(); | |
| int expected_fingerprint_caller = MakeUsageFingerprint( | |
| {UsageEvent::AUDIO_ADDED, UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED, | |
| UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED, | |
| UsageEvent::CANDIDATE_COLLECTED, UsageEvent::PRIVATE_CANDIDATE_COLLECTED, | |
| UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED, UsageEvent::ICE_STATE_CONNECTED, | |
| UsageEvent::REMOTE_CANDIDATE_ADDED, | |
| UsageEvent::DIRECT_CONNECTION_SELECTED, UsageEvent::CLOSE_CALLED}); | |
| int expected_fingerprint_callee = MakeUsageFingerprint( | |
| {UsageEvent::AUDIO_ADDED, UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED, | |
| UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED, | |
| UsageEvent::CANDIDATE_COLLECTED, UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED, | |
| UsageEvent::REMOTE_PRIVATE_CANDIDATE_ADDED, | |
| UsageEvent::ICE_STATE_CONNECTED, UsageEvent::REMOTE_CANDIDATE_ADDED, | |
| UsageEvent::DIRECT_CONNECTION_SELECTED, UsageEvent::CLOSE_CALLED}); | |
| EXPECT_METRIC_EQ(2, metrics::NumSamples(kUsagePatternMetric)); | |
| EXPECT_METRIC_EQ( | |
| 1, metrics::NumEvents(kUsagePatternMetric, expected_fingerprint_caller)); | |
| EXPECT_METRIC_EQ( | |
| 1, metrics::NumEvents(kUsagePatternMetric, expected_fingerprint_callee)); | |
| } | |
| TEST_F(PeerConnectionUsageHistogramTest, FingerprintWithPrivateIpv6Callee) { | |
| auto caller = CreatePeerConnection(); | |
| auto callee = CreatePeerConnectionWithPrivateIpv6LocalAddresses(); | |
| caller->AddAudioTrack("audio"); | |
| ASSERT_TRUE(caller->ConnectTo(callee.get())); | |
| caller->pc()->Close(); | |
| callee->pc()->Close(); | |
| int expected_fingerprint_caller = MakeUsageFingerprint( | |
| {UsageEvent::AUDIO_ADDED, UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED, | |
| UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED, | |
| UsageEvent::CANDIDATE_COLLECTED, UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED, | |
| UsageEvent::REMOTE_PRIVATE_CANDIDATE_ADDED, | |
| UsageEvent::ICE_STATE_CONNECTED, UsageEvent::REMOTE_CANDIDATE_ADDED, | |
| UsageEvent::REMOTE_IPV6_CANDIDATE_ADDED, | |
| UsageEvent::DIRECT_CONNECTION_SELECTED, UsageEvent::CLOSE_CALLED}); | |
| int expected_fingerprint_callee = MakeUsageFingerprint( | |
| {UsageEvent::AUDIO_ADDED, UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED, | |
| UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED, | |
| UsageEvent::CANDIDATE_COLLECTED, UsageEvent::PRIVATE_CANDIDATE_COLLECTED, | |
| UsageEvent::IPV6_CANDIDATE_COLLECTED, | |
| UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED, | |
| UsageEvent::REMOTE_CANDIDATE_ADDED, UsageEvent::ICE_STATE_CONNECTED, | |
| UsageEvent::DIRECT_CONNECTION_SELECTED, UsageEvent::CLOSE_CALLED}); | |
| EXPECT_METRIC_EQ(2, metrics::NumSamples(kUsagePatternMetric)); | |
| EXPECT_METRIC_EQ( | |
| 1, metrics::NumEvents(kUsagePatternMetric, expected_fingerprint_caller)); | |
| EXPECT_METRIC_EQ( | |
| 1, metrics::NumEvents(kUsagePatternMetric, expected_fingerprint_callee)); | |
| } | |
| struct IPAddressTypeTestConfig { | |
| absl::string_view address; | |
| IPAddressType address_type; | |
| } const kAllCandidateIPAddressTypeTestConfigs[] = { | |
| {.address = "127.0.0.1", .address_type = IPAddressType::kLoopback}, | |
| {.address = "::1", .address_type = IPAddressType::kLoopback}, | |
| {.address = "localhost", .address_type = IPAddressType::kLoopback}, | |
| {.address = "10.0.0.3", .address_type = IPAddressType::kPrivate}, | |
| {.address = "FE80::3", .address_type = IPAddressType::kPrivate}, | |
| {.address = "1.1.1.1", .address_type = IPAddressType::kPublic}, | |
| {.address = "2001:4860:4860::8888", .address_type = IPAddressType::kPublic}, | |
| }; | |
| // Used by the test framework to print the param value for parameterized tests. | |
| std::string PrintToString(const IPAddressTypeTestConfig& param) { | |
| return std::string(param.address); | |
| } | |
| class PeerConnectionCandidateIPAddressTypeHistogramTest | |
| : public PeerConnectionUsageHistogramTest, | |
| public ::testing::WithParamInterface<IPAddressTypeTestConfig> {}; | |
| // Tests that the correct IPAddressType is logged when adding candidates. | |
| TEST_P(PeerConnectionCandidateIPAddressTypeHistogramTest, | |
| CandidateAddressType) { | |
| auto caller = CreatePeerConnection(); | |
| caller->AddAudioTrack("audio"); | |
| ASSERT_TRUE(caller->SetLocalDescription(caller->CreateOffer())); | |
| // Set the remote description which includes a candidate using the IP Address | |
| // from the test's params. | |
| EXPECT_TRUE(caller->SetRemoteDescription(CreateSessionDescription( | |
| SdpType::kAnswer, | |
| absl::StrFormat(kBasicRemoteDescription, GetParam().address)))); | |
| ASSERT_THAT( | |
| WaitUntil([&] { return caller->ice_gathering_state(); }, | |
| ::testing::Eq(PeerConnectionInterface::kIceGatheringComplete)), | |
| IsRtcOk()); | |
| ASSERT_TRUE(caller->observer()->candidate_gathered()); | |
| auto samples = metrics::Samples("WebRTC.PeerConnection.CandidateAddressType"); | |
| ASSERT_EQ(samples.size(), 1u); | |
| EXPECT_EQ(samples[static_cast<int>(GetParam().address_type)], 1); | |
| } | |
| INSTANTIATE_TEST_SUITE_P( | |
| All, | |
| PeerConnectionCandidateIPAddressTypeHistogramTest, | |
| ::testing::ValuesIn(kAllCandidateIPAddressTypeTestConfigs)); | |
| #ifndef WEBRTC_ANDROID | |
| #ifdef WEBRTC_HAVE_SCTP | |
| // Test that the usage pattern bits for adding remote (private IPv6) candidates | |
| // are set when the remote candidates are retrieved from the Offer SDP instead | |
| // of trickled ICE messages. | |
| TEST_F(PeerConnectionUsageHistogramTest, | |
| AddRemoteCandidatesFromRemoteDescription) { | |
| // We construct the following data-channel-only scenario. The caller collects | |
| // IPv6 private local candidates and appends them in the Offer as in | |
| // non-trickled sessions. The callee collects mDNS candidates that are not | |
| // contained in the Answer as in Trickle ICE. Only the Offer and Answer are | |
| // signaled and we expect a connection with prflx remote candidates at the | |
| // caller side. | |
| auto caller = CreatePeerConnectionWithPrivateIpv6LocalAddresses(); | |
| RTCConfiguration config; | |
| config.sdp_semantics = SdpSemantics::kUnifiedPlan; | |
| auto callee = CreatePeerConnectionWithMdns(config); | |
| caller->CreateDataChannel("test_channel"); | |
| ASSERT_TRUE(caller->SetLocalDescription(caller->CreateOffer())); | |
| // Wait until the gathering completes so that the session description would | |
| // have contained ICE candidates. | |
| EXPECT_THAT( | |
| WaitUntil([&] { return caller->ice_gathering_state(); }, | |
| ::testing::Eq(PeerConnectionInterface::kIceGatheringComplete)), | |
| IsRtcOk()); | |
| EXPECT_TRUE(caller->observer()->candidate_gathered()); | |
| // Get the current offer that contains candidates and pass it to the callee. | |
| // | |
| // Note that we cannot use CloneSessionDescription on `cur_offer` to obtain an | |
| // SDP with candidates. The method above does not strictly copy everything, in | |
| // particular, not copying the ICE candidates. | |
| // TODO(qingsi): Technically, this is a bug. Fix it. | |
| auto cur_offer = caller->pc()->local_description(); | |
| ASSERT_TRUE(cur_offer); | |
| std::string sdp_with_candidates_str; | |
| cur_offer->ToString(&sdp_with_candidates_str); | |
| std::unique_ptr<SessionDescriptionInterface> offer = | |
| SdpDeserialize(SdpType::kOffer, sdp_with_candidates_str); | |
| ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer))); | |
| // By default, the Answer created does not contain ICE candidates. | |
| std::unique_ptr<SessionDescriptionInterface> answer = callee->CreateAnswer(); | |
| callee->SetLocalDescription(answer->Clone()); | |
| caller->SetRemoteDescription(std::move(answer)); | |
| EXPECT_THAT( | |
| WaitUntil([&] { return caller->IsConnected(); }, ::testing::IsTrue()), | |
| IsRtcOk()); | |
| EXPECT_THAT( | |
| WaitUntil([&] { return callee->IsConnected(); }, ::testing::IsTrue()), | |
| IsRtcOk()); | |
| // The callee needs to process the open message to have the data channel open. | |
| EXPECT_THAT( | |
| WaitUntil( | |
| [&] { return callee->observer()->last_datachannel_ != nullptr; }, | |
| ::testing::IsTrue()), | |
| IsRtcOk()); | |
| caller->pc()->Close(); | |
| callee->pc()->Close(); | |
| // The caller should not have added any remote candidate either via | |
| // AddIceCandidate or from the remote description. Also, the caller connects | |
| // with the callee via a prflx candidate and hence no direct connection bit | |
| // should be set. | |
| int expected_fingerprint_caller = MakeUsageFingerprint( | |
| {UsageEvent::DATA_ADDED, UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED, | |
| UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED, | |
| UsageEvent::CANDIDATE_COLLECTED, UsageEvent::PRIVATE_CANDIDATE_COLLECTED, | |
| UsageEvent::IPV6_CANDIDATE_COLLECTED, UsageEvent::ICE_STATE_CONNECTED, | |
| UsageEvent::CLOSE_CALLED}); | |
| int expected_fingerprint_callee = MakeUsageFingerprint( | |
| {UsageEvent::DATA_ADDED, UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED, | |
| UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED, | |
| UsageEvent::CANDIDATE_COLLECTED, UsageEvent::MDNS_CANDIDATE_COLLECTED, | |
| UsageEvent::REMOTE_CANDIDATE_ADDED, | |
| UsageEvent::REMOTE_PRIVATE_CANDIDATE_ADDED, | |
| UsageEvent::REMOTE_IPV6_CANDIDATE_ADDED, UsageEvent::ICE_STATE_CONNECTED, | |
| UsageEvent::DIRECT_CONNECTION_SELECTED, UsageEvent::CLOSE_CALLED}); | |
| EXPECT_METRIC_EQ(2, metrics::NumSamples(kUsagePatternMetric)); | |
| EXPECT_METRIC_EQ( | |
| 1, metrics::NumEvents(kUsagePatternMetric, expected_fingerprint_caller)); | |
| EXPECT_METRIC_EQ( | |
| 1, metrics::NumEvents(kUsagePatternMetric, expected_fingerprint_callee)); | |
| } | |
| TEST_F(PeerConnectionUsageHistogramTest, NotableUsageNoted) { | |
| auto caller = CreatePeerConnection(); | |
| caller->CreateDataChannel("foo"); | |
| caller->GenerateOfferAndCollectCandidates(); | |
| caller->pc()->Close(); | |
| int expected_fingerprint = MakeUsageFingerprint( | |
| {UsageEvent::DATA_ADDED, UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED, | |
| UsageEvent::CANDIDATE_COLLECTED, UsageEvent::CLOSE_CALLED}); | |
| EXPECT_METRIC_EQ(1, metrics::NumSamples(kUsagePatternMetric)); | |
| EXPECT_METRIC_TRUE( | |
| expected_fingerprint == ObservedFingerprint() || | |
| (expected_fingerprint | | |
| static_cast<int>(UsageEvent::PRIVATE_CANDIDATE_COLLECTED)) == | |
| ObservedFingerprint()); | |
| EXPECT_METRIC_EQ(std::make_optional(ObservedFingerprint()), | |
| caller->observer()->interesting_usage_detected()); | |
| } | |
| TEST_F(PeerConnectionUsageHistogramTest, NotableUsageOnEventFiring) { | |
| auto caller = CreatePeerConnection(); | |
| caller->CreateDataChannel("foo"); | |
| caller->GenerateOfferAndCollectCandidates(); | |
| int expected_fingerprint = MakeUsageFingerprint( | |
| {UsageEvent::DATA_ADDED, UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED, | |
| UsageEvent::CANDIDATE_COLLECTED}); | |
| EXPECT_METRIC_EQ(0, metrics::NumSamples(kUsagePatternMetric)); | |
| caller->GetInternalPeerConnection()->RequestUsagePatternReportForTesting(); | |
| EXPECT_THAT( | |
| WaitUntil([&] { return metrics::NumSamples(kUsagePatternMetric); }, | |
| ::testing::Eq(1)), | |
| IsRtcOk()); | |
| EXPECT_METRIC_TRUE( | |
| expected_fingerprint == ObservedFingerprint() || | |
| (expected_fingerprint | | |
| static_cast<int>(UsageEvent::PRIVATE_CANDIDATE_COLLECTED)) == | |
| ObservedFingerprint()); | |
| EXPECT_METRIC_EQ(std::make_optional(ObservedFingerprint()), | |
| caller->observer()->interesting_usage_detected()); | |
| } | |
| TEST_F(PeerConnectionUsageHistogramTest, | |
| NoNotableUsageOnEventFiringAfterClose) { | |
| auto caller = CreatePeerConnection(); | |
| caller->CreateDataChannel("foo"); | |
| caller->GenerateOfferAndCollectCandidates(); | |
| int expected_fingerprint = MakeUsageFingerprint( | |
| {UsageEvent::DATA_ADDED, UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED, | |
| UsageEvent::CANDIDATE_COLLECTED, UsageEvent::CLOSE_CALLED}); | |
| EXPECT_METRIC_EQ(0, metrics::NumSamples(kUsagePatternMetric)); | |
| caller->pc()->Close(); | |
| EXPECT_METRIC_EQ(1, metrics::NumSamples(kUsagePatternMetric)); | |
| caller->GetInternalPeerConnection()->RequestUsagePatternReportForTesting(); | |
| caller->observer()->ClearInterestingUsageDetector(); | |
| EXPECT_THAT( | |
| WaitUntil([&] { return metrics::NumSamples(kUsagePatternMetric); }, | |
| ::testing::Eq(2)), | |
| IsRtcOk()); | |
| EXPECT_METRIC_TRUE( | |
| expected_fingerprint == ObservedFingerprint() || | |
| (expected_fingerprint | | |
| static_cast<int>(UsageEvent::PRIVATE_CANDIDATE_COLLECTED)) == | |
| ObservedFingerprint()); | |
| // After close, the usage-detection callback should NOT have been called. | |
| EXPECT_METRIC_FALSE(caller->observer()->interesting_usage_detected()); | |
| } | |
| #endif | |
| #endif | |
| } // namespace | |
| } // namespace webrtc |