#include #include #include #include #include #include #include #include "absl/synchronization/mutex.h" #include "absl/synchronization/notification.h" #include "internal/platform/implementation/ble.h" #include "sharing/internal/api/fast_init_ble_beacon.h" #include "sharing/internal/api/fast_initiation_manager.h" #include "internal/platform/implementation/linux/bluetooth_adapter.h" #include "internal/platform/implementation/linux/bluetooth_devices.h" #include "internal/platform/implementation/linux/bluez.h" #include "internal/platform/implementation/linux/bluez_advertisement_monitor.h" #include "internal/platform/implementation/linux/bluez_advertisement_monitor_manager.h" #include "internal/platform/implementation/linux/bluez_le_advertisement.h" #include "internal/platform/implementation/linux/dbus.h" #include "internal/platform/uuid.h" #include "sharing/linux/nearby_fast_init_manager.h" namespace nearby { namespace sharing { namespace linux { namespace { constexpr char kFastInitMonitorRootPath[] = "/com/google/nearby/sharing/fast_init/monitor"; constexpr char kFastInitMonitorPath[] = "/com/google/nearby/sharing/fast_init/monitor/fe2c"; constexpr size_t kFastInitServiceDataSize = nearby::api::FastInitBleBeacon::kAdvertiseDataTotalSize - nearby::api::FastInitBleBeacon::kFastInitServiceUuidSize; nearby::api::FastInitiationManager::Error MapBluezError( const std::string& error_name) { if (error_name == "org.bluez.Error.AlreadyExists" || error_name == "org.bluez.Error.InProgress") { return nearby::api::FastInitiationManager::Error::kResourceInUse; } if (error_name == "org.bluez.Error.NotPermitted" || error_name == "org.bluez.Error.NotAuthorized") { return nearby::api::FastInitiationManager::Error::kDisabledByUser; } if (error_name == "org.bluez.Error.NotSupported") { return nearby::api::FastInitiationManager::Error::kHardwareNotSupported; } return nearby::api::FastInitiationManager::Error::kUnknown; } } // namespace LinuxFastInitiationManager::~LinuxFastInitiationManager() { StopScanning(nullptr); StopAdvertising(nullptr); } void LinuxFastInitiationManager::StartAdvertising( nearby::api::FastInitBleBeacon::FastInitType type, std::function callback, std::function error_callback) { absl::MutexLock lock(&mutex_); if (advertisement_ != nullptr) { if (error_callback) { error_callback(nearby::api::FastInitiationManager::Error::kResourceInUse); } return; } if (adapter_ == nullptr || !adapter_->IsEnabled()) { if (error_callback) { error_callback(nearby::api::FastInitiationManager::Error:: kBluetoothRadioUnavailable); } return; } if (adv_manager_ == nullptr) { if (error_callback) { error_callback( nearby::api::FastInitiationManager::Error::kHardwareNotSupported); } return; } auto fast_init_uuid = Uuid::FromString(kFastInitServiceUuid); if (!fast_init_uuid.has_value()) { if (error_callback) { error_callback(nearby::api::FastInitiationManager::Error::kUnknown); } return; } beacon_.SetVersion(nearby::api::FastInitBleBeacon::FastInitVersion::kV1); beacon_.SetType(type); beacon_.SetUwbSupported(false); beacon_.SetSenderCertSupported(false); beacon_.SetAdjustedTxPower( 45); // TODO: make this set the real value from adapter beacon_.SetUwbMetadata({}); beacon_.SetUwbAddress({}); beacon_.SetSalt({}); beacon_.SetSecretIdHash({}); beacon_.SetRequireBtAdvertising(true); beacon_.SetSelfOnlyAdvertising(false); beacon_.SerializeToByteArray(); const auto ad_data = beacon_.GetAdDataByteArray(); nearby::api::ble::BleAdvertisementData advertising_data; advertising_data.is_extended_advertisement = false; advertising_data.service_data.insert( {*fast_init_uuid, nearby::ByteArray(reinterpret_cast(ad_data.data() + 2), ad_data.size() - 2)}); nearby::api::ble::AdvertiseParameters advertising_parameters{ .tx_power_level = nearby::api::ble::TxPowerLevel::kHigh, .is_connectable = false, }; advertisement_ = ::nearby::linux::bluez::LEAdvertisement::CreateLEAdvertisement( *adapter_->GetConnection(), advertising_data, advertising_parameters); try { adv_manager_->RegisterAdvertisementSync( advertisement_->getObject().getObjectPath(), {}); } catch (const sdbus::Error& e) { advertisement_.reset(); if (error_callback) { if (e.getName() == "org.bluez.Error.AlreadyExists") { error_callback( nearby::api::FastInitiationManager::Error::kResourceInUse); } else if (e.getName() == "org.bluez.Error.NotPermitted") { error_callback( nearby::api::FastInitiationManager::Error::kDisabledByUser); } else { error_callback(nearby::api::FastInitiationManager::Error::kUnknown); } } return; } if (callback) { callback(); } } void LinuxFastInitiationManager::StopAdvertising( std::function callback) { absl::MutexLock lock(&mutex_); if (advertisement_ != nullptr && adv_manager_ != nullptr) { try { adv_manager_->UnregisterAdvertisementSync( advertisement_->getObject().getObjectPath()); } catch (const sdbus::Error& e) { DBUS_LOG_METHOD_CALL_ERROR(adv_manager_.get(), "UnregisterAdvertisementSync", e); } advertisement_.reset(); } if (callback) { callback(); } } void LinuxFastInitiationManager::StartScanning( std::function devices_discovered_callback, std::function devices_not_discovered_callback, std::function error_callback) { std::optional error; { absl::MutexLock operation_lock(&scan_operation_mutex_); error = StartScanningInternal(std::move(devices_discovered_callback), std::move(devices_not_discovered_callback)); } if (error.has_value() && error_callback) { error_callback(*error); } } std::optional LinuxFastInitiationManager::StartScanningInternal( std::function devices_discovered_callback, std::function devices_not_discovered_callback) { bool already_scanning = false; { absl::MutexLock lock(&mutex_); already_scanning = is_scanning_; } if (already_scanning) { return nearby::api::FastInitiationManager::Error::kResourceInUse; } if (adapter_ == nullptr || !adapter_->IsEnabled()) { return nearby::api::FastInitiationManager::Error:: kBluetoothRadioUnavailable; } auto fast_init_uuid = Uuid::FromString(kFastInitServiceUuid); if (!fast_init_uuid.has_value()) { return nearby::api::FastInitiationManager::Error::kUnknown; } auto connection = adapter_->GetConnection(); auto monitor_manager = ::nearby::linux::bluez::AdvertisementMonitorManager:: DiscoverAdvertisementMonitorManager(*connection, *adapter_); if (monitor_manager == nullptr) { return nearby::api::FastInitiationManager::Error::kHardwareNotSupported; } try { const std::vector supported_types = monitor_manager->SupportedMonitorTypes(); if (std::find(supported_types.begin(), supported_types.end(), "or_patterns") == supported_types.end()) { return nearby::api::FastInitiationManager::Error::kHardwareNotSupported; } } catch (const sdbus::Error& error) { return MapBluezError(error.getName()); } uint64_t generation; try { { absl::MutexLock lock(&mutex_); is_scanning_ = true; generation = ++scan_generation_; devices_discovered_callback_ = std::move(devices_discovered_callback); devices_not_discovered_callback_ = std::move(devices_not_discovered_callback); discovered_peripherals_.clear(); } scan_devices_ = ::nearby::linux::GetSharedBluetoothDevices( connection, adapter_->GetObjectPath()); scan_root_object_manager_ = std::make_unique<::nearby::linux::RootObjectManager>( *connection, sdbus::ObjectPath(kFastInitMonitorRootPath)); scan_monitor_ = std::make_unique<::nearby::linux::bluez::AdvertisementMonitor>( *connection, sdbus::ObjectPath(kFastInitMonitorPath), *fast_init_uuid, nearby::api::ble::TxPowerLevel::kLow, "or_patterns", scan_devices_, nearby::api::ble::BleMedium::ScanningCallback{ .advertisement_found_cb = [this, generation]( nearby::api::ble::BlePeripheral::UniqueId peripheral_id, nearby::api::ble::BleAdvertisementData data) { OnAdvertisementFound(generation, peripheral_id, std::move(data)); }, .advertisement_lost_cb = [this, generation](nearby::api::ble::BlePeripheral::UniqueId id) { OnAdvertisementLost(generation, id); }, }); scan_monitor_manager_ = std::move(monitor_manager); } catch (const sdbus::Error& error) { { absl::MutexLock lock(&mutex_); is_scanning_ = false; devices_discovered_callback_ = nullptr; devices_not_discovered_callback_ = nullptr; } scan_monitor_.reset(); scan_root_object_manager_.reset(); scan_devices_.reset(); return MapBluezError(error.getName()); } absl::Notification registration_complete; std::string registration_error_name; scan_monitor_manager_->SetRegisterMonitorReplyCallback( [®istration_complete, ®istration_error_name](std::optional error) { if (error.has_value() && error->isValid()) { registration_error_name = error->getName(); } registration_complete.Notify(); }); try { scan_monitor_manager_->RegisterMonitor( scan_root_object_manager_->getObject().getObjectPath()); registration_complete.WaitForNotification(); } catch (const sdbus::Error& error) { registration_error_name = error.getName(); } if (!registration_error_name.empty()) { { absl::MutexLock lock(&mutex_); is_scanning_ = false; ++scan_generation_; devices_discovered_callback_ = nullptr; devices_not_discovered_callback_ = nullptr; discovered_peripherals_.clear(); } scan_monitor_.reset(); scan_root_object_manager_.reset(); scan_monitor_manager_.reset(); scan_devices_.reset(); return MapBluezError(registration_error_name); } try { auto& bluez_adapter = adapter_->GetBluezAdapterObject(); if (!bluez_adapter.Discovering()) { std::map filter; filter["Transport"] = sdbus::Variant("le"); filter["DuplicateData"] = sdbus::Variant(true); bluez_adapter.SetDiscoveryFilter(filter); bluez_adapter.StartDiscovery(); absl::MutexLock lock(&mutex_); owns_bluez_discovery_ = true; } } catch (const sdbus::Error& error) { // Release the scan application before reporting a failed start. StopScanningInternal(); return MapBluezError(error.getName()); } return std::nullopt; } void LinuxFastInitiationManager::StopScanning(std::function callback) { { absl::MutexLock operation_lock(&scan_operation_mutex_); StopScanningInternal(); } if (callback) { callback(); } } void LinuxFastInitiationManager::StopScanningInternal() { bool owns_discovery = false; { absl::MutexLock lock(&mutex_); if (!is_scanning_) { return; } is_scanning_ = false; owns_discovery = owns_bluez_discovery_; owns_bluez_discovery_ = false; ++scan_generation_; devices_discovered_callback_ = nullptr; devices_not_discovered_callback_ = nullptr; discovered_peripherals_.clear(); } if (owns_discovery && adapter_ != nullptr) { try { adapter_->GetBluezAdapterObject().StopDiscovery(); } catch (const sdbus::Error& error) { DBUS_LOG_METHOD_CALL_ERROR(&adapter_->GetBluezAdapterObject(), "StopDiscovery", error); } } if (scan_monitor_manager_ != nullptr && scan_root_object_manager_ != nullptr) { absl::Notification unregistration_complete; scan_monitor_manager_->SetUnregisterMonitorReplyCallback( [&unregistration_complete](std::optional) { unregistration_complete.Notify(); }); try { scan_monitor_manager_->UnregisterMonitor( scan_root_object_manager_->getObject().getObjectPath()); unregistration_complete.WaitForNotification(); } catch (const sdbus::Error& error) { DBUS_LOG_METHOD_CALL_ERROR(scan_monitor_manager_.get(), "UnregisterMonitor", error); } } scan_monitor_.reset(); scan_root_object_manager_.reset(); scan_monitor_manager_.reset(); scan_devices_.reset(); } bool LinuxFastInitiationManager::IsScanning() { absl::MutexLock lock(&mutex_); return is_scanning_; } void LinuxFastInitiationManager::OnAdvertisementFound( uint64_t scan_generation, nearby::api::ble::BlePeripheral::UniqueId peripheral_id, nearby::api::ble::BleAdvertisementData advertisement_data) { if (!IsFastInitAdvertisement(advertisement_data)) { return; } std::function callback; { absl::MutexLock lock(&mutex_); if (!is_scanning_ || scan_generation != scan_generation_) { return; } auto [unused, inserted] = discovered_peripherals_.insert(peripheral_id); if (inserted && discovered_peripherals_.size() == 1) { callback = devices_discovered_callback_; } } if (callback) { callback(); } } void LinuxFastInitiationManager::OnAdvertisementLost( uint64_t scan_generation, nearby::api::ble::BlePeripheral::UniqueId peripheral_id) { std::function callback; { absl::MutexLock lock(&mutex_); if (!is_scanning_ || scan_generation != scan_generation_ || discovered_peripherals_.erase(peripheral_id) == 0) { return; } if (discovered_peripherals_.empty()) { callback = devices_not_discovered_callback_; } } if (callback) { callback(); } } bool LinuxFastInitiationManager::IsFastInitAdvertisement( const nearby::api::ble::BleAdvertisementData& advertisement_data) const { auto fast_init_uuid = Uuid::FromString(kFastInitServiceUuid); if (!fast_init_uuid.has_value()) { return false; } auto service_data = advertisement_data.service_data.find(*fast_init_uuid); if (service_data == advertisement_data.service_data.end() || service_data->second.size() != kFastInitServiceDataSize) { return false; } const auto bytes = service_data->second.AsStringView(); for (size_t i = 0; i < nearby::api::FastInitBleBeacon::kFastInitModelIdSize; ++i) { if (static_cast(bytes[i]) != nearby::api::FastInitBleBeacon::kFastInitModelId[i]) { return false; } } const uint8_t metadata = static_cast( bytes[nearby::api::FastInitBleBeacon::kFastInitModelIdSize]); const uint8_t version = (metadata >> 5) & 0x07; const uint8_t type = (metadata >> 2) & 0x07; return version == static_cast( nearby::api::FastInitBleBeacon::FastInitVersion::kV1) && type <= static_cast( nearby::api::FastInitBleBeacon::FastInitType::kSilent); } } // namespace linux } // namespace sharing } // namespace nearby