// Copyright 2021 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "connections/implementation/client_proxy.h" #include #include #include #include #include #include #include #include #include #include #include "absl/container/flat_hash_map.h" #include "absl/container/flat_hash_set.h" #include "absl/functional/any_invocable.h" #include "absl/random/random.h" #include "absl/strings/escaping.h" #include "absl/strings/string_view.h" #include "absl/time/time.h" #include "absl/types/span.h" #include "connections/advertising_options.h" #include "connections/connection_options.h" #include "connections/discovery_options.h" #include "connections/implementation/analytics/advertising_metadata_params.h" #include "connections/implementation/analytics/analytics_recorder.h" #include "connections/implementation/analytics/connection_attempt_metadata_params.h" #include "connections/implementation/analytics/discovery_metadata_params.h" #include "connections/implementation/analytics/operation_result_with_medium.h" #include "connections/implementation/flags/nearby_connections_feature_flags.h" #include "connections/implementation/mediums/advertisements/dct_advertisement.h" #include "connections/listeners.h" #include "connections/medium_selector.h" #include "connections/payload.h" #include "connections/payload_type.h" #include "connections/status.h" #include "connections/strategy.h" #include "connections/v3/bandwidth_info.h" #include "connections/v3/connection_listening_options.h" #include "connections/v3/connection_result.h" #include "connections/v3/connections_device.h" #include "connections/v3/connections_device_provider.h" #include "connections/v3/listeners.h" #include "internal/base/file_path.h" #include "internal/base/files.h" #include "internal/flags/nearby_flags.h" #include "internal/interop/device.h" #include "internal/platform/byte_array.h" #include "internal/platform/cancelable_alarm.h" #include "internal/platform/cancellation_flag.h" #ifndef NEARBY_CHROMIUM #include "internal/platform/implementation/device_info.h" #endif #include "internal/platform/error_code_params.h" #include "internal/platform/error_code_recorder.h" #include "internal/platform/feature_flags.h" #include "internal/platform/implementation/app_lifecycle_monitor.h" #include "internal/platform/implementation/platform.h" #include "internal/platform/implementation/system_clock.h" #include "internal/platform/logging.h" #include "internal/platform/mac_address.h" #include "internal/platform/mutex_lock.h" #include "internal/platform/os_name.h" #include "internal/platform/prng.h" #include "proto/connections_enums.pb.h" namespace nearby::connections { namespace { using ::location::nearby::connections::MediumRole; using ::location::nearby::connections::OsInfo; using ::nearby::analytics::AnalyticsRecorder; constexpr char kEndpointIdChars[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0'}; constexpr absl::string_view kPreferencesFilePath = "Google/Nearby/Connections"; constexpr absl::string_view kAdvertisingEndpointId = "nc.advertising.endpoint_id"; constexpr absl::string_view kAdvertisingTimestamp = "nc.advertising.timestamp"; constexpr absl::Duration kAdvertisingKeepAliveDuration = absl::Seconds(30); class NoOpAnalyticsRecorder : public AnalyticsRecorder { public: NoOpAnalyticsRecorder() = default; ~NoOpAnalyticsRecorder() override = default; // Advertising phase void OnStartAdvertising( connections::Strategy strategy, const std::vector& mediums, AdvertisingMetadataParams* advertising_metadata_params) override {} void OnStopAdvertising() override {} int GetNextAdvertisingUpdateIndex() override { return 0; } // Connection listening void OnStartedIncomingConnectionListening( connections::Strategy strategy) override {} void OnStoppedIncomingConnectionListening() override {} // Discovery phase void OnStartDiscovery( connections::Strategy strategy, const std::vector& mediums, DiscoveryMetadataParams* discovery_metadata_params) override {} void OnStopDiscovery() override {} int GetNextDiscoveryUpdateIndex() override { return 0; } void OnEndpointFound( location::nearby::proto::connections::Medium medium) override {} // Connection request void OnRequestConnection(const connections::Strategy& strategy, const std::string& endpoint_id) override {} void OnConnectionRequestReceived( const std::string& remote_endpoint_id) override {} void OnConnectionRequestSent( const std::string& remote_endpoint_id) override {} void OnRemoteEndpointAccepted( const std::string& remote_endpoint_id) override {} void OnLocalEndpointAccepted( const std::string& remote_endpoint_id) override {} void OnRemoteEndpointRejected( const std::string& remote_endpoint_id) override {} void OnLocalEndpointRejected( const std::string& remote_endpoint_id) override {} // Connection attempt void OnIncomingConnectionAttempt( location::nearby::proto::connections::ConnectionAttemptType type, location::nearby::proto::connections::Medium medium, location::nearby::proto::connections::ConnectionAttemptResult result, absl::Duration duration, const std::string& connection_token, ConnectionAttemptMetadataParams* connection_attempt_metadata_params) override {} void OnOutgoingConnectionAttempt( const std::string& remote_endpoint_id, location::nearby::proto::connections::ConnectionAttemptType type, location::nearby::proto::connections::Medium medium, location::nearby::proto::connections::ConnectionAttemptResult result, absl::Duration duration, const std::string& connection_token, ConnectionAttemptMetadataParams* connection_attempt_metadata_params) override {} // Connection established void OnConnectionEstablished( const std::string& endpoint_id, location::nearby::proto::connections::Medium medium, const std::string& connection_token) override {} void OnConnectionClosed( const std::string& endpoint_id, location::nearby::proto::connections::Medium medium, location::nearby::proto::connections::DisconnectionReason reason, nearby::analytics::SafeDisconnectionResult result) override {} // Payload void OnIncomingPayloadStarted(const std::string& endpoint_id, std::int64_t payload_id, connections::PayloadType type, std::int64_t total_size_bytes) override {} void OnPayloadChunkReceived(const std::string& endpoint_id, std::int64_t payload_id, std::int64_t chunk_size_bytes) override {} void OnIncomingPayloadDone( const std::string& endpoint_id, std::int64_t payload_id, location::nearby::proto::connections::PayloadStatus status, location::nearby::proto::connections::OperationResultCode operation_result_code) override {} void OnOutgoingPayloadStarted( const std::vector& endpoint_ids, std::int64_t payload_id, connections::PayloadType type, std::int64_t total_size_bytes) override {} void OnPayloadChunkSent(const std::string& endpoint_id, std::int64_t payload_id, std::int64_t chunk_size_bytes) override {} void OnOutgoingPayloadDone( const std::string& endpoint_id, std::int64_t payload_id, location::nearby::proto::connections::PayloadStatus status, location::nearby::proto::connections::OperationResultCode operation_result_code) override {} // BandwidthUpgrade void OnBandwidthUpgradeStarted( const std::string& endpoint_id, location::nearby::proto::connections::Medium from_medium, location::nearby::proto::connections::Medium to_medium, location::nearby::proto::connections::ConnectionAttemptDirection direction, const std::string& connection_token) override {} void UpdateBwUpgradeNetworkInfo(const std::string& endpoint_id, int num_interfaces, int num_ipv6_only_interfaces) override {} void OnBandwidthUpgradeError( const std::string& endpoint_id, location::nearby::proto::connections::BandwidthUpgradeResult result, location::nearby::proto::connections::BandwidthUpgradeErrorStage error_stage, location::nearby::proto::connections::OperationResultCode operation_result_code) override {} void OnBandwidthUpgradeSuccess(const std::string& endpoint_id) override {} // Error Code void OnErrorCode(const ErrorCodeParams& params) override {} void LogStartSession() override {} void LogSession() override {} bool IsSessionLogged() override { return false; } location::nearby::proto::connections::OperationResultCategory GetOperationResultCategory( location::nearby::proto::connections::OperationResultCode result_code) override { return location::nearby::proto::connections::OperationResultCategory:: CATEGORY_UNKNOWN; } void Sync() override {} }; } // namespace ClientProxy::ClientProxy(std::unique_ptr analytics_recorder) : client_id_(Prng().NextInt64()), analytics_recorder_(std::move(analytics_recorder)) { if (analytics_recorder_ == nullptr) { analytics_recorder_ = std::make_unique(); } if (NearbyFlags::GetInstance().GetBoolFlag( config_package_nearby::nearby_connections_feature:: kEnableNearbyConnectionsPreferences)) { InitializePreferencesManager(); } is_dct_enabled_ = NearbyFlags::GetInstance().GetBoolFlag( config_package_nearby::nearby_connections_feature::kEnableDct); error_code_recorder_ = std::make_unique( [this](const ErrorCodeParams& params) { analytics_recorder_->OnErrorCode(params); }); local_os_info_.set_type( OSNameToOsInfoType(api::ImplementationPlatform::GetCurrentOS())); supports_safe_to_disconnect_ = NearbyFlags::GetInstance().GetBoolFlag( config_package_nearby::nearby_connections_feature:: kEnableSafeToDisconnect); LOG(INFO) << "[safe-to-disconnect]: Local enabled: " << supports_safe_to_disconnect_ << "; Version: " << NearbyFlags::GetInstance().GetInt64Flag( config_package_nearby::nearby_connections_feature:: kSafeToDisconnectVersion); // Generate a 7 bits dedup value. absl::BitGen bitgen; dct_dedup_ = absl::Uniform(bitgen, 0, 1 << 7); // Load advertising info from preferences. LoadClientInfoFromPreferences(); #ifndef NEARBY_CHROMIUM local_device_name_ = api::ImplementationPlatform::CreateDeviceInfo() ->GetOsDeviceName() .value_or(""); #endif if (preferences_manager_ != nullptr) { app_lifecycle_monitor_ = api::ImplementationPlatform::CreateAppLifecycleMonitor( [this](api::AppLifecycleMonitor::AppLifecycleState state) { if (state == api::AppLifecycleMonitor::AppLifecycleState::kInactive || state == api::AppLifecycleMonitor::AppLifecycleState:: kBackground) { SaveClientInfoToPreferences(); } }); } } ClientProxy::~ClientProxy() { Reset(); } std::int64_t ClientProxy::GetClientId() const { return client_id_; } std::string ClientProxy::GetLocalEndpointId() { MutexLock lock(&mutex_); if (IsDctEnabled() && GetEndpointIdForDct().has_value()) { LOG(INFO) << "DCT is using genereted endpoint id."; return GetEndpointIdForDct().value(); } else { if (!local_endpoint_id_.empty()) { LOG(INFO) << __func__ << ": Reusing cached endpoint id: " << local_endpoint_id_; return local_endpoint_id_; } if (external_device_provider_ == nullptr) { local_endpoint_id_ = GenerateLocalEndpointId(); } else { local_endpoint_id_ = external_device_provider_->GetLocalDevice()->GetEndpointId(); LOG(INFO) << __func__ << ": From external device provider, populating endpoint id: " << local_endpoint_id_; } return local_endpoint_id_; } } const NearbyDevice* ClientProxy::GetLocalDevice() { if (external_device_provider_ == nullptr && connections_device_provider_ == nullptr) { // TODO(b/285602283): Plug in actual endpoint info once available. auto provider = v3::ConnectionsDeviceProvider(GetLocalEndpointId(), "V3 endpoint", {}); RegisterConnectionsDeviceProvider( std::make_unique(provider)); } return GetLocalDeviceProvider()->GetLocalDevice(); } std::string ClientProxy::GetConnectionToken(const std::string& endpoint_id) { MutexLock lock(&mutex_); ConnectionPair* item = LookupConnection(endpoint_id); if (item != nullptr) { return item->first.connection_token; } return {}; } bool ClientProxy::OverrideSavePath(absl::string_view endpoint_id, absl::string_view path) { MutexLock lock(&mutex_); ConnectionPair* item = LookupConnection(endpoint_id); if (item != nullptr) { item->first.save_path = path; return true; } return false; } std::string ClientProxy::GetSavePath( absl::string_view endpoint_id) const { MutexLock lock(&mutex_); const ConnectionPair* item = LookupConnection(endpoint_id); if (item != nullptr) { return item->first.save_path; } return ""; } std::optional ClientProxy::GetBluetoothMacAddress( const std::string& endpoint_id) { MutexLock lock(&mutex_); auto item = bluetooth_mac_addresses_.find(endpoint_id); if (item != bluetooth_mac_addresses_.end()) return item->second; return std::nullopt; } void ClientProxy::SetBluetoothMacAddress(const std::string& endpoint_id, MacAddress bluetooth_mac_address) { MutexLock lock(&mutex_); bluetooth_mac_addresses_[endpoint_id] = bluetooth_mac_address; } std::string ClientProxy::GenerateLocalEndpointId() { if (!cached_endpoint_id_.empty()) { if (stable_endpoint_id_mode_ || HasOngoingConnection()) { LOG(INFO) << "ClientProxy [Local Endpoint Re-using cached " "endpoint id due to in stable endpoint id mode or having " "ongoing connection]: " "client=" << GetClientId() << "; cached_endpoint_id_=" << cached_endpoint_id_; return cached_endpoint_id_; } } std::string id; Prng prng; for (int i = 0; i < kEndpointIdLength; i++) { id += kEndpointIdChars[prng.NextUint32() % sizeof(kEndpointIdChars)]; } LOG(INFO) << "ClientProxy [Local Endpoint Generated]: client=" << GetClientId() << "; endpoint_id=" << id; return id; } void ClientProxy::Reset() { MutexLock lock(&mutex_); StoppedAdvertising(); StoppedDiscovery(); RemoveAllEndpoints(); ExitStableEndpointIdMode(); } void ClientProxy::StartedAdvertising( const std::string& service_id, Strategy strategy, const ConnectionListener& listener, absl::Span mediums, const std::vector& operation_result_with_mediums, const AdvertisingOptions& advertising_options) { MutexLock lock(&mutex_); LOG(INFO) << "ClientProxy [StartedAdvertising]: client=" << GetClientId(); if (stable_endpoint_id_mode_) { cached_endpoint_id_ = local_endpoint_id_; } else { cached_endpoint_id_.clear(); } CancelClearCachedEndpointIdAlarm(); advertising_info_ = {service_id, listener}; advertising_options_ = advertising_options; const std::vector medium_vector( mediums.begin(), mediums.end()); std::unique_ptr advertising_metadata_params; advertising_metadata_params = AnalyticsRecorder::BuildAdvertisingMetadataParams(); advertising_metadata_params->operation_result_with_mediums = operation_result_with_mediums; analytics_recorder_->OnStartAdvertising(strategy, medium_vector, advertising_metadata_params.get()); } void ClientProxy::StoppedAdvertising() { MutexLock lock(&mutex_); LOG(INFO) << "ClientProxy [StoppedAdvertising]: client=" << GetClientId(); if (IsAdvertising()) { advertising_info_.Clear(); analytics_recorder_->OnStopAdvertising(); local_endpoint_info_.clear(); } // advertising_options_ is purposefully not cleared here. OnSessionComplete(); ExitStableEndpointIdMode(); } bool ClientProxy::IsAdvertising() const { MutexLock lock(&mutex_); return !advertising_info_.IsEmpty(); } std::string ClientProxy::GetAdvertisingServiceId() const { MutexLock lock(&mutex_); return advertising_info_.service_id; } void ClientProxy::StartedListeningForIncomingConnections( absl::string_view service_id, Strategy strategy, v3::ConnectionListener listener, const v3::ConnectionListeningOptions& options) { MutexLock lock(&mutex_); listening_options_ = options; listening_info_ = ListeningInfo{ .service_id = std::string(service_id), .listener = std::move(listener), }; analytics_recorder_->OnStartedIncomingConnectionListening(strategy); } void ClientProxy::StoppedListeningForIncomingConnections() { MutexLock lock(&mutex_); listening_info_.Clear(); analytics_recorder_->OnStoppedIncomingConnectionListening(); } bool ClientProxy::IsListeningForIncomingConnections() const { MutexLock lock(&mutex_); return !listening_info_.IsEmpty(); } std::string ClientProxy::GetListeningForIncomingConnectionsServiceId() const { MutexLock lock(&mutex_); if (IsListeningForIncomingConnections()) { return listening_info_.service_id; } return ""; } ConnectionListener ClientProxy::GetAdvertisingOrIncomingConnectionListener() { if (IsListeningForIncomingConnections()) { ConnectionListener listener = { .initiated_cb = [this](const std::string& endpoint_id, const ConnectionResponseInfo& info) { auto remote_device = v3::ConnectionsDevice( endpoint_id, info.remote_endpoint_info.AsStringView(), {}); this->listening_info_.listener.initiated_cb( remote_device, v3::InitialConnectionInfo{ .authentication_digits = info.authentication_token, .raw_authentication_token = info.raw_authentication_token.string_data(), .is_incoming_connection = info.is_incoming_connection, }); }, .accepted_cb = [this](const std::string& endpoint_id) { auto remote_device = v3::ConnectionsDevice(endpoint_id, "", {}); this->listening_info_.listener.result_cb( remote_device, v3::ConnectionResult{.status = Status{ .value = Status::kSuccess, }}); }, .rejected_cb = [this](const std::string& endpoint_id, Status status) { auto remote_device = v3::ConnectionsDevice(endpoint_id, "", {}); this->listening_info_.listener.result_cb( remote_device, v3::ConnectionResult{.status = status}); }, .disconnected_cb = [this](const std::string& endpoint_id) { auto remote_device = v3::ConnectionsDevice(endpoint_id, "", {}); this->listening_info_.listener.disconnected_cb(remote_device); }, .bandwidth_changed_cb = [this](const std::string& endpoint_id, Medium medium) { auto remote_device = v3::ConnectionsDevice(endpoint_id, "", {}); this->listening_info_.listener.bandwidth_changed_cb( remote_device, v3::BandwidthInfo{.medium = medium}); }, }; return listener; } return advertising_info_.listener; } void ClientProxy::StartedDiscovery( const std::string& service_id, Strategy strategy, DiscoveryListener listener, absl::Span mediums, const std::vector& operation_result_with_mediums, const DiscoveryOptions& discovery_options) { MutexLock lock(&mutex_); discovery_info_ = DiscoveryInfo{service_id, std::move(listener)}; discovery_options_ = discovery_options; const std::vector medium_vector( mediums.begin(), mediums.end()); std::unique_ptr discovery_metadata_params; discovery_metadata_params = AnalyticsRecorder::BuildDiscoveryMetadataParams(); discovery_metadata_params->operation_result_with_mediums = operation_result_with_mediums; analytics_recorder_->OnStartDiscovery(strategy, medium_vector, discovery_metadata_params.get()); } void ClientProxy::StoppedDiscovery() { MutexLock lock(&mutex_); if (IsDiscovering()) { discovered_endpoint_ids_.clear(); discovery_info_.Clear(); analytics_recorder_->OnStopDiscovery(); } // discovery_options_ is purposefully not cleared here. OnSessionComplete(); } bool ClientProxy::IsDiscoveringServiceId(const std::string& service_id) const { MutexLock lock(&mutex_); return IsDiscovering() && service_id == discovery_info_.service_id; } bool ClientProxy::IsDiscovering() const { MutexLock lock(&mutex_); return !discovery_info_.IsEmpty(); } std::string ClientProxy::GetDiscoveryServiceId() const { MutexLock lock(&mutex_); return discovery_info_.service_id; } void ClientProxy::OnEndpointFound( const std::string& service_id, const std::string& endpoint_id, const ByteArray& endpoint_info, location::nearby::proto::connections::Medium medium) { MutexLock lock(&mutex_); LOG(INFO) << "ClientProxy [Endpoint Found]: [enter] id=" << endpoint_id << "; service=" << service_id << "; info=" << absl::BytesToHexString(endpoint_info.data()) << "; medium=" << location::nearby::proto::connections::Medium_Name(medium); if (!IsDiscoveringServiceId(service_id)) { LOG(INFO) << "ClientProxy [Endpoint Found]: Ignoring event for id=" << endpoint_id << " because this client is not discovering."; return; } if (discovered_endpoint_ids_.count(endpoint_id)) { LOG(WARNING) << "ClientProxy [Endpoint Found]: Ignoring event for id=" << endpoint_id << " because this client has already reported this endpoint as found."; return; } discovered_endpoint_ids_.insert(endpoint_id); discovery_info_.listener.endpoint_found_cb(endpoint_id, endpoint_info, service_id); analytics_recorder_->OnEndpointFound(medium); } void ClientProxy::OnEndpointLost(const std::string& service_id, const std::string& endpoint_id) { MutexLock lock(&mutex_); LOG(INFO) << "ClientProxy [Endpoint Lost]: [enter] id=" << endpoint_id << "; service=" << service_id; if (!IsDiscoveringServiceId(service_id)) { LOG(INFO) << "ClientProxy [Endpoint Lost]: Ignoring event for id=" << endpoint_id << " because this client is not discovering."; return; } const auto it = discovered_endpoint_ids_.find(endpoint_id); if (it == discovered_endpoint_ids_.end()) { LOG(WARNING) << "ClientProxy [Endpoint Lost]: Ignoring event for id=" << endpoint_id << " because this client has not yet reported this endpoint as found"; return; } discovered_endpoint_ids_.erase(it); discovery_info_.listener.endpoint_lost_cb(endpoint_id); } void ClientProxy::OnRequestConnection( const Strategy& strategy, const std::string& endpoint_id, const ConnectionOptions& connection_options) { LOG(INFO) << "ClientProxy [RequestConnection]: id=" << endpoint_id; analytics_recorder_->OnRequestConnection(strategy, endpoint_id); } void ClientProxy::OnConnectionInitiated( const std::string& endpoint_id, const ConnectionResponseInfo& info, const ConnectionOptions& connection_options, const ConnectionListener& listener, const std::string& connection_token) { MutexLock lock(&mutex_); // Whether this is incoming or outgoing, the local and remote endpoints both // still need to accept this connection, so set its establishment status to // PENDING. auto result = connections_.emplace( endpoint_id, std::make_pair( Connection{ .is_incoming = info.is_incoming_connection, .connection_listener = listener, .connection_options = connection_options, .connection_token = connection_token, }, PayloadListener{ .payload_cb = [](absl::string_view, Payload) {}, .payload_progress_cb = [](absl::string_view, PayloadProgressInfo) {}, })); // Instead of using structured binding which is nice, but banned // (can not use c++17 features, until chromium does) we unpack manually. auto& pair_iter = result.first; bool inserted = result.second; LOG(INFO) << "ClientProxy [Connection Initiated]: add Connection: client=" << GetClientId() << "; endpoint_id=" << endpoint_id << "; inserted=" << inserted; DCHECK(inserted); const ConnectionPair& item = pair_iter->second; // Notify the client. // // Note: we allow devices to connect to an advertiser even after it stops // advertising, so no need to check IsAdvertising() here. item.first.connection_listener.initiated_cb(endpoint_id, info); if (info.is_incoming_connection) { // Add CancellationFlag for advertisers once encryption succeeds. AddCancellationFlag(endpoint_id); analytics_recorder_->OnConnectionRequestReceived(endpoint_id); } else { analytics_recorder_->OnConnectionRequestSent(endpoint_id); } } void ClientProxy::OnConnectionAccepted(const std::string& endpoint_id) { LOG(INFO) << "ClientProxy [ConnectionAccepted]: id=" << endpoint_id; MutexLock lock(&mutex_); if (!HasPendingConnectionToEndpoint(endpoint_id)) { LOG(INFO) << "ClientProxy [Connection Accepted]: no pending " "connection; endpoint_id=" << endpoint_id; return; } // Notify the client. ConnectionPair* item = LookupConnection(endpoint_id); if (item != nullptr) { item->first.connection_listener.accepted_cb(endpoint_id); item->first.status = Connection::kConnected; } } void ClientProxy::OnConnectionRejected(const std::string& endpoint_id, const Status& status) { LOG(INFO) << "ClientProxy [ConnectionRejected]: id=" << endpoint_id; MutexLock lock(&mutex_); if (!HasPendingConnectionToEndpoint(endpoint_id)) { LOG(INFO) << "ClientProxy [Connection Rejected]: no pending " "connection; endpoint_id=" << endpoint_id; return; } // Notify the client. const ConnectionPair* item = LookupConnection(endpoint_id); if (item != nullptr) { item->first.connection_listener.rejected_cb(endpoint_id, status); OnDisconnected(endpoint_id, false /* notify */); } } void ClientProxy::OnBandwidthChanged(const std::string& endpoint_id, Medium new_medium) { LOG(INFO) << "ClientProxy [BandwidthChanged]: id=" << endpoint_id; MutexLock lock(&mutex_); ConnectionPair* item = LookupConnection(endpoint_id); if (item != nullptr) { item->first.connected_medium = new_medium; item->first.connection_listener.bandwidth_changed_cb(endpoint_id, new_medium); LOG(INFO) << "ClientProxy [reporting onBandwidthChanged]: client=" << GetClientId() << "; endpoint_id=" << endpoint_id; } } void ClientProxy::OnDisconnected(const std::string& endpoint_id, bool notify) { LOG(INFO) << "ClientProxy [OnDisconnected]: id=" << endpoint_id; MutexLock lock(&mutex_); const ConnectionPair* item = LookupConnection(endpoint_id); if (item != nullptr) { if (notify) { item->first.connection_listener.disconnected_cb({endpoint_id}); } connections_.erase(endpoint_id); OnSessionComplete(); } CancelEndpoint(endpoint_id); if (!stable_endpoint_id_mode_ && !HasOngoingConnection()) { ScheduleClearCachedEndpointIdAlarm(); } } bool ClientProxy::ConnectionStatusMatches(const std::string& endpoint_id, Connection::Status status) const { MutexLock lock(&mutex_); const ConnectionPair* item = LookupConnection(endpoint_id); if (item != nullptr) { return item->first.status == status; } return false; } Medium ClientProxy::GetConnectedMedium(const std::string& endpoint_id) const { MutexLock lock(&mutex_); const ConnectionPair* item = LookupConnection(endpoint_id); if (item != nullptr) { return item->first.connected_medium; } return Medium::UNKNOWN_MEDIUM; } BooleanMediumSelector ClientProxy::GetUpgradeMediums( const std::string& endpoint_id) const { MutexLock lock(&mutex_); const ConnectionPair* item = LookupConnection(endpoint_id); if (item != nullptr) { return item->first.connection_options.allowed; } return {}; } bool ClientProxy::Is5GHzSupported(const std::string& endpoint_id) const { MutexLock lock(&mutex_); const ConnectionPair* item = LookupConnection(endpoint_id); if (item != nullptr) { return item->first.connection_options.connection_info.supports_5_ghz; } return false; } std::string ClientProxy::GetBssid(const std::string& endpoint_id) const { MutexLock lock(&mutex_); const ConnectionPair* item = LookupConnection(endpoint_id); if (item != nullptr) { return item->first.connection_options.connection_info.bssid; } return {}; } std::int32_t ClientProxy::GetApFrequency(const std::string& endpoint_id) const { MutexLock lock(&mutex_); const ConnectionPair* item = LookupConnection(endpoint_id); if (item != nullptr) { return item->first.connection_options.connection_info.ap_frequency; } return -1; } bool ClientProxy::IsConnectedToEndpoint(const std::string& endpoint_id) const { return ConnectionStatusMatches(endpoint_id, Connection::kConnected); } std::vector ClientProxy::GetMatchingEndpoints( absl::AnyInvocable pred) const { MutexLock lock(&mutex_); std::vector connected_endpoints; for (const auto& pair : connections_) { const auto& endpoint_id = pair.first; const auto& connection_pair = pair.second; if (pred(connection_pair.first)) { connected_endpoints.push_back(endpoint_id); } } return connected_endpoints; } std::vector ClientProxy::GetPendingConnectedEndpoints() const { return GetMatchingEndpoints([](const Connection& connection) { return connection.status != Connection::kConnected; }); } std::vector ClientProxy::GetConnectedEndpoints() const { return GetMatchingEndpoints([](const Connection& connection) { return connection.status == Connection::kConnected; }); } bool ClientProxy::HasOngoingConnection() const { return !GetPendingConnectedEndpoints().empty() || !GetConnectedEndpoints().empty(); } bool ClientProxy::HasWifiDirectConnection() const { MutexLock lock(&mutex_); for (const auto& entry : connections_) { if (entry.second.first.connected_medium == Medium::WIFI_DIRECT) { LOG(INFO) << "ClientProxy [HasWifiDirectConnection]: true"; return true; } } LOG(INFO) << "ClientProxy [HasWifiDirectConnection]: false"; return false; } bool ClientProxy::HasWifiHotspotConnection() const { MutexLock lock(&mutex_); for (const auto& entry : connections_) { if (entry.second.first.connected_medium == Medium::WIFI_HOTSPOT) { return true; } } return false; } bool ClientProxy::HasWifiAwareConnection() const { MutexLock lock(&mutex_); for (const auto& entry : connections_) { if (entry.second.first.connected_medium == Medium::WIFI_AWARE) { return true; } } return false; } std::string ClientProxy::GetLastLocalEndpointId() const { MutexLock lock(&mutex_); return last_local_endpoint_id_; } void ClientProxy::SetLastLocalEndpointId(absl::string_view endpoint_id) { MutexLock lock(&mutex_); last_local_endpoint_id_ = std::string(endpoint_id); } std::int32_t ClientProxy::GetNumOutgoingConnections() const { return GetMatchingEndpoints([](const Connection& connection) { return connection.status == Connection::kConnected && !connection.is_incoming; }) .size(); } std::int32_t ClientProxy::GetNumIncomingConnections() const { return GetMatchingEndpoints([](const Connection& connection) { return connection.status == Connection::kConnected && connection.is_incoming; }) .size(); } bool ClientProxy::IsIncomingConnection(const std::string& endpoint_id) const { MutexLock lock(&mutex_); const ConnectionPair* item = LookupConnection(endpoint_id); if (item != nullptr && item->first.status == Connection::kConnected) { return item->first.is_incoming; } return false; } bool ClientProxy::IsOutgoingConnection(const std::string& endpoint_id) const { MutexLock lock(&mutex_); const ConnectionPair* item = LookupConnection(endpoint_id); if (item != nullptr && item->first.status == Connection::kConnected) { return !item->first.is_incoming; } return false; } bool ClientProxy::HasPendingConnectionToEndpoint( const std::string& endpoint_id) const { MutexLock lock(&mutex_); const ConnectionPair* item = LookupConnection(endpoint_id); if (item != nullptr) { return item->first.status != Connection::kConnected; } return false; } bool ClientProxy::HasLocalEndpointResponded( const std::string& endpoint_id) const { MutexLock lock(&mutex_); return ConnectionStatusesContains( endpoint_id, static_cast(Connection::kLocalEndpointAccepted | Connection::kLocalEndpointRejected)); } bool ClientProxy::HasRemoteEndpointResponded( const std::string& endpoint_id) const { MutexLock lock(&mutex_); return ConnectionStatusesContains( endpoint_id, static_cast(Connection::kRemoteEndpointAccepted | Connection::kRemoteEndpointRejected)); } void ClientProxy::LocalEndpointAcceptedConnection( const std::string& endpoint_id, PayloadListener listener) { MutexLock lock(&mutex_); if (HasLocalEndpointResponded(endpoint_id)) { LOG(INFO) << "ClientProxy [Local Accepted]: local endpoint has responded; id=" << endpoint_id; return; } AppendConnectionStatus(endpoint_id, Connection::kLocalEndpointAccepted); LOG(INFO) << "ClientProxy [Local Accepted]: id=" << endpoint_id; ConnectionPair* item = LookupConnection(endpoint_id); if (item != nullptr) { item->second = std::move(listener); } analytics_recorder_->OnLocalEndpointAccepted(endpoint_id); } void ClientProxy::LocalEndpointRejectedConnection( const std::string& endpoint_id) { MutexLock lock(&mutex_); if (HasLocalEndpointResponded(endpoint_id)) { LOG(INFO) << "ClientProxy [Local Rejected]: local endpoint has responded; id=" << endpoint_id; return; } AppendConnectionStatus(endpoint_id, Connection::kLocalEndpointRejected); analytics_recorder_->OnLocalEndpointRejected(endpoint_id); } void ClientProxy::RemoteEndpointAcceptedConnection( const std::string& endpoint_id) { MutexLock lock(&mutex_); if (HasRemoteEndpointResponded(endpoint_id)) { LOG(INFO) << "ClientProxy [Remote Accepted]: remote endpoint has responded; id=" << endpoint_id; return; } AppendConnectionStatus(endpoint_id, Connection::kRemoteEndpointAccepted); analytics_recorder_->OnRemoteEndpointAccepted(endpoint_id); } void ClientProxy::RemoteEndpointRejectedConnection( const std::string& endpoint_id) { MutexLock lock(&mutex_); if (HasRemoteEndpointResponded(endpoint_id)) { LOG(INFO) << "ClientProxy [Remote Rejected]: remote endpoint has responded; id=" << endpoint_id; return; } AppendConnectionStatus(endpoint_id, Connection::kRemoteEndpointRejected); analytics_recorder_->OnRemoteEndpointRejected(endpoint_id); } bool ClientProxy::IsConnectionAccepted(const std::string& endpoint_id) const { MutexLock lock(&mutex_); return ConnectionStatusesContains(endpoint_id, Connection::kLocalEndpointAccepted) && ConnectionStatusesContains(endpoint_id, Connection::kRemoteEndpointAccepted); } bool ClientProxy::IsConnectionRejected(const std::string& endpoint_id) const { MutexLock lock(&mutex_); return ConnectionStatusesContains( endpoint_id, static_cast(Connection::kLocalEndpointRejected | Connection::kRemoteEndpointRejected)); } bool ClientProxy::LocalConnectionIsAccepted(std::string endpoint_id) const { MutexLock lock(&mutex_); return ConnectionStatusesContains( endpoint_id, ClientProxy::Connection::kLocalEndpointAccepted); } bool ClientProxy::RemoteConnectionIsAccepted(std::string endpoint_id) const { MutexLock lock(&mutex_); return ConnectionStatusesContains( endpoint_id, ClientProxy::Connection::kRemoteEndpointAccepted); } bool ClientProxy::AutoUpgradeBandwidth() const { MutexLock lock(&mutex_); bool result = false; if (IsAdvertising() && (GetAdvertisingOptions().strategy.IsNone() || GetAdvertisingOptions().auto_upgrade_bandwidth)) { result |= true; } if (IsListeningForIncomingConnections() && (GetListeningOptions().strategy.IsNone() || GetListeningOptions().auto_upgrade_bandwidth)) { result |= true; } return result; } bool ClientProxy::ShouldEnforceTopologyConstraints() const { MutexLock lock(&mutex_); bool result = false; if (IsAdvertising() && (GetAdvertisingOptions().strategy.IsNone() || GetAdvertisingOptions().enforce_topology_constraints)) { result |= true; } if (IsListeningForIncomingConnections() && (GetListeningOptions().strategy.IsNone() || GetListeningOptions().enforce_topology_constraints)) { result |= true; } return result; } void ClientProxy::AddCancellationFlag(const std::string& endpoint_id) { // Don't insert the CancellationFlag to the map if feature flag is disabled. if (!FeatureFlags::GetInstance().GetFlags().enable_cancellation_flag) { return; } MutexLock lock(&mutex_); auto item = cancellation_flags_.find(endpoint_id); if (item != cancellation_flags_.end()) { // A new flag may be added to the map with the same endpoint, even if a // flag already in the map has already been cancelled, when an endpoint // is being reused (for example, the case when users use NS to share/receive // a file, then cancel in the middle because the wrong file was selected // and then re-do right after). The flag needs to be uncancelled in order // to support a new attempt with the same endpoint. if (item->second->Cancelled()) { item->second->Uncancel(); } return; } cancellation_flags_.emplace(endpoint_id, std::make_shared()); } std::shared_ptr ClientProxy::GetCancellationFlag( const std::string& endpoint_id) { MutexLock lock(&mutex_); const auto item = cancellation_flags_.find(endpoint_id); if (item == cancellation_flags_.end()) { return default_cancellation_flag_; } return item->second; } void ClientProxy::CancelEndpoint(const std::string& endpoint_id) { MutexLock lock(&mutex_); const auto item = cancellation_flags_.find(endpoint_id); if (item != cancellation_flags_.end()) { item->second->Cancel(); } } const OsInfo& ClientProxy::GetLocalOsInfo() const { return local_os_info_; } std::optional ClientProxy::GetRemoteOsInfo( absl::string_view endpoint_id) const { MutexLock lock(&mutex_); const ConnectionPair* item = LookupConnection(endpoint_id); if (item != nullptr) { return item->first.os_info; } return std::nullopt; } void ClientProxy::SetLocalOsType( const location::nearby::connections::OsInfo::OsType& os_type) { MutexLock lock(&mutex_); local_os_info_.set_type(os_type); } void ClientProxy::SetRemoteOsInfo(absl::string_view endpoint_id, const OsInfo& remote_os_info) { MutexLock lock(&mutex_); ConnectionPair* item = LookupConnection(endpoint_id); if (item != nullptr) { item->first.os_info.emplace(remote_os_info); } } void ClientProxy::SetRemoteDeviceName(absl::string_view endpoint_id, absl::string_view device_name) { MutexLock lock(&mutex_); ConnectionPair* item = LookupConnection(endpoint_id); if (item != nullptr) { item->first.device_name = std::string(device_name); LOG(INFO) << "ClientProxy [SetRemoteDeviceName]: " << device_name; } } std::string ClientProxy::GetRemoteDeviceName( absl::string_view endpoint_id) const { MutexLock lock(&mutex_); const ConnectionPair* item = LookupConnection(endpoint_id); if (item != nullptr) { return item->first.device_name; } return ""; } std::optional ClientProxy::GetRemoteSafeToDisconnectVersion( absl::string_view endpoint_id) const { MutexLock lock(&mutex_); const ConnectionPair* item = LookupConnection(endpoint_id); if (item != nullptr) { return item->first.safe_to_disconnect_version; } return std::nullopt; } void ClientProxy::SetRemoteSafeToDisconnectVersion( absl::string_view endpoint_id, const std::int32_t& safe_to_disconnect_version) { MutexLock lock(&mutex_); ConnectionPair* item = LookupConnection(endpoint_id); if (item != nullptr) { item->first.safe_to_disconnect_version = safe_to_disconnect_version; } } bool ClientProxy::IsSafeToDisconnectEnabled(absl::string_view endpoint_id) { return IsSupportSafeToDisconnect() && GetRemoteSafeToDisconnectVersion(endpoint_id).has_value() && (GetRemoteSafeToDisconnectVersion(endpoint_id) >= FeatureFlags::GetInstance() .GetFlags() .min_nc_version_supports_safe_to_disconnect); } bool ClientProxy::IsPayloadReceivedAckEnabled(absl::string_view endpoint_id) { return IsSupportSafeToDisconnect() && GetRemoteSafeToDisconnectVersion(endpoint_id).has_value() && (GetRemoteSafeToDisconnectVersion(endpoint_id) >= FeatureFlags::GetInstance() .GetFlags() .min_nc_version_supports_payload_received_ack); } void ClientProxy::CancelAllEndpoints() { MutexLock lock(&mutex_); for (const auto& item : cancellation_flags_) { std::shared_ptr cancellation_flag = item.second; if (cancellation_flag->Cancelled()) { continue; } cancellation_flag->Cancel(); } } void ClientProxy::OnPayload(const std::string& endpoint_id, Payload payload) { MutexLock lock(&mutex_); if (IsConnectedToEndpoint(endpoint_id)) { const std::pair* item = LookupConnection(endpoint_id); if (item != nullptr) { LOG(INFO) << "ClientProxy [reporting onPayloadReceived]: client=" << GetClientId() << "; endpoint_id=" << endpoint_id << " ; payload {id:" << payload.GetId() << ", type:" << payload.GetType() << "}"; item->second.payload_cb(endpoint_id, std::move(payload)); } } } const ClientProxy::ConnectionPair* ClientProxy::LookupConnection( absl::string_view endpoint_id) const { auto item = connections_.find(endpoint_id); return item != connections_.end() ? &item->second : nullptr; } ClientProxy::ConnectionPair* ClientProxy::LookupConnection( absl::string_view endpoint_id) { auto item = connections_.find(endpoint_id); return item != connections_.end() ? &item->second : nullptr; } void ClientProxy::OnPayloadProgress(const std::string& endpoint_id, const PayloadProgressInfo& info) { MutexLock lock(&mutex_); if (IsConnectedToEndpoint(endpoint_id)) { std::pair* item = LookupConnection(endpoint_id); if (item != nullptr) { item->second.payload_progress_cb(endpoint_id, info); if (info.status == PayloadProgressInfo::Status::kInProgress) { VLOG(1) << "ClientProxy [reporting onPayloadProgress]: client=" << GetClientId() << "; endpoint_id=" << endpoint_id << "; payload_id=" << info.payload_id << ", payload_status=" << ToString(info.status); } else { LOG(INFO) << "ClientProxy [reporting onPayloadProgress]: client=" << GetClientId() << "; endpoint_id=" << endpoint_id << "; payload_id=" << info.payload_id << ", payload_status=" << ToString(info.status); } } } } void ClientProxy::RemoveAllEndpoints() { MutexLock lock(&mutex_); // Note: we may want to notify the client of onDisconnected() for each // endpoint, in the case when this is called from stopAllEndpoints(). For now, // just remove without notifying. connections_.clear(); cancellation_flags_.clear(); bluetooth_mac_addresses_.clear(); OnSessionComplete(); } void ClientProxy::ResetLocalEndpointId() { MutexLock lock(&mutex_); if (HasOngoingConnection()) { return; } if (!local_endpoint_id_.empty()) { last_local_endpoint_id_ = local_endpoint_id_; local_endpoint_id_.clear(); } } void ClientProxy::OnSessionComplete() { MutexLock lock(&mutex_); if (connections_.empty() && !IsAdvertising()) { if (!local_endpoint_id_.empty()) { last_local_endpoint_id_ = local_endpoint_id_; } local_endpoint_id_.clear(); analytics_recorder_->LogSession(); analytics_recorder_->LogStartSession(); } } bool ClientProxy::ConnectionStatusesContains( const std::string& endpoint_id, Connection::Status status_to_match) const { const ConnectionPair* item = LookupConnection(endpoint_id); if (item != nullptr) { return (item->first.status & status_to_match) != 0; } return false; } void ClientProxy::AppendConnectionStatus(const std::string& endpoint_id, Connection::Status status_to_append) { ConnectionPair* item = LookupConnection(endpoint_id); if (item != nullptr) { item->first.status = static_cast(item->first.status | status_to_append); } } AdvertisingOptions ClientProxy::GetAdvertisingOptions() const { MutexLock lock(&mutex_); return advertising_options_; } DiscoveryOptions ClientProxy::GetDiscoveryOptions() const { MutexLock lock(&mutex_); return discovery_options_; } v3::ConnectionListeningOptions ClientProxy::GetListeningOptions() const { MutexLock lock(&mutex_); return listening_options_; } void ClientProxy::EnterStableEndpointIdMode() { MutexLock lock(&mutex_); VLOG(1) << "ClientProxy [EnterStableEndpointIdMode]: client=" << GetClientId(); stable_endpoint_id_mode_ = true; if (!IsAdvertising() && !IsDiscovering() && !HasOngoingConnection()) { ResetLocalEndpointId(); } } void ClientProxy::ExitStableEndpointIdMode() { MutexLock lock(&mutex_); VLOG(1) << "ClientProxy [ExitStableEndpointIdMode]: client=" << GetClientId(); stable_endpoint_id_mode_ = false; ResetLocalEndpointId(); ScheduleClearCachedEndpointIdAlarm(); } void ClientProxy::ScheduleClearCachedEndpointIdAlarm() { CancelClearCachedEndpointIdAlarm(); if (cached_endpoint_id_.empty()) { VLOG(1) << "ClientProxy [There is no cached local high power " "advertising endpoint Id]: client=" << GetClientId(); return; } if (IsAdvertising() || IsDiscovering() || HasOngoingConnection()) { VLOG(1) << "ClientProxy [Handle clearing cached endpoint ID " "during disconnection]: client=" << GetClientId(); return; } // Schedule to clear cache high visibility mode advertisement endpoint id in // 30s. LOG(INFO) << "ClientProxy [High Visibility Mode Adv, Schedule to " "Clear Cache EndpointId]: client=" << GetClientId() << "; cached_endpoint_id_=" << cached_endpoint_id_; cached_endpoint_id_alarm_ = std::make_unique( "clear_high_power_endpoint_id_cache", [this]() { ClearCachedLocalEndpointId(); }, kHighPowerAdvertisementEndpointIdCacheTimeout, &single_thread_executor_); } void ClientProxy::CancelClearCachedEndpointIdAlarm() { if (cached_endpoint_id_alarm_ && cached_endpoint_id_alarm_->IsValid()) { cached_endpoint_id_alarm_->Cancel(); cached_endpoint_id_alarm_.reset(); } } void ClientProxy::ClearCachedLocalEndpointId() { MutexLock lock(&mutex_); LOG(INFO) << "ClientProxy [Cleared cached local endpoint Id.]: client=" << GetClientId() << "; cached_endpoint_id_=" << cached_endpoint_id_; cached_endpoint_id_.clear(); } OsInfo::OsType ClientProxy::OSNameToOsInfoType(api::OSName osName) { switch (osName) { case api::OSName::kLinux: return OsInfo::LINUX; case api::OSName::kWindows: return OsInfo::WINDOWS; case api::OSName::kApple: return OsInfo::APPLE; case api::OSName::kChromeOS: return OsInfo::CHROME_OS; case api::OSName::kAndroid: return OsInfo::ANDROID; } } void ClientProxy::SetRemoteMultiplexSocketBitmask( absl::string_view endpoint_id, int remote_multiplex_socket_bitmask) { MutexLock lock(&mutex_); ConnectionPair* item = LookupConnection(endpoint_id); if (item != nullptr) { item->first.remote_multiplex_socket_bitmask = remote_multiplex_socket_bitmask; LOG(INFO) << "ClientProxy [SetRemoteMultiplexSocketBitmask]: " << remote_multiplex_socket_bitmask; } } bool ClientProxy::GetWebRtcNonCellular() { MutexLock lock(&mutex_); return webrtc_non_cellular_; } void ClientProxy::SetWebRtcNonCellular(bool webrtc_non_cellular) { MutexLock lock(&mutex_); VLOG(1) << "ClientProxy: client=" << GetClientId() << (webrtc_non_cellular ? " disallow" : " allow") << " to use mobile data."; webrtc_non_cellular_ = webrtc_non_cellular; } bool ClientProxy::IsDctEnabled() const { MutexLock lock(&mutex_); return is_dct_enabled_; } uint8_t ClientProxy::GetDctDedup() const { MutexLock lock(&mutex_); return dct_dedup_; } void ClientProxy::UpdateDctDeviceName(absl::string_view device_name) { MutexLock lock(&mutex_); if (!dct_device_name_.empty() && dct_device_name_ != device_name) { // Need to update dedup value if device name is changed. absl::BitGen bitgen; dct_dedup_ = absl::Uniform(bitgen, 0, 1 << 7); } dct_device_name_ = device_name; // The DCT endpoint ID should be derived from device name and dedup value. std::optional dct_endpoint_id = advertisements::ble::DctAdvertisement::GenerateEndpointId( dct_dedup_, dct_device_name_); if (dct_endpoint_id.has_value()) { dct_endpoint_id_ = *dct_endpoint_id; } else { dct_endpoint_id_.clear(); } } std::optional ClientProxy::GetMediumRole( absl::string_view endpoint_id) const { MutexLock lock(&mutex_); const ConnectionPair* item = LookupConnection(endpoint_id); if (item != nullptr) { return item->first.connection_options.connection_info.medium_role; } return std::nullopt; } location::nearby::connections::MediumRole ClientProxy::GetLocalMediumRole( const ClientProxy::MediumsAvailability& mediums_availability) const { location::nearby::connections::MediumRole medium_role; if (!NearbyFlags::GetInstance().GetBoolFlag( config_package_nearby::nearby_connections_feature:: kEnableDynamicRoleSwitch)) { return medium_role; } if (GetLocalOsInfo().type() == OsInfo::APPLE) { medium_role.set_support_awdl_publisher(true); medium_role.set_support_awdl_subscriber(true); // Apple always supports wifi hotspot client role since they can always // join a hotspot. medium_role.set_support_wifi_hotspot_client(true); return medium_role; } medium_role.set_support_wifi_direct_group_owner( mediums_availability.is_wifi_direct_go_available && !IsUsingP2pMedium()); medium_role.set_support_wifi_direct_group_client( mediums_availability.is_wifi_direct_gc_available); medium_role.set_support_wifi_hotspot_host( mediums_availability.is_wifi_hotspot_ap_available && !IsUsingP2pMedium()); medium_role.set_support_wifi_hotspot_client( mediums_availability.is_wifi_hotspot_client_available); LOG(INFO) << "medium_role: " << medium_role.DebugString(); return medium_role; } bool ClientProxy::IsUsingP2pMedium() const { return HasWifiDirectConnection() || HasWifiHotspotConnection() || HasWifiAwareConnection(); } std::optional ClientProxy::GetEndpointIdForDct() const { MutexLock lock(&mutex_); if (dct_endpoint_id_.empty()) { return std::nullopt; } return dct_endpoint_id_; } #ifdef NEARBY_CHROMIUM void ClientProxy::InitializePreferencesManager() { // This method is not currently used by Chromium. NOTREACHED(); } #else void ClientProxy::InitializePreferencesManager() { LOG(INFO) << "ClientProxy [InitializePreferencesManager]: client=" << GetClientId(); std::unique_ptr device_info_ = nearby::api::ImplementationPlatform::CreateDeviceInfo(); FilePath preferences_path = device_info_->GetLocalAppDataPath(FilePath(kPreferencesFilePath)); if (!Files::FileExists(preferences_path)) { Files::CreateDirectories(preferences_path); } preferences_manager_ = api::ImplementationPlatform::CreatePreferencesManager( preferences_path.ToString()); if (preferences_manager_ == nullptr) { LOG(ERROR) << "ClientProxy [Failed to initialize preferences manager]: " "client=" << GetClientId(); } } #endif void ClientProxy::SaveClientInfoToPreferences() { MutexLock lock(&mutex_); if (preferences_manager_ == nullptr) { return; } if (advertising_info_.IsEmpty()) { preferences_manager_->Remove(kAdvertisingEndpointId); preferences_manager_->Remove(kAdvertisingTimestamp); return; } preferences_manager_->SetString(kAdvertisingEndpointId, local_endpoint_id_); preferences_manager_->SetTime(kAdvertisingTimestamp, SystemClock::ElapsedRealtime()); LOG(INFO) << "ClientProxy [SaveClientInfoToPreferences]: client=" << GetClientId() << "; local_endpoint_id_=" << local_endpoint_id_; } void ClientProxy::LoadClientInfoFromPreferences() { MutexLock lock(&mutex_); if (preferences_manager_ == nullptr) { return; } absl::Time last_advertising_time = preferences_manager_->GetTime( kAdvertisingTimestamp, absl::InfinitePast()); if (SystemClock::ElapsedRealtime() - last_advertising_time < kAdvertisingKeepAliveDuration) { std::string endpoint_id = preferences_manager_->GetString(kAdvertisingEndpointId, ""); if (!endpoint_id.empty() && endpoint_id.length() == kEndpointIdLength) { bool is_valid_endpoint_id = true; for (const auto& c : endpoint_id) { if (!((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))) { is_valid_endpoint_id = false; break; } } if (is_valid_endpoint_id) { local_endpoint_id_ = endpoint_id; cached_endpoint_id_ = local_endpoint_id_; LOG(INFO) << "ClientProxy [LoadClientInfoFromPreferences]: client=" << GetClientId() << "; local_endpoint_id_=" << local_endpoint_id_; } } } preferences_manager_->Remove(kAdvertisingEndpointId); preferences_manager_->Remove(kAdvertisingTimestamp); } std::string ClientProxy::ToString(PayloadProgressInfo::Status status) const { switch (status) { case PayloadProgressInfo::Status::kSuccess: return std::string("Success"); case PayloadProgressInfo::Status::kFailure: return std::string("Failure"); case PayloadProgressInfo::Status::kInProgress: return std::string("In Progress"); case PayloadProgressInfo::Status::kCanceled: return std::string("Cancelled"); } } std::string ClientProxy::Dump() { std::stringstream sstream; sstream << "Nearby Connections State" << std::endl; sstream << " Client ID: " << GetClientId() << std::endl; sstream << " Local Endpoint ID: " << GetLocalEndpointId() << std::endl; sstream << std::boolalpha; sstream << " Is Advertising: " << IsAdvertising() << std::endl; sstream << " Is Discovering: " << IsDiscovering() << std::endl; sstream << std::noboolalpha; sstream << " Advertising Service ID: " << GetAdvertisingServiceId() << std::endl; sstream << " Discovery Service ID: " << GetDiscoveryServiceId() << std::endl; sstream << " Connections: " << std::endl; for (auto it = connections_.begin(); it != connections_.end(); ++it) { // TODO(deling): write Connection.ToString() sstream << " " << it->first << " :(connection token) " << it->second.first.connection_token << ", (remote os type) " << (it->second.first.os_info.has_value() ? location::nearby::connections::OsInfo::OsType_Name( it->second.first.os_info->type()) : "unknown") << ", (remote device name) " << it->second.first.device_name << std::endl; } sstream << " Discovered endpoint IDs: " << std::endl; for (auto it = discovered_endpoint_ids_.begin(); it != discovered_endpoint_ids_.end(); ++it) { sstream << " " << *it << std::endl; } return sstream.str(); } } // namespace nearby::connections