// Copyright 2020 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/bwu_manager.h" #include #include #include #include #include #include #include "absl/container/flat_hash_map.h" #include "absl/functional/bind_front.h" #include "absl/strings/str_cat.h" #include "absl/time/time.h" #include "connections/implementation/analytics/connection_attempt_metadata_params.h" #include "connections/implementation/awdl_bwu_handler.h" #include "connections/implementation/bluetooth_bwu_handler.h" #include "connections/implementation/bwu_handler.h" #include "connections/implementation/client_proxy.h" #include "connections/implementation/endpoint_channel.h" #include "connections/implementation/endpoint_channel_manager.h" #include "connections/implementation/endpoint_manager.h" #include "connections/implementation/flags/nearby_connections_feature_flags.h" #include "connections/implementation/mediums/mediums.h" #include "connections/implementation/offline_frames.h" #include "connections/implementation/service_id_constants.h" #include "internal/flags/nearby_flags.h" #ifdef NO_WEBRTC #include "connections/implementation/webrtc_bwu_handler_stub.h" #else #include "connections/implementation/webrtc_bwu_handler.h" #endif #include "connections/implementation/wifi_direct_bwu_handler.h" #include "connections/implementation/wifi_hotspot_bwu_handler.h" #include "connections/implementation/wifi_lan_bwu_handler.h" #include "connections/medium_selector.h" #include "internal/platform/byte_array.h" #include "internal/platform/cancelable_alarm.h" #include "internal/platform/count_down_latch.h" #include "internal/platform/expected.h" #include "internal/platform/feature_flags.h" #include "internal/platform/implementation/system_clock.h" #include "internal/platform/logging.h" #include "internal/platform/runnable.h" #include "proto/connections_enums.pb.h" namespace nearby { namespace connections { namespace { using ::location::nearby::connections::BandwidthUpgradeNegotiationFrame; using ::location::nearby::connections::MediumRole; using ::location::nearby::connections::OfflineFrame; using ::location::nearby::connections::OsInfo; using ::location::nearby::connections::V1Frame; using ::location::nearby::proto::connections::BandwidthUpgradeErrorStage; using ::location::nearby::proto::connections::BandwidthUpgradeResult; using ::location::nearby::proto::connections::ConnectionAttemptResult; using ::location::nearby::proto::connections::ConnectionAttemptType; using ::location::nearby::proto::connections::DisconnectionReason; using ::location::nearby::proto::connections::OperationResultCode; } // namespace BwuManager::BwuManager( Mediums& mediums, EndpointManager& endpoint_manager, EndpointChannelManager& channel_manager, absl::flat_hash_map> handlers, Config config) : config_(config), mediums_(&mediums), endpoint_manager_(&endpoint_manager), channel_manager_(&channel_manager) { if (config_.bandwidth_upgrade_retry_delay == absl::ZeroDuration()) { if (FeatureFlags::GetInstance().GetFlags().use_exp_backoff_in_bwu_retry) { config_.bandwidth_upgrade_retry_delay = FeatureFlags::GetInstance() .GetFlags() .bwu_retry_exp_backoff_initial_delay; } else { config_.bandwidth_upgrade_retry_delay = absl::Seconds(5); } } if (config_.bandwidth_upgrade_retry_max_delay == absl::ZeroDuration()) { if (FeatureFlags::GetInstance().GetFlags().use_exp_backoff_in_bwu_retry) { config_.bandwidth_upgrade_retry_max_delay = FeatureFlags::GetInstance() .GetFlags() .bwu_retry_exp_backoff_maximum_delay; } else { config_.bandwidth_upgrade_retry_max_delay = absl::Seconds(10); } } if (config_.allow_upgrade_to.All(false)) { config_.allow_upgrade_to.web_rtc = true; config_.allow_upgrade_to.wifi_direct = true; config_.allow_upgrade_to.wifi_lan = true; config_.allow_upgrade_to.wifi_hotspot = true; if (NearbyFlags::GetInstance().GetBoolFlag( config_package_nearby::nearby_connections_feature::kEnableAwdl)) { config_.allow_upgrade_to.awdl = true; } } if (!handlers.empty()) { handlers_ = std::move(handlers); } else { InitBwuHandlers(); } // Register the offline frame processor. endpoint_manager_->RegisterFrameProcessor( V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION, this); } BwuManager::~BwuManager() { LOG(INFO) << "BwuManager going down"; Shutdown(); } void BwuManager::InitBwuHandlers() { // Register the supported concrete BwuMedium implementations. if (config_.allow_upgrade_to.awdl) { handlers_.emplace( Medium::AWDL, std::make_unique( *mediums_, absl::bind_front(&BwuManager::OnIncomingConnection, this))); } if (config_.allow_upgrade_to.wifi_hotspot) { handlers_.emplace( Medium::WIFI_HOTSPOT, std::make_unique( *mediums_, absl::bind_front(&BwuManager::OnIncomingConnection, this))); } if (config_.allow_upgrade_to.wifi_direct) { handlers_.emplace( Medium::WIFI_DIRECT, std::make_unique( *mediums_, absl::bind_front(&BwuManager::OnIncomingConnection, this))); } if (config_.allow_upgrade_to.wifi_lan) { handlers_.emplace( Medium::WIFI_LAN, std::make_unique( *mediums_, absl::bind_front(&BwuManager::OnIncomingConnection, this))); } if (config_.allow_upgrade_to.web_rtc) { handlers_.emplace( Medium::WEB_RTC, std::make_unique( *mediums_, absl::bind_front(&BwuManager::OnIncomingConnection, this))); } if (config_.allow_upgrade_to.bluetooth) { handlers_.emplace( Medium::BLUETOOTH, std::make_unique( *mediums_, absl::bind_front(&BwuManager::OnIncomingConnection, this))); } } void BwuManager::Shutdown() { LOG(INFO) << "Initiating shutdown of BwuManager."; endpoint_manager_->UnregisterFrameProcessor( V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION, this); // Stop all the ongoing Runnables (as gracefully as possible). ShutdownExecutors(); // After worker threads are down we became exclusive owners of data and // may access it from current thread. for (auto& item : previous_endpoint_channels_) { EndpointChannel* channel = item.second.get(); if (!channel) continue; channel->Close(DisconnectionReason::SHUTDOWN); } CancelAllRetryUpgradeAlarms(); medium_ = Medium::UNKNOWN_MEDIUM; endpoint_id_to_bwu_medium_.clear(); for (auto& medium_handler_pair : handlers_) { assert(medium_handler_pair.second); medium_handler_pair.second->RevertInitiatorState(); } handlers_.clear(); LOG(INFO) << "BwuManager has shut down."; } void BwuManager::MakeSingleThreadedForTesting() { is_single_threaded_for_testing_ = true; } void BwuManager::InvokeOnIncomingConnectionForTesting( ClientProxy* client, std::unique_ptr mutable_connection) { OnIncomingConnection(client, std::move(mutable_connection)); } void BwuManager::ShutdownExecutors() { alarm_executor_.Shutdown(); serial_executor_.Shutdown(); } void BwuManager::InitiateBwuForEndpoint(ClientProxy* client, const std::string& endpoint_id, Medium new_medium) { // Select the best medium if one is not provided. Medium proposed_medium = new_medium == Medium::UNKNOWN_MEDIUM ? ChooseBestUpgradeMedium( endpoint_id, client->GetUpgradeMediums(endpoint_id).GetMediums(true)) : new_medium; RunOnBwuManagerThread("bwu-init", [this, client, endpoint_id, proposed_medium]() { LOG(INFO) << "InitiateBwuForEndpoint for endpoint " << endpoint_id << " with medium " << location::nearby::proto::connections::Medium_Name( proposed_medium); if (channel_manager_->isWifiLanConnected() && (proposed_medium == Medium::WIFI_HOTSPOT)) { LOG(INFO) << "Some endpoint is using WIFI_LAN and proposed upgrade medium is " "WIFI_HOTSPOT. Don't do the BWU because STA connecting to " "WIFI_HOTSPOT will destroy WIFI_LAN which will lead BWU fail and " "other endpoint connection fail"; return; } SetBwuMediumForEndpoint(endpoint_id, proposed_medium); BwuHandler* handler = GetHandlerForMedium(proposed_medium); if (!handler) { LOG(ERROR) << "BwuManager cannot initiate bandwidth upgrade for endpoint " << endpoint_id << " because the current BandwidthUpgradeMedium cannot be deduced."; return; } if (in_progress_upgrades_.contains(endpoint_id)) { LOG(INFO) << "BwuManager is ignoring bandwidth upgrade for endpoint " << endpoint_id << " because we're already upgrading bandwidth for that endpoint."; return; } CancelRetryUpgradeAlarm(endpoint_id); auto channel = channel_manager_->GetChannelForEndpoint(endpoint_id); Medium channel_medium = channel ? channel->GetMedium() : Medium::UNKNOWN_MEDIUM; // Ignore requests where the medium we're upgrading to is the medium we're // already connected over. This can happen now that Bluetooth is both an // advertising medium and a potential bandwidth upgrade, and will continue // to be possible as we add other new advertising mediums like mDNS (WiFi // LAN). Very specifically, this happens now when a device uses // P2P_CLUSTER, connects over Bluetooth, and is not connected to LAN. // Bluetooth is the best medium, and we attempt to upgrade from Bluetooth // to Bluetooth. if (proposed_medium == channel_medium) { LOG(INFO) << "BwuManager ignoring the upgrade for endpoint " << endpoint_id << " because it is already connected over medium " << location::nearby::proto::connections::Medium_Name( proposed_medium); return; } client->GetAnalyticsRecorder().OnBandwidthUpgradeStarted( endpoint_id, channel_medium, proposed_medium, location::nearby::proto::connections::INCOMING, client->GetConnectionToken(endpoint_id)); if (channel == nullptr) { LOG(INFO) << "BwuManager couldn't complete the upgrade for endpoint " << endpoint_id << " because it couldn't find an existing EndpointChannel for it."; client->GetAnalyticsRecorder().OnBandwidthUpgradeError( endpoint_id, BandwidthUpgradeResult::CHANNEL_ERROR, BandwidthUpgradeErrorStage::NETWORK_AVAILABLE, OperationResultCode::NEARBY_GENERIC_OLD_ENDPOINT_CHANNEL_NULL); return; } if (is_dynamic_role_switch_enabled_ && client->GetMediumRole(endpoint_id).has_value()) { MediumRole medium_role = client->GetMediumRole(endpoint_id).value(); if (NeedToSwitchRole(client, endpoint_id, proposed_medium, medium_role)) { if (!channel ->Write(parser::ForBwuPathRequest( client->GetUpgradeMediums(endpoint_id).GetMediums(true), medium_role)) .Ok()) { LOG(ERROR) << "BwuManager couldn't complete the upgrade for endpoint " << endpoint_id << " to medium " << location::nearby::proto::connections::Medium_Name( proposed_medium) << " because it failed to write the " "BWU_NEGOTIATION.UPGRADE_PATH_REQUEST OfflineFrame."; client->GetAnalyticsRecorder().OnBandwidthUpgradeError( endpoint_id, BandwidthUpgradeResult::RESULT_IO_ERROR, BandwidthUpgradeErrorStage::NETWORK_AVAILABLE, OperationResultCode:: CONNECTIVITY_GENERIC_WRITING_CHANNEL_IO_ERROR); return; } LOG(INFO) << "BwuManager successfully wrote the " "BANDWIDTH_UPGRADE_NEGOTIATION.UPGRADE_PATH_REQUEST " "OfflineFrame while upgrading endpoint " << endpoint_id << " to medium " << location::nearby::proto::connections::Medium_Name( proposed_medium); return; } } std::string service_id = channel->GetServiceId(); ByteArray bytes = handler->InitializeUpgradedMediumForEndpoint( client, service_id, endpoint_id); // Because we grab the endpointChannel first thing, it is possible the // endpointChannel is stale by the time we attempt to write over it. if (bytes.Empty()) { LOG(ERROR) << "BwuManager couldn't complete the upgrade for endpoint " << endpoint_id << " to medium " << location::nearby::proto::connections::Medium_Name( proposed_medium) << " because it failed to initialize the " "BWU_NEGOTIATION.UPGRADE_PATH_AVAILABLE OfflineFrame."; UpgradePathInfo info; info.set_medium(parser::MediumToUpgradePathInfoMedium(proposed_medium)); ProcessUpgradeFailureEvent( client, endpoint_id, info, BandwidthUpgradeResult::MEDIUM_ERROR, /*record_analytic=*/false, OperationResultCode::CONNECTIVITY_GENERIC_WRITING_CHANNEL_IO_ERROR); client->GetAnalyticsRecorder().OnBandwidthUpgradeError( endpoint_id, BandwidthUpgradeResult::MEDIUM_ERROR, BandwidthUpgradeErrorStage::NETWORK_AVAILABLE, OperationResultCode::CONNECTIVITY_GENERIC_WRITING_CHANNEL_IO_ERROR); return; } if (!channel->Write(bytes).Ok()) { LOG(ERROR) << "BwuManager couldn't complete the upgrade for endpoint " << endpoint_id << " to medium " << location::nearby::proto::connections::Medium_Name( proposed_medium) << " because it failed to write the " "BWU_NEGOTIATION.UPGRADE_PATH_AVAILABLE OfflineFrame."; UpgradePathInfo info; info.set_medium(parser::MediumToUpgradePathInfoMedium(proposed_medium)); ProcessUpgradeFailureEvent( client, endpoint_id, info, BandwidthUpgradeResult::RESULT_IO_ERROR, /*record_analytic=*/false, OperationResultCode::CONNECTIVITY_GENERIC_WRITING_CHANNEL_IO_ERROR); client->GetAnalyticsRecorder().OnBandwidthUpgradeError( endpoint_id, BandwidthUpgradeResult::RESULT_IO_ERROR, BandwidthUpgradeErrorStage::NETWORK_AVAILABLE, OperationResultCode::CONNECTIVITY_GENERIC_WRITING_CHANNEL_IO_ERROR); return; } LOG(INFO) << "BwuManager successfully wrote the " "BWU_NEGOTIATION.UPGRADE_PATH_AVAILABLE OfflineFrame while " "upgrading endpoint " << endpoint_id << " to medium " << location::nearby::proto::connections::Medium_Name( proposed_medium); in_progress_upgrades_.emplace(endpoint_id, client); }); } void BwuManager::OnIncomingFrame(OfflineFrame& frame, const std::string& endpoint_id, ClientProxy* client, Medium medium, PacketMetaData& packet_meta_data) { V1Frame::FrameType frame_type = parser::GetFrameType(frame); if (frame_type != V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION) return; auto bwu_frame = frame.v1().bandwidth_upgrade_negotiation(); LOG(INFO) << "OnIncomingFrame: bwu_frame=" << BandwidthUpgradeNegotiationFrame::EventType_Name( bwu_frame.event_type()) << ", endpoint_id=" << endpoint_id << ", medium=" << location::nearby::proto::connections::Medium_Name(medium); if (FeatureFlags::GetInstance().GetFlags().enable_async_bandwidth_upgrade) { RunOnBwuManagerThread( "bwu-on-incoming-frame", [this, client, endpoint_id, bwu_frame]() { OnBwuNegotiationFrame(client, bwu_frame, endpoint_id); }); } else { CountDownLatch latch(1); RunOnBwuManagerThread("bwu-on-incoming-frame", [this, client, endpoint_id, bwu_frame, &latch]() { OnBwuNegotiationFrame(client, bwu_frame, endpoint_id); latch.CountDown(); }); latch.Await(); } } void BwuManager::OnEndpointDisconnect(ClientProxy* client, const std::string& service_id, const std::string& endpoint_id, CountDownLatch barrier, DisconnectionReason reason) { LOG(INFO) << "BwuManager has processed endpoint disconnection for endpoint " << endpoint_id << " with reason " << DisconnectionReason_Name(reason); RunOnBwuManagerThread("bwu-on-endpoint-disconnect", [this, client, service_id, endpoint_id, barrier]() mutable { Medium medium = GetBwuMediumForEndpoint(endpoint_id); BwuHandler* handler = GetHandlerForMedium(medium); if (handler) { handler->OnEndpointDisconnect(client, endpoint_id); } auto item = previous_endpoint_channels_.extract(endpoint_id); if (!item.empty()) { auto old_channel = item.mapped(); if (old_channel != nullptr) { old_channel->Close(DisconnectionReason::SHUTDOWN); } } in_progress_upgrades_.erase(endpoint_id); retry_delays_.erase(endpoint_id); CancelRetryUpgradeAlarm(endpoint_id); successfully_upgraded_endpoints_.erase(endpoint_id); // Note(nohle): I'm skeptical of the "<= 1", which seems like it should be // "== 0". Luckily, we will enable the flag by default, and it won't matter. if (FeatureFlags::GetInstance().GetFlags().support_multiple_bwu_mediums || channel_manager_->GetConnectedEndpointsCount() <= 1) { RevertBwuMediumForEndpoint(service_id, endpoint_id); } barrier.CountDown(); }); } void BwuManager::RevertBwuMediumForEndpoint(const std::string& service_id, const std::string& endpoint_id) { Medium medium = GetBwuMediumForEndpoint(endpoint_id); // If |support_multiple_bwu_mediums| is disabled, we take a less fine-grained // approach and revert the handler for _all_ endpoints. if (!FeatureFlags::GetInstance().GetFlags().support_multiple_bwu_mediums) { LOG(INFO) << "Reverting medium " << location::nearby::proto::connections::Medium_Name(medium) << " for all endpoints for service " << service_id; medium_ = Medium::UNKNOWN_MEDIUM; BwuHandler* handler = GetHandlerForMedium(medium); if (!handler) return; handler->RevertInitiatorState(); return; } LOG(INFO) << "Reverting medium " << location::nearby::proto::connections::Medium_Name(medium) << " for service ID " << service_id << " and endpoint " << endpoint_id; endpoint_id_to_bwu_medium_.erase(endpoint_id); BwuHandler* handler = GetHandlerForMedium(medium); if (!handler) { LOG(INFO) << "No BWU handler can be found for " << location::nearby::proto::connections::Medium_Name(medium); return; } // If |service_id| isn't of the INITIATOR-upgrade format--for example, if this // is called by the RESPONDER--there is no need to call RevertInitiatorState // unless the BWU Medium is Hotspot. The client needs to disconnect from // Hotspot, then it can restore the previous AP connection right away. if (!IsInitiatorUpgradeServiceId(service_id)) { if (medium == Medium::WIFI_HOTSPOT || medium == Medium::WIFI_DIRECT) { handler->RevertResponderState(service_id); } return; } handler->RevertInitiatorState(service_id, endpoint_id); } bool BwuManager::IsUpgradeOngoing(const std::string& endpoint_id) { CountDownLatch latch(1); RunOnBwuManagerThread("is_upgrade_ongoing", [&latch]() { latch.CountDown(); }); latch.Await(); return in_progress_upgrades_.contains(endpoint_id); } Medium BwuManager::GetBwuMediumForEndpoint( const std::string& endpoint_id) const { if (!FeatureFlags::GetInstance().GetFlags().support_multiple_bwu_mediums) { return medium_; } auto it = endpoint_id_to_bwu_medium_.find(endpoint_id); return it == endpoint_id_to_bwu_medium_.end() ? Medium::UNKNOWN_MEDIUM : it->second; } void BwuManager::SetBwuMediumForEndpoint(const std::string& endpoint_id, Medium medium) { if (!FeatureFlags::GetInstance().GetFlags().support_multiple_bwu_mediums) { medium_ = medium; return; } endpoint_id_to_bwu_medium_[endpoint_id] = medium; } BwuHandler* BwuManager::GetHandlerForMedium(Medium medium) const { if (medium == Medium::UNKNOWN_MEDIUM) return nullptr; auto it = handlers_.find(medium); if (it == handlers_.end()) return nullptr; return it->second.get(); } void BwuManager::OnBwuNegotiationFrame(ClientProxy* client, const BwuNegotiationFrame frame, const std::string& endpoint_id) { LOG(INFO) << "OnBwuNegotiationFrame: processing incoming " << BwuNegotiationFrame::EventType_Name(frame.event_type()) << " frame for endpoint " << endpoint_id; if (!client->IsConnectedToEndpoint(endpoint_id)) { LOG(WARNING) << "BwuManager skips the process BANDWIDTH_UPGRADE_NEGOTIATION before " "PCP connected, " << frame.event_type(); // For the case discover side not yet get the local accept from client, but // advertise side already get, discover side should inform advertise side // the upgrade failed, so the advertise side could have chance to initialize // another upgrade flow again. if (frame.event_type() == BwuNegotiationFrame::UPGRADE_PATH_AVAILABLE) { RunUpgradeFailedProtocol(client, endpoint_id, frame.upgrade_path_info()); } return; } switch (frame.event_type()) { case BwuNegotiationFrame::UPGRADE_PATH_AVAILABLE: ProcessBwuPathAvailableEvent(client, endpoint_id, frame.upgrade_path_info()); break; case BwuNegotiationFrame::UPGRADE_FAILURE: ProcessUpgradeFailureEvent( client, endpoint_id, frame.upgrade_path_info(), BandwidthUpgradeResult::REMOTE_CONNECTION_ERROR, /* record_analytic= */ true, OperationResultCode::NEARBY_GENERIC_REMOTE_UPGRADE_FAILURE); break; case BwuNegotiationFrame::LAST_WRITE_TO_PRIOR_CHANNEL: ProcessLastWriteToPriorChannelEvent(client, endpoint_id); break; case BwuNegotiationFrame::SAFE_TO_CLOSE_PRIOR_CHANNEL: ProcessSafeToClosePriorChannelEvent(client, endpoint_id); break; default: LOG(WARNING) << "BwuManager can't process unknown incoming OfflineFrame of type " << BandwidthUpgradeNegotiationFrame::EventType_Name( frame.event_type()) << ", ignoring it."; break; } } void BwuManager::OnIncomingConnection( ClientProxy* client, std::unique_ptr mutable_connection) { LOG(INFO) << "BwuManager process incoming connection"; std::shared_ptr connection( mutable_connection.release()); RunOnBwuManagerThread("bwu-on-incoming-connection", [this, client, connection]() { absl::Time connection_attempt_start_time = SystemClock::ElapsedRealtime(); EndpointChannel* channel = connection->channel.get(); if (channel == nullptr) { LOG(ERROR) << "BwuManager failed to create new EndpointChannel for incoming " "socket."; connection->socket->Close(); AttemptToRecordBandwidthUpgradeErrorForUnknownEndpoint( location::nearby::proto::connections::MEDIUM_ERROR, BandwidthUpgradeErrorStage::SOCKET_CREATION, OperationResultCode::NEARBY_GENERIC_NEW_ENDPOINT_CHANNEL_NULL); return; } VLOG(1) << "BwuManager successfully created new EndpointChannel for " "incoming socket"; ClientIntroduction introduction; if (!ReadClientIntroductionFrame(channel, introduction)) { // This was never a fully EstablishedConnection, no need to provide a // closure reason. channel->Close(); LOG(ERROR) << "BwuManager failed to read " "BWU_NEGOTIATION.CLIENT_INTRODUCTION OfflineFrame from " "newly-created EndpointChannel " << channel->GetName() << ", so the EndpointChannel was discarded."; return; } VLOG(1) << "BwuManager successfully received " "BWU_NEGOTIATION.CLIENT_INTRODUCTION " "OfflineFrame on EndpointChannel " << channel->GetName(); if (!WriteClientIntroductionAckFrame(channel)) { // This was never a fully EstablishedConnection, no need to provide a // closure reason. LOG(ERROR) << "BwuManager failed to write" "BWU_NEGOTIATION.CLIENT_INTRODUCTION_ACK " "OfflineFrame on EndpointChannel " << channel->GetName(); channel->Close(); return; } VLOG(1) << "BwuManager successfully wrote " "BWU_NEGOTIATION.CLIENT_INTRODUCTION_ACK " "OfflineFrame on EndpointChannel " << channel->GetName(); const std::string& endpoint_id = introduction.endpoint_id(); ClientProxy* mapped_client; const auto item = in_progress_upgrades_.find(endpoint_id); if (item == in_progress_upgrades_.end()) return; mapped_client = item->second; CancelRetryUpgradeAlarm(endpoint_id); if (mapped_client == nullptr) { // This was never a fully EstablishedConnection, no need to provide a // closure reason. channel->Close(); return; } CHECK(client == mapped_client); // The ConnectionAttempt has now succeeded, so record it as such. std::unique_ptr connections_attempt_metadata_params; if (channel != nullptr) { connections_attempt_metadata_params = client->GetAnalyticsRecorder().BuildConnectionAttemptMetadataParams( channel->GetTechnology(), channel->GetBand(), channel->GetFrequency(), channel->GetTryCount()); connections_attempt_metadata_params->operation_result_code = OperationResultCode::DETAIL_SUCCESS; } client->GetAnalyticsRecorder().OnIncomingConnectionAttempt( location::nearby::proto::connections::UPGRADE, channel->GetMedium(), location::nearby::proto::connections::RESULT_SUCCESS, SystemClock::ElapsedRealtime() - connection_attempt_start_time, client->GetConnectionToken(endpoint_id), connections_attempt_metadata_params.get()); // Use the introductory client information sent over to run the upgrade // protocol. RunUpgradeProtocol(mapped_client, endpoint_id, std::move(connection->channel), !introduction.supports_disabling_encryption()); }); } void BwuManager::RunOnBwuManagerThread(const std::string& name, Runnable runnable) { if (is_single_threaded_for_testing_) { runnable(); return; } serial_executor_.Execute(name, std::move(runnable)); } void BwuManager::RunUpgradeProtocol( ClientProxy* client, const std::string& endpoint_id, std::unique_ptr new_channel, bool enable_encryption) { LOG(INFO) << "RunUpgradeProtocol new channel @" << new_channel.get() << " name: " << new_channel->GetName() << ", medium: " << location::nearby::proto::connections::Medium_Name( new_channel->GetMedium()); // First, register this new EndpointChannel as *the* EndpointChannel to use // for this endpoint here onwards. NOTE: We pause this new EndpointChannel // until we've completely drained the old EndpointChannel to avoid out of // order reads on the other side. This is a consequence of using the same // UKEY2 context for both the previous and new EndpointChannels. UKEY2 uses // sequence numbers for writes and reads, and simultaneously sending Payloads // on the new channel and control messages on the old channel cause the other // side to read messages out of sequence new_channel->Pause(); auto old_channel = channel_manager_->GetChannelForEndpoint(endpoint_id); if (!old_channel) { LOG(INFO) << "BwuManager didn't find a previous EndpointChannel for " << endpoint_id << " when registering the new EndpointChannel, short-circuiting the " "upgrade protocol."; client->GetAnalyticsRecorder().OnBandwidthUpgradeError( endpoint_id, BandwidthUpgradeResult::CHANNEL_ERROR, BandwidthUpgradeErrorStage::PRIOR_ENDPOINT_CHANNEL, OperationResultCode::NEARBY_GENERIC_OLD_ENDPOINT_CHANNEL_NULL); return; } channel_manager_->ReplaceChannelForEndpoint( client, endpoint_id, std::move(new_channel), enable_encryption); // Next, initiate a clean shutdown for the previous EndpointChannel used for // this endpoint by telling the remote device that it will not receive any // more writes over that EndpointChannel. if (!old_channel->Write(parser::ForBwuLastWrite()).Ok()) { LOG(ERROR) << "BwuManager failed to write " "BWU_NEGOTIATION.LAST_WRITE_TO_PRIOR_CHANNEL OfflineFrame to " "endpoint " << endpoint_id << ", short-circuiting the upgrade protocol."; client->GetAnalyticsRecorder().OnBandwidthUpgradeError( endpoint_id, BandwidthUpgradeResult::RESULT_IO_ERROR, BandwidthUpgradeErrorStage::LAST_WRITE_TO_PRIOR_CHANNEL, OperationResultCode::CONNECTIVITY_GENERIC_WRITING_CHANNEL_IO_ERROR); return; } VLOG(1) << "BwuManager successfully wrote " "BWU_NEGOTIATION.LAST_WRITE_TO_PRIOR_CHANNEL " "OfflineFrame while upgrading endpoint " << endpoint_id; // The remainder of this clean shutdown for the previous EndpointChannel will // continue when we receive a corresponding // BANDWIDTH_UPGRADE_NEGOTIATION.LAST_WRITE_TO_PRIOR_CHANNEL OfflineFrame from // the remote device, so for now, just store that previous EndpointChannel. previous_endpoint_channels_.emplace(endpoint_id, old_channel); // If we already read LAST_WRITE on the old endpoint channel, then we can // safely close it now. auto item = successfully_upgraded_endpoints_.extract(endpoint_id); if (!item.empty()) { ProcessLastWriteToPriorChannelEvent(client, endpoint_id); } } // Outgoing BWU session. void BwuManager::ProcessBwuPathAvailableEvent( ClientProxy* client, const std::string& endpoint_id, const UpgradePathInfo& upgrade_path_info) { Medium upgrade_medium = parser::UpgradePathInfoMediumToMedium(upgrade_path_info.medium()); LOG(INFO) << "ProcessBwuPathAvailableEvent for endpoint " << endpoint_id << " medium " << location::nearby::proto::connections::Medium_Name( upgrade_medium); if (channel_manager_->isWifiLanConnected() && ((upgrade_medium == Medium::WIFI_HOTSPOT) || ((upgrade_medium == Medium::WIFI_DIRECT) && (client->GetLocalOsInfo().type() == location::nearby::connections::OsInfo::WINDOWS)))) { LOG(INFO) << "Some endpoint is using WIFI_LAN and proposed upgrade medium is " << location::nearby::proto::connections::Medium_Name(upgrade_medium) << ". Don't do the BWU because this will destroy WIFI_LAN which will " "lead BWU fail and other endpoint connection fail"; RunUpgradeFailedProtocol(client, endpoint_id, upgrade_path_info); return; } bool abort_bwu = false; if (client->IsIncomingConnection(endpoint_id)) { if (!is_dynamic_role_switch_enabled_) { abort_bwu = true; } else { auto medium_role = client->GetMediumRole(endpoint_id); if (medium_role.has_value() && !NeedToSwitchRole(client, endpoint_id, upgrade_medium, medium_role.value())) { abort_bwu = true; } } } if (abort_bwu) { LOG(INFO) << "ProcessBandwidthUpgradePathAvailableEvent ignored by Advertiser"; return; } if (in_progress_upgrades_.contains(endpoint_id)) { LOG(ERROR) << "BwuManager received a duplicate bandwidth upgrade for endpoint " << endpoint_id << ". We're out of sync with the remote device and cannot recover; " "closing all channels."; auto item = previous_endpoint_channels_.extract(endpoint_id); if (!item.empty()) { std::shared_ptr previous_endpoint_channel = item.mapped(); if (previous_endpoint_channel) { previous_endpoint_channel->Close(DisconnectionReason::UNFINISHED); } } std::shared_ptr new_channel = channel_manager_->GetChannelForEndpoint(endpoint_id); if (new_channel) { // The upgraded channel never finished upgrading, and therefore is still // paused. new_channel->Resume(); new_channel->Close(DisconnectionReason::UNFINISHED); } return; } Medium current_bwu_medium = GetBwuMediumForEndpoint(endpoint_id); if (current_bwu_medium == Medium::UNKNOWN_MEDIUM) { SetBwuMediumForEndpoint(endpoint_id, upgrade_medium); } // Check for the correct medium so we don't process an incorrect OfflineFrame. if (upgrade_medium != GetBwuMediumForEndpoint(endpoint_id)) { LOG(INFO) << "Medium not matching"; RunUpgradeFailedProtocol(client, endpoint_id, upgrade_path_info); return; } auto current_channel = channel_manager_->GetChannelForEndpoint(endpoint_id); Medium current_medium = current_channel ? current_channel->GetMedium() : Medium::UNKNOWN_MEDIUM; client->GetAnalyticsRecorder().OnBandwidthUpgradeStarted( endpoint_id, current_medium, upgrade_medium, location::nearby::proto::connections::OUTGOING, client->GetConnectionToken(endpoint_id)); absl::Time connection_attempt_start_time = SystemClock::ElapsedRealtime(); ErrorOr> result = ProcessBwuPathAvailableEventInternal(client, endpoint_id, upgrade_path_info); std::unique_ptr channel = result.has_value() ? std::move(result.value()) : nullptr; ConnectionAttemptResult connection_attempt_result; if (channel != nullptr) { connection_attempt_result = location::nearby::proto::connections::RESULT_SUCCESS; } else if (client->GetCancellationFlag(endpoint_id)->Cancelled()) { connection_attempt_result = location::nearby::proto::connections::RESULT_CANCELLED; client->GetAnalyticsRecorder().OnBandwidthUpgradeError( endpoint_id, BandwidthUpgradeResult::RESULT_REMOTE_ERROR, BandwidthUpgradeErrorStage::UPGRADE_CANCEL, OperationResultCode::CLIENT_CANCELLATION_UPGRADE_CANCELED_BY_REMOTE); } else { connection_attempt_result = location::nearby::proto::connections::RESULT_ERROR; } if (channel != nullptr) { std::unique_ptr connections_attempt_metadata_params = client->GetAnalyticsRecorder().BuildConnectionAttemptMetadataParams( channel->GetTechnology(), channel->GetBand(), channel->GetFrequency(), channel->GetTryCount()); connections_attempt_metadata_params->operation_result_code = OperationResultCode::DETAIL_SUCCESS; client->GetAnalyticsRecorder().OnOutgoingConnectionAttempt( endpoint_id, ConnectionAttemptType::UPGRADE, upgrade_medium, connection_attempt_result, SystemClock::ElapsedRealtime() - connection_attempt_start_time, client->GetConnectionToken(endpoint_id), connections_attempt_metadata_params.get()); } else { auto connections_attempt_metadata_params = std::make_unique(); connections_attempt_metadata_params->operation_result_code = result.error().operation_result_code().value(); client->GetAnalyticsRecorder().OnOutgoingConnectionAttempt( endpoint_id, ConnectionAttemptType::UPGRADE, upgrade_medium, connection_attempt_result, SystemClock::ElapsedRealtime() - connection_attempt_start_time, client->GetConnectionToken(endpoint_id), connections_attempt_metadata_params.get()); } if (channel == nullptr) { LOG(INFO) << "Failed to get new channel."; RunUpgradeFailedProtocol(client, endpoint_id, upgrade_path_info); return; } in_progress_upgrades_.emplace(endpoint_id, client); RunUpgradeProtocol(client, endpoint_id, std::move(channel), !upgrade_path_info.supports_disabling_encryption()); } ErrorOr> BwuManager::ProcessBwuPathAvailableEventInternal( ClientProxy* client, const std::string& endpoint_id, const UpgradePathInfo& upgrade_path_info) { Medium medium = parser::UpgradePathInfoMediumToMedium(upgrade_path_info.medium()); if (medium != GetBwuMediumForEndpoint(endpoint_id)) { LOG(ERROR) << "ProcessBwuPathAvailableEventInternal failed for endpoint " << endpoint_id << " medium " << location::nearby::proto::connections::Medium_Name(medium) << ". Upgrade medium not yet set for endpoint."; return {Error(OperationResultCode::NEARBY_UPGRADE_PATH_ON_WRONG_MEDIUM)}; } BwuHandler* handler = GetHandlerForMedium(medium); if (!handler) { LOG(ERROR) << "ProcessBwuPathAvailableEventInternal failed for endpoint " << endpoint_id << " medium " << location::nearby::proto::connections::Medium_Name(medium) << ". No handler for medium."; return {Error(OperationResultCode::NEARBY_UPGRADE_PATH_ON_WRONG_MEDIUM)}; } LOG(INFO) << "ProcessBwuPathAvailableEventInternal for " "endpoint " << endpoint_id << " medium " << location::nearby::proto::connections::Medium_Name(medium); // Get service ID from the old channel. Don't keep the old channel's shared // pointer in scope longer than necessary. std::string service_id; Medium old_medium = Medium::UNKNOWN_MEDIUM; bool disable_ble_scanning = false; { std::shared_ptr old_channel = channel_manager_->GetChannelForEndpoint(endpoint_id); if (!old_channel) { LOG(ERROR) << "ProcessBwuPathAvailableEventInternal failed for endpoint " << endpoint_id << " medium " << location::nearby::proto::connections::Medium_Name(medium) << ". Old endpoint channel is missing."; return { Error(OperationResultCode::NEARBY_GENERIC_OLD_ENDPOINT_CHANNEL_NULL)}; } service_id = old_channel->GetServiceId(); old_medium = old_channel->GetMedium(); } bool enable_ble_v2 = NearbyFlags::GetInstance().GetBoolFlag( config_package_nearby::nearby_connections_feature::kEnableBleV2); if (NearbyFlags::GetInstance().GetBoolFlag( config_package_nearby::nearby_connections_feature:: kEnableStopBleScanningOnWifiUpgrade)) { if (client->GetLocalOsInfo().type() == location::nearby::connections::OsInfo::APPLE && old_medium == Medium::BLE && medium == Medium::WIFI_HOTSPOT) { disable_ble_scanning = true; if (enable_ble_v2) { LOG(INFO) << "For Apple OS, if upgrade from BLE_V2 to WIFI_HOTSPOT, " "we need to pause " "BLE_V2 scanning because it can interfere with WIFI " "Hotspot scanning and connection."; ble_v2_medium_.PauseMediumScanning(); } } } ErrorOr> result = handler->CreateUpgradedEndpointChannel(client, service_id, endpoint_id, upgrade_path_info); if (NearbyFlags::GetInstance().GetBoolFlag( config_package_nearby::nearby_connections_feature:: kEnableStopBleScanningOnWifiUpgrade)) { if (disable_ble_scanning) { if (enable_ble_v2) { LOG(INFO) << "Resume BLE_V2 scanning."; ble_v2_medium_.ResumeMediumScanning(); } } } if (result.has_error() || !result.has_value()) { LOG(ERROR) << "BwuManager failed to create an endpoint " "channel to endpoint" << endpoint_id << ", aborting upgrade."; client->GetAnalyticsRecorder().OnBandwidthUpgradeError( endpoint_id, BandwidthUpgradeResult::RESULT_IO_ERROR, BandwidthUpgradeErrorStage::SOCKET_CREATION, result.error().operation_result_code().value()); return result; } std::unique_ptr new_channel = std::move(result.value()); // Write the requisite BANDWIDTH_UPGRADE_NEGOTIATION.CLIENT_INTRODUCTION as // the first OfflineFrame on this new EndpointChannel. if (!new_channel ->Write(parser::ForBwuIntroduction( client->GetLocalEndpointId(), upgrade_path_info.supports_disabling_encryption())) .Ok()) { // This was never a fully EstablishedConnection, no need to provide a // closure reason. new_channel->Close(); LOG(ERROR) << "BwuManager failed to write BWU_NEGOTIATION.CLIENT_INTRODUCTION " "OfflineFrame to newly-created EndpointChannel " << new_channel->GetName() << ", aborting upgrade."; client->GetAnalyticsRecorder().OnBandwidthUpgradeError( endpoint_id, BandwidthUpgradeResult::RESULT_IO_ERROR, BandwidthUpgradeErrorStage::CLIENT_INTRODUCTION, OperationResultCode:: NEARBY_GENERIC_READ_CLIENT_INTRODUCTION_ACK_FORMAT_ERROR); return { Error(OperationResultCode:: NEARBY_GENERIC_READ_CLIENT_INTRODUCTION_ACK_FORMAT_ERROR)}; } if (upgrade_path_info.supports_client_introduction_ack()) { if (!ReadClientIntroductionAckFrame(new_channel.get())) { // This was never a fully EstablishedConnection, no need to provide a // closure reason. new_channel->Close(); LOG(ERROR) << "BwuManager failed to read " "BWU_NEGOTIATION.CLIENT_INTRODUCTION_ACK " "OfflineFrame to newly-created EndpointChannel " << new_channel->GetName() << ", aborting upgrade."; return {Error( OperationResultCode:: NEARBY_GENERIC_READ_CLIENT_INTRODUCTION_ACK_FRAME_TYPE_ERROR)}; } } LOG(INFO) << "BwuManager successfully wrote " "BWU_NEGOTIATION.CLIENT_INTRODUCTION OfflineFrame to " "newly-created EndpointChannel " << new_channel->GetName() << " while upgrading endpoint " << endpoint_id; // Set the AnalyticsRecorder so that the future closure of this // EndpointChannel will be recorded. return {std::move(new_channel)}; } void BwuManager::RunUpgradeFailedProtocol( ClientProxy* client, const std::string& endpoint_id, const UpgradePathInfo& upgrade_path_info) { LOG(INFO) << "RunUpgradeFailedProtocol for endpoint " << endpoint_id << " medium " << location::nearby::proto::connections::Medium_Name( parser::UpgradePathInfoMediumToMedium( upgrade_path_info.medium())); // We attempted to connect to the new medium that the remote device has set up // for us but we failed. We need to let the remote device know so that they // can pick another medium for us to try. std::shared_ptr channel = channel_manager_->GetChannelForEndpoint(endpoint_id); if (!channel) { LOG(ERROR) << "BwuManager didn't find a previous EndpointChannel for " << endpoint_id << " when sending an upgrade failure frame, short-circuiting the " "upgrade protocol."; client->GetAnalyticsRecorder().OnBandwidthUpgradeError( endpoint_id, BandwidthUpgradeResult::CHANNEL_ERROR, BandwidthUpgradeErrorStage::NETWORK_AVAILABLE, OperationResultCode::NEARBY_GENERIC_OLD_ENDPOINT_CHANNEL_NULL); return; } // Report UPGRADE_FAILURE to the remote device. if (!channel->Write(parser::ForBwuFailure(upgrade_path_info)).Ok()) { channel->Close(DisconnectionReason::IO_ERROR); LOG(ERROR) << "BwuManager failed to write BWU_NEGOTIATION.UPGRADE_FAILURE " "OfflineFrame to endpoint " << endpoint_id << ", short-circuiting the upgrade protocol."; client->GetAnalyticsRecorder().OnBandwidthUpgradeError( endpoint_id, BandwidthUpgradeResult::RESULT_IO_ERROR, BandwidthUpgradeErrorStage::NETWORK_AVAILABLE, OperationResultCode::CONNECTIVITY_GENERIC_WRITING_CHANNEL_IO_ERROR); return; } // And lastly, clean up our medium since we failed to utilize it anyways. if (GetBwuMediumForEndpoint(endpoint_id) != Medium::UNKNOWN_MEDIUM) { RevertBwuMediumForEndpoint(channel->GetServiceId(), endpoint_id); } in_progress_upgrades_.erase(endpoint_id); LOG(INFO) << "BwuManager has informed endpoint " << endpoint_id << " that the bandwidth upgrade failed."; } bool BwuManager::ReadClientIntroductionFrame(EndpointChannel* channel, ClientIntroduction& introduction) { LOG(INFO) << "ReadClientIntroductionFrame with channel name: " << channel->GetName() << ", medium: " << location::nearby::proto::connections::Medium_Name( channel->GetMedium()); CancelableAlarm timeout_alarm( "BwuManager::ReadClientIntroductionFrame", [channel]() { LOG(ERROR) << "In BwuManager, failed to read the " "ClientIntroductionFrame after " << absl::FormatDuration(kReadClientIntroductionFrameTimeout) << ". Timing out and closing EndpointChannel " << channel->GetType(); channel->Close(); }, kReadClientIntroductionFrameTimeout, &alarm_executor_); auto data = channel->Read(); timeout_alarm.Cancel(); if (!data.ok()) return false; auto transfer(parser::FromBytes(data.result())); if (!transfer.ok()) { LOG(ERROR) << "In ReadClientIntroductionFrame, attempted to read a " "ClientIntroductionFrame from EndpointChannel " << channel->GetType() << " but was unable to obtain any OfflineFrame."; return false; } OfflineFrame frame = transfer.result(); if (!frame.has_v1() || !frame.v1().has_bandwidth_upgrade_negotiation()) { LOG(ERROR) << "In ReadClientIntroductionFrame, expected a " "BANDWIDTH_UPGRADE_NEGOTIATION v1 OfflineFrame but got a " << parser::GetFrameType(frame) << " frame instead."; return false; } if (frame.v1().bandwidth_upgrade_negotiation().event_type() != BandwidthUpgradeNegotiationFrame::CLIENT_INTRODUCTION) { LOG(ERROR) << "In ReadClientIntroductionFrame, expected a CLIENT_INTRODUCTION " "v1 OfflineFrame but got a BANDWIDTH_UPGRADE_NEGOTIATION frame " "with eventType " << frame.v1().bandwidth_upgrade_negotiation().event_type() << " instead."; return false; } const auto& frame_intro = frame.v1().bandwidth_upgrade_negotiation().client_introduction(); introduction = frame_intro; return true; } bool BwuManager::ReadClientIntroductionAckFrame(EndpointChannel* channel) { LOG(INFO) << "ReadClientIntroductionAckFrame with channel name: " << channel->GetName() << ", medium: " << location::nearby::proto::connections::Medium_Name( channel->GetMedium()); CancelableAlarm timeout_alarm( "BwuManager::ReadClientIntroductionAckFrame", [channel]() { LOG(ERROR) << "In BwuManager, failed to read the ClientIntroductionAckFrame " "after " << absl::FormatDuration(kReadClientIntroductionFrameTimeout) << ". Timing out and closing EndpointChannel " << channel->GetType(); channel->Close(); }, kReadClientIntroductionFrameTimeout, &alarm_executor_); auto data = channel->Read(); timeout_alarm.Cancel(); if (!data.ok()) return false; auto transfer(parser::FromBytes(data.result())); if (!transfer.ok()) return false; OfflineFrame frame = transfer.result(); if (!frame.has_v1() || !frame.v1().has_bandwidth_upgrade_negotiation()) return false; if (frame.v1().bandwidth_upgrade_negotiation().event_type() != BandwidthUpgradeNegotiationFrame::CLIENT_INTRODUCTION_ACK) return false; return true; } bool BwuManager::WriteClientIntroductionAckFrame(EndpointChannel* channel) { LOG(INFO) << "WriteClientIntroductionAckFrame channel name: " << channel->GetName() << ", medium: " << location::nearby::proto::connections::Medium_Name( channel->GetMedium()); return channel->Write(parser::ForBwuIntroductionAck()).Ok(); } void BwuManager::ProcessLastWriteToPriorChannelEvent( ClientProxy* client, const std::string& endpoint_id) { // By this point in the upgrade protocol, there is the guarantee that both // involved endpoints have registered a new EndpointChannel with the // EndpointChannelManager as the official channel for communication; given // the way communication is structured in the EndpointManager, this means // that all new writes are happening over that new EndpointChannel, but // reads are still happening over this prior EndpointChannel (to avoid data // loss). But now that we've received this definitive final write over that // prior EndpointChannel, we can let the remote device that they can safely // close their end of this now-dormant EndpointChannel. EndpointChannel* previous_endpoint_channel = previous_endpoint_channels_[endpoint_id].get(); if (!previous_endpoint_channel) { LOG(ERROR) << "BwuManager received a BWU_NEGOTIATION.LAST_WRITE_TO_PRIOR_CHANNEL " "OfflineFrame for unknown endpoint " << endpoint_id << ", can't complete the upgrade protocol."; successfully_upgraded_endpoints_.emplace(endpoint_id); return; } LOG(INFO) << "ProcessLastWriteToPriorChannelEvent: service_id=" << previous_endpoint_channel->GetServiceId() << ", endpoint_id=" << endpoint_id << ", medium=" << location::nearby::proto::connections::Medium_Name( previous_endpoint_channel->GetMedium()); if (!previous_endpoint_channel->Write(parser::ForBwuSafeToClose()).Ok()) { previous_endpoint_channel->Close(DisconnectionReason::IO_ERROR); // Remove this prior EndpointChannel from previous_endpoint_channels to // avoid leaks. previous_endpoint_channels_.erase(endpoint_id); LOG(ERROR) << "BwuManager failed to write " "BWU_NEGOTIATION.SAFE_TO_CLOSE_PRIOR_CHANNEL " "OfflineFrame to endpoint " << endpoint_id << ", short-circuiting the upgrade protocol."; client->GetAnalyticsRecorder().OnBandwidthUpgradeError( endpoint_id, BandwidthUpgradeResult::RESULT_IO_ERROR, BandwidthUpgradeErrorStage::SAFE_TO_CLOSE_PRIOR_CHANNEL, OperationResultCode::CONNECTIVITY_GENERIC_WRITING_CHANNEL_IO_ERROR); return; } VLOG(1) << "BwuManager successfully wrote " "BWU_NEGOTIATION.SAFE_TO_CLOSE_PRIOR_CHANNEL " "OfflineFrame while trying to upgrade endpoint " << endpoint_id; // The upgrade protocol's clean shutdown of the prior EndpointChannel will // conclude when we receive a corresponding // BANDWIDTH_UPGRADE_NEGOTIATION.SAFE_TO_CLOSE_PRIOR_CHANNEL OfflineFrame // from the remote device. } void BwuManager::ProcessSafeToClosePriorChannelEvent( ClientProxy* client, const std::string& endpoint_id) { LOG(INFO) << "ProcessSafeToClosePriorChannelEvent for endpoint " << endpoint_id; // By this point in the upgrade protocol, there's no more writes happening // over the prior EndpointChannel, and the remote device has given us the // go-ahead to close this EndpointChannel [1], so we can safely close it // (and depend on the EndpointManager querying the EndpointChannelManager to // start reading from the new EndpointChannel). // // [1] Which also implies that they've received our // BANDWIDTH_UPGRADE_NEGOTIATION.LAST_WRITE_TO_PRIOR_CHANNEL OfflineFrame), // so there can be no data loss, regardless of whether the EndpointChannel // allows reads of queued, unread data after the EndpointChannel has been // closed from the other end (as is the case with conventional TCP sockets) // or not (as is the case with Android's Bluetooth sockets, where closing // instantly throws an IOException on the remote device). auto item = previous_endpoint_channels_.extract(endpoint_id); auto& previous_endpoint_channel = item.mapped(); if (previous_endpoint_channel == nullptr) { LOG(ERROR) << "BwuManager received a BWU_NEGOTIATION.SAFE_TO_CLOSE_PRIOR_CHANNEL " "OfflineFrame for unknown endpoint " << endpoint_id << ", can't complete the upgrade protocol."; return; } LOG(INFO) << "BwuManager successfully received a " << "BWU_NEGOTIATION.SAFE_TO_CLOSE_PRIOR_CHANNEL OfflineFrame while " << "trying to upgrade endpoint " << endpoint_id; // Each encrypted message includes the key to decrypt the next message. The // disconnect message is optional and may not be received under normal // circumstances so it is necessary to send it unencrypted. This way the // serial crypto context does not increment here. previous_endpoint_channel->DisableEncryption(); LOG(INFO) << "[safe-to-disconnect] Sending " "DISCONNECTION frame with request 0, ack 0"; previous_endpoint_channel->Write( parser::ForDisconnection(/* request_safe_to_disconnect */ false, /* ack_safe_to_disconnect */ false)); // Attempt to read the disconnect message from the previous channel. We don't // care whether we successfully read it or whether we get an exception here. // The idea is just to make sure the other side has had a chance to receive // the full SAFE_TO_CLOSE_PRIOR_CHANNEL message before we actually close the // channel. See b/172380349 for more context. previous_endpoint_channel->Read(); previous_endpoint_channel->Close(DisconnectionReason::UPGRADED); VLOG(1) << "BwuManager cleanly shut down prior " << previous_endpoint_channel->GetType() << " EndpointChannel to conclude upgrade protocol for endpoint " << endpoint_id; // Now the upgrade protocol has completed, record analytics for this new // upgraded bandwidth connection... client->GetAnalyticsRecorder().OnConnectionEstablished( endpoint_id, GetBwuMediumForEndpoint(endpoint_id), client->GetConnectionToken(endpoint_id)); // ...and the success of the upgrade itself. client->GetAnalyticsRecorder().OnBandwidthUpgradeSuccess(endpoint_id); // Now that the old channel has been drained, we can unpause the new channel std::shared_ptr channel = channel_manager_->GetChannelForEndpoint(endpoint_id); if (!channel) { LOG(ERROR) << "BwuManager attempted to resume the current " "EndpointChannel with endpoint " << endpoint_id << ", but none was found."; return; } channel->Resume(); // Report the success to the client Medium medium = channel->GetMedium(); if (FeatureFlags::GetInstance() .GetFlags() .support_web_rtc_non_cellular_medium) { if (medium == Medium::WEB_RTC && !mediums_->GetWebRtc().IsUsingCellular()) { medium = Medium::WEB_RTC_NON_CELLULAR; } } client->OnBandwidthChanged(endpoint_id, medium); in_progress_upgrades_.erase(endpoint_id); } void BwuManager::ProcessUpgradeFailureEvent( ClientProxy* client, const std::string& endpoint_id, const UpgradePathInfo& upgrade_info, BandwidthUpgradeResult result, bool record_analytic, OperationResultCode operation_result_code) { LOG(INFO) << "ProcessUpgradeFailureEvent for endpoint " << endpoint_id << " from medium: " << location::nearby::proto::connections::Medium_Name( parser::UpgradePathInfoMediumToMedium( upgrade_info.medium())); // The remote device failed to upgrade to the new medium we set up for them. // That's alright! We'll just try the next available medium (if there is one). in_progress_upgrades_.erase(endpoint_id); // The first thing we have to do is to replace our currentBwuMedium with the // next best upgrade medium we share with the remote device. The catch is that // we can only do this if we only have one connected endpoint. Otherwise, // we'll end up disrupting our other connected peers. // Note: This is only necessary when |support_multiple_bwu_mediums| is // disabled. if (!FeatureFlags::GetInstance().GetFlags().support_multiple_bwu_mediums && channel_manager_->GetConnectedEndpointsCount() > 1) { // We can't change the currentBwuMedium, so there are no more upgrade // attempts for this endpoint. Sorry. LOG(ERROR) << "BwuManager failed to attempt a new bandwidth upgrade for endpoint " << endpoint_id << " because we have other connected endpoints and can't try a new " "upgrade medium."; client->GetAnalyticsRecorder().OnBandwidthUpgradeError( endpoint_id, BandwidthUpgradeResult::CHANNEL_ERROR, BandwidthUpgradeErrorStage::NETWORK_AVAILABLE, operation_result_code); return; } EndpointChannel* channel = channel_manager_->GetChannelForEndpoint(endpoint_id).get(); std::string service_id = channel ? channel->GetServiceId() : std::string(kUnknownServiceId); // Revert the existing upgrade medium for now. if (GetBwuMediumForEndpoint(endpoint_id) != Medium::UNKNOWN_MEDIUM) { // This is the BWU initiator, so append "_UPGRADE" to service_id. // Otherwise the Revert logic won't call into platform medium layer code. std::string upgrade_service_id = WrapInitiatorUpgradeServiceId(service_id); RevertBwuMediumForEndpoint(upgrade_service_id, endpoint_id); } if (record_analytic) { client->GetAnalyticsRecorder().OnBandwidthUpgradeError( endpoint_id, result, BandwidthUpgradeErrorStage::NETWORK_AVAILABLE, operation_result_code); } // Loop through the ordered list of upgrade mediums. One by one, remove the // top element until we get to the medium we last attempted to upgrade to. The // remainder of the list will contain the mediums we haven't attempted yet. Medium last = parser::UpgradePathInfoMediumToMedium(upgrade_info.medium()); std::vector all_possible_mediums = client->GetUpgradeMediums(endpoint_id).GetMediums(true); std::vector untried_mediums(all_possible_mediums); for (Medium medium : all_possible_mediums) { untried_mediums.erase(untried_mediums.begin()); if (medium == last) { break; } } TryNextBestUpgradeMediums(client, endpoint_id, untried_mediums); } void BwuManager::TryNextBestUpgradeMediums( ClientProxy* client, const std::string& endpoint_id, std::vector upgrade_mediums) { Medium next_medium = ChooseBestUpgradeMedium(endpoint_id, upgrade_mediums); LOG(INFO) << "Try Next Best Medium for endpoint " << endpoint_id << " after ChooseBestUpgradeMedium: " << location::nearby::proto::connections::Medium_Name(next_medium); // If current medium is not WiFi and we have not succeeded with upgrading yet, // retry upgrade. // TODO(b/228610864): Do we actually want to treat WIFI_LAN differently? auto channel = channel_manager_->GetChannelForEndpoint(endpoint_id); Medium current_medium = channel ? channel->GetMedium() : Medium::UNKNOWN_MEDIUM; VLOG(1) << "current_medium: " << location::nearby::proto::connections::Medium_Name(current_medium); if (current_medium != Medium::WIFI_LAN && (next_medium == current_medium || next_medium == Medium::UNKNOWN_MEDIUM || upgrade_mediums.empty())) { RetryUpgradesAfterDelay(client, endpoint_id); return; } // Attempt to set the new upgrade medium. if (!GetHandlerForMedium(next_medium)) { // As Medium without handler has been stripped out, this shouldn't be hit LOG(INFO) << "BwuManager failed to attempt a new bandwidth upgrade for endpoint " << endpoint_id << " because we couldn't set a new bandwidth upgrade medium."; return; } SetBwuMediumForEndpoint(endpoint_id, next_medium); // Now that we've successfully picked a new upgrade medium to try, // re-initiate the bandwidth upgrade. LOG(INFO) << "BwuManager is attempting to upgrade endpoint " << endpoint_id << " again with a new bandwidth upgrade medium."; InitiateBwuForEndpoint(client, endpoint_id, next_medium); } std::vector BwuManager::StripOutUnavailableMediums( const std::vector& mediums) const { std::vector available_mediums; for (Medium m : mediums) { bool available = false; if (GetHandlerForMedium(m)) { switch (m) { case Medium::AWDL: available = mediums_->GetAwdl().IsAvailable(); break; case Medium::WIFI_LAN: available = mediums_->GetWifiLan().IsAvailable(); break; case Medium::WIFI_DIRECT: available = mediums_->GetWifiDirect().IsGOAvailable(); break; case Medium::WIFI_HOTSPOT: available = mediums_->GetWifiHotspot().IsAPAvailable(); break; case Medium::WEB_RTC: available = mediums_->GetWebRtc().IsAvailable(); break; case Medium::BLUETOOTH: available = mediums_->GetBluetoothClassic().IsAvailable(); break; default: break; } } if (available) { available_mediums.push_back(m); } } return available_mediums; } // Returns the optimal medium supported by both devices. // Each medium in the passed in list is checked for its availability with the // medium_manager_ to ensure that the chosen upgrade medium is supported and // available locally before continuing the upgrade. Once we pick a medium, all // future connections will use it too. eg. If we chose Wifi LAN, we'll attempt // to upgrade the 2nd, 3rd, etc remote endpoints with Wifi LAN even if they're // on a different network (or had a better medium). This is a quick and easy // way to prevent mediums, like Wifi Hotspot, from interfering with active // connections (although it's suboptimal for bandwidth throughput). When all // endpoints disconnect, we reset the bandwidth upgrade medium. Medium BwuManager::ChooseBestUpgradeMedium( const std::string& endpoint_id, const std::vector& mediums) const { auto available_mediums = StripOutUnavailableMediums(mediums); Medium current_medium = GetBwuMediumForEndpoint(endpoint_id); if (current_medium == Medium::UNKNOWN_MEDIUM) { if (!available_mediums.empty()) { // Case 1: This is our first time upgrading, and we have at least one // supported medium to choose from. Return the first medium in the list, // since they are ordered by preference. return available_mediums[0]; } // Case 2: This is our first time upgrading, but there are no available // upgrade mediums. Fall through to returning UNKNOWN_MEDIUM at the // bottom. LOG(INFO) << "Current upgrade medium is unset, but there are no common supported " "upgrade mediums."; } else { // Case 3: We have already upgraded, and there is a list of supported // mediums to check against. Return the current upgrade medium if it's in // the supported list. if (std::find(available_mediums.begin(), available_mediums.end(), current_medium) != available_mediums.end()) { return current_medium; } // Case 4: We have already upgraded, but the current medium is not // supported by the remote endpoint (it's not in the list, or the list is // empty). Fall through and return Medium.UNKNOWN_MEDIUM because we cannot // continue with the current upgrade medium, and we are not allowed to // switch. std::string mediums_string; for (const auto& medium : available_mediums) { absl::StrAppend(&mediums_string, location::nearby::proto::connections::Medium_Name(medium), "; "); } LOG(INFO) << "Current upgrade medium " << location::nearby::proto::connections::Medium_Name( current_medium) << " is not supported by the remote endpoint (supported mediums: " << mediums_string << ")"; } return Medium::UNKNOWN_MEDIUM; } void BwuManager::RetryUpgradesAfterDelay(ClientProxy* client, const std::string& endpoint_id) { absl::Duration delay = CalculateNextRetryDelay(endpoint_id); CancelRetryUpgradeAlarm(endpoint_id); auto alarm = std::make_unique( "BWU alarm", [this, client, endpoint_id]() { RunOnBwuManagerThread( "bwu-retry-upgrade", [this, client, endpoint_id]() { if (!client->IsConnectedToEndpoint(endpoint_id)) { return; } TryNextBestUpgradeMediums( client, endpoint_id, client->GetUpgradeMediums(endpoint_id).GetMediums(true)); }); }, delay, &alarm_executor_); retry_upgrade_alarms_.emplace(endpoint_id, std::make_pair(std::move(alarm), delay)); retry_delays_[endpoint_id] = delay; LOG(INFO) << "Retry bandwidth upgrade after " << absl::FormatDuration(delay); } void BwuManager::AttemptToRecordBandwidthUpgradeErrorForUnknownEndpoint( BandwidthUpgradeResult result, BandwidthUpgradeErrorStage error_stage, OperationResultCode operation_result_code) { if (in_progress_upgrades_.size() == 1) { auto it = in_progress_upgrades_.begin(); std::string endpoint_id = it->first; ClientProxy* client = it->second; // Note: Even though we know this is an error, we cannot clear state yet. // We've sent the remote device the credentials they need and it's up to // them if they want to repeatedly attempt to connect or if they want to // give up and have us try a different medium. This isn't a decision we can // make for them. client->GetAnalyticsRecorder().OnBandwidthUpgradeError( endpoint_id, result, error_stage, operation_result_code); LOG(INFO) << "BwuManager got error " << BandwidthUpgradeResult_Name(result) << " at stage " << BandwidthUpgradeErrorStage_Name(error_stage) << " when upgrading endpoint " << endpoint_id; } // Otherwise, we have no way of knowing which endpoint was trying to connect // to us :( LOG(INFO) << "BwuManager got error " << BandwidthUpgradeResult_Name(result) << " at stage " << BandwidthUpgradeErrorStage_Name(error_stage) << ", but we don't know which endpoint was trying to " "connect to us, so skipping analytics for his error."; } bool BwuManager::NeedToSwitchRole( ClientProxy* client, const std::string& endpoint_id, Medium medium, const location::nearby::connections::MediumRole& medium_role) { if (GetLocalOsInfo(client).type() == OsInfo::APPLE) { switch (medium) { case Medium::WIFI_HOTSPOT: return medium_role.support_wifi_hotspot_host(); default: break; } } return false; } const location::nearby::connections::OsInfo& BwuManager::GetLocalOsInfo( ClientProxy* client) const { return client->GetLocalOsInfo(); } absl::Duration BwuManager::CalculateNextRetryDelay( const std::string& endpoint_id) { auto item = retry_delays_.find(endpoint_id); auto initial_delay = config_.bandwidth_upgrade_retry_delay; if (item == retry_delays_.end()) return initial_delay; /* * Without use_exp_backoff_in_bwu_retry, bwu retry intervals for the same * endpoint_id in seconds will be like: 5, 10, 10, 10... * With use_exp_backoff_in_bwu_retry enabled, bwu retry intervals in seconds * would be like: 3, 6, 12, 24, ... 300, 300, ... */ auto delay = FeatureFlags::GetInstance().GetFlags().use_exp_backoff_in_bwu_retry ? item->second * 2 : item->second + initial_delay; return std::min(delay, config_.bandwidth_upgrade_retry_max_delay); } void BwuManager::CancelRetryUpgradeAlarm(const std::string& endpoint_id) { LOG(INFO) << "CancelRetryUpgradeAlarm for endpoint " << endpoint_id; auto item = retry_upgrade_alarms_.extract(endpoint_id); if (item.empty()) return; auto& pair = item.mapped(); pair.first->Cancel(); } void BwuManager::CancelAllRetryUpgradeAlarms() { LOG(INFO) << "CancelAllRetryUpgradeAlarms invoked"; for (auto& item : retry_upgrade_alarms_) { const std::string& endpoint_id = item.first; CancelableAlarm* cancellable_alarm = item.second.first.get(); LOG(INFO) << "CancelRetryUpgradeAlarm for endpoint " << endpoint_id; cancellable_alarm->Cancel(); } retry_upgrade_alarms_.clear(); retry_delays_.clear(); } } // namespace connections } // namespace nearby