| // Copyright 2020 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/nearby_sharing/nearby_sharing_service_impl.h" |
| |
| #include <utility> |
| |
| #include "ash/public/cpp/session/session_controller.h" |
| #include "base/barrier_closure.h" |
| #include "base/bind.h" |
| #include "base/files/file.h" |
| #include "base/logging.h" |
| #include "base/numerics/checked_math.h" |
| #include "base/rand_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/system/sys_info.h" |
| #include "base/task/post_task.h" |
| #include "base/task_runner_util.h" |
| #include "base/threading/sequenced_task_runner_handle.h" |
| #include "build/chromeos_buildflags.h" |
| #include "chrome/browser/download/download_prefs.h" |
| #include "chrome/browser/nearby_sharing/certificates/common.h" |
| #include "chrome/browser/nearby_sharing/certificates/nearby_share_certificate_manager_impl.h" |
| #include "chrome/browser/nearby_sharing/certificates/nearby_share_encrypted_metadata_key.h" |
| #include "chrome/browser/nearby_sharing/client/nearby_share_client_impl.h" |
| #include "chrome/browser/nearby_sharing/common/nearby_share_prefs.h" |
| #include "chrome/browser/nearby_sharing/constants.h" |
| #include "chrome/browser/nearby_sharing/contacts/nearby_share_contact_manager_impl.h" |
| #include "chrome/browser/nearby_sharing/fast_initiation_manager.h" |
| #include "chrome/browser/nearby_sharing/local_device_data/nearby_share_local_device_data_manager_impl.h" |
| #include "chrome/browser/nearby_sharing/logging/logging.h" |
| #include "chrome/browser/nearby_sharing/nearby_connections_manager.h" |
| #include "chrome/browser/nearby_sharing/nearby_share_default_device_name.h" |
| #include "chrome/browser/nearby_sharing/nearby_share_metrics_logger.h" |
| #include "chrome/browser/nearby_sharing/paired_key_verification_runner.h" |
| #include "chrome/browser/nearby_sharing/transfer_metadata.h" |
| #include "chrome/browser/nearby_sharing/transfer_metadata_builder.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/signin/identity_manager_factory.h" |
| #include "chrome/browser/ui/browser_tabstrip.h" |
| #include "chrome/browser/ui/scoped_tabbed_browser_displayer.h" |
| #include "chrome/services/sharing/public/cpp/advertisement.h" |
| #include "chrome/services/sharing/public/cpp/conversions.h" |
| #include "chromeos/services/nearby/public/mojom/nearby_connections_types.mojom.h" |
| #include "chromeos/services/nearby/public/mojom/nearby_share_target_types.mojom.h" |
| #include "components/prefs/pref_service.h" |
| #include "content/public/browser/download_manager.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "crypto/random.h" |
| #include "device/bluetooth/bluetooth_adapter_factory.h" |
| #include "services/network/public/cpp/shared_url_loader_factory.h" |
| #include "url/gurl.h" |
| |
| namespace { |
| |
| constexpr base::TimeDelta kBackgroundAdvertisementRotationDelayMin = |
| base::TimeDelta::FromMinutes(12); |
| // 870 seconds represents 14:30 minutes |
| constexpr base::TimeDelta kBackgroundAdvertisementRotationDelayMax = |
| base::TimeDelta::FromSeconds(870); |
| constexpr base::TimeDelta kInvalidateSurfaceStateDelayAfterTransferDone = |
| base::TimeDelta::FromMilliseconds(3000); |
| |
| // Used to hash a token into a 4 digit string. |
| constexpr int kHashModulo = 9973; |
| constexpr int kHashBaseMultiplier = 31; |
| |
| std::string ReceiveSurfaceStateToString( |
| NearbySharingService::ReceiveSurfaceState state) { |
| switch (state) { |
| case NearbySharingService::ReceiveSurfaceState::kForeground: |
| return "FOREGROUND"; |
| case NearbySharingService::ReceiveSurfaceState::kBackground: |
| return "BACKGROUND"; |
| case NearbySharingService::ReceiveSurfaceState::kUnknown: |
| return "UNKNOWN"; |
| } |
| } |
| |
| std::string SendSurfaceStateToString( |
| NearbySharingService::SendSurfaceState state) { |
| switch (state) { |
| case NearbySharingService::SendSurfaceState::kForeground: |
| return "FOREGROUND"; |
| case NearbySharingService::SendSurfaceState::kBackground: |
| return "BACKGROUND"; |
| case NearbySharingService::SendSurfaceState::kUnknown: |
| return "UNKNOWN"; |
| } |
| } |
| |
| std::string PowerLevelToString(PowerLevel level) { |
| switch (level) { |
| case PowerLevel::kLowPower: |
| return "LOW_POWER"; |
| case PowerLevel::kMediumPower: |
| return "MEDIUM_POWER"; |
| case PowerLevel::kHighPower: |
| return "HIGH_POWER"; |
| case PowerLevel::kUnknown: |
| return "UNKNOWN"; |
| } |
| } |
| |
| base::Optional<std::string> GetDeviceName( |
| const sharing::mojom::AdvertisementPtr& advertisement, |
| const base::Optional<NearbyShareDecryptedPublicCertificate>& certificate) { |
| DCHECK(advertisement); |
| |
| // Device name is always included when visible to everyone. |
| if (advertisement->device_name) |
| return *(advertisement->device_name); |
| |
| // For contacts only advertisements, we can't do anything without the |
| // certificate. |
| if (!certificate || !certificate->unencrypted_metadata().has_device_name()) |
| return base::nullopt; |
| |
| return certificate->unencrypted_metadata().device_name(); |
| } |
| |
| std::string GetDeviceId( |
| const std::string& endpoint_id, |
| const base::Optional<NearbyShareDecryptedPublicCertificate>& certificate) { |
| if (!certificate || certificate->id().empty()) |
| return endpoint_id; |
| |
| return std::string(certificate->id().begin(), certificate->id().end()); |
| } |
| |
| base::Optional<std::string> ToFourDigitString( |
| const base::Optional<std::vector<uint8_t>>& bytes) { |
| if (!bytes) |
| return base::nullopt; |
| |
| int hash = 0; |
| int multiplier = 1; |
| for (uint8_t byte : *bytes) { |
| // Java bytes are signed two's complement so cast to use the correct sign. |
| hash = (hash + static_cast<int8_t>(byte) * multiplier) % kHashModulo; |
| multiplier = (multiplier * kHashBaseMultiplier) % kHashModulo; |
| } |
| |
| return base::StringPrintf("%04d", std::abs(hash)); |
| } |
| |
| bool IsOutOfStorage(base::FilePath file_path, |
| int64_t storage_required, |
| base::Optional<int64_t> free_disk_space_for_testing) { |
| int64_t free_space = free_disk_space_for_testing.value_or( |
| base::SysInfo::AmountOfFreeDiskSpace(file_path)); |
| return free_space < storage_required; |
| } |
| |
| bool DoAttachmentsExceedThreshold(const ShareTarget& share_target, |
| int64_t threshold) { |
| for (const auto& attachment : share_target.text_attachments) { |
| if (attachment.size() > threshold) |
| return false; |
| |
| threshold -= attachment.size(); |
| } |
| |
| for (const auto& attachment : share_target.file_attachments) { |
| if (attachment.size() > threshold) |
| return false; |
| |
| threshold -= attachment.size(); |
| } |
| |
| return true; |
| } |
| |
| DataUsage CheckFileSizeForDataUsagePreference(DataUsage client_preference, |
| const ShareTarget& share_target) { |
| if (client_preference == DataUsage::kOffline) |
| return client_preference; |
| |
| if (DoAttachmentsExceedThreshold(share_target, kOnlineFileSizeLimitBytes)) |
| return DataUsage::kOffline; |
| |
| return client_preference; |
| } |
| |
| int64_t GeneratePayloadId() { |
| int64_t payload_id = 0; |
| crypto::RandBytes(&payload_id, sizeof(payload_id)); |
| return payload_id; |
| } |
| |
| // Wraps a call to OnTransferUpdate() to filter any updates after receiving a |
| // final status. |
| class TransferUpdateDecorator : public TransferUpdateCallback { |
| public: |
| using Callback = base::RepeatingCallback<void(const ShareTarget&, |
| const TransferMetadata&)>; |
| |
| explicit TransferUpdateDecorator(Callback callback) |
| : callback_(std::move(callback)) {} |
| TransferUpdateDecorator(const TransferUpdateDecorator&) = delete; |
| TransferUpdateDecorator& operator=(const TransferUpdateDecorator&) = delete; |
| ~TransferUpdateDecorator() override = default; |
| |
| void OnTransferUpdate(const ShareTarget& share_target, |
| const TransferMetadata& transfer_metadata) override { |
| if (got_final_status_) { |
| // If we already got a final status, we can ignore any subsequent final |
| // statuses caused by race conditions. |
| NS_LOG(VERBOSE) |
| << __func__ << ": Transfer update decorator swallowed " |
| << "status update because a final status was already received: " |
| << share_target.id << ": " |
| << TransferMetadata::StatusToString(transfer_metadata.status()); |
| return; |
| } |
| got_final_status_ = transfer_metadata.is_final_status(); |
| callback_.Run(share_target, transfer_metadata); |
| } |
| |
| private: |
| bool got_final_status_ = false; |
| Callback callback_; |
| }; |
| |
| } // namespace |
| |
| NearbySharingServiceImpl::NearbySharingServiceImpl( |
| PrefService* prefs, |
| NotificationDisplayService* notification_display_service, |
| Profile* profile, |
| std::unique_ptr<NearbyConnectionsManager> nearby_connections_manager, |
| NearbyProcessManager* process_manager, |
| std::unique_ptr<PowerClient> power_client) |
| : profile_(profile), |
| nearby_connections_manager_(std::move(nearby_connections_manager)), |
| process_manager_(process_manager), |
| power_client_(std::move(power_client)), |
| http_client_factory_(std::make_unique<NearbyShareClientFactoryImpl>( |
| IdentityManagerFactory::GetForProfile(profile), |
| profile->GetURLLoaderFactory(), |
| &nearby_share_http_notifier_)), |
| local_device_data_manager_( |
| NearbyShareLocalDeviceDataManagerImpl::Factory::Create( |
| prefs, |
| http_client_factory_.get(), |
| GetNearbyShareDefaultDeviceName(profile_))), |
| contact_manager_(NearbyShareContactManagerImpl::Factory::Create( |
| prefs, |
| http_client_factory_.get(), |
| local_device_data_manager_.get(), |
| profile->GetProfileUserName())), |
| certificate_manager_(NearbyShareCertificateManagerImpl::Factory::Create( |
| local_device_data_manager_.get(), |
| contact_manager_.get(), |
| prefs, |
| content::BrowserContext::GetDefaultStoragePartition(profile) |
| ->GetProtoDatabaseProvider(), |
| profile->GetPath(), |
| http_client_factory_.get())), |
| settings_(prefs, local_device_data_manager_.get()) { |
| DCHECK(profile_); |
| DCHECK(nearby_connections_manager_); |
| DCHECK(power_client_); |
| |
| RecordNearbyShareEnabledMetric(prefs); |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| auto* session_controller = ash::SessionController::Get(); |
| if (session_controller) { |
| is_screen_locked_ = session_controller->IsScreenLocked(); |
| session_controller->AddObserver(this); |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| nearby_process_observer_.Add(process_manager_); |
| power_client_->AddObserver(this); |
| certificate_manager_->AddObserver(this); |
| |
| settings_.AddSettingsObserver(settings_receiver_.BindNewPipeAndPassRemote()); |
| |
| GetBluetoothAdapter(); |
| |
| nearby_notification_manager_ = std::make_unique<NearbyNotificationManager>( |
| notification_display_service, this, prefs, profile_); |
| |
| if (settings_.GetEnabled()) { |
| local_device_data_manager_->Start(); |
| contact_manager_->Start(); |
| certificate_manager_->Start(); |
| } |
| } |
| |
| NearbySharingServiceImpl::~NearbySharingServiceImpl() { |
| // Make sure the service has been shut down properly before. |
| DCHECK(!nearby_notification_manager_); |
| DCHECK(!bluetooth_adapter_ || !bluetooth_adapter_->HasObserver(this)); |
| } |
| |
| void NearbySharingServiceImpl::Shutdown() { |
| // Before we clean up, lets give observers a heads up we are shutting down. |
| for (auto& observer : observers_) { |
| observer.OnShutdown(); |
| } |
| observers_.Clear(); |
| |
| // Clear in-progress transfers. |
| ClearOutgoingShareTargetInfoMap(); |
| incoming_share_target_info_map_.clear(); |
| |
| StopAdvertising(); |
| StopFastInitiationAdvertising(); |
| StopScanning(); |
| nearby_connections_manager_->Shutdown(); |
| |
| // Destroy NearbyNotificationManager as its profile has been shut down. |
| nearby_notification_manager_.reset(); |
| |
| // Stop listening to NearbyProcessManager events and stop the utility process. |
| nearby_process_observer_.Remove(process_manager_); |
| if (process_manager_->IsActiveProfile(profile_)) |
| process_manager_->StopProcess(profile_); |
| |
| power_client_->RemoveObserver(this); |
| certificate_manager_->RemoveObserver(this); |
| |
| // TODO(crbug/1147652): The call to update the advertising interval is |
| // removed to prevent a Bluez crash. We need to either reduce the global |
| // advertising interval asynchronously and wait for the result or use the |
| // updated API referenced in the bug which allows setting a per-advertisement |
| // interval. |
| |
| if (bluetooth_adapter_) |
| bluetooth_adapter_->RemoveObserver(this); |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| auto* session_controller = ash::SessionController::Get(); |
| if (session_controller) |
| session_controller->RemoveObserver(this); |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| foreground_receive_callbacks_.Clear(); |
| background_receive_callbacks_.Clear(); |
| foreground_send_transfer_callbacks_.Clear(); |
| foreground_send_discovery_callbacks_.Clear(); |
| background_send_transfer_callbacks_.Clear(); |
| background_send_discovery_callbacks_.Clear(); |
| |
| last_incoming_metadata_.reset(); |
| last_outgoing_metadata_.reset(); |
| attachment_info_map_.clear(); |
| mutual_acceptance_timeout_alarm_.Cancel(); |
| disconnection_timeout_alarms_.clear(); |
| |
| is_transferring_ = false; |
| is_receiving_files_ = false; |
| is_sending_files_ = false; |
| is_connecting_ = false; |
| |
| settings_receiver_.reset(); |
| |
| if (settings_.GetEnabled()) { |
| local_device_data_manager_->Stop(); |
| contact_manager_->Stop(); |
| certificate_manager_->Stop(); |
| } |
| |
| process_shutdown_pending_timer_.Stop(); |
| rotate_background_advertisement_timer_.Stop(); |
| |
| // |profile_| has now been shut down so we shouldn't use it anymore. |
| profile_ = nullptr; |
| } |
| |
| void NearbySharingServiceImpl::AddObserver( |
| NearbySharingService::Observer* observer) { |
| observers_.AddObserver(observer); |
| } |
| |
| void NearbySharingServiceImpl::RemoveObserver( |
| NearbySharingService::Observer* observer) { |
| observers_.RemoveObserver(observer); |
| } |
| |
| bool NearbySharingServiceImpl::HasObserver( |
| NearbySharingService::Observer* observer) { |
| return observers_.HasObserver(observer); |
| } |
| |
| NearbySharingService::StatusCodes NearbySharingServiceImpl::RegisterSendSurface( |
| TransferUpdateCallback* transfer_callback, |
| ShareTargetDiscoveredCallback* discovery_callback, |
| SendSurfaceState state) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(transfer_callback); |
| DCHECK(discovery_callback); |
| DCHECK_NE(state, SendSurfaceState::kUnknown); |
| |
| if (foreground_send_transfer_callbacks_.HasObserver(transfer_callback) || |
| background_send_transfer_callbacks_.HasObserver(transfer_callback)) { |
| NS_LOG(VERBOSE) << __func__ |
| << ": RegisterSendSurface failed. Already registered for a " |
| "different state."; |
| return StatusCodes::kError; |
| } |
| |
| if (state == SendSurfaceState::kForeground) { |
| foreground_send_transfer_callbacks_.AddObserver(transfer_callback); |
| foreground_send_discovery_callbacks_.AddObserver(discovery_callback); |
| } else { |
| background_send_transfer_callbacks_.AddObserver(transfer_callback); |
| background_send_discovery_callbacks_.AddObserver(discovery_callback); |
| } |
| |
| if (is_receiving_files_) { |
| UnregisterSendSurface(transfer_callback, discovery_callback); |
| NS_LOG(VERBOSE) |
| << __func__ |
| << ": Ignore registering (and unregistering if registered) send " |
| "surface because we're currently receiving files."; |
| return StatusCodes::kTransferAlreadyInProgress; |
| } |
| |
| // If the share sheet to be registered is a foreground surface, let it catch |
| // up with most recent transfer metadata immediately. |
| if (state == SendSurfaceState::kForeground && last_outgoing_metadata_) { |
| // When a new share sheet is registered, we want to immediately show the |
| // in-progress bar. |
| discovery_callback->OnShareTargetDiscovered(last_outgoing_metadata_->first); |
| transfer_callback->OnTransferUpdate(last_outgoing_metadata_->first, |
| last_outgoing_metadata_->second); |
| } |
| |
| // Sync down data from Nearby server when the sending flow starts, making our |
| // best effort to have fresh contact and certificate data. There is no need to |
| // wait for these calls to finish. The periodic server requests will typically |
| // be sufficient, but we don't want the user to be blocked for hours waiting |
| // for a periodic sync. |
| if (state == SendSurfaceState::kForeground && !last_outgoing_metadata_) { |
| NS_LOG(VERBOSE) |
| << __func__ |
| << ": Downloading local device data, contacts, and certificates from " |
| << "Nearby server at start of sending flow."; |
| local_device_data_manager_->DownloadDeviceData(); |
| contact_manager_->DownloadContacts(); |
| certificate_manager_->DownloadPublicCertificates(); |
| } |
| |
| // Let newly registered send surface catch up with discovered share targets |
| // from current scanning session. |
| for (const std::pair<std::string, ShareTarget>& item : |
| outgoing_share_target_map_) { |
| discovery_callback->OnShareTargetDiscovered(item.second); |
| } |
| |
| NS_LOG(VERBOSE) << __func__ |
| << ": A SendSurface has been registered for state: " |
| << SendSurfaceStateToString(state); |
| InvalidateSendSurfaceState(); |
| return StatusCodes::kOk; |
| } |
| |
| NearbySharingService::StatusCodes |
| NearbySharingServiceImpl::UnregisterSendSurface( |
| TransferUpdateCallback* transfer_callback, |
| ShareTargetDiscoveredCallback* discovery_callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(transfer_callback); |
| DCHECK(discovery_callback); |
| if (!foreground_send_transfer_callbacks_.HasObserver(transfer_callback) && |
| !background_send_transfer_callbacks_.HasObserver(transfer_callback)) { |
| NS_LOG(VERBOSE) |
| << __func__ |
| << ": unregisterSendSurface failed. Unknown TransferUpdateCallback"; |
| return StatusCodes::kError; |
| } |
| |
| if (foreground_send_transfer_callbacks_.might_have_observers() && |
| last_outgoing_metadata_ && |
| last_outgoing_metadata_->second.is_final_status()) { |
| // We already saw the final status in the foreground |
| // Nullify it so the next time the user opens sharing, it starts the UI from |
| // the beginning |
| last_outgoing_metadata_.reset(); |
| } |
| |
| SendSurfaceState state = SendSurfaceState::kUnknown; |
| if (foreground_send_transfer_callbacks_.HasObserver(transfer_callback)) { |
| foreground_send_transfer_callbacks_.RemoveObserver(transfer_callback); |
| foreground_send_discovery_callbacks_.RemoveObserver(discovery_callback); |
| state = SendSurfaceState::kForeground; |
| } else { |
| background_send_transfer_callbacks_.RemoveObserver(transfer_callback); |
| background_send_discovery_callbacks_.RemoveObserver(discovery_callback); |
| state = SendSurfaceState::kBackground; |
| } |
| |
| // Displays the most recent payload status processed by foreground surfaces on |
| // background surfaces. |
| if (!foreground_send_transfer_callbacks_.might_have_observers() && |
| last_outgoing_metadata_) { |
| for (TransferUpdateCallback& background_transfer_callback : |
| background_send_transfer_callbacks_) { |
| background_transfer_callback.OnTransferUpdate( |
| last_outgoing_metadata_->first, last_outgoing_metadata_->second); |
| } |
| } |
| |
| NS_LOG(VERBOSE) << __func__ << ": A SendSurface has been unregistered: " |
| << SendSurfaceStateToString(state); |
| InvalidateSurfaceState(); |
| return StatusCodes::kOk; |
| } |
| |
| NearbySharingService::StatusCodes |
| NearbySharingServiceImpl::RegisterReceiveSurface( |
| TransferUpdateCallback* transfer_callback, |
| ReceiveSurfaceState state) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(transfer_callback); |
| DCHECK_NE(state, ReceiveSurfaceState::kUnknown); |
| |
| // Only check these errors cases for foreground receivers. |
| if (state == ReceiveSurfaceState::kForeground) { |
| if (is_sending_files_) { |
| UnregisterReceiveSurface(transfer_callback); |
| NS_LOG(VERBOSE) |
| << __func__ |
| << ": Ignore registering (and unregistering if registered) receive " |
| "surface, because we're currently sending files."; |
| return StatusCodes::kTransferAlreadyInProgress; |
| } |
| |
| if (!HasAvailableConnectionMediums()) { |
| NS_LOG(VERBOSE) << __func__ << ": No available connection medium."; |
| return StatusCodes::kNoAvailableConnectionMedium; |
| } |
| } |
| |
| // We specifically allow re-registring with out error so it is clear to caller |
| // that the transfer_callback is currently registered. |
| if (GetReceiveCallbacksFromState(state).HasObserver(transfer_callback)) { |
| NS_LOG(VERBOSE) << __func__ |
| << ": transfer callback already registered, ignoring"; |
| return StatusCodes::kOk; |
| } else if (foreground_receive_callbacks_.HasObserver(transfer_callback) || |
| background_receive_callbacks_.HasObserver(transfer_callback)) { |
| NS_LOG(ERROR) |
| << __func__ |
| << ": transfer callback already registered but for a different state."; |
| return StatusCodes::kError; |
| } |
| |
| // If the receive surface to be registered is a foreground surface, let it |
| // catch up with most recent transfer metadata immediately. |
| if (state == ReceiveSurfaceState::kForeground && last_incoming_metadata_) { |
| transfer_callback->OnTransferUpdate(last_incoming_metadata_->first, |
| last_incoming_metadata_->second); |
| } |
| |
| GetReceiveCallbacksFromState(state).AddObserver(transfer_callback); |
| |
| NS_LOG(VERBOSE) << __func__ << ": A ReceiveSurface(" |
| << ReceiveSurfaceStateToString(state) |
| << ") has been registered"; |
| InvalidateReceiveSurfaceState(); |
| return StatusCodes::kOk; |
| } |
| |
| NearbySharingService::StatusCodes |
| NearbySharingServiceImpl::UnregisterReceiveSurface( |
| TransferUpdateCallback* transfer_callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(transfer_callback); |
| bool is_foreground = |
| foreground_receive_callbacks_.HasObserver(transfer_callback); |
| bool is_background = |
| background_receive_callbacks_.HasObserver(transfer_callback); |
| if (!is_foreground && !is_background) { |
| NS_LOG(VERBOSE) |
| << __func__ |
| << ": Unknown transfer callback was un-registered, ignoring."; |
| // We intentionally allow this be successful so the caller can be sure |
| // they are not registered anymore. |
| return StatusCodes::kOk; |
| } |
| |
| if (foreground_receive_callbacks_.might_have_observers() && |
| last_incoming_metadata_ && |
| last_incoming_metadata_->second.is_final_status()) { |
| // We already saw the final status in the foreground. |
| // Nullify it so the next time the user opens sharing, it starts the UI from |
| // the beginning |
| last_incoming_metadata_.reset(); |
| } |
| |
| if (is_foreground) { |
| foreground_receive_callbacks_.RemoveObserver(transfer_callback); |
| } else { |
| background_receive_callbacks_.RemoveObserver(transfer_callback); |
| } |
| |
| // Displays the most recent payload status processed by foreground surfaces on |
| // background surface. |
| if (!foreground_receive_callbacks_.might_have_observers() && |
| last_incoming_metadata_) { |
| for (TransferUpdateCallback& background_callback : |
| background_receive_callbacks_) { |
| background_callback.OnTransferUpdate(last_incoming_metadata_->first, |
| last_incoming_metadata_->second); |
| } |
| } |
| |
| NS_LOG(VERBOSE) << __func__ << ": A ReceiveSurface(" |
| << (is_foreground ? "foreground" : "background") |
| << ") has been unregistered"; |
| InvalidateSurfaceState(); |
| return StatusCodes::kOk; |
| } |
| |
| NearbySharingService::StatusCodes |
| NearbySharingServiceImpl::ClearForegroundReceiveSurfaces() { |
| std::vector<TransferUpdateCallback*> fg_receivers; |
| for (auto& callback : foreground_receive_callbacks_) |
| fg_receivers.push_back(&callback); |
| |
| StatusCodes status = StatusCodes::kOk; |
| for (TransferUpdateCallback* callback : fg_receivers) { |
| if (UnregisterReceiveSurface(callback) != StatusCodes::kOk) |
| status = StatusCodes::kError; |
| } |
| return status; |
| } |
| |
| bool NearbySharingServiceImpl::IsInHighVisibility() { |
| return in_high_visibility; |
| } |
| |
| NearbySharingService::StatusCodes NearbySharingServiceImpl::SendAttachments( |
| const ShareTarget& share_target, |
| std::vector<std::unique_ptr<Attachment>> attachments) { |
| if (!is_scanning_) { |
| NS_LOG(WARNING) << __func__ |
| << ": Failed to send attachments. Not scanning."; |
| return StatusCodes::kError; |
| } |
| |
| // |is_scanning_| means at least one send transfer callback. |
| DCHECK(foreground_send_transfer_callbacks_.might_have_observers() || |
| background_send_transfer_callbacks_.might_have_observers()); |
| // |is_scanning_| and |is_transferring_| are mutually exclusive. |
| DCHECK(!is_transferring_); |
| |
| ShareTargetInfo* info = GetShareTargetInfo(share_target); |
| if (!info || !info->endpoint_id()) { |
| // TODO(crbug.com/1119276): Support scanning for unknown share targets. |
| NS_LOG(WARNING) << __func__ |
| << ": Failed to send attachments. Unknown ShareTarget."; |
| return StatusCodes::kError; |
| } |
| |
| ShareTarget share_target_copy = share_target; |
| for (std::unique_ptr<Attachment>& attachment : attachments) { |
| DCHECK(attachment); |
| attachment->MoveToShareTarget(share_target_copy); |
| } |
| |
| if (!share_target_copy.has_attachments()) { |
| NS_LOG(WARNING) << __func__ << ": No attachments to send."; |
| return StatusCodes::kError; |
| } |
| |
| // For sending advertisement from scanner, the request advertisement should |
| // always be visible to everyone. |
| base::Optional<std::vector<uint8_t>> endpoint_info = |
| CreateEndpointInfo(local_device_data_manager_->GetDeviceName()); |
| if (!endpoint_info) { |
| NS_LOG(WARNING) << __func__ << ": Could not create local endpoint info."; |
| return StatusCodes::kError; |
| } |
| |
| info->set_transfer_update_callback(std::make_unique<TransferUpdateDecorator>( |
| base::BindRepeating(&NearbySharingServiceImpl::OnOutgoingTransferUpdate, |
| weak_ptr_factory_.GetWeakPtr()))); |
| |
| OnTransferStarted(/*is_incoming=*/false); |
| is_connecting_ = true; |
| InvalidateSendSurfaceState(); |
| |
| // Send process initialized successfully, from now on status updated will be |
| // sent out via OnOutgoingTransferUpdate(). |
| info->transfer_update_callback()->OnTransferUpdate( |
| share_target_copy, TransferMetadataBuilder() |
| .set_status(TransferMetadata::Status::kConnecting) |
| .build()); |
| |
| CreatePayloads(std::move(share_target_copy), |
| base::BindOnce(&NearbySharingServiceImpl::OnCreatePayloads, |
| weak_ptr_factory_.GetWeakPtr(), |
| std::move(*endpoint_info))); |
| |
| return StatusCodes::kOk; |
| } |
| |
| void NearbySharingServiceImpl::Accept( |
| const ShareTarget& share_target, |
| StatusCodesCallback status_codes_callback) { |
| ShareTargetInfo* info = GetShareTargetInfo(share_target); |
| if (!info || !info->connection()) { |
| NS_LOG(WARNING) << __func__ << ": Accept invoked for unknown share target"; |
| std::move(status_codes_callback).Run(StatusCodes::kOutOfOrderApiCall); |
| return; |
| } |
| |
| base::Optional<std::pair<ShareTarget, TransferMetadata>> metadata = |
| share_target.is_incoming ? last_incoming_metadata_ |
| : last_outgoing_metadata_; |
| if (!metadata || metadata->second.status() != |
| TransferMetadata::Status::kAwaitingLocalConfirmation) { |
| std::move(status_codes_callback).Run(StatusCodes::kOutOfOrderApiCall); |
| return; |
| } |
| |
| if (share_target.is_incoming) { |
| ReceivePayloads(share_target, std::move(status_codes_callback)); |
| return; |
| } |
| |
| std::move(status_codes_callback).Run(SendPayloads(share_target)); |
| } |
| |
| void NearbySharingServiceImpl::Reject( |
| const ShareTarget& share_target, |
| StatusCodesCallback status_codes_callback) { |
| ShareTargetInfo* info = GetShareTargetInfo(share_target); |
| if (!info || !info->connection()) { |
| NS_LOG(WARNING) << __func__ << ": Reject invoked for unknown share target"; |
| std::move(status_codes_callback).Run(StatusCodes::kOutOfOrderApiCall); |
| return; |
| } |
| NearbyConnection* connection = info->connection(); |
| |
| base::SequencedTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&NearbySharingServiceImpl::CloseConnection, |
| weak_ptr_factory_.GetWeakPtr(), share_target), |
| kIncomingRejectionDelay); |
| |
| connection->SetDisconnectionListener( |
| base::BindOnce(&NearbySharingServiceImpl::UnregisterShareTarget, |
| weak_ptr_factory_.GetWeakPtr(), share_target)); |
| |
| WriteResponse(*connection, sharing::nearby::ConnectionResponseFrame::REJECT); |
| NS_LOG(VERBOSE) << __func__ |
| << ": Successfully wrote a rejection response frame"; |
| |
| if (info->transfer_update_callback()) { |
| info->transfer_update_callback()->OnTransferUpdate( |
| share_target, TransferMetadataBuilder() |
| .set_status(TransferMetadata::Status::kRejected) |
| .build()); |
| } |
| |
| std::move(status_codes_callback).Run(StatusCodes::kOk); |
| } |
| |
| void NearbySharingServiceImpl::Cancel( |
| const ShareTarget& share_target, |
| StatusCodesCallback status_codes_callback) { |
| NS_LOG(INFO) << __func__ << ": User canceled transfer"; |
| DoCancel(share_target, std::move(status_codes_callback), |
| /*write_cancel_frame=*/true); |
| } |
| |
| void NearbySharingServiceImpl::DoCancel( |
| ShareTarget share_target, |
| StatusCodesCallback status_codes_callback, |
| bool write_cancel_frame) { |
| ShareTargetInfo* info = GetShareTargetInfo(share_target); |
| if (!info || !info->endpoint_id()) { |
| NS_LOG(ERROR) << __func__ |
| << ": Cancel invoked for unknown share target, returning " |
| "kOutOfOrderApiCall"; |
| // Make sure to clean up files just in case. |
| RemoveIncomingPayloads(share_target); |
| std::move(status_codes_callback).Run(StatusCodes::kOutOfOrderApiCall); |
| return; |
| } |
| |
| // For metrics. |
| cancelled_share_target_ids_.insert(share_target.id); |
| |
| // Cancel all ongoing payload transfers before invoking the transfer update |
| // callback. Invoking the transfer update callback first could result in |
| // payload cleanup before we have a chance to cancel the payload via Nearby |
| // Connections, and the payload tracker might not receive the expected |
| // cancellation signals. Also, note that there might not be any ongoing |
| // payload transfer, for example, if a connection has not been established |
| // yet. |
| for (int64_t attachment_id : share_target.GetAttachmentIds()) { |
| base::Optional<int64_t> payload_id = GetAttachmentPayloadId(attachment_id); |
| if (payload_id) { |
| nearby_connections_manager_->Cancel(*payload_id); |
| } |
| } |
| |
| // Inform the user that the transfer has been cancelled before disconnecting |
| // because subsequent disconnections might be interpreted as failure. The |
| // TransferUpdateDecorator will ignore subsequent statuses in favor of this |
| // cancelled status. Note that the transfer update callback might have already |
| // been invoked as a result of the payload cancellations above, but again, |
| // superfluous status updates are handled gracefully by the |
| // TransferUpdateDecorator. |
| if (info->transfer_update_callback()) { |
| info->transfer_update_callback()->OnTransferUpdate( |
| share_target, TransferMetadataBuilder() |
| .set_status(TransferMetadata::Status::kCancelled) |
| .build()); |
| } |
| |
| // If a connection exists, close the connection after a short delay that |
| // allows for final processing by the other device. Otherwise, disconnect from |
| // endpoint id directly. Note: A share attempt can be cancelled by the user |
| // before a connection is fully established, in which case, info->connection() |
| // will be null. |
| if (info->connection()) { |
| info->connection()->SetDisconnectionListener( |
| base::BindOnce(&NearbySharingServiceImpl::UnregisterShareTarget, |
| weak_ptr_factory_.GetWeakPtr(), share_target)); |
| base::SequencedTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&NearbySharingServiceImpl::CloseConnection, |
| weak_ptr_factory_.GetWeakPtr(), share_target), |
| kIncomingCancelDelay); |
| |
| if (write_cancel_frame) { |
| WriteCancel(*info->connection()); |
| } |
| } else { |
| nearby_connections_manager_->Disconnect(*info->endpoint_id()); |
| UnregisterShareTarget(share_target); |
| } |
| |
| std::move(status_codes_callback).Run(StatusCodes::kOk); |
| } |
| |
| void NearbySharingServiceImpl::Open(const ShareTarget& share_target, |
| StatusCodesCallback status_codes_callback) { |
| std::move(status_codes_callback).Run(StatusCodes::kOk); |
| } |
| |
| void NearbySharingServiceImpl::OpenURL(GURL url) { |
| DCHECK(profile_); |
| chrome::ScopedTabbedBrowserDisplayer displayer(profile_); |
| chrome::AddSelectedTabWithURL(displayer.browser(), url, |
| ui::PAGE_TRANSITION_AUTO_TOPLEVEL); |
| } |
| |
| NearbyNotificationDelegate* NearbySharingServiceImpl::GetNotificationDelegate( |
| const std::string& notification_id) { |
| if (!nearby_notification_manager_) |
| return nullptr; |
| |
| return nearby_notification_manager_->GetNotificationDelegate(notification_id); |
| } |
| |
| NearbyShareSettings* NearbySharingServiceImpl::GetSettings() { |
| return &settings_; |
| } |
| |
| NearbyShareHttpNotifier* NearbySharingServiceImpl::GetHttpNotifier() { |
| return &nearby_share_http_notifier_; |
| } |
| |
| NearbyShareLocalDeviceDataManager* |
| NearbySharingServiceImpl::GetLocalDeviceDataManager() { |
| return local_device_data_manager_.get(); |
| } |
| |
| NearbyShareContactManager* NearbySharingServiceImpl::GetContactManager() { |
| return contact_manager_.get(); |
| } |
| |
| NearbyShareCertificateManager* |
| NearbySharingServiceImpl::GetCertificateManager() { |
| return certificate_manager_.get(); |
| } |
| |
| void NearbySharingServiceImpl::OnNearbyProfileChanged(Profile* profile) { |
| // TODO(crbug.com/1084576): Notify UI about the new active profile. |
| if (profile) { |
| NS_LOG(VERBOSE) << __func__ << ": Active Nearby profile changed"; |
| } else { |
| NS_LOG(VERBOSE) << __func__ << ": Active Nearby profile cleared"; |
| } |
| InvalidateSurfaceState(); |
| } |
| |
| void NearbySharingServiceImpl::OnNearbyProcessStarted() { |
| DCHECK(profile_); |
| if (process_manager_->IsActiveProfile(profile_)) { |
| NS_LOG(VERBOSE) << __func__ |
| << ": Nearby process started for active profile"; |
| } |
| } |
| |
| void NearbySharingServiceImpl::OnNearbyProcessStopped() { |
| DCHECK(profile_); |
| InvalidateSurfaceState(); |
| if (process_manager_->IsActiveProfile(profile_)) { |
| NS_LOG(VERBOSE) << __func__ |
| << ": Nearby process stopped for active profile"; |
| } |
| } |
| |
| void NearbySharingServiceImpl::OnIncomingConnection( |
| const std::string& endpoint_id, |
| const std::vector<uint8_t>& endpoint_info, |
| NearbyConnection* connection) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(connection); |
| DCHECK(profile_); |
| |
| // Sync down data from Nearby server when the receiving flow starts, making |
| // our best effort to have fresh contact and certificate data. There is no |
| // need to wait for these calls to finish. The periodic server requests will |
| // typically be sufficient, but we don't want the user to be blocked for hours |
| // waiting for a periodic sync. |
| NS_LOG(VERBOSE) |
| << __func__ |
| << ": Downloading local device data, contacts, and certificates from " |
| << "Nearby server at start of receiving flow."; |
| local_device_data_manager_->DownloadDeviceData(); |
| contact_manager_->DownloadContacts(); |
| certificate_manager_->DownloadPublicCertificates(); |
| |
| ShareTarget placeholder_share_target; |
| placeholder_share_target.is_incoming = true; |
| ShareTargetInfo& share_target_info = |
| GetOrCreateShareTargetInfo(placeholder_share_target, endpoint_id); |
| share_target_info.set_connection(connection); |
| |
| connection->SetDisconnectionListener( |
| base::BindOnce(&NearbySharingServiceImpl::RefreshUIOnDisconnection, |
| weak_ptr_factory_.GetWeakPtr(), placeholder_share_target)); |
| |
| process_manager_->GetOrStartNearbySharingDecoder(profile_) |
| ->DecodeAdvertisement( |
| endpoint_info, |
| base::BindOnce( |
| &NearbySharingServiceImpl::OnIncomingAdvertisementDecoded, |
| weak_ptr_factory_.GetWeakPtr(), endpoint_id, |
| std::move(placeholder_share_target))); |
| } |
| |
| void NearbySharingServiceImpl::FlushMojoForTesting() { |
| settings_receiver_.FlushForTesting(); |
| } |
| |
| void NearbySharingServiceImpl::OnEnabledChanged(bool enabled) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (enabled) { |
| NS_LOG(VERBOSE) << __func__ << ": Nearby sharing enabled!"; |
| local_device_data_manager_->Start(); |
| contact_manager_->Start(); |
| certificate_manager_->Start(); |
| } else { |
| NS_LOG(VERBOSE) << __func__ << ": Nearby sharing disabled!"; |
| StopAdvertising(); |
| StopScanning(); |
| nearby_connections_manager_->Shutdown(); |
| local_device_data_manager_->Stop(); |
| contact_manager_->Stop(); |
| certificate_manager_->Stop(); |
| } |
| InvalidateSurfaceState(); |
| } |
| |
| void NearbySharingServiceImpl::OnDeviceNameChanged( |
| const std::string& device_name) { |
| NS_LOG(VERBOSE) << __func__ << ": Nearby sharing device name changed"; |
| // TODO(vecore): handle device name change |
| } |
| |
| void NearbySharingServiceImpl::OnDataUsageChanged(DataUsage data_usage) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| NS_LOG(VERBOSE) << __func__ << ": Nearby sharing data usage changed to " |
| << data_usage; |
| |
| if (advertising_power_level_ != PowerLevel::kUnknown) |
| StopAdvertising(); |
| |
| InvalidateSurfaceState(); |
| } |
| |
| void NearbySharingServiceImpl::OnVisibilityChanged(Visibility new_visibility) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| NS_LOG(VERBOSE) << __func__ << ": Nearby sharing visibility changed to " |
| << new_visibility; |
| if (advertising_power_level_ != PowerLevel::kUnknown) |
| StopAdvertising(); |
| |
| InvalidateSurfaceState(); |
| } |
| |
| void NearbySharingServiceImpl::OnAllowedContactsChanged( |
| const std::vector<std::string>& allowed_contacts) { |
| NS_LOG(VERBOSE) << __func__ << ": Nearby sharing visible contacts changed"; |
| // TODO(vecore): handle visible contacts change |
| } |
| |
| void NearbySharingServiceImpl::OnPublicCertificatesDownloaded() { |
| // TODO(https://crbug.com/1152158): Possibly restart scanning after public |
| // certificates are downloaded. |
| } |
| |
| void NearbySharingServiceImpl::OnPrivateCertificatesChanged() { |
| // If we are currently advertising, restart advertising using the updated |
| // private certificates. |
| if (rotate_background_advertisement_timer_.IsRunning()) { |
| NS_LOG(VERBOSE) |
| << __func__ |
| << ": Private certificates changed; rotating background advertisement."; |
| rotate_background_advertisement_timer_.FireNow(); |
| } |
| } |
| |
| void NearbySharingServiceImpl::OnEndpointDiscovered( |
| const std::string& endpoint_id, |
| const std::vector<uint8_t>& endpoint_info) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(profile_); |
| if (!is_scanning_) { |
| NS_LOG(VERBOSE) |
| << __func__ |
| << ": Ignoring discovered endpoint because we're no longer scanning"; |
| return; |
| } |
| |
| process_manager_->GetOrStartNearbySharingDecoder(profile_) |
| ->DecodeAdvertisement( |
| endpoint_info, |
| base::BindOnce( |
| &NearbySharingServiceImpl::OnOutgoingAdvertisementDecoded, |
| weak_ptr_factory_.GetWeakPtr(), endpoint_id)); |
| } |
| |
| void NearbySharingServiceImpl::OnEndpointLost(const std::string& endpoint_id) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (!is_scanning_) { |
| NS_LOG(VERBOSE) |
| << __func__ |
| << ": Ignoring lost endpoint because we're no longer scanning"; |
| return; |
| } |
| |
| RemoveOutgoingShareTargetWithEndpointId(endpoint_id); |
| } |
| |
| void NearbySharingServiceImpl::OnLockStateChanged(bool locked) { |
| NS_LOG(VERBOSE) << __func__ << ": Screen lock state changed. (" << locked |
| << ")"; |
| is_screen_locked_ = locked; |
| InvalidateSurfaceState(); |
| } |
| |
| void NearbySharingServiceImpl::AdapterPresentChanged( |
| device::BluetoothAdapter* adapter, |
| bool present) { |
| NS_LOG(VERBOSE) << "Bluetooth present changed: " << present; |
| InvalidateSurfaceState(); |
| } |
| |
| void NearbySharingServiceImpl::AdapterPoweredChanged( |
| device::BluetoothAdapter* adapter, |
| bool powered) { |
| NS_LOG(VERBOSE) << "Bluetooth powered changed: " << powered; |
| InvalidateSurfaceState(); |
| } |
| |
| void NearbySharingServiceImpl::SuspendImminent() { |
| NS_LOG(VERBOSE) << __func__ << ": Suspend imminent."; |
| InvalidateSurfaceState(); |
| } |
| |
| void NearbySharingServiceImpl::SuspendDone() { |
| NS_LOG(VERBOSE) << __func__ << ": Suspend done."; |
| InvalidateSurfaceState(); |
| } |
| |
| base::ObserverList<TransferUpdateCallback>& |
| NearbySharingServiceImpl::GetReceiveCallbacksFromState( |
| ReceiveSurfaceState state) { |
| switch (state) { |
| case ReceiveSurfaceState::kForeground: |
| return foreground_receive_callbacks_; |
| case ReceiveSurfaceState::kBackground: |
| return background_receive_callbacks_; |
| case ReceiveSurfaceState::kUnknown: |
| NOTREACHED(); |
| return foreground_receive_callbacks_; |
| } |
| } |
| |
| bool NearbySharingServiceImpl::IsVisibleInBackground(Visibility visibility) { |
| return visibility == Visibility::kAllContacts || |
| visibility == Visibility::kSelectedContacts; |
| } |
| |
| const base::Optional<std::vector<uint8_t>> |
| NearbySharingServiceImpl::CreateEndpointInfo( |
| const base::Optional<std::string>& device_name) { |
| std::vector<uint8_t> salt; |
| std::vector<uint8_t> encrypted_key; |
| |
| nearby_share::mojom::Visibility visibility = settings_.GetVisibility(); |
| if (visibility == Visibility::kAllContacts || |
| visibility == Visibility::kSelectedContacts) { |
| base::Optional<NearbyShareEncryptedMetadataKey> encrypted_metadata_key = |
| certificate_manager_->EncryptPrivateCertificateMetadataKey(visibility); |
| if (encrypted_metadata_key) { |
| salt = encrypted_metadata_key->salt(); |
| encrypted_key = encrypted_metadata_key->encrypted_key(); |
| } else { |
| NS_LOG(WARNING) << __func__ |
| << ": Failed to encrypt private certificate metadata key " |
| << "for advertisement."; |
| } |
| } |
| |
| if (salt.empty() || encrypted_key.empty()) { |
| // Generate random metadata key. |
| salt = GenerateRandomBytes(sharing::Advertisement::kSaltSize); |
| encrypted_key = GenerateRandomBytes( |
| sharing::Advertisement::kMetadataEncryptionKeyHashByteSize); |
| } |
| |
| nearby_share::mojom::ShareTargetType device_type = |
| nearby_share::mojom::ShareTargetType::kLaptop; |
| |
| std::unique_ptr<sharing::Advertisement> advertisement = |
| sharing::Advertisement::NewInstance( |
| std::move(salt), std::move(encrypted_key), device_type, device_name); |
| if (advertisement) { |
| return advertisement->ToEndpointInfo(); |
| } else { |
| return base::nullopt; |
| } |
| } |
| |
| void NearbySharingServiceImpl::GetBluetoothAdapter() { |
| auto* adapter_factory = device::BluetoothAdapterFactory::Get(); |
| if (!adapter_factory->IsBluetoothSupported()) |
| return; |
| |
| // Because this will be called from the constructor, GetAdapter() may call |
| // OnGetBluetoothAdapter() immediately which can cause problems during tests |
| // since the class is not fully constructed yet. |
| base::SequencedTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &device::BluetoothAdapterFactory::GetAdapter, |
| base::Unretained(adapter_factory), |
| base::BindOnce(&NearbySharingServiceImpl::OnGetBluetoothAdapter, |
| weak_ptr_factory_.GetWeakPtr()))); |
| } |
| |
| void NearbySharingServiceImpl::OnGetBluetoothAdapter( |
| scoped_refptr<device::BluetoothAdapter> adapter) { |
| bluetooth_adapter_ = adapter; |
| bluetooth_adapter_->AddObserver(this); |
| |
| // TODO(crbug/1147652): The call to update the advertising interval is |
| // removed to prevent a Bluez crash. We need to either reduce the global |
| // advertising interval asynchronously and wait for the result or use the |
| // updated API referenced in the bug which allows setting a per-advertisement |
| // interval. |
| |
| // TODO(crbug.com/1132469): This was added to fix an issue where advertising |
| // was not starting on sign-in. Add a unit test to cover this case. |
| InvalidateSurfaceState(); |
| } |
| |
| void NearbySharingServiceImpl::StartFastInitiationAdvertising() { |
| NS_LOG(VERBOSE) << __func__ << ": Starting fast initiation advertising."; |
| |
| fast_initiation_manager_ = |
| FastInitiationManager::Factory::Create(bluetooth_adapter_); |
| |
| // TODO(crbug/1147652): The call to update the advertising interval is |
| // removed to prevent a Bluez crash. We need to either reduce the global |
| // advertising interval asynchronously and wait for the result or use the |
| // updated API referenced in the bug which allows setting a per-advertisement |
| // interval. |
| |
| // TODO(crbug.com/1100686): Determine whether to call StartAdvertising() with |
| // kNotify or kSilent. |
| fast_initiation_manager_->StartAdvertising( |
| FastInitiationManager::FastInitType::kNotify, |
| base::BindOnce( |
| &NearbySharingServiceImpl::OnStartFastInitiationAdvertising, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::BindOnce( |
| &NearbySharingServiceImpl::OnStartFastInitiationAdvertisingError, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void NearbySharingServiceImpl::OnStartFastInitiationAdvertising() { |
| // TODO(hansenmichael): Do not invoke |
| // |register_send_surface_callback_| until Nearby Connections |
| // scanning is kicked off. |
| NS_LOG(VERBOSE) << "Started advertising FastInitiation."; |
| } |
| |
| void NearbySharingServiceImpl::OnStartFastInitiationAdvertisingError() { |
| fast_initiation_manager_.reset(); |
| NS_LOG(ERROR) << "Failed to start FastInitiation advertising."; |
| } |
| |
| void NearbySharingServiceImpl::StopFastInitiationAdvertising() { |
| if (!fast_initiation_manager_) { |
| NS_LOG(VERBOSE) |
| << "Can't stop advertising FastInitiation. Not advertising."; |
| return; |
| } |
| |
| fast_initiation_manager_->StopAdvertising( |
| base::BindOnce(&NearbySharingServiceImpl::OnStopFastInitiationAdvertising, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void NearbySharingServiceImpl::OnStopFastInitiationAdvertising() { |
| fast_initiation_manager_.reset(); |
| NS_LOG(VERBOSE) << "Stopped advertising FastInitiation"; |
| |
| // TODO(crbug/1147652): The call to update the advertising interval is |
| // removed to prevent a Bluez crash. We need to either reduce the global |
| // advertising interval asynchronously and wait for the result or use the |
| // updated API referenced in the bug which allows setting a per-advertisement |
| // interval. |
| } |
| |
| void NearbySharingServiceImpl::OnOutgoingAdvertisementDecoded( |
| const std::string& endpoint_id, |
| sharing::mojom::AdvertisementPtr advertisement) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (!advertisement) { |
| NS_LOG(VERBOSE) << __func__ |
| << ": Failed to parse discovered advertisement."; |
| return; |
| } |
| |
| // Now we will report endpoints met before in NearbyConnectionsManager. |
| // Check outgoingShareTargetInfoMap first and pass the same shareTarget if we |
| // found one. |
| |
| // Looking for the ShareTarget based on endpoint id. |
| if (outgoing_share_target_map_.find(endpoint_id) != |
| outgoing_share_target_map_.end()) { |
| return; |
| } |
| |
| // Once we get the advertisement, the first thing to do is decrypt the |
| // certificate. |
| NearbyShareEncryptedMetadataKey encrypted_metadata_key( |
| advertisement->salt, advertisement->encrypted_metadata_key); |
| GetCertificateManager()->GetDecryptedPublicCertificate( |
| std::move(encrypted_metadata_key), |
| base::BindOnce(&NearbySharingServiceImpl::OnOutgoingDecryptedCertificate, |
| weak_ptr_factory_.GetWeakPtr(), endpoint_id, |
| std::move(advertisement))); |
| } |
| |
| void NearbySharingServiceImpl::OnOutgoingDecryptedCertificate( |
| const std::string& endpoint_id, |
| sharing::mojom::AdvertisementPtr advertisement, |
| base::Optional<NearbyShareDecryptedPublicCertificate> certificate) { |
| // Check again for this endpoint id, to avoid race conditions. |
| if (outgoing_share_target_map_.find(endpoint_id) != |
| outgoing_share_target_map_.end()) { |
| return; |
| } |
| |
| // The certificate provides the device name, in order to create a ShareTarget |
| // to represent this remote device. |
| base::Optional<ShareTarget> share_target = CreateShareTarget( |
| endpoint_id, std::move(advertisement), std::move(certificate), |
| /*is_incoming=*/false); |
| if (!share_target) { |
| NS_LOG(VERBOSE) << __func__ |
| << ": Failed to convert advertisement to share target from " |
| "discovered advertisement. Ignoring endpoint."; |
| return; |
| } |
| |
| // Update the endpoint id for the share target. |
| NS_LOG(VERBOSE) << __func__ |
| << ": An endpoint has been discovered, with an advertisement " |
| "containing a valid share target."; |
| |
| // Notifies the user that we discovered a device. |
| for (ShareTargetDiscoveredCallback& discovery_callback : |
| foreground_send_discovery_callbacks_) { |
| discovery_callback.OnShareTargetDiscovered(*share_target); |
| } |
| for (ShareTargetDiscoveredCallback& discovery_callback : |
| background_send_discovery_callbacks_) { |
| discovery_callback.OnShareTargetDiscovered(*share_target); |
| } |
| |
| NS_LOG(VERBOSE) << __func__ << ": Reported OnShareTargetDiscovered " |
| << (base::Time::Now() - scanning_start_timestamp_); |
| |
| // TODO(crbug/1108348) CachingManager should cache known and non-external |
| // share targets. |
| } |
| |
| bool NearbySharingServiceImpl::IsBluetoothPresent() const { |
| return bluetooth_adapter_.get() && bluetooth_adapter_->IsPresent(); |
| } |
| |
| bool NearbySharingServiceImpl::IsBluetoothPowered() const { |
| return IsBluetoothPresent() && bluetooth_adapter_->IsPowered(); |
| } |
| |
| bool NearbySharingServiceImpl::HasAvailableConnectionMediums() { |
| // Check if Wifi or Ethernet LAN is off. Advertisements won't work, so |
| // disable them, unless bluetooth is known to be enabled. Not all platforms |
| // have bluetooth, so wifi LAN is a platform-agnostic check. |
| net::NetworkChangeNotifier::ConnectionType connection_type = |
| net::NetworkChangeNotifier::GetConnectionType(); |
| bool hasNetworkConnection = |
| connection_type == |
| net::NetworkChangeNotifier::ConnectionType::CONNECTION_WIFI || |
| connection_type == |
| net::NetworkChangeNotifier::ConnectionType::CONNECTION_ETHERNET; |
| return IsBluetoothPowered() || (kIsWifiLanSupported && hasNetworkConnection); |
| } |
| |
| void NearbySharingServiceImpl::InvalidateSurfaceState() { |
| InvalidateSendSurfaceState(); |
| InvalidateReceiveSurfaceState(); |
| if (ShouldStopNearbyProcess()) { |
| // We need to debounce the call to shut down the process in case this state |
| // is temporary (we don't want to the thrash the process). Any advertisment, |
| // scanning or transfering will stop this timer from triggering. |
| NS_LOG(INFO) << __func__ |
| << ": Scheduling process shutdown if not needed in 15 seconds"; |
| // NOTE: Using base::Unretained is safe because if shutdown_pending_timer_ |
| // goes out of scope the timer will be canceled. |
| process_shutdown_pending_timer_.Start( |
| FROM_HERE, base::TimeDelta::FromSeconds(15), |
| base::BindOnce(&NearbySharingServiceImpl::OnProcessShutdownTimerFired, |
| base::Unretained(this))); |
| } else { |
| process_shutdown_pending_timer_.Stop(); |
| } |
| } |
| |
| bool NearbySharingServiceImpl::ShouldStopNearbyProcess() { |
| // Nothing to do if we're shutting down the profile. |
| if (!profile_) |
| return false; |
| |
| // Cannot stop process without being the active profile. |
| if (!process_manager_->IsActiveProfile(profile_)) |
| return false; |
| |
| // We're currently advertising. |
| if (advertising_power_level_ != PowerLevel::kUnknown) |
| return false; |
| |
| // We're currently discovering. |
| if (is_scanning_) |
| return false; |
| |
| // We're currently attempting to connect to a remote device. |
| if (is_connecting_) |
| return false; |
| |
| // We're currently sending or receiving a file. |
| if (is_transferring_) |
| return false; |
| |
| // We're not using NearbyConnections, should stop the process. |
| return true; |
| } |
| |
| void NearbySharingServiceImpl::OnProcessShutdownTimerFired() { |
| if (ShouldStopNearbyProcess()) { |
| NS_LOG(INFO) << __func__ |
| << ": Shutdown Process timer fired, shutting down process"; |
| process_manager_->StopProcess(profile_); |
| } |
| } |
| |
| void NearbySharingServiceImpl::InvalidateSendSurfaceState() { |
| InvalidateScanningState(); |
| InvalidateFastInitiationAdvertising(); |
| } |
| |
| void NearbySharingServiceImpl::InvalidateScanningState() { |
| // Nothing to do if we're shutting down the profile. |
| if (!profile_) |
| return; |
| |
| if (power_client_->IsSuspended()) { |
| StopScanning(); |
| NS_LOG(VERBOSE) << __func__ |
| << ": Stopping discovery because the system is suspended."; |
| return; |
| } |
| |
| if (!process_manager_->IsActiveProfile(profile_)) { |
| NS_LOG(VERBOSE) << __func__ |
| << ": Stopping discovery because profile was not active"; |
| StopScanning(); |
| return; |
| } |
| |
| // Screen is off. Do no work. |
| if (is_screen_locked_) { |
| StopScanning(); |
| NS_LOG(VERBOSE) << __func__ |
| << ": Stopping discovery because the screen is locked."; |
| return; |
| } |
| |
| if (!HasAvailableConnectionMediums()) { |
| StopScanning(); |
| NS_LOG(VERBOSE) |
| << __func__ |
| << ": Stopping scanning because both bluetooth and wifi LAN are " |
| "disabled."; |
| return; |
| } |
| |
| // Nearby Sharing is disabled. Don't advertise. |
| if (!settings_.GetEnabled()) { |
| StopScanning(); |
| NS_LOG(VERBOSE) |
| << __func__ |
| << ": Stopping discovery because Nearby Sharing is disabled."; |
| return; |
| } |
| |
| if (is_transferring_ || is_connecting_) { |
| StopScanning(); |
| NS_LOG(VERBOSE) |
| << __func__ |
| << ": Stopping discovery because we're currently in the midst of a " |
| "transfer."; |
| return; |
| } |
| |
| if (!foreground_send_transfer_callbacks_.might_have_observers()) { |
| StopScanning(); |
| NS_LOG(VERBOSE) |
| << __func__ |
| << ": Stopping discovery because no scanning surface has been " |
| "registered."; |
| return; |
| } |
| |
| process_shutdown_pending_timer_.Stop(); |
| // Screen is on, Bluetooth is enabled, and Nearby Sharing is enabled! Start |
| // discovery. |
| StartScanning(); |
| } |
| |
| void NearbySharingServiceImpl::InvalidateFastInitiationAdvertising() { |
| // Nothing to do if we're shutting down the profile. |
| if (!profile_) |
| return; |
| |
| if (power_client_->IsSuspended()) { |
| StopFastInitiationAdvertising(); |
| NS_LOG(VERBOSE) |
| << __func__ |
| << ": Stopping fast init advertising because the system is suspended."; |
| return; |
| } |
| |
| if (!process_manager_->IsActiveProfile(profile_)) { |
| StopFastInitiationAdvertising(); |
| NS_LOG(VERBOSE) |
| << __func__ |
| << ": Stopping fast init advertising because profile was not active"; |
| return; |
| } |
| |
| // Screen is off. Do no work. |
| if (is_screen_locked_) { |
| StopFastInitiationAdvertising(); |
| NS_LOG(VERBOSE) |
| << __func__ |
| << ": Stopping fast init advertising because the screen is locked."; |
| return; |
| } |
| |
| if (!IsBluetoothPowered()) { |
| StopFastInitiationAdvertising(); |
| NS_LOG(VERBOSE) << __func__ |
| << ": Stopping fast init advertising because both " |
| "bluetooth is disabled."; |
| return; |
| } |
| |
| // Nearby Sharing is disabled. Don't fast init advertise. |
| if (!settings_.GetEnabled()) { |
| StopFastInitiationAdvertising(); |
| NS_LOG(VERBOSE) << __func__ |
| << ": Stopping fast init advertising because Nearby " |
| "Sharing is disabled."; |
| return; |
| } |
| |
| if (!foreground_send_transfer_callbacks_.might_have_observers()) { |
| StopFastInitiationAdvertising(); |
| NS_LOG(VERBOSE) << __func__ |
| << ": Stopping fast init advertising because no send " |
| "surface is registered."; |
| return; |
| } |
| |
| if (fast_initiation_manager_) { |
| NS_LOG(VERBOSE) |
| << "Failed to advertise FastInitiation. Already advertising."; |
| return; |
| } |
| |
| process_shutdown_pending_timer_.Stop(); |
| |
| StartFastInitiationAdvertising(); |
| } |
| |
| void NearbySharingServiceImpl::InvalidateReceiveSurfaceState() { |
| InvalidateAdvertisingState(); |
| // TODO(b/161889067) InvalidateFastInitScan(); |
| } |
| |
| void NearbySharingServiceImpl::InvalidateAdvertisingState() { |
| // Nothing to do if we're shutting down the profile. |
| if (!profile_) |
| return; |
| |
| if (power_client_->IsSuspended()) { |
| StopAdvertising(); |
| NS_LOG(VERBOSE) |
| << __func__ |
| << ": Stopping advertising because the system is suspended."; |
| return; |
| } |
| |
| if (!process_manager_->IsActiveProfile(profile_)) { |
| NS_LOG(VERBOSE) << __func__ |
| << ": Stopping advertising because profile was not active"; |
| StopAdvertising(); |
| return; |
| } |
| |
| // Screen is off. Do no work. |
| if (is_screen_locked_) { |
| StopAdvertising(); |
| NS_LOG(VERBOSE) << __func__ |
| << ": Stopping advertising because the screen is locked."; |
| return; |
| } |
| |
| if (!HasAvailableConnectionMediums()) { |
| StopAdvertising(); |
| NS_LOG(VERBOSE) |
| << __func__ |
| << ": Stopping advertising because both bluetooth and wifi LAN are " |
| "disabled."; |
| return; |
| } |
| |
| // Nearby Sharing is disabled. Don't advertise. |
| if (!settings_.GetEnabled()) { |
| StopAdvertising(); |
| NS_LOG(VERBOSE) |
| << __func__ |
| << ": Stopping advertising because Nearby Sharing is disabled."; |
| return; |
| } |
| |
| // We're scanning for other nearby devices. Don't advertise. |
| if (is_scanning_) { |
| StopAdvertising(); |
| NS_LOG(VERBOSE) |
| << __func__ |
| << ": Stopping advertising because we're scanning for other devices."; |
| return; |
| } |
| |
| if (is_transferring_) { |
| StopAdvertising(); |
| NS_LOG(VERBOSE) |
| << __func__ |
| << ": Stopping advertising because we're currently in the midst of " |
| "a transfer."; |
| return; |
| } |
| |
| if (!foreground_receive_callbacks_.might_have_observers() && |
| !background_receive_callbacks_.might_have_observers()) { |
| StopAdvertising(); |
| NS_LOG(VERBOSE) |
| << __func__ |
| << ": Stopping advertising because no receive surface is registered."; |
| return; |
| } |
| |
| if (!IsVisibleInBackground(settings_.GetVisibility()) && |
| !foreground_receive_callbacks_.might_have_observers()) { |
| StopAdvertising(); |
| NS_LOG(VERBOSE) |
| << __func__ |
| << ": Stopping advertising because no high power receive surface " |
| "is registered and device is visible to NO_ONE."; |
| return; |
| } |
| |
| process_shutdown_pending_timer_.Stop(); |
| |
| PowerLevel power_level; |
| if (foreground_receive_callbacks_.might_have_observers()) { |
| power_level = PowerLevel::kHighPower; |
| // TODO(crbug/1100367) handle fast init |
| // } else if (isFastInitDeviceNearby) { |
| // power_level = PowerLevel::kMediumPower; |
| } else { |
| power_level = PowerLevel::kLowPower; |
| } |
| |
| DataUsage data_usage = settings_.GetDataUsage(); |
| if (advertising_power_level_ != PowerLevel::kUnknown) { |
| if (power_level == advertising_power_level_) { |
| NS_LOG(VERBOSE) |
| << __func__ |
| << ": Failed to advertise because we're already advertising with " |
| "power level " |
| << PowerLevelToString(advertising_power_level_) |
| << " and data usage preference " << data_usage; |
| return; |
| } |
| |
| StopAdvertising(); |
| NS_LOG(VERBOSE) << __func__ << ": Restart advertising with power level " |
| << PowerLevelToString(power_level) |
| << " and data usage preference " << data_usage; |
| } |
| |
| base::Optional<std::string> device_name; |
| if (foreground_receive_callbacks_.might_have_observers()) |
| device_name = local_device_data_manager_->GetDeviceName(); |
| |
| // Starts advertising through Nearby Connections. Caller is expected to ensure |
| // |listener| remains valid until StopAdvertising is called. |
| base::Optional<std::vector<uint8_t>> endpoint_info = |
| CreateEndpointInfo(device_name); |
| if (!endpoint_info) { |
| NS_LOG(VERBOSE) << __func__ |
| << ": Unable to advertise since could not parse the " |
| "endpoint info from the advertisement."; |
| return; |
| } |
| |
| // TODO(crbug/1147652): The call to update the advertising interval is |
| // removed to prevent a Bluez crash. We need to either reduce the global |
| // advertising interval asynchronously and wait for the result or use the |
| // updated API referenced in the bug which allows setting a per-advertisement |
| // interval. |
| |
| nearby_connections_manager_->StartAdvertising( |
| *endpoint_info, |
| /*listener=*/this, power_level, data_usage, |
| base::BindOnce(&NearbySharingServiceImpl::OnStartAdvertisingResult, |
| weak_ptr_factory_.GetWeakPtr(), device_name.has_value())); |
| |
| advertising_power_level_ = power_level; |
| NS_LOG(VERBOSE) << __func__ |
| << ": StartAdvertising requested over Nearby Connections: " |
| << " power level: " << PowerLevelToString(power_level) |
| << " visibility: " << settings_.GetVisibility() |
| << " data usage: " << data_usage |
| << " advertise device name?: " |
| << (device_name.has_value() ? "yes" : "no"); |
| |
| ScheduleRotateBackgroundAdvertisementTimer(); |
| } |
| |
| void NearbySharingServiceImpl::StopAdvertising() { |
| SetInHighVisibility(false); |
| if (advertising_power_level_ == PowerLevel::kUnknown) { |
| NS_LOG(VERBOSE) |
| << __func__ |
| << ": Failed to stop advertising because we weren't advertising"; |
| return; |
| } |
| |
| nearby_connections_manager_->StopAdvertising(); |
| advertising_power_level_ = PowerLevel::kUnknown; |
| |
| // TODO(crbug/1147652): The call to update the advertising interval is |
| // removed to prevent a Bluez crash. We need to either reduce the global |
| // advertising interval asynchronously and wait for the result or use the |
| // updated API referenced in the bug which allows setting a per-advertisement |
| // interval. |
| |
| NS_LOG(VERBOSE) << __func__ << ": Advertising has stopped"; |
| } |
| |
| void NearbySharingServiceImpl::StartScanning() { |
| DCHECK(profile_); |
| DCHECK(!power_client_->IsSuspended()); |
| DCHECK(settings_.GetEnabled()); |
| DCHECK(!is_screen_locked_); |
| DCHECK(HasAvailableConnectionMediums()); |
| DCHECK(foreground_send_transfer_callbacks_.might_have_observers()); |
| |
| if (is_scanning_) { |
| NS_LOG(VERBOSE) << __func__ |
| << ": Failed to scan because we're currently scanning."; |
| return; |
| } |
| |
| scanning_start_timestamp_ = base::Time::Now(); |
| is_scanning_ = true; |
| InvalidateReceiveSurfaceState(); |
| |
| ClearOutgoingShareTargetInfoMap(); |
| |
| nearby_connections_manager_->StartDiscovery( |
| /*listener=*/this, settings_.GetDataUsage(), |
| base::BindOnce([](NearbyConnectionsManager::ConnectionsStatus status) { |
| NS_LOG(VERBOSE) << __func__ |
| << ": Scanning start attempted over Nearby Connections " |
| "with result " |
| << status; |
| })); |
| |
| InvalidateSendSurfaceState(); |
| NS_LOG(VERBOSE) << __func__ << ": Scanning has started"; |
| } |
| |
| NearbySharingService::StatusCodes NearbySharingServiceImpl::StopScanning() { |
| if (!is_scanning_) { |
| NS_LOG(VERBOSE) << __func__ |
| << ": Failed to stop scanning because weren't scanning."; |
| return StatusCodes::kStatusAlreadyStopped; |
| } |
| |
| nearby_connections_manager_->StopDiscovery(); |
| is_scanning_ = false; |
| |
| // Note: We don't know if we stopped scanning in preparation to send a file, |
| // or we stopped because the user left the page. We'll invalidate after a |
| // short delay. |
| base::SequencedTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&NearbySharingServiceImpl::InvalidateSurfaceState, |
| weak_ptr_factory_.GetWeakPtr()), |
| kInvalidateDelay); |
| |
| NS_LOG(VERBOSE) << __func__ << ": Scanning has stopped."; |
| return StatusCodes::kOk; |
| } |
| |
| void NearbySharingServiceImpl::ScheduleRotateBackgroundAdvertisementTimer() { |
| uint64_t delayRangeMilliseconds = base::checked_cast<uint64_t>( |
| kBackgroundAdvertisementRotationDelayMax.InMilliseconds() - |
| kBackgroundAdvertisementRotationDelayMin.InMilliseconds()); |
| uint64_t delayMilliseconds = |
| base::RandGenerator(delayRangeMilliseconds) + |
| base::checked_cast<uint64_t>( |
| kBackgroundAdvertisementRotationDelayMin.InMilliseconds()); |
| rotate_background_advertisement_timer_.Start( |
| FROM_HERE, |
| base::TimeDelta::FromMilliseconds( |
| base::checked_cast<uint64_t>(delayMilliseconds)), |
| base::BindOnce( |
| &NearbySharingServiceImpl::OnRotateBackgroundAdvertisementTimerFired, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void NearbySharingServiceImpl::OnRotateBackgroundAdvertisementTimerFired() { |
| if (foreground_receive_callbacks_.might_have_observers()) { |
| ScheduleRotateBackgroundAdvertisementTimer(); |
| } else { |
| StopAdvertising(); |
| InvalidateSurfaceState(); |
| } |
| } |
| |
| void NearbySharingServiceImpl::RemoveOutgoingShareTargetWithEndpointId( |
| const std::string& endpoint_id) { |
| auto it = outgoing_share_target_map_.find(endpoint_id); |
| if (it == outgoing_share_target_map_.end()) |
| return; |
| |
| NS_LOG(VERBOSE) << __func__ << ": Removing (endpoint_id=" << it->first |
| << ", share_target.id=" << it->second.id |
| << ") from outgoing share target map"; |
| ShareTarget share_target = std::move(it->second); |
| outgoing_share_target_map_.erase(it); |
| |
| auto info_it = outgoing_share_target_info_map_.find(share_target.id); |
| if (info_it != outgoing_share_target_info_map_.end()) { |
| file_handler_.ReleaseFilePayloads(info_it->second.ExtractFilePayloads()); |
| outgoing_share_target_info_map_.erase(info_it); |
| } |
| |
| for (ShareTargetDiscoveredCallback& discovery_callback : |
| foreground_send_discovery_callbacks_) { |
| discovery_callback.OnShareTargetLost(share_target); |
| } |
| for (ShareTargetDiscoveredCallback& discovery_callback : |
| background_send_discovery_callbacks_) { |
| discovery_callback.OnShareTargetLost(share_target); |
| } |
| |
| NS_LOG(VERBOSE) << __func__ << ": Reported OnShareTargetLost"; |
| } |
| |
| void NearbySharingServiceImpl::OnTransferComplete() { |
| bool was_sending_files = is_sending_files_; |
| is_receiving_files_ = false; |
| is_transferring_ = false; |
| is_sending_files_ = false; |
| |
| NS_LOG(VERBOSE) << __func__ |
| << ": NearbySharing state change transfer finished"; |
| // Files transfer is done! Receivers can immediately cancel, but senders |
| // should add a short delay to ensure the final in-flight packet(s) make |
| // it to the remote device. |
| base::SequencedTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&NearbySharingServiceImpl::InvalidateSurfaceState, |
| weak_ptr_factory_.GetWeakPtr()), |
| was_sending_files ? kInvalidateSurfaceStateDelayAfterTransferDone |
| : base::TimeDelta()); |
| } |
| |
| void NearbySharingServiceImpl::OnTransferStarted(bool is_incoming) { |
| is_transferring_ = true; |
| if (is_incoming) { |
| is_receiving_files_ = true; |
| } else { |
| is_sending_files_ = true; |
| } |
| InvalidateSurfaceState(); |
| } |
| |
| void NearbySharingServiceImpl::ReceivePayloads( |
| ShareTarget share_target, |
| StatusCodesCallback status_codes_callback) { |
| DCHECK(profile_); |
| mutual_acceptance_timeout_alarm_.Cancel(); |
| |
| base::FilePath download_path = |
| DownloadPrefs::FromDownloadManager( |
| content::BrowserContext::GetDownloadManager(profile_)) |
| ->DownloadPath(); |
| |
| // Register payload path for all valid file payloads. |
| base::flat_map<int64_t, base::FilePath> valid_file_payloads; |
| for (auto& file : share_target.file_attachments) { |
| base::Optional<int64_t> payload_id = GetAttachmentPayloadId(file.id()); |
| if (!payload_id) { |
| NS_LOG(WARNING) |
| << __func__ |
| << ": Failed to register payload path for attachment id - " |
| << file.id(); |
| continue; |
| } |
| |
| base::FilePath file_path = download_path.AppendASCII(file.file_name()); |
| valid_file_payloads.emplace(file.id(), std::move(file_path)); |
| } |
| |
| auto aggregated_success = std::make_unique<bool>(true); |
| bool* aggregated_success_ptr = aggregated_success.get(); |
| |
| if (valid_file_payloads.empty()) { |
| OnPayloadPathsRegistered(share_target, std::move(aggregated_success), |
| std::move(status_codes_callback)); |
| return; |
| } |
| |
| auto all_paths_registered_callback = base::BarrierClosure( |
| valid_file_payloads.size(), |
| base::BindOnce(&NearbySharingServiceImpl::OnPayloadPathsRegistered, |
| weak_ptr_factory_.GetWeakPtr(), share_target, |
| std::move(aggregated_success), |
| std::move(status_codes_callback))); |
| |
| for (const auto& payload : valid_file_payloads) { |
| base::Optional<int64_t> payload_id = GetAttachmentPayloadId(payload.first); |
| DCHECK(payload_id); |
| |
| file_handler_.GetUniquePath( |
| payload.second, |
| base::BindOnce( |
| &NearbySharingServiceImpl::OnUniquePathFetched, |
| weak_ptr_factory_.GetWeakPtr(), payload.first, *payload_id, |
| base::BindOnce( |
| &NearbySharingServiceImpl::OnPayloadPathRegistered, |
| weak_ptr_factory_.GetWeakPtr(), |
| base::ScopedClosureRunner(all_paths_registered_callback), |
| aggregated_success_ptr))); |
| } |
| } |
| |
| NearbySharingService::StatusCodes NearbySharingServiceImpl::SendPayloads( |
| const ShareTarget& share_target) { |
| NS_LOG(VERBOSE) << __func__ << ": Preparing to send payloads to " |
| << share_target.id; |
| ShareTargetInfo* info = GetShareTargetInfo(share_target); |
| if (!info || !info->connection()) { |
| NS_LOG(WARNING) << "Failed to send payload due to missing connection."; |
| return StatusCodes::kOutOfOrderApiCall; |
| } |
| if (!info->transfer_update_callback()) { |
| NS_LOG(WARNING) << "Failed to send payload due to missing transfer update " |
| "callback. Disconnecting."; |
| info->connection()->Close(); |
| return StatusCodes::kOutOfOrderApiCall; |
| } |
| |
| info->transfer_update_callback()->OnTransferUpdate( |
| share_target, |
| TransferMetadataBuilder() |
| .set_token(info->token()) |
| .set_status(TransferMetadata::Status::kAwaitingRemoteAcceptance) |
| .build()); |
| |
| if (!info->endpoint_id()) { |
| info->transfer_update_callback()->OnTransferUpdate( |
| share_target, TransferMetadataBuilder() |
| .set_status(TransferMetadata::Status::kFailed) |
| .build()); |
| info->connection()->Close(); |
| NS_LOG(WARNING) << "Failed to send payload due to missing endpoint id."; |
| return StatusCodes::kOutOfOrderApiCall; |
| } |
| |
| ReceiveConnectionResponse(share_target); |
| return StatusCodes::kOk; |
| } |
| |
| void NearbySharingServiceImpl::OnUniquePathFetched( |
| int64_t attachment_id, |
| int64_t payload_id, |
| base::OnceCallback<void(location::nearby::connections::mojom::Status)> |
| callback, |
| base::FilePath path) { |
| attachment_info_map_[attachment_id].file_path = path; |
| nearby_connections_manager_->RegisterPayloadPath(payload_id, path, |
| std::move(callback)); |
| } |
| |
| void NearbySharingServiceImpl::OnPayloadPathRegistered( |
| base::ScopedClosureRunner closure_runner, |
| bool* aggregated_success, |
| location::nearby::connections::mojom::Status status) { |
| if (status != location::nearby::connections::mojom::Status::kSuccess) |
| *aggregated_success = false; |
| } |
| |
| void NearbySharingServiceImpl::OnPayloadPathsRegistered( |
| const ShareTarget& share_target, |
| std::unique_ptr<bool> aggregated_success, |
| StatusCodesCallback status_codes_callback) { |
| DCHECK(aggregated_success); |
| if (!*aggregated_success) { |
| NS_LOG(WARNING) |
| << __func__ |
| << ": Not all payload paths could be registered successfully."; |
| std::move(status_codes_callback).Run(StatusCodes::kError); |
| return; |
| } |
| |
| ShareTargetInfo* info = GetShareTargetInfo(share_target); |
| if (!info || !info->connection()) { |
| NS_LOG(WARNING) << __func__ << ": Accept invoked for unknown share target"; |
| std::move(status_codes_callback).Run(StatusCodes::kOutOfOrderApiCall); |
| return; |
| } |
| NearbyConnection* connection = info->connection(); |
| |
| if (!info->transfer_update_callback()) { |
| NS_LOG(WARNING) << __func__ |
| << ": Accept invoked for share target without transfer " |
| "update callback. Disconnecting."; |
| connection->Close(); |
| std::move(status_codes_callback).Run(StatusCodes::kOutOfOrderApiCall); |
| return; |
| } |
| |
| info->set_payload_tracker(std::make_unique<PayloadTracker>( |
| share_target, attachment_info_map_, |
| base::BindRepeating(&NearbySharingServiceImpl::OnPayloadTransferUpdate, |
| weak_ptr_factory_.GetWeakPtr()))); |
| |
| // Register status listener for all payloads. |
| for (int64_t attachment_id : share_target.GetAttachmentIds()) { |
| base::Optional<int64_t> payload_id = GetAttachmentPayloadId(attachment_id); |
| if (!payload_id) { |
| NS_LOG(WARNING) << __func__ |
| << ": Failed to retrieve payload for attachment id - " |
| << attachment_id; |
| continue; |
| } |
| |
| NS_LOG(VERBOSE) << __func__ |
| << ": Started listening for progress on payload - " |
| << *payload_id; |
| |
| nearby_connections_manager_->RegisterPayloadStatusListener( |
| *payload_id, info->payload_tracker()); |
| |
| NS_LOG(VERBOSE) << __func__ |
| << ": Accepted incoming files from share target - " |
| << share_target.id; |
| } |
| |
| WriteResponse(*connection, sharing::nearby::ConnectionResponseFrame::ACCEPT); |
| NS_LOG(VERBOSE) << __func__ << ": Successfully wrote response frame"; |
| |
| info->transfer_update_callback()->OnTransferUpdate( |
| share_target, |
| TransferMetadataBuilder() |
| .set_status(TransferMetadata::Status::kAwaitingRemoteAcceptance) |
| .set_token(info->token()) |
| .build()); |
| |
| base::Optional<std::string> endpoint_id = info->endpoint_id(); |
| if (endpoint_id) { |
| nearby_connections_manager_->UpgradeBandwidth(*endpoint_id); |
| } else { |
| NS_LOG(WARNING) << __func__ |
| << ": Failed to initiate bandwidth upgrade. No endpoint_id " |
| "found for target - " |
| << share_target.id; |
| std::move(status_codes_callback).Run(StatusCodes::kOutOfOrderApiCall); |
| return; |
| } |
| |
| std::move(status_codes_callback).Run(StatusCodes::kOk); |
| } |
| |
| void NearbySharingServiceImpl::OnOutgoingConnection( |
| const ShareTarget& share_target, |
| base::TimeTicks connect_start_time, |
| NearbyConnection* connection) { |
| OutgoingShareTargetInfo* info = GetOutgoingShareTargetInfo(share_target); |
| bool success = info && info->endpoint_id() && connection; |
| RecordNearbyShareEstablishConnectionMetrics( |
| success, /*cancelled=*/ |
| base::Contains(cancelled_share_target_ids_, share_target.id), |
| base::TimeTicks::Now() - connect_start_time); |
| |
| if (!success) { |
| NS_LOG(WARNING) << __func__ |
| << ": Failed to initate connection to share target " |
| << share_target.id; |
| if (info && info->transfer_update_callback()) { |
| info->transfer_update_callback()->OnTransferUpdate( |
| share_target, TransferMetadataBuilder() |
| .set_status(TransferMetadata::Status::kFailed) |
| .build()); |
| } |
| return; |
| } |
| |
| info->set_connection(connection); |
| |
| connection->SetDisconnectionListener(base::BindOnce( |
| &NearbySharingServiceImpl::OnOutgoingConnectionDisconnected, |
| weak_ptr_factory_.GetWeakPtr(), share_target)); |
| |
| base::Optional<std::string> four_digit_token = |
| ToFourDigitString(nearby_connections_manager_->GetRawAuthenticationToken( |
| *info->endpoint_id())); |
| |
| RunPairedKeyVerification( |
| share_target, *info->endpoint_id(), |
| base::BindOnce( |
| &NearbySharingServiceImpl::OnOutgoingConnectionKeyVerificationDone, |
| weak_ptr_factory_.GetWeakPtr(), share_target, |
| std::move(four_digit_token))); |
| } |
| |
| void NearbySharingServiceImpl::SendIntroduction( |
| const ShareTarget& share_target, |
| base::Optional<std::string> four_digit_token) { |
| // We successfully connected! Now lets build up Payloads for all the files we |
| // want to send them. We won't send any just yet, but we'll send the Payload |
| // IDs in our our introduction frame so that they know what to expect if they |
| // accept. |
| NS_LOG(VERBOSE) << __func__ << ": Preparing to send introduction to " |
| << share_target.id; |
| |
| ShareTargetInfo* info = GetShareTargetInfo(share_target); |
| if (!info || !info->connection()) { |
| NS_LOG(WARNING) << __func__ << ": No NearbyConnection tied to " |
| << share_target.id; |
| return; |
| } |
| NearbyConnection* connection = info->connection(); |
| |
| if (!info->transfer_update_callback()) { |
| connection->Close(); |
| NS_LOG(WARNING) << __func__ |
| << ": No transfer update callback, disconnecting."; |
| return; |
| } |
| |
| if (!foreground_send_transfer_callbacks_.might_have_observers() && |
| !background_send_transfer_callbacks_.might_have_observers()) { |
| connection->Close(); |
| NS_LOG(WARNING) << __func__ << ": No transfer callbacks, disconnecting."; |
| return; |
| } |
| |
| // Build the introduction. |
| auto introduction = std::make_unique<sharing::nearby::IntroductionFrame>(); |
| NS_LOG(VERBOSE) << __func__ << ": Sending attachments to " << share_target.id; |
| |
| // Write introduction of file payloads. |
| for (const auto& file : share_target.file_attachments) { |
| base::Optional<int64_t> payload_id = GetAttachmentPayloadId(file.id()); |
| if (!payload_id) { |
| NS_LOG(VERBOSE) << __func__ << ": Skipping unknown file attachment"; |
| continue; |
| } |
| auto* file_metadata = introduction->add_file_metadata(); |
| file_metadata->set_id(file.id()); |
| file_metadata->set_name(file.file_name()); |
| file_metadata->set_payload_id(*payload_id); |
| file_metadata->set_type(sharing::ConvertFileMetadataType(file.type())); |
| file_metadata->set_mime_type(file.mime_type()); |
| file_metadata->set_size(file.size()); |
| } |
| |
| // Write introduction of text payloads. |
| for (const auto& text : share_target.text_attachments) { |
| base::Optional<int64_t> payload_id = GetAttachmentPayloadId(text.id()); |
| if (!payload_id) { |
| NS_LOG(VERBOSE) << __func__ << ": Skipping unknown text attachment"; |
| continue; |
| } |
| auto* text_metadata = introduction->add_text_metadata(); |
| text_metadata->set_id(text.id()); |
| text_metadata->set_text_title(text.text_title()); |
| text_metadata->set_type(sharing::ConvertTextMetadataType(text.type())); |
| text_metadata->set_size(text.size()); |
| text_metadata->set_payload_id(*payload_id); |
| } |
| |
| if (introduction->file_metadata_size() == 0 && |
| introduction->text_metadata_size() == 0) { |
| NS_LOG(WARNING) << __func__ |
| << ": No payloads tied to transfer, disconnecting."; |
| info->transfer_update_callback()->OnTransferUpdate( |
| share_target, TransferMetadataBuilder() |
| .set_status(TransferMetadata::Status::kFailed) |
| .build()); |
| connection->Close(); |
| return; |
| } |
| |
| // Write the introduction to the remote device. |
| sharing::nearby::Frame frame; |
| frame.set_version(sharing::nearby::Frame::V1); |
| sharing::nearby::V1Frame* v1_frame = frame.mutable_v1(); |
| v1_frame->set_type(sharing::nearby::V1Frame::INTRODUCTION); |
| v1_frame->set_allocated_introduction(introduction.release()); |
| |
| std::vector<uint8_t> data(frame.ByteSize()); |
| frame.SerializeToArray(data.data(), frame.ByteSize()); |
| connection->Write(std::move(data)); |
| |
| // We've successfully written the introduction, so we now have to wait for the |
| // remote side to accept. |
| NS_LOG(VERBOSE) << __func__ << ": Successfully wrote the introduction frame"; |
| |
| mutual_acceptance_timeout_alarm_.Reset(base::BindOnce( |
| &NearbySharingServiceImpl::OnOutgoingMutualAcceptanceTimeout, |
| weak_ptr_factory_.GetWeakPtr(), share_target)); |
| |
| base::SequencedTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, base::BindOnce(mutual_acceptance_timeout_alarm_.callback()), |
| kReadResponseFrameTimeout); |
| |
| info->transfer_update_callback()->OnTransferUpdate( |
| share_target, |
| TransferMetadataBuilder() |
| .set_status(TransferMetadata::Status::kAwaitingLocalConfirmation) |
| .set_token(four_digit_token) |
| .build()); |
| } |
| |
| void NearbySharingServiceImpl::CreatePayloads( |
| ShareTarget share_target, |
| base::OnceCallback<void(ShareTarget, bool)> callback) { |
| OutgoingShareTargetInfo* info = GetOutgoingShareTargetInfo(share_target); |
| if (!info || !share_target.has_attachments()) { |
| std::move(callback).Run(std::move(share_target), /*success=*/false); |
| return; |
| } |
| |
| if (!info->file_payloads().empty() || !info->text_payloads().empty()) { |
| // We may have already created the payloads in the case of retry, so we can |
| // skip this step. |
| std::move(callback).Run(std::move(share_target), /*success=*/false); |
| return; |
| } |
| |
| info->set_text_payloads(CreateTextPayloads(share_target.text_attachments)); |
| if (share_target.file_attachments.empty()) { |
| std::move(callback).Run(std::move(share_target), /*success=*/true); |
| return; |
| } |
| |
| std::vector<base::FilePath> file_paths; |
| for (const FileAttachment& attachment : share_target.file_attachments) { |
| if (!attachment.file_path()) { |
| NS_LOG(WARNING) << __func__ << ": Got file attachment without path"; |
| std::move(callback).Run(std::move(share_target), /*success=*/false); |
| return; |
| } |
| file_paths.push_back(*attachment.file_path()); |
| } |
| |
| file_handler_.OpenFiles( |
| std::move(file_paths), |
| base::BindOnce(&NearbySharingServiceImpl::OnOpenFiles, |
| weak_ptr_factory_.GetWeakPtr(), std::move(share_target), |
| std::move(callback))); |
| } |
| |
| void NearbySharingServiceImpl::OnCreatePayloads( |
| std::vector<uint8_t> endpoint_info, |
| ShareTarget share_target, |
| bool success) { |
| OutgoingShareTargetInfo* info = GetOutgoingShareTargetInfo(share_target); |
| bool has_payloads = info && (!info->text_payloads().empty() || |
| !info->file_payloads().empty()); |
| if (!success || !has_payloads || !info->endpoint_id()) { |
| NS_LOG(WARNING) << __func__ |
| << ": Failed to send file to remote ShareTarget. Failed to " |
| "create payloads."; |
| if (info && info->transfer_update_callback()) { |
| info->transfer_update_callback()->OnTransferUpdate( |
| share_target, |
| TransferMetadataBuilder() |
| .set_status(TransferMetadata::Status::kMediaUnavailable) |
| .build()); |
| } |
| return; |
| } |
| |
| base::Optional<std::vector<uint8_t>> bluetooth_mac_address = |
| GetBluetoothMacAddress(share_target); |
| |
| DataUsage adjusted_data_usage = CheckFileSizeForDataUsagePreference( |
| settings_.GetDataUsage(), share_target); |
| |
| // For metrics. |
| cancelled_share_target_ids_.clear(); |
| |
| // TODO(crbug.com/1111458): Add preferred transfer type. |
| nearby_connections_manager_->Connect( |
| std::move(endpoint_info), *info->endpoint_id(), |
| std::move(bluetooth_mac_address), adjusted_data_usage, |
| base::BindOnce(&NearbySharingServiceImpl::OnOutgoingConnection, |
| weak_ptr_factory_.GetWeakPtr(), share_target, |
| base::TimeTicks::Now())); |
| } |
| |
| void NearbySharingServiceImpl::OnOpenFiles( |
| ShareTarget share_target, |
| base::OnceCallback<void(ShareTarget, bool)> callback, |
| std::vector<NearbyFileHandler::FileInfo> files) { |
| OutgoingShareTargetInfo* info = GetOutgoingShareTargetInfo(share_target); |
| if (!info || files.size() != share_target.file_attachments.size()) { |
| std::move(callback).Run(std::move(share_target), /*success=*/false); |
| return; |
| } |
| |
| std::vector<location::nearby::connections::mojom::PayloadPtr> payloads; |
| payloads.reserve(files.size()); |
| |
| for (size_t i = 0; i < files.size(); ++i) { |
| FileAttachment& attachment = share_target.file_attachments[i]; |
| attachment.set_size(files[i].size); |
| base::File& file = files[i].file; |
| int64_t payload_id = GeneratePayloadId(); |
| SetAttachmentPayloadId(attachment, payload_id); |
| payloads.push_back(location::nearby::connections::mojom::Payload::New( |
| payload_id, |
| location::nearby::connections::mojom::PayloadContent::NewFile( |
| location::nearby::connections::mojom::FilePayload::New( |
| std::move(file))))); |
| } |
| |
| info->set_file_payloads(std::move(payloads)); |
| std::move(callback).Run(std::move(share_target), /*success=*/true); |
| } |
| |
| std::vector<location::nearby::connections::mojom::PayloadPtr> |
| NearbySharingServiceImpl::CreateTextPayloads( |
| const std::vector<TextAttachment>& attachments) { |
| std::vector<location::nearby::connections::mojom::PayloadPtr> payloads; |
| payloads.reserve(attachments.size()); |
| for (const TextAttachment& attachment : attachments) { |
| const std::string& body = attachment.text_body(); |
| std::vector<uint8_t> bytes(body.begin(), body.end()); |
| |
| int64_t payload_id = GeneratePayloadId(); |
| SetAttachmentPayloadId(attachment, payload_id); |
| payloads.push_back(location::nearby::connections::mojom::Payload::New( |
| payload_id, |
| location::nearby::connections::mojom::PayloadContent::NewBytes( |
| location::nearby::connections::mojom::BytesPayload::New( |
| std::move(bytes))))); |
| } |
| return payloads; |
| } |
| |
| void NearbySharingServiceImpl::WriteResponse( |
| NearbyConnection& connection, |
| sharing::nearby::ConnectionResponseFrame::Status status) { |
| sharing::nearby::Frame frame; |
| frame.set_version(sharing::nearby::Frame::V1); |
| sharing::nearby::V1Frame* v1_frame = frame.mutable_v1(); |
| v1_frame->set_type(sharing::nearby::V1Frame::RESPONSE); |
| v1_frame->mutable_connection_response()->set_status(status); |
| |
| std::vector<uint8_t> data(frame.ByteSize()); |
| frame.SerializeToArray(data.data(), frame.ByteSize()); |
| |
| connection.Write(std::move(data)); |
| } |
| |
| void NearbySharingServiceImpl::WriteCancel(NearbyConnection& connection) { |
| NS_LOG(INFO) << __func__ << ": Writing cancel frame."; |
| |
| sharing::nearby::Frame frame; |
| frame.set_version(sharing::nearby::Frame::V1); |
| sharing::nearby::V1Frame* v1_frame = frame.mutable_v1(); |
| v1_frame->set_type(sharing::nearby::V1Frame::CANCEL); |
| |
| std::vector<uint8_t> data(frame.ByteSize()); |
| frame.SerializeToArray(data.data(), frame.ByteSize()); |
| |
| connection.Write(std::move(data)); |
| } |
| |
| void NearbySharingServiceImpl::Fail(const ShareTarget& share_target, |
| TransferMetadata::Status status) { |
| ShareTargetInfo* info = GetShareTargetInfo(share_target); |
| if (!info || !info->connection()) { |
| NS_LOG(WARNING) << __func__ << ": Fail invoked for unknown share target."; |
| return; |
| } |
| NearbyConnection* connection = info->connection(); |
| |
| base::SequencedTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&NearbySharingServiceImpl::CloseConnection, |
| weak_ptr_factory_.GetWeakPtr(), share_target), |
| kIncomingRejectionDelay); |
| |
| connection->SetDisconnectionListener( |
| base::BindOnce(&NearbySharingServiceImpl::UnregisterShareTarget, |
| weak_ptr_factory_.GetWeakPtr(), share_target)); |
| |
| // Send response to remote device. |
| sharing::nearby::ConnectionResponseFrame::Status response_status; |
| switch (status) { |
| case TransferMetadata::Status::kNotEnoughSpace: |
| response_status = |
| sharing::nearby::ConnectionResponseFrame::NOT_ENOUGH_SPACE; |
| break; |
| |
| case TransferMetadata::Status::kUnsupportedAttachmentType: |
| response_status = |
| sharing::nearby::ConnectionResponseFrame::UNSUPPORTED_ATTACHMENT_TYPE; |
| break; |
| |
| case TransferMetadata::Status::kTimedOut: |
| response_status = sharing::nearby::ConnectionResponseFrame::TIMED_OUT; |
| break; |
| |
| default: |
| response_status = sharing::nearby::ConnectionResponseFrame::UNKNOWN; |
| break; |
| } |
| |
| WriteResponse(*connection, response_status); |
| |
| if (info->transfer_update_callback()) { |
| info->transfer_update_callback()->OnTransferUpdate( |
| share_target, TransferMetadataBuilder().set_status(status).build()); |
| } |
| } |
| |
| void NearbySharingServiceImpl::OnIncomingAdvertisementDecoded( |
| const std::string& endpoint_id, |
| ShareTarget placeholder_share_target, |
| sharing::mojom::AdvertisementPtr advertisement) { |
| NearbyConnection* connection = GetConnection(placeholder_share_target); |
| if (!connection) { |
| NS_LOG(VERBOSE) << __func__ << ": Invalid connection for endoint id - " |
| << endpoint_id; |
| return; |
| } |
| |
| if (!advertisement) { |
| NS_LOG(VERBOSE) << __func__ |
| << "Failed to parse incoming connection from endpoint - " |
| << endpoint_id << ", disconnecting."; |
| connection->Close(); |
| return; |
| } |
| |
| NearbyShareEncryptedMetadataKey encrypted_metadata_key( |
| advertisement->salt, advertisement->encrypted_metadata_key); |
| GetCertificateManager()->GetDecryptedPublicCertificate( |
| std::move(encrypted_metadata_key), |
| base::BindOnce(&NearbySharingServiceImpl::OnIncomingDecryptedCertificate, |
| weak_ptr_factory_.GetWeakPtr(), endpoint_id, |
| std::move(advertisement), |
| std::move(placeholder_share_target))); |
| } |
| |
| void NearbySharingServiceImpl::OnIncomingTransferUpdate( |
| const ShareTarget& share_target, |
| const TransferMetadata& metadata) { |
| // kInProgress status is logged extensively elsewhere so avoid the spam. |
| if (metadata.status() != TransferMetadata::Status::kInProgress) { |
| NS_LOG(VERBOSE) << __func__ << ": Nearby Share service: " |
| << "Incoming transfer update for share target with ID " |
| << share_target.id << ": " |
| << TransferMetadata::StatusToString(metadata.status()); |
| } |
| if (metadata.status() != TransferMetadata::Status::kCancelled && |
| metadata.status() != TransferMetadata::Status::kRejected) { |
| last_incoming_metadata_ = |
| std::make_pair(share_target, TransferMetadataBuilder::Clone(metadata) |
| .set_is_original(false) |
| .build()); |
| } else { |
| last_incoming_metadata_ = base::nullopt; |
| } |
| |
| if (metadata.is_final_status()) { |
| RecordNearbyShareTransferFinalStatusMetric( |
| /*is_incoming=*/true, share_target.type, metadata.status(), |
| share_target.is_known); |
| OnTransferComplete(); |
| if (metadata.status() != TransferMetadata::Status::kComplete) { |
| // For any type of failure, lets make sure any pending files get cleaned |
| // up. |
| RemoveIncomingPayloads(share_target); |
| } |
| } else if (metadata.status() == |
| TransferMetadata::Status::kAwaitingLocalConfirmation) { |
| OnTransferStarted(/*is_incoming=*/true); |
| } |
| |
| base::ObserverList<TransferUpdateCallback>& transfer_callbacks = |
| foreground_receive_callbacks_.might_have_observers() |
| ? foreground_receive_callbacks_ |
| : background_receive_callbacks_; |
| |
| for (TransferUpdateCallback& callback : transfer_callbacks) { |
| callback.OnTransferUpdate(share_target, metadata); |
| } |
| } |
| |
| void NearbySharingServiceImpl::OnOutgoingTransferUpdate( |
| const ShareTarget& share_target, |
| const TransferMetadata& metadata) { |
| // kInProgress status is logged extensively elsewhere so avoid the spam. |
| if (metadata.status() != TransferMetadata::Status::kInProgress) { |
| NS_LOG(VERBOSE) << __func__ << ": Nearby Share service: " |
| << "Outgoing transfer update for share target with ID " |
| << share_target.id << ": " |
| << TransferMetadata::StatusToString(metadata.status()); |
| } |
| |
| if (metadata.is_final_status()) { |
| is_connecting_ = false; |
| RecordNearbyShareTransferFinalStatusMetric( |
| /*is_incoming=*/false, share_target.type, metadata.status(), |
| share_target.is_known); |
| OnTransferComplete(); |
| } else if (metadata.status() == TransferMetadata::Status::kMediaDownloading || |
| metadata.status() == |
| TransferMetadata::Status::kAwaitingLocalConfirmation) { |
| is_connecting_ = false; |
| OnTransferStarted(/*is_incoming=*/false); |
| } |
| |
| bool has_foreground_send_surface = |
| foreground_send_transfer_callbacks_.might_have_observers(); |
| base::ObserverList<TransferUpdateCallback>& transfer_callbacks = |
| has_foreground_send_surface ? foreground_send_transfer_callbacks_ |
| : background_send_transfer_callbacks_; |
| |
| for (TransferUpdateCallback& callback : transfer_callbacks) |
| callback.OnTransferUpdate(share_target, metadata); |
| |
| if (has_foreground_send_surface && metadata.is_final_status()) { |
| last_outgoing_metadata_ = base::nullopt; |
| } else { |
| last_outgoing_metadata_ = |
| std::make_pair(share_target, TransferMetadataBuilder::Clone(metadata) |
| .set_is_original(false) |
| .build()); |
| } |
| } |
| |
| void NearbySharingServiceImpl::CloseConnection( |
| const ShareTarget& share_target) { |
| NearbyConnection* connection = GetConnection(share_target); |
| if (!connection) { |
| NS_LOG(WARNING) << __func__ << ": Invalid connection for target - " |
| << share_target.id; |
| return; |
| } |
| connection->Close(); |
| } |
| |
| void NearbySharingServiceImpl::OnIncomingDecryptedCertificate( |
| const std::string& endpoint_id, |
| sharing::mojom::AdvertisementPtr advertisement, |
| ShareTarget placeholder_share_target, |
| base::Optional<NearbyShareDecryptedPublicCertificate> certificate) { |
| NearbyConnection* connection = GetConnection(placeholder_share_target); |
| if (!connection) { |
| NS_LOG(VERBOSE) << __func__ << ": Invalid connection for endpoint id - " |
| << endpoint_id; |
| return; |
| } |
| |
| // Remove placeholder share target since we are creating the actual share |
| // target below. |
| incoming_share_target_info_map_.erase(placeholder_share_target.id); |
| |
| base::Optional<ShareTarget> share_target = CreateShareTarget( |
| endpoint_id, advertisement, std::move(certificate), /*is_incoming=*/true); |
| |
| if (!share_target) { |
| NS_LOG(VERBOSE) << __func__ |
| << "Failed to convert advertisement to share target for " |
| "incoming connection, disconnecting"; |
| connection->Close(); |
| return; |
| } |
| |
| NS_LOG(VERBOSE) << __func__ << "Received incoming connection from " |
| << share_target->id; |
| |
| ShareTargetInfo* share_target_info = GetShareTargetInfo(*share_target); |
| DCHECK(share_target_info); |
| share_target_info->set_connection(connection); |
| |
| share_target_info->set_transfer_update_callback( |
| std::make_unique<TransferUpdateDecorator>(base::BindRepeating( |
| &NearbySharingServiceImpl::OnIncomingTransferUpdate, |
| weak_ptr_factory_.GetWeakPtr()))); |
| |
| connection->SetDisconnectionListener( |
| base::BindOnce(&NearbySharingServiceImpl::UnregisterShareTarget, |
| weak_ptr_factory_.GetWeakPtr(), *share_target)); |
| |
| base::Optional<std::string> four_digit_token = ToFourDigitString( |
| nearby_connections_manager_->GetRawAuthenticationToken(endpoint_id)); |
| |
| RunPairedKeyVerification( |
| *share_target, endpoint_id, |
| base::BindOnce( |
| &NearbySharingServiceImpl::OnIncomingConnectionKeyVerificationDone, |
| weak_ptr_factory_.GetWeakPtr(), *share_target, |
| std::move(four_digit_token))); |
| } |
| |
| void NearbySharingServiceImpl::RunPairedKeyVerification( |
| const ShareTarget& share_target, |
| const std::string& endpoint_id, |
| base::OnceCallback<void( |
| PairedKeyVerificationRunner::PairedKeyVerificationResult)> callback) { |
| DCHECK(profile_); |
| base::Optional<std::vector<uint8_t>> token = |
| nearby_connections_manager_->GetRawAuthenticationToken(endpoint_id); |
| if (!token) { |
| NS_LOG(VERBOSE) << __func__ |
| << ": Failed to read authentication token from endpoint - " |
| << endpoint_id; |
| std::move(callback).Run( |
| PairedKeyVerificationRunner::PairedKeyVerificationResult::kFail); |
| return; |
| } |
| |
| ShareTargetInfo* share_target_info = GetShareTargetInfo(share_target); |
| DCHECK(share_target_info); |
| |
| share_target_info->set_frames_reader(std::make_unique<IncomingFramesReader>( |
| process_manager_, profile_, share_target_info->connection())); |
| |
| bool restrict_to_contacts = |
| share_target.is_incoming && |
| advertising_power_level_ != PowerLevel::kHighPower; |
| share_target_info->set_key_verification_runner( |
| std::make_unique<PairedKeyVerificationRunner>( |
| share_target, endpoint_id, *token, share_target_info->connection(), |
| share_target_info->certificate(), GetCertificateManager(), |
| settings_.GetVisibility(), restrict_to_contacts, |
| share_target_info->frames_reader(), kReadFramesTimeout)); |
| share_target_info->key_verification_runner()->Run(std::move(callback)); |
| } |
| |
| void NearbySharingServiceImpl::OnIncomingConnectionKeyVerificationDone( |
| ShareTarget share_target, |
| base::Optional<std::string> four_digit_token, |
| PairedKeyVerificationRunner::PairedKeyVerificationResult result) { |
| ShareTargetInfo* info = GetShareTargetInfo(share_target); |
| if (!info || !info->connection() || !info->endpoint_id()) { |
| NS_LOG(VERBOSE) << __func__ << ": Invalid connection or endpoint id"; |
| return; |
| } |
| |
| switch (result) { |
| case PairedKeyVerificationRunner::PairedKeyVerificationResult::kFail: |
| NS_LOG(VERBOSE) << __func__ << ": Paired key handshake failed for target " |
| << share_target.id << ". Disconnecting."; |
| info->connection()->Close(); |
| return; |
| |
| case PairedKeyVerificationRunner::PairedKeyVerificationResult::kSuccess: |
| NS_LOG(VERBOSE) << __func__ |
| << ": Paired key handshake succeeded for target - " |
| << share_target.id; |
| nearby_connections_manager_->UpgradeBandwidth(*info->endpoint_id()); |
| ReceiveIntroduction(share_target, /*four_digit_token=*/base::nullopt); |
| break; |
| |
| case PairedKeyVerificationRunner::PairedKeyVerificationResult::kUnable: |
| NS_LOG(VERBOSE) << __func__ |
| << ": Unable to verify paired key encryption when " |
| "receiving connection from target - " |
| << share_target.id; |
| if (advertising_power_level_ == PowerLevel::kHighPower) |
| nearby_connections_manager_->UpgradeBandwidth(*info->endpoint_id()); |
| |
| if (four_digit_token) |
| info->set_token(*four_digit_token); |
| |
| ReceiveIntroduction(share_target, std::move(four_digit_token)); |
| break; |
| |
| case PairedKeyVerificationRunner::PairedKeyVerificationResult::kUnknown: |
| NS_LOG(VERBOSE) << __func__ |
| << ": Unknown PairedKeyVerificationResult for target " |
| << share_target.id << ". Disconnecting."; |
| info->connection()->Close(); |
| break; |
| } |
| } |
| |
| void NearbySharingServiceImpl::OnOutgoingConnectionKeyVerificationDone( |
| const ShareTarget& share_target, |
| base::Optional<std::string> four_digit_token, |
| PairedKeyVerificationRunner::PairedKeyVerificationResult result) { |
| ShareTargetInfo* info = GetShareTargetInfo(share_target); |
| if (!info || !info->connection()) |
| return; |
| |
| if (!info->transfer_update_callback()) { |
| NS_LOG(VERBOSE) << __func__ |
| << ": No transfer update callback. Disconnecting."; |
| info->connection()->Close(); |
| return; |
| } |
| |
| // TODO(crbug.com/1119279): Check if we need to set this to false for |
| // Advanced Protection users. |
| bool sender_skips_confirmation = true; |
| |
| switch (result) { |
| case PairedKeyVerificationRunner::PairedKeyVerificationResult::kFail: |
| NS_LOG(VERBOSE) << __func__ << ": Paired key handshake failed for target " |
| << share_target.id << ". Disconnecting."; |
| info->transfer_update_callback()->OnTransferUpdate( |
| share_target, TransferMetadataBuilder() |
| .set_status(TransferMetadata::Status::kFailed) |
| .build()); |
| info->connection()->Close(); |
| return; |
| |
| case PairedKeyVerificationRunner::PairedKeyVerificationResult::kSuccess: |
| NS_LOG(VERBOSE) << __func__ |
| << ": Paired key handshake succeeded for target - " |
| << share_target.id; |
| SendIntroduction(share_target, /*four_digit_token=*/base::nullopt); |
| SendPayloads(share_target); |
| return; |
| |
| case PairedKeyVerificationRunner::PairedKeyVerificationResult::kUnable: |
| NS_LOG(VERBOSE) << __func__ |
| << ": Unable to verify paired key encryption when " |
| "initating connection to target - " |
| << share_target.id; |
| |
| if (four_digit_token) |
| info->set_token(*four_digit_token); |
| |
| if (sender_skips_confirmation) { |
| NS_LOG(VERBOSE) << __func__ |
| << ": Sender-side verification is disabled. Skipping " |
| "token comparison with " |
| << share_target.id; |
| SendIntroduction(share_target, /*four_digit_token=*/base::nullopt); |
| SendPayloads(share_target); |
| } else { |
| SendIntroduction(share_target, std::move(four_digit_token)); |
| } |
| return; |
| |
| case PairedKeyVerificationRunner::PairedKeyVerificationResult::kUnknown: |
| NS_LOG(VERBOSE) << __func__ |
| << ": Unknown PairedKeyVerificationResult for target " |
| << share_target.id << ". Disconnecting."; |
| info->connection()->Close(); |
| break; |
| } |
| } |
| |
| void NearbySharingServiceImpl::RefreshUIOnDisconnection( |
| ShareTarget share_target) { |
| ShareTargetInfo* info = GetShareTargetInfo(share_target); |
| if (info && info->transfer_update_callback()) { |
| info->transfer_update_callback()->OnTransferUpdate( |
| share_target, |
| TransferMetadataBuilder() |
| .set_status( |
| TransferMetadata::Status::kAwaitingRemoteAcceptanceFailed) |
| .build()); |
| } |
| |
| UnregisterShareTarget(share_target); |
| } |
| |
| void NearbySharingServiceImpl::ReceiveIntroduction( |
| ShareTarget share_target, |
| base::Optional<std::string> four_digit_token) { |
| NS_LOG(INFO) << __func__ << ": Receiving introduction from " |
| << share_target.id; |
| ShareTargetInfo* info = GetShareTargetInfo(share_target); |
| DCHECK(info && info->connection()); |
| |
| info->frames_reader()->ReadFrame( |
| sharing::mojom::V1Frame::Tag::INTRODUCTION, |
| base::BindOnce(&NearbySharingServiceImpl::OnReceivedIntroduction, |
| weak_ptr_factory_.GetWeakPtr(), std::move(share_target), |
| std::move(four_digit_token)), |
| kReadFramesTimeout); |
| } |
| |
| void NearbySharingServiceImpl::OnReceivedIntroduction( |
| ShareTarget share_target, |
| base::Optional<std::string> four_digit_token, |
| base::Optional<sharing::mojom::V1FramePtr> frame) { |
| ShareTargetInfo* info = GetShareTargetInfo(share_target); |
| if (!info || !info->connection()) { |
| NS_LOG(WARNING) |
| << __func__ |
| << ": Ignore received introduction, due to no connection established."; |
| return; |
| } |
| NearbyConnection* connection = info->connection(); |
| DCHECK(profile_); |
| |
| if (!frame) { |
| connection->Close(); |
| NS_LOG(WARNING) << __func__ << ": Invalid introduction frame"; |
| return; |
| } |
| |
| NS_LOG(INFO) << __func__ << ": Successfully read the introduction frame."; |
| |
| base::CheckedNumeric<int64_t> file_size_sum(0); |
| |
| sharing::mojom::IntroductionFramePtr introduction_frame = |
| std::move((*frame)->get_introduction()); |
| for (const auto& file : introduction_frame->file_metadata) { |
| if (file->size <= 0) { |
| Fail(share_target, TransferMetadata::Status::kUnsupportedAttachmentType); |
| NS_LOG(WARNING) |
| << __func__ |
| << ": Ignore introduction, due to invalid attachment size"; |
| return; |
| } |
| |
| NS_LOG(VERBOSE) << __func__ << "Found file attachment " << file->name |
| << " of type " << file->type << " with mimeType " |
| << file->mime_type; |
| FileAttachment attachment(file->id, file->size, file->name, file->mime_type, |
| file->type); |
| SetAttachmentPayloadId(attachment, file->payload_id); |
| share_target.file_attachments.push_back(std::move(attachment)); |
| |
| file_size_sum += file->size; |
| if (!file_size_sum.IsValid()) { |
| Fail(share_target, TransferMetadata::Status::kNotEnoughSpace); |
| NS_LOG(WARNING) << __func__ |
| << ": Ignoring introduction, total file size overflowed " |
| "64 bit integer."; |
| return; |
| } |
| } |
| |
| for (const auto& text : introduction_frame->text_metadata) { |
| if (text->size <= 0) { |
| Fail(share_target, TransferMetadata::Status::kUnsupportedAttachmentType); |
| NS_LOG(WARNING) |
| << __func__ |
| << ": Ignore introduction, due to invalid attachment size"; |
| return; |
| } |
| |
| NS_LOG(VERBOSE) << __func__ << "Found text attachment " << text->text_title |
| << " of type " << text->type; |
| TextAttachment attachment(text->id, text->type, text->text_title, |
| text->size); |
| SetAttachmentPayloadId(attachment, text->payload_id); |
| share_target.text_attachments.push_back(std::move(attachment)); |
| } |
| |
| if (!share_target.has_attachments()) { |
| NS_LOG(WARNING) << __func__ |
| << ": No attachment is found for this share target. It can " |
| "be result of unrecognizable attachment type"; |
| Fail(share_target, TransferMetadata::Status::kUnsupportedAttachmentType); |
| |
| NS_LOG(VERBOSE) << __func__ |
| << ": We don't support the attachments sent by the sender. " |
| "We have informed " |
| << share_target.id; |
| return; |
| } |
| |
| if (file_size_sum.ValueOrDie() == 0) { |
| OnStorageCheckCompleted(std::move(share_target), |
| std::move(four_digit_token), |
| /*is_out_of_storage=*/false); |
| return; |
| } |
| |
| base::FilePath download_path = |
| DownloadPrefs::FromDownloadManager( |
| content::BrowserContext::GetDownloadManager(profile_)) |
| ->DownloadPath(); |
| |
| base::ThreadPool::PostTaskAndReplyWithResult( |
| FROM_HERE, {base::MayBlock()}, |
| base::BindOnce(&IsOutOfStorage, std::move(download_path), |
| file_size_sum.ValueOrDie(), free_disk_space_for_testing_), |
| base::BindOnce(&NearbySharingServiceImpl::OnStorageCheckCompleted, |
| weak_ptr_factory_.GetWeakPtr(), std::move(share_target), |
| std::move(four_digit_token))); |
| } |
| |
| void NearbySharingServiceImpl::ReceiveConnectionResponse( |
| ShareTarget share_target) { |
| NS_LOG(VERBOSE) << __func__ << ": Receiving response frame from " |
| << share_target.id; |
| ShareTargetInfo* info = GetShareTargetInfo(share_target); |
| DCHECK(info && info->connection()); |
| |
| info->frames_reader()->ReadFrame( |
| sharing::mojom::V1Frame::Tag::CONNECTION_RESPONSE, |
| base::BindOnce(&NearbySharingServiceImpl::OnReceiveConnectionResponse, |
| weak_ptr_factory_.GetWeakPtr(), std::move(share_target)), |
| kReadResponseFrameTimeout); |
| } |
| |
| void NearbySharingServiceImpl::OnReceiveConnectionResponse( |
| ShareTarget share_target, |
| base::Optional<sharing::mojom::V1FramePtr> frame) { |
| OutgoingShareTargetInfo* info = GetOutgoingShareTargetInfo(share_target); |
| if (!info || !info->connection()) { |
| NS_LOG(WARNING) << __func__ |
| << ": Ignore received connection response, due to no " |
| "connection established."; |
| return; |
| } |
| NearbyConnection* connection = info->connection(); |
| |
| if (!info->transfer_update_callback()) { |
| NS_LOG(WARNING) << __func__ |
| << ": No transfer update callback. Disconnecting."; |
| connection->Close(); |
| return; |
| } |
| |
| if (!frame) { |
| NS_LOG(WARNING) |
| << __func__ |
| << ": Failed to read a response from the remote device. Disconnecting."; |
| info->transfer_update_callback()->OnTransferUpdate( |
| share_target, TransferMetadataBuilder() |
| .set_status(TransferMetadata::Status::kFailed) |
| .build()); |
| connection->Close(); |
| return; |
| } |
| |
| mutual_acceptance_timeout_alarm_.Cancel(); |
| |
| NS_LOG(VERBOSE) << __func__ |
| << ": Successfully read the connection response frame."; |
| |
| sharing::mojom::ConnectionResponseFramePtr response = |
| std::move((*frame)->get_connection_response()); |
| switch (response->status) { |
| case sharing::mojom::ConnectionResponseFrame::Status::kAccept: { |
| info->frames_reader()->ReadFrame( |
| base::BindOnce(&NearbySharingServiceImpl::OnFrameRead, |
| weak_ptr_factory_.GetWeakPtr(), share_target)); |
| |
| info->transfer_update_callback()->OnTransferUpdate( |
| share_target, TransferMetadataBuilder() |
| .set_status(TransferMetadata::Status::kInProgress) |
| .build()); |
| |
| info->set_payload_tracker(std::make_unique<PayloadTracker>( |
| share_target, attachment_info_map_, |
| base::BindRepeating( |
| &NearbySharingServiceImpl::OnPayloadTransferUpdate, |
| weak_ptr_factory_.GetWeakPtr()))); |
| |
| for (auto& payload : info->ExtractTextPayloads()) { |
| nearby_connections_manager_->Send( |
| *info->endpoint_id(), std::move(payload), info->payload_tracker()); |
| } |
| for (auto& payload : info->ExtractFilePayloads()) { |
| nearby_connections_manager_->Send( |
| *info->endpoint_id(), std::move(payload), info->payload_tracker()); |
| } |
| NS_LOG(VERBOSE) |
| << __func__ |
| << ": The connection was accepted. Payloads are now being sent."; |
| break; |
| } |
| case sharing::mojom::ConnectionResponseFrame::Status::kReject: |
| info->transfer_update_callback()->OnTransferUpdate( |
| share_target, TransferMetadataBuilder() |
| .set_status(TransferMetadata::Status::kRejected) |
| .build()); |
| connection->Close(); |
| NS_LOG(VERBOSE) |
| << __func__ |
| << ": The connection was rejected. The connection has been closed."; |
| break; |
| case sharing::mojom::ConnectionResponseFrame::Status::kNotEnoughSpace: |
| info->transfer_update_callback()->OnTransferUpdate( |
| share_target, |
| TransferMetadataBuilder() |
| .set_status(TransferMetadata::Status::kNotEnoughSpace) |
| .build()); |
| connection->Close(); |
| NS_LOG(VERBOSE) |
| << __func__ |
| << ": The connection was rejected because the remote device " |
| "does not have enough space for our attachments. The " |
| "connection has been closed."; |
| break; |
| case sharing::mojom::ConnectionResponseFrame::Status:: |
| kUnsupportedAttachmentType: |
| info->transfer_update_callback()->OnTransferUpdate( |
| share_target, |
| TransferMetadataBuilder() |
| .set_status(TransferMetadata::Status::kUnsupportedAttachmentType) |
| .build()); |
| connection->Close(); |
| NS_LOG(VERBOSE) |
| << __func__ |
| << ": The connection was rejected because the remote device " |
| "does not support the attachments we were sending. The " |
| "connection has been closed."; |
| break; |
| case sharing::mojom::ConnectionResponseFrame::Status::kTimedOut: |
| info->transfer_update_callback()->OnTransferUpdate( |
| share_target, TransferMetadataBuilder() |
| .set_status(TransferMetadata::Status::kTimedOut) |
| .build()); |
| connection->Close(); |
| NS_LOG(VERBOSE) |
| << __func__ |
| << ": The connection was rejected because the remote device " |
| "timed out. The connection has been closed."; |
| break; |
| default: |
| info->transfer_update_callback()->OnTransferUpdate( |
| share_target, TransferMetadataBuilder() |
| .set_status(TransferMetadata::Status::kFailed) |
| .build()); |
| connection->Close(); |
| NS_LOG(VERBOSE) |
| << __func__ |
| << ": The connection failed. The connection has been closed."; |
| break; |
| } |
| } |
| |
| void NearbySharingServiceImpl::OnStorageCheckCompleted( |
| ShareTarget share_target, |
| base::Optional<std::string> four_digit_token, |
| bool is_out_of_storage) { |
| if (is_out_of_storage) { |
| Fail(share_target, TransferMetadata::Status::kNotEnoughSpace); |
| NS_LOG(WARNING) << __func__ |
| << ": Not enough space on the receiver. We have informed " |
| << share_target.id; |
| return; |
| } |
| |
| ShareTargetInfo* info = GetShareTargetInfo(share_target); |
| if (!info || !info->connection()) { |
| NS_LOG(WARNING) << __func__ << ": Invalid connection for share target - " |
| << share_target.id; |
| return; |
| } |
| NearbyConnection* connection = info->connection(); |
| |
| if (!info->transfer_update_callback()) { |
| connection->Close(); |
| NS_LOG(VERBOSE) << __func__ |
| << ": No transfer update callback. Disconnecting."; |
| return; |
| } |
| |
| mutual_acceptance_timeout_alarm_.Reset(base::BindOnce( |
| &NearbySharingServiceImpl::OnIncomingMutualAcceptanceTimeout, |
| weak_ptr_factory_.GetWeakPtr(), share_target)); |
| |
| base::SequencedTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, base::BindOnce(mutual_acceptance_timeout_alarm_.callback()), |
| kReadResponseFrameTimeout); |
| |
| info->transfer_update_callback()->OnTransferUpdate( |
| share_target, |
| TransferMetadataBuilder() |
| .set_status(TransferMetadata::Status::kAwaitingLocalConfirmation) |
| .set_token(std::move(four_digit_token)) |
| .build()); |
| |
| if (!incoming_share_target_info_map_.count(share_target.id)) { |
| connection->Close(); |
| NS_LOG(VERBOSE) << __func__ |
| << ": IncomingShareTarget not found, disconnecting " |
| << share_target.id; |
| return; |
| } |
| |
| connection->SetDisconnectionListener(base::BindOnce( |
| &NearbySharingServiceImpl::OnIncomingConnectionDisconnected, |
| weak_ptr_factory_.GetWeakPtr(), share_target)); |
| |
| auto* frames_reader = info->frames_reader(); |
| if (!frames_reader) { |
| NS_LOG(WARNING) << __func__ |
| << ": Stopped reading further frames, due to no connection " |
| "established."; |
| return; |
| } |
| |
| frames_reader->ReadFrame( |
| base::BindOnce(&NearbySharingServiceImpl::OnFrameRead, |
| weak_ptr_factory_.GetWeakPtr(), std::move(share_target))); |
| } |
| |
| void NearbySharingServiceImpl::OnFrameRead( |
| ShareTarget share_target, |
| base::Optional<sharing::mojom::V1FramePtr> frame) { |
| if (!frame) { |
| // This is the case when the connection has been closed since we wait |
| // indefinitely for incoming frames. |
| return; |
| } |
| |
| sharing::mojom::V1FramePtr v1_frame = std::move(*frame); |
| switch (v1_frame->which()) { |
| case sharing::mojom::V1Frame::Tag::CANCEL_FRAME: |
| NS_LOG(INFO) << __func__ << ": Read the cancel frame, closing connection"; |
| DoCancel(share_target, base::DoNothing(), /*write_cancel_frame=*/false); |
| break; |
| |
| case sharing::mojom::V1Frame::Tag::CERTIFICATE_INFO: |
| HandleCertificateInfoFrame(v1_frame->get_certificate_info()); |
| break; |
| |
| default: |
| NS_LOG(VERBOSE) << __func__ << ": Discarding unknown frame of type"; |
| break; |
| } |
| |
| ShareTargetInfo* info = GetShareTargetInfo(share_target); |
| if (!info || !info->frames_reader()) { |
| NS_LOG(WARNING) << __func__ |
| << ": Stopped reading further frames, due to no connection " |
| "established."; |
| return; |
| } |
| |
| info->frames_reader()->ReadFrame( |
| base::BindOnce(&NearbySharingServiceImpl::OnFrameRead, |
| weak_ptr_factory_.GetWeakPtr(), std::move(share_target))); |
| } |
| |
| void NearbySharingServiceImpl::HandleCertificateInfoFrame( |
| const sharing::mojom::CertificateInfoFramePtr& certificate_frame) { |
| DCHECK(certificate_frame); |
| |
| // TODO(crbug.com/1113858): Allow saving certificates from remote devices. |
| } |
| |
| void NearbySharingServiceImpl::OnIncomingConnectionDisconnected( |
| const ShareTarget& share_target) { |
| ShareTargetInfo* info = GetShareTargetInfo(share_target); |
| if (info && info->transfer_update_callback()) { |
| info->transfer_update_callback()->OnTransferUpdate( |
| share_target, TransferMetadataBuilder() |
| .set_status(TransferMetadata::Status::kFailed) |
| .build()); |
| } |
| UnregisterShareTarget(share_target); |
| } |
| |
| bool NearbySharingServiceImpl::IsTransferring() const { |
| return is_transferring_; |
| } |
| |
| void NearbySharingServiceImpl::OnOutgoingConnectionDisconnected( |
| const ShareTarget& share_target) { |
| ShareTargetInfo* info = GetShareTargetInfo(share_target); |
| if (info && info->transfer_update_callback()) { |
| info->transfer_update_callback()->OnTransferUpdate( |
| share_target, |
| TransferMetadataBuilder() |
| .set_status( |
| TransferMetadata::Status::kAwaitingRemoteAcceptanceFailed) |
| .build()); |
| } |
| UnregisterShareTarget(share_target); |
| } |
| |
| void NearbySharingServiceImpl::OnIncomingMutualAcceptanceTimeout( |
| const ShareTarget& share_target) { |
| DCHECK(share_target.is_incoming); |
| |
| NS_LOG(VERBOSE) |
| << __func__ |
| << ": Incoming mutual acceptance timed out, closing connection for " |
| << share_target.id; |
| |
| Fail(share_target, TransferMetadata::Status::kTimedOut); |
| } |
| |
| void NearbySharingServiceImpl::OnOutgoingMutualAcceptanceTimeout( |
| const ShareTarget& share_target) { |
| DCHECK(!share_target.is_incoming); |
| |
| NS_LOG(VERBOSE) |
| << __func__ |
| << ": Outgoing mutual acceptance timed out, closing connection for " |
| << share_target.id; |
| |
| ShareTargetInfo* info = GetShareTargetInfo(share_target); |
| if (!info) |
| return; |
| |
| if (info->transfer_update_callback()) { |
| info->transfer_update_callback()->OnTransferUpdate( |
| share_target, TransferMetadataBuilder() |
| .set_status(TransferMetadata::Status::kTimedOut) |
| .build()); |
| } |
| |
| if (info->connection()) |
| info->connection()->Close(); |
| } |
| |
| base::Optional<ShareTarget> NearbySharingServiceImpl::CreateShareTarget( |
| const std::string& endpoint_id, |
| const sharing::mojom::AdvertisementPtr& advertisement, |
| base::Optional<NearbyShareDecryptedPublicCertificate> certificate, |
| bool is_incoming) { |
| DCHECK(advertisement); |
| |
| if (!advertisement->device_name && !certificate) { |
| NS_LOG(VERBOSE) << __func__ |
| << ": Failed to retrieve public certificate for contact " |
| "only advertisement."; |
| return base::nullopt; |
| } |
| |
| base::Optional<std::string> device_name = |
| GetDeviceName(advertisement, certificate); |
| if (!device_name) { |
| NS_LOG(VERBOSE) << __func__ |
| << ": Failed to retrieve device name for advertisement."; |
| return base::nullopt; |
| } |
| |
| ShareTarget target; |
| target.type = advertisement->device_type; |
| target.device_name = std::move(*device_name); |
| target.is_incoming = is_incoming; |
| target.device_id = GetDeviceId(endpoint_id, certificate); |
| |
| ShareTargetInfo& info = GetOrCreateShareTargetInfo(target, endpoint_id); |
| |
| if (certificate) { |
| if (certificate->unencrypted_metadata().has_full_name()) |
| target.full_name = certificate->unencrypted_metadata().full_name(); |
| |
| if (certificate->unencrypted_metadata().has_icon_url()) |
| target.image_url = GURL(certificate->unencrypted_metadata().icon_url()); |
| |
| target.is_known = true; |
| info.set_certificate(std::move(*certificate)); |
| } |
| |
| return target; |
| } |
| |
| void NearbySharingServiceImpl::OnPayloadTransferUpdate( |
| ShareTarget share_target, |
| TransferMetadata metadata) { |
| // kInProgress status is logged extensively elsewhere so avoid the spam. |
| if (metadata.status() != TransferMetadata::Status::kInProgress) { |
| NS_LOG(VERBOSE) << __func__ << ": Nearby Share service: " |
| << "Payload transfer update for share target with ID " |
| << share_target.id << ": " |
| << TransferMetadata::StatusToString(metadata.status()); |
| } |
| |
| if (metadata.status() == TransferMetadata::Status::kComplete && |
| share_target.is_incoming && !OnIncomingPayloadsComplete(share_target)) { |
| metadata = TransferMetadataBuilder() |
| .set_status(TransferMetadata::Status::kFailed) |
| .build(); |
| |
| // Reset file paths for file attachments. |
| for (auto& file : share_target.file_attachments) |
| file.set_file_path(base::nullopt); |
| |
| // Reset body of text attachments. |
| for (auto& text : share_target.text_attachments) |
| text.set_text_body(std::string()); |
| } |
| |
| // Make sure to call this before calling Disconnect or we risk loosing some |
| // transfer updates in the receive case due to the Disconnect call cleaning up |
| // share targets. |
| ShareTargetInfo* info = GetShareTargetInfo(share_target); |
| if (info && info->transfer_update_callback()) |
| info->transfer_update_callback()->OnTransferUpdate(share_target, metadata); |
| |
| // Cancellation has its own disconnection strategy, possibly adding a delay |
| // before disconnection to provide the other party time to process the |
| // cancellation. |
| if (TransferMetadata::IsFinalStatus(metadata.status()) && |
| metadata.status() != TransferMetadata::Status::kCancelled) { |
| Disconnect(share_target, metadata); |
| } |
| } |
| |
| bool NearbySharingServiceImpl::OnIncomingPayloadsComplete( |
| ShareTarget& share_target) { |
| DCHECK(share_target.is_incoming); |
| |
| ShareTargetInfo* info = GetShareTargetInfo(share_target); |
| if (!info || !info->connection()) { |
| NS_LOG(VERBOSE) << __func__ << ": Connection not found for target - " |
| << share_target.id; |
| |
| return false; |
| } |
| NearbyConnection* connection = info->connection(); |
| |
| connection->SetDisconnectionListener( |
| base::BindOnce(&NearbySharingServiceImpl::UnregisterShareTarget, |
| weak_ptr_factory_.GetWeakPtr(), share_target)); |
| |
| for (auto& file : share_target.file_attachments) { |
| AttachmentInfo& attachment_info = attachment_info_map_[file.id()]; |
| base::Optional<int64_t> payload_id = attachment_info.payload_id; |
| if (!payload_id) { |
| NS_LOG(WARNING) << __func__ << ": No payload id found for file - " |
| << file.id(); |
| return false; |
| } |
| |
| location::nearby::connections::mojom::Payload* incoming_payload = |
| nearby_connections_manager_->GetIncomingPayload(*payload_id); |
| if (!incoming_payload || !incoming_payload->content || |
| !incoming_payload->content->is_file()) { |
| NS_LOG(WARNING) << __func__ << ": No payload found for file - " |
| << file.id(); |
| return false; |
| } |
| |
| file.set_file_path(attachment_info.file_path); |
| } |
| |
| for (auto& text : share_target.text_attachments) { |
| AttachmentInfo& attachment_info = attachment_info_map_[text.id()]; |
| base::Optional<int64_t> payload_id = attachment_info.payload_id; |
| if (!payload_id) { |
| NS_LOG(WARNING) << __func__ << ": No payload id found for text - " |
| << text.id(); |
| return false; |
| } |
| |
| location::nearby::connections::mojom::Payload* incoming_payload = |
| nearby_connections_manager_->GetIncomingPayload(*payload_id); |
| if (!incoming_payload || !incoming_payload->content || |
| !incoming_payload->content->is_bytes()) { |
| NS_LOG(WARNING) << __func__ << ": No payload found for text - " |
| << text.id(); |
| return false; |
| } |
| |
| std::vector<uint8_t>& bytes = incoming_payload->content->get_bytes()->bytes; |
| if (bytes.empty()) { |
| NS_LOG(WARNING) |
| << __func__ |
| << ": Incoming bytes is empty for text payload with payload_id - " |
| << *payload_id; |
| return false; |
| } |
| |
| std::string text_body(bytes.begin(), bytes.end()); |
| text.set_text_body(text_body); |
| |
| attachment_info.text_body = std::move(text_body); |
| } |
| return true; |
| } |
| |
| void NearbySharingServiceImpl::RemoveIncomingPayloads( |
| ShareTarget share_target) { |
| if (!share_target.is_incoming) |
| return; |
| |
| NS_LOG(INFO) << __func__ << ": Cleaning up payloads due to transfer failure"; |
| |
| nearby_connections_manager_->ClearIncomingPayloads(); |
| std::vector<base::FilePath> files_for_deletion; |
| for (const auto& file : share_target.file_attachments) { |
| auto it = attachment_info_map_.find(file.id()); |
| if (it == attachment_info_map_.end()) |
| continue; |
| |
| files_for_deletion.push_back(it->second.file_path); |
| } |
| |
| file_handler_.DeleteFilesFromDisk(std::move(files_for_deletion)); |
| } |
| |
| void NearbySharingServiceImpl::Disconnect(const ShareTarget& share_target, |
| TransferMetadata metadata) { |
| ShareTargetInfo* share_target_info = GetShareTargetInfo(share_target); |
| if (!share_target_info) { |
| NS_LOG(WARNING) |
| << __func__ |
| << ": Failed to disconnect. No share target info found for target - " |
| << share_target.id; |
| return; |
| } |
| |
| base::Optional<std::string> endpoint_id = share_target_info->endpoint_id(); |
| if (!endpoint_id) { |
| NS_LOG(WARNING) |
| << __func__ |
| << ": Failed to disconnect. No endpoint id found for share target - " |
| << share_target.id; |
| return; |
| } |
| |
| // Failed to send or receive. No point in continuing, so disconnect |
| // immediately. |
| if (metadata.status() != TransferMetadata::Status::kComplete) { |
| if (share_target_info->connection()) { |
| share_target_info->connection()->Close(); |
| } else { |
| nearby_connections_manager_->Disconnect(*endpoint_id); |
| } |
| return; |
| } |
| |
| // Files received successfully. Receivers can immediately cancel. |
| if (share_target.is_incoming) { |
| if (share_target_info->connection()) { |
| share_target_info->connection()->Close(); |
| } else { |
| nearby_connections_manager_->Disconnect(*endpoint_id); |
| } |
| return; |
| } |
| |
| // Disconnect after a timeout to make sure any pending payloads are sent. |
| auto timer = std::make_unique<base::CancelableOnceClosure>(base::BindOnce( |
| &NearbySharingServiceImpl::OnDisconnectingConnectionTimeout, |
| weak_ptr_factory_.GetWeakPtr(), *endpoint_id)); |
| base::SequencedTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, timer->callback(), kOutgoingDisconnectionDelay); |
| disconnection_timeout_alarms_[*endpoint_id] = std::move(timer); |
| |
| // Stop the disconnection timeout if the connection has been closed already. |
| if (share_target_info->connection()) { |
| share_target_info->connection()->SetDisconnectionListener(base::BindOnce( |
| &NearbySharingServiceImpl::OnDisconnectingConnectionDisconnected, |
| weak_ptr_factory_.GetWeakPtr(), share_target, *endpoint_id)); |
| } |
| } |
| |
| void NearbySharingServiceImpl::OnDisconnectingConnectionTimeout( |
| const std::string& endpoint_id) { |
| disconnection_timeout_alarms_.erase(endpoint_id); |
| nearby_connections_manager_->Disconnect(endpoint_id); |
| } |
| |
| void NearbySharingServiceImpl::OnDisconnectingConnectionDisconnected( |
| const ShareTarget& share_target, |
| const std::string& endpoint_id) { |
| disconnection_timeout_alarms_.erase(endpoint_id); |
| UnregisterShareTarget(share_target); |
| } |
| |
| ShareTargetInfo& NearbySharingServiceImpl::GetOrCreateShareTargetInfo( |
| const ShareTarget& share_target, |
| const std::string& endpoint_id) { |
| if (share_target.is_incoming) { |
| auto& info = incoming_share_target_info_map_[share_target.id]; |
| info.set_endpoint_id(endpoint_id); |
| return info; |
| } else { |
| // We need to explicitly remove any previous share target for |
| // |endpoint_id| if one exists, notifying observers that a share target is |
| // lost. |
| const auto it = outgoing_share_target_map_.find(endpoint_id); |
| if (it != outgoing_share_target_map_.end() && |
| it->second.id != share_target.id) { |
| RemoveOutgoingShareTargetWithEndpointId(endpoint_id); |
| } |
| |
| NS_LOG(VERBOSE) << __func__ << ": Adding (endpoint_id=" << endpoint_id |
| << ", share_target_id=" << share_target.id |
| << ") to outgoing share target map"; |
| outgoing_share_target_map_.insert_or_assign(endpoint_id, share_target); |
| auto& info = outgoing_share_target_info_map_[share_target.id]; |
| info.set_endpoint_id(endpoint_id); |
| return info; |
| } |
| } |
| |
| ShareTargetInfo* NearbySharingServiceImpl::GetShareTargetInfo( |
| const ShareTarget& share_target) { |
| if (share_target.is_incoming) |
| return GetIncomingShareTargetInfo(share_target); |
| else |
| return GetOutgoingShareTargetInfo(share_target); |
| } |
| |
| IncomingShareTargetInfo* NearbySharingServiceImpl::GetIncomingShareTargetInfo( |
| const ShareTarget& share_target) { |
| auto it = incoming_share_target_info_map_.find(share_target.id); |
| if (it == incoming_share_target_info_map_.end()) |
| return nullptr; |
| |
| return &it->second; |
| } |
| |
| OutgoingShareTargetInfo* NearbySharingServiceImpl::GetOutgoingShareTargetInfo( |
| const ShareTarget& share_target) { |
| auto it = outgoing_share_target_info_map_.find(share_target.id); |
| if (it == outgoing_share_target_info_map_.end()) |
| return nullptr; |
| |
| return &it->second; |
| } |
| |
| NearbyConnection* NearbySharingServiceImpl::GetConnection( |
| const ShareTarget& share_target) { |
| ShareTargetInfo* share_target_info = GetShareTargetInfo(share_target); |
| return share_target_info ? share_target_info->connection() : nullptr; |
| } |
| |
| base::Optional<std::vector<uint8_t>> |
| NearbySharingServiceImpl::GetBluetoothMacAddress( |
| const ShareTarget& share_target) { |
| ShareTargetInfo* info = GetShareTargetInfo(share_target); |
| if (!info) |
| return base::nullopt; |
| |
| const base::Optional<NearbyShareDecryptedPublicCertificate>& certificate = |
| info->certificate(); |
| if (!certificate || |
| !certificate->unencrypted_metadata().has_bluetooth_mac_address()) { |
| return base::nullopt; |
| } |
| |
| std::string mac_address = |
| certificate->unencrypted_metadata().bluetooth_mac_address(); |
| if (mac_address.size() != 6) |
| return base::nullopt; |
| |
| return std::vector<uint8_t>(mac_address.begin(), mac_address.end()); |
| } |
| |
| void NearbySharingServiceImpl::ClearOutgoingShareTargetInfoMap() { |
| NS_LOG(VERBOSE) << __func__ << ": Clearing outgoing share target map."; |
| while (!outgoing_share_target_map_.empty()) { |
| RemoveOutgoingShareTargetWithEndpointId( |
| /*endpoint_id=*/outgoing_share_target_map_.begin()->first); |
| } |
| DCHECK(outgoing_share_target_map_.empty()); |
| DCHECK(outgoing_share_target_info_map_.empty()); |
| } |
| |
| void NearbySharingServiceImpl::SetAttachmentPayloadId( |
| const Attachment& attachment, |
| int64_t payload_id) { |
| attachment_info_map_[attachment.id()].payload_id = payload_id; |
| } |
| |
| base::Optional<int64_t> NearbySharingServiceImpl::GetAttachmentPayloadId( |
| int64_t attachment_id) { |
| auto it = attachment_info_map_.find(attachment_id); |
| if (it == attachment_info_map_.end()) |
| return base::nullopt; |
| |
| return it->second.payload_id; |
| } |
| |
| void NearbySharingServiceImpl::UnregisterShareTarget( |
| const ShareTarget& share_target) { |
| NS_LOG(VERBOSE) << __func__ << ": Unregistering share target - " |
| << share_target.id; |
| |
| // For metrics. |
| cancelled_share_target_ids_.erase(share_target.id); |
| |
| if (share_target.is_incoming) { |
| if (last_incoming_metadata_ && |
| last_incoming_metadata_->first.id == share_target.id) { |
| last_incoming_metadata_.reset(); |
| } |
| |
| // Clear legacy incoming payloads to release resource. |
| nearby_connections_manager_->ClearIncomingPayloads(); |
| incoming_share_target_info_map_.erase(share_target.id); |
| } else { |
| if (last_outgoing_metadata_ && |
| last_outgoing_metadata_->first.id == share_target.id) { |
| last_outgoing_metadata_.reset(); |
| } |
| // Find the endpoint id that matches the given share target. |
| base::Optional<std::string> endpoint_id; |
| auto it = outgoing_share_target_info_map_.find(share_target.id); |
| if (it != outgoing_share_target_info_map_.end()) |
| endpoint_id = it->second.endpoint_id(); |
| |
| // TODO(crbug/1108348): Support caching manager by keeping track of the |
| // share_target/endpoint_id for next time. |
| ClearOutgoingShareTargetInfoMap(); |
| |
| NS_LOG(VERBOSE) << __func__ |
| << ": Unregister share target: " << share_target.id; |
| } |
| mutual_acceptance_timeout_alarm_.Cancel(); |
| } |
| |
| void NearbySharingServiceImpl::OnStartAdvertisingResult( |
| bool used_device_name, |
| NearbyConnectionsManager::ConnectionsStatus status) { |
| RecordNearbyShareStartAdvertisingResultMetric( |
| /*is_high_visibility=*/used_device_name, status); |
| |
| if (status == NearbyConnectionsManager::ConnectionsStatus::kSuccess) { |
| NS_LOG(VERBOSE) |
| << "StartAdvertising over Nearby Connections was successful."; |
| SetInHighVisibility(used_device_name); |
| } else { |
| NS_LOG(ERROR) << "StartAdvertising over Nearby Connections failed: " |
| << NearbyConnectionsManager::ConnectionsStatusToString( |
| status); |
| SetInHighVisibility(false); |
| } |
| } |
| |
| void NearbySharingServiceImpl::SetInHighVisibility( |
| bool new_in_high_visibility) { |
| if (in_high_visibility == new_in_high_visibility) |
| return; |
| |
| in_high_visibility = new_in_high_visibility; |
| for (auto& observer : observers_) { |
| observer.OnHighVisibilityChanged(in_high_visibility); |
| } |
| } |