// Copyright 2026 #include "sharing/linux/platform/linux_sharing_platform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "absl/container/flat_hash_map.h" #include "absl/container/flat_hash_set.h" #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "absl/synchronization/mutex.h" #include "absl/time/clock.h" #include "absl/time/time.h" #include "internal/base/file_path.h" #include "internal/base/files.h" #include "internal/platform/bluetooth_adapter.h" #include "internal/platform/byte_array.h" #include "internal/platform/implementation/account_info.h" #include "internal/platform/implementation/auth_status.h" #include "internal/platform/implementation/ble.h" #include "internal/platform/implementation/linux/bluetooth_adapter.h" #include "internal/platform/implementation/linux/bluez.h" #include "internal/platform/implementation/linux/bluez_le_advertisement.h" #include "internal/platform/implementation/linux/dbus.h" #include "internal/platform/implementation/platform.h" #include "internal/platform/mac_address.h" #include "internal/platform/task_runner_impl.h" #include "internal/platform/uuid.h" #include "nlohmann/json.hpp" #include "sharing/internal/api/app_info.h" #include "sharing/internal/api/bluetooth_adapter.h" #include "sharing/internal/api/fast_init_ble_beacon.h" #include "sharing/internal/api/fast_initiation_manager.h" #include "sharing/internal/api/network_monitor.h" #include "sharing/internal/api/preference_manager.h" #include "sharing/internal/api/private_certificate_data.h" #include "sharing/internal/api/public_certificate_database.h" #include "sharing/internal/api/system_info.h" #include "sharing/internal/public/pref_names.h" #include "sharing/proto/rpc_resources.pb.h" namespace nearby::sharing::linux { namespace { using Json = nlohmann::json; using ::nearby::sharing::api::PreferenceManager; using ::nearby::sharing::api::PrivateCertificateData; using ::nearby::sharing::api::PublicCertificateDatabase; using ::nearby::sharing::PrefNames; using ::nearby::sharing::proto::PublicCertificate; using ::nearby::sharing::sync::SyncBindingPrefs; using ::nearby::sharing::sync::SyncConfigPrefs; constexpr absl::string_view kPreferencesFile = "nearby_sharing.json"; constexpr absl::string_view kFileSyncBindingName = "FileSync"; constexpr absl::string_view kAppFirstRunPref = "nearby_sharing.app.first_run"; constexpr absl::string_view kAppActivePref = "nearby_sharing.app.active"; constexpr char kFastInitServiceUuid[] = "0000fe2c-0000-1000-8000-00805f9b34fb"; std::optional GetFastInitUuid() { return Uuid::FromString(kFastInitServiceUuid); } std::string GetFastInitSeed( nearby::api::FastInitBleBeacon::FastInitType type, const ::nearby::linux::BluetoothAdapter* adapter) { std::string seed = absl::StrCat("linux-fast-init:", static_cast(type)); if (adapter != nullptr) { seed = absl::StrCat(seed, ":", adapter->GetMacAddress().ToString()); } return seed; } std::array BuildFastInitSecretIdHash(absl::string_view seed) { std::array hash{}; const std::uint64_t value = std::hash{}(std::string(seed)); for (size_t i = 0; i < hash.size(); ++i) { hash[i] = static_cast((value >> (i * 8)) & 0xff); } return hash; } std::array BuildFastInitSalt( absl::string_view seed) { return {static_cast(std::hash{}(std::string(seed)) & 0xff)}; } std::array BuildFastInitAdvertisingData(nearby::api::FastInitBleBeacon& beacon) { std::array data{}; size_t offset = 0; data[offset++] = nearby::api::FastInitBleBeacon::kFastInitServiceUuid[0]; data[offset++] = nearby::api::FastInitBleBeacon::kFastInitServiceUuid[1]; for (uint8_t byte : nearby::api::FastInitBleBeacon::kFastInitModelId) { data[offset++] = byte; } const uint8_t metadata = (static_cast(beacon.GetVersion()) << 5) | (static_cast(beacon.GetType()) << 2) | (beacon.GetUwbSupported() ? 0x02 : 0x00) | (beacon.GetSenderCertSupported() ? 0x01 : 0x00); data[offset++] = metadata; data[offset++] = static_cast(beacon.GetAdjustedTxPower()); for (uint8_t byte : beacon.GetUwbMetadata()) { data[offset++] = byte; } for (uint8_t byte : beacon.GetUwbAddress()) { data[offset++] = byte; } for (uint8_t byte : beacon.GetSalt()) { data[offset++] = byte; } for (uint8_t byte : beacon.GetSecretIdHash()) { data[offset++] = byte; } data[offset++] = (beacon.GetRequireBtAdvertising() ? 0x80 : 0x00) | (beacon.GetSelfOnlyAdvertising() ? 0x40 : 0x00); return data; } std::unique_ptr<::nearby::linux::BluetoothAdapter> CreateFastInitBluetoothAdapter() { auto system_bus = ::nearby::linux::getSystemBusConnection(); auto manager = ::nearby::linux::bluez::BluezObjectManager(*system_bus); try { auto interfaces = manager.GetManagedObjects(); for (auto& [object, properties] : interfaces) { if (properties.count(sdbus::InterfaceName(org::bluez::Adapter1_proxy::INTERFACE_NAME)) == 1) { LOG(INFO) << __func__ << ": found bluetooth adapter " << object; return std::make_unique<::nearby::linux::BluetoothAdapter>(system_bus, object); } } } catch (const sdbus::Error& e) { DBUS_LOG_METHOD_CALL_ERROR(&manager, "GetManagedObjects", e); } LOG(ERROR) << __func__ << ": couldn't find a bluetooth adapter on this system"; return nullptr; } std::optional GetLanguageCode() { const char* lang = std::getenv("LANG"); if (lang == nullptr || *lang == '\0') { return std::string("en"); } std::string value(lang); size_t dot = value.find('.'); if (dot != std::string::npos) value.resize(dot); size_t underscore = value.find('_'); if (underscore != std::string::npos) value.resize(underscore); if (value.empty()) { return std::string("en"); } return value; } std::string GetEnvOrDefault(const char* key, std::string fallback) { const char* value = std::getenv(key); if (value == nullptr || *value == '\0') { return fallback; } return value; } FilePath BuildPathFromBase(const std::string& base, std::initializer_list components) { std::filesystem::path path(base); for (const std::string& component : components) { path /= component; } return FilePath(path.string()); } std::string GetHomeDirectory() { return GetEnvOrDefault("HOME", "/tmp"); } bool HasNonLoopbackInterface() { struct ifaddrs* interfaces = nullptr; if (getifaddrs(&interfaces) != 0) { return false; } bool connected = false; for (struct ifaddrs* current = interfaces; current != nullptr; current = current->ifa_next) { if (current->ifa_name == nullptr || current->ifa_flags == 0) { continue; } if ((current->ifa_flags & IFF_UP) == 0 || (current->ifa_flags & IFF_LOOPBACK) != 0) { continue; } connected = true; break; } freeifaddrs(interfaces); return connected; } Json ToJson(const PrivateCertificateData& certificate) { return Json{ {PrivateCertificateData::kVisibility, certificate.visibility}, {PrivateCertificateData::kNotBefore, certificate.not_before}, {PrivateCertificateData::kNotAfter, certificate.not_after}, {PrivateCertificateData::kKeyPair, certificate.key_pair}, {PrivateCertificateData::kSecretKey, certificate.secret_key}, {PrivateCertificateData::kMetadataEncryptionKey, certificate.metadata_encryption_key}, {PrivateCertificateData::kId, certificate.id}, {PrivateCertificateData::kUnencryptedMetadata, certificate.unencrypted_metadata_proto}, {PrivateCertificateData::kConsumedSalts, certificate.consumed_salts}, }; } std::optional FromJson(const Json& value) { if (!value.is_object()) { return std::nullopt; } PrivateCertificateData certificate; try { certificate.visibility = value.at(PrivateCertificateData::kVisibility); certificate.not_before = value.at(PrivateCertificateData::kNotBefore); certificate.not_after = value.at(PrivateCertificateData::kNotAfter); certificate.key_pair = value.at(PrivateCertificateData::kKeyPair); certificate.secret_key = value.at(PrivateCertificateData::kSecretKey); certificate.metadata_encryption_key = value.at(PrivateCertificateData::kMetadataEncryptionKey); certificate.id = value.at(PrivateCertificateData::kId); certificate.unencrypted_metadata_proto = value.at(PrivateCertificateData::kUnencryptedMetadata); certificate.consumed_salts = value.at(PrivateCertificateData::kConsumedSalts); return certificate; } catch (const Json::exception&) { return std::nullopt; } } class FailedSigninAttempt final : public SigninAttempt { public: std::string Start( absl::AnyInvocable callback) override { if (callback) { std::move(callback)(UNSUPPORTED, "", "", AccountInfo{}); } return {}; } void Close() override {} }; class LinuxAccountManager final : public AccountManager { public: std::optional GetCurrentAccount() override { return std::nullopt; } std::unique_ptr Login(absl::string_view client_id, absl::string_view client_secret) override { last_client_id_ = std::string(client_id); last_client_secret_ = std::string(client_secret); return std::make_unique(); } void Logout(absl::AnyInvocable logout_callback) override { if (logout_callback) { std::move(logout_callback)(absl::OkStatus()); } } bool GetAccessToken( absl::AnyInvocable)> callback) override { if (!callback) { return false; } std::move(callback)( absl::UnavailableError("Linux account integration is not available")); return true; } std::pair GetOAuthClientCredential() override { return {last_client_id_, last_client_secret_}; } void AddObserver(Observer* observer) override { observers_.insert(observer); } void RemoveObserver(Observer* observer) override { observers_.erase(observer); } void SaveAccountPrefs(absl::string_view user_id, absl::string_view client_id, absl::string_view client_secret) override { last_client_id_ = std::string(client_id); last_client_secret_ = std::string(client_secret); } private: absl::flat_hash_set observers_; std::string last_client_id_; std::string last_client_secret_; }; class SafeLinuxDeviceInfo final : public nearby::DeviceInfo { public: std::string GetOsDeviceName() const override { char hostname[256] = {}; if (gethostname(hostname, sizeof(hostname)) == 0 && hostname[0] != '\0') { return hostname; } const char* env_hostname = std::getenv("HOSTNAME"); if (env_hostname != nullptr && *env_hostname != '\0') { return env_hostname; } return "Linux"; } nearby::api::DeviceInfo::DeviceType GetDeviceType() const override { return nearby::api::DeviceInfo::DeviceType::kLaptop; } nearby::api::DeviceInfo::OsType GetOsType() const override { return nearby::api::DeviceInfo::OsType::kWindows; } FilePath GetDownloadPath() const override { const char* xdg_download_dir = std::getenv("XDG_DOWNLOAD_DIR"); if (xdg_download_dir != nullptr && *xdg_download_dir != '\0') { return FilePath(std::string(xdg_download_dir)); } return BuildPathFromBase(GetHomeDirectory(), {"Downloads"}); } FilePath GetAppDataPath() const override { const std::string config_home = GetEnvOrDefault("XDG_CONFIG_HOME", BuildPathFromBase(GetHomeDirectory(), {".config"}) .ToString()); return BuildPathFromBase(config_home, {"Google Nearby"}); } FilePath GetTemporaryPath() const override { const char* runtime_dir = std::getenv("XDG_RUNTIME_DIR"); if (runtime_dir != nullptr && *runtime_dir != '\0') { return BuildPathFromBase(runtime_dir, {"Google Nearby"}); } const char* tmpdir = std::getenv("TMPDIR"); if (tmpdir != nullptr && *tmpdir != '\0') { return BuildPathFromBase(tmpdir, {"Google Nearby"}); } return BuildPathFromBase("/tmp", {"Google Nearby"}); } FilePath GetLogPath() const override { const std::string state_home = GetEnvOrDefault("XDG_STATE_HOME", BuildPathFromBase(GetHomeDirectory(), {".local", "state"}) .ToString()); return BuildPathFromBase(state_home, {"Google Nearby", "logs"}); } std::optional GetAvailableDiskSpaceInBytes( const FilePath& path) const override { return Files::GetAvailableDiskSpaceInBytes(path); } bool IsScreenLocked() const override { return false; } void RegisterScreenLockedListener( absl::string_view listener_name, std::function callback) override { absl::MutexLock lock(&mutex_); screen_lock_listeners_[std::string(listener_name)] = std::move(callback); } void UnregisterScreenLockedListener(absl::string_view listener_name) override { absl::MutexLock lock(&mutex_); screen_lock_listeners_.erase(std::string(listener_name)); } bool PreventSleep() override { return true; } bool AllowSleep() override { return true; } private: mutable absl::Mutex mutex_; absl::flat_hash_map< std::string, std::function> screen_lock_listeners_ ABSL_GUARDED_BY(mutex_); }; class LinuxPreferenceManager final : public PreferenceManager { public: LinuxPreferenceManager() : storage_(nearby::api::ImplementationPlatform::CreatePreferencesManager( kPreferencesFile)) {} void SetBoolean(absl::string_view key, bool value) override { if (storage_ != nullptr && storage_->SetBoolean(key, value)) { NotifyPreferenceChanged(key); } } void SetInteger(absl::string_view key, int value) override { if (storage_ != nullptr && storage_->SetInteger(key, value)) { NotifyPreferenceChanged(key); } } void SetInt64(absl::string_view key, int64_t value) override { if (storage_ != nullptr && storage_->SetInt64(key, value)) { NotifyPreferenceChanged(key); } } void SetString(absl::string_view key, absl::string_view value) override { if (storage_ != nullptr && storage_->SetString(key, value)) { NotifyPreferenceChanged(key); } } void SetTime(absl::string_view key, absl::Time value) override { if (storage_ != nullptr && storage_->SetTime(key, value)) { NotifyPreferenceChanged(key); } } void SetBooleanArray(absl::string_view key, absl::Span value) override { if (storage_ != nullptr && storage_->SetBooleanArray(key, value)) { NotifyPreferenceChanged(key); } } void SetIntegerArray(absl::string_view key, absl::Span value) override { if (storage_ != nullptr && storage_->SetIntegerArray(key, value)) { NotifyPreferenceChanged(key); } } void SetInt64Array(absl::string_view key, absl::Span value) override { if (storage_ != nullptr && storage_->SetInt64Array(key, value)) { NotifyPreferenceChanged(key); } } void SetStringArray(absl::string_view key, absl::Span value) override { if (storage_ != nullptr && storage_->SetStringArray(key, value)) { NotifyPreferenceChanged(key); } } void SetPrivateCertificateArray( absl::string_view key, absl::Span value) override { Json certificates = Json::array(); for (const PrivateCertificateData& certificate : value) { certificates.push_back(ToJson(certificate)); } if (storage_ != nullptr && storage_->Set(key, certificates)) { NotifyPreferenceChanged(key); } } void SetCertificateExpirationArray( absl::string_view key, absl::Span> value) override { Json expirations = Json::array(); for (const auto& [id, expiration] : value) { expirations.push_back(Json{{"id", id}, {"expiration", expiration}}); } if (storage_ != nullptr && storage_->Set(key, expirations)) { NotifyPreferenceChanged(key); } } void SetDictionaryBooleanValue(absl::string_view key, absl::string_view dictionary_item, bool value) override { SetDictionaryValue(key, dictionary_item, value); } void SetDictionaryIntegerValue(absl::string_view key, absl::string_view dictionary_item, int value) override { SetDictionaryValue(key, dictionary_item, value); } void SetDictionaryInt64Value(absl::string_view key, absl::string_view dictionary_item, int64_t value) override { SetDictionaryValue(key, dictionary_item, value); } void SetDictionaryStringValue(absl::string_view key, absl::string_view dictionary_item, std::string value) override { SetDictionaryValue(key, dictionary_item, std::move(value)); } void RemoveDictionaryItem(absl::string_view key, absl::string_view dictionary_item) override { if (storage_ == nullptr) { return; } Json dictionary = storage_->Get(key, Json::object()); if (!dictionary.is_object() || !dictionary.contains(std::string(dictionary_item))) { return; } dictionary.erase(std::string(dictionary_item)); if (storage_->Set(key, dictionary)) { NotifyPreferenceChanged(key); } } void SetSyncConfigValue(absl::string_view binding_id, const SyncConfigPrefs& value) override { SetString(absl::StrCat(PrefNames::kSyncConfigPrefix, binding_id), value.SerializeAsString()); } void SetSyncBindingValue(const SyncBindingPrefs& value) override { SetString(absl::StrCat(PrefNames::kBindingConfigPrefix, kFileSyncBindingName), value.SerializeAsString()); } bool GetBoolean(absl::string_view key, bool default_value) const override { return storage_ == nullptr ? default_value : storage_->GetBoolean(key, default_value); } int GetInteger(absl::string_view key, int default_value) const override { return storage_ == nullptr ? default_value : storage_->GetInteger(key, default_value); } int64_t GetInt64(absl::string_view key, int64_t default_value) const override { return storage_ == nullptr ? default_value : storage_->GetInt64(key, default_value); } std::string GetString(absl::string_view key, const std::string& default_value) const override { return storage_ == nullptr ? default_value : storage_->GetString(key, default_value); } absl::Time GetTime(absl::string_view key, absl::Time default_value) const override { return storage_ == nullptr ? default_value : storage_->GetTime(key, default_value); } std::vector GetBooleanArray( absl::string_view key, absl::Span default_value) const override { return storage_ == nullptr ? std::vector(default_value.begin(), default_value.end()) : storage_->GetBooleanArray(key, default_value); } std::vector GetIntegerArray( absl::string_view key, absl::Span default_value) const override { return storage_ == nullptr ? std::vector(default_value.begin(), default_value.end()) : storage_->GetIntegerArray(key, default_value); } std::vector GetInt64Array( absl::string_view key, absl::Span default_value) const override { return storage_ == nullptr ? std::vector(default_value.begin(), default_value.end()) : storage_->GetInt64Array(key, default_value); } std::vector GetStringArray( absl::string_view key, absl::Span default_value) const override { return storage_ == nullptr ? std::vector(default_value.begin(), default_value.end()) : storage_->GetStringArray(key, default_value); } std::vector GetPrivateCertificateArray( absl::string_view key) const override { std::vector certificates; if (storage_ == nullptr) { return certificates; } Json values = storage_->Get(key, Json::array()); if (!values.is_array()) { return certificates; } for (const Json& value : values) { std::optional certificate = FromJson(value); if (certificate.has_value()) { certificates.push_back(*certificate); } } return certificates; } std::vector> GetCertificateExpirationArray( absl::string_view key) const override { std::vector> expirations; if (storage_ == nullptr) { return expirations; } Json values = storage_->Get(key, Json::array()); if (!values.is_array()) { return expirations; } for (const Json& value : values) { if (!value.is_object()) { continue; } try { expirations.emplace_back(value.at("id"), value.at("expiration")); } catch (const Json::exception&) { } } return expirations; } std::optional GetDictionaryBooleanValue( absl::string_view key, absl::string_view dictionary_item) const override { return GetDictionaryValue(key, dictionary_item); } std::optional GetDictionaryIntegerValue( absl::string_view key, absl::string_view dictionary_item) const override { return GetDictionaryValue(key, dictionary_item); } std::optional GetDictionaryInt64Value( absl::string_view key, absl::string_view dictionary_item) const override { return GetDictionaryValue(key, dictionary_item); } std::optional GetDictionaryStringValue( absl::string_view key, absl::string_view dictionary_item) const override { return GetDictionaryValue(key, dictionary_item); } std::optional GetSyncConfigValue( absl::string_view binding_id) const override { std::string serialized = GetString(absl::StrCat(PrefNames::kSyncConfigPrefix, binding_id), ""); if (serialized.empty()) { return std::nullopt; } SyncConfigPrefs value; if (!value.ParseFromString(serialized)) { return std::nullopt; } return value; } std::optional GetSyncBindingValue() const override { std::string serialized = GetString( absl::StrCat(PrefNames::kBindingConfigPrefix, kFileSyncBindingName), ""); if (serialized.empty()) { return std::nullopt; } SyncBindingPrefs value; if (!value.ParseFromString(serialized)) { return std::nullopt; } return value; } void Remove(absl::string_view key) override { if (storage_ == nullptr) { return; } storage_->Remove(key); NotifyPreferenceChanged(key); } void RemoveAllSyncConfigs() override { if (storage_ != nullptr) { storage_->RemoveKeyPrefix(PrefNames::kSyncConfigPrefix); } } void RemoveAllBindingConfigs() override { if (storage_ != nullptr) { storage_->RemoveKeyPrefix(PrefNames::kBindingConfigPrefix); } } void AddObserver( absl::string_view name, std::function observer) override { absl::MutexLock lock(&mutex_); observers_[std::string(name)] = std::move(observer); } void RemoveObserver(absl::string_view name) override { absl::MutexLock lock(&mutex_); observers_.erase(std::string(name)); } private: template void SetDictionaryValue(absl::string_view key, absl::string_view dictionary_item, T value) { if (storage_ == nullptr) { return; } Json dictionary = storage_->Get(key, Json::object()); if (!dictionary.is_object()) { return; } const std::string item(dictionary_item); if (dictionary.contains(item) && dictionary[item] == value) { return; } dictionary[item] = value; if (storage_->Set(key, dictionary)) { NotifyPreferenceChanged(key); } } template std::optional GetDictionaryValue( absl::string_view key, absl::string_view dictionary_item) const { if (storage_ == nullptr) { return std::nullopt; } Json dictionary = storage_->Get(key, Json::object()); if (!dictionary.is_object()) { return std::nullopt; } const std::string item(dictionary_item); if (!dictionary.contains(item)) { return std::nullopt; } try { return dictionary.at(item).get(); } catch (const Json::exception&) { return std::nullopt; } } void NotifyPreferenceChanged(absl::string_view key) { absl::flat_hash_map> observers_copy; { absl::MutexLock lock(&mutex_); observers_copy = observers_; } for (const auto& [name, observer] : observers_copy) { if (observer) { observer(key); } } } std::unique_ptr storage_; mutable absl::Mutex mutex_; absl::flat_hash_map> observers_ ABSL_GUARDED_BY(mutex_); }; class LinuxNetworkMonitor final : public nearby::api::NetworkMonitor { public: LinuxNetworkMonitor(std::function lan_connected_callback, std::function internet_connected_callback) : nearby::api::NetworkMonitor(std::move(lan_connected_callback), std::move(internet_connected_callback)) { const bool lan_connected = IsLanConnected(); const bool internet_connected = IsInternetConnected(); if (lan_connected_callback_) { lan_connected_callback_(lan_connected); } if (internet_connected_callback_) { internet_connected_callback_(internet_connected); } } bool IsLanConnected() override { return HasNonLoopbackInterface(); } bool IsInternetConnected() override { return HasNonLoopbackInterface(); } }; class LinuxSystemInfo final : public nearby::api::SystemInfo { public: std::string GetComputerManufacturer() override { return "Unknown"; } std::string GetComputerModel() override { return "Unknown"; } int64_t GetComputerPhysicalMemory() override { struct sysinfo info; if (sysinfo(&info) != 0) { return 0; } return static_cast(info.totalram) * info.mem_unit; } int GetComputerProcessorCount() override { long processors = sysconf(_SC_NPROCESSORS_CONF); return processors > 0 ? static_cast(processors) : 1; } int GetComputerLogicProcessorCount() override { long processors = sysconf(_SC_NPROCESSORS_ONLN); return processors > 0 ? static_cast(processors) : 1; } int GetProcessorMemoryInfo() override { return 0; } BatteryChargeStatus QueryBatteryInfo(int& seconds, int& percent, bool& battery_saver) override { seconds = 0; percent = 0; battery_saver = false; return BatteryChargeStatus::UNKNOWN; } std::string GetOsManufacturer() override { return "Linux"; } std::string GetOsName() override { return "Linux"; } std::string GetOsVersion() override { struct utsname info; if (uname(&info) != 0) { return {}; } return info.release; } std::string GetOsArchitecture() override { struct utsname info; if (uname(&info) != 0) { return {}; } return info.machine; } std::string GetOsLanguage() override { return GetLanguageCode().value_or("en"); } std::string GetProcessorManufacturer() override { return "Unknown"; } std::string GetProcessorName() override { return "Unknown"; } std::list GetBluetoothDriverInfos() override { return {}; } std::list GetNetworkDriverInfos() override { return {}; } void GetBatteryUsageReport(const FilePath& save_path) override {} }; class LinuxAppInfo final : public nearby::api::AppInfo { public: explicit LinuxAppInfo(PreferenceManager& preference_manager) : preference_manager_(preference_manager) {} std::optional GetAppVersion() override { return std::string("linux"); } std::optional GetAppLanguage() override { return GetLanguageCode(); } std::optional GetUpdateTrack() override { return std::nullopt; } std::optional GetAppInstallSource() override { return std::string("manual"); } bool GetFirstRunDone() override { return preference_manager_.GetBoolean(kAppFirstRunPref, false); } bool SetFirstRunDone(bool value) override { preference_manager_.SetBoolean(kAppFirstRunPref, value); return true; } bool SetActiveFlag() override { preference_manager_.SetInt64(kAppActivePref, absl::ToUnixNanos(absl::Now())); return true; } private: PreferenceManager& preference_manager_; }; class LinuxFastInitBleBeacon final : public nearby::api::FastInitBleBeacon { public: void SerializeToByteArray() override { SetAdDataByteArray(BuildFastInitAdvertisingData(*this)); } void ParseFromByteArray() override { const auto data = GetAdDataByteArray(); if (data[0] != nearby::api::FastInitBleBeacon::kFastInitServiceUuid[0] || data[1] != nearby::api::FastInitBleBeacon::kFastInitServiceUuid[1]) { return; } const uint8_t metadata = data[5]; SetVersion(static_cast((metadata >> 5) & 0x07)); SetType(static_cast((metadata >> 2) & 0x07)); SetUwbSupported((metadata & 0x02) != 0); SetSenderCertSupported((metadata & 0x01) != 0); SetAdjustedTxPower(static_cast(data[6])); std::array uwb_metadata{}; uwb_metadata[0] = data[7]; SetUwbMetadata(uwb_metadata); std::array uwb_address{}; for (size_t i = 0; i < uwb_address.size(); ++i) { uwb_address[i] = data[8 + i]; } SetUwbAddress(uwb_address); std::array salt{}; salt[0] = data[16]; SetSalt(salt); std::array secret_id_hash{}; for (size_t i = 0; i < secret_id_hash.size(); ++i) { secret_id_hash[i] = data[17 + i]; } SetSecretIdHash(secret_id_hash); const uint8_t flags = data[25]; SetRequireBtAdvertising((flags & 0x80) != 0); SetSelfOnlyAdvertising((flags & 0x40) != 0); } }; class LinuxFastInitiationManager final : public nearby::api::FastInitiationManager { public: explicit LinuxFastInitiationManager(nearby::api::FastInitBleBeacon& beacon) : beacon_(beacon), adapter_(CreateFastInitBluetoothAdapter()) { if (adapter_ != nullptr) { adv_manager_ = std::make_unique<::nearby::linux::bluez::LEAdvertisementManager>( *adapter_->GetConnection(), *adapter_); } } void StartAdvertising( nearby::api::FastInitBleBeacon::FastInitType type, std::function callback, std::function error_callback) override { 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 = GetFastInitUuid(); 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(0); beacon_.SetUwbMetadata({}); beacon_.SetUwbAddress({}); const std::string seed = GetFastInitSeed(type, adapter_.get()); beacon_.SetSalt(BuildFastInitSalt(seed)); beacon_.SetSecretIdHash(BuildFastInitSecretIdHash(seed)); beacon_.SetRequireBtAdvertising(false); beacon_.SetSelfOnlyAdvertising( type == nearby::api::FastInitBleBeacon::FastInitType::kSilent); beacon_.SerializeToByteArray(); const auto ad_data = beacon_.GetAdDataByteArray(); nearby::api::ble::BleAdvertisementData advertising_data; advertising_data.is_extended_advertisement = true; 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 StopAdvertising(std::function callback) override { 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 StartScanning( std::function devices_discovered_callback, std::function devices_not_discovered_callback, std::function error_callback) override { if (error_callback) { error_callback( nearby::api::FastInitiationManager::Error::kHardwareNotSupported); } } void StopScanning(std::function callback) override { if (callback) { callback(); } } bool IsAdvertising() override { absl::MutexLock lock(&mutex_); return advertisement_ != nullptr; } bool IsScanning() override { return false; } private: nearby::api::FastInitBleBeacon& beacon_; std::unique_ptr<::nearby::linux::BluetoothAdapter> adapter_; std::unique_ptr<::nearby::linux::bluez::LEAdvertisementManager> adv_manager_; absl::Mutex mutex_; std::unique_ptr<::nearby::linux::bluez::LEAdvertisement> advertisement_ ABSL_GUARDED_BY(mutex_); }; class LinuxBluetoothAdapter final : public nearby::sharing::api::BluetoothAdapter { public: bool IsPresent() const override { return GetAddress().IsSet(); } bool IsPowered() const override { return adapter_.IsValid() && adapter_.IsEnabled(); } bool IsLowEnergySupported() const override { return true; } bool IsScanOffloadSupported() const override { return false; } bool IsAdvertisementOffloadSupported() const override { return false; } bool IsExtendedAdvertisingSupported() const override { return false; } bool IsPeripheralRoleSupported() const override { return true; } PermissionStatus GetOsPermissionStatus() const override { return PermissionStatus::kAllowed; } void SetPowered(bool powered, std::function success_callback, std::function error_callback) override { if (!adapter_.IsValid()) { if (error_callback) { error_callback(); } return; } const bool success = adapter_.SetStatus( powered ? nearby::api::BluetoothAdapter::Status::kEnabled : nearby::api::BluetoothAdapter::Status::kDisabled); if (success) { if (success_callback) { success_callback(); } return; } if (error_callback) { error_callback(); } } std::optional GetAdapterId() const override { if (!adapter_.IsValid()) { return std::nullopt; } std::string name = adapter_.GetName(); if (name.empty()) { return std::nullopt; } return name; } MacAddress GetAddress() const override { if (!adapter_.IsValid()) { return {}; } return adapter_.GetAddress(); } void AddObserver(Observer* observer) override { observers_.insert(observer); } void RemoveObserver(Observer* observer) override { observers_.erase(observer); } bool HasObserver(Observer* observer) override { return observers_.contains(observer); } private: nearby::BluetoothAdapter adapter_; absl::flat_hash_set observers_; }; class LinuxPublicCertificateDatabase final : public PublicCertificateDatabase { public: void Initialize(absl::AnyInvocable callback) override { if (callback) { std::move(callback)(InitStatus::kOk); } } void LoadEntries( absl::AnyInvocable>) &&> callback) override { auto entries = std::make_unique>(); { absl::MutexLock lock(&mutex_); for (const auto& [id, certificate] : entries_) { entries->push_back(certificate); } } if (callback) { std::move(callback)(true, std::move(entries)); } } void LoadCertificate( absl::string_view id, absl::AnyInvocable) &&> callback) override { std::unique_ptr certificate; { absl::MutexLock lock(&mutex_); auto it = entries_.find(std::string(id)); if (it != entries_.end()) { certificate = std::make_unique(it->second); } } if (callback) { std::move(callback)(certificate != nullptr, std::move(certificate)); } } void AddCertificates(absl::Span certificates, absl::AnyInvocable callback) override { { absl::MutexLock lock(&mutex_); for (const PublicCertificate& certificate : certificates) { entries_[certificate.secret_id()] = certificate; } } if (callback) { std::move(callback)(true); } } void RemoveCertificatesById( std::vector ids_to_remove, absl::AnyInvocable callback) override { { absl::MutexLock lock(&mutex_); for (const std::string& id : ids_to_remove) { entries_.erase(id); } } if (callback) { std::move(callback)(true); } } void Destroy(absl::AnyInvocable callback) override { { absl::MutexLock lock(&mutex_); entries_.clear(); } if (callback) { std::move(callback)(true); } } private: absl::Mutex mutex_; std::map entries_ ABSL_GUARDED_BY(mutex_); }; } // namespace LinuxSharingPlatform::LinuxSharingPlatform() { Initialize({}); } LinuxSharingPlatform::LinuxSharingPlatform(std::string device_name_override) { Initialize(std::move(device_name_override)); } LinuxSharingPlatform::~LinuxSharingPlatform() = default; void LinuxSharingPlatform::Initialize(std::string device_name_override) { preference_manager_ = std::make_unique(); account_manager_ = std::make_unique(); bluetooth_adapter_ = std::make_unique(); fast_init_ble_beacon_ = std::make_unique(); fast_initiation_manager_ = std::make_unique(*fast_init_ble_beacon_); default_task_runner_ = std::make_unique(1); device_info_ = std::make_unique(); if (!device_name_override.empty()) { preference_manager_->SetString(PrefNames::kDeviceName, device_name_override); } } void LinuxSharingPlatform::InitProductIdGetter( absl::string_view (*product_id_getter)()) { product_id_getter_ = product_id_getter; } std::unique_ptr LinuxSharingPlatform::CreateNetworkMonitor( std::function lan_connected_callback, std::function internet_connected_callback) { return std::make_unique( std::move(lan_connected_callback), std::move(internet_connected_callback)); } nearby::sharing::api::BluetoothAdapter& LinuxSharingPlatform::GetBluetoothAdapter() { return *bluetooth_adapter_; } nearby::api::FastInitBleBeacon& LinuxSharingPlatform::GetFastInitBleBeacon() { return *fast_init_ble_beacon_; } nearby::api::FastInitiationManager& LinuxSharingPlatform::GetFastInitiationManager() { return *fast_initiation_manager_; } std::unique_ptr LinuxSharingPlatform::CreateSystemInfo() { return std::make_unique(); } std::unique_ptr LinuxSharingPlatform::CreateAppInfo() { return std::make_unique(*preference_manager_); } nearby::sharing::api::PreferenceManager& LinuxSharingPlatform::GetPreferenceManager() { return *preference_manager_; } AccountManager& LinuxSharingPlatform::GetAccountManager() { return *account_manager_; } TaskRunner& LinuxSharingPlatform::GetDefaultTaskRunner() { return *default_task_runner_; } nearby::DeviceInfo& LinuxSharingPlatform::GetDeviceInfo() { return *device_info_; } std::unique_ptr LinuxSharingPlatform::CreatePublicCertificateDatabase( const FilePath& database_path) { return std::make_unique(); } bool LinuxSharingPlatform::UpdateFileOriginMetadata( std::vector& file_paths) { return true; } } // namespace nearby::sharing::linux