// Copyright 2023 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/reconnect_manager.h" #include #include #include #include #include "securegcm/ukey2_handshake.h" #include "absl/container/flat_hash_set.h" #include "absl/functional/any_invocable.h" #include "absl/functional/bind_front.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "connections/implementation/bluetooth_endpoint_channel.h" #include "connections/implementation/client_proxy.h" #include "connections/implementation/encryption_runner.h" #include "connections/implementation/endpoint_channel.h" #include "connections/implementation/endpoint_channel_manager.h" #include "connections/implementation/mediums/mediums.h" #include "connections/implementation/offline_frames.h" #include "connections/implementation/service_id_constants.h" #include "internal/platform/bluetooth_adapter.h" #include "internal/platform/bluetooth_classic.h" #include "internal/platform/byte_array.h" #include "internal/platform/cancelable_alarm.h" #include "internal/platform/cancellation_flag.h" #include "internal/platform/cancellation_flag_listener.h" #include "internal/platform/count_down_latch.h" #include "internal/platform/exception.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/mac_address.h" #include "internal/platform/mutex.h" #include "internal/platform/mutex_lock.h" #include "proto/connections_enums.pb.h" namespace nearby { namespace connections { constexpr absl::string_view TAG = "[ReconnectManager]"; ReconnectManager::ReconnectManager(Mediums& mediums, EndpointChannelManager& channel_manager) : mediums_(&mediums), channel_manager_(&channel_manager) {} ReconnectManager::~ReconnectManager() { Shutdown(); } bool ReconnectManager::AutoReconnect(ClientProxy* client, const std::string& endpoint_id, AutoReconnectCallback& callback, bool send_disconnection_notification, DisconnectionReason disconnection_reason) { if (!client->IsAutoReconnectEnabled(endpoint_id)) { return false; } if (resumed_endpoints_.contains(endpoint_id)) { LOG(INFO) << TAG << "AutoReconnect is not needed for endpoint_id = " << endpoint_id << ", since it's just reconnected successfully."; return true; } auto endpoint_channel = channel_manager_->GetChannelForEndpoint(endpoint_id); if (endpoint_channel == nullptr) { LOG(INFO) << TAG << " endpoint_channel shouldn't be null for endpoint_id = " << endpoint_id; return false; } Medium medium = endpoint_channel->GetMedium(); bool is_incoming = client->IsIncomingConnection(endpoint_id); if (is_incoming == client->IsOutgoingConnection(endpoint_id)) { LOG(INFO) << TAG << " autoReconnect failed for medium: " << location::nearby::proto::connections::Medium_Name(medium) << " because there is no existing incoming/outgoing connection, " "is_incoming_connection = " << is_incoming << ", is_outgoing_connection = " << client->IsOutgoingConnection(endpoint_id); return false; } std::string reconnect_service_id = WrapInitiatorReconnectServiceId(endpoint_channel->GetServiceId()); endpoint_id_metadata_map_.emplace( endpoint_id, ReconnectMetadata(is_incoming, std::move(callback), send_disconnection_notification, disconnection_reason, reconnect_service_id)); LOG(INFO) << TAG << "add a new endpoint_id " << endpoint_id << " into metadata_by_service_id_map."; if (Start(is_incoming, client, endpoint_id, reconnect_service_id, medium)) { resumed_endpoints_.emplace(endpoint_id); auto time_out = FeatureFlags::GetInstance() .GetFlags() .safe_to_disconnect_reconnect_skip_duplicated_endpoint_duration; std::make_unique( absl::StrCat("RemoveSuccessfulResumedEndpointId for ", endpoint_id), [this, endpoint_id, time_out]() { LOG(INFO) << TAG << "Timeout after " << time_out << "ms. RemoveSuccessfulResumedEndpointId for " << endpoint_id; resumed_endpoints_.erase(endpoint_id); }, time_out, &alarm_executor_); return true; } ClearReconnectData(client, reconnect_service_id, is_incoming); return false; } bool ReconnectManager::Start(bool is_incoming, ClientProxy* client, const std::string& endpoint_id, const std::string& reconnect_service_id, Medium medium) { auto retry_delay_millis = FeatureFlags::GetInstance() .GetFlags() .safe_to_disconnect_reconnect_retry_delay_millis; auto reconnect_retry_num = FeatureFlags::GetInstance() .GetFlags() .safe_to_disconnect_reconnect_retry_attempts; LOG(INFO) << TAG << " " << (is_incoming ? "rehost" : "reconnect") << " for medium: " << location::nearby::proto::connections::Medium_Name(medium) << " for endpoint_id " << endpoint_id << " started..."; bool final_result = false; CountDownLatch latch(1); reconnect_executor_.Execute( "reconnect-start", [this, &final_result, is_incoming, client, endpoint_id, &reconnect_service_id, retry_delay_millis, reconnect_retry_num, medium, &latch]() mutable { for (int i = 0; i < reconnect_retry_num; ++i) { if (client->GetCancellationFlag(endpoint_id)->Cancelled()) { LOG(INFO) << TAG << " Stop retry, Endpoint connection is cancelled"; break; } if (RunOnce(is_incoming, client, endpoint_id, reconnect_service_id, medium)) { final_result = true; break; } SystemClock::Sleep(retry_delay_millis); } LOG(INFO) << "Reconnect " << (final_result ? "succeeded" : "failed"); latch.CountDown(); }); latch.Await(); if (!final_result) { client->GetCancellationFlag(endpoint_id)->Cancel(); } return final_result; } bool ReconnectManager::RunOnce(bool is_incoming, ClientProxy* client, const std::string& endpoint_id, const std::string& reconnect_service_id, Medium medium) { bool result = false; switch (medium) { case Medium::BLUETOOTH: { BluetoothImpl bluetooth_impl(client, endpoint_id, reconnect_service_id, is_incoming, medium, mediums_, channel_manager_, *this); result = bluetooth_impl.Run(); } break; default: LOG(INFO) << "AutoReconnect not implemented yet for " << location::nearby::proto::connections::Medium_Name( medium); break; } return result; } void ReconnectManager::ClearReconnectData( ClientProxy* client, const std::string& reconnect_service_id, bool is_incoming) { for (auto& item : endpoint_id_metadata_map_) { if (item.second.reconnect_service_id == reconnect_service_id && item.second.is_incoming == is_incoming) { if (item.second.reconnect_cb.on_reconnect_failure_cb) { item.second.reconnect_cb.on_reconnect_failure_cb( client, item.first, item.second.send_disconnection_notification, item.second.disconnection_reason); } } LOG(INFO) << TAG << "erase endpoint_id " << item.first; endpoint_id_metadata_map_.erase(item.first); } } void ReconnectManager::Shutdown() { LOG(INFO) << TAG << "Initiating shutdown of ReconnectManager."; { MutexLock lock(&mutex_); listen_timeout_alarm_by_service_id_.clear(); } new_endpoint_channels_.clear(); endpoint_id_metadata_map_.clear(); resumed_endpoints_.clear(); alarm_executor_.Shutdown(); reconnect_executor_.Shutdown(); encryption_cb_executor_.Shutdown(); incoming_connection_cb_executor_.Shutdown(); LOG(INFO) << TAG << "ReconnectManager has shut down."; } bool ReconnectManager::BaseMediumImpl::Run() { if (!IsMediumRadioOn()) { LOG(INFO) << TAG << location::nearby::proto::connections::Medium_Name( medium_) << " radio is turned off, try later"; return false; } if (client_->IsConnectedToEndpoint(endpoint_id_)) { LOG(INFO) << TAG << "ReconnectBluetooth is not needed since it's already " "connected to the RemoteDevice: "; return true; } auto previou_channel = channel_manager_->GetChannelForEndpoint(endpoint_id_); if (previou_channel == nullptr) { LOG(INFO) << TAG << "ReconnectionManager didn't find a previous EndpointChannel " "for " << endpoint_id_ << " in this run, stop Reconnection!"; return false; } previou_channel->Close( DisconnectionReason::PREV_CHANNEL_DISCONNECTION_IN_RECONNECT); return is_incoming_ ? RehostForIncomingConnections(/*is_last_medium*/ true) : ReconnectToRemoteDevice(); } bool ReconnectManager::BaseMediumImpl::RehostForIncomingConnections( bool is_last_medium) { auto time_out = FeatureFlags::GetInstance() .GetFlags() .safe_to_disconnect_reconnect_timeout_millis; auto cancellation_flag = client_->GetCancellationFlag(endpoint_id_); if (IsListeningForIncomingConnections()) { LOG(INFO) << "Rehosting is not needed since it's already " "rehosts for: " << reconnect_service_id_; if (cancellation_flag == nullptr) { return true; } if (cancellation_flag->Cancelled()) { StopListeningIfAllConnected( reconnect_service_id_, [this]() { StopListeningForIncomingConnections(); }, /* forceStop= */ false); return false; } else { auto cancellation_listener = std::make_unique( cancellation_flag, [this]() { LOG(INFO) << "Calling CancellationFlagListener."; ProcessFailedReconnection(endpoint_id_, [this]() { StopListeningForIncomingConnections(); }); }); std::make_unique( absl::StrCat(TAG, " unregisterOnCancelListener"), [cancellation_listener = std::move(cancellation_listener)]() mutable { // clean up the listener after auto reconnect is done. cancellation_listener.reset(); }, time_out, &reconnect_manager_.alarm_executor_); } return true; } LOG(INFO) << "Start rehosting for: " << reconnect_service_id_; if (!StartListeningForIncomingConnections()) { LOG(ERROR) << TAG << "Rehost failed since " "StartListeningForIncomingConnections return false."; return false; } { MutexLock lock(&reconnect_manager_.mutex_); reconnect_manager_ .listen_timeout_alarm_by_service_id_[reconnect_service_id_] = std::make_unique( absl::StrCat("Rehost listen timeout for ", reconnect_service_id_), [this, time_out, is_last_medium]() { LOG(INFO) << "Timeout after " << time_out << "ms. Stop listening for incoming " "Connections for serviceId " << reconnect_service_id_ << " for rehost, initiated by " << endpoint_id_ << ", unregister all still not connected endpointIds."; StopListeningIfAllConnected( reconnect_service_id_, [this]() { StopListeningForIncomingConnections(); }, /* forceStop= */ is_last_medium); }, time_out, &reconnect_manager_.alarm_executor_); } if (cancellation_flag == nullptr) { return true; } if (cancellation_flag->Cancelled()) { StopListeningIfAllConnected( reconnect_service_id_, [this]() { StopListeningForIncomingConnections(); }, /* forceStop= */ false); return false; } auto cancellation_listener = std::make_unique( cancellation_flag, [this]() { LOG(INFO) << "Calling CancellationFlagListener."; ProcessFailedReconnection(endpoint_id_, [this]() { StopListeningForIncomingConnections(); }); }); std::make_unique( absl::StrCat(TAG, " unregisterOnCancelListener"), [cancellation_listener = std::move(cancellation_listener)]() mutable { // clean up the listener after auto reconnect is done. cancellation_listener.reset(); }, time_out, &reconnect_manager_.alarm_executor_); return true; } bool ReconnectManager::BaseMediumImpl::ReconnectToRemoteDevice() { if (!ConnectOverMedium()) { LOG(INFO) << TAG << "Connect over medium " << location::nearby::proto::connections::Medium_Name( medium_) << " failed."; return false; } LOG(INFO) << TAG << "Write CLIENT_INTRODUCTION frame"; Exception write_exception = reconnect_channel_->Write( parser::ForAutoReconnectIntroduction(client_->GetLocalEndpointId())); if (!write_exception.Ok()) { LOG(ERROR) << TAG << "Failed to write forAutoReconnectClientIntroductionEvent."; QuietlyCloseChannelAndSocket(); return false; } if (!ReadClientIntroductionAckFrame(reconnect_channel_.get())) { LOG(ERROR) << TAG << "Failed to read ClientIntroductionAck frame."; QuietlyCloseChannelAndSocket(); return false; } if (ReplaceChannelForEndpoint(client_, endpoint_id_, std::move(reconnect_channel_), SupportEncryptionDisabled(), nullptr)) { LOG(INFO) << TAG << " successfully rebuild the outgoing connection with " << location::nearby::proto::connections::Medium_Name( medium_) << " for the endpointId:" << endpoint_id_; return true; } LOG(INFO) << TAG << " ReplaceChannelForEndpoint for the outgoing connection with " << location::nearby::proto::connections::Medium_Name(medium_) << " for the endpointId:" << endpoint_id_ << " failed. Please retry"; return false; } void ReconnectManager::BaseMediumImpl::OnIncomingConnection( const std::string& reconnect_service_id) { LOG(INFO) << TAG << "Received reconnection successfully"; reconnect_manager_.incoming_connection_cb_executor_.Execute( "OnIncomingConnection", [this]() { auto incoming_endpoint_id = ReadClientIntroductionFrame(reconnect_channel_.get()); if (incoming_endpoint_id.empty()) { LOG(ERROR) << TAG << "read ClientIntroductionFrame failed"; QuietlyCloseChannelAndSocket(); return; } Exception write_exception = reconnect_channel_->Write( parser::ForAutoReconnectIntroductionAck()); if (!write_exception.Ok()) { LOG(ERROR) << TAG << "Failed to write forAutoReconnectClientIntroductionAckEvent."; QuietlyCloseChannelAndSocket(); return; } LOG(INFO) << TAG << "successfully read ClientIntroductionFrame and write" " ClientIntroductionAckFrame with" << location::nearby::proto::connections::Medium_Name(medium_) << " for the incoming endpointId " << incoming_endpoint_id; if (ReplaceChannelForEndpoint( client_, incoming_endpoint_id, std::move(reconnect_channel_), SupportEncryptionDisabled(), [this]() { StopListeningForIncomingConnections(); })) { LOG(INFO) << TAG << " successfully rebuild the incoming connection with " << location::nearby::proto::connections::Medium_Name(medium_) << " for the endpointId:" << incoming_endpoint_id; return; } QuietlyCloseChannelAndSocket(); LOG(INFO) << TAG << " ReplaceChannelForEndpoint for the incoming connection with " << location::nearby::proto::connections::Medium_Name(medium_) << " for the endpointId:" << incoming_endpoint_id << " failed. Please retry"; return; }); } std::string ReconnectManager::BaseMediumImpl::ReadClientIntroductionFrame( EndpointChannel* endpoint_channel) { LOG(INFO) << TAG << "Read CLIENT_INTRODUCTION frame"; auto timeout = FeatureFlags::GetInstance() .GetFlags() .safe_to_disconnect_auto_resume_timeout_millis; CancelableAlarm timeout_alarm( "ReconnectManager::ReadClientIntroductionFrame", [timeout, endpoint_channel]() { LOG(ERROR) << "In ReconnectManager, failed to read the " "ClientIntroductionFrame after " << timeout << ". Timing out and closing EndpointChannel " << endpoint_channel->GetType(); endpoint_channel->Close(); }, timeout, &reconnect_manager_.alarm_executor_); auto data = endpoint_channel->Read(); timeout_alarm.Cancel(); if (!data.ok()) { LOG(ERROR) << "Data read fail when expecting a ClientIntroductionFrame from " "EndpointChannel " << endpoint_channel->GetType(); return {}; } auto transfer(parser::FromBytes(data.result())); if (!transfer.ok()) { LOG(ERROR) << "Attempted to read a ClientIntroductionFrame from " "EndpointChannel " << endpoint_channel->GetType() << ", but was unable to obtain any OfflineFrame."; return {}; } OfflineFrame frame = transfer.result(); if (!frame.has_v1() || !frame.v1().has_auto_reconnect()) { LOG(ERROR) << "In ReadClientIntroductionFrame(), eExpected a " "AUTO_RECONNECT v1 OfflineFrame but got a " << parser::GetFrameType(frame) << " frame instead."; return {}; } if (frame.v1().auto_reconnect().event_type() != AutoReconnectFrame::CLIENT_INTRODUCTION) { LOG(ERROR) << "In ReadClientIntroductionFrame(), expected a " "CLIENT_INTRODUCTION " "v1 OfflineFrame but got a AUTO_RECONNECT frame " "with eventType " << frame.v1().auto_reconnect().event_type() << " instead."; return {}; } return frame.v1().auto_reconnect().endpoint_id(); } bool ReconnectManager::BaseMediumImpl::ReadClientIntroductionAckFrame( EndpointChannel* endpoint_channel) { LOG(INFO) << TAG << "Read CLIENT_INTRODUCTION_ACK frame"; auto timeout = FeatureFlags::GetInstance() .GetFlags() .safe_to_disconnect_auto_resume_timeout_millis; CancelableAlarm timeout_alarm( "ReconnectManager::ReadClientIntroductionAckFrame", [timeout, endpoint_channel]() { LOG(ERROR) << "In ReconnectManager, failed to read the " "ClientIntroductionAckFrame after " << timeout << ". Timing out and closing EndpointChannel " << endpoint_channel->GetType(); endpoint_channel->Close(); }, timeout, &reconnect_manager_.alarm_executor_); auto data = endpoint_channel->Read(); timeout_alarm.Cancel(); if (!data.ok()) return false; auto transfer(parser::FromBytes(data.result())); if (!transfer.ok()) { LOG(ERROR) << "Attempted to read a ClientIntroductionAckFrame from " "EndpointChannel " << endpoint_channel->GetType() << ", but was unable to obtain any OfflineFrame."; return false; } OfflineFrame frame = transfer.result(); if (!frame.has_v1() || !frame.v1().has_auto_reconnect()) { LOG(ERROR) << "In ReadClientIntroductionAckFrame(), eExpected a " "AUTO_RECONNECT v1 OfflineFrame but got a " << parser::GetFrameType(frame) << " frame instead."; return false; } if (frame.v1().auto_reconnect().event_type() != AutoReconnectFrame::CLIENT_INTRODUCTION_ACK) { LOG(ERROR) << "In ReadClientIntroductionAckFrame(), expected a " "CLIENT_INTRODUCTION_ACK " "v1 OfflineFrame but got a AUTO_RECONNECT frame " "with eventType " << frame.v1().auto_reconnect().event_type() << " instead."; return false; } return true; } bool ReconnectManager::BaseMediumImpl::ReplaceChannelForEndpoint( ClientProxy* client, const std::string& endpoint_id, std::unique_ptr new_channel, bool support_encryption_disabled, absl::AnyInvocable stop_listening_incoming_connection) { auto& endpoint_id_metadata_map = reconnect_manager_.endpoint_id_metadata_map_; auto reconnect_metadata = endpoint_id_metadata_map.find(endpoint_id); if (reconnect_metadata == endpoint_id_metadata_map.end()) { LOG(ERROR) << TAG << "ReconnectMetadata is null for endpointId: " << endpoint_id << " ,please retry!"; return false; } EndpointChannel* endpoint_channel = reconnect_manager_.new_endpoint_channels_ .emplace(endpoint_id, std::move(new_channel)) .first->second.get(); { MutexLock lock(&mutex_); replace_channel_succeed_ = false; wait_encryption_to_finish_ = std::make_unique(1); if (reconnect_metadata->second.is_incoming) { reconnect_manager_.encryption_runner_.StartServer( client, endpoint_id, endpoint_channel, GetResultListener()); } else { reconnect_manager_.encryption_runner_.StartClient( client, endpoint_id, endpoint_channel, GetResultListener()); } auto cancellation_flag = client_->GetCancellationFlag(endpoint_id_); std::unique_ptr cancellation_listener; if (cancellation_flag != nullptr) { cancellation_listener = std::make_unique( cancellation_flag, [this]() { LOG(INFO) << "Calling CancellationFlagListener for " "stopping wait_encryption_to_finish"; MutexLock lock(&mutex_); replace_channel_succeed_ = false; wait_encryption_to_finish_->CountDown(); }); } wait_encryption_to_finish_->Await( FeatureFlags::GetInstance() .GetFlags() .safe_to_disconnect_reconnect_timeout_millis); LOG(INFO) << TAG << "replace_channel_succeed_: " << replace_channel_succeed_ << " for endpointId: " << endpoint_id; if (replace_channel_succeed_) { ProcessSuccessfulReconnection( endpoint_id, [this]() { StopListeningForIncomingConnections(); }); client->GetAnalyticsRecorder().OnConnectionEstablished( endpoint_id, endpoint_channel->GetMedium(), client->GetConnectionToken(endpoint_id)); } else { ProcessFailedReconnection( endpoint_id, [this]() { StopListeningForIncomingConnections(); }); } cancellation_listener.reset(); reconnect_manager_.new_endpoint_channels_.erase(endpoint_id); return replace_channel_succeed_; } } EncryptionRunner::ResultListener ReconnectManager::BaseMediumImpl::GetResultListener() { return { .on_success_cb = [this](const std::string& endpoint_id, std::unique_ptr ukey2, const std::string& auth_token, const ByteArray& raw_auth_token) { reconnect_manager_.encryption_cb_executor_.Execute( "encryption-success", [this, endpoint_id, raw_ukey2 = ukey2.release(), auth_token, raw_auth_token]() mutable { OnEncryptionSuccessRunnable( endpoint_id, std::unique_ptr(raw_ukey2), auth_token, raw_auth_token); wait_encryption_to_finish_->CountDown(); }); }, .on_failure_cb = [this](const std::string& endpoint_id, EndpointChannel* channel) { reconnect_manager_.encryption_cb_executor_.Execute( "encryption-failure", [this, endpoint_id, channel]() mutable { LOG(ERROR) << "Encryption failed for endpoint_id=" << endpoint_id << " on medium=" << location::nearby::proto::connections::Medium_Name( channel->GetMedium()); OnEncryptionFailureRunnable(endpoint_id, channel); wait_encryption_to_finish_->CountDown(); }); }, }; } void ReconnectManager::BaseMediumImpl::OnEncryptionSuccessRunnable( const std::string& endpoint_id, std::unique_ptr ukey2, const std::string& auth_token, const ByteArray& raw_auth_token) { auto item = reconnect_manager_.new_endpoint_channels_.find(endpoint_id); if (item == reconnect_manager_.new_endpoint_channels_.end()) { LOG(INFO) << "TAG" << "OnEncryptionSuccess failed, new_endpoint_channel is " "null for Endpoint:" << endpoint_id; return; } if (!ukey2) { LOG(INFO) << "TAG" << "OnEncryptionSuccess failed, ukey2 is null for Endpoint:" << endpoint_id; return; } // After both parties accepted connection (presumably after verifying & // matching security tokens), we are allowed to extract the shared key. bool succeeded = ukey2->VerifyHandshake(); CHECK(succeeded); // If this fails, it's a UKEY2 protocol bug. auto context = ukey2->ToConnectionContext(); CHECK(context); // there is no way how this can fail, if Verify succeeded. // If it did, it's a UKEY2 protocol bug. if (!reconnect_manager_.channel_manager_->EncryptChannelForEndpoint( endpoint_id, std::move(context))) { LOG(INFO) << "TAG" << "new_endpoint_channel failed to update " "EncryptionContext for Endpoint:" << endpoint_id; return; } auto previous_channel = reconnect_manager_.channel_manager_->GetChannelForEndpoint(endpoint_id); if (previous_channel == nullptr) { LOG(INFO) << "TAG" << "ReconnectionManager didn't find a previous EndpointChannel for " << endpoint_id << " when registering the new EndpointChannel, stop Reconnection!"; item->second->Close(DisconnectionReason::UNFINISHED); return; } reconnect_manager_.channel_manager_->ReplaceChannelForEndpoint( client_, endpoint_id, std::move(item->second), SupportEncryptionDisabled()); { MutexLock lock(&mutex_); replace_channel_succeed_ = true; } } void ReconnectManager::BaseMediumImpl::OnEncryptionFailureRunnable( const std::string& endpoint_id, EndpointChannel* endpoint_channel) { LOG(INFO) << "TAG" << "new_endpoint_channel failed to use encryption for Endpoint:" << endpoint_id; } void ReconnectManager::BaseMediumImpl::ProcessSuccessfulReconnection( const std::string& endpoint_id, absl::AnyInvocable stop_listening_incoming_connection) { auto& endpoint_id_metadata_map = reconnect_manager_.endpoint_id_metadata_map_; auto reconnect_metadata = endpoint_id_metadata_map.find(endpoint_id); if (reconnect_metadata == endpoint_id_metadata_map.end()) { LOG(ERROR) << TAG << "when ProcessSuccessfulReconnection, endpoint_id: " << endpoint_id << " is already removed fromendpoint_id_metadata_map."; return; } auto medatdata = std::move(reconnect_metadata->second); endpoint_id_metadata_map.erase(reconnect_metadata); auto& callback = medatdata.reconnect_cb; if (callback.on_reconnect_success_cb) { callback.on_reconnect_success_cb(client_, endpoint_id); } else { LOG(ERROR) << TAG << "when ProcessSuccessfulReconnection, endpoint_id: " << endpoint_id << " callback.on_reconnect_success_cb is null"; } if (medatdata.is_incoming && stop_listening_incoming_connection) { StopListeningIfAllConnected(medatdata.reconnect_service_id, std::move(stop_listening_incoming_connection), /* forceStop= */ false); } } void ReconnectManager::BaseMediumImpl::ProcessFailedReconnection( const std::string& endpoint_id, absl::AnyInvocable stop_listening_incoming_connection) { auto& endpoint_id_metadata_map = reconnect_manager_.endpoint_id_metadata_map_; auto reconnect_metadata = endpoint_id_metadata_map.find(endpoint_id); if (reconnect_metadata == endpoint_id_metadata_map.end()) { LOG(ERROR) << TAG << "when ProcessFailedReconnection, endpoint_id: " << endpoint_id << " is already removed fromendpoint_id_metadata_map."; return; } auto medatdata = std::move(reconnect_metadata->second); auto& callback = medatdata.reconnect_cb; if (medatdata.is_incoming) { endpoint_id_metadata_map.erase(reconnect_metadata); if (callback.on_reconnect_failure_cb) { callback.on_reconnect_failure_cb( client_, endpoint_id, medatdata.send_disconnection_notification, medatdata.disconnection_reason); } else { LOG(ERROR) << TAG << "when ProcessFailedReconnection, endpoint_id: " << endpoint_id << " callback.on_reconnect_success_cb is null"; } if (stop_listening_incoming_connection) { StopListeningIfAllConnected(medatdata.reconnect_service_id, std::move(stop_listening_incoming_connection), /* forceStop= */ false); } } } void ReconnectManager::BaseMediumImpl::StopListeningIfAllConnected( const std::string& reconnect_service_id, absl::AnyInvocable stop_listening_incoming_connection, bool force_stop) { if (!force_stop && HasPendingIncomingConnections(reconnect_service_id)) { return; } CancelClearHostTimeoutAlarm(reconnect_service_id); stop_listening_incoming_connection(); ClearReconnectData(reconnect_service_id, /* is_incoming= */ true); LOG(INFO) << TAG << " No more pending incoming connections, " "stop_listening_incoming_connection for " << reconnect_service_id << " before timeout."; } bool ReconnectManager::BaseMediumImpl::HasPendingIncomingConnections( const std::string& reconnect_service_id) { for (auto& item : reconnect_manager_.endpoint_id_metadata_map_) { if (item.second.reconnect_service_id == reconnect_service_id && item.second.is_incoming) { return true; } } return false; } void ReconnectManager::BaseMediumImpl::CancelClearHostTimeoutAlarm( const std::string& service_id) { MutexLock lock(&reconnect_manager_.mutex_); auto item = reconnect_manager_.listen_timeout_alarm_by_service_id_.find(service_id); if (item == reconnect_manager_.listen_timeout_alarm_by_service_id_.end()) return; if (item->second->IsValid()) { item->second->Cancel(); item->second.reset(); } reconnect_manager_.listen_timeout_alarm_by_service_id_.erase(item); } void ReconnectManager::BaseMediumImpl::ClearReconnectData( const std::string& service_id, bool is_incoming) { absl::flat_hash_set endpoint_ids_pending_removal; auto& metadata_map = reconnect_manager_.endpoint_id_metadata_map_; for (auto item = metadata_map.begin(); item != metadata_map.end();) { if (item->second.reconnect_service_id == service_id && is_incoming) { auto& callback = item->second.reconnect_cb.on_reconnect_failure_cb; if (callback) callback(client_, item->first, item->second.send_disconnection_notification, item->second.disconnection_reason); endpoint_ids_pending_removal.insert(item->first); } else { ++item; } } for (const auto& endpoint_id : endpoint_ids_pending_removal) { metadata_map.erase(endpoint_id); } } bool ReconnectManager::BluetoothImpl::IsMediumRadioOn() const { return bluetooth_medium_.IsAvailable(); } bool ReconnectManager::BluetoothImpl::IsListeningForIncomingConnections() const { return bluetooth_medium_.IsAcceptingConnections(reconnect_service_id_); } bool ReconnectManager::BluetoothImpl::StartListeningForIncomingConnections() { if (!bluetooth_medium_.StartAcceptingConnections( reconnect_service_id_, absl::bind_front( &ReconnectManager::BluetoothImpl::OnIncomingBluetoothConnection, this, client_))) { LOG(ERROR) << "ReconnectManager::BluetoothImpl couldn't initiate the " "BLUETOOTH reconnect for endpoint " << endpoint_id_ << " because it failed to start listening for " "incoming Bluetooth connections."; return false; } LOG(INFO) << "ReconnectManager::BluetoothImpl successfully started " "listening for incoming " "reconnection on service_id=" << reconnect_service_id_ << " for endpoint " << endpoint_id_; return true; } void ReconnectManager::BluetoothImpl::OnIncomingBluetoothConnection( ClientProxy* client, const std::string& upgrade_service_id, BluetoothSocket socket) { reconnect_channel_ = std::make_unique( upgrade_service_id, /*channel_name=*/upgrade_service_id, socket); if (reconnect_channel_ == nullptr) { LOG(ERROR) << TAG << "Create new endpointChannel for incoming socket " "failed, close the socket"; socket.Close(); return; } bluetooth_socket_ = std::move(socket); LOG(INFO) << TAG << "Create new endpointChannel successfully for incoming socket."; OnIncomingConnection(upgrade_service_id); } void ReconnectManager::BluetoothImpl::StopListeningForIncomingConnections() { bluetooth_medium_.StopAcceptingConnections(reconnect_service_id_); } bool ReconnectManager::BluetoothImpl::ConnectOverMedium() { std::optional remote_mac_address = client_->GetBluetoothMacAddress(endpoint_id_); if (!remote_mac_address.has_value()) { LOG(INFO) << "ReconnectBluetooth failed since remoteMacAddress is empty"; return false; } auto& bluetooth_medium = mediums_->GetBluetoothClassic(); BluetoothDevice remote_bluetooth_device = bluetooth_medium.GetRemoteDevice(remote_mac_address.value()); if (!remote_bluetooth_device.IsValid()) { LOG(INFO) << "ReconnectBluetooth failed since remoteBluetoothDevice is null: " << remote_mac_address.value().ToString(); return false; } ErrorOr bluetooth_socket_result = bluetooth_medium.Connect(remote_bluetooth_device, reconnect_service_id_, client_->GetCancellationFlag(endpoint_id_)); if (bluetooth_socket_result.has_error()) { LOG(ERROR) << "Failed to reconnect to Bluetooth device " << remote_bluetooth_device.GetName() << " for endpoint(id=" << endpoint_id_ << ")."; return false; } bluetooth_socket_ = std::move(bluetooth_socket_result.value()); reconnect_channel_ = std::make_unique( UnWrapInitiatorReconnectServiceId(reconnect_service_id_), /*channel_name=*/endpoint_id_, bluetooth_socket_); if (reconnect_channel_ == nullptr) { LOG(ERROR) << "ReconnectBluetooth Failed to get the Bluetooth " "channel, please retry "; bluetooth_socket_.Close(); return false; } return true; } bool ReconnectManager::BluetoothImpl::SupportEncryptionDisabled() { return false; } void ReconnectManager::BluetoothImpl::QuietlyCloseChannelAndSocket() { reconnect_channel_->Close(DisconnectionReason::UNFINISHED); bluetooth_socket_.Close(); } } // namespace connections } // namespace nearby