diff --git a/internal/platform/implementation/linux/BUILD b/internal/platform/implementation/linux/BUILD new file mode 100644 index 00000000..0e150f61 --- /dev/null +++ b/internal/platform/implementation/linux/BUILD @@ -0,0 +1,282 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +licenses(["notice"]) + +cc_library( + name = "types", + hdrs = [ + "atomic_boolean.h", + "atomic_reference.h", + "atomic_uint32.h", + "bluetooth_adapter.h", + "condition_variable.h", + "device_info.h", + "executor.h", + "future.h", + "mutex.h", + "preferences_manager.h", + "preferences_repository.h", + "scheduled_executor.h", + "submittable_executor.h", + "timer.h", + "thread_pool.h", + "log_message.h", + "utils.h", + ], + srcs = [ + "device_info.cc", + "log_message.cc", + "timer.cc", + ], + copts = ["-lrt"], + visibility = ["//third_party/nearby/sharing/internal/impl/linux:__pkg__"], + deps = [ + ":comm", + "//internal/platform/implementation:types", + "@com_google_absl//absl/strings", + "@libsystemd//:lib", + "@sdbus_cpp//:lib", + ], +) + +cc_library( + name = "comm", + hdrs = [ + "avahi.h", + "ble_gatt_server.h", + "ble_gatt_client.h", + "ble_medium.h", + "ble_v2_medium.h", + "ble_v2_server_socket.h", + "bluetooth_adapter.h", + "bluetooth_bluez_profile.h", + "bluetooth_classic_device.h", + "bluetooth_classic_medium.h", + "bluetooth_classic_server_socket.h", + "bluetooth_classic_socket.h", + "bluetooth_devices.h", + "bluetooth_pairing.h", + "bluez.h", + "bluez_advertisement_monitor.h", + "bluez_advertisement_monitor_manager.h", + "bluez_gatt_characteristic_client.h", + "bluez_gatt_characteristic_server.h", + "bluez_gatt_manager.h", + "bluez_gatt_service_client.h", + "bluez_gatt_service_server.h", + "bluez_le_advertisement.h", + "dbus.h", + "network_manager.h", + "network_manager_active_connection.h", + "network_manager_access_point.h", + "stream.h", + "tcp_server_socket.h", + "wifi_direct.h", + "wifi_direct_server_socket.h", + "wifi_direct_socket.h", + "wifi_hotspot.h", + "wifi_hotspot_server_socket.h", + "wifi_hotspot_socket.h", + "wifi_lan.h", + "wifi_lan_server_socket.h", + "wifi_lan_socket.h", + "wifi_medium.h", + "wifi_socket.h", + ], + deps = [ + "//internal/platform:base", + "//internal/platform:comm", + "//internal/platform:types", + "//internal/platform:uuid", + "//internal/platform/implementation:comm", + "//internal/platform/implementation:types", + "//internal/platform/implementation/linux/generated:types", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/functional:any_invocable", + "@com_google_absl//absl/memory", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/synchronization", + "@com_google_absl//absl/time", + "@com_google_absl//absl/types:optional", + "@libsystemd//:lib", + "@sdbus_cpp//:lib", + ], + visibility = ["//visibility:private"], +) + +cc_library( + name = "crypto", + srcs = [ + "crypto.cc", + ], + visibility = ["//visibility:private"], + deps = [ + "//internal/platform:base", + "//internal/platform/implementation:types", + "@boringssl//:crypto", + "@com_google_absl//absl/strings", +]) + +cc_library( + name = "linux", + srcs = [ + "avahi.cc", + "ble_gatt_client.cc", + "ble_gatt_server.cc", + "ble_v2_medium.cc", + "bluetooth_adapter.cc", + "bluetooth_bluez_profile.cc", + "bluetooth_classic_socket.cc", + "bluetooth_classic_device.cc", + "bluetooth_classic_medium.cc", + "bluetooth_classic_server_socket.cc", + "bluetooth_devices.cc", + "bluetooth_pairing.cc", + "bluez.cc", + "bluez_advertisement_monitor.cc", + "bluez_gatt_characteristic_client.cc", + "bluez_gatt_characteristic_server.cc", + "bluez_gatt_service_server.cc", + "bluez_le_advertisement.cc", + "dbus.cc", + "executor.cc", + "network_manager.cc", + "network_manager_active_connection.cc", + "platform.cc", + "preferences_manager.cc", + "preferences_repository.cc", + "scheduled_executor.cc", + "stream.cc", + "submittable_executor.cc", + "system_clock.cc", + "thread_pool.cc", + "utils.cc", + "wifi_direct.cc", + "wifi_direct_server_socket.cc", + "wifi_hotspot.cc", + "wifi_hotspot_server_socket.cc", + "wifi_lan.cc", + "wifi_lan_server_socket.cc", + "wifi_medium.cc", + ], + visibility = [ + "//connections:__subpackages__", + "//fastpair:__subpackages__", + "//location/nearby:__subpackages__", + "//presence:__subpackages__", + "//third_party/nearby/sharing:__subpackages__", + ], + deps = [ + ":comm", + ":crypto", # build_cleaner: keep + ":types", + "//internal/flags:nearby_flags", + "//internal/platform:base", + "//internal/platform:cancellation_flag", + "//internal/platform:comm", + "//internal/platform:types", + "//internal/platform:uuid", + "//internal/platform/flags:platform_flags", + "//internal/platform/implementation:comm", + "//internal/platform/implementation:platform", + "//internal/platform/implementation:types", + "//internal/platform/implementation/shared:count_down_latch", + "//internal/platform/implementation/shared:file", + "//internal/platform/implementation/linux/generated:types", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/functional:any_invocable", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/memory", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", + "@com_google_absl//absl/synchronization", + "@com_google_absl//absl/time", + "@com_google_absl//absl/types:optional", + "@nlohmann_json//:json", + "@libsystemd//:lib", + "@sdbus_cpp//:lib", + "@libcurl//:lib" + ], +) + +cc_library( + name = "test_utils", + srcs = [ + "test_utils.cc", + ], + hdrs = [ + "test_data.h", + "test_utils.h", + ], + visibility = [ + "//visibility:private", # Only private by automation, not intent. Owner may accept CLs adding visibility. See go/scheuklappen#explicit-private. + ], + deps = [ + "//internal/platform:base", + "@nlohmann_json//:json", + ":types", + ], +) + +cc_test( + name = "impl_test", + size = "small", + srcs = [ + "atomic_boolean_test.cc", + "atomic_reference_test.cc", + "mutex_test.cc", + "utils_test.cc", + # "bluetooth_adapter_test.cc", + # "crypto_test.cc", + # "device_info_test.cc", + # "executor_test.cc", + # "file_path_test.cc", + # "http_loader_test.cc", + # "preferences_manager_test.cc", + # "preferences_repository_test.cc", + # "scheduled_executor_test.cc", + # "submittable_executor_test.cc", + # "thread_pool_test.cc", + # "timer_test.cc", + ], + tags = ["notap"], + deps = [ + ":comm", + ":crypto", + ":test_utils", + ":types", + ":linux", + "//internal/platform:base", + "//internal/platform/implementation:comm", + "//internal/platform/implementation:platform", + "//internal/platform/implementation:types", + "//internal/platform/implementation/shared:count_down_latch", + "@com_github_protobuf_matchers//protobuf-matchers", + "@com_google_absl//absl/status", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/synchronization", + "@com_google_absl//absl/time", + "@com_google_absl//absl/types:span", + "@com_google_googletest//:gtest_main", + "@nlohmann_json//:json", + ], +) diff --git a/internal/platform/implementation/linux/atomic_boolean.h b/internal/platform/implementation/linux/atomic_boolean.h new file mode 100644 index 00000000..cb067bdc --- /dev/null +++ b/internal/platform/implementation/linux/atomic_boolean.h @@ -0,0 +1,41 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_ATOMIC_BOOLEAN_H_ +#define PLATFORM_IMPL_LINUX_ATOMIC_BOOLEAN_H_ + +#include +#include "internal/platform/implementation/atomic_boolean.h" +namespace nearby { +namespace linux { +// A boolean value that may be updated atomically. +class AtomicBoolean : public api::AtomicBoolean { + public: + AtomicBoolean(bool initial_value = false) : atomic_boolean_(initial_value) {} + ~AtomicBoolean() override = default; + + // Atomically read and return current value. + bool Get() const override { return atomic_boolean_; }; + + // Atomically exchange original value with a new one. Return previous value. + bool Set(bool value) override { return atomic_boolean_.exchange(value); }; + + private: + std::atomic_bool atomic_boolean_ = false; +}; + +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPL_LINUX_ATOMIC_BOOLEAN_H_ diff --git a/internal/platform/implementation/linux/atomic_boolean_test.cc b/internal/platform/implementation/linux/atomic_boolean_test.cc new file mode 100644 index 00000000..ecd5e41a --- /dev/null +++ b/internal/platform/implementation/linux/atomic_boolean_test.cc @@ -0,0 +1,32 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "internal/platform/implementation/linux/atomic_boolean.h" + +#include "gtest/gtest.h" + +TEST(atomic_boolean, SuccessfulCreation) { + // Arrange + nearby::linux::AtomicBoolean atomicBoolean; + bool oldValue = true; + bool result = false; + + // Act + oldValue = atomicBoolean.Set(true); + result = atomicBoolean.Get(); + + // Assert + EXPECT_TRUE(result); + EXPECT_FALSE(oldValue); +} \ No newline at end of file diff --git a/internal/platform/implementation/linux/atomic_reference.h b/internal/platform/implementation/linux/atomic_reference.h new file mode 100644 index 00000000..ec7793dd --- /dev/null +++ b/internal/platform/implementation/linux/atomic_reference.h @@ -0,0 +1,43 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_ATOMIC_REFERENCE_H_ +#define PLATFORM_IMPL_LINUX_ATOMIC_REFERENCE_H_ + +#include + +#include "internal/platform/implementation/atomic_reference.h" + +namespace nearby { +namespace linux { + +// Type that allows 32-bit atomic reads and writes. +class AtomicUint32 : public api::AtomicUint32 { + public: + ~AtomicUint32() override = default; + + // Atomically reads and returns stored value. + std::uint32_t Get() const override { return atomic_uint32_; }; + + // Atomically stores value. + void Set(std::uint32_t value) override { atomic_uint32_ = value; } + + private: + std::atomic_int32_t atomic_uint32_ = 0; +}; + +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPL_LINUX_ATOMIC_REFERENCE_H_ \ No newline at end of file diff --git a/internal/platform/implementation/linux/atomic_reference_test.cc b/internal/platform/implementation/linux/atomic_reference_test.cc new file mode 100644 index 00000000..8e69a64a --- /dev/null +++ b/internal/platform/implementation/linux/atomic_reference_test.cc @@ -0,0 +1,72 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "internal/platform/implementation/linux/atomic_reference.h" + +#include "gtest/gtest.h" + +TEST(atomic_reference, SuccessfulCreation) { + // Arrange + nearby::linux::AtomicUint32 atomicUint32; + uint32_t result = UINT32_MAX; + const uint32_t expected = 0; + + // Act + result = atomicUint32.Get(); + + // Assert + EXPECT_EQ(result, expected); +} + +TEST(atomic_reference, SuccessfulMaxSet) { + // Arrange + nearby::linux::AtomicUint32 atomicUint32; + uint32_t result = 0; + const uint32_t expected = UINT32_MAX; + + // Act + atomicUint32.Set(UINT32_MAX); + result = atomicUint32.Get(); + + // Assert + EXPECT_EQ(result, expected); +} + +TEST(atomic_reference, SuccessfulMinSet) { + // Arrange + nearby::linux::AtomicUint32 atomicUint32; + uint32_t result = UINT32_MAX; + const uint32_t expected = 0; + + // Act + atomicUint32.Set(0); + result = atomicUint32.Get(); + + // Assert + EXPECT_EQ(result, expected); +} + +TEST(atomic_reference, SetNegativeOneReturnsMAXUINT) { + // Arrange + nearby::linux::AtomicUint32 atomicUint32; + uint32_t result = 0; + const uint32_t expected = UINT32_MAX; + + // Act + atomicUint32.Set(-1); // Try Set -1, should actually store UINT32_MAX + result = atomicUint32.Get(); + + // Assert + EXPECT_EQ(result, expected); +} \ No newline at end of file diff --git a/internal/platform/implementation/linux/atomic_uint32.h b/internal/platform/implementation/linux/atomic_uint32.h new file mode 100644 index 00000000..28ce3016 --- /dev/null +++ b/internal/platform/implementation/linux/atomic_uint32.h @@ -0,0 +1,42 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_ATOMIC_UINT32_H_ +#define PLATFORM_IMPL_LINUX_ATOMIC_UINT32_H_ + +#include +#include +#include "internal/platform/implementation/atomic_reference.h" + +namespace nearby { +namespace linux { +// A boolean value that may be updated atomically. +class AtomicUint32 : public api::AtomicUint32 { + public: + AtomicUint32(std::uint32_t initial_value) : atomic_uint_(initial_value) {} + ~AtomicUint32() override = default; + + // Atomically read and return current value. + std::uint32_t Get() const override { return atomic_uint_; }; + + // Atomically exchange original value with a new one. Return previous value. + void Set(std::uint32_t value) override { atomic_uint_ = value; }; + + private: + std::atomic_bool atomic_uint_ = false; +}; +} // namespace linux +} // namespace nearby + +#endif diff --git a/internal/platform/implementation/linux/avahi.cc b/internal/platform/implementation/linux/avahi.cc new file mode 100644 index 00000000..1092a73b --- /dev/null +++ b/internal/platform/implementation/linux/avahi.cc @@ -0,0 +1,128 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "internal/platform/implementation/linux/avahi.h" +#include "internal/platform/implementation/linux/dbus.h" +#include "internal/platform/logging.h" +#include "internal/platform/nsd_service_info.h" + +namespace nearby { +namespace linux { +namespace avahi { +void ServiceBrowser::onItemNew(const int32_t &interface, + const int32_t &protocol, const std::string &name, + const std::string &type, + const std::string &domain, + const uint32_t &flags) { + NEARBY_LOGS(VERBOSE) << __func__ << ": " << getObjectPath() + << ": Found new item through the ServiceBrowser: " + << "interface: " << interface << ", protocol: " + << protocol << ", name: '" << name << "', type: '" + << type << "', domain: '" << domain + << "', flags: " << flags; + if (flags & kAvahiLookupResultLocal) { + NEARBY_LOGS(VERBOSE) << __func__ << ": Ignoring local service."; + return; + } + + NsdServiceInfo info; + try { + auto [r_iface, r_protocol, r_name, r_type, r_domain, r_host, r_aprotocol, + r_address, r_port, r_txt, r_flags] = + server_->ResolveService(interface, protocol, name, type, domain, + 0, // AVAHI_PROTO_INET + 0); + info.SetServiceName(r_name); + info.SetIPAddress(r_address); + info.SetPort(r_port); + info.SetServiceType(r_type); + for (auto &attr : r_txt) { + auto attr_str = std::string(attr.begin(), attr.end()); + size_t pos = attr_str.find('='); + if (pos == 0 || pos == std::string::npos || pos == attr_str.size() - 1) { + NEARBY_LOGS(WARNING) << " found invalid text attribute: " << attr_str; + continue; + } + + info.SetTxtRecord(attr_str.substr(0, pos), attr_str.substr(pos + 1)); + } + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(server_, "ResolveService", e); + } + + discovery_cb_.service_discovered_cb(std::move(info)); +} + +void ServiceBrowser::onItemRemove( + const int32_t &interface, const int32_t &protocol, const std::string &name, + const std::string &type, const std::string &domain, const uint32_t &flags) { + // TODO: Can we even resolve removed items? + NEARBY_LOGS(VERBOSE) << __func__ << ": " << getObjectPath() + << ": Item removed through the ServiceBrowser: " + << "interface: " << interface << ", protocol: " + << protocol << ", name: '" << name << "', type: '" + << type << "', domain: '" << domain + << "', flags: " << flags; + if (flags & kAvahiLookupResultLocal) { + NEARBY_LOGS(VERBOSE) << __func__ << ": Ignoring local service."; + return; + } + + NsdServiceInfo info; + try { + auto [r_iface, r_protocol, r_name, r_type, r_domain, r_host, r_aprotocol, + r_address, r_port, r_txt, r_flags] = + server_->ResolveService(interface, protocol, name, type, domain, + 0, // AVAHI_PROTO_INET + flags); + info.SetServiceName(r_name); + info.SetIPAddress(r_address); + info.SetPort(r_port); + info.SetServiceType(r_type); + for (auto &attr : r_txt) { + auto attr_str = std::string(attr.begin(), attr.end()); + size_t pos = attr_str.find('='); + if (pos == 0 || pos == std::string::npos || pos == attr_str.size() - 1) { + NEARBY_LOGS(WARNING) << " found invalid text attribute: " << attr_str; + continue; + } + + info.SetTxtRecord(attr_str.substr(0, pos), attr_str.substr(pos + 1)); + } + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(server_, "ResolveService", e); + } + + discovery_cb_.service_lost_cb(std::move(info)); +} + +void ServiceBrowser::onFailure(const std::string &error) { + NEARBY_LOGS(ERROR) << __func__ << ": " << getObjectPath() + << ": ServiceBrowser reported a failure: " << error; +} + +void ServiceBrowser::onAllForNow() { + NEARBY_LOGS(VERBOSE) << __func__ << ": " << getObjectPath() + << ": notified via ServiceBrowser that all records have " + "been added for now"; +} + +void ServiceBrowser::onCacheExhausted() { + NEARBY_LOGS(VERBOSE) << __func__ << ": " << getObjectPath() + << ": notified via ServiceBrowser of cache exhaustion"; +} + +} // namespace avahi +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/avahi.h b/internal/platform/implementation/linux/avahi.h new file mode 100644 index 00000000..e7ed6e20 --- /dev/null +++ b/internal/platform/implementation/linux/avahi.h @@ -0,0 +1,127 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_AVAHI_H_ +#define PLATFORM_IMPL_LINUX_AVAHI_H_ + +#include + +#include +#include + +#include "internal/platform/implementation/linux/dbus.h" +#include "internal/platform/implementation/linux/generated/dbus/avahi/entrygroup_client.h" +#include "internal/platform/implementation/linux/generated/dbus/avahi/server2_client.h" +#include "internal/platform/implementation/linux/generated/dbus/avahi/servicebrowser_client.h" +#include "internal/platform/implementation/wifi_lan.h" + +namespace nearby { +namespace linux { +namespace avahi { +class Server final + : public sdbus::ProxyInterfaces { + public: + Server(sdbus::IConnection &system_bus) + : ProxyInterfaces(system_bus, "org.freedesktop.Avahi", "/") { + registerProxy(); + } + ~Server() { unregisterProxy(); } + + protected: + void onStateChanged(const int32_t &state, const std::string &error) override { + } +}; + +class EntryGroup final + : public sdbus::ProxyInterfaces { + public: + EntryGroup(sdbus::IConnection &system_bus, + const sdbus::ObjectPath &entry_group_object_path) + : ProxyInterfaces(system_bus, "org.freedesktop.Avahi", + entry_group_object_path) { + registerProxy(); + } + ~EntryGroup() { + NEARBY_LOGS(VERBOSE) << __func__ << ": Freeing entry group " + << getObjectPath(); + + try { + Free(); + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(this, "Free", e); + } + + unregisterProxy(); + } + + protected: + void onStateChanged(const int32_t &state, const std::string &error) override { + } +}; + +class ServiceBrowser final + : public sdbus::ProxyInterfaces< + org::freedesktop::Avahi::ServiceBrowser_proxy> { + public: + ServiceBrowser(sdbus::IConnection &system_bus, + const sdbus::ObjectPath &service_browser_object_path, + api::WifiLanMedium::DiscoveredServiceCallback callback, + std::shared_ptr avahi_server) + : ProxyInterfaces(system_bus, "org.freedesktop.Avahi", + service_browser_object_path), + discovery_cb_(std::move(callback)), + server_(avahi_server) { + registerProxy(); + } + ~ServiceBrowser() { + NEARBY_LOGS(VERBOSE) << __func__ << ": Freeing service browser " + << getObjectPath(); + + try { + Free(); + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(this, "Free", e); + } + unregisterProxy(); + } + + protected: + void onItemNew(const int32_t &interface, const int32_t &protocol, + const std::string &name, const std::string &type, + const std::string &domain, const uint32_t &flags) override; + void onItemRemove(const int32_t &interface, const int32_t &protocol, + const std::string &name, const std::string &type, + const std::string &domain, const uint32_t &flags) override; + void onFailure(const std::string &error) override; + void onAllForNow() override; + void onCacheExhausted() override; + + private: + enum LookupResultFlags { + kAvahiLookupResultFlagCached = 1, + kAvahiLookupResultFlagWideArea = 2, + kAvahiLookupResultFlagMulticast = 4, + kAvahiLookupResultLocal = 8, + kAvahiLookupResultOurOwn = 16, + kAvahiLookupResultStatic = 32, + }; + + api::WifiLanMedium::DiscoveredServiceCallback discovery_cb_; + std::shared_ptr server_; +}; +} // namespace avahi +} // namespace linux +} // namespace nearby + +#endif diff --git a/internal/platform/implementation/linux/ble_gatt_client.cc b/internal/platform/implementation/linux/ble_gatt_client.cc new file mode 100644 index 00000000..936ec6c2 --- /dev/null +++ b/internal/platform/implementation/linux/ble_gatt_client.cc @@ -0,0 +1,428 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include + +#include + +#include "absl/strings/substitute.h" +#include "absl/synchronization/mutex.h" +#include "internal/platform/cancellation_flag_listener.h" +#include "internal/platform/implementation/ble_v2.h" +#include "internal/platform/implementation/linux/ble_gatt_client.h" +#include "internal/platform/implementation/linux/bluez_gatt_characteristic_client.h" +#include "internal/platform/implementation/linux/bluez_gatt_service_client.h" +#include "internal/platform/implementation/linux/dbus.h" +#include "internal/platform/implementation/linux/generated/dbus/bluez/gatt_characteristic_client.h" +#include "internal/platform/implementation/linux/generated/dbus/bluez/gatt_service_client.h" +#include "internal/platform/implementation/linux/utils.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { +bool GattClient::DiscoverServiceAndCharacteristics( + const Uuid &service_uuid, const std::vector &characteristic_uuids) { + return gatt_discovery_->DiscoverServiceAndCharacteristics( + peripheral_object_path_, service_uuid, characteristic_uuids, + discovery_cancel_); +} + +absl::optional GattClient::GetCharacteristic( + const Uuid &service_uuid, const Uuid &characteristic_uuid) { + auto chr_proxy = gatt_discovery_->GetCharacteristic( + peripheral_object_path_, service_uuid, characteristic_uuid); + if (chr_proxy == nullptr) return std::nullopt; + + api::ble_v2::GattCharacteristic chr; + chr.service_uuid = service_uuid; + chr.uuid = characteristic_uuid; + chr.property = api::ble_v2::GattCharacteristic::Property::kNone; + chr.permission = api::ble_v2::GattCharacteristic::Permission::kNone; + + std::vector flags; + try { + flags = chr_proxy->Flags(); + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_GET_ERROR(chr_proxy, "Flags", e); + return std::nullopt; + } + + for (const auto &flag : flags) { + if (flag == "read") { + chr.property |= api::ble_v2::GattCharacteristic::Property::kRead; + chr.permission |= api::ble_v2::GattCharacteristic::Permission::kRead; + } else if (flag == "write") { + chr.property |= api::ble_v2::GattCharacteristic::Property::kWrite; + chr.permission |= api::ble_v2::GattCharacteristic::Permission::kWrite; + } else if (flag == "notify") { + chr.property |= api::ble_v2::GattCharacteristic::Property::kNotify; + } else if (flag == "indicate") { + chr.property |= api::ble_v2::GattCharacteristic::Property::kIndicate; + } + } + + absl::MutexLock lock(&characteristics_mutex_); + characteristics_.emplace(chr, std::move(chr_proxy)); + + return chr; +} + +absl::optional GattClient::ReadCharacteristic( + const api::ble_v2::GattCharacteristic &characteristic) { + absl::ReaderMutexLock lock(&characteristics_mutex_); + if (characteristics_.count(characteristic) == 0) { + NEARBY_LOGS(ERROR) << __func__ << ": Unknown characteristic '" + << absl::Substitute("$0", characteristic) << "'"; + return std::nullopt; + } + + return std::visit( + [](auto &&chr) { + try { + auto value_bytes = chr->ReadValue({}); + return std::optional{ + std::string(value_bytes.begin(), value_bytes.end())}; + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(chr, "ReadValue", e); + return std::optional(); + } + }, + characteristics_[characteristic]); +} + +bool GattClient::WriteCharacteristic( + const api::ble_v2::GattCharacteristic &characteristic, + absl::string_view value, WriteType type) { + absl::ReaderMutexLock lock(&characteristics_mutex_); + if (characteristics_.count(characteristic) == 0) { + NEARBY_LOGS(ERROR) << __func__ << ": Unknown characteristic '" + << absl::Substitute("$0", characteristic) << "'"; + return false; + } + + return std::visit( + [value, type](auto &&chr) { + std::vector value_bytes(value.begin(), value.end()); + try { + chr->WriteValue( + value_bytes, + {{"type", + type == api::ble_v2::GattClient::WriteType::kWithResponse + ? "request" + : "command"}}); + return true; + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(chr, "WriteValue", e); + return false; + } + }, + characteristics_[characteristic]); +} + +bool GattClient::SetCharacteristicSubscription( + const api::ble_v2::GattCharacteristic &characteristic, bool enable, + absl::AnyInvocable + on_characteristic_changed_cb) { + absl::MutexLock lock(&characteristics_mutex_); + if (characteristics_.count(characteristic) == 0) { + NEARBY_LOGS(ERROR) << __func__ << ": Unknown characteristic '" + << absl::Substitute("$0", characteristic) << "'"; + return false; + } + + if (enable) { + auto subbed_chr = gatt_discovery_->GetSubscribedCharacteristic( + peripheral_object_path_, characteristic.service_uuid, + characteristic.uuid, std::move(on_characteristic_changed_cb)); + if (subbed_chr == nullptr) return false; + try { + subbed_chr->StartNotify(); + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(subbed_chr, "StartNotify", e); + return false; + } + characteristics_[characteristic] = std::move(subbed_chr); + } else if (std::holds_alternative< + std::unique_ptr>( + characteristics_[characteristic])) { + auto chr = gatt_discovery_->GetCharacteristic(peripheral_object_path_, + characteristic.service_uuid, + characteristic.uuid); + if (chr == nullptr) return false; + try { + chr->StopNotify(); + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(chr, "StopNotify", e); + return false; + } + + characteristics_[characteristic] = std::move(chr); + } + return true; +} + +void GattClient::Disconnect() { + absl::MutexLock lock(&disconnected_callback_mutex_); + if (!discovery_cancel_.Cancelled()) { + discovery_cancel_.Cancel(); + if (*disconnected_callback_it_ != nullptr) (*disconnected_callback_it_)(); + gatt_discovery_->RemovePeripheralConnection(peripheral_object_path_, + disconnected_callback_it_); + } +} + +void BluezGattDiscovery::Shutdown() { + auto no_discovery = [&]() { + mutex_.AssertReaderHeld(); + return pending_discovery_ == 0; + }; + + mutex_.Lock(); + shutdown_ = true; + mutex_.Await(absl::Condition(&no_discovery)); + mutex_.Unlock(); +} + +bool BluezGattDiscovery::InitializeKnownServices() { + std::map>> + objects; + try { + objects = GetManagedObjects(); + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(this, "GetManagedObjects", e); + return false; + } + + absl::flat_hash_map cached_services; + absl::MutexLock lock(&mutex_); + auto chr_it = std::find_if( + objects.cbegin(), objects.cend(), + [](std::pair>> + object) { + return object.second.count( + org::bluez::GattCharacteristic1_proxy::INTERFACE_NAME) == 1; + }); + + for (; chr_it != objects.cend(); chr_it++) { + const auto &[path, ifaces] = *chr_it; + const auto &properties = + ifaces.at(org::bluez::GattCharacteristic1_proxy::INTERFACE_NAME); + auto maybe_props = characteristicProperties(path, properties); + if (!maybe_props.has_value()) continue; + auto [chr_uuid, service_uuid, device_path] = *maybe_props; + + discovered_characteristics_.emplace( + std::make_tuple(chr_uuid, service_uuid, device_path), path); + characteristics_properties_.emplace( + path, std::make_tuple(chr_uuid, service_uuid, device_path)); + } + + return true; +} + +BluezGattDiscovery::CallbackIter BluezGattDiscovery::AddPeripheralConnection( + const sdbus::ObjectPath &device_object_path, + absl::AnyInvocable disconnected_callback_) { + absl::MutexLock lock(&peripheral_disconnected_callbacks_mutex_); + if (peripheral_disconnected_callbacks_.count(device_object_path) == 0) + peripheral_disconnected_callbacks_.emplace( + device_object_path, std::list>{}); + auto &list = peripheral_disconnected_callbacks_[device_object_path]; + list.push_back(std::move(disconnected_callback_)); + return list.begin(); +} + +void BluezGattDiscovery::RemovePeripheralConnection( + const sdbus::ObjectPath &device_object_path, + BluezGattDiscovery::CallbackIter cb) { + absl::MutexLock lock(&peripheral_disconnected_callbacks_mutex_); + auto it = peripheral_disconnected_callbacks_.find(device_object_path); + if (it != peripheral_disconnected_callbacks_.end()) { + it->second.erase(cb); + if (it->second.empty()) + peripheral_disconnected_callbacks_.erase(device_object_path); + } +} + +bool BluezGattDiscovery::DiscoverServiceAndCharacteristics( + const sdbus::ObjectPath &device_object_path, const Uuid &service_uuid, + const std::vector &characteristic_uuids, CancellationFlag &cancel) { + CancellationFlagListener cancel_listen(&cancel, [&]() { + mutex_.Lock(); + mutex_.Unlock(); + }); + + auto discovered = [this, device_object_path, service_uuid, + characteristic_uuids, &cancel]() { + mutex_.AssertReaderHeld(); + return cancel.Cancelled() || + std::all_of( + characteristic_uuids.cbegin(), characteristic_uuids.cend(), + [this, service_uuid, device_object_path](auto &chr_uuid) { + mutex_.AssertReaderHeld(); + return discovered_characteristics_.count( + {service_uuid, chr_uuid, device_object_path}) == 1; + }); + }; + + absl::ReaderMutexLock lock(&mutex_, absl::Condition(&discovered)); + return !cancel.Cancelled(); +} + +std::unique_ptr +BluezGattDiscovery::GetCharacteristic( + const sdbus::ObjectPath &device_object_path, const Uuid &service_uuid, + const Uuid &characteristic_uuid) { + auto key = + std::make_tuple(service_uuid, characteristic_uuid, device_object_path); + + absl::ReaderMutexLock lock(&mutex_); + auto path_it = discovered_characteristics_.find(key); + if (path_it == discovered_characteristics_.end()) { + NEARBY_LOGS(ERROR) << __func__ << ": No characteristic known for device " + << device_object_path << " with service " + << std::string{service_uuid} << " and UUID " + << std::string{characteristic_uuid}; + return nullptr; + } + + return std::make_unique(system_bus_, + path_it->second); +} + +std::unique_ptr +BluezGattDiscovery::GetSubscribedCharacteristic( + const sdbus::ObjectPath &device_object_path, const Uuid &service_uuid, + const Uuid &characteristic_uuid, + absl::AnyInvocable + on_characteristic_changed_cb) { + auto key = + std::make_tuple(service_uuid, characteristic_uuid, device_object_path); + + absl::ReaderMutexLock lock(&mutex_); + auto path_it = discovered_characteristics_.find(key); + if (path_it == discovered_characteristics_.end()) { + NEARBY_LOGS(ERROR) << __func__ << ": No characteristic known for device " + << device_object_path << " with service " + << std::string{service_uuid} << " and UUID " + << std::string{characteristic_uuid}; + return nullptr; + } + + return std::make_unique( + system_bus_, device_object_path, std::move(on_characteristic_changed_cb)); +} + +std::optional> +BluezGattDiscovery::characteristicProperties( + const sdbus::ObjectPath &path, + const std::map &properties) { + mutex_.AssertHeld(); + + const std::string &chr_uuid_str = properties.at("UUID"); + auto chr_uuid = UuidFromString(chr_uuid_str); + if (!chr_uuid.has_value()) { + NEARBY_LOGS(ERROR) << ": Couldn't parse UUID '" << chr_uuid_str + << "' in characteristic " << path; + return std::nullopt; + } + + const sdbus::ObjectPath &service_path = properties.at("Service"); + if (cached_services_.count(service_path) == 0) { + cached_services_.emplace( + path, std::make_unique(system_bus_, path)); + } + + auto &service = cached_services_.at(service_path); + nearby::Uuid service_uuid; + try { + const std::string &service_uuid_str = service->UUID(); + auto service_uuid_maybe = UuidFromString(service_uuid_str); + if (!service_uuid_maybe.has_value()) { + NEARBY_LOGS(ERROR) << ": Couldn't parse UUID '" << service_uuid_str + << "' in service " << service_path; + return std::nullopt; + } + service_uuid = *service_uuid_maybe; + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_GET_ERROR(service, "UUID", e); + return std::nullopt; + } + + sdbus::ObjectPath device_path; + try { + device_path = service->Device(); + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_GET_ERROR(service, "Device", e); + return std::nullopt; + } + + return std::make_tuple(*chr_uuid, service_uuid, device_path); +} + +void BluezGattDiscovery::onInterfacesAdded( + const sdbus::ObjectPath &objectPath, + const std::map> + &interfacesAndProperties) { + if (interfacesAndProperties.count( + org::bluez::GattCharacteristic1_proxy::INTERFACE_NAME) == 0) + return; + + const auto &properties = interfacesAndProperties.at( + org::bluez::GattCharacteristic1_proxy::INTERFACE_NAME); + + absl::MutexLock lock(&mutex_); + auto maybe_props = characteristicProperties(objectPath, properties); + if (!maybe_props.has_value()) return; + auto [chr_uuid, service_uuid, device_path] = *maybe_props; + + discovered_characteristics_.emplace( + std::make_tuple(chr_uuid, service_uuid, device_path), objectPath); + characteristics_properties_.emplace( + objectPath, std::make_tuple(chr_uuid, service_uuid, device_path)); +} + +void BluezGattDiscovery::onInterfacesRemoved( + const sdbus::ObjectPath &objectPath, + const std::vector &interfaces) { + auto begin = interfaces.cbegin(); + auto end = interfaces.cend(); + + auto service_it = + std::find(begin, end, org::bluez::GattService1_proxy::INTERFACE_NAME); + if (service_it != end) { + absl::MutexLock lock(&mutex_); + cached_services_.erase(objectPath); + return; + } + + auto chr_it = std::find( + begin, end, org::bluez::GattCharacteristic1_proxy::INTERFACE_NAME); + if (chr_it != end) { + absl::MutexLock lock(&mutex_); + { + auto &props = characteristics_properties_.at(objectPath); + discovered_characteristics_.erase(props); + } + characteristics_properties_.erase(objectPath); + } +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/ble_gatt_client.h b/internal/platform/implementation/linux/ble_gatt_client.h new file mode 100644 index 00000000..f86f9970 --- /dev/null +++ b/internal/platform/implementation/linux/ble_gatt_client.h @@ -0,0 +1,210 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_API_BLE_GATT_CLIENT_H_ +#define PLATFORM_IMPL_LINUX_API_BLE_GATT_CLIENT_H_ + +#include + +#include +#include + +#include "absl/container/flat_hash_map.h" +#include "absl/synchronization/mutex.h" +#include "internal/platform/cancellation_flag.h" +#include "internal/platform/implementation/ble_v2.h" +#include "internal/platform/implementation/linux/bluez.h" +#include "internal/platform/implementation/linux/bluez_gatt_characteristic_client.h" +#include "internal/platform/implementation/linux/bluez_gatt_service_client.h" + +namespace nearby { +namespace linux { + +class BluezGattDiscovery final : public bluez::BluezObjectManager { + public: + explicit BluezGattDiscovery(std::shared_ptr system_bus) + : bluez::BluezObjectManager(*system_bus), + system_bus_(system_bus), + shutdown_(false), + pending_discovery_(0) {} + ~BluezGattDiscovery() override { Shutdown(); } + + bool InitializeKnownServices() ABSL_LOCKS_EXCLUDED(mutex_); + + using CallbackIter = typename std::list>::iterator; + CallbackIter AddPeripheralConnection( + const sdbus::ObjectPath &device_object_path, + absl::AnyInvocable disconnected_callback_) + ABSL_LOCKS_EXCLUDED(peripheral_disconnected_callbacks_mutex_); + void RemovePeripheralConnection(const sdbus::ObjectPath &device_object_path, + BluezGattDiscovery::CallbackIter cb) + ABSL_LOCKS_EXCLUDED(peripheral_disconnected_callbacks_mutex_); + + bool DiscoverServiceAndCharacteristics( + const sdbus::ObjectPath &device_object_path, const Uuid &service_uuid, + const std::vector &characteristic_uuids, CancellationFlag &cancel) + ABSL_LOCKS_EXCLUDED(mutex_); + std::unique_ptr GetCharacteristic( + const sdbus::ObjectPath &device_object_path, const Uuid &service_uuid, + const Uuid &characteristic_uuid) ABSL_LOCKS_EXCLUDED(mutex_); + std::unique_ptr GetSubscribedCharacteristic( + const sdbus::ObjectPath &device_object_path, const Uuid &service_uuid, + const Uuid &characteristic_uuid, + absl::AnyInvocable + on_characteristic_changed_cb) ABSL_LOCKS_EXCLUDED(mutex_); + + protected: + void onInterfacesAdded( + const sdbus::ObjectPath &objectPath, + const std::map> + &interfacesAndProperties) override ABSL_LOCKS_EXCLUDED(mutex_); + void onInterfacesRemoved(const sdbus::ObjectPath &objectPath, + const std::vector &interfaces) override + ABSL_LOCKS_EXCLUDED(mutex_); + + private: + std::optional> + characteristicProperties( + const sdbus::ObjectPath &path, + const std::map &properties) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + void Shutdown() ABSL_LOCKS_EXCLUDED(mutex_); + + std::shared_ptr system_bus_; + + absl::Mutex peripheral_disconnected_callbacks_mutex_; + absl::flat_hash_map>> + peripheral_disconnected_callbacks_ + ABSL_GUARDED_BY(peripheral_disconnected_callbacks_mutex_); + + absl::Mutex mutex_; + absl::flat_hash_map> + cached_services_ ABSL_GUARDED_BY(mutex_); + // Tuple order: service uuid, characteristic uuid, device object path + absl::flat_hash_map, + sdbus::ObjectPath> + discovered_characteristics_ ABSL_GUARDED_BY(mutex_); + absl::flat_hash_map> + characteristics_properties_ ABSL_GUARDED_BY(mutex_); + bool shutdown_ ABSL_GUARDED_BY(mutex_); + std::size_t pending_discovery_ ABSL_GUARDED_BY(mutex_); +}; + +// https://developer.android.com/reference/android/bluetooth/BluetoothGatt +// +// Representation of a client GATT connection to a remote GATT server. +class GattClient : public api::ble_v2::GattClient { + public: + GattClient(const GattClient &) = delete; + GattClient(GattClient &&) = delete; + GattClient &operator=(const GattClient &) = delete; + GattClient &operator=(GattClient &&) = delete; + + explicit GattClient(std::shared_ptr system_bus, + const sdbus::ObjectPath &peripheral_object_path, + std::shared_ptr gatt_discovery, + absl::AnyInvocable disconnected_callback) + : system_bus_(std::move(system_bus)), + peripheral_object_path_(peripheral_object_path), + gatt_discovery_(std::move(gatt_discovery)), + discovery_cancel_(false) { + disconnected_callback_it_ = gatt_discovery->AddPeripheralConnection( + peripheral_object_path_, std::move(disconnected_callback)); + } + ~GattClient() override { + absl::MutexLock lock(&disconnected_callback_mutex_); + if (!discovery_cancel_.Cancelled()) { + discovery_cancel_.Cancel(); + gatt_discovery_->RemovePeripheralConnection(peripheral_object_path_, + disconnected_callback_it_); + } + } + // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#discoverServices() + // + // Discovers available service and characteristics on this connection. + // Returns whether or not discovery finished successfully. + // + // This function should block until discovery has finished. + bool DiscoverServiceAndCharacteristics( + const Uuid &service_uuid, + const std::vector &characteristic_uuids) override; + + // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#getService(java.util.UUID) + // https://developer.android.com/reference/android/bluetooth/BluetoothGattService.html#getCharacteristic(java.util.UUID) + // + // Retrieves a GATT characteristic. On error, does not return a value. + // + // DiscoverServiceAndCharacteristics() should be called before this method to + // fetch all available services and characteristics first. + // + // It is okay for duplicate services to exist, as long as the specified + // characteristic UUID is unique among all services of the same UUID. + // NOLINTNEXTLINE(google3-legacy-absl-backports) + absl::optional GetCharacteristic( + const Uuid &service_uuid, const Uuid &characteristic_uuid) override + ABSL_LOCKS_EXCLUDED(characteristics_mutex_); + + // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#readCharacteristic(android.bluetooth.BluetoothGattCharacteristic) + // https://developer.android.com/reference/android/bluetooth/BluetoothGattCharacteristic.html#getValue() + // NOLINTNEXTLINE(google3-legacy-absl-backports) + absl::optional ReadCharacteristic( + const api::ble_v2::GattCharacteristic &characteristic) override + ABSL_LOCKS_EXCLUDED(characteristics_mutex_); + + // https://developer.android.com/reference/android/bluetooth/BluetoothGattCharacteristic.html#setValue(byte[]) + // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#writeCharacteristic(android.bluetooth.BluetoothGattCharacteristic) + // + // Sends a remote characteristic write request to the server and returns + // whether or not it was successful. + bool WriteCharacteristic( + const api::ble_v2::GattCharacteristic &characteristic, + absl::string_view value, WriteType type) override + ABSL_LOCKS_EXCLUDED(characteristics_mutex_); + + // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#setCharacteristicNotification(android.bluetooth.BluetoothGattCharacteristic,%20boolean) + // + // Enable or disable notifications/indications for a given characteristic. + bool SetCharacteristicSubscription( + const api::ble_v2::GattCharacteristic &characteristic, bool enable, + absl::AnyInvocable + on_characteristic_changed_cb) override + ABSL_LOCKS_EXCLUDED(characteristics_mutex_); + + // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#disconnect() + void Disconnect() override; + + private: + std::shared_ptr system_bus_; + sdbus::ObjectPath peripheral_object_path_; + std::shared_ptr gatt_discovery_; + + absl::Mutex disconnected_callback_mutex_; + BluezGattDiscovery::CallbackIter disconnected_callback_it_ + ABSL_GUARDED_BY(disconnected_callback_mutex_); + CancellationFlag discovery_cancel_; + + using CharacteristicProxy = + std::variant, + std::unique_ptr>; + absl::Mutex characteristics_mutex_; + absl::flat_hash_map + characteristics_ ABSL_GUARDED_BY(characteristics_mutex_); +}; + +} // namespace linux +} // namespace nearby + +#endif diff --git a/internal/platform/implementation/linux/ble_gatt_server.cc b/internal/platform/implementation/linux/ble_gatt_server.cc new file mode 100644 index 00000000..7a25a47d --- /dev/null +++ b/internal/platform/implementation/linux/ble_gatt_server.cc @@ -0,0 +1,147 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "internal/platform/implementation/linux/ble_gatt_server.h" +#include "absl/strings/substitute.h" +#include "internal/platform/implementation/ble_v2.h" +#include "internal/platform/implementation/linux/bluez_gatt_characteristic_server.h" +#include "internal/platform/implementation/linux/bluez_gatt_manager.h" +#include "internal/platform/implementation/linux/bluez_gatt_service_server.h" +#include "internal/platform/implementation/linux/dbus.h" +#include "internal/platform/implementation/linux/generated/dbus/bluez/gatt_service_server.h" +#include "internal/platform/uuid.h" + +namespace nearby { +namespace linux { +absl::optional +GattServer::CreateCharacteristic( + const Uuid& service_uuid, const Uuid& characteristic_uuid, + api::ble_v2::GattCharacteristic::Permission permission, + api::ble_v2::GattCharacteristic::Property property) { + absl::MutexLock lock(&services_mutex_); + if (services_.count(service_uuid) == 1) { + if (services_[service_uuid]->AddCharacteristic( + service_uuid, characteristic_uuid, permission, property)) { + api::ble_v2::GattCharacteristic characteristic{ + characteristic_uuid, service_uuid, permission, property}; + return characteristic; + } + return std::nullopt; + } + + auto count = services_.size(); + auto service = std::make_unique( + system_bus_, count, service_uuid, server_cb_, devices_); + try { + service->emitInterfacesAddedSignal( + {org::bluez::GattService1_adaptor::INTERFACE_NAME}); + } catch (const sdbus::Error& e) { + NEARBY_LOGS(ERROR) + << __func__ + << ": error emitting InterfacesAdded signal for object path " + << service->getObjectPath() << " with name '" << e.getName() + << "' and message '" << e.getMessage() << "'"; + return std::nullopt; + } + + if (service->AddCharacteristic(service_uuid, characteristic_uuid, permission, + property)) { + bluez::GattManager manager(system_bus_, adapter_.GetObjectPath()); + try { + NEARBY_LOGS(VERBOSE) << __func__ << ": registering service " + << service->getObjectPath(); + manager.RegisterApplication("/", {}); + } catch (const sdbus::Error& e) { + DBUS_LOG_METHOD_CALL_ERROR(&manager, "RegisterApplication", e); + return std::nullopt; + } + + services_.insert({service_uuid, std::move(service)}); + + api::ble_v2::GattCharacteristic characteristic{ + characteristic_uuid, service_uuid, permission, property}; + return characteristic; + } + + return std::nullopt; +} + +bool GattServer::UpdateCharacteristic( + const api::ble_v2::GattCharacteristic& characteristic, + const nearby::ByteArray& value) { + std::shared_ptr chr = nullptr; + { + absl::ReaderMutexLock lock(&services_mutex_); + if (services_.count(characteristic.service_uuid) == 0) { + NEARBY_LOGS(ERROR) << __func__ << ": GATT Service " + << std::string{characteristic.service_uuid} + << " doesn't exist"; + return false; + } + chr = services_[characteristic.service_uuid]->GetCharacteristic( + characteristic.uuid); + } + if (chr == nullptr) { + NEARBY_LOGS(ERROR) << __func__ << ": Characteristic " + << std::string{characteristic.uuid} + << " does not exist under service " + << std::string{characteristic.service_uuid}; + return false; + } + assert(chr != nullptr); + chr->Update(value); + return true; +} + +absl::Status GattServer::NotifyCharacteristicChanged( + const api::ble_v2::GattCharacteristic& characteristic, bool confirm, + const ByteArray& new_value) { + std::shared_ptr chr = nullptr; + { + absl::ReaderMutexLock lock(&services_mutex_); + if (services_.count(characteristic.service_uuid) == 0) { + return absl::NotFoundError( + absl::Substitute("Service $0 doesn't exist", + std::string{characteristic.service_uuid})); + } + chr = services_[characteristic.service_uuid]->GetCharacteristic( + characteristic.uuid); + } + if (chr == nullptr) { + return absl::NotFoundError( + absl::Substitute("characteristic $0 doesn't exist under service $1", + std::string{characteristic.uuid}, + std::string{characteristic.service_uuid})); + } + + return chr->NotifyChanged(confirm, new_value); +} + +void GattServer::Stop() { + bluez::GattManager manager(system_bus_, adapter_.GetObjectPath()); + absl::MutexLock lock(&services_mutex_); + for (auto& [uuid, service] : services_) { + NEARBY_LOGS(VERBOSE) << __func__ << ": Unregistering service " + << service->getObjectPath(); + try { + manager.UnregisterApplication("/"); + } catch (const sdbus::Error& e) { + DBUS_LOG_METHOD_CALL_ERROR(&manager, "UnregisterApplication", e); + } + } + // services_.clear(); +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/ble_gatt_server.h b/internal/platform/implementation/linux/ble_gatt_server.h new file mode 100644 index 00000000..b7f38706 --- /dev/null +++ b/internal/platform/implementation/linux/ble_gatt_server.h @@ -0,0 +1,96 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_API_BLE_GATT_SERVER_H_ +#define PLATFORM_IMPL_LINUX_API_BLE_GATT_SERVER_H_ + +#include + +#include + +#include "absl/container/flat_hash_map.h" +#include "absl/synchronization/mutex.h" +#include "absl/types/optional.h" +#include "internal/platform/bluetooth_utils.h" +#include "internal/platform/byte_array.h" +#include "internal/platform/implementation/ble_v2.h" +#include "internal/platform/implementation/linux/bluetooth_adapter.h" +#include "internal/platform/implementation/linux/bluetooth_devices.h" +#include "internal/platform/implementation/linux/bluez_gatt_service_server.h" +#include "internal/platform/uuid.h" + +namespace nearby { +namespace linux { +class LocalBlePeripheral : public api::ble_v2::BlePeripheral { + public: + explicit LocalBlePeripheral(BluetoothAdapter& adapter) : adapter_(adapter) { + unique_id_ = BluetoothUtils::ToNumber(adapter_.GetMacAddress()); + } + + std::string GetAddress() const override { return adapter_.GetMacAddress(); } + UniqueId GetUniqueId() const override { return unique_id_; } + + private: + BluetoothAdapter adapter_; + UniqueId unique_id_; +}; + +class GattServer : public api::ble_v2::GattServer { + public: + GattServer(const GattServer&) = delete; + GattServer(GattServer&&) = delete; + GattServer& operator=(const GattServer&) = delete; + GattServer& operator=(GattServer&&) = delete; + + explicit GattServer(sdbus::IConnection& system_bus, BluetoothAdapter& adapter, + std::shared_ptr devices, + api::ble_v2::ServerGattConnectionCallback server_cb) + : system_bus_(system_bus), + devices_(std::move(devices)), + adapter_(adapter), + local_peripheral_(adapter_), + server_cb_(std::make_shared( + std::move(server_cb))) {} + ~GattServer() override = default; + + api::ble_v2::BlePeripheral& GetBlePeripheral() override { + return local_peripheral_; + } + absl::optional CreateCharacteristic( + const Uuid& service_uuid, const Uuid& characteristic_uuid, + api::ble_v2::GattCharacteristic::Permission permission, + api::ble_v2::GattCharacteristic::Property property) override; + bool UpdateCharacteristic( + const api::ble_v2::GattCharacteristic& characteristic, + const nearby::ByteArray& value) override; + absl::Status NotifyCharacteristicChanged( + const api::ble_v2::GattCharacteristic& characteristic, bool confirm, + const ByteArray& new_value) override; + void Stop() override; + + private: + sdbus::IConnection& system_bus_; + std::shared_ptr devices_; + BluetoothAdapter adapter_; + LocalBlePeripheral local_peripheral_; + + std::shared_ptr server_cb_; + absl::Mutex services_mutex_; + absl::flat_hash_map> services_ + ABSL_GUARDED_BY(services_mutex_); +}; + +} // namespace linux +} // namespace nearby +#endif diff --git a/internal/platform/implementation/linux/ble_medium.h b/internal/platform/implementation/linux/ble_medium.h new file mode 100644 index 00000000..84c474a5 --- /dev/null +++ b/internal/platform/implementation/linux/ble_medium.h @@ -0,0 +1,73 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_API_BLE_MEDIUM_H_ +#define PLATFORM_IMPL_LINUX_API_BLE_MEDIUM_H_ + +#include "internal/platform/implementation/ble.h" + +namespace nearby { +namespace linux { +// Container of operations that can be performed over the BLE medium. +class BleMedium : public api::BleMedium { + public: + BleMedium() {} + ~BleMedium() = default; + + bool StartAdvertising( + const std::string &service_id, const ByteArray &advertisement_bytes, + const std::string &fast_advertisement_service_uuid) override { + return false; + } + bool StopAdvertising(const std::string &service_id) override { return false; } + + // Returns true once the BLE scan has been initiated. + bool StartScanning(const std::string &service_id, + const std::string &fast_advertisement_service_uuid, + DiscoveredPeripheralCallback callback) override { + return false; + } + + // Returns true once BLE scanning for service_id is well and truly stopped; + // after this returns, there must be no more invocations of the + // DiscoveredPeripheralCallback passed in to StartScanning() for service_id. + bool StopScanning(const std::string &service_id) override { return false; } + + // Callback that is invoked when a new connection is accepted. + using AcceptedConnectionCallback = absl::AnyInvocable; + + // Returns true once BLE socket connection requests to service_id can be + // accepted. + bool StartAcceptingConnections(const std::string &service_id, + AcceptedConnectionCallback callback) override { + return false; + } + bool StopAcceptingConnections(const std::string &service_id) override { + return false; + } + + // Connects to a BLE peripheral. + // On success, returns a new BleSocket. + // On error, returns nullptr. + std::unique_ptr Connect( + api::BlePeripheral &peripheral, const std::string &service_id, + CancellationFlag *cancellation_flag) override { + return nullptr; + } +}; +} // namespace linux +} // namespace nearby + +#endif diff --git a/internal/platform/implementation/linux/ble_v2_medium.cc b/internal/platform/implementation/linux/ble_v2_medium.cc new file mode 100644 index 00000000..aafe2d68 --- /dev/null +++ b/internal/platform/implementation/linux/ble_v2_medium.cc @@ -0,0 +1,480 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include +#include + +#include "absl/synchronization/mutex.h" +#include "internal/platform/implementation/ble_v2.h" +#include "internal/platform/implementation/linux/ble_gatt_client.h" +#include "internal/platform/implementation/linux/ble_gatt_server.h" +#include "internal/platform/implementation/linux/ble_v2_medium.h" +#include "internal/platform/implementation/linux/bluetooth_classic_device.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/implementation/linux/generated/dbus/bluez/advertisement_monitor_server.h" +#include "internal/platform/implementation/linux/generated/dbus/bluez/le_advertisement_manager_client.h" + +namespace nearby { +namespace linux { +BleV2Medium::BleV2Medium(BluetoothAdapter &adapter) + : system_bus_(adapter.GetConnection()), + adapter_(adapter), + devices_(std::make_unique( + system_bus_, adapter_.GetObjectPath(), observers_)), + gatt_discovery_(std::make_shared(system_bus_)), + root_object_manager_(std::make_unique(*system_bus_)), + adv_monitor_manager_( + bluez::AdvertisementMonitorManager:: + DiscoverAdvertisementMonitorManager(*system_bus_, adapter_)), + adv_manager_(std::make_unique(*system_bus_, + adapter)), + cur_adv_(nullptr) { + if (adv_monitor_manager_) { + NEARBY_LOGS(VERBOSE) + << __func__ + << ": Registering path / with AdvertisementMonitorManager at " + << adv_monitor_manager_->getObjectPath(); + try { + adv_monitor_manager_->RegisterMonitor("/"); + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(adv_monitor_manager_, "RegisterMonitor", e); + } + } + if (gatt_discovery_->InitializeKnownServices()) { + NEARBY_LOGS(ERROR) << __func__ + << ": Could not initialize known GATT services"; + } +} + +bool BleV2Medium::StartAdvertising( + const api::ble_v2::BleAdvertisementData &advertising_data, + api::ble_v2::AdvertiseParameters advertise_set_parameters) { + if (!adapter_.IsEnabled()) { + NEARBY_LOGS(WARNING) << "BLE cannot start advertising because the " + "bluetooth adapter is not enabled."; + return false; + } + + if (advertising_data.service_data.empty()) { + NEARBY_LOGS(WARNING) + << "BLE cannot start to advertise due to invalid service data."; + return false; + } + + absl::MutexLock lock(&cur_adv_mutex_); + if (cur_adv_ != nullptr) { + NEARBY_LOGS(ERROR) << __func__ + << "Advertising is already enabled for this medium."; + return false; + } + + cur_adv_ = bluez::LEAdvertisement::CreateLEAdvertisement( + *system_bus_, advertising_data, advertise_set_parameters); + + NEARBY_LOGS(INFO) << __func__ << ": Registering advertisement " + << cur_adv_->getObjectPath() << " on bluetooth adapter " + << adapter_.GetObjectPath(); + + try { + adv_manager_->RegisterAdvertisement(cur_adv_->getObjectPath(), {}); + } catch (const sdbus::Error &e) { + cur_adv_ = nullptr; + DBUS_LOG_METHOD_CALL_ERROR(adv_manager_, "RegisterAdvertisement", e); + return false; + } + + return true; +} + +bool BleV2Medium::StopAdvertising() { + absl::MutexLock lock(&cur_adv_mutex_); + if (cur_adv_ == nullptr) { + NEARBY_LOGS(ERROR) << __func__ << ": Advertising is not enabled."; + return false; + } + NEARBY_LOGS(VERBOSE) << __func__ << "Unregistering advertisement object " + << cur_adv_->getObjectPath(); + + try { + adv_manager_->UnregisterAdvertisement(cur_adv_->getObjectPath()); + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(adv_manager_, "UnregisterAdvertisement", e); + return false; + } + + cur_adv_ = nullptr; + return true; +} + +std::unique_ptr +BleV2Medium::StartAdvertising( + const api::ble_v2::BleAdvertisementData &advertising_data, + api::ble_v2::AdvertiseParameters advertise_set_parameters, + AdvertisingCallback callback) { + if (!adapter_.IsEnabled()) { + NEARBY_LOGS(WARNING) << ": BLE cannot start advertising because the " + "bluetooth adapter is not enabled."; + return nullptr; + } + + if (advertising_data.service_data.empty()) { + NEARBY_LOGS(WARNING) + << ": BLE cannot start to advertise due to invalid service data."; + return nullptr; + } + + std::shared_ptr proxy = + sdbus::createProxy(*system_bus_, "org.bluez", adapter_.GetObjectPath()); + proxy->finishRegistration(); + + std::shared_ptr shared_cb = + std::make_shared(std::move(callback)); + + absl::MutexLock lock(&advs_mutex_); + advs_.push_front(bluez::LEAdvertisement::CreateLEAdvertisement( + *system_bus_, advertising_data, advertise_set_parameters)); + auto adv_it = advs_.begin(); + + auto pending_call = + proxy->callMethodAsync("RegisterAdvertisement") + .onInterface(org::bluez::LEAdvertisingManager1_proxy::INTERFACE_NAME) + .withArguments((*adv_it)->getObjectPath(), + std::map{}) + .uponReplyInvoke( + [this, proxy, shared_cb, adv_it](const sdbus::Error *error) { + if (error != nullptr && error->isValid()) { + { + absl::MutexLock lock(&advs_mutex_); + advs_.erase(adv_it); + } + DBUS_LOG_METHOD_CALL_ERROR(adv_manager_, + "RegisterAdvertisement", *error); + auto name = error->getName(); + std::string msg = error->getMessage(); + absl::Status status; + + if (name == "org.bluez.Error.InvalidArguments" || + name == "org.bluez.Error.InvalidLength") { + status = absl::InvalidArgumentError(msg); + } else if (name == "org.bluez.Error.AlreadyExists") { + status = absl::AlreadyExistsError(msg); + } else if (name == "org.bluez.Error.NotPermitted") { + status = absl::ResourceExhaustedError(msg); + } else { + status = absl::UnknownError(msg); + } + shared_cb->start_advertising_result(std::move(status)); + } else { + shared_cb->start_advertising_result(absl::OkStatus()); + } + }); + + absl::AnyInvocable stop_adv = [&, adv_it]() { + NEARBY_LOGS(VERBOSE) << __func__ << ": Unregistering advertisement object " + << (*adv_it)->getObjectPath(); + absl::MutexLock lock(&advs_mutex_); + try { + adv_manager_->UnregisterAdvertisement((*adv_it)->getObjectPath()); + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(adv_manager_, "UnregisterAdvertisement", e); + return absl::UnknownError(e.getMessage()); + } + advs_.erase(adv_it); + return absl::OkStatus(); + }; + + return std::make_unique( + api::ble_v2::BleMedium::AdvertisingSession{std::move(stop_adv)}); +} + +std::unique_ptr BleV2Medium::StartGattServer( + api::ble_v2::ServerGattConnectionCallback callback) { + return std::make_unique(*system_bus_, adapter_, devices_, + std::move(callback)); +} + +std::unique_ptr BleV2Medium::ConnectToGattServer( + api::ble_v2::BlePeripheral &peripheral, + api::ble_v2::TxPowerLevel tx_power_level, + api::ble_v2::ClientGattConnectionCallback callback) { + auto path = bluez::device_object_path(adapter_.GetObjectPath(), + peripheral.GetAddress()); + + return std::make_unique(system_bus_, path, gatt_discovery_, + std::move(callback.disconnected_cb)); +} + +bool BleV2Medium::IsExtendedAdvertisementsAvailable() { + try { + auto supported_channels = adv_manager_->SupportedSecondaryChannels(); + return !supported_channels.empty(); + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_GET_ERROR(adv_manager_, "SupportedSecondaryChannels", e); + return false; + } +} + +bool BleV2Medium::StartLEDiscovery() { + std::map filter; + filter["Transport"] = "auto"; + auto &adapter = adapter_.GetBluezAdapterObject(); + + try { + adapter.SetDiscoveryFilter(filter); + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(&adapter, "SetDiscoveryFilter", e); + return false; + } + + try { + NEARBY_LOGS(INFO) << __func__ << ": Starting LE discovery on " + << adapter.getObjectPath(); + adapter.StartDiscovery(); + } catch (const sdbus::Error &e) { + if (e.getName() != "org.bluez.Error.InProgress") { + DBUS_LOG_METHOD_CALL_ERROR(&adapter, "StartDiscovery", e); + return false; + } + } + + return true; +} + +bool BleV2Medium::StartScanning(const Uuid &service_uuid, + api::ble_v2::TxPowerLevel tx_power_level, + ScanCallback callback) { + if (cur_monitored_service_uuid_.has_value()) { + NEARBY_LOGS(ERROR) << __func__ + << ": A sync scanning session is already active for " + << std::string{*cur_monitored_service_uuid_}; + return false; + } + + if (adv_monitor_manager_ == nullptr) { + NEARBY_LOGS(WARNING) << __func__ + << ": Advertising monitor not supported by BlueZ"; + // TODO: Implement manual monitoring. + return false; + } + + if (!MonitorManagerSupportsOr()) { + NEARBY_LOGS(WARNING) + << __func__ + << ": \"or_patterns\" not supported by AdvertisementMonitorManager"; + // TODO: Implement manual monitoring. + return false; + } + + absl::MutexLock lock(&active_adv_monitors_mutex_); + if (active_adv_monitors_.count(service_uuid) == 1) { + NEARBY_LOGS(ERROR) << __func__ << ": an advertising session for service " + << std::string{service_uuid} << " already exists"; + return false; + } + + auto monitor = std::make_unique( + *system_bus_, service_uuid, tx_power_level, "or_patterns", devices_, + std::move(callback)); + try { + monitor->emitInterfacesAddedSignal( + {org::bluez::AdvertisementMonitor1_adaptor::INTERFACE_NAME}); + } catch (const sdbus::Error &e) { + NEARBY_LOGS(ERROR) + << __func__ + << ": error emitting InterfacesAdded signal for object path " + << monitor->getObjectPath() << " with name '" << e.getName() + << "' and message '" << e.getMessage() << "'"; + return false; + } + + auto device_watcher = std::make_unique( + *system_bus_, adapter_.GetObjectPath(), devices_); + if (!StartLEDiscovery()) { + NEARBY_LOGS(ERROR) << __func__ + << ": Could not start LE discovery on adapter " + << adapter_.GetObjectPath(); + device_watcher = nullptr; + try { + monitor->emitInterfacesRemovedSignal( + {org::bluez::AdvertisementMonitor1_adaptor::INTERFACE_NAME}); + } catch (const sdbus::Error &e) { + NEARBY_LOGS(ERROR) + << __func__ + << ": error emitting InterfacesRemoved signal for object path " + << monitor->getObjectPath() << " with name '" << e.getName() + << "' and message '" << e.getMessage() << "'"; + } + return false; + } + + active_adv_monitors_[service_uuid] = + std::make_pair(std::move(monitor), std::move(device_watcher)); + cur_monitored_service_uuid_ = service_uuid; + return true; +} + +bool BleV2Medium::StopScanning() { + if (!cur_monitored_service_uuid_.has_value()) { + NEARBY_LOGS(ERROR) << __func__ + << ": No sync scanning session is currently active."; + return false; + } + + if (adv_monitor_manager_ == nullptr) { + // TODO: Implement manual monitoring. + return false; + } + + auto &adapter = adapter_.GetBluezAdapterObject(); + NEARBY_LOGS(VERBOSE) << __func__ << ": Stopping discovery for adapter " + << adapter.getObjectPath(); + try { + adapter.StopDiscovery(); + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(&adapter, "StopDiscovery", e); + } + + absl::MutexLock lock(&active_adv_monitors_mutex_); + auto monitor_it = active_adv_monitors_.find(*cur_monitored_service_uuid_); + assert(monitor_it != active_adv_monitors_.end()); + { + auto &[_uuid, session] = *monitor_it; + auto &[adv_monitor, _watcher] = session; + + NEARBY_LOGS(VERBOSE) << __func__ << ": Removing advertising monitor " + << adv_monitor->getObjectPath(); + adv_monitor->emitInterfacesRemovedSignal( + {org::bluez::AdvertisementMonitor1_adaptor::INTERFACE_NAME}); + } + active_adv_monitors_.erase(monitor_it); + cur_monitored_service_uuid_ = std::nullopt; + + return true; +} + +std::unique_ptr +BleV2Medium::StartScanning(const Uuid &service_uuid, + api::ble_v2::TxPowerLevel tx_power_level, + ScanningCallback callback) { + if (adv_monitor_manager_ == nullptr) { + // TODO: Implement manual monitoring. + return nullptr; + } + + absl::MutexLock lock(&active_adv_monitors_mutex_); + if (active_adv_monitors_.count(service_uuid) == 1) { + NEARBY_LOGS(ERROR) << __func__ << ": Service " << std::string{service_uuid} + << " is already being advertised"; + return nullptr; + } + + auto monitor = std::make_unique( + *system_bus_, service_uuid, tx_power_level, "or_patterns", devices_, + std::move(callback)); + try { + monitor->emitInterfacesAddedSignal( + {org::bluez::AdvertisementMonitor1_adaptor::INTERFACE_NAME}); + } catch (const sdbus::Error &e) { + NEARBY_LOGS(ERROR) + << __func__ + << ": error emitting InterfacesAdded signal for object path " + << monitor->getObjectPath() << " with name '" << e.getName() + << "' and message '" << e.getMessage() << "'"; + return nullptr; + } + + auto device_watcher = std::make_unique( + *system_bus_, adapter_.GetObjectPath(), devices_); + if (!StartLEDiscovery()) { + NEARBY_LOGS(ERROR) << __func__ + << ": Could not start LE discovery on adapter " + << adapter_.GetObjectPath(); + try { + monitor->emitInterfacesRemovedSignal( + {org::bluez::AdvertisementMonitor1_adaptor::INTERFACE_NAME}); + } catch (const sdbus::Error &e) { + NEARBY_LOGS(ERROR) + << __func__ + << ": error emitting InterfacesRemoved signal for object path " + << monitor->getObjectPath() << " with name '" << e.getName() + << "' and message '" << e.getMessage() << "'"; + } + return nullptr; + } + + active_adv_monitors_[service_uuid] = + std::make_pair(std::move(monitor), std::move(device_watcher)); + + return std::make_unique( + ScanningSession{.stop_scanning = [this, service_uuid]() { + absl::MutexLock lock(&active_adv_monitors_mutex_); + if (active_adv_monitors_.count(service_uuid) == 0) { + NEARBY_LOGS(ERROR) + << __func__ << ": Advertising monitor for service " + << std::string{service_uuid} << " does not exist anymore"; + return absl::NotFoundError( + "Advertising monitor for this service does not exist"); + } + + auto &[monitor, watcher] = active_adv_monitors_[service_uuid]; + try { + monitor->emitInterfacesRemovedSignal( + {org::bluez::AdvertisementMonitor1_adaptor::INTERFACE_NAME}); + } catch (const sdbus::Error &e) { + NEARBY_LOGS(ERROR) + << __func__ + << ": error emitting InterfacesRemoved signal for object path " + << monitor->getObjectPath() << " with name '" << e.getName() + << "' and message '" << e.getMessage() << "'"; + } + + auto &adapter = adapter_.GetBluezAdapterObject(); + absl::Status status; + try { + adapter.StopDiscovery(); + status = absl::OkStatus(); + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(&adapter, "StopDiscovery", e); + status = absl::InternalError(e.getMessage()); + } + active_adv_monitors_.erase(service_uuid); + return status; + }}); +} + +bool BleV2Medium::GetRemotePeripheral(const std::string &mac_address, + GetRemotePeripheralCallback callback) { + auto device = devices_->get_device_by_address(mac_address); + if (device == nullptr) return false; + callback(*device); + return true; +} + +bool BleV2Medium::GetRemotePeripheral(api::ble_v2::BlePeripheral::UniqueId id, + GetRemotePeripheralCallback callback) { + auto device = devices_->get_device_by_unique_id(id); + if (device == nullptr) return false; + callback(*device); + return true; +} +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/ble_v2_medium.h b/internal/platform/implementation/linux/ble_v2_medium.h new file mode 100644 index 00000000..c3a9f638 --- /dev/null +++ b/internal/platform/implementation/linux/ble_v2_medium.h @@ -0,0 +1,144 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_API_BLE_V2_MEDIUM_H_ +#define PLATFORM_IMPL_LINUX_API_BLE_V2_MEDIUM_H_ + +#include + +#include + +#include "absl/base/attributes.h" +#include "absl/container/flat_hash_map.h" +#include "absl/synchronization/mutex.h" +#include "internal/platform/implementation/ble_v2.h" +#include "internal/platform/implementation/linux/ble_gatt_client.h" +#include "internal/platform/implementation/linux/ble_v2_server_socket.h" +#include "internal/platform/implementation/linux/bluetooth_adapter.h" +#include "internal/platform/implementation/linux/bluetooth_devices.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" + +namespace nearby { +namespace linux { +class BleV2Medium final : public api::ble_v2::BleMedium { + public: + BleV2Medium(const BleV2Medium &) = delete; + BleV2Medium(BleV2Medium &&) = delete; + BleV2Medium &operator=(const BleV2Medium &) = delete; + BleV2Medium &operator=(BleV2Medium &&) = delete; + + explicit BleV2Medium(BluetoothAdapter &adapter); + ~BleV2Medium() override = default; + + bool StartAdvertising( + const api::ble_v2::BleAdvertisementData &advertising_data, + api::ble_v2::AdvertiseParameters advertise_set_parameters) override + ABSL_LOCKS_EXCLUDED(cur_adv_mutex_); + std::unique_ptr StartAdvertising( + const api::ble_v2::BleAdvertisementData &advertising_data, + api::ble_v2::AdvertiseParameters advertise_set_parameters, + AdvertisingCallback callback) ABSL_LOCKS_EXCLUDED(advs_mutex_) override; + bool StopAdvertising() override ABSL_LOCKS_EXCLUDED(advs_mutex_); + + bool StartScanning(const Uuid &service_uuid, + api::ble_v2::TxPowerLevel tx_power_level, + ScanCallback callback) override + ABSL_LOCKS_EXCLUDED(active_adv_monitors_mutex_); + bool StopScanning() override ABSL_LOCKS_EXCLUDED(active_adv_monitors_mutex_); + + std::unique_ptr StartScanning( + const Uuid &service_uuid, api::ble_v2::TxPowerLevel tx_power_level, + ScanningCallback callback) override; + + std::unique_ptr StartGattServer( + api::ble_v2::ServerGattConnectionCallback callback) override; + + std::unique_ptr ConnectToGattServer( + api::ble_v2::BlePeripheral &peripheral, + api::ble_v2::TxPowerLevel tx_power_level, + api::ble_v2::ClientGattConnectionCallback callback) override; + + std::unique_ptr OpenServerSocket( + const std::string &service_id) override { + return std::make_unique(); + } + + std::unique_ptr Connect( + const std::string &service_id, api::ble_v2::TxPowerLevel tx_power_level, + api::ble_v2::BlePeripheral &peripheral, + CancellationFlag *cancellation_flag) override { + return nullptr; + } + bool IsExtendedAdvertisementsAvailable() override; + bool GetRemotePeripheral(const std::string &mac_address, + GetRemotePeripheralCallback callback) override; + bool GetRemotePeripheral(api::ble_v2::BlePeripheral::UniqueId id, + GetRemotePeripheralCallback callback) override; + + private: + bool StartLEDiscovery(); + + bool MonitorManagerSupportsOr() { + std::vector supported_types; + try { + supported_types = adv_monitor_manager_->SupportedMonitorTypes(); + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_GET_ERROR(adv_monitor_manager_, "SupportedMonitorTypes", + e); + return false; + } + + auto is_supported_type = [](std::string pattern) { + return pattern == "or_patterns"; + }; + + auto end = supported_types.cend(); + return std::find_if(supported_types.cbegin(), end, is_supported_type) != + end; + } + + std::shared_ptr system_bus_; + BluetoothAdapter adapter_; + ObserverList observers_ = {}; + std::shared_ptr devices_; + std::shared_ptr gatt_discovery_; + + std::unique_ptr root_object_manager_; + std::unique_ptr adv_monitor_manager_; + absl::Mutex active_adv_monitors_mutex_; + absl::flat_hash_map< + Uuid, + std::pair, std::unique_ptr>> + active_adv_monitors_ ABSL_GUARDED_BY(active_adv_monitors_mutex_); + // Used by the synchronous variant of StartScanning + std::optional cur_monitored_service_uuid_; + + std::unique_ptr adv_manager_; + + absl::Mutex cur_adv_mutex_; + std::unique_ptr cur_adv_ + ABSL_GUARDED_BY(cur_adv_mutex_); + + absl::Mutex advs_mutex_; + std::list> advs_ + ABSL_GUARDED_BY(advs_mutex_); +}; +} // namespace linux +} // namespace nearby + +#endif diff --git a/internal/platform/implementation/linux/ble_v2_server_socket.h b/internal/platform/implementation/linux/ble_v2_server_socket.h new file mode 100644 index 00000000..ea6734c7 --- /dev/null +++ b/internal/platform/implementation/linux/ble_v2_server_socket.h @@ -0,0 +1,42 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_API_BLE_V2_SERVER_SOCKET_H_ +#define PLATFORM_IMPL_LINUX_API_BLE_V2_SERVER_SOCKET_H_ + +#include "absl/synchronization/notification.h" +#include "internal/platform/implementation/ble_v2.h" + +namespace nearby { +namespace linux { +class BleV2ServerSocket final : public api::ble_v2::BleServerSocket { + public: + std::unique_ptr Accept() override { + stopped_.WaitForNotification(); + return nullptr; + } + + Exception Close() override { + if (stopped_.HasBeenNotified()) return {Exception::kIo}; + stopped_.Notify(); + return {Exception::kSuccess}; + } + + private: + absl::Notification stopped_; +}; +} // namespace linux +} // namespace nearby + +#endif diff --git a/internal/platform/implementation/linux/bluetooth_adapter.cc b/internal/platform/implementation/linux/bluetooth_adapter.cc new file mode 100644 index 00000000..99f1bb0f --- /dev/null +++ b/internal/platform/implementation/linux/bluetooth_adapter.cc @@ -0,0 +1,121 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "internal/platform/implementation/bluetooth_adapter.h" +#include "internal/platform/implementation/linux/bluetooth_adapter.h" +#include "internal/platform/implementation/linux/dbus.h" +#include "internal/platform/implementation/linux/generated/dbus/bluez/adapter_client.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { + +bool BluetoothAdapter::SetStatus(Status status) { + try { + bool val = status == api::BluetoothAdapter::Status::kEnabled; + bluez_adapter_->Powered(val); + return true; + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_SET_ERROR(bluez_adapter_, "Powered", e); + return false; + } +} + +bool BluetoothAdapter::IsEnabled() const { + try { + return bluez_adapter_->Powered(); + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_GET_ERROR(bluez_adapter_, "Powered", e); + return false; + } +} + +BluetoothAdapter::ScanMode BluetoothAdapter::GetScanMode() const { + bool powered = IsEnabled(); + if (!powered) { + return ScanMode::kNone; + } + + try { + bool discoverable = bluez_adapter_->Discoverable(); + return discoverable ? ScanMode::kConnectableDiscoverable + : ScanMode::kConnectable; + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_GET_ERROR(bluez_adapter_, "Discoverable", e); + return ScanMode::kUnknown; + } +} + +bool BluetoothAdapter::SetScanMode(ScanMode scan_mode) { + switch (scan_mode) { + case ScanMode::kConnectable: + return SetStatus(Status::kEnabled); + case ScanMode::kConnectableDiscoverable: { + if (!SetStatus(Status::kEnabled)) { + return false; + } + + try { + bluez_adapter_->Discoverable(true); + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_SET_ERROR(bluez_adapter_, "Discoverable", e); + return false; + } + + return true; + } + case ScanMode::kNone: + return SetStatus(Status::kDisabled); + default: + return false; + } +} + +std::string BluetoothAdapter::GetName() const { + try { + return bluez_adapter_->Alias(); + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_GET_ERROR(bluez_adapter_, "Alias", e); + return {}; + } +} + +bool BluetoothAdapter::SetName(absl::string_view name, bool /*persist*/) { + return SetName(name); +} + +bool BluetoothAdapter::SetName(absl::string_view name) { + try { + bluez_adapter_->Alias(std::string(name)); + return true; + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_SET_ERROR(bluez_adapter_, "Alias", e); + return false; + } +} + +std::string BluetoothAdapter::GetMacAddress() const { + try { + return bluez_adapter_->Address(); + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_GET_ERROR(bluez_adapter_, "Address", e); + return {}; + } +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/bluetooth_adapter.h b/internal/platform/implementation/linux/bluetooth_adapter.h new file mode 100644 index 00000000..810cae8f --- /dev/null +++ b/internal/platform/implementation/linux/bluetooth_adapter.h @@ -0,0 +1,84 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_BLUETOOTH_ADAPTER_H_ +#define PLATFORM_IMPL_LINUX_BLUETOOTH_ADAPTER_H_ +#include +#include + +#include "absl/strings/string_view.h" +#include "internal/platform/implementation/bluetooth_adapter.h" +#include "internal/platform/implementation/linux/bluez.h" +#include "internal/platform/implementation/linux/dbus.h" +#include "internal/platform/implementation/linux/generated/dbus/bluez/adapter_client.h" + +namespace nearby { +namespace linux { +class BluezAdapter : public sdbus::ProxyInterfaces { + public: + BluezAdapter(sdbus::IConnection &system_bus, + const sdbus::ObjectPath &adapter_object_path) + : ProxyInterfaces(system_bus, bluez::SERVICE_DEST, adapter_object_path) { + registerProxy(); + } + ~BluezAdapter() { unregisterProxy(); } +}; + +class BluetoothAdapter : public api::BluetoothAdapter { + public: + BluetoothAdapter(std::shared_ptr system_bus, + const sdbus::ObjectPath &adapter_object_path) + : system_bus_(std::move(system_bus)), + bluez_adapter_(std::make_shared(*system_bus_, + adapter_object_path)) {} + + ~BluetoothAdapter() override = default; + + bool SetStatus(Status status) override; + bool IsEnabled() const override; + + ScanMode GetScanMode() const override; + + bool SetScanMode(ScanMode scan_mode) override; + std::string GetName() const override; + + bool SetName(absl::string_view name) override; + bool SetName(absl::string_view name, bool persist) override; + std::string GetMacAddress() const override; + + bool RemoveDeviceByObjectPath(const sdbus::ObjectPath &device_object_path) { + try { + bluez_adapter_->RemoveDevice(device_object_path); + return true; + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(bluez_adapter_, "RemoveDevice", e); + return false; + } + } + + sdbus::ObjectPath GetObjectPath() const { + return bluez_adapter_->getObjectPath(); + } + + BluezAdapter &GetBluezAdapterObject() { return *bluez_adapter_; } + std::shared_ptr GetConnection() { return system_bus_; } + + private: + std::shared_ptr system_bus_; + std::shared_ptr bluez_adapter_; +}; +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPL_LINUX_BLUETOOTH_ADAPTER_H_ diff --git a/internal/platform/implementation/linux/bluetooth_bluez_profile.cc b/internal/platform/implementation/linux/bluetooth_bluez_profile.cc new file mode 100644 index 00000000..d371a315 --- /dev/null +++ b/internal/platform/implementation/linux/bluetooth_bluez_profile.cc @@ -0,0 +1,285 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "absl/synchronization/mutex.h" +#include "internal/platform/cancellation_flag_listener.h" +#include "internal/platform/implementation/linux/bluetooth_bluez_profile.h" +#include "internal/platform/implementation/linux/bluetooth_classic_device.h" +#include "internal/platform/implementation/linux/bluetooth_devices.h" +#include "internal/platform/implementation/linux/bluez.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { + +bool ProfileManager::ProfileRegistered(absl::string_view service_uuid) { + registered_service_uuids_mutex_.ReaderLock(); + bool registered = registered_services_.count(std::string(service_uuid)) == 1; + registered_service_uuids_mutex_.ReaderUnlock(); + return registered; +} + +void Profile::Release() { + released_ = true; + NEARBY_LOGS(VERBOSE) << __func__ << ": Profile object " << getObjectPath() + << " has been released"; +} + +void Profile::NewConnection( + const sdbus::ObjectPath &device_object_path, const sdbus::UnixFd &fd, + const std::map &fd_props) { + if (released_) { + NEARBY_LOGS(ERROR) << __func__ + << ": NewConnection called on released object " + << getObjectPath(); + throw sdbus::Error("org.bluez.Error.Rejected", + "NewConnection called on released object"); + } + + auto device = devices_.get_device_by_path(device_object_path); + + if (device == nullptr) { + device = devices_.add_new_device(device_object_path); + } + + auto alias = device->GetName(); + auto mac_addr = device->GetAddress(); + NEARBY_LOGS(VERBOSE) << __func__ << ": " << getObjectPath() + << ": Connected to " << mac_addr; + + FDProperties props(fd_props); + + absl::MutexLock l(&connections_lock_); + connections_[mac_addr].push_back(std::pair(fd, props)); +} + +void Profile::RequestDisconnection( + const sdbus::ObjectPath &device_object_path) { + auto device = devices_.get_device_by_path(device_object_path); + if (device == nullptr) { + NEARBY_LOGS(ERROR) << __func__ << ": " << getObjectPath() + << ": RequestDisconnection called with a device object " + "we don't know about: " + << device_object_path; + throw sdbus::Error("org.bluez.Error.Rejected", "Unknown object"); + } + + auto mac_addr = device->GetMacAddress(); + NEARBY_LOGS(VERBOSE) << __func__ << ": Disconnection requested for device " + << device_object_path; + + absl::MutexLock l(&connections_lock_); + if (connections_.count(mac_addr) == 0) { + NEARBY_LOGS(ERROR) + << __func__ + << ": Disconnection requested, but we are not connected to this device"; + return; + } + + connections_.erase(mac_addr); +} + +bool ProfileManager::Register(std::optional name, + absl::string_view service_uuid) { + absl::MutexLock l(®istered_service_uuids_mutex_); + if (registered_services_.count(std::string(service_uuid)) == 1) { + NEARBY_LOGS(WARNING) << __func__ << ": Trying to register profile " + << service_uuid << " which was already registered."; + return true; + } + + auto profile = std::make_shared( + getProxy().getConnection(), bluez::profile_object_path(service_uuid), + devices_); + + try { + std::map options; + if (name.has_value()) { + options["Name"] = std::string(*name); + } + options["RequireAuthorization"] = false; + options["RequireAuthentication"] = false; + options["Channel"] = static_cast(0); + options["PSM"] = static_cast(0); + + RegisterProfile(profile->getObjectPath(), std::string(service_uuid), + options); + } catch (const sdbus::Error &e) { + BLUEZ_LOG_METHOD_CALL_ERROR(&getProxy(), "RegisterProfile", e); + return false; + } + + registered_services_.emplace(service_uuid, profile); + + NEARBY_LOGS(INFO) << __func__ + << ": Registered profile instance for service uuid " + << service_uuid; + + return true; +} + +void ProfileManager::Unregister(absl::string_view service_uuid) { + absl::MutexLock l(®istered_service_uuids_mutex_); + if (registered_services_.count(std::string(service_uuid)) == 0) { + NEARBY_LOGS(WARNING) + << __func__ + << ": attempted to unregister a profile that is not registered"; + return; + } + + auto profile_object_path = bluez::profile_object_path(service_uuid); + NEARBY_LOGS(VERBOSE) << __func__ << ": Unregistering profile " + << profile_object_path; + + try { + UnregisterProfile(profile_object_path); + } catch (const sdbus::Error &e) { + BLUEZ_LOG_METHOD_CALL_ERROR(&getProxy(), "UnregisterProfile", e); + } + + registered_services_.erase(std::string(service_uuid)); +} + +// Get a service record FD for a connected profile (identified by service_uuid) +// to the given device. +std::optional ProfileManager::GetServiceRecordFD( + api::BluetoothDevice &remote_device, absl::string_view service_uuid, + CancellationFlag *cancellation_flag) { + std::shared_ptr profile; + { + absl::ReaderMutexLock lock(®istered_service_uuids_mutex_); + if (registered_services_.count(std::string(service_uuid)) == 0) { + NEARBY_LOGS(ERROR) << __func__ << ": Service " << service_uuid + << " is not registered"; + return std::nullopt; + } + profile = registered_services_[std::string(service_uuid)]; + } + auto mac_addr = remote_device.GetMacAddress(); + + std::unique_ptr cancel_listener; + if (cancellation_flag != nullptr) + cancel_listener = std::make_unique( + cancellation_flag, [&profile]() { + profile->connections_lock_.Lock(); + profile->connections_lock_.Unlock(); + }); + + NEARBY_LOGS(VERBOSE) << __func__ << ": " << profile->getObjectPath() + << ": Attempting to get a FD for service " + << service_uuid << " on device " << mac_addr; + + auto cond = [mac_addr, profile, cancellation_flag]() { + profile->connections_lock_.AssertReaderHeld(); + return profile->connections_.count(mac_addr) != 0 || + (cancellation_flag != nullptr && cancellation_flag->Cancelled()); + }; + + absl::MutexLock connections_lock(&profile->connections_lock_, + absl::Condition(&cond)); + + if (cancellation_flag != nullptr && cancellation_flag->Cancelled()) { + NEARBY_LOGS(VERBOSE) + << __func__ << ": " << profile->getObjectPath() << ": " + << remote_device.GetMacAddress() + << ": Cancelled waiting for a new connection on profile " + << service_uuid; + return std::nullopt; + } + + auto [fd, properties] = profile->connections_[mac_addr].back(); + profile->connections_[mac_addr].pop_back(); + if (profile->connections_[mac_addr].empty()) + profile->connections_.erase(mac_addr); + + return std::move(fd); +} + +// Listen for a connected profile on any device, returning the connected device +// with its FD. +std::optional, sdbus::UnixFd>> +ProfileManager::GetServiceRecordFD(absl::string_view service_uuid, + CancellationFlag *cancellation_flag) { + std::shared_ptr profile; + + { + absl::ReaderMutexLock lock(®istered_service_uuids_mutex_); + if (registered_services_.count(std::string(service_uuid)) == 0) { + return std::nullopt; + } + + profile = registered_services_[std::string(service_uuid)]; + } + + NEARBY_LOGS(VERBOSE) << __func__ << ": " << profile->getObjectPath() + << ": Attempting to get a FD for service " + << service_uuid; + + std::unique_ptr cancel_listener; + if (cancellation_flag != nullptr) + cancel_listener = std::make_unique( + cancellation_flag, [&profile]() { + profile->connections_lock_.Lock(); + profile->connections_lock_.Unlock(); + }); + + profile->connections_lock_.Lock(); + auto cond = [profile, &cancellation_flag]() { + profile->connections_lock_.AssertReaderHeld(); + return !profile->connections_.empty() || + (cancellation_flag != nullptr && cancellation_flag->Cancelled()); + }; + profile->connections_lock_.Await(absl::Condition(&cond)); + + if (cancellation_flag != nullptr && cancellation_flag->Cancelled()) { + NEARBY_LOGS(VERBOSE) + << __func__ << ": Cancelled waiting for new connections on profile " + << profile->getObjectPath(); + profile->connections_lock_.Unlock(); + return std::nullopt; + } + + auto it = profile->connections_.begin(); + auto mac_addr = it->first; + auto [fd, properties] = it->second.back(); + it->second.pop_back(); + if (it->second.empty()) profile->connections_.erase(it); + profile->connections_lock_.Unlock(); + + auto device = devices_.get_device_by_address(mac_addr); + if (device == nullptr) { + NEARBY_LOGS(ERROR) << __func__ << ": Device " << mac_addr + << " is no longer available"; + return std::nullopt; + } + + return std::pair(device, std::move(fd)); +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/bluetooth_bluez_profile.h b/internal/platform/implementation/linux/bluetooth_bluez_profile.h new file mode 100644 index 00000000..31cc1bf3 --- /dev/null +++ b/internal/platform/implementation/linux/bluetooth_bluez_profile.h @@ -0,0 +1,147 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_BLUETOOTH_BLUEZ_PROFILE_H_ +#define PLATFORM_IMPL_LINUX_BLUETOOTH_BLUEZ_PROFILE_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "absl/strings/string_view.h" +#include "absl/synchronization/mutex.h" +#include "internal/platform/implementation/bluetooth_classic.h" +#include "internal/platform/implementation/linux/bluetooth_devices.h" +#include "internal/platform/implementation/linux/bluez.h" +#include "internal/platform/implementation/linux/generated/dbus/bluez/profile_manager_client.h" +#include "internal/platform/implementation/linux/generated/dbus/bluez/profile_server.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { +class ProfileManager; + +class Profile final + : public sdbus::AdaptorInterfaces { + public: + Profile(const Profile &) = delete; + Profile(Profile &&) = delete; + Profile &operator=(const Profile &) = delete; + Profile &operator=(Profile &&) = delete; + Profile(sdbus::IConnection &system_bus, sdbus::ObjectPath profile_object_path, + BluetoothDevices &devices) + : AdaptorInterfaces(system_bus, std::move(profile_object_path)), + released_(false), + devices_(devices) { + registerAdaptor(); + NEARBY_LOGS(VERBOSE) << __func__ << ": Created a new BlueZ profile at :" + << getObjectPath(); + } + ~Profile() { unregisterAdaptor(); } + + private: + friend class ProfileManager; + + struct FDProperties { + explicit FDProperties(const std::map &fd_props) + : version(std::nullopt), features(std::nullopt) { + if (fd_props.count("Version") == 1) { + version = fd_props.at("Version"); + } + if (fd_props.count("Features") == 1) { + features = fd_props.at("Features"); + } + } + + std::optional version; + std::optional features; + }; + + void Release() override; + void NewConnection(const sdbus::ObjectPath &, const sdbus::UnixFd &, + const std::map &) override + ABSL_LOCKS_EXCLUDED(connections_lock_); + void RequestDisconnection(const sdbus::ObjectPath &) override + ABSL_LOCKS_EXCLUDED(connections_lock_); + + std::atomic_bool released_; + + absl::Mutex connections_lock_; + std::map>> + connections_ ABSL_GUARDED_BY(connections_lock_); + + BluetoothDevices &devices_; +}; + +class ProfileManager final + : private sdbus::ProxyInterfaces { + public: + ProfileManager(const ProfileManager &) = delete; + ProfileManager(ProfileManager &&) = delete; + ProfileManager &operator=(const ProfileManager &) = delete; + ProfileManager &operator=(ProfileManager &&) = delete; + ProfileManager(sdbus::IConnection &system_bus, BluetoothDevices &devices) + : ProxyInterfaces(system_bus, bluez::SERVICE_DEST, "/org/bluez"), + devices_(devices) { + registerProxy(); + } + ~ProfileManager() { unregisterProxy(); } + + bool ProfileRegistered(absl::string_view service_uuid) + ABSL_LOCKS_EXCLUDED(registered_service_uuids_mutex_); + bool Register(std::optional service_name, + absl::string_view service_uuid) + ABSL_LOCKS_EXCLUDED(registered_service_uuids_mutex_); + bool Register(absl::string_view service_uuid) + ABSL_LOCKS_EXCLUDED(registered_service_uuids_mutex_) { + return Register(std::nullopt, service_uuid); + } + void Unregister(absl::string_view service_uuid) + ABSL_LOCKS_EXCLUDED(registered_service_uuids_mutex_); + + std::optional GetServiceRecordFD( + api::BluetoothDevice &remote_device, absl::string_view service_uuid, + CancellationFlag *cancellation_flag) + ABSL_LOCKS_EXCLUDED(registered_service_uuids_mutex_); + std::optional, sdbus::UnixFd>> + GetServiceRecordFD(absl::string_view service_uuid, + CancellationFlag *cancellation_flag) + ABSL_LOCKS_EXCLUDED(registered_service_uuids_mutex_); + + private: + BluetoothDevices &devices_; + // Maps service UUIDs to RegisteredService + absl::Mutex registered_service_uuids_mutex_; + std::map> registered_services_ + ABSL_GUARDED_BY(registered_service_uuids_mutex_); +}; + +} // namespace linux +} // namespace nearby +#endif diff --git a/internal/platform/implementation/linux/bluetooth_classic_device.cc b/internal/platform/implementation/linux/bluetooth_classic_device.cc new file mode 100644 index 00000000..035c887d --- /dev/null +++ b/internal/platform/implementation/linux/bluetooth_classic_device.cc @@ -0,0 +1,148 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include +#include + +#include "absl/strings/string_view.h" +#include "internal/platform/bluetooth_utils.h" +#include "internal/platform/implementation/linux/bluetooth_classic_device.h" +#include "internal/platform/implementation/linux/bluez.h" +#include "internal/platform/implementation/linux/bluez_device.h" +#include "internal/platform/implementation/linux/dbus.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { +BluetoothDevice::BluetoothDevice(std::shared_ptr device) + : lost_(false), device_(device) { + try { + last_known_name_ = device->Alias(); + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_GET_ERROR(device, "Alias", e); + } + try { + last_known_address_ = device->Address(); + unique_id_ = BluetoothUtils::ToNumber(last_known_address_); + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_GET_ERROR(device, "Address", e); + } +} + +std::string BluetoothDevice::GetName() const { + auto device = device_.lock(); + if (device == nullptr) { + absl::ReaderMutexLock l(&properties_mutex_); + return last_known_name_; + } + + try { + std::string alias = device->Alias(); + { + absl::MutexLock l(&properties_mutex_); + last_known_name_ = alias; + } + return alias; + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_GET_ERROR(device, "Alias", e); + return {}; + } +} + +std::string BluetoothDevice::GetMacAddress() const { + auto device = device_.lock(); + if (device == nullptr) { + absl::ReaderMutexLock l(&properties_mutex_); + return last_known_name_; + } + + try { + std::string addr = device->Address(); + { + absl::MutexLock l(&properties_mutex_); + last_known_address_ = addr; + } + return addr; + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_GET_ERROR(device, "Address", e); + return std::string(); + } +} + +bool BluetoothDevice::ConnectToProfile(absl::string_view service_uuid) { + auto device = device_.lock(); + if (device == nullptr) return false; + try { + device->ConnectProfile(std::string(service_uuid)); + return true; + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(device, "ConnectProfile", e); + return false; + } +} + +MonitoredBluetoothDevice::MonitoredBluetoothDevice( + std::shared_ptr system_bus, + std::shared_ptr device, + ObserverList &observers) + : BluetoothDevice(std::move(device)), + ProxyInterfaces(*system_bus, bluez::SERVICE_DEST, + device->getObjectPath()), + system_bus_(std::move(system_bus)), + observers_(observers) { + registerProxy(); +} + +void MonitoredBluetoothDevice::onPropertiesChanged( + const std::string &interfaceName, + const std::map &changedProperties, + const std::vector &invalidatedProperties) { + if (interfaceName != bluez::DEVICE_INTERFACE) { + return; + } + + for (auto it = changedProperties.begin(); it != changedProperties.end(); + it++) { + if (it->first == bluez::DEVICE_PROP_ADDRESS) { + NEARBY_LOGS(VERBOSE) << __func__ << ": " << getObjectPath() + << ": Notifying observers about address change"; + std::string address = it->second; + for (const auto &observer : observers_.GetObservers()) { + observer->DeviceAddressChanged(*this, address); + } + } else if (it->first == bluez::DEVICE_PROP_PAIRED) { + NEARBY_LOGS(VERBOSE) << __func__ << ": " << getObjectPath() + << "Notifying observers about paired status change."; + for (const auto &observer : observers_.GetObservers()) { + observer->DevicePairedChanged(*this, it->second); + } + } else if (it->first == bluez::DEVICE_PROP_CONNECTED) { + NEARBY_LOGS(VERBOSE) + << __func__ << ": " << getObjectPath() + << "Notifying observers about connected status change"; + for (const auto &observer : observers_.GetObservers()) { + observer->DeviceConnectedStateChanged(*this, it->second); + } + } else if (it->first == bluez::DEVICE_NAME) { + auto callback = GetDiscoveryCallback(); + if (callback != nullptr && callback->device_name_changed_cb != nullptr) + callback->device_name_changed_cb(*this); + } + } +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/bluetooth_classic_device.h b/internal/platform/implementation/linux/bluetooth_classic_device.h new file mode 100644 index 00000000..57c6a2c2 --- /dev/null +++ b/internal/platform/implementation/linux/bluetooth_classic_device.h @@ -0,0 +1,182 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_BLUETOOTH_CLASSIC_DEVICE_H_ +#define PLATFORM_IMPL_LINUX_BLUETOOTH_CLASSIC_DEVICE_H_ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "absl/functional/any_invocable.h" +#include "absl/strings/string_view.h" +#include "absl/synchronization/mutex.h" +#include "internal/base/observer_list.h" +#include "internal/platform/implementation/ble_v2.h" +#include "internal/platform/implementation/bluetooth_classic.h" +#include "internal/platform/implementation/linux/bluez_device.h" +#include "internal/platform/implementation/linux/dbus.h" +#include "internal/platform/implementation/linux/generated/dbus/bluez/device_client.h" + +namespace nearby { +namespace linux { +// https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html. +class BluetoothDevice : public api::BluetoothDevice, + public api::ble_v2::BlePeripheral { + public: + using UniqueId = std::uint64_t; + + BluetoothDevice(const BluetoothDevice &) = delete; + BluetoothDevice(BluetoothDevice &&) = delete; + BluetoothDevice &operator=(const BluetoothDevice &) = delete; + BluetoothDevice &operator=(BluetoothDevice &&) = delete; + explicit BluetoothDevice(std::shared_ptr device); + + // BluetoothDevice methods + // https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#getName() + std::string GetName() const override; + // Returns BT MAC address assigned to this device. + std::string GetMacAddress() const override; + + // BlePeripheral methods + std::string GetAddress() const override { return GetMacAddress(); } + UniqueId GetUniqueId() const override { return unique_id_; }; + + std::optional> ServiceData() { + auto device = device_.lock(); + if (device == nullptr) return std::nullopt; + + try { + return device->ServiceData(); + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_GET_ERROR(device, "ServiceData", e); + return std::nullopt; + } + } + bool Bonded() { + auto device = device_.lock(); + if (device == nullptr) return false; + + try { + return device->Bonded(); + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(device, "Bonded", e); + return false; + } + } + + std::optional Pair() { + auto device = device_.lock(); + if (device == nullptr) return std::nullopt; + + try { + return device->Pair(); + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(device, "Pair", e); + return std::nullopt; + } + } + + bool CancelPairing() { + auto device = device_.lock(); + if (device == nullptr) return false; + + try { + device->CancelPairing(); + return true; + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(device, "CancelPairing", e); + return false; + } + } + + void SetPairReplyCallback(absl::AnyInvocable cb) { + auto device = device_.lock(); + if (device != nullptr) device->SetPairReplyCallback(std::move(cb)); + } + + bool ConnectToProfile(absl::string_view service_uuid); + void MarkLost() { lost_ = true; } + void UnmarkLost() { lost_ = false; } + bool Lost() const { return lost_; } + + private: + UniqueId unique_id_; + std::atomic_bool lost_; + + mutable absl::Mutex properties_mutex_; + mutable std::string last_known_name_ ABSL_GUARDED_BY(properties_mutex_); + mutable std::string last_known_address_ ABSL_GUARDED_BY(properties_mutex_); + mutable std::weak_ptr device_; +}; + +class MonitoredBluetoothDevice final + : public BluetoothDevice, + public sdbus::ProxyInterfaces { + public: + using sdbus::ProxyInterfaces::registerProxy; + using sdbus::ProxyInterfaces::unregisterProxy; + using sdbus::ProxyInterfaces::getObjectPath; + + MonitoredBluetoothDevice(const MonitoredBluetoothDevice &) = delete; + MonitoredBluetoothDevice(MonitoredBluetoothDevice &&) = delete; + MonitoredBluetoothDevice &operator=(const MonitoredBluetoothDevice &) = + delete; + MonitoredBluetoothDevice &operator=(MonitoredBluetoothDevice &&) = delete; + MonitoredBluetoothDevice( + std::shared_ptr system_bus, + std::shared_ptr device, + ObserverList &observers); + ~MonitoredBluetoothDevice() override { unregisterProxy(); } + + void SetDiscoveryCallback( + std::shared_ptr &callback) + ABSL_LOCKS_EXCLUDED(discovery_cb_mutex_) { + absl::MutexLock lock(&discovery_cb_mutex_); + discovery_cb_ = callback; + }; + + protected: + void onPropertiesChanged( + const std::string &interfaceName, + const std::map &changedProperties, + const std::vector &invalidatedProperties) override; + + private: + std::shared_ptr system_bus_; + std::shared_ptr + GetDiscoveryCallback() ABSL_LOCKS_EXCLUDED(discovery_cb_mutex_) { + discovery_cb_mutex_.ReaderLock(); + auto callback = discovery_cb_.lock(); + discovery_cb_mutex_.ReaderUnlock(); + + return callback; + } + + ObserverList &observers_; + absl::Mutex discovery_cb_mutex_; + std::weak_ptr discovery_cb_ + ABSL_GUARDED_BY(discovery_cb_mutex_); +}; + +} // namespace linux +} // namespace nearby + +#endif diff --git a/internal/platform/implementation/linux/bluetooth_classic_medium.cc b/internal/platform/implementation/linux/bluetooth_classic_medium.cc new file mode 100644 index 00000000..e179340f --- /dev/null +++ b/internal/platform/implementation/linux/bluetooth_classic_medium.cc @@ -0,0 +1,165 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include +#include + +#include "absl/strings/string_view.h" +#include "internal/base/observer_list.h" +#include "internal/platform/implementation/bluetooth_classic.h" +#include "internal/platform/implementation/linux/bluetooth_adapter.h" +#include "internal/platform/implementation/linux/bluetooth_bluez_profile.h" +#include "internal/platform/implementation/linux/bluetooth_classic_device.h" +#include "internal/platform/implementation/linux/bluetooth_classic_medium.h" +#include "internal/platform/implementation/linux/bluetooth_classic_server_socket.h" +#include "internal/platform/implementation/linux/bluetooth_classic_socket.h" +#include "internal/platform/implementation/linux/bluetooth_pairing.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { +BluetoothClassicMedium::BluetoothClassicMedium(BluetoothAdapter &adapter) + : system_bus_(adapter.GetConnection()), + adapter_(adapter), + observers_(std::make_shared>()), + devices_(std::make_shared( + system_bus_, adapter.GetObjectPath(), *observers_)), + device_watcher_(nullptr), + profile_manager_( + std::make_unique(*system_bus_, *devices_)) {} + +bool BluetoothClassicMedium::StartDiscovery( + DiscoveryCallback discovery_callback) { + device_watcher_ = std::make_unique( + *system_bus_, adapter_.GetObjectPath(), devices_, + std::make_unique(std::move(discovery_callback)), + observers_); + + std::map filter; + filter["Transport"] = "auto"; + auto &adapter = adapter_.GetBluezAdapterObject(); + + try { + adapter.SetDiscoveryFilter(filter); + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(&adapter, "SetDiscoveryFilter", e); + device_watcher_ = nullptr; + return false; + } + + try { + NEARBY_LOGS(INFO) << __func__ << ": Starting BR/EDR discovery on " + << adapter_.GetObjectPath(); + adapter.StartDiscovery(); + } catch (const sdbus::Error &e) { + if (e.getName() != "org.bluez.Error.InProgress") { + DBUS_LOG_METHOD_CALL_ERROR(&adapter, "StartDiscovery", e); + device_watcher_ = nullptr; + return false; + } + } + + return true; +} + +bool BluetoothClassicMedium::StopDiscovery() { + auto &adapter = adapter_.GetBluezAdapterObject(); + NEARBY_LOGS(INFO) << __func__ << "Stopping discovery on " + << adapter.getObjectPath(); + auto ret = true; + try { + adapter.StopDiscovery(); + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(&adapter, "StopDiscovery", e); + ret = false; + } + device_watcher_ = nullptr; + + return ret; +} + +std::unique_ptr BluetoothClassicMedium::ConnectToService( + api::BluetoothDevice &remote_device, const std::string &service_uuid, + CancellationFlag *cancellation_flag) { + if (!profile_manager_->ProfileRegistered(service_uuid)) { + if (!profile_manager_->Register(std::nullopt, service_uuid)) { + NEARBY_LOGS(ERROR) << __func__ << ": Could not register profile " + << service_uuid << " with Bluez"; + return nullptr; + } + } + + auto address = remote_device.GetMacAddress(); + auto device = devices_->get_device_by_address(address); + if (device == nullptr) { + NEARBY_LOGS(ERROR) << __func__ << ": Device " << address + << " is no longer known"; + return nullptr; + } + + if (!device->ConnectToProfile(service_uuid)) { + return nullptr; + } + + auto fd = profile_manager_->GetServiceRecordFD(remote_device, service_uuid, + cancellation_flag); + if (!fd.has_value()) { + NEARBY_LOGS(WARNING) << __func__ + << ": Failed to get a new connection for profile " + << service_uuid << " for device " << address; + return nullptr; + } + + return std::unique_ptr( + new BluetoothSocket(device, fd.value())); +} + +std::unique_ptr +BluetoothClassicMedium::ListenForService(const std::string &service_name, + const std::string &service_uuid) { + if (!profile_manager_->ProfileRegistered(service_uuid)) { + if (!profile_manager_->Register(service_name, service_uuid)) { + NEARBY_LOGS(ERROR) << __func__ << ": Could not register profile " + << service_name << " " << service_uuid + << " with Bluez"; + return nullptr; + } + } + + return std::unique_ptr( + new BluetoothServerSocket(*profile_manager_, service_uuid)); +} + +api::BluetoothDevice *BluetoothClassicMedium::GetRemoteDevice( + const std::string &mac_address) { + auto device = devices_->get_device_by_address(mac_address); + if (device == nullptr) return nullptr; + + return device.get(); +} + +std::unique_ptr BluetoothClassicMedium::CreatePairing( + api::BluetoothDevice &remote_device) { + auto device = devices_->get_device_by_address(remote_device.GetMacAddress()); + if (device == nullptr) return nullptr; + + return std::unique_ptr( + new BluetoothPairing(adapter_, device)); +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/bluetooth_classic_medium.h b/internal/platform/implementation/linux/bluetooth_classic_medium.h new file mode 100644 index 00000000..7192206c --- /dev/null +++ b/internal/platform/implementation/linux/bluetooth_classic_medium.h @@ -0,0 +1,116 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_BLUETOOTH_CLASSIC_MEDIUM_H_ +#define PLATFORM_IMPL_LINUX_BLUETOOTH_CLASSIC_MEDIUM_H_ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "internal/base/observer_list.h" +#include "internal/platform/implementation/bluetooth_classic.h" +#include "internal/platform/implementation/linux/bluetooth_adapter.h" +#include "internal/platform/implementation/linux/bluetooth_bluez_profile.h" +#include "internal/platform/implementation/linux/bluetooth_devices.h" + +namespace nearby { +namespace linux { +// Container of operations that can be performed over the Bluetooth Classic +// medium. +class BluetoothClassicMedium : public api::BluetoothClassicMedium { + public: + explicit BluetoothClassicMedium(BluetoothAdapter &adapter); + + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#startDiscovery() + // + // Returns true once the process of discovery has been initiated. + bool StartDiscovery(DiscoveryCallback discovery_callback) override; + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#cancelDiscovery() + // + // Returns true once discovery is well and truly stopped; after this returns, + // there must be no more invocations of the DiscoveryCallback passed in to + // StartDiscovery(). + bool StopDiscovery() override; + + // A combination of + // https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#createInsecureRfcommSocketToServiceRecord + // followed by + // https://developer.android.com/reference/android/bluetooth/BluetoothSocket.html#connect(). + // + // service_uuid is the canonical textual representation + // (https://en.wikipedia.org/wiki/Universally_unique_identifier#Format) of a + // type 3 name-based + // (https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions_3_and_5_(namespace_name-based)) + // UUID. + // + // On success, returns a new BluetoothSocket. + // On error, returns nullptr. + std::unique_ptr ConnectToService( + api::BluetoothDevice &remote_device, const std::string &service_uuid, + CancellationFlag *cancellation_flag) override; + + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#listenUsingInsecureRfcommWithServiceRecord + // + // service_uuid is the canonical textual representation + // (https://en.wikipedia.org/wiki/Universally_unique_identifier#Format) of a + // type 3 name-based + // (https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions_3_and_5_(namespace_name-based)) + // UUID. + // + // Returns nullptr error. + std::unique_ptr ListenForService( + const std::string &service_name, + const std::string &service_uuid) override; + + // https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#createBond() + // + // Start the bonding (pairing) process with the remote device. + // Return a Bluetooth pairing instance to handle the pairing process with the + // remote device. + std::unique_ptr CreatePairing( + api::BluetoothDevice &remote_device) override; + + api::BluetoothDevice *GetRemoteDevice( + const std::string &mac_address) override; + + void AddObserver(Observer *observer) override { + observers_->AddObserver(observer); + }; + void RemoveObserver(Observer *observer) override { + observers_->RemoveObserver(observer); + }; + + private: + std::shared_ptr system_bus_; + + BluetoothAdapter adapter_; + std::shared_ptr> observers_; + std::shared_ptr devices_; + std::unique_ptr device_watcher_; + + std::unique_ptr profile_manager_; +}; + +} // namespace linux +} // namespace nearby + +#endif diff --git a/internal/platform/implementation/linux/bluetooth_classic_server_socket.cc b/internal/platform/implementation/linux/bluetooth_classic_server_socket.cc new file mode 100644 index 00000000..f70162c8 --- /dev/null +++ b/internal/platform/implementation/linux/bluetooth_classic_server_socket.cc @@ -0,0 +1,55 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "internal/platform/implementation/linux/bluetooth_classic_server_socket.h" +#include "internal/platform/exception.h" +#include "internal/platform/implementation/bluetooth_classic.h" +#include "internal/platform/implementation/linux/bluetooth_classic_device.h" +#include "internal/platform/implementation/linux/bluetooth_classic_socket.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { +std::unique_ptr BluetoothServerSocket::Accept() { + if (stopped_.Cancelled()) { + NEARBY_LOGS(ERROR) << __func__ << ": server socket has been stopped"; + return nullptr; + } + + NEARBY_LOGS(VERBOSE) << __func__ + << ": accepting new connections for service uuid " + << service_uuid_; + + auto pair = profile_manager_.GetServiceRecordFD(service_uuid_, &stopped_); + if (!pair.has_value()) { + if (!stopped_.Cancelled()) + NEARBY_LOGS(ERROR) << __func__ + << ": Failed to get a new connection for profile " + << service_uuid_; + return nullptr; + } + + auto [device, fd] = *pair; + return std::make_unique(device, std::move(fd)); +} + +Exception BluetoothServerSocket::Close() { + NEARBY_LOGS(ERROR) << __func__ << ": closing bluetooth server socket"; + stopped_.Cancel(); + profile_manager_.Unregister(service_uuid_); + + return {Exception::kSuccess}; +} +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/bluetooth_classic_server_socket.h b/internal/platform/implementation/linux/bluetooth_classic_server_socket.h new file mode 100644 index 00000000..7697eefb --- /dev/null +++ b/internal/platform/implementation/linux/bluetooth_classic_server_socket.h @@ -0,0 +1,56 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_BLUETOOTH_SERVER_SOCKET_H_ +#define PLATFORM_IMPL_LINUX_BLUETOOTH_SERVER_SOCKET_H_ + +#include "absl/strings/string_view.h" +#include "internal/platform/cancellation_flag.h" +#include "internal/platform/exception.h" +#include "internal/platform/implementation/bluetooth_classic.h" +#include "internal/platform/implementation/linux/bluetooth_bluez_profile.h" + +namespace nearby { +namespace linux { +class BluetoothServerSocket final : public api::BluetoothServerSocket { + public: + BluetoothServerSocket(ProfileManager &profile_manager, + absl::string_view service_uuid) + : profile_manager_(profile_manager), service_uuid_(service_uuid) {} + + // https://developer.android.com/reference/android/bluetooth/BluetoothServerSocket.html#accept() + // + // Blocks until either: + // - at least one incoming connection request is available, or + // - ServerSocket is closed. + // On success, returns connected socket, ready to exchange data. + // Returns nullptr on error. + // Once error is reported, it is permanent, and ServerSocket has to be + // closed. + std::unique_ptr Accept() override; + + // https://developer.android.com/reference/android/bluetooth/BluetoothServerSocket.html#close() + // + // Returns Exception::kIo on error, Exception::kSuccess otherwise. + Exception Close() override; + + private: + CancellationFlag stopped_; + ProfileManager &profile_manager_; + std::string service_uuid_; +}; +} // namespace linux +} // namespace nearby + +#endif diff --git a/internal/platform/implementation/linux/bluetooth_classic_socket.cc b/internal/platform/implementation/linux/bluetooth_classic_socket.cc new file mode 100644 index 00000000..325cce9f --- /dev/null +++ b/internal/platform/implementation/linux/bluetooth_classic_socket.cc @@ -0,0 +1,123 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include +#include + +#include "internal/platform/byte_array.h" +#include "internal/platform/exception.h" +#include "internal/platform/implementation/linux/bluetooth_classic_socket.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { +Exception Poller::Ready() { + while (true) { + auto ret = poll(fds_, 1, -1); + if (ret < 0) { + if (errno == EAGAIN) continue; + NEARBY_LOGS(ERROR) << __func__ << ": error polling socket for I/O: " + << std::strerror(errno); + return {Exception::kIo}; + } + if ((fds_[0].revents & poll_event_) != 0) { + return {Exception::kSuccess}; + } + if ((fds_[0].revents & POLLHUP) != 0) { + NEARBY_LOGS(ERROR) << __func__ << ": socket disconnected"; + return {Exception::kIo}; + } + if ((fds_[0].revents & (POLLERR | POLLNVAL)) != 0) { + NEARBY_LOGS(ERROR) << __func__ << ": an error occured on the socket"; + return {Exception::kIo}; + } + } +} + +ExceptionOr BluetoothInputStream::Read(std::int64_t size) { + if (!fd_.isValid()) return Exception{Exception::kIo}; + + auto poller = Poller::CreateInputPoller(fd_); + + std::string buffer; + buffer.resize(size); + char *data = buffer.data(); + + size_t total_read = 0; + + while (total_read < size) { + auto result = poller.Ready(); + if (result.Raised()) return result; + + auto bytes_read = read(fd_.get(), &data[total_read], (size - total_read)); + if (bytes_read < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) continue; + NEARBY_LOGS(ERROR) << __func__ + << ": error reading data on bluetooth socket: " + << std::strerror(errno); + return {Exception::kIo}; + } + total_read += bytes_read; + } + + return ExceptionOr{ByteArray(std::move(buffer))}; +} + +Exception BluetoothInputStream::Close() { + if (!fd_.isValid()) return {Exception::kIo}; + fd_.reset(); + return {Exception::kSuccess}; +} + +Exception BluetoothOutputStream::Write(const ByteArray &data) { + if (!fd_.isValid()) return Exception{Exception::kIo}; + + auto poller = Poller::CreateOutputPoller(fd_); + + size_t total_wrote = 0; + + while (total_wrote < data.size()) { + auto result = poller.Ready(); + if (result.Raised()) return result; + + const char *buf = data.data(); + auto wrote = + write(fd_.get(), &buf[total_wrote], (data.size() - total_wrote)); + if (wrote < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) continue; + NEARBY_LOGS(ERROR) << __func__ + << ": error writing data on bluetooth socket: " + << std::strerror(errno); + return {Exception::kIo}; + } + + total_wrote += wrote; + } + + return {Exception::kSuccess}; +} + +Exception BluetoothOutputStream::Close() { + if (!fd_.isValid()) return {Exception::kIo}; + fd_.reset(); + return {Exception::kSuccess}; +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/bluetooth_classic_socket.h b/internal/platform/implementation/linux/bluetooth_classic_socket.h new file mode 100644 index 00000000..58ffa2c8 --- /dev/null +++ b/internal/platform/implementation/linux/bluetooth_classic_socket.h @@ -0,0 +1,103 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_BLUETOOTH_SOCKET_H_ +#define PLATFORM_IMPL_LINUX_BLUETOOTH_SOCKET_H_ + +#include +#include + +#include +#include +#include + +#include "internal/platform/exception.h" +#include "internal/platform/implementation/bluetooth_classic.h" +#include "internal/platform/implementation/linux/bluetooth_classic_device.h" +#include "internal/platform/input_stream.h" +#include "internal/platform/output_stream.h" + +namespace nearby { +namespace linux { +// BlueZ's NewConnection gives us a non-blocking FD, so we need to poll +// it to be able to write/read bytes. +class Poller final { + public: + static Poller CreateInputPoller(const sdbus::UnixFd &fd) { + return Poller(fd, POLLIN); + } + + static Poller CreateOutputPoller(const sdbus::UnixFd &fd) { + return Poller(fd, POLLOUT); + } + + Exception Ready(); + + private: + Poller(const sdbus::UnixFd &fd, short event) : poll_event_(event) { + fds_[0].fd = fd.get(); + fds_[0].events = event; + } + + short poll_event_; + struct pollfd fds_[1]; +}; + +class BluetoothInputStream final : public nearby::InputStream { + public: + explicit BluetoothInputStream(sdbus::UnixFd fd) : fd_(std::move(fd)){}; + + ExceptionOr Read(std::int64_t size) override; + Exception Close() override; + + private: + sdbus::UnixFd fd_; +}; + +class BluetoothOutputStream : public nearby::OutputStream { + public: + explicit BluetoothOutputStream(sdbus::UnixFd fd) : fd_(std::move(fd)){}; + + Exception Write(const ByteArray &data) override; + Exception Flush() override { return {Exception::kSuccess}; } + Exception Close() override; + + private: + sdbus::UnixFd fd_; +}; + +class BluetoothSocket final : public api::BluetoothSocket { + public: + BluetoothSocket(std::shared_ptr device, + const sdbus::UnixFd &fd) + : device_(std::move(device)), output_stream_(fd), input_stream_(fd) {} + + nearby::InputStream &GetInputStream() override { return input_stream_; } + nearby::OutputStream &GetOutputStream() override { return output_stream_; } + Exception Close() override { + input_stream_.Close(); + output_stream_.Close(); + + return Exception{Exception::kSuccess}; + } + api::BluetoothDevice *GetRemoteDevice() override { return device_.get(); }; + + private: + std::shared_ptr device_; + BluetoothOutputStream output_stream_; + BluetoothInputStream input_stream_; +}; +} // namespace linux +} // namespace nearby +#endif diff --git a/internal/platform/implementation/linux/bluetooth_devices.cc b/internal/platform/implementation/linux/bluetooth_devices.cc new file mode 100644 index 00000000..05058c97 --- /dev/null +++ b/internal/platform/implementation/linux/bluetooth_devices.cc @@ -0,0 +1,192 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include + +#include +#include + +#include "absl/strings/substitute.h" +#include "absl/synchronization/mutex.h" +#include "internal/platform/implementation/linux/bluetooth_classic_device.h" +#include "internal/platform/implementation/linux/bluetooth_devices.h" +#include "internal/platform/implementation/linux/bluez.h" +#include "internal/platform/implementation/linux/dbus.h" +#include "internal/platform/implementation/linux/generated/dbus/bluez/device_client.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { +static constexpr std::chrono::minutes kLostPeripheralsCleanupMinFreq(5); + +std::shared_ptr BluetoothDevices::get_device_by_path( + const sdbus::ObjectPath &device_object_path) { + absl::ReaderMutexLock l(&devices_by_path_lock_); + + if (devices_by_path_.count(device_object_path) == 0) { + return nullptr; + } + + return devices_by_path_[device_object_path]; +} + +std::shared_ptr BluetoothDevices::get_device_by_address( + const std::string &addr) { + auto device_object_path = + bluez::device_object_path(adapter_object_path_, addr); + return get_device_by_path(device_object_path); +} + +void BluetoothDevices::remove_device_by_path( + const sdbus::ObjectPath &device_object_path) { + absl::MutexLock l(&devices_by_path_lock_); + + devices_by_path_.erase(device_object_path); +} + +void BluetoothDevices::mark_peripheral_lost( + const sdbus::ObjectPath &device_object_path) { + absl::ReaderMutexLock lock(&devices_by_path_lock_); + if (devices_by_path_.count(device_object_path) == 0) { + NEARBY_LOGS(ERROR) << __func__ << ": Device " << device_object_path + << " doesn't exist"; + return; + } + devices_by_path_[device_object_path]->MarkLost(); +} + +void BluetoothDevices::cleanup_lost_peripherals() { + auto now = std::chrono::steady_clock::now(); + absl::MutexLock lock(&devices_by_path_lock_); + if ((now - last_cleanup_) < kLostPeripheralsCleanupMinFreq) { + return; + } + last_cleanup_ = now; + + for (auto it = devices_by_path_.begin(), end = devices_by_path_.end(); + it != end;) { + auto copy = it++; + if (copy->second->Lost()) devices_by_path_.erase(copy); + } +} + +std::shared_ptr BluetoothDevices::add_new_device( + sdbus::ObjectPath device_object_path) { + absl::MutexLock l(&devices_by_path_lock_); + auto [device_it, inserted] = devices_by_path_.emplace( + std::string(device_object_path), + std::make_shared( + system_bus_, + std::make_shared(system_bus_, device_object_path), + observers_)); + if (!inserted) device_it->second->UnmarkLost(); + return device_it->second; +} + +void DeviceWatcher::onInterfacesAdded( + const sdbus::ObjectPath &object, + const std::map> + &interfaces) { + auto path_prefix = absl::Substitute("$0/dev_", adapter_object_path_); + if (object.find(path_prefix) != 0) { + return; + } + + if (interfaces.count(org::bluez::Device1_proxy::INTERFACE_NAME) == 0) return; + + auto device = devices_->add_new_device(object); + device->SetDiscoveryCallback(discovery_cb_); + if (discovery_cb_ != nullptr && + discovery_cb_->device_discovered_cb != nullptr) { + discovery_cb_->device_discovered_cb(*device); + } + + if (observers_ != nullptr) { + for (const auto &observer : observers_->GetObservers()) { + observer->DeviceAdded(*device); + } + } +} + +void DeviceWatcher::onInterfacesRemoved( + const sdbus::ObjectPath &object, + const std::vector &interfaces) { + auto path_prefix = absl::Substitute("$0/dev_", adapter_object_path_); + if (object.find(path_prefix) != 0) { + return; + } + + auto removed_device_it = std::find(interfaces.begin(), interfaces.end(), + org::bluez::Device1_proxy::INTERFACE_NAME); + if (removed_device_it != interfaces.end()) { + auto device = devices_->get_device_by_path(object); + if (device == nullptr) { + NEARBY_LOGS(WARNING) << __func__ + << ": received InterfacesRemoved for a device " + "we don't know about: " + << object; + return; + } + + NEARBY_LOGS(INFO) << __func__ << ": Device " << object + << " has been removed"; + if (discovery_cb_ != nullptr && discovery_cb_->device_lost_cb != nullptr) { + discovery_cb_->device_lost_cb(*device); + } + + if (observers_ != nullptr) { + for (const auto &observer : observers_->GetObservers()) { + observer->DeviceRemoved(*device); + } + devices_->remove_device_by_path(object); + } else { + devices_->mark_peripheral_lost(object); + } + } +} + +void DeviceWatcher::notifyExistingDevices() { + std::map>> + objects; + try { + objects = GetManagedObjects(); + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(this, "GetManagedObjects", e); + return; + } + auto device_it = + std::find_if(objects.begin(), objects.end(), [&](auto entry) { + auto &[device_path, interfaces] = entry; + + return device_path.find( + absl::Substitute("$0/dev_", adapter_object_path_)) == 0 && + interfaces.count(org::bluez::Device1_proxy::INTERFACE_NAME) == 1; + }); + + for (; device_it != objects.end(); device_it++) { + NEARBY_LOGS(VERBOSE) << __func__ << ": Adding existing device " + << device_it->first; + auto device = devices_->add_new_device(device_it->first); + if (discovery_cb_ != nullptr) { + device->SetDiscoveryCallback(discovery_cb_); + } + } +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/bluetooth_devices.h b/internal/platform/implementation/linux/bluetooth_devices.h new file mode 100644 index 00000000..08c4d24a --- /dev/null +++ b/internal/platform/implementation/linux/bluetooth_devices.h @@ -0,0 +1,127 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_BLUETOOTH_DEVICES_H_ +#define PLATFORM_IMPL_LINUX_BLUETOOTH_DEVICES_H_ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "absl/container/flat_hash_map.h" +#include "absl/synchronization/mutex.h" +#include "internal/base/observer_list.h" +#include "internal/platform/bluetooth_utils.h" +#include "internal/platform/implementation/bluetooth_classic.h" +#include "internal/platform/implementation/linux/bluetooth_classic_device.h" + +namespace nearby { +namespace linux { +class BluetoothDevices final { + public: + BluetoothDevices( + std::shared_ptr system_bus, + sdbus::ObjectPath adapter_object_path, + ObserverList &observers) + : system_bus_(std::move(system_bus)), + observers_(observers), + adapter_object_path_(std::move(adapter_object_path)) {} + + std::shared_ptr get_device_by_path(const sdbus::ObjectPath &) + ABSL_LOCKS_EXCLUDED(devices_by_path_lock_); + std::shared_ptr get_device_by_address(const std::string &); + std::shared_ptr get_device_by_unique_id( + api::ble_v2::BlePeripheral::UniqueId id) { + auto addr = BluetoothUtils::FromNumber(id); + return get_device_by_address(addr); + } + + std::shared_ptr add_new_device(sdbus::ObjectPath) + ABSL_LOCKS_EXCLUDED(devices_by_path_lock_); + + void remove_device_by_path(const sdbus::ObjectPath &) + ABSL_LOCKS_EXCLUDED(devices_by_path_lock_); + void mark_peripheral_lost(const sdbus::ObjectPath &) + ABSL_LOCKS_EXCLUDED(devices_by_path_lock_); + void cleanup_lost_peripherals() ABSL_LOCKS_EXCLUDED(devices_by_path_lock_); + + private: + std::shared_ptr system_bus_; + ObserverList &observers_; + sdbus::ObjectPath adapter_object_path_; + + absl::Mutex devices_by_path_lock_; + absl::flat_hash_map> + devices_by_path_ ABSL_GUARDED_BY(devices_by_path_lock_); + std::chrono::time_point last_cleanup_ + ABSL_GUARDED_BY(devices_by_path_lock_); +}; + +class DeviceWatcher final : sdbus::ProxyInterfaces { + public: + DeviceWatcher(const DeviceWatcher &) = delete; + DeviceWatcher(DeviceWatcher &&) = delete; + DeviceWatcher &operator=(const DeviceWatcher &) = delete; + DeviceWatcher &operator=(DeviceWatcher &&) = delete; + + DeviceWatcher( + sdbus::IConnection &system_bus, + const sdbus::ObjectPath &adapter_object_path, + std::shared_ptr devices, + std::unique_ptr + discovery_callback, + std::shared_ptr> + observers) + : ProxyInterfaces(system_bus, "org.bluez", "/"), + adapter_object_path_(adapter_object_path), + devices_(std::move(devices)), + discovery_cb_(std::move(discovery_callback)), + observers_(std::move(observers)) { + notifyExistingDevices(); + registerProxy(); + } + DeviceWatcher(sdbus::IConnection &system_bus, + const sdbus::ObjectPath &adapter_object_path, + std::shared_ptr devices) + : DeviceWatcher(system_bus, adapter_object_path, std::move(devices), + nullptr, nullptr) {} + ~DeviceWatcher() { unregisterProxy(); } + + void onInterfacesAdded( + const sdbus::ObjectPath &object, + const std::map> + &interfaces) override; + void onInterfacesRemoved(const sdbus::ObjectPath &object, + const std::vector &interfaces) override; + + private: + void notifyExistingDevices(); + + sdbus::ObjectPath adapter_object_path_; + std::shared_ptr devices_; + std::shared_ptr discovery_cb_; + std::shared_ptr> + observers_; +}; + +} // namespace linux +} // namespace nearby + +#endif diff --git a/internal/platform/implementation/linux/bluetooth_pairing.cc b/internal/platform/implementation/linux/bluetooth_pairing.cc new file mode 100644 index 00000000..0e51961c --- /dev/null +++ b/internal/platform/implementation/linux/bluetooth_pairing.cc @@ -0,0 +1,105 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include +#include +#include + +#include "internal/platform/implementation/bluetooth_classic.h" +#include "internal/platform/implementation/linux/bluetooth_adapter.h" +#include "internal/platform/implementation/linux/bluetooth_pairing.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { + +void BluetoothPairing::pairing_reply_handler(const sdbus::Error *error) { + if (error != nullptr && error->isValid()) { + const auto &name = error->getName(); + api::BluetoothPairingCallback::PairingError err = + api::BluetoothPairingCallback::PairingError::kAuthFailed; + + NEARBY_LOGS(ERROR) << __func__ << ": " + << "Got error '" << error->getName() + << "' with message '" << error->getMessage() + << "' while pairing with device " + << device_->GetMacAddress(); + + if (name == "org.bluez.Error.AuthenticationCanceled") { + err = api::BluetoothPairingCallback::PairingError::kAuthCanceled; + } else if (name == "org.bluez.Error.AuthenticationFailed") { + err = api::BluetoothPairingCallback::PairingError::kAuthFailed; + } else if (name == "org.bluez.Error.AuthenticationRejected") { + err = api::BluetoothPairingCallback::PairingError::kAuthRejected; + } else if (name == "org.bluez.Error.AuthenticationTimeout") { + err = api::BluetoothPairingCallback::PairingError::kAuthTimeout; + } + + if (pairing_cb_.on_pairing_error_cb != nullptr) { + pairing_cb_.on_pairing_error_cb(err); + } + + return; + } + if (pairing_cb_.on_paired_cb != nullptr) { + pairing_cb_.on_paired_cb(); + } +} + +BluetoothPairing::BluetoothPairing( + BluetoothAdapter &adapter, std::shared_ptr remote_device) + : device_(std::move(remote_device)), + device_object_path_(bluez::device_object_path(adapter.GetObjectPath(), + device_->GetAddress())), + adapter_(adapter) {} + +bool BluetoothPairing::InitiatePairing( + api::BluetoothPairingCallback pairing_cb) { + pairing_cb_ = std::move(pairing_cb); + if (pairing_cb_.on_pairing_initiated_cb != nullptr) + pairing_cb_.on_pairing_initiated_cb(api::PairingParams{ + api::PairingParams::PairingType::kConsent, std::string()}); + + return true; +} + +bool BluetoothPairing::FinishPairing( + std::optional pin_code) { + device_->SetPairReplyCallback([this](const sdbus::Error *error) { + this->pairing_reply_handler(error); + }); + + auto call = device_->Pair(); + if (!call.has_value()) return false; + pair_async_call_ = *call; + return true; +} + +bool BluetoothPairing::CancelPairing() { + if (pair_async_call_.isPending()) { + pair_async_call_.cancel(); + } + + return device_->CancelPairing(); +} + +bool BluetoothPairing::Unpair() { + return adapter_.RemoveDeviceByObjectPath(device_object_path_); +} + +bool BluetoothPairing::IsPaired() { return device_->Bonded(); } +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/bluetooth_pairing.h b/internal/platform/implementation/linux/bluetooth_pairing.h new file mode 100644 index 00000000..0f7cc51d --- /dev/null +++ b/internal/platform/implementation/linux/bluetooth_pairing.h @@ -0,0 +1,57 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_BLUETOOTH_PAIRING_H_ +#define PLATFORM_IMPL_LINUX_BLUETOOTH_PAIRING_H_ + +#include +#include + +#include +#include +#include +#include + +#include "absl/strings/string_view.h" +#include "internal/platform/implementation/linux/bluetooth_adapter.h" +#include "internal/platform/implementation/linux/bluetooth_classic_device.h" + +namespace nearby { +namespace linux { +class BluetoothPairing final : public api::BluetoothPairing { + public: + BluetoothPairing(BluetoothAdapter &adapter, + std::shared_ptr remote_device); + + bool InitiatePairing(api::BluetoothPairingCallback pairing_cb) override; + bool FinishPairing(std::optional pin_code) override; + bool CancelPairing() override; + bool Unpair() override; + bool IsPaired() override; + + private: + void pairing_reply_handler(const sdbus::Error *e); + + sdbus::PendingAsyncCall pair_async_call_; + + std::shared_ptr device_; + sdbus::ObjectPath device_object_path_; + linux::BluetoothAdapter adapter_; + + api::BluetoothPairingCallback pairing_cb_; +}; +} // namespace linux +} // namespace nearby + +#endif diff --git a/internal/platform/implementation/linux/bluez.cc b/internal/platform/implementation/linux/bluez.cc new file mode 100644 index 00000000..dd4755c1 --- /dev/null +++ b/internal/platform/implementation/linux/bluez.cc @@ -0,0 +1,79 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "absl/strings/str_replace.h" +#include "absl/strings/string_view.h" +#include "absl/strings/substitute.h" +#include "internal/platform/implementation/linux/bluez.h" + +namespace nearby { +namespace linux { +namespace bluez { +std::string device_object_path(const sdbus::ObjectPath &adapter_object_path, + absl::string_view mac_address) { + return absl::Substitute( + "$0/dev_$1", adapter_object_path, + absl::StrReplaceAll(absl::AsciiStrToUpper(mac_address), {{":", "_"}})); +} + +sdbus::ObjectPath profile_object_path(absl::string_view service_uuid) { + return absl::Substitute( + "/com/google/nearby/medium/bluetooth_classic/profiles/$0", + absl::StrReplaceAll(service_uuid, {{"-", "_"}})); +} + +sdbus::ObjectPath adapter_object_path(absl::string_view name) { + return absl::Substitute("/org/bluez/$0", name); +} + +sdbus::ObjectPath gatt_service_path(size_t num) { + return absl::Substitute("$0/service$1", NEARBY_BLE_GATT_PATH_ROOT, num); +} + +sdbus::ObjectPath gatt_characteristic_path( + const sdbus::ObjectPath &service_path, size_t num) { + return absl::Substitute("$0/char$1", service_path, num); +} + +sdbus::ObjectPath ble_advertisement_path(size_t num) { + return absl::Substitute("/com/google/nearby/medium/ble/advertisement/$0", + num); +} + +sdbus::ObjectPath advertisement_monitor_path(absl::string_view uuid) { + return absl::Substitute( + "/com/google/nearby/medium/ble/advertisement/monitor/$0", + absl::StrReplaceAll(uuid, {{"-", "_"}})); +} + +int16_t TxPowerLevelDbm(api::ble_v2::TxPowerLevel level) { + switch (level) { + case api::ble_v2::TxPowerLevel::kUnknown: + return 0; + case api::ble_v2::TxPowerLevel::kUltraLow: + return -3; + case api::ble_v2::TxPowerLevel::kLow: + return 0; + case api::ble_v2::TxPowerLevel::kMedium: + return 3; + case api::ble_v2::TxPowerLevel::kHigh: + return 6; + } +} + +} // namespace bluez +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/bluez.h b/internal/platform/implementation/linux/bluez.h new file mode 100644 index 00000000..2d595a67 --- /dev/null +++ b/internal/platform/implementation/linux/bluez.h @@ -0,0 +1,86 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_BLUEZ_H_ +#define PLATFORM_IMPL_LINUX_BLUEZ_H_ + +#include +#include +#include + +#include "absl/strings/string_view.h" +#include "internal/platform/implementation/ble_v2.h" + +#include + +#define BLUEZ_LOG_METHOD_CALL_ERROR(proxy, method, err) \ + do { \ + NEARBY_LOGS(ERROR) << __func__ << ": Got error '" << (err).getName() \ + << "' with message '" << (err).getMessage() \ + << "' while calling " << method << " on object " \ + << (proxy)->getObjectPath(); \ + } while (false) + +namespace nearby { +namespace linux { +namespace bluez { +static constexpr const char *SERVICE_DEST = "org.bluez"; + +static constexpr const char *ADAPTER_INTERFACE = "org.bluez.Adapter1"; + +static constexpr const char *DEVICE_INTERFACE = "org.bluez.Device1"; +static constexpr const char *DEVICE_PROP_ADDRESS = "Address"; +static constexpr const char *DEVICE_PROP_ALIAS = "Alias"; +static constexpr const char *DEVICE_PROP_PAIRED = "Paired"; +static constexpr const char *DEVICE_PROP_CONNECTED = "Connected"; +static constexpr const char *DEVICE_NAME = "Name"; + +static constexpr const char *NEARBY_BLE_GATT_PATH_ROOT = + "/com/google/nearby/medium/ble/gatt"; + +std::string device_object_path(const sdbus::ObjectPath &adapter_object_path, + absl::string_view mac_address); +sdbus::ObjectPath profile_object_path(absl::string_view service_uuid); +sdbus::ObjectPath adapter_object_path(absl::string_view name); +sdbus::ObjectPath gatt_service_path(size_t num); +sdbus::ObjectPath gatt_characteristic_path( + const sdbus::ObjectPath &service_path, size_t num); +sdbus::ObjectPath ble_advertisement_path(size_t num); +sdbus::ObjectPath advertisement_monitor_path(absl::string_view uuid); +int16_t TxPowerLevelDbm(api::ble_v2::TxPowerLevel level); + +class BluezObjectManager + : public sdbus::ProxyInterfaces { + public: + explicit BluezObjectManager(sdbus::IConnection &system_bus) + : ProxyInterfaces(system_bus, "org.bluez", "/") { + registerProxy(); + } + virtual ~BluezObjectManager() { unregisterProxy(); } + + protected: + void onInterfacesAdded( + const sdbus::ObjectPath &objectPath, + const std::map> + &interfacesAndProperties) override {} + void onInterfacesRemoved( + const sdbus::ObjectPath &objectPath, + const std::vector &interfaces) override {} +}; + +} // namespace bluez +} // namespace linux +} // namespace nearby + +#endif diff --git a/internal/platform/implementation/linux/bluez_advertisement_monitor.cc b/internal/platform/implementation/linux/bluez_advertisement_monitor.cc new file mode 100644 index 00000000..cdb7d04a --- /dev/null +++ b/internal/platform/implementation/linux/bluez_advertisement_monitor.cc @@ -0,0 +1,68 @@ +#include "internal/platform/implementation/linux/bluez_advertisement_monitor.h" +#include "internal/platform/byte_array.h" +#include "internal/platform/implementation/ble_v2.h" +#include "internal/platform/implementation/linux/dbus.h" +#include "internal/platform/implementation/linux/utils.h" +#include "internal/platform/uuid.h" +namespace nearby { +namespace linux { +namespace bluez { +AdvertisementMonitor::AdvertisementMonitor( + sdbus::IConnection &system_bus, Uuid service_uuid, + api::ble_v2::TxPowerLevel tx_power_level, absl::string_view type, + std::shared_ptr devices, + api::ble_v2::BleMedium::ScanCallback scan_callback) + : AdvertisementMonitor( + system_bus, service_uuid, tx_power_level, type, std::move(devices), + api::ble_v2::BleMedium::ScanningCallback{ + .start_scanning_result = nullptr, + .advertisement_found_cb = + std::move(scan_callback.advertisement_found_cb)}) {} + +AdvertisementMonitor::AdvertisementMonitor( + sdbus::IConnection &system_bus, Uuid service_uuid, + api::ble_v2::TxPowerLevel tx_power_level, absl::string_view type, + std::shared_ptr devices, + api::ble_v2::BleMedium::ScanningCallback scan_callback) + : AdaptorInterfaces(system_bus, bluez::advertisement_monitor_path( + std::string{service_uuid})), + devices_(std::move(devices)), + scan_callback_{std::move(scan_callback.advertisement_found_cb)}, + start_scanning_result_callback_( + std::move(scan_callback.start_scanning_result)), + type_(type), + service_uuid_(service_uuid), + tx_power_level_(tx_power_level) { + registerAdaptor(); +} + +void AdvertisementMonitor::DeviceFound(const sdbus::ObjectPath &device) { + devices_->cleanup_lost_peripherals(); + auto peripheral = devices_->add_new_device(device); + auto service_data = peripheral->ServiceData(); + if (!service_data.has_value()) return; + + struct api::ble_v2::BleAdvertisementData adv_data; + for (const auto &[uuid_str, data] : *service_data) { + auto uuid = UuidFromString(uuid_str); + if (!uuid.has_value()) { + NEARBY_LOGS(ERROR) + << __func__ + << ": Could not parse UUID string in ServiceData for peripheral " + << peripheral->getObjectPath(); + continue; + } + + std::vector bytes = data; + adv_data.service_data.emplace(*uuid, + std::string(bytes.begin(), bytes.end())); + } + scan_callback_.advertisement_found_cb(*peripheral, adv_data); +} + +void AdvertisementMonitor::DeviceLost(const sdbus::ObjectPath &device) { + devices_->mark_peripheral_lost(device); +} +} // namespace bluez +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/bluez_advertisement_monitor.h b/internal/platform/implementation/linux/bluez_advertisement_monitor.h new file mode 100644 index 00000000..24e57696 --- /dev/null +++ b/internal/platform/implementation/linux/bluez_advertisement_monitor.h @@ -0,0 +1,95 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_BLUEZ_ADVERTISEMENT_MONITOR_H_ +#define PLATFORM_IMPL_LINUX_BLUEZ_ADVERTISEMENT_MONITOR_H_ +#include +#include +#include + +#include "absl/strings/string_view.h" +#include "internal/platform/implementation/ble_v2.h" +#include "internal/platform/implementation/linux/bluetooth_devices.h" +#include "internal/platform/implementation/linux/bluez.h" +#include "internal/platform/implementation/linux/generated/dbus/bluez/advertisement_monitor_server.h" +#include "internal/platform/uuid.h" + +namespace nearby { +namespace linux { +namespace bluez { +class AdvertisementMonitor final + : public sdbus::AdaptorInterfaces { + public: + AdvertisementMonitor(const AdvertisementMonitor&) = delete; + AdvertisementMonitor(AdvertisementMonitor&&) = delete; + AdvertisementMonitor& operator=(const AdvertisementMonitor&) = delete; + AdvertisementMonitor& operator=(AdvertisementMonitor&&) = delete; + + AdvertisementMonitor(sdbus::IConnection& system_bus, Uuid service_uuid, + api::ble_v2::TxPowerLevel tx_power_level, + absl::string_view type, + std::shared_ptr devices, + api::ble_v2::BleMedium::ScanCallback scan_callback); + AdvertisementMonitor(sdbus::IConnection& system_bus, Uuid service_uuid, + api::ble_v2::TxPowerLevel tx_power_level, + absl::string_view type, + std::shared_ptr devices, + api::ble_v2::BleMedium::ScanningCallback scan_callback); + ~AdvertisementMonitor() { unregisterAdaptor(); } + + private: + // Methods + void Release() override {} + void Activate() override { + if (start_scanning_result_callback_ != nullptr) { + start_scanning_result_callback_(absl::OkStatus()); + } + } + + void DeviceFound(const sdbus::ObjectPath& device) override; + void DeviceLost(const sdbus::ObjectPath& device) override; + + // Properties + std::string Type() override { return type_; }; + int16_t RSSILowThreshold() override { return 0; }; + int16_t RSSIHighThreshold() override { + return bluez::TxPowerLevelDbm(tx_power_level_); + } + uint16_t RSSISamplingPeriod() override { + // The Windows implementation uses a sampling interval of 2 seconds. + return 20; + } + std::vector>> Patterns() + override { + std::array service_id_data = service_uuid_.data(); + return {{0, + 0x16, + {static_cast(service_id_data[3] & 0xFF), + static_cast(service_id_data[2] & 0xFF)}}}; + }; + + std::shared_ptr devices_; + api::ble_v2::BleMedium::ScanCallback scan_callback_; + absl::AnyInvocable start_scanning_result_callback_; + + std::string type_; + Uuid service_uuid_; + api::ble_v2::TxPowerLevel tx_power_level_; +}; +} // namespace bluez +} // namespace linux +} // namespace nearby + +#endif diff --git a/internal/platform/implementation/linux/bluez_advertisement_monitor_manager.h b/internal/platform/implementation/linux/bluez_advertisement_monitor_manager.h new file mode 100644 index 00000000..e8ed5d57 --- /dev/null +++ b/internal/platform/implementation/linux/bluez_advertisement_monitor_manager.h @@ -0,0 +1,87 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_BLUEZ_ADVERTISEMENT_MONITOR_MANAGER_H_ +#define PLATFORM_IMPL_LINUX_BLUEZ_ADVERTISEMENT_MONITOR_MANAGER_H_ + +#include +#include + +#include "internal/platform/implementation/linux/bluetooth_adapter.h" +#include "internal/platform/implementation/linux/bluez.h" +#include "internal/platform/implementation/linux/dbus.h" +#include "internal/platform/implementation/linux/generated/dbus/bluez/advertisement_monitor_manager_client.h" + +namespace nearby { +namespace linux { +namespace bluez { +class AdvertisementMonitorManager final + : public sdbus::ProxyInterfaces< + org::bluez::AdvertisementMonitorManager1_proxy> { + private: + friend std::unique_ptr + std::make_unique(sdbus::IConnection &, + const BluetoothAdapter &); + AdvertisementMonitorManager(sdbus::IConnection &system_bus, + const BluetoothAdapter &adapter) + : ProxyInterfaces(system_bus, "org.bluez", adapter.GetObjectPath()) { + registerProxy(); + } + + public: + AdvertisementMonitorManager(const AdvertisementMonitorManager &) = delete; + AdvertisementMonitorManager(AdvertisementMonitorManager &&) = delete; + AdvertisementMonitorManager &operator=(const AdvertisementMonitorManager &) = + delete; + AdvertisementMonitorManager &operator=(AdvertisementMonitorManager &&) = + delete; + ~AdvertisementMonitorManager() { unregisterProxy(); } + + static std::unique_ptr + DiscoverAdvertisementMonitorManager(sdbus::IConnection &system_bus, + const BluetoothAdapter &adapter) { + bluez::BluezObjectManager manager(system_bus); + std::map>> + objects; + try { + objects = manager.GetManagedObjects(); + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(&manager, "GetManagedObjects", e); + return nullptr; + } + if (objects.count(adapter.GetObjectPath()) == 0) { + NEARBY_LOGS(ERROR) << __func__ << ": Adapter object no longer exists " + << adapter.GetObjectPath(); + return nullptr; + } + + if (objects[adapter.GetObjectPath()].count( + org::bluez::AdvertisementMonitorManager1_proxy::INTERFACE_NAME) == + 0) { + NEARBY_LOGS(ERROR) + << __func__ << ": Adapter " << adapter.GetObjectPath() + << " doesn't provide " + << org::bluez::AdvertisementMonitorManager1_proxy::INTERFACE_NAME; + return nullptr; + } + + return std::make_unique(system_bus, adapter); + } +}; +} // namespace bluez +} // namespace linux +} // namespace nearby + +#endif diff --git a/internal/platform/implementation/linux/bluez_device.h b/internal/platform/implementation/linux/bluez_device.h new file mode 100644 index 00000000..f419cbf0 --- /dev/null +++ b/internal/platform/implementation/linux/bluez_device.h @@ -0,0 +1,65 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_BLUEZ_DEVICE_H_ +#define PLATFORM_IMPL_LINUX_BLUEZ_DEVICE_H_ +#include +#include +#include + +#include "absl/functional/any_invocable.h" +#include "absl/synchronization/mutex.h" +#include "internal/platform/implementation/linux/generated/dbus/bluez/device_client.h" + +namespace nearby { +namespace linux { +namespace bluez { +class Device : public sdbus::ProxyInterfaces { + public: + Device(std::shared_ptr system_bus, + const sdbus::ObjectPath &device_path) + : ProxyInterfaces(*system_bus, "org.bluez", device_path), + system_bus(std::move(system_bus)) { + registerProxy(); + } + ~Device() { unregisterProxy(); } + + void SetPairReplyCallback(absl::AnyInvocable cb) + ABSL_LOCKS_EXCLUDED(pair_callback_lock_) { + absl::MutexLock l(&pair_callback_lock_); + on_pair_reply_cb_ = std::move(cb); + } + + void ResetPairReplyCallback() ABSL_LOCKS_EXCLUDED(pair_callback_lock_) { + absl::MutexLock l(&pair_callback_lock_); + on_pair_reply_cb_ = nullptr; + } + + protected: + void onPairReply(const sdbus::Error *error) override + ABSL_LOCKS_EXCLUDED(pair_callback_lock_) { + absl::ReaderMutexLock l(&pair_callback_lock_); + if (on_pair_reply_cb_ != nullptr) on_pair_reply_cb_(error); + }; + + private: + std::shared_ptr system_bus; + absl::Mutex pair_callback_lock_; + absl::AnyInvocable on_pair_reply_cb_ + ABSL_GUARDED_BY(pair_callback_lock_) = nullptr; +}; +} // namespace bluez +} // namespace linux +} // namespace nearby +#endif diff --git a/internal/platform/implementation/linux/bluez_gatt_characteristic_client.cc b/internal/platform/implementation/linux/bluez_gatt_characteristic_client.cc new file mode 100644 index 00000000..19e4b7c5 --- /dev/null +++ b/internal/platform/implementation/linux/bluez_gatt_characteristic_client.cc @@ -0,0 +1,41 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include "internal/platform/implementation/linux/bluez_gatt_characteristic_client.h" +#include "internal/platform/implementation/linux/generated/dbus/bluez/gatt_characteristic_client.h" +namespace nearby { +namespace linux { +namespace bluez { +void SubscribedGattCharacteristicClient::onPropertiesChanged( + const std::string& interfaceName, + const std::map& changedProperties, + const std::vector& invalidatedProperties) { + if (interfaceName != org::bluez::GattCharacteristic1_proxy::INTERFACE_NAME) + return; + + if (changedProperties.count("Value") == 1) { + std::vector value_bytes = changedProperties.at("Value"); + if (notify_callback_ != nullptr) { + auto value = std::string(value_bytes.cbegin(), value_bytes.cend()); + notify_callback_(value); + } + } +} +} // namespace bluez +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/bluez_gatt_characteristic_client.h b/internal/platform/implementation/linux/bluez_gatt_characteristic_client.h new file mode 100644 index 00000000..38b731cb --- /dev/null +++ b/internal/platform/implementation/linux/bluez_gatt_characteristic_client.h @@ -0,0 +1,71 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_BLUEZ_GATT_CHARACTERISTIC_CLIENT_H_ +#define PLATFORM_IMPL_LINUX_BLUEZ_GATT_CHARACTERISTIC_CLIENT_H_ + +#include +#include +#include + +#include "absl/functional/any_invocable.h" +#include "absl/strings/string_view.h" +#include "internal/platform/implementation/linux/dbus.h" +#include "internal/platform/implementation/linux/generated/dbus/bluez/gatt_characteristic_client.h" +namespace nearby { +namespace linux { +namespace bluez { +class GattCharacteristicClient + : public sdbus::ProxyInterfaces { + public: + GattCharacteristicClient(std::shared_ptr system_bus, + sdbus::ObjectPath path) + : ProxyInterfaces(*system_bus, "org.bluez", std::move(path)), + system_bus_(std::move(system_bus)) { + registerProxy(); + } + virtual ~GattCharacteristicClient() { unregisterProxy(); } + + protected: + void onPropertiesChanged( + const std::string& interfaceName, + const std::map& changedProperties, + const std::vector& invalidatedProperties) override {} + + std::shared_ptr system_bus_; +}; + +class SubscribedGattCharacteristicClient : public GattCharacteristicClient { + public: + SubscribedGattCharacteristicClient( + std::shared_ptr system_bus, sdbus::ObjectPath path, + absl::AnyInvocable notify_callback) + : GattCharacteristicClient(std::move(system_bus), std::move(path)), + notify_callback_(std::move(notify_callback)) {} + + protected: + void onPropertiesChanged( + const std::string& interfaceName, + const std::map& changedProperties, + const std::vector& invalidatedProperties) override; + + private: + absl::AnyInvocable notify_callback_; +}; +} // namespace bluez +} // namespace linux +} // namespace nearby + +#endif diff --git a/internal/platform/implementation/linux/bluez_gatt_characteristic_server.cc b/internal/platform/implementation/linux/bluez_gatt_characteristic_server.cc new file mode 100644 index 00000000..f6404283 --- /dev/null +++ b/internal/platform/implementation/linux/bluez_gatt_characteristic_server.cc @@ -0,0 +1,238 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include "internal/platform/byte_array.h" +#include "internal/platform/implementation/ble_v2.h" +#include "internal/platform/implementation/linux/bluez_gatt_characteristic_server.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { +namespace bluez { +void GattCharacteristicServer::Update(const nearby::ByteArray &value) { + std::vector bytes(value.size()); + const auto *buf = value.data(); + for (auto i = 0; i < value.size(); i++) bytes[i] = buf[i]; + + absl::MutexLock static_value_lock(&static_value_mutex_); + static_value_ = std::move(bytes); +} + +absl::Status GattCharacteristicServer::NotifyChanged( + bool confirm, const ByteArray &new_value) { + std::vector bytes(new_value.size()); + const auto *buf = new_value.data(); + for (auto i = 0; i < new_value.size(); i++) bytes[i] = buf[i]; + + { + absl::MutexLock lock(&cached_value_mutex_); + cached_value_ = bytes; + } + + if (confirm) { + auto confirmed = [&]() { + confirmed_mutex_.AssertReaderHeld(); + return confirmed_; + }; + { + absl::MutexLock lock(&confirmed_mutex_); + confirmed_ = false; + } + absl::ReaderMutexLock lock(&confirmed_mutex_, absl::Condition(&confirmed)); + } + + try { + emitPropertiesChangedSignal(GattCharacteristic1_adaptor::INTERFACE_NAME, + {"Value"}); + return absl::OkStatus(); + } catch (const sdbus::Error &e) { + NEARBY_LOGS(ERROR) << __func__ + << ": Error emitting PropertiesChanged signal on " + << getObjectPath() << " with name '" << e.getName() + << "' and message '" << e.getMessage() << "'"; + return absl::UnknownError(e.getMessage()); + } +} + +void GattCharacteristicServer::ReadValue( + sdbus::Result> &&result, + std::map options) { + { + absl::ReaderMutexLock static_value_lock(&static_value_mutex_); + if (static_value_.has_value()) { + result.returnResults(*static_value_); + + absl::MutexLock cached_value_lock(&cached_value_mutex_); + cached_value_ = *static_value_; + return; + } + } + + uint16_t offset = options["offset"]; + sdbus::ObjectPath device_path = options["device"]; + + auto device = devices_->get_device_by_path(device_path); + if (device == nullptr) { + result.returnError( + sdbus::Error("org.bluez.Error.NotAuthorized", "device does not exist")); + return; + } + auto characteristic = characteristic_; + server_cb_->on_characteristic_read_cb( + *device, characteristic, static_cast(offset), + [result = std::move(result), + this](absl::StatusOr data) { + const auto &status = data.status(); + if (status.ok()) { + auto str = data.value(); + std::vector bytes(str.size()); + for (auto i = 0; i < str.size(); i++) { + bytes[i] = str[i]; + } + result.returnResults(bytes); + + absl::MutexLock lock(&cached_value_mutex_); + cached_value_ = bytes; + } else if (absl::IsPermissionDenied(status)) { + result.returnError(sdbus::Error("org.bluez.Error.NotPermitted", + std::string(status.message()))); + } else if (absl::IsUnauthenticated(status)) { + result.returnError(sdbus::Error("org.bluez.Error.NotAuthorized", + std::string(status.message()))); + } else if (absl::IsOutOfRange(status)) { + result.returnError(sdbus::Error("org.bluez.Error.InvalidOffset", + std::string(status.message()))); + } else if (absl::IsUnimplemented(status)) { + result.returnError(sdbus::Error("org.bluez.Error.NotSupported", + std::string(status.message()))); + } else { + result.returnError(sdbus::Error("org.bluez.Error.Failed", + std::string(status.message()))); + } + }); +} + +void GattCharacteristicServer::WriteValue( + sdbus::Result<> &&result, std::vector value, + std::map options) { + uint16_t offset = options["offset"]; + sdbus::ObjectPath device_path = options["device"]; + + auto device = devices_->get_device_by_path(device_path); + if (device == nullptr) { + result.returnError( + sdbus::Error("org.bluez.Error.NotAuthorized", "device does not exist")); + return; + } + std::string type = options["type"]; + + std::string data(value.begin(), value.end()); + auto characteristic = characteristic_; + + // TODO: Support writes without response. + server_cb_->on_characteristic_write_cb( + *device, characteristic, static_cast(offset), data, + [result = std::move(result)](absl::Status status) { + if (status.ok()) { + result.returnResults(); + } else if (absl::IsPermissionDenied(status)) { + result.returnError(sdbus::Error("org.bluez.Error.NotPermitted", + std::string(status.message()))); + } else if (absl::IsUnauthenticated(status)) { + result.returnError(sdbus::Error("org.bluez.Error.NotAuthorized", + std::string(status.message()))); + } else if (absl::IsOutOfRange(status)) { + result.returnError(sdbus::Error("org.bluez.Error.InvalidOffset", + std::string(status.message()))); + } else if (absl::IsUnimplemented(status)) { + result.returnError(sdbus::Error("org.bluez.Error.NotSupported", + std::string(status.message()))); + } else { + result.returnError(sdbus::Error("org.bluez.Error.Failed", + std::string(status.message()))); + } + }); +} + +void GattCharacteristicServer::StartNotify() { + if ((characteristic_.property | + api::ble_v2::GattCharacteristic::Property::kNotify) == + api::ble_v2::GattCharacteristic::Property::kNotify) { + if (notify_sessions_.fetch_add(1) == 0) { + if (server_cb_->characteristic_subscription_cb != nullptr) { + server_cb_->characteristic_subscription_cb(characteristic_); + } + notifying_ = true; + } + } else { + throw(sdbus::Error("org.bluez.Error.NotSupported")); + } +} + +void GattCharacteristicServer::StopNotify() { + if ((characteristic_.property | + api::ble_v2::GattCharacteristic::Property::kNotify) == + api::ble_v2::GattCharacteristic::Property::kNotify) { + if (notify_sessions_.fetch_sub(0) == 1) { + if (server_cb_->characteristic_unsubscription_cb != nullptr) { + server_cb_->characteristic_unsubscription_cb(characteristic_); + } + notifying_ = false; + } + } else { + throw(sdbus::Error("org.bluez.Error.Failed")); + } +} + +std::vector GattCharacteristicServer::Flags() { + auto characteristic = characteristic_; + std::vector flags; + + if ((characteristic.permission & + api::ble_v2::GattCharacteristic::Permission::kRead) == + api::ble_v2::GattCharacteristic::Permission::kRead || + (characteristic.property & + api::ble_v2::GattCharacteristic::Property::kRead) == + api::ble_v2::GattCharacteristic::Property::kRead) + flags.push_back("read"); + + if ((characteristic.permission & + api::ble_v2::GattCharacteristic::Permission::kWrite) == + api::ble_v2::GattCharacteristic::Permission::kWrite || + (characteristic.property & + api::ble_v2::GattCharacteristic::Property::kWrite) == + api::ble_v2::GattCharacteristic::Property::kWrite) { + flags.push_back("write"); + flags.push_back("write-without-response"); + } + + if ((characteristic.property & + api::ble_v2::GattCharacteristic::Property::kIndicate) == + api::ble_v2::GattCharacteristic::Property::kIndicate) + flags.push_back("indicate"); + + if ((characteristic.property & + api::ble_v2::GattCharacteristic::Property::kNotify) == + api::ble_v2::GattCharacteristic::Property::kNotify) + flags.push_back("notify"); + + return flags; +} +} // namespace bluez +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/bluez_gatt_characteristic_server.h b/internal/platform/implementation/linux/bluez_gatt_characteristic_server.h new file mode 100644 index 00000000..3fcd7652 --- /dev/null +++ b/internal/platform/implementation/linux/bluez_gatt_characteristic_server.h @@ -0,0 +1,129 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_BLUEZ_GATT_CHARACTERISTIC_SERVER_H_ +#define PLATFORM_IMPL_LINUX_BLUEZ_GATT_CHARACTERISTIC_SERVER_H_ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "absl/strings/string_view.h" +#include "absl/synchronization/mutex.h" +#include "internal/platform/implementation/ble_v2.h" +#include "internal/platform/implementation/linux/bluetooth_devices.h" +#include "internal/platform/implementation/linux/bluez.h" +#include "internal/platform/implementation/linux/generated/dbus/bluez/gatt_characteristic_server.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { +namespace bluez { +class GattCharacteristicServer final + : public sdbus::AdaptorInterfaces { + public: + GattCharacteristicServer(const GattCharacteristicServer &) = delete; + GattCharacteristicServer(GattCharacteristicServer &&) = delete; + GattCharacteristicServer &operator=(const GattCharacteristicServer &) = + delete; + GattCharacteristicServer &operator=(GattCharacteristicServer &&) = delete; + + GattCharacteristicServer( + sdbus::IConnection &system_bus, + const sdbus::ObjectPath &service_object_path, size_t num, + const api::ble_v2::GattCharacteristic &characteristic, + std::shared_ptr server_cb, + std::shared_ptr devices) + : AdaptorInterfaces(system_bus, bluez::gatt_characteristic_path( + service_object_path, num)), + devices_(std::move(devices)), + server_cb_(std::move(server_cb)), + characteristic_(characteristic), + service_object_path_(service_object_path), + notifying_(false), + confirmed_(false), + notify_sessions_(0) { + registerAdaptor(); + NEARBY_LOGS(VERBOSE) + << __func__ << "Creating a " + << org::bluez::GattCharacteristic1_adaptor::INTERFACE_NAME + << " object at " << getObjectPath(); + } + ~GattCharacteristicServer() { unregisterAdaptor(); } + + void Update(const nearby::ByteArray &value) + ABSL_LOCKS_EXCLUDED(static_value_mutex_); + absl::Status NotifyChanged(bool confirm, const ByteArray &new_value) + ABSL_LOCKS_EXCLUDED(confirmed_mutex_); + + private: + // Methods + void ReadValue(sdbus::Result> &&result, + std::map options) override + ABSL_LOCKS_EXCLUDED(cached_value_mutex_, static_value_mutex_); + void WriteValue(sdbus::Result<> &&result, std::vector value, + std::map options) override; + void StartNotify() override; + void StopNotify() override; + void Confirm() override ABSL_LOCKS_EXCLUDED(confirmed_mutex_) { + absl::MutexLock lock(&confirmed_mutex_); + confirmed_ = true; + }; + + // Properties + std::string UUID() override { return std::string{characteristic_.uuid}; } + sdbus::ObjectPath Service() override { return service_object_path_; } + bool Notifying() override { return notifying_; } + std::vector Flags() override; + std::vector Value() override + ABSL_LOCKS_EXCLUDED(cached_value_mutex_) { + absl::ReaderMutexLock lock(&cached_value_mutex_); + return cached_value_; + } + + std::shared_ptr devices_; + std::shared_ptr server_cb_; + api::ble_v2::GattCharacteristic characteristic_; + + // Set by `GattServer::UpdateCharacteristic()` + absl::Mutex static_value_mutex_; + std::optional> static_value_ + ABSL_GUARDED_BY(static_value_mutex_); + + sdbus::ObjectPath service_object_path_; + std::atomic_bool notifying_; + absl::Mutex cached_value_mutex_; + std::vector cached_value_ ABSL_GUARDED_BY(cached_value_mutex_); + + absl::Mutex confirmed_mutex_; + bool confirmed_; + + std::atomic_size_t notify_sessions_; +}; + +} // namespace bluez +} // namespace linux +} // namespace nearby + +#endif diff --git a/internal/platform/implementation/linux/bluez_gatt_manager.h b/internal/platform/implementation/linux/bluez_gatt_manager.h new file mode 100644 index 00000000..0c6830ca --- /dev/null +++ b/internal/platform/implementation/linux/bluez_gatt_manager.h @@ -0,0 +1,44 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_API_BLUEZ_GATT_MANAGER_H_ +#define PLATFORM_IMPL_LINUX_API_BLUEZ_GATT_MANAGER_H_ +#include +#include +#include + +#include "internal/platform/implementation/linux/generated/dbus/bluez/gatt_manager_client.h" +namespace nearby { +namespace linux { +namespace bluez { +class GattManager + : public sdbus::ProxyInterfaces { + public: + GattManager(const GattManager &) = delete; + GattManager(GattManager &&) = delete; + GattManager &operator=(const GattManager &) = delete; + GattManager &operator=(GattManager &&) = delete; + + GattManager(sdbus::IConnection &system_bus, + sdbus::ObjectPath adapter_object_path) + : ProxyInterfaces(system_bus, "org.bluez", + std::move(adapter_object_path)) { + registerProxy(); + } + ~GattManager() { unregisterProxy(); } +}; +} // namespace bluez +} // namespace linux +} // namespace nearby +#endif diff --git a/internal/platform/implementation/linux/bluez_gatt_service_client.h b/internal/platform/implementation/linux/bluez_gatt_service_client.h new file mode 100644 index 00000000..ac1d99d4 --- /dev/null +++ b/internal/platform/implementation/linux/bluez_gatt_service_client.h @@ -0,0 +1,40 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_BLUEZ_GATT_SERVICE_CLIENT_H_ +#define PLATFORM_IMPL_LINUX_BLUEZ_GATT_SERVICE_CLIENT_H_ +#include + +#include +#include + +#include "internal/platform/implementation/linux/generated/dbus/bluez/gatt_service_client.h" + +namespace nearby { +namespace linux { +class GattServiceClient final + : public sdbus::ProxyInterfaces { + public: + GattServiceClient(std::shared_ptr system_bus, + sdbus::ObjectPath service_object_path) + : ProxyInterfaces(*system_bus, "org.bluez", + std::move(service_object_path)) { + registerProxy(); + } + ~GattServiceClient() { unregisterProxy(); } +}; +} // namespace linux +} // namespace nearby + +#endif diff --git a/internal/platform/implementation/linux/bluez_gatt_service_server.cc b/internal/platform/implementation/linux/bluez_gatt_service_server.cc new file mode 100644 index 00000000..34ab4a50 --- /dev/null +++ b/internal/platform/implementation/linux/bluez_gatt_service_server.cc @@ -0,0 +1,63 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "internal/platform/implementation/linux/bluez_gatt_service_server.h" +#include "absl/synchronization/mutex.h" +#include "internal/platform/implementation/ble_v2.h" +#include "internal/platform/implementation/linux/bluez_gatt_characteristic_server.h" +#include "internal/platform/implementation/linux/generated/dbus/bluez/gatt_characteristic_server.h" +#include "internal/platform/uuid.h" + +namespace nearby { +namespace linux { +namespace bluez { +bool GattServiceServer::AddCharacteristic( + const Uuid &service_uuid, const Uuid &characteristic_uuid, + api::ble_v2::GattCharacteristic::Permission permission, + api::ble_v2::GattCharacteristic::Property property) { + absl::MutexLock lock(&characterstics_mutex_); + api::ble_v2::GattCharacteristic characteristic{ + characteristic_uuid, service_uuid, permission, property}; + auto count = characteristics_.size(); + std::shared_ptr chr = + std::make_shared( + getObject().getConnection(), getObjectPath(), count, characteristic, + server_cb_, devices_); + try { + chr->emitInterfacesAddedSignal( + {org::bluez::GattCharacteristic1_adaptor::INTERFACE_NAME}); + } catch (const sdbus::Error &e) { + NEARBY_LOGS(ERROR) + << __func__ + << ": error emitting InterfacesAdded signal for object path " + << chr->getObjectPath() << " with name '" << e.getName() + << "' and message '" << e.getMessage() << "'"; + return false; + } + + characteristics_.insert({characteristic_uuid, std::move(chr)}); + return true; +} + +std::shared_ptr GattServiceServer::GetCharacteristic( + const Uuid &uuid) { + absl::ReaderMutexLock lock(&characterstics_mutex_); + if (characteristics_.count(uuid) == 0) { + return nullptr; + } + return characteristics_[uuid]; +} +} // namespace bluez +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/bluez_gatt_service_server.h b/internal/platform/implementation/linux/bluez_gatt_service_server.h new file mode 100644 index 00000000..d0422762 --- /dev/null +++ b/internal/platform/implementation/linux/bluez_gatt_service_server.h @@ -0,0 +1,110 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_BLUEZ_GATT_SERVICE_H_ +#define PLATFORM_IMPL_LINUX_BLUEZ_GATT_SERVICE_H_ + +#include +#include +#include +#include +#include + +#include "absl/container/flat_hash_map.h" +#include "absl/strings/string_view.h" +#include "internal/platform/implementation/ble_v2.h" +#include "internal/platform/implementation/linux/bluez.h" +#include "internal/platform/implementation/linux/bluez_gatt_characteristic_server.h" +#include "internal/platform/implementation/linux/generated/dbus/bluez/gatt_characteristic_server.h" +#include "internal/platform/implementation/linux/generated/dbus/bluez/gatt_service_server.h" +#include "internal/platform/logging.h" +#include "internal/platform/uuid.h" + +namespace nearby { +namespace linux { +namespace bluez { +class GattServiceServer final + : public sdbus::AdaptorInterfaces { + public: + GattServiceServer(const GattServiceServer &) = delete; + GattServiceServer(GattServiceServer &&) = delete; + GattServiceServer &operator=(const GattServiceServer &) = delete; + GattServiceServer &operator=(GattServiceServer &&) = delete; + + GattServiceServer( + sdbus::IConnection &system_bus, size_t num, const Uuid &service_uuid, + std::shared_ptr server_cb, + std::shared_ptr devices) + : AdaptorInterfaces(system_bus, bluez::gatt_service_path(num)), + devices_(std::move(devices)), + server_cb_(std::move(server_cb)), + uuid_(service_uuid), + primary_(true) { + registerAdaptor(); + NEARBY_LOGS(VERBOSE) << __func__ << ": Created a " + << org::bluez::GattService1_adaptor::INTERFACE_NAME + << " object at " << getObjectPath(); + } + + ~GattServiceServer() { + absl::MutexLock lock(&characterstics_mutex_); + for (auto &[_uuid, characteristic] : characteristics_) { + NEARBY_LOGS(VERBOSE) << __func__ << ": Removing characteristic " + << characteristic->getObjectPath(); + try { + characteristic->emitInterfacesRemovedSignal( + {org::bluez::GattCharacteristic1_adaptor::INTERFACE_NAME}); + } catch (const sdbus::Error &e) { + NEARBY_LOGS(ERROR) + << __func__ + << ": error emitting InterfacesRemoved signal for object path " + << characteristic->getObjectPath() << " with name '" << e.getName() + << "' and message '" << e.getMessage() << "'"; + } + } + unregisterAdaptor(); + } + + bool AddCharacteristic(const Uuid &service_uuid, + const Uuid &characteristic_uuid, + api::ble_v2::GattCharacteristic::Permission permission, + api::ble_v2::GattCharacteristic::Property property) + ABSL_LOCKS_EXCLUDED(characterstics_mutex_); + std::shared_ptr GetCharacteristic(const Uuid &uuid) + ABSL_LOCKS_EXCLUDED(characterstics_mutex_); + + private: + // Properties + std::string UUID() override { return uuid_; } + bool Primary() override { return primary_; } + sdbus::ObjectPath Device() override { return "/"; } + std::vector Includes() override { return {}; } + + absl::Mutex characterstics_mutex_; + absl::flat_hash_map> + characteristics_ ABSL_GUARDED_BY(characterstics_mutex_); + + std::shared_ptr devices_; + std::shared_ptr server_cb_; + + const std::string uuid_; + const bool primary_; +}; +} // namespace bluez +} // namespace linux +} // namespace nearby + +#endif diff --git a/internal/platform/implementation/linux/bluez_le_advertisement.cc b/internal/platform/implementation/linux/bluez_le_advertisement.cc new file mode 100644 index 00000000..a64244c0 --- /dev/null +++ b/internal/platform/implementation/linux/bluez_le_advertisement.cc @@ -0,0 +1,54 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "internal/platform/implementation/ble_v2.h" +#include "internal/platform/implementation/linux/bluez.h" +#include "internal/platform/implementation/linux/bluez_le_advertisement.h" +#include "internal/platform/logging.h" +#include "internal/platform/uuid.h" + +namespace nearby { +namespace linux { +namespace bluez { +LEAdvertisement::LEAdvertisement( + sdbus::IConnection& system_bus, sdbus::ObjectPath path, + const api::ble_v2::BleAdvertisementData& advertising_data, + api::ble_v2::AdvertiseParameters advertise_set_parameters) + : AdaptorInterfaces(system_bus, std::move(path)), + is_extended_advertisement_(advertising_data.is_extended_advertisement), + advertise_set_parameters_(advertise_set_parameters) { + for (const auto& [uuid, data] : advertising_data.service_data) { + std::string uuid_string(uuid); + std::vector data_bytes(data.size()); + const auto* bytes = data.data(); + + service_uuids_.push_back(uuid_string); + for (size_t i = 0; i < data.size(); i++) { + data_bytes[i] = bytes[i]; + } + + service_data_.insert({uuid_string, std::move(data_bytes)}); + } + + registerAdaptor(); + + NEARBY_LOGS(VERBOSE) << __func__ + << ": Created a org.bluez.LEAdvertisement1 instance at " + << getObjectPath(); +} +} // namespace bluez +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/bluez_le_advertisement.h b/internal/platform/implementation/linux/bluez_le_advertisement.h new file mode 100644 index 00000000..df391474 --- /dev/null +++ b/internal/platform/implementation/linux/bluez_le_advertisement.h @@ -0,0 +1,112 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_API_BLUEZ_BLE_ADVERTISEMENT_H_ +#define PLATFORM_IMPL_LINUX_API_BLUEZ_BLE_ADVERTISEMENT_H_ + +#include +#include +#include +#include +#include + +#include "internal/platform/implementation/ble_v2.h" +#include "internal/platform/implementation/linux/bluetooth_adapter.h" +#include "internal/platform/implementation/linux/bluez.h" +#include "internal/platform/implementation/linux/generated/dbus/bluez/le_advertisement_manager_client.h" +#include "internal/platform/implementation/linux/generated/dbus/bluez/le_advertisement_server.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { +namespace bluez { +class LEAdvertisement final + : public sdbus::AdaptorInterfaces { + public: + LEAdvertisement(const LEAdvertisement&) = delete; + LEAdvertisement(LEAdvertisement&&) = delete; + LEAdvertisement& operator=(const LEAdvertisement&) = delete; + LEAdvertisement& operator=(LEAdvertisement&&) = delete; + + LEAdvertisement(sdbus::IConnection& system_bus, sdbus::ObjectPath path, + const api::ble_v2::BleAdvertisementData& advertising_data, + api::ble_v2::AdvertiseParameters advertise_set_parameters); + + static std::unique_ptr CreateLEAdvertisement( + sdbus::IConnection& system_bus, + const api::ble_v2::BleAdvertisementData& advertising_data, + api::ble_v2::AdvertiseParameters advertising_parameters) { + static std::atomic adv_count = 0; + auto object_path = bluez::ble_advertisement_path(adv_count++); + return std::make_unique( + system_bus, object_path, advertising_data, advertising_parameters); + } + ~LEAdvertisement() { unregisterAdaptor(); } + + private: + // Methods + void Release() override { + NEARBY_LOGS(INFO) << __func__ + << ": LE Advertisement released: " << getObjectPath(); + } + + // Properties + std::string Type() override { return "peripheral"; } + std::vector ServiceUUIDs() override { return service_uuids_; } + std::map ManufacturerData() override { + return {}; + } + std::vector SolicitUUIDs() override { return {}; } + std::map ServiceData() override { + return service_data_; + } + std::vector Includes() override { return {}; } + std::string LocalName() override { return {}; } + uint16_t Duration() override { return 0; } + uint16_t Timeout() override { return 0; } + // Windows seems to hardcode the scan interval to 118.125 milliseconds, so + // lets just replicate that. + uint32_t MinInterval() override { return 118; } + uint32_t MaxInterval() override { return 119; } + int16_t TxPower() override { + return bluez::TxPowerLevelDbm(advertise_set_parameters_.tx_power_level); + }; + + bool is_extended_advertisement_; + std::vector service_uuids_; + std::map service_data_; + api::ble_v2::AdvertiseParameters advertise_set_parameters_; +}; + +class LEAdvertisementManager final + : public sdbus::ProxyInterfaces { + public: + LEAdvertisementManager(sdbus::IConnection& system_bus, + BluetoothAdapter& adapter) + : ProxyInterfaces(system_bus, "org.bluez", adapter.GetObjectPath()) { + registerProxy(); + } + ~LEAdvertisementManager() { unregisterProxy(); } + + LEAdvertisementManager(const LEAdvertisementManager&) = delete; + LEAdvertisementManager(LEAdvertisementManager&&) = delete; + LEAdvertisementManager& operator=(const LEAdvertisementManager&) = delete; + LEAdvertisementManager& operator=(LEAdvertisementManager&&) = delete; +}; +} // namespace bluez +} // namespace linux +} // namespace nearby + +#endif diff --git a/internal/platform/implementation/linux/condition_variable.h b/internal/platform/implementation/linux/condition_variable.h new file mode 100644 index 00000000..afde337e --- /dev/null +++ b/internal/platform/implementation/linux/condition_variable.h @@ -0,0 +1,50 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_CONDITION_VARIABLE_H_ +#define PLATFORM_IMPL_LINUX_CONDITION_VARIABLE_H_ + +#include "absl/synchronization/mutex.h" +#include "internal/platform/implementation/condition_variable.h" +#include "internal/platform/implementation/linux/mutex.h" +#include "internal/platform/implementation/mutex.h" + +namespace nearby { +namespace linux { +class ConditionVariable : public api::ConditionVariable { + public: + explicit ConditionVariable(api::Mutex *mutex) + : mutex_(&(static_cast(mutex)->GetMutex())) {} + ~ConditionVariable() = default; + + Exception Wait() override { + cond_var_.Wait(mutex_); + return {Exception::kSuccess}; + } + + Exception Wait(absl::Duration timeout) override { + cond_var_.WaitWithTimeout(mutex_, timeout); + return {Exception::kSuccess}; + } + + void Notify() override { cond_var_.SignalAll(); } + + private: + absl::Mutex *mutex_; + absl::CondVar cond_var_; +}; +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPL_LINUX_CONDITION_VARIABLE_H_ diff --git a/internal/platform/implementation/linux/condition_variable_test.cc b/internal/platform/implementation/linux/condition_variable_test.cc new file mode 100644 index 00000000..b25b47e4 --- /dev/null +++ b/internal/platform/implementation/linux/condition_variable_test.cc @@ -0,0 +1,100 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "internal/platform/implementation/linux/condition_variable.h" + +#include // NOLINT + +#include "absl/time/clock.h" +#include "internal/platform/exception.h" +#include "internal/platform/implementation/linux/mutex.h" + +#include "gtest/gtest.h" + +class ConditionVariableTests : public testing::Test { + public: + class ConditionVariableTest { + public: + ConditionVariableTest() {} + + std::future WaitForEvent(bool timedWait, // NOLINT + const absl::Duration* timeout) { + return std::async( + std::launch::async, [this, timedWait, timeout]() mutable -> bool { + if (timedWait == true) { + auto result = this->condition_variable_actual_.Wait(*timeout); + if (result.value == nearby::Exception::kSuccess) { + return true; + } else { + return false; + } + } else { + this->condition_variable_actual_.Wait(); + } + return true; + }); + } + + void PostEvent() { + absl::MutexLock(&mutex_actual_.GetMutex()); + condition_variable_actual_.Notify(); + } + + private: + nearby::linux::Mutex mutex_actual_ = + nearby::linux::Mutex(nearby::linux::Mutex::Mode::kRegular); + nearby::linux::Mutex& mutex_ = mutex_actual_; + nearby::linux::ConditionVariable condition_variable_actual_ = + nearby::linux::ConditionVariable(&mutex_); + nearby::linux::ConditionVariable& condition_variable_ = + condition_variable_actual_; + }; +}; + +TEST_F(ConditionVariableTests, SuccessfulCreation) { + // Arrange + ConditionVariableTest conditionVariableTest; + + auto result = conditionVariableTest.WaitForEvent(false, nullptr); + + sleep(1); + + // Act + conditionVariableTest.PostEvent(); + + // Assert + ASSERT_TRUE(result.get()); +} + +TEST_F(ConditionVariableTests, TimedCreation) { + // Arrange + ConditionVariableTest conditionVariableTest; + const absl::Duration duration = absl::Milliseconds(100); + + // Act + auto result = conditionVariableTest.WaitForEvent(true, &duration); + + // Assert + ASSERT_FALSE(result.get()); // Timed out + + // Act + result = conditionVariableTest.WaitForEvent(true, &duration); + + sleep(1); + + conditionVariableTest.PostEvent(); + + // Assert + ASSERT_TRUE(result.get()); // Didn't timeout +} diff --git a/internal/platform/implementation/linux/count_down_latch_test.cc b/internal/platform/implementation/linux/count_down_latch_test.cc new file mode 100644 index 00000000..672067ed --- /dev/null +++ b/internal/platform/implementation/linux/count_down_latch_test.cc @@ -0,0 +1,164 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "internal/platform/implementation/shared/count_down_latch.h" + +#include "gtest/gtest.h" +#include "internal/platform/implementation/platform.h" + +#include +#include +#include + +class CountDownLatchTests : public testing::Test { + public: + class TestData { + public: + std::unique_ptr& countDownLatch; + long volatile& count; + }; + + class CountDownLatchTest { + public: + static unsigned int ThreadProcCountDown(void* lpParam) { + TestData* testData = static_cast(lpParam); + + sleep(1); + + __sync_fetch_and_add(&testData->count, 1); + + testData->countDownLatch->CountDown(); + return 0; + } + + static unsigned int ThreadProcAwait(void* lpParam) { + TestData* testData = static_cast(lpParam); + + sleep(1); + + testData->countDownLatch->Await(); + __sync_fetch_and_add(&testData->count, 1); + + return 0; + } + }; + + CountDownLatchTests() {} +}; + +TEST_F(CountDownLatchTests, CountDownLatchAwaitSucceeds) { + // Arrange + long volatile count = 0; + + std::unique_ptr countDownLatch = + nearby::api::ImplementationPlatform::CreateCountDownLatch(3); + + std::vector threads; + + TestData testData{countDownLatch, count}; + + // Setup 3 threads + for (int i = 0; i < 3; i++) { + // TODO: More complex scenarios may require use of a parameter + // to the thread procedure, such as an event per thread to + // be used for synchronization. + // https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createthread + // Could use C++ concurrency for this possibly + threads.emplace_back(CountDownLatchTest::ThreadProcCountDown, &testData); + } + + // Act + nearby::Exception result = countDownLatch->Await(); + + for (auto& thread : threads) { + thread.join(); + } + + // Assert + EXPECT_EQ(result.value, nearby::Exception::kSuccess); + EXPECT_EQ(count, 3); +} + +TEST_F(CountDownLatchTests, CountDownLatchAwaitTimeoutTimesOut) { + // Arrange + + std::unique_ptr countDownLatch = + nearby::api::ImplementationPlatform::CreateCountDownLatch(3); + + // Act + nearby::ExceptionOr result = + countDownLatch->Await(absl::Milliseconds(5)); + + sleep(40); + + // Assert + EXPECT_FALSE(result.GetResult()); + // TODO(jfcarroll)I think there's a bug in the shared version of this, it's + // not returning a timeout exception, need to look at it some more. + // EXPECT_EQ(result.GetException().value, + // nearby::Exception::kTimeout); +} + +TEST_F(CountDownLatchTests, CountDownLatchAwaitNoTimeoutSucceeds) { + // Arrange + long volatile count = 0; + + std::unique_ptr countDownLatch = + nearby::api::ImplementationPlatform::CreateCountDownLatch(3); + + TestData testData{countDownLatch, count}; + + std::vector threads; + + // Setup 3 threads + for (int i = 0; i < 3; i++) { + // TODO: More complex scenarios may require use of a parameter + // to the thread procedure, such as an event per thread to + // be used for synchronization. + threads.emplace_back(CountDownLatchTest::ThreadProcAwait, &testData); + } + + for (auto& thread : threads) { + thread.join(); + } + // Act + nearby::ExceptionOr result = + countDownLatch->Await(absl::Milliseconds(100)); + + // Assert + EXPECT_TRUE(result.GetResult()); + EXPECT_EQ(result.GetException().value, nearby::Exception::kSuccess); + EXPECT_EQ(count, 3); +} + +void test(std::string str) { + std::cout << str << std::endl; + return; +} + +TEST_F(CountDownLatchTests, CountDownLatchCountDownBeforeAwaitSucceeds) { + // Arrange + long volatile count = 0; + std::unique_ptr countDownLatch = + nearby::api::ImplementationPlatform::CreateCountDownLatch(1); + + TestData testData{countDownLatch, count}; + std::thread thread(CountDownLatchTest::ThreadProcCountDown, &testData); + + // Act + countDownLatch->CountDown(); // This countdown occurs before the thread has a + // chance to run + // Assert + EXPECT_EQ(count, 1); +} diff --git a/internal/platform/implementation/linux/credential_storage.h b/internal/platform/implementation/linux/credential_storage.h new file mode 100644 index 00000000..0ef5cd32 --- /dev/null +++ b/internal/platform/implementation/linux/credential_storage.h @@ -0,0 +1,64 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_CREDENTIAL_STORAGE_H_ +#define PLATFORM_IMPL_LINUX_CREDENTIAL_STORAGE_H_ +#include + +#include +#include +#include + +#include "absl/strings/string_view.h" +#include "internal/platform/implementation/credential_storage.h" + +namespace nearby { +namespace linux { +class CredentialStorage : public api::CredentialStorage { + using LocalCredential = ::nearby::internal::LocalCredential; + using SharedCredential = ::nearby::internal::SharedCredential; + using PublicCredentialType = ::nearby::presence::PublicCredentialType; + using SaveCredentialsResultCallback = + ::nearby::presence::SaveCredentialsResultCallback; + using CredentialSelector = ::nearby::presence::CredentialSelector; + using GetLocalCredentialsResultCallback = + ::nearby::presence::GetLocalCredentialsResultCallback; + using GetPublicCredentialsResultCallback = + ::nearby::presence::GetPublicCredentialsResultCallback; + + CredentialStorage(sdbus::IConnection &connection); + ~CredentialStorage() override = default; + + void SaveCredentials(absl::string_view manager_app_id, + absl::string_view account_name, + const std::vector &Local_credentials, + const std::vector &Shared_credentials, + PublicCredentialType public_credential_type, + SaveCredentialsResultCallback callback) override; + void UpdateLocalCredential(absl::string_view manager_app_id, + absl::string_view account_name, + nearby::internal::LocalCredential credential, + SaveCredentialsResultCallback callback) override; + void GetPublicCredentials( + const CredentialSelector &credential_selector, + PublicCredentialType public_credential_type, + GetPublicCredentialsResultCallback callback) override; + + private: + std::unique_ptr proxy; +}; +} // namespace linux +} // namespace nearby + +#endif diff --git a/internal/platform/implementation/linux/crypto.cc b/internal/platform/implementation/linux/crypto.cc new file mode 100644 index 00000000..bdd1d926 --- /dev/null +++ b/internal/platform/implementation/linux/crypto.cc @@ -0,0 +1,54 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "internal/platform/implementation/crypto.h" + +#include +#include + +#include "absl/strings/string_view.h" +#include "internal/platform/byte_array.h" + +#include + +// Function implementations for platform/implementation/crypto.h. + +namespace nearby { + +// Initialize global crypto state. +void Crypto::Init() {} + +static ByteArray Hash(absl::string_view input, const EVP_MD* algo) { + unsigned int md_out_size = EVP_MAX_MD_SIZE; + uint8_t digest_buffer[EVP_MAX_MD_SIZE]; + if (input.empty()) return {}; + + if (!EVP_Digest(input.data(), input.size(), digest_buffer, &md_out_size, algo, + nullptr)) + return {}; + + return ByteArray{reinterpret_cast(digest_buffer), md_out_size}; +} + +// Return MD5 hash of input. +ByteArray Crypto::Md5(absl::string_view input) { + return Hash(input, EVP_md5()); +} + +// Return SHA256 hash of input. +ByteArray Crypto::Sha256(absl::string_view input) { + return Hash(input, EVP_sha256()); +} + +} // namespace nearby diff --git a/internal/platform/implementation/linux/crypto_test.cc b/internal/platform/implementation/linux/crypto_test.cc new file mode 100644 index 00000000..551616ee --- /dev/null +++ b/internal/platform/implementation/linux/crypto_test.cc @@ -0,0 +1,50 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "internal/platform/implementation/crypto.h" + +#include + +#include "gtest/gtest.h" + +namespace nearby { +namespace { + +TEST(CryptoTest, Md5Hash) { + const std::string input{"Hello Nearby Connection"}; + const ByteArray expected_md5( + "\x94\xa3\xbe\xc1\x8d\x30\xe3\x24\x5f\xa1\x4c\xee\xe7\x52\xe9\x36"); + ByteArray md5_hash = Crypto::Md5(input); + EXPECT_EQ(md5_hash, expected_md5); +} + +TEST(CryptoTest, Md5HashOnEmptyInput) { + EXPECT_EQ(Crypto::Md5(""), ByteArray{}); +} + +TEST(CryptoTest, Sha256Hash) { + const std::string input("Hello Nearby Connection"); + const ByteArray expected_sha256( + "\xb4\x24\xd3\xc0\x58\x12\x9a\x42\xcb\x81\xa0\x4b\x6e\x9d\xfe\x45\x45\x9f" + "\x15\xf7\xc0\xa9\x32\x2f\xfb\x9\x45\xf0\xf9\xbe\x75\xb"); + ByteArray sha256_hash = Crypto::Sha256(input); + EXPECT_EQ(sha256_hash, expected_sha256); +} + +TEST(CryptoTest, Sha256HashOnEmptyInput) { + EXPECT_EQ(Crypto::Sha256(""), ByteArray{}); +} + +} // namespace +} // namespace nearby \ No newline at end of file diff --git a/internal/platform/implementation/linux/dbus.cc b/internal/platform/implementation/linux/dbus.cc new file mode 100644 index 00000000..6a45b1f6 --- /dev/null +++ b/internal/platform/implementation/linux/dbus.cc @@ -0,0 +1,47 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include + +#include "absl/base/call_once.h" +#include "absl/synchronization/mutex.h" +#include "internal/platform/implementation/linux/dbus.h" + +namespace nearby { +namespace linux { + +namespace { +static absl::Mutex global_system_bus_mutex; +static std::weak_ptr global_system_bus_connection + ABSL_GUARDED_BY(global_system_bus_mutex); +} // namespace + +std::shared_ptr getSystemBusConnection() { + absl::MutexLock lock(&global_system_bus_mutex); + auto bus = global_system_bus_connection.lock(); + if (bus == nullptr) { + bus = + std::shared_ptr(sdbus::createSystemBusConnection()); + bus->enterEventLoopAsync(); + global_system_bus_connection = bus; + } + + return bus; +} +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/dbus.h b/internal/platform/implementation/linux/dbus.h new file mode 100644 index 00000000..d9908495 --- /dev/null +++ b/internal/platform/implementation/linux/dbus.h @@ -0,0 +1,63 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_DBUS_H_ +#define PLATFORM_IMPL_LINUX_DBUS_H_ + +#include +#include +#include +#include "internal/platform/logging.h" + +#define DBUS_LOG_METHOD_CALL_ERROR(p, m, e) \ + do { \ + NEARBY_LOGS(ERROR) << __func__ << ": Got error '" << (e).getName() \ + << "' with message '" << (e).getMessage() \ + << "' while calling " << (m) << " on object " \ + << (p)->getObjectPath(); \ + } while (false) + +#define DBUS_LOG_PROPERTY_GET_ERROR(p, prop, e) \ + do { \ + NEARBY_LOGS(ERROR) << __func__ << ": Got error '" << (e).getName() \ + << "' with message '" << (e).getMessage() \ + << "' while getting property " << (prop) \ + << " on object " << (p)->getObjectPath(); \ + } while (false) + +#define DBUS_LOG_PROPERTY_SET_ERROR(p, prop, e) \ + do { \ + NEARBY_LOGS(ERROR) << __func__ << ": Got error '" << (e).getName() \ + << "' with message '" << (e).getMessage() \ + << "' while setting property " << (prop) \ + << " on object " << (p)->getObjectPath(); \ + } while (false) + +namespace nearby { +namespace linux { +extern std::shared_ptr getSystemBusConnection(); +class RootObjectManager final + : public sdbus::AdaptorInterfaces { + public: + explicit RootObjectManager(sdbus::IConnection &system_bus) + : AdaptorInterfaces(system_bus, "/") { + registerAdaptor(); + } + ~RootObjectManager() { unregisterAdaptor(); } +}; + +} // namespace linux +} // namespace nearby +#endif diff --git a/internal/platform/implementation/linux/device_info.cc b/internal/platform/implementation/linux/device_info.cc new file mode 100644 index 00000000..02eb564c --- /dev/null +++ b/internal/platform/implementation/linux/device_info.cc @@ -0,0 +1,183 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "absl/synchronization/mutex.h" +#include "internal/platform/implementation/device_info.h" +#include "internal/platform/implementation/linux/avahi.h" +#include "internal/platform/implementation/linux/dbus.h" +#include "internal/platform/implementation/linux/device_info.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { +void CurrentUserSession::RegisterScreenLockedListener( + absl::string_view listener_name, + std::function callback) { + absl::MutexLock l(&screen_lock_listeners_mutex_); + screen_lock_listeners_[listener_name] = std::move(callback); +} + +void CurrentUserSession::UnregisterScreenLockedListener( + absl::string_view listener_name) { + absl::MutexLock l(&screen_lock_listeners_mutex_); + screen_lock_listeners_.erase(listener_name); +} + +void CurrentUserSession::onLock() { + absl::ReaderMutexLock l(&screen_lock_listeners_mutex_); + for (auto &[_, callback] : screen_lock_listeners_) { + callback(api::DeviceInfo::ScreenStatus::kLocked); + } +} + +void CurrentUserSession::onUnlock() { + absl::ReaderMutexLock l(&screen_lock_listeners_mutex_); + for (auto &[_, callback] : screen_lock_listeners_) { + callback(api::DeviceInfo::ScreenStatus::kUnlocked); + } +} + +DeviceInfo::DeviceInfo(std::shared_ptr system_bus) + : system_bus_(std::move(system_bus)), + current_user_session_(std::make_unique(*system_bus_)), + login_manager_(std::make_unique(*system_bus_)) {} + +std::optional DeviceInfo::GetOsDeviceName() const { + avahi::Server avahi(*system_bus_); + try { + std::string hostname = avahi.GetHostNameFqdn(); + std::wstring_convert, char16_t> convert; + return convert.from_bytes(hostname); + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_GET_ERROR(&avahi, "GetHostNameFqdn", e); + return std::nullopt; + } +} + +api::DeviceInfo::DeviceType DeviceInfo::GetDeviceType() const { + Hostnamed hostnamed(*system_bus_); + try { + std::string chasis = hostnamed.Chassis(); + api::DeviceInfo::DeviceType device = api::DeviceInfo::DeviceType::kUnknown; + if (chasis == "phone" || chasis == "handset") { + device = api::DeviceInfo::DeviceType::kPhone; + } else if (chasis == "laptop" || chasis == "desktop") { + device = api::DeviceInfo::DeviceType::kLaptop; + } else if (chasis == "tablet") { + device = api::DeviceInfo::DeviceType::kTablet; + } + return device; + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_GET_ERROR(&hostnamed, "Chasis", e); + return api::DeviceInfo::DeviceType::kUnknown; + } +} + +std::optional DeviceInfo::GetFullName() const { + struct passwd *pwd = getpwuid(getuid()); + if (pwd == nullptr) { + return std::nullopt; + } + char *name = strtok(pwd->pw_gecos, ","); + + std::wstring_convert, char16_t> convert; + return convert.from_bytes(name != nullptr ? name : pwd->pw_gecos); +} + +std::optional DeviceInfo::GetProfileUserName() const { + char *logname = secure_getenv("LOGNAME"); + return logname == nullptr ? std::nullopt + : std::optional(std::string(logname)); +} + +std::optional DeviceInfo::GetDownloadPath() const { + char *dir = getenv("XDG_DOWNLOAD_DIR"); + return std::filesystem::path(std::string(dir)); +} + +std::optional DeviceInfo::GetLocalAppDataPath() const { + char *dir = getenv("XDG_CONFIG_HOME"); + if (dir == nullptr) { + return std::filesystem::path("/tmp"); + } + return std::filesystem::path(std::string(dir)) / "Google Nearby"; +} + +std::optional DeviceInfo::GetTemporaryPath() const { + char *dir = getenv("XDG_RUNTIME_PATH"); + if (dir == nullptr) { + return std::filesystem::path("/tmp"); + } + return std::filesystem::path(std::string(dir)) / "Google Nearby"; +} + +std::optional DeviceInfo::GetLogPath() const { + char *dir = getenv("XDG_STATE_HOME"); + if (dir == nullptr) { + return std::filesystem::path("/tmp"); + } + return std::filesystem::path(std::string(dir)) / "Google Nearby" / "logs"; +} + +std::optional DeviceInfo::GetCrashDumpPath() const { + char *dir = getenv("XDG_STATE_HOME"); + if (dir == nullptr) { + return std::filesystem::path("/tmp"); + } + return std::filesystem::path(std::string(dir)) / "Google Nearby" / "crashes"; +} + +bool DeviceInfo::IsScreenLocked() const { + try { + return current_user_session_->LockedHint(); + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_GET_ERROR(current_user_session_, "LockedHint", e); + return false; + } +} + +bool DeviceInfo::PreventSleep() { + try { + inhibit_fd_ = login_manager_->Inhibit("sleep", "Google Nearby", + "Google Nearby", "block"); + return true; + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(login_manager_, "Inhibit", e); + return false; + } +} + +bool DeviceInfo::AllowSleep() { + if (!inhibit_fd_.has_value()) { + NEARBY_LOGS(ERROR) << __func__ + << "No inhibit lock is acquired at the moment"; + return false; + } + + inhibit_fd_.reset(); + return true; +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/device_info.h b/internal/platform/implementation/linux/device_info.h new file mode 100644 index 00000000..f864117d --- /dev/null +++ b/internal/platform/implementation/linux/device_info.h @@ -0,0 +1,171 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_DEVICE_INFO_H_ +#define PLATFORM_IMPL_LINUX_DEVICE_INFO_H_ + +#include +#include + +#include +#include +#include +#include + +#include "absl/container/flat_hash_map.h" +#include "absl/strings/string_view.h" +#include "absl/synchronization/mutex.h" +#include "internal/platform/implementation/device_info.h" +#include "internal/platform/implementation/linux/generated/dbus/hostname/hostname_client.h" +#include "internal/platform/implementation/linux/generated/dbus/login/login_manager_client.h" +#include "internal/platform/implementation/linux/generated/dbus/login/login_session_client.h" + +namespace nearby { +namespace linux { + +class CurrentUserSession final + : public sdbus::ProxyInterfaces { + public: + CurrentUserSession(const CurrentUserSession &) = delete; + CurrentUserSession(CurrentUserSession &&) = delete; + CurrentUserSession &operator=(const CurrentUserSession &) = delete; + CurrentUserSession &operator=(CurrentUserSession &&) = delete; + ~CurrentUserSession() { unregisterProxy(); } + explicit CurrentUserSession(sdbus::IConnection &system_bus) + : ProxyInterfaces(system_bus, "org.freedesktop.login1", + "/org/freedesktop/login1/session/auto") { + registerProxy(); + } + + void RegisterScreenLockedListener( + absl::string_view listener_name, + std::function callback) + ABSL_LOCKS_EXCLUDED(screen_lock_listeners_mutex_); + void UnregisterScreenLockedListener(absl::string_view listener_name) + ABSL_LOCKS_EXCLUDED(screen_lock_listeners_mutex_); + + protected: + void onPauseDevice(const uint32_t &major, const uint32_t &minor, + const std::string &type) override {} + void onResumeDevice(const uint32_t &major, const uint32_t &minor, + const sdbus::UnixFd &fd) override {} + + void onLock() override ABSL_LOCKS_EXCLUDED(screen_lock_listeners_mutex_); + void onUnlock() override ABSL_LOCKS_EXCLUDED(screen_lock_listeners_mutex_); + + private: + absl::Mutex screen_lock_listeners_mutex_; + absl::flat_hash_map> + screen_lock_listeners_ ABSL_GUARDED_BY(screen_lock_listeners_mutex_); +}; + +class Hostnamed + : public sdbus::ProxyInterfaces { + public: + Hostnamed(const Hostnamed &) = delete; + Hostnamed(Hostnamed &&) = delete; + Hostnamed &operator=(const Hostnamed &) = delete; + Hostnamed &operator=(Hostnamed &&) = delete; + explicit Hostnamed(sdbus::IConnection &system_bus) + : ProxyInterfaces(system_bus, "org.freedesktop.hostname1", + "/org/freedesktop/hostname1") { + registerProxy(); + } + ~Hostnamed() { unregisterProxy(); } +}; + +class LoginManager final + : public sdbus::ProxyInterfaces { + public: + LoginManager(const LoginManager &) = delete; + LoginManager(LoginManager &&) = delete; + LoginManager &operator=(const LoginManager &) = delete; + LoginManager &operator=(LoginManager &&) = delete; + explicit LoginManager(sdbus::IConnection &system_bus) + : ProxyInterfaces(system_bus, "org.freedesktop.login1", + "/org/freedesktop/login1") { + registerProxy(); + } + ~LoginManager() { unregisterProxy(); } + + protected: + void onSessionNew(const std::string &session_id, + const sdbus::ObjectPath &object_path) override {} + void onSessionRemoved(const std::string &session_id, + const sdbus::ObjectPath &object_path) override {} + void onUserNew(const uint32_t &uid, + const sdbus::ObjectPath &object_path) override {} + void onUserRemoved(const uint32_t &uid, + const sdbus::ObjectPath &object_path) override {} + void onSeatNew(const std::string &seat_id, + const sdbus::ObjectPath &object_path) override {} + void onSeatRemoved(const std::string &seat_id, + const sdbus::ObjectPath &object_path) override {} + void onPrepareForShutdown(const bool &start) override {} + void onPrepareForSleep(const bool &start) override {} +}; + +class DeviceInfo final : public api::DeviceInfo { + public: + explicit DeviceInfo(std::shared_ptr system_bus); + + std::optional GetOsDeviceName() const override; + api::DeviceInfo::DeviceType GetDeviceType() const override; + api::DeviceInfo::OsType GetOsType() const override { + return api::DeviceInfo::OsType::kWindows; // Or ChromeOS? + } + std::optional GetFullName() const override; + std::optional GetGivenName() const override { + return GetFullName(); + } + std::optional GetLastName() const override { + return GetFullName(); + } + std::optional GetProfileUserName() const override; + + std::optional GetDownloadPath() const override; + std::optional GetLocalAppDataPath() const override; + std::optional GetCommonAppDataPath() const override { + return std::nullopt; + }; + std::optional GetTemporaryPath() const override; + std::optional GetLogPath() const override; + std::optional GetCrashDumpPath() const override; + + bool IsScreenLocked() const override; + void RegisterScreenLockedListener( + absl::string_view listener_name, + std::function callback) override { + current_user_session_->RegisterScreenLockedListener(listener_name, + std::move(callback)); + } + void UnregisterScreenLockedListener( + absl::string_view listener_name) override { + current_user_session_->UnregisterScreenLockedListener(listener_name); + } + + bool PreventSleep() override; + bool AllowSleep() override; + + private: + std::shared_ptr system_bus_; + std::unique_ptr current_user_session_; + std::unique_ptr login_manager_; + std::optional inhibit_fd_; +}; +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPL_LINUX_DEVICE_INFO_H_ diff --git a/internal/platform/implementation/linux/device_info_test.cc b/internal/platform/implementation/linux/device_info_test.cc new file mode 100644 index 00000000..ad8713d9 --- /dev/null +++ b/internal/platform/implementation/linux/device_info_test.cc @@ -0,0 +1,129 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "internal/platform/implementation/linux/device_info.h" + +#include +#include + +#include "absl/synchronization/notification.h" +#include "gtest/gtest.h" +#include "internal/platform/implementation/device_info.h" + +namespace nearby { +namespace linux { +namespace { + +TEST(DeviceInfo, DISABLED_GetComputerName) { + EXPECT_TRUE(DeviceInfo().GetOsDeviceName().has_value()); +} + +TEST(DeviceInfo, DISABLED_GetDeviceType) { + EXPECT_EQ(DeviceInfo().GetDeviceType(), api::DeviceInfo::DeviceType::kLaptop); +} + +TEST(DeviceInfo, GetOsType) { + EXPECT_EQ(DeviceInfo().GetOsType(), api::DeviceInfo::OsType::kLinux); +} + +TEST(DeviceInfo, DISABLED_GetFullName) { + EXPECT_TRUE(DeviceInfo().GetFullName().has_value()); +} + +TEST(DeviceInfo, DISABLED_GetGivenName) { + EXPECT_TRUE(DeviceInfo().GetGivenName().has_value()); +} + +TEST(DeviceInfo, DISABLED_GetLastName) { + EXPECT_TRUE(DeviceInfo().GetLastName().has_value()); +} + +TEST(DeviceInfo, DISABLED_GetProfileUserName) { + EXPECT_TRUE(DeviceInfo().GetProfileUserName().has_value()); +} + +TEST(DeviceInfo, DISABLED_GetLocalAppDataPath) { + EXPECT_TRUE(DeviceInfo().GetLocalAppDataPath().has_value()); +} + +TEST(DeviceInfo, DISABLED_GetDownloadPath) { + EXPECT_TRUE(DeviceInfo().GetDownloadPath().has_value()); +} + +TEST(DeviceInfo, DISABLED_GetTemporaryPath) { + EXPECT_TRUE(DeviceInfo().GetTemporaryPath().has_value()); +} + +TEST(DeviceInfo, DISABLED_IsScreenLocked) { + EXPECT_FALSE(DeviceInfo().IsScreenLocked()); +} + +TEST(DeviceInfo, DISABLED_RegisterScreenLockedListener) { + std::function listener_1 = + [](api::DeviceInfo::ScreenStatus) {}; + std::function listener_2 = + [](api::DeviceInfo::ScreenStatus) {}; + + DeviceInfo device_info; + EXPECT_EQ(device_info.screen_locked_listeners_.size(), 0); + + device_info.RegisterScreenLockedListener("listener_1", listener_1); + EXPECT_EQ(device_info.screen_locked_listeners_.size(), 1); + + device_info.RegisterScreenLockedListener("listener_2", listener_2); + EXPECT_EQ(device_info.screen_locked_listeners_.size(), 2); +} + +TEST(DeviceInfo, DISABLED_UnregisterScreenLockedListener) { + std::function listener_1 = + [](api::DeviceInfo::ScreenStatus) {}; + std::function listener_2 = + [](api::DeviceInfo::ScreenStatus) {}; + + DeviceInfo device_info; + EXPECT_EQ(device_info.screen_locked_listeners_.size(), 0); + + device_info.RegisterScreenLockedListener("listener_1", listener_1); + device_info.RegisterScreenLockedListener("listener_2", listener_2); + EXPECT_EQ(device_info.screen_locked_listeners_.size(), 2); + + device_info.UnregisterScreenLockedListener("listener_1"); + EXPECT_EQ(device_info.screen_locked_listeners_.size(), 1); + + device_info.UnregisterScreenLockedListener("listener_2"); + EXPECT_EQ(device_info.screen_locked_listeners_.size(), 0); +} + +TEST(DeviceInfo, DISABLED_UpdateScreenLockedListener) { + absl::Notification notification; + + api::DeviceInfo::ScreenStatus screen_locked_tracker = + api::DeviceInfo::ScreenStatus::kUndetermined; + + std::function listener = + [&screen_locked_tracker, + ¬ification](api::DeviceInfo::ScreenStatus status) { + screen_locked_tracker = api::DeviceInfo::ScreenStatus::kLocked; + notification.Notify(); + }; + + DeviceInfo device_info; + device_info.RegisterScreenLockedListener("listener", listener); + EXPECT_TRUE(notification.WaitForNotificationWithTimeout(absl::Seconds(5))); + EXPECT_EQ(screen_locked_tracker, api::DeviceInfo::ScreenStatus::kLocked); +} + +} // namespace +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/executor.cc b/internal/platform/implementation/linux/executor.cc new file mode 100644 index 00000000..c4b22d37 --- /dev/null +++ b/internal/platform/implementation/linux/executor.cc @@ -0,0 +1,52 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "internal/platform/implementation/linux/executor.h" + +#include + +#include "internal/platform/implementation/linux/thread_pool.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { +Executor::Executor(size_t max_concurrency) + : thread_pool_(std::make_unique(max_concurrency)) { + assert(max_concurrency >= 1); + assert(thread_pool_ != nullptr); +} + +void Executor::Execute(Runnable &&runnable) { + if (shut_down_) { + NEARBY_LOGS(VERBOSE) << "Warning: " << __func__ + << ": Attempt to execute on a shut down pool."; + return; + } + + if (runnable == nullptr) { + NEARBY_LOGS(ERROR) << __func__ << ": Runnable was null."; + return; + } + + thread_pool_->Run(std::move(runnable)); +} + +void Executor::Shutdown() { + shut_down_ = true; + thread_pool_->ShutDown(); + thread_pool_ = nullptr; +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/executor.h b/internal/platform/implementation/linux/executor.h new file mode 100644 index 00000000..7552945a --- /dev/null +++ b/internal/platform/implementation/linux/executor.h @@ -0,0 +1,48 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_EXECUTOR_H_ +#define PLATFORM_IMPL_LINUX_EXECUTOR_H_ + +#include +#include + +#include "internal/platform/implementation/executor.h" +#include "internal/platform/implementation/linux/thread_pool.h" + +namespace nearby { +namespace linux { + +// This abstract class is the superclass of all classes representing an +// Executor. +class Executor : public api::Executor { + public: + Executor(size_t max_concurrency = 1); + + // Before returning from destructor, executor must wait for all pending + // jobs to finish. + ~Executor() override = default; + + void Execute(Runnable&& runnable) override; + void Shutdown() override; + + private: + std::unique_ptr thread_pool_ = nullptr; + std::atomic shut_down_ = false; +}; + +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPL_LINUX_EXECUTOR_H_ diff --git a/internal/platform/implementation/linux/executor_test.cc b/internal/platform/implementation/linux/executor_test.cc new file mode 100644 index 00000000..8cfadfa4 --- /dev/null +++ b/internal/platform/implementation/linux/executor_test.cc @@ -0,0 +1,323 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "internal/platform/implementation/linux/executor.h" + +#include +#include +#include + +#include "absl/synchronization/blocking_counter.h" +#include "absl/synchronization/mutex.h" +#include "absl/synchronization/notification.h" +#include "absl/time/time.h" +#include "gtest/gtest.h" +#include "internal/platform/implementation/linux/test_data.h" + +namespace nearby { +namespace linux { +namespace { + +constexpr absl::Duration kWaitTimeout = absl::Milliseconds(200); + +TEST(ExecutorTests, SingleThreadedExecutorSucceeds) { + absl::Notification notification; + // Arrange + std::string expected(RUNNABLE_0_TEXT.c_str()); + + auto executor = std::make_unique(); + std::string output = std::string(); + // Container to note threads that ran + std::unique_ptr> threadIds = + std::make_unique>(); + + threadIds->push_back(std::this_thread::get_id()); + + // Act + executor->Execute([&]() { + threadIds->push_back(std::this_thread::get_id()); + output.append(RUNNABLE_0_TEXT.c_str()); + notification.Notify(); + }); + + ASSERT_TRUE(notification.WaitForNotificationWithTimeout(kWaitTimeout)); + executor->Shutdown(); + + // Assert + // We should've run 1 time on the main thread, and 5 times on the + // workerThread + ASSERT_EQ(threadIds->size(), 2); + // We should still be on the main thread + ASSERT_EQ(std::this_thread::get_id(), threadIds->at(0)); + // We should've run all runnables on the worker thread + ASSERT_EQ(output, expected); +} + +TEST(ExecutorTests, SingleThreadedExecutorAfterShutdownFails) { + // Arrange + std::string expected(""); + + std::unique_ptr executor = std::make_unique(); + std::unique_ptr output = std::make_unique(); + // Container to note threads that ran + std::unique_ptr> threadIds = + std::make_unique>(); + + threadIds->push_back(std::this_thread::get_id()); + executor->Shutdown(); + + // Act + executor->Execute([&output, &threadIds]() { + threadIds->push_back(std::this_thread::get_id()); + output->append(RUNNABLE_0_TEXT.c_str()); + }); + + // Assert + // We should've run 1 time on the main thread, and 5 times on the + // workerThread + ASSERT_EQ(threadIds->size(), 1); + // We should still be on the main thread + ASSERT_EQ(std::this_thread::get_id(), threadIds->at(0)); + // We should've run all runnables on the worker thread + ASSERT_EQ(*output.get(), expected); +} + +TEST(ExecutorTests, SingleThreadedExecutorExecuteNullSucceeds) { + absl::Notification notification; + // Arrange + std::string expected(RUNNABLE_0_TEXT.c_str()); + + auto executor = std::make_unique(); + std::string output = std::string(); + // Container to note threads that ran + std::unique_ptr> threadIds = + std::make_unique>(); + + threadIds->push_back(std::this_thread::get_id()); + + // Act + executor->Execute(nullptr); + executor->Execute([&]() { + threadIds->push_back(std::this_thread::get_id()); + output.append(RUNNABLE_0_TEXT.c_str()); + notification.Notify(); + }); + executor->Execute(nullptr); + + ASSERT_TRUE(notification.WaitForNotificationWithTimeout(kWaitTimeout)); + executor->Shutdown(); + + // Assert + // We should've run 1 time on the main thread, and 5 times on the + // workerThread + ASSERT_EQ(threadIds->size(), 2); + // We should still be on the main thread + ASSERT_EQ(std::this_thread::get_id(), threadIds->at(0)); + // We should've run all runnables on the worker thread + ASSERT_EQ(output, expected); +} + +TEST(ExecutorTests, SingleThreadedExecutorMultipleTasksSucceeds) { + absl::BlockingCounter block_count(5); + + // Arrange + std::string expected(RUNNABLE_ALL_TEXT.c_str()); + + auto executor = std::make_unique(); + std::string output = std::string(); + // Container to note threads that ran + std::unique_ptr> threadIds = + std::make_unique>(); + + auto parent_thread = std::this_thread::get_id(); + + // Act + for (int index = 0; index < 5; index++) { + executor->Execute([&, index]() { + threadIds->push_back(std::this_thread::get_id()); + char buffer[128]; + snprintf(buffer, sizeof(buffer), "%s%d, ", RUNNABLE_TEXT.c_str(), index); + output.append(std::string(buffer)); + block_count.DecrementCount(); + }); + } + + block_count.Wait(); + executor->Shutdown(); + + // Assert + // We should've run 1 time on the main thread, and 5 times on the + // workerThread + ASSERT_EQ(threadIds->size(), 5); + // We should still be on the main thread + ASSERT_EQ(std::this_thread::get_id(), parent_thread); + // We should've run all runnables on the worker thread + auto workerThreadId = threadIds->at(0); + for (int index = 0; index < threadIds->size(); index++) { + ASSERT_EQ(threadIds->at(index), workerThreadId); + } + + // We should of run them in the order submitted + ASSERT_EQ(output, expected); +} + +TEST(ExecutorTests, MultiThreadedExecutorSingleTaskSucceeds) { + absl::Notification notification; + + // Arrange + std::string expected(RUNNABLE_0_TEXT.c_str()); + + auto executor = std::make_unique(2); + + // Container to note threads that ran + std::unique_ptr> threadIds = + std::make_unique>(); + + std::shared_ptr output = std::make_shared(); + + threadIds->push_back(std::this_thread::get_id()); + + // Act + executor->Execute([&, output]() { + threadIds->push_back(std::this_thread::get_id()); + output->append(RUNNABLE_0_TEXT.c_str()); + notification.Notify(); + }); + + ASSERT_TRUE(notification.WaitForNotificationWithTimeout(kWaitTimeout)); + executor->Shutdown(); + + // Assert + // We should've run 1 time on the main thread, and 5 times on the + // workerThread + ASSERT_EQ(threadIds->size(), 2); + // We should still be on the main thread + ASSERT_EQ(std::this_thread::get_id(), threadIds->at(0)); + // We should've run the task + ASSERT_EQ(*output.get(), expected); +} + +TEST(ExecutorTests, MultiThreadedExecutorMultipleTasksSucceeds) { + absl::BlockingCounter block_count(5); + + // Arrange + auto executor = std::make_unique(2); + + // Container to note threads that ran + std::unique_ptr> threadIds = + std::make_unique>(); + + std::shared_ptr output = std::make_shared(); + + threadIds->push_back(std::this_thread::get_id()); + + // Act + for (int index = 0; index < 5; index++) { + executor->Execute([&, index]() { + threadIds->push_back(std::this_thread::get_id()); + char buffer[128]; + snprintf(buffer, sizeof(buffer), "%s %d, ", RUNNABLE_TEXT.c_str(), index); + output->append(std::string(buffer)); + block_count.DecrementCount(); + }); + } + + block_count.Wait(); + executor->Shutdown(); + + // Assert + // We should've run 1 time on the main thread, and 5 times on the + // workerThread + ASSERT_EQ(threadIds->size(), 6); + // We should still be on the main thread + ASSERT_EQ(std::this_thread::get_id(), threadIds->at(0)); +} + +TEST(ExecutorTests, MultiThreadedExecutorSingleTaskAfterShutdownFails) { + // Arrange + std::string expected(""); + + auto executor = std::make_unique(2); + + // Container to note threads that ran + std::unique_ptr> threadIds = + std::make_unique>(); + + std::shared_ptr output = std::make_shared(); + + threadIds->push_back(std::this_thread::get_id()); + + executor->Shutdown(); + + // Act + executor->Execute([output, &threadIds]() { + threadIds->push_back(std::this_thread::get_id()); + output->append(RUNNABLE_0_TEXT.c_str()); + }); + + // Assert + // We should've run 1 time on the main thread, and 5 times on the + // workerThread + ASSERT_EQ(threadIds->size(), 1); + // We should still be on the main thread + ASSERT_EQ(std::this_thread::get_id(), threadIds->at(0)); + // We should've run the task + ASSERT_EQ(*output.get(), expected); +} + +TEST(ExecutorTests, + MultiThreadedExecutorMultipleTasksLargeNumberOfThreadsSucceeds) { + absl::BlockingCounter block_count(250); + + // Arrange + auto executor = std::make_unique(32); + + // Container to note threads that ran + std::vector threadIds = std::vector(); + + threadIds.push_back(std::this_thread::get_id()); + absl::Mutex mutex; + // Act + for (int index = 0; index < 250; index++) { + executor->Execute([&]() mutable { + std::thread::id id = std::this_thread::get_id(); + { + absl::MutexLock lock(&mutex); + threadIds.push_back(id); + } + + // Using rand since this is in a critical section + // and windows doesn't have a rand_r anyway + auto sleepTime = (std::rand() % 101) + 1; // NOLINT + + sleep(sleepTime); + block_count.DecrementCount(); + }); + } + + block_count.Wait(); + executor->Shutdown(); + + // Assert + // We should still be on the main thread + ASSERT_EQ(std::this_thread::get_id(), threadIds.at(0)); + + // We should've run 1 time on the main thread, and 200 times on the + // workerThreads + ASSERT_EQ(threadIds.size(), 251); +} + +} // namespace +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/file.cc b/internal/platform/implementation/linux/file.cc new file mode 100644 index 00000000..0c3b9d2b --- /dev/null +++ b/internal/platform/implementation/linux/file.cc @@ -0,0 +1,113 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "internal/platform/implementation/linux/file.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "absl/memory/memory.h" +#include "absl/strings/string_view.h" +#include "internal/platform/exception.h" +#include "internal/platform/implementation/linux/utils.h" + +namespace nearby { +namespace linux { + +// InputFile +std::unique_ptr IOFile::CreateInputFile( + const absl::string_view file_path, size_t size) { + return absl::WrapUnique(new IOFile(file_path, size)); +} + +IOFile::IOFile(const absl::string_view file_path, size_t size) + : path_(file_path) { + // Always open input file path as wide string on Linux platform. + file_.open(std::filesystem::path(linux::string_to_wstring(path_)), + std::ios::binary | std::ios::in | std::ios::ate); + + total_size_ = file_.tellg(); + file_.seekg(0); +} + +std::unique_ptr IOFile::CreateOutputFile(const absl::string_view path) { + return std::unique_ptr(new IOFile(path)); +} + +IOFile::IOFile(const absl::string_view file_path) + : file_(), path_(file_path), total_size_(0) { + // Always open input file path as wide string on Windows platform. + std::wstring_convert> converter; + file_.open(std::filesystem::path(converter.from_bytes(path_)), + std::ios::binary | std::ios::out); +} + +ExceptionOr IOFile::Read(std::int64_t size) { + if (!file_.is_open()) { + return ExceptionOr{Exception::kIo}; + } + + if (file_.peek() == EOF) { + return ExceptionOr{ByteArray{}}; + } + + if (!file_.good()) { + return ExceptionOr{Exception::kIo}; + } + + ByteArray bytes(size); + std::unique_ptr read_bytes{new char[size]}; + file_.read(read_bytes.get(), static_cast(size)); + auto num_bytes_read = file_.gcount(); + if (num_bytes_read == 0) { + return ExceptionOr{Exception::kIo}; + } + + return ExceptionOr(ByteArray(read_bytes.get(), num_bytes_read)); +} + +Exception IOFile::Close() { + if (file_.is_open()) { + file_.close(); + } + return {Exception::kSuccess}; +} + +Exception IOFile::Write(const ByteArray& data) { + if (!file_.is_open()) { + return {Exception::kIo}; + } + + if (!file_.good()) { + return {Exception::kIo}; + } + + file_.write(data.data(), data.size()); + file_.flush(); + return {file_.good() ? Exception::kSuccess : Exception::kIo}; +} + +Exception IOFile::Flush() { + file_.flush(); + return {file_.good() ? Exception::kSuccess : Exception::kIo}; +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/file.h b/internal/platform/implementation/linux/file.h new file mode 100644 index 00000000..5176f33c --- /dev/null +++ b/internal/platform/implementation/linux/file.h @@ -0,0 +1,60 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_FILE_H_ +#define PLATFORM_IMPL_LINUX_FILE_H_ + +#include +#include +#include +#include + +#include "absl/strings/string_view.h" +#include "internal/platform/exception.h" +#include "internal/platform/implementation/input_file.h" +#include "internal/platform/implementation/output_file.h" + +namespace nearby { +namespace linux { + +class IOFile final : public api::InputFile, public api::OutputFile { + public: + static std::unique_ptr CreateInputFile( + const absl::string_view file_path, size_t size); + + static std::unique_ptr CreateOutputFile(const absl::string_view path); + + ExceptionOr Read(std::int64_t size) override; + + std::string GetFilePath() const override { return path_; } + + std::int64_t GetTotalSize() const override { return total_size_; } + Exception Close() override; + + Exception Write(const ByteArray& data) override; + Exception Flush() override; + + private: + explicit IOFile(const absl::string_view file_path, size_t size); + explicit IOFile(const absl::string_view file_path); + + std::fstream file_; + std::string path_; + std::int64_t total_size_; +}; + +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPL_LINUX_FILE_H_ diff --git a/internal/platform/implementation/linux/file_path.cc b/internal/platform/implementation/linux/file_path.cc new file mode 100644 index 00000000..57a49860 --- /dev/null +++ b/internal/platform/implementation/linux/file_path.cc @@ -0,0 +1,209 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "internal/platform/implementation/linux/file_path.h" + +#include +#include +#include +#include +#include +#include + +#include "absl/strings/str_cat.h" +#include "internal/platform/implementation/linux/dbus.h" +#include "internal/platform/implementation/linux/device_info.h" +#include "internal/platform/implementation/linux/utils.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { + +const wchar_t* kUpOneLevel = L"/.."; +constexpr wchar_t kPathDelimiter = L'/'; +constexpr wchar_t kReplacementChar = L'_'; +constexpr wchar_t kForwardSlash = L'/'; +constexpr wchar_t kBackSlash = L'\\'; + +std::wstring FilePath::GetCustomSavePath(std::wstring parent_folder, + std::wstring file_name) { + std::wstring path; + path += parent_folder + kPathDelimiter + file_name; + return CreateOutputFileWithRename(path); +} + +std::wstring FilePath::GetDownloadPath(std::wstring parent_folder, + std::wstring file_name) { + return CreateOutputFileWithRename( + GetDownloadPathInternal(parent_folder, file_name)); +} + +std::wstring FilePath::GetDownloadPathInternal(std::wstring parent_folder, + std::wstring file_name) { + DeviceInfo info = DeviceInfo(linux::getSystemBusConnection()); + + std::optional download_path = info.GetDownloadPath(); + + std::string base_path; + + std::wstring wide_path(string_to_wstring(base_path)); + + if (!download_path) { + // If grabbing the download path fails then we make a custom one + base_path = getenv("HOME"); + base_path.append("/Downloads"); + } else { + base_path = download_path.value(); + } + + // If parent_folder starts with a \\ or /, then strip it + while (!parent_folder.empty() && (*parent_folder.begin() == kBackSlash || + *parent_folder.begin() == kForwardSlash)) { + parent_folder.erase(0, 1); + } + + // If parent_folder ends with a \\ or /, then strip it + while (!parent_folder.empty() && (*parent_folder.rbegin() == kBackSlash || + *parent_folder.rbegin() == kForwardSlash)) { + parent_folder.erase(parent_folder.size() - 1, 1); + } + + // If file_name starts with a \\, then strip it + while (!file_name.empty() && (*file_name.begin() == kBackSlash || + *file_name.begin() == kForwardSlash)) { + file_name.erase(0, 1); + } + + // If file_name ends with a \\, then strip it + while (!file_name.empty() && (*file_name.rbegin() == kBackSlash || + *file_name.rbegin() == kForwardSlash)) { + file_name.erase(file_name.size() - 1, 1); + } + + std::wstring path; + + if (parent_folder.empty()) { + path = + file_name.empty() ? wide_path : wide_path + kForwardSlash + file_name; + } else { + path = file_name.empty() ? wide_path + kForwardSlash + parent_folder + : wide_path + kForwardSlash + parent_folder + + kForwardSlash + file_name; + } + + // Convert to UTF8 format. + return path; +} + +// If the file already exists we add " (x)", where x is an incrementing number, +// starting at 1, using the next non-existing number, to the file name, just +// before the first dot, or at the end if no dot. The absolute path is returned. +std::wstring FilePath::CreateOutputFileWithRename(std::wstring path) { + std::wstring sanitized_path(path); + + // Replace any \\ with / + std::replace(sanitized_path.begin(), sanitized_path.end(), kBackSlash, + kForwardSlash); + + // Remove any /..'s + SanitizePath(sanitized_path); + + auto last_delimiter = sanitized_path.find_last_of(kPathDelimiter); + std::wstring folder(sanitized_path.substr(0, last_delimiter)); + std::wstring file_name(sanitized_path.substr(last_delimiter)); + + // Locate the last dot + auto first = file_name.find_last_of('.'); + + if (first == std::string::npos) { + first = file_name.size(); + } + + // Break the string at the dot. + auto file_name1 = file_name.substr(0, first); + auto file_name2 = file_name.substr(first); + + // Construct the target file name + std::wstring target(sanitized_path); + + std::fstream file; + + // Open file as std::wstring + file.open(wstring_to_string(target), std::fstream::binary | std::fstream::in); + + // While we successfully open the file, keep incrementing the count. + int count = 0; + while (!(file.rdstate() & std::ifstream::failbit)) { + file.close(); + + target = (folder + file_name1 + L" (" + std::to_wstring(++count) + L")" + + file_name2); + + file.clear(); + file.open(wstring_to_string(target), + std::fstream::binary | std::fstream::in); + } + + if (count > 0) { + NEARBY_LOGS(INFO) << "Renamed " << wstring_to_string(path) << " to " + << wstring_to_string(target); + } + + // The above leaves the file open, so close it. + file.close(); + + return target; +} + +std::wstring FilePath::MutateForbiddenPathElements(std::wstring& str) { + // There are no forbidden paths in Linux + return str; +} + +void FilePath::SanitizePath(std::wstring& path) { + size_t pos = std::wstring::npos; + // Search for the substring in string in a loop until nothing is found + while ((pos = path.find(kUpOneLevel)) != std::string::npos) { + // If found then erase it from string + path.erase(pos, wcslen(kUpOneLevel)); + } + + ReplaceInvalidCharacters(path); +} + +// Legit the only illegal character in Linux +char kIllegalFileCharacters[] = {'/'}; + +void FilePath::ReplaceInvalidCharacters(std::wstring& path) { + for (auto& character : path) { + // If 0 < character < 32, it's illegal, replace it + if (character > 0 && character < 32) { + NEARBY_LOGS(INFO) << "In path " << wstring_to_string(path) + << " replaced \'" << std::string(1, character) + << "\' with \'" << std::string(1, kReplacementChar); + character = kReplacementChar; + } + for (auto illegal_character : kIllegalFileCharacters) { + if (character == illegal_character) { + NEARBY_LOGS(INFO) << "In path " << wstring_to_string(path) + << " replaced \'" << std::string(1, character) + << "\' with \'" << std::string(1, kReplacementChar); + character = kReplacementChar; + } + } + } +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/file_path.h b/internal/platform/implementation/linux/file_path.h new file mode 100644 index 00000000..6053b2f4 --- /dev/null +++ b/internal/platform/implementation/linux/file_path.h @@ -0,0 +1,49 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_LINUX_FILE_PATH_H_ +#define THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_LINUX_FILE_PATH_H_ + +#include + +#include "absl/strings/string_view.h" + +namespace nearby { +namespace linux { + +class FilePath { + public: + static std::wstring GetCustomSavePath(std::wstring parent_folder, + std::wstring file_name); + static std::wstring GetDownloadPath(std::wstring parent_folder, + std::wstring file_name); + + private: + // If the file already exists we add " (x)", where x is an incrementing + // number, starting at 1, using the next non-existing number, to the + // file name, just before the first dot, or at the end if no dot. The + // absolute path is returned. + static std::wstring CreateOutputFileWithRename(std::wstring path); + + static void ReplaceInvalidCharacters(std::wstring& path); + static void SanitizePath(std::wstring& path); + static std::wstring MutateForbiddenPathElements(std::wstring& str); + static std::wstring GetDownloadPathInternal(std::wstring parent_folder, + std::wstring file_name); +}; + +} // namespace linux +} // namespace nearby + +#endif // THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_LINUX_FILE_PATH_H_ diff --git a/internal/platform/implementation/linux/file_path_test.cc b/internal/platform/implementation/linux/file_path_test.cc new file mode 100644 index 00000000..e7fe44e0 --- /dev/null +++ b/internal/platform/implementation/linux/file_path_test.cc @@ -0,0 +1,779 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "internal/platform/implementation/linux/file_path.h" + +#include +#include +#include +#include +#include + +#include +#include +#include "gtest/gtest.h" + +namespace nearby { +namespace linux { + +namespace { + +const wchar_t* kFileName(L"increment_file_test.txt"); +const wchar_t* kFirstIterationFileName(L"/increment_file_test (1).txt"); +const wchar_t* kSecondIterationFileName(L"/increment_file_test (2).txt"); +const wchar_t* kThirdIterationFileName(L"/increment_file_test (3).txt"); +const wchar_t* kNoDotsFileName(L"incrementfiletesttxt"); +const wchar_t* kOneIterationNoDotsFileName(L"/incrementfiletesttxt (1)"); +const wchar_t* kMultipleDotsFileName(L"increment.file.test.txt"); +const wchar_t* kOneIterationMultipleDotsFileName( + L"/increment.file.test (1).txt"); +const wchar_t* kImmediateEscape(L"../"); +const wchar_t* kLongEscapeBackSlash(L"..\\test\\..\\..\\test"); +const wchar_t* kTwoLevelFolder(L"/test/test"); +const wchar_t* kLongEscapeSlash(L"../test/../../test"); +const wchar_t* kLongEscapeMixedSlash(L"../test\\..\\../test"); +const wchar_t* kLongEscapeEndingEscape(L"../test/../../test/.."); +const wchar_t* kLongEscapeEndingEscapeWithSlash( + L"../test/../../test/../../../"); +} // namespace + +// Can't run on google 3, I presume the SHGetKnownFolderPath +// fails. +class FilePathTests : public testing::Test { + protected: + // You can define per-test set-up logic as usual. + FilePathTests() { + default_download_path_ = + string_to_wstring(DeviceInfo().GetDownloadPath().value_or( + std::string(getenv("HOME")).append("/Downloads"))); + } + std::wstring default_download_path_; +}; + +TEST_F(FilePathTests, GetDownloadPathWithEmptyStringArguments\ +ShouldReturnBaseDownloadPath) { + std::wstring parent_folder(L""); + std::wstring file_name(L""); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, default_download_path_); +} // NOLINT false lint error here + +TEST_F(FilePathTests, GetDownloadPathWithSlashParent\ +FolderArgumentsShouldReturnBaseDownloadPath) { + std::wstring parent_folder(L"/"); + std::wstring file_name(L""); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, default_download_path_); +} // NOLINT false lint error here + +TEST_F(FilePathTests, GetDownloadPathWithBackslashParent\ +FolderArgumentsShouldReturnBaseDownloadPath) { + std::wstring parent_folder(L"\\"); + std::wstring file_name(L""); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, default_download_path_); +} // NOLINT false lint error here + +TEST_F(FilePathTests, GetDownloadPathWithAttemptToEscape\ +UsersDownloadFolderShouldReturnDownloadPathNotEscapingUsersDownloadFolder) { + std::wstring parent_folder(kImmediateEscape); + std::wstring file_name(L""); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, default_download_path_); +} + +TEST_F(FilePathTests, GetDownloadPathWithMultiple\ +AttemptsToEscapeUsersDownloadFolderWithBackslashShouldReturnDownloadPath\ +NotEscapingUsersDownloadFolder) { + std::wstring parent_folder(kLongEscapeBackSlash); + std::wstring file_name(L""); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, default_download_path_ + kTwoLevelFolder); +} + +TEST_F(FilePathTests, GetDownloadPathWithMultiple\ +AttemptsToEscapeUsersDownloadFolderShouldReturnDownloadPathNotEscapingUsers\ +DownloadFolder) { + std::wstring parent_folder(kLongEscapeSlash); + std::wstring file_name(L""); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, default_download_path_ + kTwoLevelFolder); +} + +TEST_F(FilePathTests, GetDownloadPathWithMultiple\ +AttemptsToEscapeUsersDownloadFolderWithMixedSlashShouldReturnDownloadPath\ +NotEscapingUsersDownloadFolder) { + std::wstring parent_folder(kLongEscapeMixedSlash); + std::wstring file_name(L""); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, default_download_path_ + kTwoLevelFolder); +} + +TEST_F(FilePathTests, GetDownloadPathWithMultiple\ +AttemptsToEscapeUsersDownloadFolderWithEndingEscapeShouldReturnDownload\ +PathNotEscapingUsersDownloadFolder) { + std::wstring parent_folder(kLongEscapeEndingEscape); + std::wstring file_name(L""); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, default_download_path_ + kTwoLevelFolder); +} + +TEST_F(FilePathTests, GetDownloadPathWithMultiple\ +AttemptsToEscapeUsersDownloadFolderWithEndingSlashShouldReturnDownloadPathNot\ +EscapingUsersDownloadFolder) { + std::wstring parent_folder(kLongEscapeEndingEscapeWithSlash); + std::wstring file_name(L""); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, default_download_path_ + kTwoLevelFolder); +} + +TEST_F(FilePathTests, GetDownloadPathWithSlashFileName\ +ArgumentsShouldReturnBaseDownloadPath) { + std::wstring parent_folder(L""); + std::wstring file_name(L"/"); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, default_download_path_); +} + +TEST_F(FilePathTests, GetDownloadPathWithBackslashFile\ +NameArgumentsShouldReturnBaseDownloadPath) { + std::wstring parent_folder(L""); + std::wstring file_name(L"\\"); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + auto result_size = actual.size(); + auto default_size = default_download_path_.size(); + + EXPECT_EQ(actual, default_download_path_); +} + +TEST_F(FilePathTests, GetDownloadPathWithParentFolder\ +ShouldReturnParentFolderAppendedToBaseDownloadPath) { + std::wstring parent_folder(L"test_parent_folder"); + std::wstring file_name(L""); + + std::wstringstream path(L""); + path << default_download_path_ << L"/" << "test_parent_folder"; + + std::wstring expected = path.str(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPathWithParentFolder\ +StartingWithSlashArgumentsShouldReturnParentFolderAppendedToBaseDownloadPath) { + std::wstring parent_folder(L"/test_parent_folder"); + std::wstring file_name(L""); + + std::wstringstream path(L""); + path << default_download_path_ << L"/" << "test_parent_folder"; + + std::wstring expected = path.str(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPathWithParentFolder\ +StartingWithBackslashArgumentsShouldReturnParentFolderAppendedToBase\ +DownloadPath) { + std::wstring parent_folder(L"\\test_parent_folder"); + std::wstring file_name(L""); + + std::wstringstream path(L""); + path << default_download_path_ << L"/" << "test_parent_folder"; + + std::wstring expected = path.str(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPathWithParentFolder\ +EndingWithSlashArgumentsShouldReturnParentFolderAppendedToBaseDownloadPath) { + std::wstring parent_folder(L"test_parent_folder/"); + std::wstring file_name(L""); + + std::wstringstream path(L""); + path << default_download_path_ << L"/" << "test_parent_folder"; + + std::wstring expected = path.str(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPathWithParentFolder\ +EndingWithBackslashArguments\ +ShouldReturnParentFolderAppendedToBaseDownloadPath) { + std::wstring parent_folder(L"test_parent_folder\\"); + std::wstring file_name(L""); + + std::wstringstream path(L""); + path << default_download_path_ << L"/" << "test_parent_folder"; + + std::wstring expected = path.str(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPathWithFileName\ +BeginningWithSlashArgumentsShouldReturnFileNameAppendedToBaseDownloadPath) { + std::wstring parent_folder(L""); + std::wstring file_name(L"/test_file_name.name"); + + std::wstringstream path(L""); + path << default_download_path_ << L"/" << "test_file_name.name"; + + std::wstring expected = path.str(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPathWithFileName\ +BeginningWithBackslashArgumentsShouldReturnFileNameAppendedToBaseDownloadPath) { + std::wstring parent_folder(L""); + std::wstring file_name(L"\\test_file_name.name"); + + std::wstringstream path(L""); + path << default_download_path_ << L"/" << "test_file_name.name"; + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, path.str().c_str()); +} + +TEST_F(FilePathTests, GetDownloadPathWithFileNameEnding\ +WithSlashArgumentsShouldReturnFileNameAppendedToBaseDownloadPath) { + std::wstring parent_folder(L""); + std::wstring file_name(L"test_file_name.name/"); + + std::wstringstream path(L""); + path << default_download_path_ << L"/" << "test_file_name.name"; + + std::wstring expected = path.str(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPathWithFileNameEnding\ +WithBackslashArgumentsShouldReturnFileNameAppendedToBaseDownloadPath) { + std::wstring parent_folder(L""); + std::wstring file_name(L"test_file_name.name\\"); + + std::wstringstream path(L""); + path << default_download_path_ << L"/" << "test_file_name.name"; + + std::wstring expected = path.str(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPathWithParentFolderAnd\ +FileNameArgumentsShould\ +ReturnParentFolderAndFileNameAppendedToBaseDownloadPath) { + std::wstring parent_folder(L"test_parent_folder"); + std::wstring file_name(L"test_file_name.name"); + + std::wstringstream path(L""); + path << default_download_path_ << L"/" << "test_parent_folder" + << "/" + << "test_file_name.name"; + + std::wstring expected = path.str(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPathWithParentFolder\ +EndingWithBackslashAndFileNameArgumentsShouldReturnParentFolderAndFileName\ +AppendedToBaseDownloadPath) { + std::wstring parent_folder(L"test_parent_folder\\"); + std::wstring file_name(L"test_file_name.name"); + + std::wstringstream path(L""); + path << default_download_path_ << L"/" << "test_parent_folder" + << "/" + << "test_file_name.name"; + + std::wstring expected = path.str(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPathWithFileName\ +StartingWithBackslashAndParentFolderArgumentsShouldReturnParentFolderAnd\ +FileNameAppendedToBaseDownloadPath) { + std::wstring parent_folder(L"test_parent_folder"); + std::wstring file_name(L"\\test_file_name.name"); + + std::wstringstream path(L""); + path << default_download_path_ << L"/" << "test_parent_folder" + << "/" + << "test_file_name.name"; + + std::wstring expected = path.str(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPath_IllegalFileNameCharacters\ +ReturnsFileNameWithUnderbarSubstituted) { + // char illegal_character_sequence[]{ 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x05, + // 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0 }; + auto illegal_character_sequence(L"Test\x5Test"); + std::wstring parent_folder(L""); + + std::wstring expected(default_download_path_); + expected.append(L"/Test_Test"); + + auto actual(FilePath::GetDownloadPath( + parent_folder, std::wstring(illegal_character_sequence))); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPath_LowestIllegalFileNameCharacter\ +ReturnsFileNameWithUnderbarSubstituted) { + // char illegal_character_sequence[]{ 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x01, + // 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0 }; + auto illegal_character_sequence(L"Test\x1Test"); + + std::wstring parent_folder(L""); + + std::wstring expected(default_download_path_); + expected.append(L"/Test_Test"); + + auto actual(FilePath::GetDownloadPath( + parent_folder, std::wstring(illegal_character_sequence))); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPath_HighestIllegalFileNameCharacter\ +ReturnsFileNameWithUnderbarSubstituted) { + // char illegal_character_sequence[]{ 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x1f, + // 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0 }; + auto illegal_character_sequence(L"Test\x1fTest"); + + std::wstring parent_folder(L""); + + std::wstring expected(default_download_path_); + expected.append(L"/Test_Test"); + + auto actual(FilePath::GetDownloadPath( + parent_folder, std::wstring(illegal_character_sequence))); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPath_IllegalFileNameCharacterQuestionMark\ +ReturnsFileNameWithUnderbarSubstituted) { + // char illegal_character_sequence[]{ 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2f, + // 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0 }; + auto illegal_character_sequence(L"Test?Test"); + + std::wstring parent_folder(L""); + + std::wstring expected(default_download_path_); + expected.append(L"/Test_Test"); + + auto actual(FilePath::GetDownloadPath( + parent_folder, std::wstring(illegal_character_sequence))); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPath_IllegalFileNameCharacterAsterisk\ +ReturnsFileNameWithUnderbarSubstituted) { + // char illegal_character_sequence[]{ 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2f, + // 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0 }; + auto illegal_character_sequence(L"Test*Test"); + + std::wstring parent_folder(L""); + + std::wstring expected(default_download_path_); + expected.append(L"/Test_Test"); + + auto actual(FilePath::GetDownloadPath( + parent_folder, std::wstring(illegal_character_sequence))); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPath_IllegalFileNameCharacterLessThan\ +ReturnsFileNameWithUnderbarSubstituted) { + // char illegal_character_sequence[]{ 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2f, + // 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0 }; + auto illegal_character_sequence(L"TestTest"); + + std::wstring parent_folder(L""); + + std::wstring expected(default_download_path_); + expected.append(L"/Test_Test"); + + auto actual(FilePath::GetDownloadPath( + parent_folder, std::wstring(illegal_character_sequence))); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPath_IllegalFileNameCharacterVerticalBar\ +ReturnsFileNameWithUnderbarSubstituted) { + // char illegal_character_sequence[]{ 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2f, + // 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0 }; + auto illegal_character_sequence(L"Test|Test"); + + std::wstring parent_folder(L""); + + std::wstring expected(default_download_path_); + expected.append(L"/Test_Test"); + + auto actual(FilePath::GetDownloadPath( + parent_folder, std::wstring(illegal_character_sequence))); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPath_IllegalFileNameCharacterColon\ +ReturnsFileNameWithUnderbarSubstituted) { + // char illegal_character_sequence[]{ 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2f, + // 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0 }; + auto illegal_character_sequence(L"Test:Test"); + + std::wstring parent_folder(L""); + + std::wstring expected(default_download_path_); + expected.append(L"/Test_Test"); + + auto actual(FilePath::GetDownloadPath( + parent_folder, std::wstring(illegal_character_sequence))); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPath_FileDoesntExist\ +ReturnsFileWithPassedName) { + std::wstring file_name(kFileName); + std::wstring parent_folder(L""); + + std::wstring expected(default_download_path_); + expected.append(L"/"); + expected.append(file_name); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPath_FileExistsReturns\ +FileWithIncrementedName) { + std::wstring file_name(kFileName); + std::wstring renamed_file_name(kFirstIterationFileName); + std::wstring parent_folder(L""); + + std::wstring output_file_path(default_download_path_); + output_file_path.append(L"/"); + output_file_path.append(file_name); + + std::wstring expected(default_download_path_); + expected += renamed_file_name; + + std::wifstream input_file; + std::wofstream output_file; + + output_file.open(wstring_to_string(output_file_path), + std::ofstream::binary | std::ofstream::out); + + ASSERT_TRUE(output_file.rdstate() == std::ofstream::goodbit); + + output_file.close(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, expected); + + // Remove the file and check that it is removed + // File 1 + std::filesystem::remove(output_file_path.c_str()); + + input_file.open(wstring_to_string(output_file_path), + std::ifstream::binary | std::ifstream::in); + + ASSERT_FALSE(input_file.rdstate() == std::ifstream::goodbit); +} + +TEST_F(FilePathTests, GetDownloadPath_MultipleFilesExist\ +ReturnsNextIncrementedFileName) { + std::ofstream output_file; + std::ifstream input_file; + + std::wstring file_name(kFileName); + std::wstring first_renamed_file_name(kFirstIterationFileName); + std::wstring second_renamed_file_name(kSecondIterationFileName); + + std::wstring parent_folder(L""); + + std::wstring expected(default_download_path_); + expected.append(second_renamed_file_name.c_str()); + + std::wstring output_file1_path(default_download_path_); + output_file1_path.append(L"/" + file_name); + + std::wstring output_file2_path(default_download_path_); + output_file2_path.append(first_renamed_file_name); + + // Create the test files + output_file.open(wstring_to_string(output_file1_path), + std::ofstream::binary | std::ofstream::out); + ASSERT_TRUE(output_file.rdstate() == std::ofstream::goodbit); + output_file.close(); + output_file.clear(); + + output_file.open(wstring_to_string(output_file2_path), + std::ofstream::binary | std::ofstream::out); + ASSERT_TRUE(output_file.rdstate() == std::ofstream::goodbit); + output_file.close(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(expected, actual); + + // Remove the test files and check that it is removed + // File 1 + std::filesystem::remove(wstring_to_string(output_file1_path).c_str()); + input_file.open(output_file1_path, std::ifstream::binary | std::ifstream::in); + + ASSERT_FALSE(input_file.rdstate() == std::ifstream::goodbit); + + // File 2 + std::filesystem::remove(wstring_to_string(output_file2_path).c_str()); + + input_file.clear(); + input_file.open(wstring_to_string(output_file2_path), + std::ifstream::binary | std::ifstream::in); + + ASSERT_FALSE(input_file.rdstate() == std::ifstream::goodbit); +} + +TEST_F(FilePathTests, GetDownloadPath_FileNameContains\ +MultipleDotsReturnsIncrementBeforeFirstDot) { + std::ifstream input_file; + std::ofstream output_file; + + std::wstring file_name(kMultipleDotsFileName); + std::wstring renamed_file_name(kOneIterationMultipleDotsFileName); + + std::wstring parent_folder(L""); + + std::wstring output_file1_path(default_download_path_); + output_file1_path.append(L"/" + file_name); + + std::wstring output_file2_path(default_download_path_); + output_file2_path.append(renamed_file_name); + + std::wstring expected(default_download_path_); + expected.append(renamed_file_name); + + output_file.open(wstring_to_string(output_file1_path), + std::ofstream::binary | std::ofstream::out); + ASSERT_TRUE(output_file.rdstate() == std::ofstream::goodbit); + output_file.close(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(expected, actual); + + std::filesystem::remove(wstring_to_string(output_file1_path).c_str()); + input_file.open(wstring_to_string(output_file1_path), + std::ifstream::binary | std::ifstream::in); + + ASSERT_FALSE(input_file.rdstate() == std::ifstream::goodbit); +} + +TEST_F(FilePathTests, GetDownloadPath_FileNameContainsNo\ +DotsReturnsWithIncrementAtEnd) { + std::ifstream input_file; + std::ofstream output_file; + + std::wstring file_name(kNoDotsFileName); + std::wstring renamed_file_name(kOneIterationNoDotsFileName); + + std::wstring parent_folder(L""); + + std::wstring output_file1_path(default_download_path_); + output_file1_path.append(L"/" + file_name); + + std::wstring output_file2_path(default_download_path_); + output_file2_path.append(L"/" + renamed_file_name); + + std::wstring expected(default_download_path_); + expected.append(renamed_file_name); + + output_file.open(wstring_to_string(output_file1_path), + std::ofstream::binary | std::ofstream::out); + ASSERT_TRUE(output_file.rdstate() == std::ofstream::goodbit); + output_file.close(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(expected, actual); + + std::filesystem::remove(wstring_to_string(output_file1_path).c_str()); + input_file.open(wstring_to_string(output_file1_path), + std::ifstream::binary | std::ifstream::in); + + ASSERT_FALSE(input_file.rdstate() == std::ifstream::goodbit); +} + +TEST_F(FilePathTests, GetDownloadPath_FileNameExistsWith\ +AHoleBetweenRenamedFiles) { + std::ifstream input_file; + std::ofstream output_file; + + std::wstring file_name(kFileName); + std::wstring file_name1(kFirstIterationFileName); + std::wstring file_name2(kSecondIterationFileName); + std::wstring file_name3(kThirdIterationFileName); + + std::wstring parent_folder(L""); + + // Create the path for the original file name + std::wstring output_file_path(default_download_path_); + output_file_path.append( + L"/" + + file_name); // Original file name example: "increment_file_test.txt" + + // Create the path for the first iteration of the original file name + std::wstring output_file1_path(default_download_path_); + output_file1_path.append(file_name1); // First iteration on original file + // name example: + // "increment_file_test (1).txt" + + // Create the path for the third iteration of the original file name + std::wstring output_file3_path(default_download_path_); + output_file3_path.append( + file_name3); // Third iteration on original file + // name example: "increment_file_test (3).txt" + + // Create the expected result which is the second iteration of the original + // file name + std::wstring expected(default_download_path_); + expected.append(file_name2); // Second iteration on original file name + // example: "increment_file_test (2).txt" + + // Create the original file + output_file.open(wstring_to_string(output_file_path), + std::ofstream::binary | std::ofstream::out); + ASSERT_TRUE(output_file.rdstate() == std::ofstream::goodbit); + output_file.close(); + + // Create the first iteration of the original file + output_file.clear(); + output_file.open(wstring_to_string(output_file1_path), + std::ofstream::binary | std::ofstream::out); + ASSERT_TRUE(output_file.rdstate() == std::ofstream::goodbit); + output_file.close(); + + // Create the third iteration of the original file + output_file.clear(); + output_file.open(wstring_to_string(output_file3_path), + std::ofstream::binary | std::ofstream::out); + ASSERT_TRUE(output_file.rdstate() == std::ofstream::goodbit); + output_file.close(); + + // This should return the second iteration of the original file + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(expected, actual); + + // Delete the original file + std::filesystem::remove(wstring_to_string(output_file_path).c_str()); + input_file.open(wstring_to_string(output_file_path), + std::ifstream::binary | std::ifstream::in); + ASSERT_FALSE(input_file.rdstate() == std::ifstream::goodbit); + + // Delete the first iteration of the original file + input_file.clear(); // Reset the input_file state + std::filesystem::remove(wstring_to_string(output_file1_path).c_str()); + input_file.open(wstring_to_string(output_file1_path), + std::ifstream::binary | std::ifstream::in); + ASSERT_FALSE(input_file.rdstate() == std::ifstream::goodbit); + + // Delete the third iteration of the original file + input_file.clear(); // Reset the input_file state + std::filesystem::remove(wstring_to_string(output_file3_path).c_str()); + input_file.open(wstring_to_string(output_file3_path), + std::ifstream::binary | std::ifstream::in); + ASSERT_FALSE(input_file.rdstate() == std::ifstream::goodbit); +} +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/future.h b/internal/platform/implementation/linux/future.h new file mode 100644 index 00000000..2f0bcab9 --- /dev/null +++ b/internal/platform/implementation/linux/future.h @@ -0,0 +1,48 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_FUTURE_H_ +#define PLATFORM_IMPL_LINUX_FUTURE_H_ + +#include "internal/platform/implementation/future.h" + +namespace nearby { +namespace linux { + +// A Future represents the result of an asynchronous computation. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Future.html +template +class Future : public api::Future { + public: + // TODO(b/184975123): replace with real implementation. + ~Future() override = default; + + // throws Exception::kInterrupted, Exception::kExecution + // TODO(b/184975123): replace with real implementation. + ExceptionOr Get() override { return ExceptionOr{Exception::kFailed}; } + + // throws Exception::kInterrupted, Exception::kExecution + // throws Exception::kTimeout if timeout is exceeded while waiting for + // result. + // TODO(b/184975123): replace with real implementation. + ExceptionOr Get(absl::Duration timeout) override { + return ExceptionOr{Exception::kFailed}; + } +}; + +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPL_LINUX_FUTURE_H_ diff --git a/internal/platform/implementation/linux/generated/BUILD b/internal/platform/implementation/linux/generated/BUILD new file mode 100644 index 00000000..7bb5ea35 --- /dev/null +++ b/internal/platform/implementation/linux/generated/BUILD @@ -0,0 +1,22 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +licenses(["notice"]) + +cc_library( + name = "types", + textual_hdrs = glob(["**/*.h"]), + visibility = [ + "//internal/platform/implementation/linux:__subpackages__", + ], +) diff --git a/internal/platform/implementation/linux/generated/dbus/avahi/entrygroup_client.h b/internal/platform/implementation/linux/generated/dbus/avahi/entrygroup_client.h new file mode 100644 index 00000000..8070deae --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/avahi/entrygroup_client.h @@ -0,0 +1,94 @@ + +/* + * This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT! + */ + +#ifndef __sdbuscpp__avahi_entrygroup_client_glue_h__proxy__H__ +#define __sdbuscpp__avahi_entrygroup_client_glue_h__proxy__H__ + +#include +#include +#include + +namespace org { +namespace freedesktop { +namespace Avahi { + +class EntryGroup_proxy +{ +public: + static constexpr const char* INTERFACE_NAME = "org.freedesktop.Avahi.EntryGroup"; + +protected: + EntryGroup_proxy(sdbus::IProxy& proxy) + : proxy_(proxy) + { + proxy_.uponSignal("StateChanged").onInterface(INTERFACE_NAME).call([this](const int32_t& state, const std::string& error){ this->onStateChanged(state, error); }); + } + + ~EntryGroup_proxy() = default; + + virtual void onStateChanged(const int32_t& state, const std::string& error) = 0; + +public: + void Free() + { + proxy_.callMethod("Free").onInterface(INTERFACE_NAME); + } + + void Commit() + { + proxy_.callMethod("Commit").onInterface(INTERFACE_NAME); + } + + void Reset() + { + proxy_.callMethod("Reset").onInterface(INTERFACE_NAME); + } + + int32_t GetState() + { + int32_t result; + proxy_.callMethod("GetState").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + bool IsEmpty() + { + bool result; + proxy_.callMethod("IsEmpty").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + void AddService(const int32_t& interface, const int32_t& protocol, const uint32_t& flags, const std::string& name, const std::string& type, const std::string& domain, const std::string& host, const uint16_t& port, const std::vector>& txt) + { + proxy_.callMethod("AddService").onInterface(INTERFACE_NAME).withArguments(interface, protocol, flags, name, type, domain, host, port, txt); + } + + void AddServiceSubtype(const int32_t& interface, const int32_t& protocol, const uint32_t& flags, const std::string& name, const std::string& type, const std::string& domain, const std::string& subtype) + { + proxy_.callMethod("AddServiceSubtype").onInterface(INTERFACE_NAME).withArguments(interface, protocol, flags, name, type, domain, subtype); + } + + void UpdateServiceTxt(const int32_t& interface, const int32_t& protocol, const uint32_t& flags, const std::string& name, const std::string& type, const std::string& domain, const std::vector>& txt) + { + proxy_.callMethod("UpdateServiceTxt").onInterface(INTERFACE_NAME).withArguments(interface, protocol, flags, name, type, domain, txt); + } + + void AddAddress(const int32_t& interface, const int32_t& protocol, const uint32_t& flags, const std::string& name, const std::string& address) + { + proxy_.callMethod("AddAddress").onInterface(INTERFACE_NAME).withArguments(interface, protocol, flags, name, address); + } + + void AddRecord(const int32_t& interface, const int32_t& protocol, const uint32_t& flags, const std::string& name, const uint16_t& clazz, const uint16_t& type, const uint32_t& ttl, const std::vector& rdata) + { + proxy_.callMethod("AddRecord").onInterface(INTERFACE_NAME).withArguments(interface, protocol, flags, name, clazz, type, ttl, rdata); + } + +private: + sdbus::IProxy& proxy_; +}; + +}}} // namespaces + +#endif diff --git a/internal/platform/implementation/linux/generated/dbus/avahi/org.freedesktop.Avahi.EntryGroup.xml b/internal/platform/implementation/linux/generated/dbus/avahi/org.freedesktop.Avahi.EntryGroup.xml new file mode 100644 index 00000000..434cc0f8 --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/avahi/org.freedesktop.Avahi.EntryGroup.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/internal/platform/implementation/linux/generated/dbus/avahi/org.freedesktop.Avahi.Server.xml b/internal/platform/implementation/linux/generated/dbus/avahi/org.freedesktop.Avahi.Server.xml new file mode 100644 index 00000000..5485a972 --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/avahi/org.freedesktop.Avahi.Server.xml @@ -0,0 +1,398 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/internal/platform/implementation/linux/generated/dbus/avahi/org.freedesktop.Avahi.ServiceBrowser.xml b/internal/platform/implementation/linux/generated/dbus/avahi/org.freedesktop.Avahi.ServiceBrowser.xml new file mode 100644 index 00000000..4e2e240f --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/avahi/org.freedesktop.Avahi.ServiceBrowser.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/internal/platform/implementation/linux/generated/dbus/avahi/org.freedesktop.Avahi.ServiceResolver.xml b/internal/platform/implementation/linux/generated/dbus/avahi/org.freedesktop.Avahi.ServiceResolver.xml new file mode 100644 index 00000000..898287ce --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/avahi/org.freedesktop.Avahi.ServiceResolver.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/internal/platform/implementation/linux/generated/dbus/avahi/server2_client.h b/internal/platform/implementation/linux/generated/dbus/avahi/server2_client.h new file mode 100644 index 00000000..6810ac8a --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/avahi/server2_client.h @@ -0,0 +1,399 @@ + +/* + * This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT! + */ + +#ifndef __sdbuscpp__avahi_server_client_glue_h__proxy__H__ +#define __sdbuscpp__avahi_server_client_glue_h__proxy__H__ + +#include +#include +#include + +namespace org { +namespace freedesktop { +namespace Avahi { + +class Server_proxy +{ +public: + static constexpr const char* INTERFACE_NAME = "org.freedesktop.Avahi.Server"; + +protected: + Server_proxy(sdbus::IProxy& proxy) + : proxy_(proxy) + { + proxy_.uponSignal("StateChanged").onInterface(INTERFACE_NAME).call([this](const int32_t& state, const std::string& error){ this->onStateChanged(state, error); }); + } + + ~Server_proxy() = default; + + virtual void onStateChanged(const int32_t& state, const std::string& error) = 0; + +public: + std::string GetVersionString() + { + std::string result; + proxy_.callMethod("GetVersionString").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + uint32_t GetAPIVersion() + { + uint32_t result; + proxy_.callMethod("GetAPIVersion").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + std::string GetHostName() + { + std::string result; + proxy_.callMethod("GetHostName").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + void SetHostName(const std::string& name) + { + proxy_.callMethod("SetHostName").onInterface(INTERFACE_NAME).withArguments(name); + } + + std::string GetHostNameFqdn() + { + std::string result; + proxy_.callMethod("GetHostNameFqdn").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + std::string GetDomainName() + { + std::string result; + proxy_.callMethod("GetDomainName").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + bool IsNSSSupportAvailable() + { + bool result; + proxy_.callMethod("IsNSSSupportAvailable").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + int32_t GetState() + { + int32_t result; + proxy_.callMethod("GetState").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + uint32_t GetLocalServiceCookie() + { + uint32_t result; + proxy_.callMethod("GetLocalServiceCookie").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + std::string GetAlternativeHostName(const std::string& name) + { + std::string result; + proxy_.callMethod("GetAlternativeHostName").onInterface(INTERFACE_NAME).withArguments(name).storeResultsTo(result); + return result; + } + + std::string GetAlternativeServiceName(const std::string& name) + { + std::string result; + proxy_.callMethod("GetAlternativeServiceName").onInterface(INTERFACE_NAME).withArguments(name).storeResultsTo(result); + return result; + } + + std::string GetNetworkInterfaceNameByIndex(const int32_t& index) + { + std::string result; + proxy_.callMethod("GetNetworkInterfaceNameByIndex").onInterface(INTERFACE_NAME).withArguments(index).storeResultsTo(result); + return result; + } + + int32_t GetNetworkInterfaceIndexByName(const std::string& name) + { + int32_t result; + proxy_.callMethod("GetNetworkInterfaceIndexByName").onInterface(INTERFACE_NAME).withArguments(name).storeResultsTo(result); + return result; + } + + std::tuple ResolveHostName(const int32_t& interface, const int32_t& protocol, const std::string& name, const int32_t& aprotocol, const uint32_t& flags) + { + std::tuple result; + proxy_.callMethod("ResolveHostName").onInterface(INTERFACE_NAME).withArguments(interface, protocol, name, aprotocol, flags).storeResultsTo(result); + return result; + } + + std::tuple ResolveAddress(const int32_t& interface, const int32_t& protocol, const std::string& address, const uint32_t& flags) + { + std::tuple result; + proxy_.callMethod("ResolveAddress").onInterface(INTERFACE_NAME).withArguments(interface, protocol, address, flags).storeResultsTo(result); + return result; + } + + std::tuple>, uint32_t> ResolveService(const int32_t& interface, const int32_t& protocol, const std::string& name, const std::string& type, const std::string& domain, const int32_t& aprotocol, const uint32_t& flags) + { + std::tuple>, uint32_t> result; + proxy_.callMethod("ResolveService").onInterface(INTERFACE_NAME).withArguments(interface, protocol, name, type, domain, aprotocol, flags).storeResultsTo(result); + return result; + } + + sdbus::ObjectPath EntryGroupNew() + { + sdbus::ObjectPath result; + proxy_.callMethod("EntryGroupNew").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + sdbus::ObjectPath DomainBrowserNew(const int32_t& interface, const int32_t& protocol, const std::string& domain, const int32_t& btype, const uint32_t& flags) + { + sdbus::ObjectPath result; + proxy_.callMethod("DomainBrowserNew").onInterface(INTERFACE_NAME).withArguments(interface, protocol, domain, btype, flags).storeResultsTo(result); + return result; + } + + sdbus::ObjectPath ServiceTypeBrowserNew(const int32_t& interface, const int32_t& protocol, const std::string& domain, const uint32_t& flags) + { + sdbus::ObjectPath result; + proxy_.callMethod("ServiceTypeBrowserNew").onInterface(INTERFACE_NAME).withArguments(interface, protocol, domain, flags).storeResultsTo(result); + return result; + } + + sdbus::ObjectPath ServiceBrowserNew(const int32_t& interface, const int32_t& protocol, const std::string& type, const std::string& domain, const uint32_t& flags) + { + sdbus::ObjectPath result; + proxy_.callMethod("ServiceBrowserNew").onInterface(INTERFACE_NAME).withArguments(interface, protocol, type, domain, flags).storeResultsTo(result); + return result; + } + + sdbus::ObjectPath ServiceResolverNew(const int32_t& interface, const int32_t& protocol, const std::string& name, const std::string& type, const std::string& domain, const int32_t& aprotocol, const uint32_t& flags) + { + sdbus::ObjectPath result; + proxy_.callMethod("ServiceResolverNew").onInterface(INTERFACE_NAME).withArguments(interface, protocol, name, type, domain, aprotocol, flags).storeResultsTo(result); + return result; + } + + sdbus::ObjectPath HostNameResolverNew(const int32_t& interface, const int32_t& protocol, const std::string& name, const int32_t& aprotocol, const uint32_t& flags) + { + sdbus::ObjectPath result; + proxy_.callMethod("HostNameResolverNew").onInterface(INTERFACE_NAME).withArguments(interface, protocol, name, aprotocol, flags).storeResultsTo(result); + return result; + } + + sdbus::ObjectPath AddressResolverNew(const int32_t& interface, const int32_t& protocol, const std::string& address, const uint32_t& flags) + { + sdbus::ObjectPath result; + proxy_.callMethod("AddressResolverNew").onInterface(INTERFACE_NAME).withArguments(interface, protocol, address, flags).storeResultsTo(result); + return result; + } + + sdbus::ObjectPath RecordBrowserNew(const int32_t& interface, const int32_t& protocol, const std::string& name, const uint16_t& clazz, const uint16_t& type, const uint32_t& flags) + { + sdbus::ObjectPath result; + proxy_.callMethod("RecordBrowserNew").onInterface(INTERFACE_NAME).withArguments(interface, protocol, name, clazz, type, flags).storeResultsTo(result); + return result; + } + +private: + sdbus::IProxy& proxy_; +}; + +}}} // namespaces + +namespace org { +namespace freedesktop { +namespace Avahi { + +class Server2_proxy +{ +public: + static constexpr const char* INTERFACE_NAME = "org.freedesktop.Avahi.Server2"; + +protected: + Server2_proxy(sdbus::IProxy& proxy) + : proxy_(proxy) + { + proxy_.uponSignal("StateChanged").onInterface(INTERFACE_NAME).call([this](const int32_t& state, const std::string& error){ this->onStateChanged(state, error); }); + } + + ~Server2_proxy() = default; + + virtual void onStateChanged(const int32_t& state, const std::string& error) = 0; + +public: + std::string GetVersionString() + { + std::string result; + proxy_.callMethod("GetVersionString").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + uint32_t GetAPIVersion() + { + uint32_t result; + proxy_.callMethod("GetAPIVersion").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + std::string GetHostName() + { + std::string result; + proxy_.callMethod("GetHostName").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + void SetHostName(const std::string& name) + { + proxy_.callMethod("SetHostName").onInterface(INTERFACE_NAME).withArguments(name); + } + + std::string GetHostNameFqdn() + { + std::string result; + proxy_.callMethod("GetHostNameFqdn").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + std::string GetDomainName() + { + std::string result; + proxy_.callMethod("GetDomainName").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + bool IsNSSSupportAvailable() + { + bool result; + proxy_.callMethod("IsNSSSupportAvailable").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + int32_t GetState() + { + int32_t result; + proxy_.callMethod("GetState").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + uint32_t GetLocalServiceCookie() + { + uint32_t result; + proxy_.callMethod("GetLocalServiceCookie").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + std::string GetAlternativeHostName(const std::string& name) + { + std::string result; + proxy_.callMethod("GetAlternativeHostName").onInterface(INTERFACE_NAME).withArguments(name).storeResultsTo(result); + return result; + } + + std::string GetAlternativeServiceName(const std::string& name) + { + std::string result; + proxy_.callMethod("GetAlternativeServiceName").onInterface(INTERFACE_NAME).withArguments(name).storeResultsTo(result); + return result; + } + + std::string GetNetworkInterfaceNameByIndex(const int32_t& index) + { + std::string result; + proxy_.callMethod("GetNetworkInterfaceNameByIndex").onInterface(INTERFACE_NAME).withArguments(index).storeResultsTo(result); + return result; + } + + int32_t GetNetworkInterfaceIndexByName(const std::string& name) + { + int32_t result; + proxy_.callMethod("GetNetworkInterfaceIndexByName").onInterface(INTERFACE_NAME).withArguments(name).storeResultsTo(result); + return result; + } + + std::tuple ResolveHostName(const int32_t& interface, const int32_t& protocol, const std::string& name, const int32_t& aprotocol, const uint32_t& flags) + { + std::tuple result; + proxy_.callMethod("ResolveHostName").onInterface(INTERFACE_NAME).withArguments(interface, protocol, name, aprotocol, flags).storeResultsTo(result); + return result; + } + + std::tuple ResolveAddress(const int32_t& interface, const int32_t& protocol, const std::string& address, const uint32_t& flags) + { + std::tuple result; + proxy_.callMethod("ResolveAddress").onInterface(INTERFACE_NAME).withArguments(interface, protocol, address, flags).storeResultsTo(result); + return result; + } + + std::tuple>, uint32_t> ResolveService(const int32_t& interface, const int32_t& protocol, const std::string& name, const std::string& type, const std::string& domain, const int32_t& aprotocol, const uint32_t& flags) + { + std::tuple>, uint32_t> result; + proxy_.callMethod("ResolveService").onInterface(INTERFACE_NAME).withArguments(interface, protocol, name, type, domain, aprotocol, flags).storeResultsTo(result); + return result; + } + + sdbus::ObjectPath EntryGroupNew() + { + sdbus::ObjectPath result; + proxy_.callMethod("EntryGroupNew").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + sdbus::ObjectPath DomainBrowserPrepare(const int32_t& interface, const int32_t& protocol, const std::string& domain, const int32_t& btype, const uint32_t& flags) + { + sdbus::ObjectPath result; + proxy_.callMethod("DomainBrowserPrepare").onInterface(INTERFACE_NAME).withArguments(interface, protocol, domain, btype, flags).storeResultsTo(result); + return result; + } + + sdbus::ObjectPath ServiceTypeBrowserPrepare(const int32_t& interface, const int32_t& protocol, const std::string& domain, const uint32_t& flags) + { + sdbus::ObjectPath result; + proxy_.callMethod("ServiceTypeBrowserPrepare").onInterface(INTERFACE_NAME).withArguments(interface, protocol, domain, flags).storeResultsTo(result); + return result; + } + + sdbus::ObjectPath ServiceBrowserPrepare(const int32_t& interface, const int32_t& protocol, const std::string& type, const std::string& domain, const uint32_t& flags) + { + sdbus::ObjectPath result; + proxy_.callMethod("ServiceBrowserPrepare").onInterface(INTERFACE_NAME).withArguments(interface, protocol, type, domain, flags).storeResultsTo(result); + return result; + } + + sdbus::ObjectPath ServiceResolverPrepare(const int32_t& interface, const int32_t& protocol, const std::string& name, const std::string& type, const std::string& domain, const int32_t& aprotocol, const uint32_t& flags) + { + sdbus::ObjectPath result; + proxy_.callMethod("ServiceResolverPrepare").onInterface(INTERFACE_NAME).withArguments(interface, protocol, name, type, domain, aprotocol, flags).storeResultsTo(result); + return result; + } + + sdbus::ObjectPath HostNameResolverPrepare(const int32_t& interface, const int32_t& protocol, const std::string& name, const int32_t& aprotocol, const uint32_t& flags) + { + sdbus::ObjectPath result; + proxy_.callMethod("HostNameResolverPrepare").onInterface(INTERFACE_NAME).withArguments(interface, protocol, name, aprotocol, flags).storeResultsTo(result); + return result; + } + + sdbus::ObjectPath AddressResolverPrepare(const int32_t& interface, const int32_t& protocol, const std::string& address, const uint32_t& flags) + { + sdbus::ObjectPath result; + proxy_.callMethod("AddressResolverPrepare").onInterface(INTERFACE_NAME).withArguments(interface, protocol, address, flags).storeResultsTo(result); + return result; + } + + sdbus::ObjectPath RecordBrowserPrepare(const int32_t& interface, const int32_t& protocol, const std::string& name, const uint16_t& clazz, const uint16_t& type, const uint32_t& flags) + { + sdbus::ObjectPath result; + proxy_.callMethod("RecordBrowserPrepare").onInterface(INTERFACE_NAME).withArguments(interface, protocol, name, clazz, type, flags).storeResultsTo(result); + return result; + } + +private: + sdbus::IProxy& proxy_; +}; + +}}} // namespaces + +#endif diff --git a/internal/platform/implementation/linux/generated/dbus/avahi/servicebrowser_client.h b/internal/platform/implementation/linux/generated/dbus/avahi/servicebrowser_client.h new file mode 100644 index 00000000..09206c00 --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/avahi/servicebrowser_client.h @@ -0,0 +1,58 @@ + +/* + * This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT! + */ + +#ifndef __sdbuscpp__avahi_servicebrowser_client_glue_h__proxy__H__ +#define __sdbuscpp__avahi_servicebrowser_client_glue_h__proxy__H__ + +#include +#include +#include + +namespace org { +namespace freedesktop { +namespace Avahi { + +class ServiceBrowser_proxy +{ +public: + static constexpr const char* INTERFACE_NAME = "org.freedesktop.Avahi.ServiceBrowser"; + +protected: + ServiceBrowser_proxy(sdbus::IProxy& proxy) + : proxy_(proxy) + { + proxy_.uponSignal("ItemNew").onInterface(INTERFACE_NAME).call([this](const int32_t& interface, const int32_t& protocol, const std::string& name, const std::string& type, const std::string& domain, const uint32_t& flags){ this->onItemNew(interface, protocol, name, type, domain, flags); }); + proxy_.uponSignal("ItemRemove").onInterface(INTERFACE_NAME).call([this](const int32_t& interface, const int32_t& protocol, const std::string& name, const std::string& type, const std::string& domain, const uint32_t& flags){ this->onItemRemove(interface, protocol, name, type, domain, flags); }); + proxy_.uponSignal("Failure").onInterface(INTERFACE_NAME).call([this](const std::string& error){ this->onFailure(error); }); + proxy_.uponSignal("AllForNow").onInterface(INTERFACE_NAME).call([this](){ this->onAllForNow(); }); + proxy_.uponSignal("CacheExhausted").onInterface(INTERFACE_NAME).call([this](){ this->onCacheExhausted(); }); + } + + ~ServiceBrowser_proxy() = default; + + virtual void onItemNew(const int32_t& interface, const int32_t& protocol, const std::string& name, const std::string& type, const std::string& domain, const uint32_t& flags) = 0; + virtual void onItemRemove(const int32_t& interface, const int32_t& protocol, const std::string& name, const std::string& type, const std::string& domain, const uint32_t& flags) = 0; + virtual void onFailure(const std::string& error) = 0; + virtual void onAllForNow() = 0; + virtual void onCacheExhausted() = 0; + +public: + void Free() + { + proxy_.callMethod("Free").onInterface(INTERFACE_NAME); + } + + void Start() + { + proxy_.callMethod("Start").onInterface(INTERFACE_NAME); + } + +private: + sdbus::IProxy& proxy_; +}; + +}}} // namespaces + +#endif diff --git a/internal/platform/implementation/linux/generated/dbus/bluez/adapter_client.h b/internal/platform/implementation/linux/generated/dbus/bluez/adapter_client.h new file mode 100644 index 00000000..23884e28 --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/bluez/adapter_client.h @@ -0,0 +1,179 @@ + +/* + * This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT! + */ + +#ifndef __sdbuscpp__bluez_adapter_client_glue_h__proxy__H__ +#define __sdbuscpp__bluez_adapter_client_glue_h__proxy__H__ + +#include +#include +#include + +namespace org { +namespace bluez { + +class Adapter1_proxy +{ +public: + static constexpr const char* INTERFACE_NAME = "org.bluez.Adapter1"; + +protected: + Adapter1_proxy(sdbus::IProxy& proxy) + : proxy_(proxy) + { + } + + ~Adapter1_proxy() = default; + +public: + void StartDiscovery() + { + proxy_.callMethod("StartDiscovery").onInterface(INTERFACE_NAME); + } + + void SetDiscoveryFilter(const std::map& properties) + { + proxy_.callMethod("SetDiscoveryFilter").onInterface(INTERFACE_NAME).withArguments(properties); + } + + void StopDiscovery() + { + proxy_.callMethod("StopDiscovery").onInterface(INTERFACE_NAME); + } + + void RemoveDevice(const sdbus::ObjectPath& device) + { + proxy_.callMethod("RemoveDevice").onInterface(INTERFACE_NAME).withArguments(device); + } + + std::vector GetDiscoveryFilters() + { + std::vector result; + proxy_.callMethod("GetDiscoveryFilters").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + void ConnectDevice(const std::map& properties) + { + proxy_.callMethod("ConnectDevice").onInterface(INTERFACE_NAME).withArguments(properties); + } + +public: + std::string Address() + { + return proxy_.getProperty("Address").onInterface(INTERFACE_NAME); + } + + std::string AddressType() + { + return proxy_.getProperty("AddressType").onInterface(INTERFACE_NAME); + } + + std::string Name() + { + return proxy_.getProperty("Name").onInterface(INTERFACE_NAME); + } + + std::string Alias() + { + return proxy_.getProperty("Alias").onInterface(INTERFACE_NAME); + } + + void Alias(const std::string& value) + { + proxy_.setProperty("Alias").onInterface(INTERFACE_NAME).toValue(value); + } + + uint32_t Class() + { + return proxy_.getProperty("Class").onInterface(INTERFACE_NAME); + } + + bool Powered() + { + return proxy_.getProperty("Powered").onInterface(INTERFACE_NAME); + } + + void Powered(const bool& value) + { + proxy_.setProperty("Powered").onInterface(INTERFACE_NAME).toValue(value); + } + + std::string PowerState() + { + return proxy_.getProperty("PowerState").onInterface(INTERFACE_NAME); + } + + bool Discoverable() + { + return proxy_.getProperty("Discoverable").onInterface(INTERFACE_NAME); + } + + void Discoverable(const bool& value) + { + proxy_.setProperty("Discoverable").onInterface(INTERFACE_NAME).toValue(value); + } + + uint32_t DiscoverableTimeout() + { + return proxy_.getProperty("DiscoverableTimeout").onInterface(INTERFACE_NAME); + } + + void DiscoverableTimeout(const uint32_t& value) + { + proxy_.setProperty("DiscoverableTimeout").onInterface(INTERFACE_NAME).toValue(value); + } + + bool Pairable() + { + return proxy_.getProperty("Pairable").onInterface(INTERFACE_NAME); + } + + void Pairable(const bool& value) + { + proxy_.setProperty("Pairable").onInterface(INTERFACE_NAME).toValue(value); + } + + uint32_t PairableTimeout() + { + return proxy_.getProperty("PairableTimeout").onInterface(INTERFACE_NAME); + } + + void PairableTimeout(const uint32_t& value) + { + proxy_.setProperty("PairableTimeout").onInterface(INTERFACE_NAME).toValue(value); + } + + bool Discovering() + { + return proxy_.getProperty("Discovering").onInterface(INTERFACE_NAME); + } + + std::vector UUIDs() + { + return proxy_.getProperty("UUIDs").onInterface(INTERFACE_NAME); + } + + std::string Modalias() + { + return proxy_.getProperty("Modalias").onInterface(INTERFACE_NAME); + } + + std::vector Roles() + { + return proxy_.getProperty("Roles").onInterface(INTERFACE_NAME); + } + + std::vector ExperimentalFeatures() + { + return proxy_.getProperty("ExperimentalFeatures").onInterface(INTERFACE_NAME); + } + +private: + sdbus::IProxy& proxy_; +}; + +}} // namespaces + +#endif diff --git a/internal/platform/implementation/linux/generated/dbus/bluez/advertisement_monitor_manager_client.h b/internal/platform/implementation/linux/generated/dbus/bluez/advertisement_monitor_manager_client.h new file mode 100644 index 00000000..6e8a86fa --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/bluez/advertisement_monitor_manager_client.h @@ -0,0 +1,57 @@ + +/* + * This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT! + */ + +#ifndef __sdbuscpp__advertisement_monitor_manager_client_h__proxy__H__ +#define __sdbuscpp__advertisement_monitor_manager_client_h__proxy__H__ + +#include +#include +#include + +namespace org { +namespace bluez { + +class AdvertisementMonitorManager1_proxy +{ +public: + static constexpr const char* INTERFACE_NAME = "org.bluez.AdvertisementMonitorManager1"; + +protected: + AdvertisementMonitorManager1_proxy(sdbus::IProxy& proxy) + : proxy_(proxy) + { + } + + ~AdvertisementMonitorManager1_proxy() = default; + +public: + void RegisterMonitor(const sdbus::ObjectPath& application) + { + proxy_.callMethod("RegisterMonitor").onInterface(INTERFACE_NAME).withArguments(application); + } + + void UnregisterMonitor(const sdbus::ObjectPath& application) + { + proxy_.callMethod("UnregisterMonitor").onInterface(INTERFACE_NAME).withArguments(application); + } + +public: + std::vector SupportedMonitorTypes() + { + return proxy_.getProperty("SupportedMonitorTypes").onInterface(INTERFACE_NAME); + } + + std::vector SupportedFeatures() + { + return proxy_.getProperty("SupportedFeatures").onInterface(INTERFACE_NAME); + } + +private: + sdbus::IProxy& proxy_; +}; + +}} // namespaces + +#endif diff --git a/internal/platform/implementation/linux/generated/dbus/bluez/advertisement_monitor_server.h b/internal/platform/implementation/linux/generated/dbus/bluez/advertisement_monitor_server.h new file mode 100644 index 00000000..1991ed31 --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/bluez/advertisement_monitor_server.h @@ -0,0 +1,57 @@ + +/* + * This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT! + */ + +#ifndef __sdbuscpp__advertisement_monitor_server_h__adaptor__H__ +#define __sdbuscpp__advertisement_monitor_server_h__adaptor__H__ + +#include +#include +#include + +namespace org { +namespace bluez { + +class AdvertisementMonitor1_adaptor +{ +public: + static constexpr const char* INTERFACE_NAME = "org.bluez.AdvertisementMonitor1"; + +protected: + AdvertisementMonitor1_adaptor(sdbus::IObject& object) + : object_(object) + { + object_.registerMethod("Release").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->Release(); }); + object_.registerMethod("Activate").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->Activate(); }); + object_.registerMethod("DeviceFound").onInterface(INTERFACE_NAME).withInputParamNames("device").implementedAs([this](const sdbus::ObjectPath& device){ return this->DeviceFound(device); }); + object_.registerMethod("DeviceLost").onInterface(INTERFACE_NAME).withInputParamNames("device").implementedAs([this](const sdbus::ObjectPath& device){ return this->DeviceLost(device); }); + object_.registerProperty("Type").onInterface(INTERFACE_NAME).withGetter([this](){ return this->Type(); }); + object_.registerProperty("RSSILowThreshold").onInterface(INTERFACE_NAME).withGetter([this](){ return this->RSSILowThreshold(); }); + object_.registerProperty("RSSIHighThreshold").onInterface(INTERFACE_NAME).withGetter([this](){ return this->RSSIHighThreshold(); }); + object_.registerProperty("RSSISamplingPeriod").onInterface(INTERFACE_NAME).withGetter([this](){ return this->RSSISamplingPeriod(); }); + object_.registerProperty("Patterns").onInterface(INTERFACE_NAME).withGetter([this](){ return this->Patterns(); }); + } + + ~AdvertisementMonitor1_adaptor() = default; + +private: + virtual void Release() = 0; + virtual void Activate() = 0; + virtual void DeviceFound(const sdbus::ObjectPath& device) = 0; + virtual void DeviceLost(const sdbus::ObjectPath& device) = 0; + +private: + virtual std::string Type() = 0; + virtual int16_t RSSILowThreshold() = 0; + virtual int16_t RSSIHighThreshold() = 0; + virtual uint16_t RSSISamplingPeriod() = 0; + virtual std::vector>> Patterns() = 0; + +private: + sdbus::IObject& object_; +}; + +}} // namespaces + +#endif diff --git a/internal/platform/implementation/linux/generated/dbus/bluez/device_client.h b/internal/platform/implementation/linux/generated/dbus/bluez/device_client.h new file mode 100644 index 00000000..7082f3a7 --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/bluez/device_client.h @@ -0,0 +1,214 @@ + +/* + * This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT! + */ + +#ifndef __sdbuscpp__device_client_h__proxy__H__ +#define __sdbuscpp__device_client_h__proxy__H__ + +#include +#include +#include + +namespace org { +namespace bluez { + +class Device1_proxy +{ +public: + static constexpr const char* INTERFACE_NAME = "org.bluez.Device1"; + +protected: + Device1_proxy(sdbus::IProxy& proxy) + : proxy_(proxy) + { + } + + ~Device1_proxy() = default; + + virtual void onPairReply(const sdbus::Error* error) = 0; + +public: + void Disconnect() + { + proxy_.callMethod("Disconnect").onInterface(INTERFACE_NAME); + } + + void Connect() + { + proxy_.callMethod("Connect").onInterface(INTERFACE_NAME); + } + + void ConnectProfile(const std::string& UUID) + { + proxy_.callMethod("ConnectProfile").onInterface(INTERFACE_NAME).withArguments(UUID); + } + + void DisconnectProfile(const std::string& UUID) + { + proxy_.callMethod("DisconnectProfile").onInterface(INTERFACE_NAME).withArguments(UUID); + } + + sdbus::PendingAsyncCall Pair() + { + return proxy_.callMethodAsync("Pair").onInterface(INTERFACE_NAME).uponReplyInvoke([this](const sdbus::Error* error){ this->onPairReply(error); }); + } + + void CancelPairing() + { + proxy_.callMethod("CancelPairing").onInterface(INTERFACE_NAME); + } + +public: + std::string Address() + { + return proxy_.getProperty("Address").onInterface(INTERFACE_NAME); + } + + std::string AddressType() + { + return proxy_.getProperty("AddressType").onInterface(INTERFACE_NAME); + } + + std::string Name() + { + return proxy_.getProperty("Name").onInterface(INTERFACE_NAME); + } + + std::string Alias() + { + return proxy_.getProperty("Alias").onInterface(INTERFACE_NAME); + } + + void Alias(const std::string& value) + { + proxy_.setProperty("Alias").onInterface(INTERFACE_NAME).toValue(value); + } + + uint32_t Class() + { + return proxy_.getProperty("Class").onInterface(INTERFACE_NAME); + } + + uint16_t Appearance() + { + return proxy_.getProperty("Appearance").onInterface(INTERFACE_NAME); + } + + std::string Icon() + { + return proxy_.getProperty("Icon").onInterface(INTERFACE_NAME); + } + + bool Paired() + { + return proxy_.getProperty("Paired").onInterface(INTERFACE_NAME); + } + + bool Bonded() + { + return proxy_.getProperty("Bonded").onInterface(INTERFACE_NAME); + } + + bool Trusted() + { + return proxy_.getProperty("Trusted").onInterface(INTERFACE_NAME); + } + + void Trusted(const bool& value) + { + proxy_.setProperty("Trusted").onInterface(INTERFACE_NAME).toValue(value); + } + + bool Blocked() + { + return proxy_.getProperty("Blocked").onInterface(INTERFACE_NAME); + } + + void Blocked(const bool& value) + { + proxy_.setProperty("Blocked").onInterface(INTERFACE_NAME).toValue(value); + } + + bool LegacyPairing() + { + return proxy_.getProperty("LegacyPairing").onInterface(INTERFACE_NAME); + } + + int16_t RSSI() + { + return proxy_.getProperty("RSSI").onInterface(INTERFACE_NAME); + } + + bool Connected() + { + return proxy_.getProperty("Connected").onInterface(INTERFACE_NAME); + } + + std::vector UUIDs() + { + return proxy_.getProperty("UUIDs").onInterface(INTERFACE_NAME); + } + + std::string Modalias() + { + return proxy_.getProperty("Modalias").onInterface(INTERFACE_NAME); + } + + sdbus::ObjectPath Adapter() + { + return proxy_.getProperty("Adapter").onInterface(INTERFACE_NAME); + } + + std::map ManufacturerData() + { + return proxy_.getProperty("ManufacturerData").onInterface(INTERFACE_NAME); + } + + std::map ServiceData() + { + return proxy_.getProperty("ServiceData").onInterface(INTERFACE_NAME); + } + + int16_t TxPower() + { + return proxy_.getProperty("TxPower").onInterface(INTERFACE_NAME); + } + + bool ServicesResolved() + { + return proxy_.getProperty("ServicesResolved").onInterface(INTERFACE_NAME); + } + + std::vector AdvertisingFlags() + { + return proxy_.getProperty("AdvertisingFlags").onInterface(INTERFACE_NAME); + } + + std::map AdvertisingData() + { + return proxy_.getProperty("AdvertisingData").onInterface(INTERFACE_NAME); + } + + bool WakeAllowed() + { + return proxy_.getProperty("WakeAllowed").onInterface(INTERFACE_NAME); + } + + void WakeAllowed(const bool& value) + { + proxy_.setProperty("WakeAllowed").onInterface(INTERFACE_NAME).toValue(value); + } + + std::map> Sets() + { + return proxy_.getProperty("Sets").onInterface(INTERFACE_NAME); + } + +private: + sdbus::IProxy& proxy_; +}; + +}} // namespaces + +#endif diff --git a/internal/platform/implementation/linux/generated/dbus/bluez/gatt_characteristic_client.h b/internal/platform/implementation/linux/generated/dbus/bluez/gatt_characteristic_client.h new file mode 100644 index 00000000..1bdc9087 --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/bluez/gatt_characteristic_client.h @@ -0,0 +1,118 @@ + +/* + * This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT! + */ + +#ifndef __sdbuscpp__gatt_characteristic_client_h__proxy__H__ +#define __sdbuscpp__gatt_characteristic_client_h__proxy__H__ + +#include +#include +#include + +namespace org { +namespace bluez { + +class GattCharacteristic1_proxy +{ +public: + static constexpr const char* INTERFACE_NAME = "org.bluez.GattCharacteristic1"; + +protected: + GattCharacteristic1_proxy(sdbus::IProxy& proxy) + : proxy_(proxy) + { + } + + ~GattCharacteristic1_proxy() = default; + +public: + std::vector ReadValue(const std::map& options) + { + std::vector result; + proxy_.callMethod("ReadValue").onInterface(INTERFACE_NAME).withArguments(options).storeResultsTo(result); + return result; + } + + void WriteValue(const std::vector& value, const std::map& options) + { + proxy_.callMethod("WriteValue").onInterface(INTERFACE_NAME).withArguments(value, options); + } + + std::tuple AcquireWrite(const std::map& options) + { + std::tuple result; + proxy_.callMethod("AcquireWrite").onInterface(INTERFACE_NAME).withArguments(options).storeResultsTo(result); + return result; + } + + std::tuple AcquireNotify(const std::map& options) + { + std::tuple result; + proxy_.callMethod("AcquireNotify").onInterface(INTERFACE_NAME).withArguments(options).storeResultsTo(result); + return result; + } + + void StartNotify() + { + proxy_.callMethod("StartNotify").onInterface(INTERFACE_NAME); + } + + void StopNotify() + { + proxy_.callMethod("StopNotify").onInterface(INTERFACE_NAME); + } + +public: + uint16_t Handle() + { + return proxy_.getProperty("Handle").onInterface(INTERFACE_NAME); + } + + std::string UUID() + { + return proxy_.getProperty("UUID").onInterface(INTERFACE_NAME); + } + + sdbus::ObjectPath Service() + { + return proxy_.getProperty("Service").onInterface(INTERFACE_NAME); + } + + std::vector Value() + { + return proxy_.getProperty("Value").onInterface(INTERFACE_NAME); + } + + bool Notifying() + { + return proxy_.getProperty("Notifying").onInterface(INTERFACE_NAME); + } + + std::vector Flags() + { + return proxy_.getProperty("Flags").onInterface(INTERFACE_NAME); + } + + bool WriteAcquired() + { + return proxy_.getProperty("WriteAcquired").onInterface(INTERFACE_NAME); + } + + bool NotifyAcquired() + { + return proxy_.getProperty("NotifyAcquired").onInterface(INTERFACE_NAME); + } + + uint16_t MTU() + { + return proxy_.getProperty("MTU").onInterface(INTERFACE_NAME); + } + +private: + sdbus::IProxy& proxy_; +}; + +}} // namespaces + +#endif diff --git a/internal/platform/implementation/linux/generated/dbus/bluez/gatt_characteristic_server.h b/internal/platform/implementation/linux/generated/dbus/bluez/gatt_characteristic_server.h new file mode 100644 index 00000000..451ac678 --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/bluez/gatt_characteristic_server.h @@ -0,0 +1,59 @@ + +/* + * This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT! + */ + +#ifndef __sdbuscpp__generated_dbus_bluez_gatt_characteristic_server_h__adaptor__H__ +#define __sdbuscpp__generated_dbus_bluez_gatt_characteristic_server_h__adaptor__H__ + +#include +#include +#include + +namespace org { +namespace bluez { + +class GattCharacteristic1_adaptor +{ +public: + static constexpr const char* INTERFACE_NAME = "org.bluez.GattCharacteristic1"; + +protected: + GattCharacteristic1_adaptor(sdbus::IObject& object) + : object_(object) + { + object_.registerMethod("ReadValue").onInterface(INTERFACE_NAME).withInputParamNames("options").withOutputParamNames("value").implementedAs([this](sdbus::Result>&& result, std::map options){ this->ReadValue(std::move(result), std::move(options)); }); + object_.registerMethod("WriteValue").onInterface(INTERFACE_NAME).withInputParamNames("value", "options").implementedAs([this](sdbus::Result<>&& result, std::vector value, std::map options){ this->WriteValue(std::move(result), std::move(value), std::move(options)); }); + object_.registerMethod("StartNotify").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->StartNotify(); }); + object_.registerMethod("StopNotify").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->StopNotify(); }); + object_.registerMethod("Confirm").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->Confirm(); }); + object_.registerProperty("UUID").onInterface(INTERFACE_NAME).withGetter([this](){ return this->UUID(); }); + object_.registerProperty("Service").onInterface(INTERFACE_NAME).withGetter([this](){ return this->Service(); }); + object_.registerProperty("Value").onInterface(INTERFACE_NAME).withGetter([this](){ return this->Value(); }); + object_.registerProperty("Notifying").onInterface(INTERFACE_NAME).withGetter([this](){ return this->Notifying(); }); + object_.registerProperty("Flags").onInterface(INTERFACE_NAME).withGetter([this](){ return this->Flags(); }); + } + + ~GattCharacteristic1_adaptor() = default; + +private: + virtual void ReadValue(sdbus::Result>&& result, std::map options) = 0; + virtual void WriteValue(sdbus::Result<>&& result, std::vector value, std::map options) = 0; + virtual void StartNotify() = 0; + virtual void StopNotify() = 0; + virtual void Confirm() = 0; + +private: + virtual std::string UUID() = 0; + virtual sdbus::ObjectPath Service() = 0; + virtual std::vector Value() = 0; + virtual bool Notifying() = 0; + virtual std::vector Flags() = 0; + +private: + sdbus::IObject& object_; +}; + +}} // namespaces + +#endif diff --git a/internal/platform/implementation/linux/generated/dbus/bluez/gatt_manager_client.h b/internal/platform/implementation/linux/generated/dbus/bluez/gatt_manager_client.h new file mode 100644 index 00000000..340ee0af --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/bluez/gatt_manager_client.h @@ -0,0 +1,46 @@ + +/* + * This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT! + */ + +#ifndef __sdbuscpp__gatt_manager_client_h__proxy__H__ +#define __sdbuscpp__gatt_manager_client_h__proxy__H__ + +#include +#include +#include + +namespace org { +namespace bluez { + +class GattManager1_proxy +{ +public: + static constexpr const char* INTERFACE_NAME = "org.bluez.GattManager1"; + +protected: + GattManager1_proxy(sdbus::IProxy& proxy) + : proxy_(proxy) + { + } + + ~GattManager1_proxy() = default; + +public: + void RegisterApplication(const sdbus::ObjectPath& application, const std::map& options) + { + proxy_.callMethod("RegisterApplication").onInterface(INTERFACE_NAME).withArguments(application, options); + } + + void UnregisterApplication(const sdbus::ObjectPath& application) + { + proxy_.callMethod("UnregisterApplication").onInterface(INTERFACE_NAME).withArguments(application); + } + +private: + sdbus::IProxy& proxy_; +}; + +}} // namespaces + +#endif diff --git a/internal/platform/implementation/linux/generated/dbus/bluez/gatt_service_client.h b/internal/platform/implementation/linux/generated/dbus/bluez/gatt_service_client.h new file mode 100644 index 00000000..fa36acc6 --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/bluez/gatt_service_client.h @@ -0,0 +1,56 @@ + +/* + * This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT! + */ + +#ifndef __sdbuscpp__gatt_service_client_h__proxy__H__ +#define __sdbuscpp__gatt_service_client_h__proxy__H__ + +#include +#include +#include + +namespace org { +namespace bluez { + +class GattService1_proxy +{ +public: + static constexpr const char* INTERFACE_NAME = "org.bluez.GattService1"; + +protected: + GattService1_proxy(sdbus::IProxy& proxy) + : proxy_(proxy) + { + } + + ~GattService1_proxy() = default; + +public: + std::string UUID() + { + return proxy_.getProperty("UUID").onInterface(INTERFACE_NAME); + } + + bool Primary() + { + return proxy_.getProperty("Primary").onInterface(INTERFACE_NAME); + } + + sdbus::ObjectPath Device() + { + return proxy_.getProperty("Device").onInterface(INTERFACE_NAME); + } + + std::vector Includes() + { + return proxy_.getProperty("Includes").onInterface(INTERFACE_NAME); + } + +private: + sdbus::IProxy& proxy_; +}; + +}} // namespaces + +#endif diff --git a/internal/platform/implementation/linux/generated/dbus/bluez/gatt_service_server.h b/internal/platform/implementation/linux/generated/dbus/bluez/gatt_service_server.h new file mode 100644 index 00000000..e37253a0 --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/bluez/gatt_service_server.h @@ -0,0 +1,45 @@ + +/* + * This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT! + */ + +#ifndef __sdbuscpp__generated_dbus_bluez_gatt_service_server_h__adaptor__H__ +#define __sdbuscpp__generated_dbus_bluez_gatt_service_server_h__adaptor__H__ + +#include +#include +#include + +namespace org { +namespace bluez { + +class GattService1_adaptor +{ +public: + static constexpr const char* INTERFACE_NAME = "org.bluez.GattService1"; + +protected: + GattService1_adaptor(sdbus::IObject& object) + : object_(object) + { + object_.registerProperty("UUID").onInterface(INTERFACE_NAME).withGetter([this](){ return this->UUID(); }); + object_.registerProperty("Primary").onInterface(INTERFACE_NAME).withGetter([this](){ return this->Primary(); }); + object_.registerProperty("Device").onInterface(INTERFACE_NAME).withGetter([this](){ return this->Device(); }); + object_.registerProperty("Includes").onInterface(INTERFACE_NAME).withGetter([this](){ return this->Includes(); }); + } + + ~GattService1_adaptor() = default; + +private: + virtual std::string UUID() = 0; + virtual bool Primary() = 0; + virtual sdbus::ObjectPath Device() = 0; + virtual std::vector Includes() = 0; + +private: + sdbus::IObject& object_; +}; + +}} // namespaces + +#endif diff --git a/internal/platform/implementation/linux/generated/dbus/bluez/le_advertisement_manager_client.h b/internal/platform/implementation/linux/generated/dbus/bluez/le_advertisement_manager_client.h new file mode 100644 index 00000000..eb3125df --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/bluez/le_advertisement_manager_client.h @@ -0,0 +1,77 @@ + +/* + * This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT! + */ + +#ifndef __sdbuscpp__generated_dbus_bluez_le_advertisement_manager_client_h__proxy__H__ +#define __sdbuscpp__generated_dbus_bluez_le_advertisement_manager_client_h__proxy__H__ + +#include +#include +#include + +namespace org { +namespace bluez { + +class LEAdvertisingManager1_proxy +{ +public: + static constexpr const char* INTERFACE_NAME = "org.bluez.LEAdvertisingManager1"; + +protected: + LEAdvertisingManager1_proxy(sdbus::IProxy& proxy) + : proxy_(proxy) + { + } + + ~LEAdvertisingManager1_proxy() = default; + +public: + void RegisterAdvertisement(const sdbus::ObjectPath& advertisement, const std::map& options) + { + proxy_.callMethod("RegisterAdvertisement").onInterface(INTERFACE_NAME).withArguments(advertisement, options); + } + + void UnregisterAdvertisement(const sdbus::ObjectPath& service) + { + proxy_.callMethod("UnregisterAdvertisement").onInterface(INTERFACE_NAME).withArguments(service); + } + +public: + uint8_t ActiveInstances() + { + return proxy_.getProperty("ActiveInstances").onInterface(INTERFACE_NAME); + } + + uint8_t SupportedInstances() + { + return proxy_.getProperty("SupportedInstances").onInterface(INTERFACE_NAME); + } + + std::vector SupportedIncludes() + { + return proxy_.getProperty("SupportedIncludes").onInterface(INTERFACE_NAME); + } + + std::vector SupportedSecondaryChannels() + { + return proxy_.getProperty("SupportedSecondaryChannels").onInterface(INTERFACE_NAME); + } + + std::vector SupportedFeatures() + { + return proxy_.getProperty("SupportedFeatures").onInterface(INTERFACE_NAME); + } + + std::map SupportedCapabilities() + { + return proxy_.getProperty("SupportedCapabilities").onInterface(INTERFACE_NAME); + } + +private: + sdbus::IProxy& proxy_; +}; + +}} // namespaces + +#endif diff --git a/internal/platform/implementation/linux/generated/dbus/bluez/le_advertisement_server.h b/internal/platform/implementation/linux/generated/dbus/bluez/le_advertisement_server.h new file mode 100644 index 00000000..89f6c0ef --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/bluez/le_advertisement_server.h @@ -0,0 +1,65 @@ + +/* + * This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT! + */ + +#ifndef __sdbuscpp__generated_dbus_bluez_le_advertisement_server_h__adaptor__H__ +#define __sdbuscpp__generated_dbus_bluez_le_advertisement_server_h__adaptor__H__ + +#include +#include +#include + +namespace org { +namespace bluez { + +class LEAdvertisement1_adaptor +{ +public: + static constexpr const char* INTERFACE_NAME = "org.bluez.LEAdvertisement1"; + +protected: + LEAdvertisement1_adaptor(sdbus::IObject& object) + : object_(object) + { + object_.registerMethod("Release").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->Release(); }); + object_.registerProperty("Type").onInterface(INTERFACE_NAME).withGetter([this](){ return this->Type(); }); + object_.registerProperty("ServiceUUIDs").onInterface(INTERFACE_NAME).withGetter([this](){ return this->ServiceUUIDs(); }); + object_.registerProperty("ManufacturerData").onInterface(INTERFACE_NAME).withGetter([this](){ return this->ManufacturerData(); }); + object_.registerProperty("SolicitUUIDs").onInterface(INTERFACE_NAME).withGetter([this](){ return this->SolicitUUIDs(); }); + object_.registerProperty("ServiceData").onInterface(INTERFACE_NAME).withGetter([this](){ return this->ServiceData(); }); + object_.registerProperty("Includes").onInterface(INTERFACE_NAME).withGetter([this](){ return this->Includes(); }); + object_.registerProperty("LocalName").onInterface(INTERFACE_NAME).withGetter([this](){ return this->LocalName(); }); + object_.registerProperty("Duration").onInterface(INTERFACE_NAME).withGetter([this](){ return this->Duration(); }); + object_.registerProperty("Timeout").onInterface(INTERFACE_NAME).withGetter([this](){ return this->Timeout(); }); + object_.registerProperty("MinInterval").onInterface(INTERFACE_NAME).withGetter([this](){ return this->MinInterval(); }); + object_.registerProperty("MaxInterval").onInterface(INTERFACE_NAME).withGetter([this](){ return this->MaxInterval(); }); + object_.registerProperty("TxPower").onInterface(INTERFACE_NAME).withGetter([this](){ return this->TxPower(); }); + } + + ~LEAdvertisement1_adaptor() = default; + +private: + virtual void Release() = 0; + +private: + virtual std::string Type() = 0; + virtual std::vector ServiceUUIDs() = 0; + virtual std::map ManufacturerData() = 0; + virtual std::vector SolicitUUIDs() = 0; + virtual std::map ServiceData() = 0; + virtual std::vector Includes() = 0; + virtual std::string LocalName() = 0; + virtual uint16_t Duration() = 0; + virtual uint16_t Timeout() = 0; + virtual uint32_t MinInterval() = 0; + virtual uint32_t MaxInterval() = 0; + virtual int16_t TxPower() = 0; + +private: + sdbus::IObject& object_; +}; + +}} // namespaces + +#endif diff --git a/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.Adapter1.xml b/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.Adapter1.xml new file mode 100644 index 00000000..b58180df --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.Adapter1.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.AdvertisementMonitor1.xml b/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.AdvertisementMonitor1.xml new file mode 100644 index 00000000..c92058ef --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.AdvertisementMonitor1.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.AdvertisementMonitorManager1.xml b/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.AdvertisementMonitorManager1.xml new file mode 100644 index 00000000..5b9f2e4c --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.AdvertisementMonitorManager1.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.Device1.xml b/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.Device1.xml new file mode 100644 index 00000000..09bb7a6b --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.Device1.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.GattCharacteristic1-client.xml b/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.GattCharacteristic1-client.xml new file mode 100644 index 00000000..94fcb206 --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.GattCharacteristic1-client.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.GattCharacteristic1.xml b/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.GattCharacteristic1.xml new file mode 100644 index 00000000..f99de722 --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.GattCharacteristic1.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.GattManager1.xml b/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.GattManager1.xml new file mode 100644 index 00000000..e353715b --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.GattManager1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.GattService1.xml b/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.GattService1.xml new file mode 100644 index 00000000..da4f8e70 --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.GattService1.xml @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.LEAdvertisement1.xml b/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.LEAdvertisement1.xml new file mode 100644 index 00000000..6fc4dcb0 --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.LEAdvertisement1.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + diff --git a/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.LEAdvertisementManager1.xml b/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.LEAdvertisementManager1.xml new file mode 100644 index 00000000..36bb0196 --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.LEAdvertisementManager1.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.Profile1.xml b/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.Profile1.xml new file mode 100644 index 00000000..5ce94584 --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.Profile1.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + diff --git a/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.ProfileManager1.xml b/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.ProfileManager1.xml new file mode 100644 index 00000000..0a43476e --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.ProfileManager1.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/internal/platform/implementation/linux/generated/dbus/bluez/profile_manager_client.h b/internal/platform/implementation/linux/generated/dbus/bluez/profile_manager_client.h new file mode 100644 index 00000000..61ee8e46 --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/bluez/profile_manager_client.h @@ -0,0 +1,46 @@ + +/* + * This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT! + */ + +#ifndef __sdbuscpp__bluez_profile_manager_client_glue_h__proxy__H__ +#define __sdbuscpp__bluez_profile_manager_client_glue_h__proxy__H__ + +#include +#include +#include + +namespace org { +namespace bluez { + +class ProfileManager1_proxy +{ +public: + static constexpr const char* INTERFACE_NAME = "org.bluez.ProfileManager1"; + +protected: + ProfileManager1_proxy(sdbus::IProxy& proxy) + : proxy_(proxy) + { + } + + ~ProfileManager1_proxy() = default; + +public: + void RegisterProfile(const sdbus::ObjectPath& profile, const std::string& UUID, const std::map& options) + { + proxy_.callMethod("RegisterProfile").onInterface(INTERFACE_NAME).withArguments(profile, UUID, options); + } + + void UnregisterProfile(const sdbus::ObjectPath& profile) + { + proxy_.callMethod("UnregisterProfile").onInterface(INTERFACE_NAME).withArguments(profile); + } + +private: + sdbus::IProxy& proxy_; +}; + +}} // namespaces + +#endif diff --git a/internal/platform/implementation/linux/generated/dbus/bluez/profile_server.h b/internal/platform/implementation/linux/generated/dbus/bluez/profile_server.h new file mode 100644 index 00000000..6ae6e00f --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/bluez/profile_server.h @@ -0,0 +1,43 @@ + +/* + * This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT! + */ + +#ifndef __sdbuscpp__bluez_profile_glue_h__adaptor__H__ +#define __sdbuscpp__bluez_profile_glue_h__adaptor__H__ + +#include +#include +#include + +namespace org { +namespace bluez { + +class Profile1_adaptor +{ +public: + static constexpr const char* INTERFACE_NAME = "org.bluez.Profile1"; + +protected: + Profile1_adaptor(sdbus::IObject& object) + : object_(object) + { + object_.registerMethod("Release").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->Release(); }); + object_.registerMethod("NewConnection").onInterface(INTERFACE_NAME).withInputParamNames("device", "fd", "fd_properties").implementedAs([this](const sdbus::ObjectPath& device, const sdbus::UnixFd& fd, const std::map& fd_properties){ return this->NewConnection(device, fd, fd_properties); }); + object_.registerMethod("RequestDisconnection").onInterface(INTERFACE_NAME).withInputParamNames("device").implementedAs([this](const sdbus::ObjectPath& device){ return this->RequestDisconnection(device); }); + } + + ~Profile1_adaptor() = default; + +private: + virtual void Release() = 0; + virtual void NewConnection(const sdbus::ObjectPath& device, const sdbus::UnixFd& fd, const std::map& fd_properties) = 0; + virtual void RequestDisconnection(const sdbus::ObjectPath& device) = 0; + +private: + sdbus::IObject& object_; +}; + +}} // namespaces + +#endif diff --git a/internal/platform/implementation/linux/generated/dbus/hostname/hostname_client.h b/internal/platform/implementation/linux/generated/dbus/hostname/hostname_client.h new file mode 100644 index 00000000..de07c548 --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/hostname/hostname_client.h @@ -0,0 +1,198 @@ + +/* + * This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT! + */ + +#ifndef __sdbuscpp__hostname_client_glue_h__proxy__H__ +#define __sdbuscpp__hostname_client_glue_h__proxy__H__ + +#include +#include +#include + +namespace org { +namespace freedesktop { + +class hostname1_proxy +{ +public: + static constexpr const char* INTERFACE_NAME = "org.freedesktop.hostname1"; + +protected: + hostname1_proxy(sdbus::IProxy& proxy) + : proxy_(proxy) + { + } + + ~hostname1_proxy() = default; + +public: + void SetHostname(const std::string& hostname, const bool& interactive) + { + proxy_.callMethod("SetHostname").onInterface(INTERFACE_NAME).withArguments(hostname, interactive); + } + + void SetStaticHostname(const std::string& hostname, const bool& interactive) + { + proxy_.callMethod("SetStaticHostname").onInterface(INTERFACE_NAME).withArguments(hostname, interactive); + } + + void SetPrettyHostname(const std::string& hostname, const bool& interactive) + { + proxy_.callMethod("SetPrettyHostname").onInterface(INTERFACE_NAME).withArguments(hostname, interactive); + } + + void SetIconName(const std::string& icon, const bool& interactive) + { + proxy_.callMethod("SetIconName").onInterface(INTERFACE_NAME).withArguments(icon, interactive); + } + + void SetChassis(const std::string& chassis, const bool& interactive) + { + proxy_.callMethod("SetChassis").onInterface(INTERFACE_NAME).withArguments(chassis, interactive); + } + + void SetDeployment(const std::string& deployment, const bool& interactive) + { + proxy_.callMethod("SetDeployment").onInterface(INTERFACE_NAME).withArguments(deployment, interactive); + } + + void SetLocation(const std::string& location, const bool& interactive) + { + proxy_.callMethod("SetLocation").onInterface(INTERFACE_NAME).withArguments(location, interactive); + } + + std::vector GetProductUUID(const bool& interactive) + { + std::vector result; + proxy_.callMethod("GetProductUUID").onInterface(INTERFACE_NAME).withArguments(interactive).storeResultsTo(result); + return result; + } + + std::string GetHardwareSerial() + { + std::string result; + proxy_.callMethod("GetHardwareSerial").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + std::string Describe() + { + std::string result; + proxy_.callMethod("Describe").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + +public: + std::string Hostname() + { + return proxy_.getProperty("Hostname").onInterface(INTERFACE_NAME); + } + + std::string StaticHostname() + { + return proxy_.getProperty("StaticHostname").onInterface(INTERFACE_NAME); + } + + std::string PrettyHostname() + { + return proxy_.getProperty("PrettyHostname").onInterface(INTERFACE_NAME); + } + + std::string DefaultHostname() + { + return proxy_.getProperty("DefaultHostname").onInterface(INTERFACE_NAME); + } + + std::string HostnameSource() + { + return proxy_.getProperty("HostnameSource").onInterface(INTERFACE_NAME); + } + + std::string IconName() + { + return proxy_.getProperty("IconName").onInterface(INTERFACE_NAME); + } + + std::string Chassis() + { + return proxy_.getProperty("Chassis").onInterface(INTERFACE_NAME); + } + + std::string Deployment() + { + return proxy_.getProperty("Deployment").onInterface(INTERFACE_NAME); + } + + std::string Location() + { + return proxy_.getProperty("Location").onInterface(INTERFACE_NAME); + } + + std::string KernelName() + { + return proxy_.getProperty("KernelName").onInterface(INTERFACE_NAME); + } + + std::string KernelRelease() + { + return proxy_.getProperty("KernelRelease").onInterface(INTERFACE_NAME); + } + + std::string KernelVersion() + { + return proxy_.getProperty("KernelVersion").onInterface(INTERFACE_NAME); + } + + std::string OperatingSystemPrettyName() + { + return proxy_.getProperty("OperatingSystemPrettyName").onInterface(INTERFACE_NAME); + } + + std::string OperatingSystemCPEName() + { + return proxy_.getProperty("OperatingSystemCPEName").onInterface(INTERFACE_NAME); + } + + uint64_t OperatingSystemSupportEnd() + { + return proxy_.getProperty("OperatingSystemSupportEnd").onInterface(INTERFACE_NAME); + } + + std::string HomeURL() + { + return proxy_.getProperty("HomeURL").onInterface(INTERFACE_NAME); + } + + std::string HardwareVendor() + { + return proxy_.getProperty("HardwareVendor").onInterface(INTERFACE_NAME); + } + + std::string HardwareModel() + { + return proxy_.getProperty("HardwareModel").onInterface(INTERFACE_NAME); + } + + std::string FirmwareVersion() + { + return proxy_.getProperty("FirmwareVersion").onInterface(INTERFACE_NAME); + } + + std::string FirmwareVendor() + { + return proxy_.getProperty("FirmwareVendor").onInterface(INTERFACE_NAME); + } + + uint64_t FirmwareDate() + { + return proxy_.getProperty("FirmwareDate").onInterface(INTERFACE_NAME); + } + +private: + sdbus::IProxy& proxy_; +}; + +}} // namespaces + +#endif diff --git a/internal/platform/implementation/linux/generated/dbus/hostname/org.freedesktop.hostname1.xml b/internal/platform/implementation/linux/generated/dbus/hostname/org.freedesktop.hostname1.xml new file mode 100644 index 00000000..822e99b2 --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/hostname/org.freedesktop.hostname1.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/internal/platform/implementation/linux/generated/dbus/logcontrol/logcontrol_server.h b/internal/platform/implementation/linux/generated/dbus/logcontrol/logcontrol_server.h new file mode 100644 index 00000000..f74e3d1a --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/logcontrol/logcontrol_server.h @@ -0,0 +1,45 @@ + +/* + * This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT! + */ + +#ifndef __sdbuscpp__org_freedesktop_logcontrol_server_glue_h__adaptor__H__ +#define __sdbuscpp__org_freedesktop_logcontrol_server_glue_h__adaptor__H__ + +#include +#include +#include + +namespace org { +namespace freedesktop { + +class LogControl1_adaptor +{ +public: + static constexpr const char* INTERFACE_NAME = "org.freedesktop.LogControl1"; + +protected: + LogControl1_adaptor(sdbus::IObject& object) + : object_(object) + { + object_.registerProperty("LogLevel").onInterface(INTERFACE_NAME).withGetter([this](){ return this->LogLevel(); }).withSetter([this](const std::string& value){ this->LogLevel(value); }).withUpdateBehavior(sdbus::Flags::EMITS_NO_SIGNAL).markAsPrivileged(); + object_.registerProperty("LogTarget").onInterface(INTERFACE_NAME).withGetter([this](){ return this->LogTarget(); }).withSetter([this](const std::string& value){ this->LogTarget(value); }).withUpdateBehavior(sdbus::Flags::EMITS_NO_SIGNAL).markAsPrivileged(); + object_.registerProperty("SyslogIdentifier").onInterface(INTERFACE_NAME).withGetter([this](){ return this->SyslogIdentifier(); }).withUpdateBehavior(sdbus::Flags::EMITS_NO_SIGNAL); + } + + ~LogControl1_adaptor() = default; + +private: + virtual std::string LogLevel() = 0; + virtual void LogLevel(const std::string& value) = 0; + virtual std::string LogTarget() = 0; + virtual void LogTarget(const std::string& value) = 0; + virtual std::string SyslogIdentifier() = 0; + +private: + sdbus::IObject& object_; +}; + +}} // namespaces + +#endif diff --git a/internal/platform/implementation/linux/generated/dbus/logcontrol/org.freedesktop.LogControl1.xml b/internal/platform/implementation/linux/generated/dbus/logcontrol/org.freedesktop.LogControl1.xml new file mode 100644 index 00000000..2c32116f --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/logcontrol/org.freedesktop.LogControl1.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + diff --git a/internal/platform/implementation/linux/generated/dbus/login/login_manager_client.h b/internal/platform/implementation/linux/generated/dbus/login/login_manager_client.h new file mode 100644 index 00000000..4de655b9 --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/login/login_manager_client.h @@ -0,0 +1,631 @@ + +/* + * This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT! + */ + +#ifndef __sdbuscpp__login_manager_client_glue_h__proxy__H__ +#define __sdbuscpp__login_manager_client_glue_h__proxy__H__ + +#include +#include +#include + +namespace org { +namespace freedesktop { +namespace login1 { + +class Manager_proxy +{ +public: + static constexpr const char* INTERFACE_NAME = "org.freedesktop.login1.Manager"; + +protected: + Manager_proxy(sdbus::IProxy& proxy) + : proxy_(proxy) + { + proxy_.uponSignal("SessionNew").onInterface(INTERFACE_NAME).call([this](const std::string& session_id, const sdbus::ObjectPath& object_path){ this->onSessionNew(session_id, object_path); }); + proxy_.uponSignal("SessionRemoved").onInterface(INTERFACE_NAME).call([this](const std::string& session_id, const sdbus::ObjectPath& object_path){ this->onSessionRemoved(session_id, object_path); }); + proxy_.uponSignal("UserNew").onInterface(INTERFACE_NAME).call([this](const uint32_t& uid, const sdbus::ObjectPath& object_path){ this->onUserNew(uid, object_path); }); + proxy_.uponSignal("UserRemoved").onInterface(INTERFACE_NAME).call([this](const uint32_t& uid, const sdbus::ObjectPath& object_path){ this->onUserRemoved(uid, object_path); }); + proxy_.uponSignal("SeatNew").onInterface(INTERFACE_NAME).call([this](const std::string& seat_id, const sdbus::ObjectPath& object_path){ this->onSeatNew(seat_id, object_path); }); + proxy_.uponSignal("SeatRemoved").onInterface(INTERFACE_NAME).call([this](const std::string& seat_id, const sdbus::ObjectPath& object_path){ this->onSeatRemoved(seat_id, object_path); }); + proxy_.uponSignal("PrepareForShutdown").onInterface(INTERFACE_NAME).call([this](const bool& start){ this->onPrepareForShutdown(start); }); + proxy_.uponSignal("PrepareForSleep").onInterface(INTERFACE_NAME).call([this](const bool& start){ this->onPrepareForSleep(start); }); + } + + ~Manager_proxy() = default; + + virtual void onSessionNew(const std::string& session_id, const sdbus::ObjectPath& object_path) = 0; + virtual void onSessionRemoved(const std::string& session_id, const sdbus::ObjectPath& object_path) = 0; + virtual void onUserNew(const uint32_t& uid, const sdbus::ObjectPath& object_path) = 0; + virtual void onUserRemoved(const uint32_t& uid, const sdbus::ObjectPath& object_path) = 0; + virtual void onSeatNew(const std::string& seat_id, const sdbus::ObjectPath& object_path) = 0; + virtual void onSeatRemoved(const std::string& seat_id, const sdbus::ObjectPath& object_path) = 0; + virtual void onPrepareForShutdown(const bool& start) = 0; + virtual void onPrepareForSleep(const bool& start) = 0; + +public: + sdbus::ObjectPath GetSession(const std::string& session_id) + { + sdbus::ObjectPath result; + proxy_.callMethod("GetSession").onInterface(INTERFACE_NAME).withArguments(session_id).storeResultsTo(result); + return result; + } + + sdbus::ObjectPath GetSessionByPID(const uint32_t& pid) + { + sdbus::ObjectPath result; + proxy_.callMethod("GetSessionByPID").onInterface(INTERFACE_NAME).withArguments(pid).storeResultsTo(result); + return result; + } + + sdbus::ObjectPath GetUser(const uint32_t& uid) + { + sdbus::ObjectPath result; + proxy_.callMethod("GetUser").onInterface(INTERFACE_NAME).withArguments(uid).storeResultsTo(result); + return result; + } + + sdbus::ObjectPath GetUserByPID(const uint32_t& pid) + { + sdbus::ObjectPath result; + proxy_.callMethod("GetUserByPID").onInterface(INTERFACE_NAME).withArguments(pid).storeResultsTo(result); + return result; + } + + sdbus::ObjectPath GetSeat(const std::string& seat_id) + { + sdbus::ObjectPath result; + proxy_.callMethod("GetSeat").onInterface(INTERFACE_NAME).withArguments(seat_id).storeResultsTo(result); + return result; + } + + std::vector> ListSessions() + { + std::vector> result; + proxy_.callMethod("ListSessions").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + std::vector> ListUsers() + { + std::vector> result; + proxy_.callMethod("ListUsers").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + std::vector> ListSeats() + { + std::vector> result; + proxy_.callMethod("ListSeats").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + std::vector> ListInhibitors() + { + std::vector> result; + proxy_.callMethod("ListInhibitors").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + std::tuple CreateSession(const uint32_t& uid, const uint32_t& pid, const std::string& service, const std::string& type, const std::string& class_, const std::string& desktop, const std::string& seat_id, const uint32_t& vtnr, const std::string& tty, const std::string& display, const bool& remote, const std::string& remote_user, const std::string& remote_host, const std::vector>& properties) + { + std::tuple result; + proxy_.callMethod("CreateSession").onInterface(INTERFACE_NAME).withArguments(uid, pid, service, type, class_, desktop, seat_id, vtnr, tty, display, remote, remote_user, remote_host, properties).storeResultsTo(result); + return result; + } + + void ReleaseSession(const std::string& session_id) + { + proxy_.callMethod("ReleaseSession").onInterface(INTERFACE_NAME).withArguments(session_id); + } + + void ActivateSession(const std::string& session_id) + { + proxy_.callMethod("ActivateSession").onInterface(INTERFACE_NAME).withArguments(session_id); + } + + void ActivateSessionOnSeat(const std::string& session_id, const std::string& seat_id) + { + proxy_.callMethod("ActivateSessionOnSeat").onInterface(INTERFACE_NAME).withArguments(session_id, seat_id); + } + + void LockSession(const std::string& session_id) + { + proxy_.callMethod("LockSession").onInterface(INTERFACE_NAME).withArguments(session_id); + } + + void UnlockSession(const std::string& session_id) + { + proxy_.callMethod("UnlockSession").onInterface(INTERFACE_NAME).withArguments(session_id); + } + + void LockSessions() + { + proxy_.callMethod("LockSessions").onInterface(INTERFACE_NAME); + } + + void UnlockSessions() + { + proxy_.callMethod("UnlockSessions").onInterface(INTERFACE_NAME); + } + + void KillSession(const std::string& session_id, const std::string& who, const int32_t& signal_number) + { + proxy_.callMethod("KillSession").onInterface(INTERFACE_NAME).withArguments(session_id, who, signal_number); + } + + void KillUser(const uint32_t& uid, const int32_t& signal_number) + { + proxy_.callMethod("KillUser").onInterface(INTERFACE_NAME).withArguments(uid, signal_number); + } + + void TerminateSession(const std::string& session_id) + { + proxy_.callMethod("TerminateSession").onInterface(INTERFACE_NAME).withArguments(session_id); + } + + void TerminateUser(const uint32_t& uid) + { + proxy_.callMethod("TerminateUser").onInterface(INTERFACE_NAME).withArguments(uid); + } + + void TerminateSeat(const std::string& seat_id) + { + proxy_.callMethod("TerminateSeat").onInterface(INTERFACE_NAME).withArguments(seat_id); + } + + void SetUserLinger(const uint32_t& uid, const bool& enable, const bool& interactive) + { + proxy_.callMethod("SetUserLinger").onInterface(INTERFACE_NAME).withArguments(uid, enable, interactive); + } + + void AttachDevice(const std::string& seat_id, const std::string& sysfs_path, const bool& interactive) + { + proxy_.callMethod("AttachDevice").onInterface(INTERFACE_NAME).withArguments(seat_id, sysfs_path, interactive); + } + + void FlushDevices(const bool& interactive) + { + proxy_.callMethod("FlushDevices").onInterface(INTERFACE_NAME).withArguments(interactive); + } + + void PowerOff(const bool& interactive) + { + proxy_.callMethod("PowerOff").onInterface(INTERFACE_NAME).withArguments(interactive); + } + + void PowerOffWithFlags(const uint64_t& flags) + { + proxy_.callMethod("PowerOffWithFlags").onInterface(INTERFACE_NAME).withArguments(flags); + } + + void Reboot(const bool& interactive) + { + proxy_.callMethod("Reboot").onInterface(INTERFACE_NAME).withArguments(interactive); + } + + void RebootWithFlags(const uint64_t& flags) + { + proxy_.callMethod("RebootWithFlags").onInterface(INTERFACE_NAME).withArguments(flags); + } + + void Halt(const bool& interactive) + { + proxy_.callMethod("Halt").onInterface(INTERFACE_NAME).withArguments(interactive); + } + + void HaltWithFlags(const uint64_t& flags) + { + proxy_.callMethod("HaltWithFlags").onInterface(INTERFACE_NAME).withArguments(flags); + } + + void Suspend(const bool& interactive) + { + proxy_.callMethod("Suspend").onInterface(INTERFACE_NAME).withArguments(interactive); + } + + void SuspendWithFlags(const uint64_t& flags) + { + proxy_.callMethod("SuspendWithFlags").onInterface(INTERFACE_NAME).withArguments(flags); + } + + void Hibernate(const bool& interactive) + { + proxy_.callMethod("Hibernate").onInterface(INTERFACE_NAME).withArguments(interactive); + } + + void HibernateWithFlags(const uint64_t& flags) + { + proxy_.callMethod("HibernateWithFlags").onInterface(INTERFACE_NAME).withArguments(flags); + } + + void HybridSleep(const bool& interactive) + { + proxy_.callMethod("HybridSleep").onInterface(INTERFACE_NAME).withArguments(interactive); + } + + void HybridSleepWithFlags(const uint64_t& flags) + { + proxy_.callMethod("HybridSleepWithFlags").onInterface(INTERFACE_NAME).withArguments(flags); + } + + void SuspendThenHibernate(const bool& interactive) + { + proxy_.callMethod("SuspendThenHibernate").onInterface(INTERFACE_NAME).withArguments(interactive); + } + + void SuspendThenHibernateWithFlags(const uint64_t& flags) + { + proxy_.callMethod("SuspendThenHibernateWithFlags").onInterface(INTERFACE_NAME).withArguments(flags); + } + + std::string CanPowerOff() + { + std::string result; + proxy_.callMethod("CanPowerOff").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + std::string CanReboot() + { + std::string result; + proxy_.callMethod("CanReboot").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + std::string CanHalt() + { + std::string result; + proxy_.callMethod("CanHalt").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + std::string CanSuspend() + { + std::string result; + proxy_.callMethod("CanSuspend").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + std::string CanHibernate() + { + std::string result; + proxy_.callMethod("CanHibernate").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + std::string CanHybridSleep() + { + std::string result; + proxy_.callMethod("CanHybridSleep").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + std::string CanSuspendThenHibernate() + { + std::string result; + proxy_.callMethod("CanSuspendThenHibernate").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + void ScheduleShutdown(const std::string& type, const uint64_t& usec) + { + proxy_.callMethod("ScheduleShutdown").onInterface(INTERFACE_NAME).withArguments(type, usec); + } + + bool CancelScheduledShutdown() + { + bool result; + proxy_.callMethod("CancelScheduledShutdown").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + sdbus::UnixFd Inhibit(const std::string& what, const std::string& who, const std::string& why, const std::string& mode) + { + sdbus::UnixFd result; + proxy_.callMethod("Inhibit").onInterface(INTERFACE_NAME).withArguments(what, who, why, mode).storeResultsTo(result); + return result; + } + + std::string CanRebootParameter() + { + std::string result; + proxy_.callMethod("CanRebootParameter").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + void SetRebootParameter(const std::string& parameter) + { + proxy_.callMethod("SetRebootParameter").onInterface(INTERFACE_NAME).withArguments(parameter); + } + + std::string CanRebootToFirmwareSetup() + { + std::string result; + proxy_.callMethod("CanRebootToFirmwareSetup").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + void SetRebootToFirmwareSetup(const bool& enable) + { + proxy_.callMethod("SetRebootToFirmwareSetup").onInterface(INTERFACE_NAME).withArguments(enable); + } + + std::string CanRebootToBootLoaderMenu() + { + std::string result; + proxy_.callMethod("CanRebootToBootLoaderMenu").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + void SetRebootToBootLoaderMenu(const uint64_t& timeout) + { + proxy_.callMethod("SetRebootToBootLoaderMenu").onInterface(INTERFACE_NAME).withArguments(timeout); + } + + std::string CanRebootToBootLoaderEntry() + { + std::string result; + proxy_.callMethod("CanRebootToBootLoaderEntry").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + void SetRebootToBootLoaderEntry(const std::string& boot_loader_entry) + { + proxy_.callMethod("SetRebootToBootLoaderEntry").onInterface(INTERFACE_NAME).withArguments(boot_loader_entry); + } + + void SetWallMessage(const std::string& wall_message, const bool& enable) + { + proxy_.callMethod("SetWallMessage").onInterface(INTERFACE_NAME).withArguments(wall_message, enable); + } + +public: + bool EnableWallMessages() + { + return proxy_.getProperty("EnableWallMessages").onInterface(INTERFACE_NAME); + } + + void EnableWallMessages(const bool& value) + { + proxy_.setProperty("EnableWallMessages").onInterface(INTERFACE_NAME).toValue(value); + } + + std::string WallMessage() + { + return proxy_.getProperty("WallMessage").onInterface(INTERFACE_NAME); + } + + void WallMessage(const std::string& value) + { + proxy_.setProperty("WallMessage").onInterface(INTERFACE_NAME).toValue(value); + } + + uint32_t NAutoVTs() + { + return proxy_.getProperty("NAutoVTs").onInterface(INTERFACE_NAME); + } + + std::vector KillOnlyUsers() + { + return proxy_.getProperty("KillOnlyUsers").onInterface(INTERFACE_NAME); + } + + std::vector KillExcludeUsers() + { + return proxy_.getProperty("KillExcludeUsers").onInterface(INTERFACE_NAME); + } + + bool KillUserProcesses() + { + return proxy_.getProperty("KillUserProcesses").onInterface(INTERFACE_NAME); + } + + std::string RebootParameter() + { + return proxy_.getProperty("RebootParameter").onInterface(INTERFACE_NAME); + } + + bool RebootToFirmwareSetup() + { + return proxy_.getProperty("RebootToFirmwareSetup").onInterface(INTERFACE_NAME); + } + + uint64_t RebootToBootLoaderMenu() + { + return proxy_.getProperty("RebootToBootLoaderMenu").onInterface(INTERFACE_NAME); + } + + std::string RebootToBootLoaderEntry() + { + return proxy_.getProperty("RebootToBootLoaderEntry").onInterface(INTERFACE_NAME); + } + + std::vector BootLoaderEntries() + { + return proxy_.getProperty("BootLoaderEntries").onInterface(INTERFACE_NAME); + } + + bool IdleHint() + { + return proxy_.getProperty("IdleHint").onInterface(INTERFACE_NAME); + } + + uint64_t IdleSinceHint() + { + return proxy_.getProperty("IdleSinceHint").onInterface(INTERFACE_NAME); + } + + uint64_t IdleSinceHintMonotonic() + { + return proxy_.getProperty("IdleSinceHintMonotonic").onInterface(INTERFACE_NAME); + } + + std::string BlockInhibited() + { + return proxy_.getProperty("BlockInhibited").onInterface(INTERFACE_NAME); + } + + std::string DelayInhibited() + { + return proxy_.getProperty("DelayInhibited").onInterface(INTERFACE_NAME); + } + + uint64_t InhibitDelayMaxUSec() + { + return proxy_.getProperty("InhibitDelayMaxUSec").onInterface(INTERFACE_NAME); + } + + uint64_t UserStopDelayUSec() + { + return proxy_.getProperty("UserStopDelayUSec").onInterface(INTERFACE_NAME); + } + + std::string HandlePowerKey() + { + return proxy_.getProperty("HandlePowerKey").onInterface(INTERFACE_NAME); + } + + std::string HandlePowerKeyLongPress() + { + return proxy_.getProperty("HandlePowerKeyLongPress").onInterface(INTERFACE_NAME); + } + + std::string HandleRebootKey() + { + return proxy_.getProperty("HandleRebootKey").onInterface(INTERFACE_NAME); + } + + std::string HandleRebootKeyLongPress() + { + return proxy_.getProperty("HandleRebootKeyLongPress").onInterface(INTERFACE_NAME); + } + + std::string HandleSuspendKey() + { + return proxy_.getProperty("HandleSuspendKey").onInterface(INTERFACE_NAME); + } + + std::string HandleSuspendKeyLongPress() + { + return proxy_.getProperty("HandleSuspendKeyLongPress").onInterface(INTERFACE_NAME); + } + + std::string HandleHibernateKey() + { + return proxy_.getProperty("HandleHibernateKey").onInterface(INTERFACE_NAME); + } + + std::string HandleHibernateKeyLongPress() + { + return proxy_.getProperty("HandleHibernateKeyLongPress").onInterface(INTERFACE_NAME); + } + + std::string HandleLidSwitch() + { + return proxy_.getProperty("HandleLidSwitch").onInterface(INTERFACE_NAME); + } + + std::string HandleLidSwitchExternalPower() + { + return proxy_.getProperty("HandleLidSwitchExternalPower").onInterface(INTERFACE_NAME); + } + + std::string HandleLidSwitchDocked() + { + return proxy_.getProperty("HandleLidSwitchDocked").onInterface(INTERFACE_NAME); + } + + uint64_t HoldoffTimeoutUSec() + { + return proxy_.getProperty("HoldoffTimeoutUSec").onInterface(INTERFACE_NAME); + } + + std::string IdleAction() + { + return proxy_.getProperty("IdleAction").onInterface(INTERFACE_NAME); + } + + uint64_t IdleActionUSec() + { + return proxy_.getProperty("IdleActionUSec").onInterface(INTERFACE_NAME); + } + + bool PreparingForShutdown() + { + return proxy_.getProperty("PreparingForShutdown").onInterface(INTERFACE_NAME); + } + + bool PreparingForSleep() + { + return proxy_.getProperty("PreparingForSleep").onInterface(INTERFACE_NAME); + } + + sdbus::Struct ScheduledShutdown() + { + return proxy_.getProperty("ScheduledShutdown").onInterface(INTERFACE_NAME); + } + + bool Docked() + { + return proxy_.getProperty("Docked").onInterface(INTERFACE_NAME); + } + + bool LidClosed() + { + return proxy_.getProperty("LidClosed").onInterface(INTERFACE_NAME); + } + + bool OnExternalPower() + { + return proxy_.getProperty("OnExternalPower").onInterface(INTERFACE_NAME); + } + + bool RemoveIPC() + { + return proxy_.getProperty("RemoveIPC").onInterface(INTERFACE_NAME); + } + + uint64_t RuntimeDirectorySize() + { + return proxy_.getProperty("RuntimeDirectorySize").onInterface(INTERFACE_NAME); + } + + uint64_t RuntimeDirectoryInodesMax() + { + return proxy_.getProperty("RuntimeDirectoryInodesMax").onInterface(INTERFACE_NAME); + } + + uint64_t InhibitorsMax() + { + return proxy_.getProperty("InhibitorsMax").onInterface(INTERFACE_NAME); + } + + uint64_t NCurrentInhibitors() + { + return proxy_.getProperty("NCurrentInhibitors").onInterface(INTERFACE_NAME); + } + + uint64_t SessionsMax() + { + return proxy_.getProperty("SessionsMax").onInterface(INTERFACE_NAME); + } + + uint64_t NCurrentSessions() + { + return proxy_.getProperty("NCurrentSessions").onInterface(INTERFACE_NAME); + } + + uint64_t StopIdleSessionUSec() + { + return proxy_.getProperty("StopIdleSessionUSec").onInterface(INTERFACE_NAME); + } + +private: + sdbus::IProxy& proxy_; +}; + +}}} // namespaces + +#endif diff --git a/internal/platform/implementation/linux/generated/dbus/login/login_session_client.h b/internal/platform/implementation/linux/generated/dbus/login/login_session_client.h new file mode 100644 index 00000000..77cd9e97 --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/login/login_session_client.h @@ -0,0 +1,254 @@ + +/* + * This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT! + */ + +#ifndef __sdbuscpp__login_session_client_glue_h__proxy__H__ +#define __sdbuscpp__login_session_client_glue_h__proxy__H__ + +#include +#include +#include + +namespace org { +namespace freedesktop { +namespace login1 { + +class Session_proxy +{ +public: + static constexpr const char* INTERFACE_NAME = "org.freedesktop.login1.Session"; + +protected: + Session_proxy(sdbus::IProxy& proxy) + : proxy_(proxy) + { + proxy_.uponSignal("PauseDevice").onInterface(INTERFACE_NAME).call([this](const uint32_t& major, const uint32_t& minor, const std::string& type){ this->onPauseDevice(major, minor, type); }); + proxy_.uponSignal("ResumeDevice").onInterface(INTERFACE_NAME).call([this](const uint32_t& major, const uint32_t& minor, const sdbus::UnixFd& fd){ this->onResumeDevice(major, minor, fd); }); + proxy_.uponSignal("Lock").onInterface(INTERFACE_NAME).call([this](){ this->onLock(); }); + proxy_.uponSignal("Unlock").onInterface(INTERFACE_NAME).call([this](){ this->onUnlock(); }); + } + + ~Session_proxy() = default; + + virtual void onPauseDevice(const uint32_t& major, const uint32_t& minor, const std::string& type) = 0; + virtual void onResumeDevice(const uint32_t& major, const uint32_t& minor, const sdbus::UnixFd& fd) = 0; + virtual void onLock() = 0; + virtual void onUnlock() = 0; + +public: + void Terminate() + { + proxy_.callMethod("Terminate").onInterface(INTERFACE_NAME); + } + + void Activate() + { + proxy_.callMethod("Activate").onInterface(INTERFACE_NAME); + } + + void Lock() + { + proxy_.callMethod("Lock").onInterface(INTERFACE_NAME); + } + + void Unlock() + { + proxy_.callMethod("Unlock").onInterface(INTERFACE_NAME); + } + + void SetIdleHint(const bool& idle) + { + proxy_.callMethod("SetIdleHint").onInterface(INTERFACE_NAME).withArguments(idle); + } + + void SetLockedHint(const bool& locked) + { + proxy_.callMethod("SetLockedHint").onInterface(INTERFACE_NAME).withArguments(locked); + } + + void Kill(const std::string& who, const int32_t& signal_number) + { + proxy_.callMethod("Kill").onInterface(INTERFACE_NAME).withArguments(who, signal_number); + } + + void TakeControl(const bool& force) + { + proxy_.callMethod("TakeControl").onInterface(INTERFACE_NAME).withArguments(force); + } + + void ReleaseControl() + { + proxy_.callMethod("ReleaseControl").onInterface(INTERFACE_NAME); + } + + void SetType(const std::string& type) + { + proxy_.callMethod("SetType").onInterface(INTERFACE_NAME).withArguments(type); + } + + void SetDisplay(const std::string& display) + { + proxy_.callMethod("SetDisplay").onInterface(INTERFACE_NAME).withArguments(display); + } + + void SetTTY(const sdbus::UnixFd& tty_fd) + { + proxy_.callMethod("SetTTY").onInterface(INTERFACE_NAME).withArguments(tty_fd); + } + + std::tuple TakeDevice(const uint32_t& major, const uint32_t& minor) + { + std::tuple result; + proxy_.callMethod("TakeDevice").onInterface(INTERFACE_NAME).withArguments(major, minor).storeResultsTo(result); + return result; + } + + void ReleaseDevice(const uint32_t& major, const uint32_t& minor) + { + proxy_.callMethod("ReleaseDevice").onInterface(INTERFACE_NAME).withArguments(major, minor); + } + + void PauseDeviceComplete(const uint32_t& major, const uint32_t& minor) + { + proxy_.callMethod("PauseDeviceComplete").onInterface(INTERFACE_NAME).withArguments(major, minor); + } + + void SetBrightness(const std::string& subsystem, const std::string& name, const uint32_t& brightness) + { + proxy_.callMethod("SetBrightness").onInterface(INTERFACE_NAME).withArguments(subsystem, name, brightness); + } + +public: + std::string Id() + { + return proxy_.getProperty("Id").onInterface(INTERFACE_NAME); + } + + sdbus::Struct User() + { + return proxy_.getProperty("User").onInterface(INTERFACE_NAME); + } + + std::string Name() + { + return proxy_.getProperty("Name").onInterface(INTERFACE_NAME); + } + + uint64_t Timestamp() + { + return proxy_.getProperty("Timestamp").onInterface(INTERFACE_NAME); + } + + uint64_t TimestampMonotonic() + { + return proxy_.getProperty("TimestampMonotonic").onInterface(INTERFACE_NAME); + } + + uint32_t VTNr() + { + return proxy_.getProperty("VTNr").onInterface(INTERFACE_NAME); + } + + sdbus::Struct Seat() + { + return proxy_.getProperty("Seat").onInterface(INTERFACE_NAME); + } + + std::string TTY() + { + return proxy_.getProperty("TTY").onInterface(INTERFACE_NAME); + } + + std::string Display() + { + return proxy_.getProperty("Display").onInterface(INTERFACE_NAME); + } + + bool Remote() + { + return proxy_.getProperty("Remote").onInterface(INTERFACE_NAME); + } + + std::string RemoteHost() + { + return proxy_.getProperty("RemoteHost").onInterface(INTERFACE_NAME); + } + + std::string RemoteUser() + { + return proxy_.getProperty("RemoteUser").onInterface(INTERFACE_NAME); + } + + std::string Service() + { + return proxy_.getProperty("Service").onInterface(INTERFACE_NAME); + } + + std::string Desktop() + { + return proxy_.getProperty("Desktop").onInterface(INTERFACE_NAME); + } + + std::string Scope() + { + return proxy_.getProperty("Scope").onInterface(INTERFACE_NAME); + } + + uint32_t Leader() + { + return proxy_.getProperty("Leader").onInterface(INTERFACE_NAME); + } + + uint32_t Audit() + { + return proxy_.getProperty("Audit").onInterface(INTERFACE_NAME); + } + + std::string Type() + { + return proxy_.getProperty("Type").onInterface(INTERFACE_NAME); + } + + std::string Class() + { + return proxy_.getProperty("Class").onInterface(INTERFACE_NAME); + } + + bool Active() + { + return proxy_.getProperty("Active").onInterface(INTERFACE_NAME); + } + + std::string State() + { + return proxy_.getProperty("State").onInterface(INTERFACE_NAME); + } + + bool IdleHint() + { + return proxy_.getProperty("IdleHint").onInterface(INTERFACE_NAME); + } + + uint64_t IdleSinceHint() + { + return proxy_.getProperty("IdleSinceHint").onInterface(INTERFACE_NAME); + } + + uint64_t IdleSinceHintMonotonic() + { + return proxy_.getProperty("IdleSinceHintMonotonic").onInterface(INTERFACE_NAME); + } + + bool LockedHint() + { + return proxy_.getProperty("LockedHint").onInterface(INTERFACE_NAME); + } + +private: + sdbus::IProxy& proxy_; +}; + +}}} // namespaces + +#endif diff --git a/internal/platform/implementation/linux/generated/dbus/login/org.freedesktop.login1.Manager.xml b/internal/platform/implementation/linux/generated/dbus/login/org.freedesktop.login1.Manager.xml new file mode 100644 index 00000000..52417e27 --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/login/org.freedesktop.login1.Manager.xml @@ -0,0 +1,388 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/internal/platform/implementation/linux/generated/dbus/login/org.freedesktop.login1.Session.xml b/internal/platform/implementation/linux/generated/dbus/login/org.freedesktop.login1.Session.xml new file mode 100644 index 00000000..dbad22f3 --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/login/org.freedesktop.login1.Session.xml @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/internal/platform/implementation/linux/generated/dbus/networkmanager/access_point_client.h b/internal/platform/implementation/linux/generated/dbus/networkmanager/access_point_client.h new file mode 100644 index 00000000..0408af2c --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/networkmanager/access_point_client.h @@ -0,0 +1,87 @@ + +/* + * This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT! + */ + +#ifndef __sdbuscpp__networkmanager_accesspoint_client_glue_h__proxy__H__ +#define __sdbuscpp__networkmanager_accesspoint_client_glue_h__proxy__H__ + +#include +#include +#include + +namespace org { +namespace freedesktop { +namespace NetworkManager { + +class AccessPoint_proxy +{ +public: + static constexpr const char* INTERFACE_NAME = "org.freedesktop.NetworkManager.AccessPoint"; + +protected: + AccessPoint_proxy(sdbus::IProxy& proxy) + : proxy_(proxy) + { + } + + ~AccessPoint_proxy() = default; + +public: + uint32_t Flags() + { + return proxy_.getProperty("Flags").onInterface(INTERFACE_NAME); + } + + uint32_t WpaFlags() + { + return proxy_.getProperty("WpaFlags").onInterface(INTERFACE_NAME); + } + + uint32_t RsnFlags() + { + return proxy_.getProperty("RsnFlags").onInterface(INTERFACE_NAME); + } + + std::vector Ssid() + { + return proxy_.getProperty("Ssid").onInterface(INTERFACE_NAME); + } + + uint32_t Frequency() + { + return proxy_.getProperty("Frequency").onInterface(INTERFACE_NAME); + } + + std::string HwAddress() + { + return proxy_.getProperty("HwAddress").onInterface(INTERFACE_NAME); + } + + uint32_t Mode() + { + return proxy_.getProperty("Mode").onInterface(INTERFACE_NAME); + } + + uint32_t MaxBitrate() + { + return proxy_.getProperty("MaxBitrate").onInterface(INTERFACE_NAME); + } + + uint8_t Strength() + { + return proxy_.getProperty("Strength").onInterface(INTERFACE_NAME); + } + + int32_t LastSeen() + { + return proxy_.getProperty("LastSeen").onInterface(INTERFACE_NAME); + } + +private: + sdbus::IProxy& proxy_; +}; + +}}} // namespaces + +#endif diff --git a/internal/platform/implementation/linux/generated/dbus/networkmanager/connection_active_client.h b/internal/platform/implementation/linux/generated/dbus/networkmanager/connection_active_client.h new file mode 100644 index 00000000..8b077ad6 --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/networkmanager/connection_active_client.h @@ -0,0 +1,126 @@ + +/* + * This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT! + */ + +#ifndef __sdbuscpp__networkmanager_connection_active_client_glue_h__proxy__H__ +#define __sdbuscpp__networkmanager_connection_active_client_glue_h__proxy__H__ + +#include +#include +#include + +namespace org { +namespace freedesktop { +namespace NetworkManager { +namespace Connection { + +class Active_proxy +{ +public: + static constexpr const char* INTERFACE_NAME = "org.freedesktop.NetworkManager.Connection.Active"; + +protected: + Active_proxy(sdbus::IProxy& proxy) + : proxy_(proxy) + { + proxy_.uponSignal("StateChanged").onInterface(INTERFACE_NAME).call([this](const uint32_t& state, const uint32_t& reason){ this->onStateChanged(state, reason); }); + } + + ~Active_proxy() = default; + + virtual void onStateChanged(const uint32_t& state, const uint32_t& reason) = 0; + +public: + sdbus::ObjectPath Connection() + { + return proxy_.getProperty("Connection").onInterface(INTERFACE_NAME); + } + + sdbus::ObjectPath SpecificObject() + { + return proxy_.getProperty("SpecificObject").onInterface(INTERFACE_NAME); + } + + std::string Id() + { + return proxy_.getProperty("Id").onInterface(INTERFACE_NAME); + } + + std::string Uuid() + { + return proxy_.getProperty("Uuid").onInterface(INTERFACE_NAME); + } + + std::string Type() + { + return proxy_.getProperty("Type").onInterface(INTERFACE_NAME); + } + + std::vector Devices() + { + return proxy_.getProperty("Devices").onInterface(INTERFACE_NAME); + } + + uint32_t State() + { + return proxy_.getProperty("State").onInterface(INTERFACE_NAME); + } + + uint32_t StateFlags() + { + return proxy_.getProperty("StateFlags").onInterface(INTERFACE_NAME); + } + + bool Default() + { + return proxy_.getProperty("Default").onInterface(INTERFACE_NAME); + } + + sdbus::ObjectPath Ip4Config() + { + return proxy_.getProperty("Ip4Config").onInterface(INTERFACE_NAME); + } + + sdbus::ObjectPath Dhcp4Config() + { + return proxy_.getProperty("Dhcp4Config").onInterface(INTERFACE_NAME); + } + + bool Default6() + { + return proxy_.getProperty("Default6").onInterface(INTERFACE_NAME); + } + + sdbus::ObjectPath Ip6Config() + { + return proxy_.getProperty("Ip6Config").onInterface(INTERFACE_NAME); + } + + sdbus::ObjectPath Dhcp6Config() + { + return proxy_.getProperty("Dhcp6Config").onInterface(INTERFACE_NAME); + } + + bool Vpn() + { + return proxy_.getProperty("Vpn").onInterface(INTERFACE_NAME); + } + + sdbus::ObjectPath Controller() + { + return proxy_.getProperty("Controller").onInterface(INTERFACE_NAME); + } + + sdbus::ObjectPath Master() + { + return proxy_.getProperty("Master").onInterface(INTERFACE_NAME); + } + +private: + sdbus::IProxy& proxy_; +}; + +}}}} // namespaces + +#endif diff --git a/internal/platform/implementation/linux/generated/dbus/networkmanager/device_wifip2p_client.h b/internal/platform/implementation/linux/generated/dbus/networkmanager/device_wifip2p_client.h new file mode 100644 index 00000000..df968c97 --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/networkmanager/device_wifip2p_client.h @@ -0,0 +1,64 @@ + +/* + * This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT! + */ + +#ifndef __sdbuscpp__networkmanager_device_wifip2p_client_glue_h__proxy__H__ +#define __sdbuscpp__networkmanager_device_wifip2p_client_glue_h__proxy__H__ + +#include +#include +#include + +namespace org { +namespace freedesktop { +namespace NetworkManager { +namespace Device { + +class WifiP2P_proxy +{ +public: + static constexpr const char* INTERFACE_NAME = "org.freedesktop.NetworkManager.Device.WifiP2P"; + +protected: + WifiP2P_proxy(sdbus::IProxy& proxy) + : proxy_(proxy) + { + proxy_.uponSignal("PeerAdded").onInterface(INTERFACE_NAME).call([this](const sdbus::ObjectPath& peer){ this->onPeerAdded(peer); }); + proxy_.uponSignal("PeerRemoved").onInterface(INTERFACE_NAME).call([this](const sdbus::ObjectPath& peer){ this->onPeerRemoved(peer); }); + } + + ~WifiP2P_proxy() = default; + + virtual void onPeerAdded(const sdbus::ObjectPath& peer) = 0; + virtual void onPeerRemoved(const sdbus::ObjectPath& peer) = 0; + +public: + void StartFind(const std::map& options) + { + proxy_.callMethod("StartFind").onInterface(INTERFACE_NAME).withArguments(options); + } + + void StopFind() + { + proxy_.callMethod("StopFind").onInterface(INTERFACE_NAME); + } + +public: + std::string HwAddress() + { + return proxy_.getProperty("HwAddress").onInterface(INTERFACE_NAME); + } + + std::vector Peers() + { + return proxy_.getProperty("Peers").onInterface(INTERFACE_NAME); + } + +private: + sdbus::IProxy& proxy_; +}; + +}}}} // namespaces + +#endif diff --git a/internal/platform/implementation/linux/generated/dbus/networkmanager/device_wireless_client.h b/internal/platform/implementation/linux/generated/dbus/networkmanager/device_wireless_client.h new file mode 100644 index 00000000..b8a85787 --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/networkmanager/device_wireless_client.h @@ -0,0 +1,103 @@ + +/* + * This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT! + */ + +#ifndef __sdbuscpp__networkmanager_device_wireless_client_glue_h__proxy__H__ +#define __sdbuscpp__networkmanager_device_wireless_client_glue_h__proxy__H__ + +#include +#include +#include + +namespace org { +namespace freedesktop { +namespace NetworkManager { +namespace Device { + +class Wireless_proxy +{ +public: + static constexpr const char* INTERFACE_NAME = "org.freedesktop.NetworkManager.Device.Wireless"; + +protected: + Wireless_proxy(sdbus::IProxy& proxy) + : proxy_(proxy) + { + proxy_.uponSignal("AccessPointAdded").onInterface(INTERFACE_NAME).call([this](const sdbus::ObjectPath& access_point){ this->onAccessPointAdded(access_point); }); + proxy_.uponSignal("AccessPointRemoved").onInterface(INTERFACE_NAME).call([this](const sdbus::ObjectPath& access_point){ this->onAccessPointRemoved(access_point); }); + } + + ~Wireless_proxy() = default; + + virtual void onAccessPointAdded(const sdbus::ObjectPath& access_point) = 0; + virtual void onAccessPointRemoved(const sdbus::ObjectPath& access_point) = 0; + +public: + std::vector GetAccessPoints() + { + std::vector result; + proxy_.callMethod("GetAccessPoints").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + std::vector GetAllAccessPoints() + { + std::vector result; + proxy_.callMethod("GetAllAccessPoints").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + void RequestScan(const std::map& options) + { + proxy_.callMethod("RequestScan").onInterface(INTERFACE_NAME).withArguments(options); + } + +public: + std::string HwAddress() + { + return proxy_.getProperty("HwAddress").onInterface(INTERFACE_NAME); + } + + std::string PermHwAddress() + { + return proxy_.getProperty("PermHwAddress").onInterface(INTERFACE_NAME); + } + + uint32_t Mode() + { + return proxy_.getProperty("Mode").onInterface(INTERFACE_NAME); + } + + uint32_t Bitrate() + { + return proxy_.getProperty("Bitrate").onInterface(INTERFACE_NAME); + } + + std::vector AccessPoints() + { + return proxy_.getProperty("AccessPoints").onInterface(INTERFACE_NAME); + } + + sdbus::ObjectPath ActiveAccessPoint() + { + return proxy_.getProperty("ActiveAccessPoint").onInterface(INTERFACE_NAME); + } + + uint32_t WirelessCapabilities() + { + return proxy_.getProperty("WirelessCapabilities").onInterface(INTERFACE_NAME); + } + + int64_t LastScan() + { + return proxy_.getProperty("LastScan").onInterface(INTERFACE_NAME); + } + +private: + sdbus::IProxy& proxy_; +}; + +}}}} // namespaces + +#endif diff --git a/internal/platform/implementation/linux/generated/dbus/networkmanager/ip4config_client.h b/internal/platform/implementation/linux/generated/dbus/networkmanager/ip4config_client.h new file mode 100644 index 00000000..442180ff --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/networkmanager/ip4config_client.h @@ -0,0 +1,102 @@ + +/* + * This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT! + */ + +#ifndef __sdbuscpp__networkmanager_ip4config_client_glue_h__proxy__H__ +#define __sdbuscpp__networkmanager_ip4config_client_glue_h__proxy__H__ + +#include +#include +#include + +namespace org { +namespace freedesktop { +namespace NetworkManager { + +class IP4Config_proxy +{ +public: + static constexpr const char* INTERFACE_NAME = "org.freedesktop.NetworkManager.IP4Config"; + +protected: + IP4Config_proxy(sdbus::IProxy& proxy) + : proxy_(proxy) + { + } + + ~IP4Config_proxy() = default; + +public: + std::vector> Addresses() + { + return proxy_.getProperty("Addresses").onInterface(INTERFACE_NAME); + } + + std::vector> AddressData() + { + return proxy_.getProperty("AddressData").onInterface(INTERFACE_NAME); + } + + std::string Gateway() + { + return proxy_.getProperty("Gateway").onInterface(INTERFACE_NAME); + } + + std::vector> Routes() + { + return proxy_.getProperty("Routes").onInterface(INTERFACE_NAME); + } + + std::vector> RouteData() + { + return proxy_.getProperty("RouteData").onInterface(INTERFACE_NAME); + } + + std::vector Nameservers() + { + return proxy_.getProperty("Nameservers").onInterface(INTERFACE_NAME); + } + + std::vector> NameserverData() + { + return proxy_.getProperty("NameserverData").onInterface(INTERFACE_NAME); + } + + std::vector Domains() + { + return proxy_.getProperty("Domains").onInterface(INTERFACE_NAME); + } + + std::vector Searches() + { + return proxy_.getProperty("Searches").onInterface(INTERFACE_NAME); + } + + std::vector DnsOptions() + { + return proxy_.getProperty("DnsOptions").onInterface(INTERFACE_NAME); + } + + int32_t DnsPriority() + { + return proxy_.getProperty("DnsPriority").onInterface(INTERFACE_NAME); + } + + std::vector WinsServers() + { + return proxy_.getProperty("WinsServers").onInterface(INTERFACE_NAME); + } + + std::vector WinsServerData() + { + return proxy_.getProperty("WinsServerData").onInterface(INTERFACE_NAME); + } + +private: + sdbus::IProxy& proxy_; +}; + +}}} // namespaces + +#endif diff --git a/internal/platform/implementation/linux/generated/dbus/networkmanager/networkmanager_client.h b/internal/platform/implementation/linux/generated/dbus/networkmanager/networkmanager_client.h new file mode 100644 index 00000000..f13b9544 --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/networkmanager/networkmanager_client.h @@ -0,0 +1,320 @@ + +/* + * This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT! + */ + +#ifndef __sdbuscpp__networkmanager_client_glue_h__proxy__H__ +#define __sdbuscpp__networkmanager_client_glue_h__proxy__H__ + +#include +#include +#include + +namespace org { +namespace freedesktop { + +class NetworkManager_proxy +{ +public: + static constexpr const char* INTERFACE_NAME = "org.freedesktop.NetworkManager"; + +protected: + NetworkManager_proxy(sdbus::IProxy& proxy) + : proxy_(proxy) + { + proxy_.uponSignal("CheckPermissions").onInterface(INTERFACE_NAME).call([this](){ this->onCheckPermissions(); }); + proxy_.uponSignal("StateChanged").onInterface(INTERFACE_NAME).call([this](const uint32_t& state){ this->onStateChanged(state); }); + proxy_.uponSignal("DeviceAdded").onInterface(INTERFACE_NAME).call([this](const sdbus::ObjectPath& device_path){ this->onDeviceAdded(device_path); }); + proxy_.uponSignal("DeviceRemoved").onInterface(INTERFACE_NAME).call([this](const sdbus::ObjectPath& device_path){ this->onDeviceRemoved(device_path); }); + } + + ~NetworkManager_proxy() = default; + + virtual void onCheckPermissions() = 0; + virtual void onStateChanged(const uint32_t& state) = 0; + virtual void onDeviceAdded(const sdbus::ObjectPath& device_path) = 0; + virtual void onDeviceRemoved(const sdbus::ObjectPath& device_path) = 0; + +public: + void Reload(const uint32_t& flags) + { + proxy_.callMethod("Reload").onInterface(INTERFACE_NAME).withArguments(flags); + } + + std::vector GetDevices() + { + std::vector result; + proxy_.callMethod("GetDevices").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + std::vector GetAllDevices() + { + std::vector result; + proxy_.callMethod("GetAllDevices").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + sdbus::ObjectPath GetDeviceByIpIface(const std::string& iface) + { + sdbus::ObjectPath result; + proxy_.callMethod("GetDeviceByIpIface").onInterface(INTERFACE_NAME).withArguments(iface).storeResultsTo(result); + return result; + } + + sdbus::ObjectPath ActivateConnection(const sdbus::ObjectPath& connection, const sdbus::ObjectPath& device, const sdbus::ObjectPath& specific_object) + { + sdbus::ObjectPath result; + proxy_.callMethod("ActivateConnection").onInterface(INTERFACE_NAME).withArguments(connection, device, specific_object).storeResultsTo(result); + return result; + } + + std::tuple AddAndActivateConnection(const std::map>& connection, const sdbus::ObjectPath& device, const sdbus::ObjectPath& specific_object) + { + std::tuple result; + proxy_.callMethod("AddAndActivateConnection").onInterface(INTERFACE_NAME).withArguments(connection, device, specific_object).storeResultsTo(result); + return result; + } + + std::tuple> AddAndActivateConnection2(const std::map>& connection, const sdbus::ObjectPath& device, const sdbus::ObjectPath& specific_object, const std::map& options) + { + std::tuple> result; + proxy_.callMethod("AddAndActivateConnection2").onInterface(INTERFACE_NAME).withArguments(connection, device, specific_object, options).storeResultsTo(result); + return result; + } + + void DeactivateConnection(const sdbus::ObjectPath& active_connection) + { + proxy_.callMethod("DeactivateConnection").onInterface(INTERFACE_NAME).withArguments(active_connection); + } + + void Sleep(const bool& sleep) + { + proxy_.callMethod("Sleep").onInterface(INTERFACE_NAME).withArguments(sleep); + } + + void Enable(const bool& enable) + { + proxy_.callMethod("Enable").onInterface(INTERFACE_NAME).withArguments(enable); + } + + std::map GetPermissions() + { + std::map result; + proxy_.callMethod("GetPermissions").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + void SetLogging(const std::string& level, const std::string& domains) + { + proxy_.callMethod("SetLogging").onInterface(INTERFACE_NAME).withArguments(level, domains); + } + + std::tuple GetLogging() + { + std::tuple result; + proxy_.callMethod("GetLogging").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + uint32_t CheckConnectivity() + { + uint32_t result; + proxy_.callMethod("CheckConnectivity").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + uint32_t state() + { + uint32_t result; + proxy_.callMethod("state").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + sdbus::ObjectPath CheckpointCreate(const std::vector& devices, const uint32_t& rollback_timeout, const uint32_t& flags) + { + sdbus::ObjectPath result; + proxy_.callMethod("CheckpointCreate").onInterface(INTERFACE_NAME).withArguments(devices, rollback_timeout, flags).storeResultsTo(result); + return result; + } + + void CheckpointDestroy(const sdbus::ObjectPath& checkpoint) + { + proxy_.callMethod("CheckpointDestroy").onInterface(INTERFACE_NAME).withArguments(checkpoint); + } + + std::map CheckpointRollback(const sdbus::ObjectPath& checkpoint) + { + std::map result; + proxy_.callMethod("CheckpointRollback").onInterface(INTERFACE_NAME).withArguments(checkpoint).storeResultsTo(result); + return result; + } + + void CheckpointAdjustRollbackTimeout(const sdbus::ObjectPath& checkpoint, const uint32_t& add_timeout) + { + proxy_.callMethod("CheckpointAdjustRollbackTimeout").onInterface(INTERFACE_NAME).withArguments(checkpoint, add_timeout); + } + +public: + std::vector Devices() + { + return proxy_.getProperty("Devices").onInterface(INTERFACE_NAME); + } + + std::vector AllDevices() + { + return proxy_.getProperty("AllDevices").onInterface(INTERFACE_NAME); + } + + std::vector Checkpoints() + { + return proxy_.getProperty("Checkpoints").onInterface(INTERFACE_NAME); + } + + bool NetworkingEnabled() + { + return proxy_.getProperty("NetworkingEnabled").onInterface(INTERFACE_NAME); + } + + bool WirelessEnabled() + { + return proxy_.getProperty("WirelessEnabled").onInterface(INTERFACE_NAME); + } + + void WirelessEnabled(const bool& value) + { + proxy_.setProperty("WirelessEnabled").onInterface(INTERFACE_NAME).toValue(value); + } + + bool WirelessHardwareEnabled() + { + return proxy_.getProperty("WirelessHardwareEnabled").onInterface(INTERFACE_NAME); + } + + bool WwanEnabled() + { + return proxy_.getProperty("WwanEnabled").onInterface(INTERFACE_NAME); + } + + void WwanEnabled(const bool& value) + { + proxy_.setProperty("WwanEnabled").onInterface(INTERFACE_NAME).toValue(value); + } + + bool WwanHardwareEnabled() + { + return proxy_.getProperty("WwanHardwareEnabled").onInterface(INTERFACE_NAME); + } + + bool WimaxEnabled() + { + return proxy_.getProperty("WimaxEnabled").onInterface(INTERFACE_NAME); + } + + void WimaxEnabled(const bool& value) + { + proxy_.setProperty("WimaxEnabled").onInterface(INTERFACE_NAME).toValue(value); + } + + bool WimaxHardwareEnabled() + { + return proxy_.getProperty("WimaxHardwareEnabled").onInterface(INTERFACE_NAME); + } + + uint32_t RadioFlags() + { + return proxy_.getProperty("RadioFlags").onInterface(INTERFACE_NAME); + } + + std::vector ActiveConnections() + { + return proxy_.getProperty("ActiveConnections").onInterface(INTERFACE_NAME); + } + + sdbus::ObjectPath PrimaryConnection() + { + return proxy_.getProperty("PrimaryConnection").onInterface(INTERFACE_NAME); + } + + std::string PrimaryConnectionType() + { + return proxy_.getProperty("PrimaryConnectionType").onInterface(INTERFACE_NAME); + } + + uint32_t Metered() + { + return proxy_.getProperty("Metered").onInterface(INTERFACE_NAME); + } + + sdbus::ObjectPath ActivatingConnection() + { + return proxy_.getProperty("ActivatingConnection").onInterface(INTERFACE_NAME); + } + + bool Startup() + { + return proxy_.getProperty("Startup").onInterface(INTERFACE_NAME); + } + + std::string Version() + { + return proxy_.getProperty("Version").onInterface(INTERFACE_NAME); + } + + std::vector VersionInfo() + { + return proxy_.getProperty("VersionInfo").onInterface(INTERFACE_NAME); + } + + std::vector Capabilities() + { + return proxy_.getProperty("Capabilities").onInterface(INTERFACE_NAME); + } + + uint32_t State() + { + return proxy_.getProperty("State").onInterface(INTERFACE_NAME); + } + + uint32_t Connectivity() + { + return proxy_.getProperty("Connectivity").onInterface(INTERFACE_NAME); + } + + bool ConnectivityCheckAvailable() + { + return proxy_.getProperty("ConnectivityCheckAvailable").onInterface(INTERFACE_NAME); + } + + bool ConnectivityCheckEnabled() + { + return proxy_.getProperty("ConnectivityCheckEnabled").onInterface(INTERFACE_NAME); + } + + void ConnectivityCheckEnabled(const bool& value) + { + proxy_.setProperty("ConnectivityCheckEnabled").onInterface(INTERFACE_NAME).toValue(value); + } + + std::string ConnectivityCheckUri() + { + return proxy_.getProperty("ConnectivityCheckUri").onInterface(INTERFACE_NAME); + } + + std::map GlobalDnsConfiguration() + { + return proxy_.getProperty("GlobalDnsConfiguration").onInterface(INTERFACE_NAME); + } + + void GlobalDnsConfiguration(const std::map& value) + { + proxy_.setProperty("GlobalDnsConfiguration").onInterface(INTERFACE_NAME).toValue(value); + } + +private: + sdbus::IProxy& proxy_; +}; + +}} // namespaces + +#endif diff --git a/internal/platform/implementation/linux/generated/dbus/networkmanager/org.freedesktop.NetworkManager.AccessPoint.xml b/internal/platform/implementation/linux/generated/dbus/networkmanager/org.freedesktop.NetworkManager.AccessPoint.xml new file mode 100644 index 00000000..7340bda3 --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/networkmanager/org.freedesktop.NetworkManager.AccessPoint.xml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/internal/platform/implementation/linux/generated/dbus/networkmanager/org.freedesktop.NetworkManager.Connection.Active.xml b/internal/platform/implementation/linux/generated/dbus/networkmanager/org.freedesktop.NetworkManager.Connection.Active.xml new file mode 100644 index 00000000..faab73a0 --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/networkmanager/org.freedesktop.NetworkManager.Connection.Active.xml @@ -0,0 +1,185 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/internal/platform/implementation/linux/generated/dbus/networkmanager/org.freedesktop.NetworkManager.Device.WifiP2P.xml b/internal/platform/implementation/linux/generated/dbus/networkmanager/org.freedesktop.NetworkManager.Device.WifiP2P.xml new file mode 100644 index 00000000..73b8cc0c --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/networkmanager/org.freedesktop.NetworkManager.Device.WifiP2P.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/internal/platform/implementation/linux/generated/dbus/networkmanager/org.freedesktop.NetworkManager.Device.Wireless.xml b/internal/platform/implementation/linux/generated/dbus/networkmanager/org.freedesktop.NetworkManager.Device.Wireless.xml new file mode 100644 index 00000000..c428e998 --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/networkmanager/org.freedesktop.NetworkManager.Device.Wireless.xml @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/internal/platform/implementation/linux/generated/dbus/networkmanager/org.freedesktop.NetworkManager.IP4Config.xml b/internal/platform/implementation/linux/generated/dbus/networkmanager/org.freedesktop.NetworkManager.IP4Config.xml new file mode 100644 index 00000000..f6e18b76 --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/networkmanager/org.freedesktop.NetworkManager.IP4Config.xml @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/internal/platform/implementation/linux/generated/dbus/networkmanager/org.freedesktop.NetworkManager.xml b/internal/platform/implementation/linux/generated/dbus/networkmanager/org.freedesktop.NetworkManager.xml new file mode 100644 index 00000000..c92c8017 --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/networkmanager/org.freedesktop.NetworkManager.xml @@ -0,0 +1,598 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/internal/platform/implementation/linux/http_loader.cc b/internal/platform/implementation/linux/http_loader.cc new file mode 100644 index 00000000..2d6a7262 --- /dev/null +++ b/internal/platform/implementation/linux/http_loader.cc @@ -0,0 +1,573 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "internal/platform/implementation/linux/http_loader.h" + +#include +#include + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/ascii.h" +#include "absl/strings/numbers.h" +#include "absl/strings/str_cat.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { +namespace { + +constexpr int32_t kSchemaMaximumLength = 10; +constexpr int32_t kHostNameMaximumLength = 256; + +using ::nearby::api::WebResponse; + +} // namespace + +HttpLoader::HttpLoader(const nearby::api::WebRequest &request) + : request_(request), + header_data_(open_memstream(&header_strings_, &header_sizeloc_)), + our_header_data_(nullptr), + response_data_(open_memstream(&response_strings_, &response_sizeloc_)), + curl_(curl_easy_init()) {} + +HttpLoader::~HttpLoader() { DisconnectWebServer(); } + +absl::StatusOr HttpLoader::GetResponse() { + absl::Status status; + + status = ParseUrl(); + if (!status.ok()) { + return status; + } + + status = ConnectWebServer(); + if (!status.ok()) { + return status; + } + + // Sends request to web server. + status = SendRequest(); + if (!status.ok()) { + return status; + } + + // Processes response from web server + absl::StatusOr result = ProcessResponse(); + if (!result.ok()) { + return result; + } + + DisconnectWebServer(); + return result; +} + +absl::StatusOr HttpLoader::QueryStatusCode(CURL *file_handle) { + long status_code; + absl::Status status; + status = QueryResponseInfo(file_handle, CURLINFO_RESPONSE_CODE, &status_code); + if (!status.ok()) { + return status; + } + + if (status_code < 0) { + return absl::InternalError("Invalid status code."); + } + + return status_code; +} + +absl::StatusOr HttpLoader::QueryStatusText(CURL *request_handle) { + absl::StatusOr status; + std::string status_text; + + status = QueryStatusCode(request_handle); + if (!status.ok()) { + return status.status(); + } + + switch (status.value()) { + case 100: + return "Continue"; + case 101: + return "Switching Protocols"; + case 102: + return "Processing"; + case 103: + return "Early Hints"; + case 200: + return "OK"; + case 201: + return "Created"; + case 202: + return "Accepted"; + case 203: + return "Non-Authoritative Information"; + case 204: + return "No Content"; + case 205: + return "Reset Content"; + case 206: + return "Partial Content"; + case 207: + return "Multi-Status"; + case 208: + return "Already Reported"; + case 226: + return "IM Used"; + case 300: + return "Multiple Choices"; + case 301: + return "Moved Permanently"; + case 302: + return "Found"; + case 303: + return "See Other"; + case 304: + return "Not Modified"; + case 305: + return "Use Proxy"; + case 307: + return "Temporary Redirect"; + case 308: + return "Permanent Redirect"; + case 400: + return "Bad Request"; + case 401: + return "Unauthorized"; + case 402: + return "Payment Required"; + case 403: + return "Forbidden"; + case 404: + return "Not Found"; + case 405: + return "Method Not Allowed"; + case 406: + return "Not Acceptable"; + case 407: + return "Proxy Authentication Required"; + case 408: + return "Request Timeout"; + case 409: + return "Conflict"; + case 410: + return "Gone"; + case 411: + return "Lenth Required"; + case 412: + return "Precondition Failed"; + case 413: + return "Payload Too Large"; + case 414: + return "URI Too Long"; + case 415: + return "Unsupported Media Type"; + case 416: + return "Range Not Satisfiable"; + case 417: + return "Expectation Failed"; + case 418: + return "I'm a teapot!"; + case 421: + return "Misdirected Request"; + case 422: + return "Unprocessable Content"; + case 423: + return "Locked"; + case 424: + return "Failed Dependency"; + case 425: + return "Too Early"; + case 426: + return "Upgrade Required"; + case 428: + return "Precondition Required"; + case 429: + return "Too Many Requests"; + case 431: + return "Request Header Fields Too Large"; + case 451: + return "Unavailable For Legal Reasons"; + case 500: + return "Internal Server Error"; + case 501: + return "Not Implemented"; + case 502: + return "Bad Gateway"; + case 503: + return "Service Unavailable"; + case 504: + return "Gateway Timeout"; + case 505: + return "HTTP Version Not Supported"; + case 506: + return "Variant Also Negotiates"; + case 507: + return "Insufficient Storage"; + case 508: + return "Loop Detected"; + case 509: + return "Network Authentication Required"; + default: + return absl::InternalError("Invalid status code."); + } +} + +absl::StatusOr> +HttpLoader::QueryResponseHeaders(CURL *request_handle) { + absl::Status status; + long header_size; + + status = QueryResponseInfo(curl_, CURLINFO_HEADER_SIZE, &header_size); + + if (!status.ok()) { + return status; + } + + std::string headers_string(header_strings_, header_size); + + std::multimap headers; + // Parse headers in response + size_t start = 0; + size_t pos = 0; + while ((pos = headers_string.find("\r\n", start)) != std::string::npos) { + std::string header = headers_string.substr(start, pos - start); + // Get key and value in header + size_t split_pos = 0; + if ((split_pos = header.find(": ")) != std::string::npos) { + std::string key = header.substr(0, split_pos); + std::string value = header.substr(split_pos + 2); + headers.emplace(key, value); + } + + start = pos + 2; + } + + return headers; +} + +const nearby::api::WebRequest &HttpLoader::GetRequest() { return request_; } + +size_t HttpLoader::CurlReadCallback(char *buffer, size_t size, size_t nitems, + void *userdata) { + size_t write_size_max = size * nitems; + size_t write_amount = 0; + for (const auto &str : + reinterpret_cast(userdata)->GetRequest().body) { + if (write_amount == write_size_max) { + break; + } + *(buffer + write_amount) = str; + write_amount++; + } + + return write_amount; +} + +// This function uses the CURL getinfo function to grab info. Each info_level +// has a different type it can return. It would not be feasable to determine the +// type and return it. IT IS UP TO THE CALLER OF THE FUNCTION TO USE THE void* +// CORRECTLY. +absl::Status HttpLoader::QueryResponseInfo(CURL *request_handle, + CURLINFO info_level, void *info) { + CURLcode query_result = curl_easy_getinfo(request_handle, info_level, &info); + + if (query_result == CURLE_OK) { + return absl::OkStatus(); + } + + return absl::InvalidArgumentError( + "Failed to query HTTP information: " + + std::string(curl_easy_strerror(query_result))); +} + +absl::Status HttpLoader::ParseUrl() { + CURLU *url_components = curl_url(); + char *schema; + char *host_name; + char *path; + + CURLUcode ret = curl_url_set(url_components, CURLUPART_URL, + request_.url.c_str(), CURLU_NON_SUPPORT_SCHEME); + if (ret) { + curl_url_cleanup(url_components); + curl_free(schema); + curl_free(host_name); + curl_free(path); + url_components = nullptr; + schema = nullptr; + host_name = nullptr; + path = nullptr; + return absl::InvalidArgumentError("Invalid URL format: " + + std::string(curl_url_strerror(ret))); + } + + ret = curl_url_get(url_components, CURLUPART_SCHEME, &schema, + CURLU_URLDECODE | CURLU_URLENCODE | CURLU_DEFAULT_PORT | + CURLU_DEFAULT_SCHEME); + if (ret) { + curl_url_cleanup(url_components); + curl_free(schema); + curl_free(host_name); + curl_free(path); + url_components = nullptr; + schema = nullptr; + host_name = nullptr; + path = nullptr; + return absl::InvalidArgumentError("Could not parse URL schema: " + + std::string(curl_url_strerror(ret))); + } + + ret = curl_url_get(url_components, CURLUPART_PATH, &path, + CURLU_URLDECODE | CURLU_URLENCODE | CURLU_DEFAULT_PORT | + CURLU_DEFAULT_SCHEME); + if (ret) { + curl_url_cleanup(url_components); + curl_free(schema); + curl_free(host_name); + curl_free(path); + url_components = nullptr; + schema = nullptr; + host_name = nullptr; + path = nullptr; + return absl::InvalidArgumentError("Could not parse URL path: " + + std::string(curl_url_strerror(ret))); + } + + if (!(schema_ == "http" || schema_ == "https")) { + curl_url_cleanup(url_components); + curl_free(schema); + curl_free(host_name); + curl_free(path); + url_components = nullptr; + schema = nullptr; + host_name = nullptr; + path = nullptr; + return absl::InvalidArgumentError("URL supports HTTP and HTTPS only."); + } + + host_ = host_name; + schema_ = schema; + path_ = path; + + if (schema_ == "https") { + is_secure_ = true; + } + + curl_url_cleanup(url_components); + curl_free(schema); + curl_free(host_name); + curl_free(path); + url_components = nullptr; + schema = nullptr; + host_name = nullptr; + path = nullptr; + return absl::OkStatus(); +} + +absl::Status HttpLoader::ConnectWebServer() { + std::vector option_return_codes; + if (curl_) { + curl_ = curl_easy_init(); + header_data_ = open_memstream(&header_strings_, &header_sizeloc_); + response_data_ = open_memstream(&response_strings_, &response_sizeloc_); + } + + option_return_codes.push_back( + curl_easy_setopt(curl_, CURLOPT_NOPROGRESS, 1L)); + option_return_codes.push_back( + curl_easy_setopt(curl_, CURLOPT_URL, request_.url.c_str())); + option_return_codes.push_back(curl_easy_setopt(curl_, CURLOPT_PORT, port_)); + option_return_codes.push_back( + curl_easy_setopt(curl_, CURLOPT_AUTOREFERER, 1L)); + option_return_codes.push_back( + curl_easy_setopt(curl_, CURLOPT_FOLLOWLOCATION, 1L)); + option_return_codes.push_back( + curl_easy_setopt(curl_, CURLOPT_USERAGENT, "Mozilla/5.0")); + option_return_codes.push_back( + curl_easy_setopt(curl_, CURLOPT_HEADERDATA, header_data_)); + option_return_codes.push_back( + curl_easy_setopt(curl_, CURLOPT_WRITEDATA, response_data_)); + + // Prepare headers + std::string request_headers; + for (const auto &header : request_.headers) { + struct curl_slist *list = curl_slist_append( + our_header_data_, + std::string(header.first + ": " + header.second).c_str()); + if (list) { + our_header_data_ = list; + } + } + + if (!request_headers.empty()) { + option_return_codes.push_back( + curl_easy_setopt(curl_, CURLOPT_HTTPHEADER, our_header_data_)); + } + + if (request_.method == "GET") { + option_return_codes.push_back(curl_easy_setopt(curl_, CURLOPT_HTTPGET, 1L)); + } else if (request_.method == "POST") { + option_return_codes.push_back(curl_easy_setopt( + curl_, CURLOPT_POSTFIELDSIZE, static_cast(request_.body.size()))); + option_return_codes.push_back( + curl_easy_setopt(curl_, CURLOPT_POSTFIELDS, request_.body.c_str())); + } else if (request_.method == "PUT") { + option_return_codes.push_back(curl_easy_setopt(curl_, CURLOPT_UPLOAD, 1L)); + option_return_codes.push_back( + curl_easy_setopt(curl_, CURLOPT_READFUNCTION, CurlReadCallback)); + option_return_codes.push_back( + curl_easy_setopt(curl_, CURLOPT_READDATA, this)); + option_return_codes.push_back(curl_easy_setopt( + curl_, + (request_.body.size() < std::numeric_limits::max() + ? CURLOPT_INFILESIZE + : CURLOPT_INFILESIZE_LARGE), + request_.body.size())); + + } else { + NEARBY_LOGS(ERROR) << "Failed to open internet with error " + << "Invalid request method: " << request_.method << "."; + return absl::FailedPreconditionError( + "Failed to open internet: Invalid request method."); + } + + for (const auto &ret : option_return_codes) { + if (ret) { + NEARBY_LOGS(ERROR) << "Failed to open internet with error " + << curl_easy_strerror(ret) << "."; + return absl::FailedPreconditionError( + absl::StrCat(curl_easy_strerror(ret))); + } + } + + return absl::OkStatus(); +} + +absl::Status HttpLoader::SendRequest() { + CURLcode ret = curl_easy_perform(curl_); + + if (ret != CURLE_OK) { + NEARBY_LOGS(ERROR) + << "Failed to send request to remote web server with error " + << curl_easy_strerror(ret) << "."; + return absl::FailedPreconditionError(absl::StrCat(curl_easy_strerror(ret))); + } + + return absl::OkStatus(); +} + +absl::StatusOr HttpLoader::ProcessResponse() { + absl::Status status; + WebResponse web_response; + auto status_code = QueryStatusCode(curl_); + if (!status_code.ok()) { + return absl::InternalError("Failed to read HTTP status"); + } + + web_response.status_code = status_code.value(); + auto status_text = QueryStatusText(curl_); + if (!status_text.ok()) { + return absl::InternalError("Failed to read HTTP status"); + } + + web_response.status_text = status_text.value(); + auto headers = QueryResponseHeaders(curl_); + if (!headers.ok()) { + headers.status(); + } + web_response.headers = *headers; + + curl_off_t download_size; + + CURLcode ret = + curl_easy_getinfo(curl_, CURLINFO_SIZE_DOWNLOAD_T, &download_size); + + if (ret) { + if (download_size != 0) { + // Append data to response + web_response.body.assign(response_strings_, download_size); + } else { + NEARBY_LOGS(ERROR) + << "Failed to read response from remote web server with error " + << curl_easy_strerror(ret) << "."; + return absl::FailedPreconditionError( + absl::StrCat(curl_easy_strerror(ret))); + } + } + + status = HTTPCodeToStatus(web_response.status_code, web_response.status_text); + if (!status.ok()) { + return status; + } + + return web_response; +} + +void HttpLoader::DisconnectWebServer() { + fclose(header_data_); + header_data_ = nullptr; + delete header_strings_; + header_strings_ = nullptr; + curl_easy_cleanup(curl_); + curl_ = nullptr; + curl_slist_free_all(our_header_data_); + our_header_data_ = nullptr; + fclose(response_data_); + response_data_ = nullptr; + delete response_strings_; + response_strings_ = nullptr; +} + +absl::Status HttpLoader::HTTPCodeToStatus(int status_code, + absl::string_view status_message) { + switch (status_code) { + case 400: + return absl::InvalidArgumentError(status_message); + case 401: + return absl::UnauthenticatedError(status_message); + case 403: + return absl::PermissionDeniedError(status_message); + case 404: + return absl::NotFoundError(status_message); + case 409: + return absl::AbortedError(status_message); + case 416: + return absl::OutOfRangeError(status_message); + case 429: + return absl::ResourceExhaustedError(status_message); + case 499: + return absl::CancelledError(status_message); + case 504: + return absl::DeadlineExceededError(status_message); + case 501: + return absl::UnimplementedError(status_message); + case 503: + return absl::UnavailableError(status_message); + default: + break; + } + if (status_code >= 200 && status_code < 300) { + return absl::OkStatus(); + } else if (status_code >= 400 && status_code < 500) { + return absl::FailedPreconditionError(status_message); + } else if (status_code >= 500 && status_code < 600) { + return absl::InternalError(status_message); + } + return absl::UnknownError(status_message); +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/http_loader.h b/internal/platform/implementation/linux/http_loader.h new file mode 100644 index 00000000..02d68f72 --- /dev/null +++ b/internal/platform/implementation/linux/http_loader.h @@ -0,0 +1,91 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_LINUX_HTTP_LOADER_H_ +#define THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_LINUX_HTTP_LOADER_H_ + +#include +#include + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/string_view.h" +#include "internal/platform/implementation/http_loader.h" + +namespace nearby { +namespace linux { + +// HttpLoader is used to get HTTP response from remote server. +// +// HttpLoader gets HTTP request information from caller, and calling Windows +// WinInet APIs to get HTTP response. The platform handles HTTP/HTTPS sessions. +class HttpLoader { + public: + explicit HttpLoader(const nearby::api::WebRequest &request); + ~HttpLoader(); + + absl::StatusOr GetResponse(); + + const nearby::api::WebRequest &GetRequest(); + + private: + // Defines the buffer size. It is used to init a buffer for receiving HTTP + // response. The unit is byte. + static constexpr int kReceiveBufferSize = 8 * 1024; + static size_t CurlReadCallback(char *buffer, size_t size, size_t nitems, + void *userdata); + + absl::Status ConnectWebServer(); + absl::Status SendRequest(); + absl::StatusOr ProcessResponse(); + void DisconnectWebServer(); + + absl::StatusOr QueryStatusCode(CURL *file_handle); + absl::StatusOr QueryStatusText(CURL *request_handle); + absl::StatusOr> QueryResponseHeaders( + CURL *request_handle); + absl::Status QueryResponseInfo(CURL *request_handle, CURLINFO info_level, + void *info); + + absl::Status ParseUrl(); + + // Converts HTTP status code to absl Status. + // + // @param status_code HTTP status code, such 200, 404 etc. + // @param status_message short description of the status code. + // @return converted absl status. + absl::Status HTTPCodeToStatus(int status_code, + absl::string_view status_message); + + nearby::api::WebRequest request_; + std::string host_; + std::string path_; + std::string schema_; + bool is_secure_ = false; + int port_ = 80; + + FILE *header_data_; + char *header_strings_; + size_t header_sizeloc_; + struct curl_slist *our_header_data_; + FILE *response_data_; + char *response_strings_; + size_t response_sizeloc_; + CURL *curl_; +}; + +} // namespace linux +} // namespace nearby + +#endif // THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_LINUX_HTTP_LOADER_H_ diff --git a/internal/platform/implementation/linux/http_loader_test.cc b/internal/platform/implementation/linux/http_loader_test.cc new file mode 100644 index 00000000..800ed6a9 --- /dev/null +++ b/internal/platform/implementation/linux/http_loader_test.cc @@ -0,0 +1,51 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "internal/platform/implementation/linux/http_loader.h" + +#include + +#include "gtest/gtest.h" + +namespace nearby { +namespace linux { +namespace { +using ::nearby::api::WebRequest; + +TEST(HttpLoader, DISABLED_TestGetUrl) { + WebRequest request; + request.url = "https://www.google.com?id=456#fragment"; + request.method = "GET"; + auto response = HttpLoader(request).GetResponse(); + ASSERT_TRUE(response.ok()); + EXPECT_EQ(response->status_code, 200); +} + +TEST(HttpLoader, DISABLED_TestGetNotExistingUrl) { + WebRequest request; + request.url = "https://www.abcdefgabcdefg.com"; + request.method = "GET"; + EXPECT_FALSE(HttpLoader(request).GetResponse().ok()); +} + +TEST(HttpLoader, DISABLED_TestInvalidUrl) { + WebRequest request; + request.url = "https:/www.abcdefgabcdefg.com/name?id=456"; + request.method = "GET"; + EXPECT_FALSE(HttpLoader(request).GetResponse().ok()); +} + +} // namespace +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/input_file.h b/internal/platform/implementation/linux/input_file.h new file mode 100644 index 00000000..955baccf --- /dev/null +++ b/internal/platform/implementation/linux/input_file.h @@ -0,0 +1,48 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_INPUT_FILE_H_ +#define PLATFORM_IMPL_LINUX_INPUT_FILE_H_ + +#include "internal/platform/byte_array.h" +#include "internal/platform/exception.h" +#include "internal/platform/implementation/input_file.h" + +namespace nearby { +namespace linux { + +// An InputFile represents a readable file on the system. +class InputFile : public api::InputFile { + public: + // TODO(b/184975123): replace with real implementation. + ~InputFile() override = default; + // TODO(b/184975123): replace with real implementation. + std::string GetFilePath() const override { return "Un-implemented"; } + // TODO(b/184975123): replace with real implementation. + std::int64_t GetTotalSize() const override { return 0; } + + // throws Exception::kIo + // TODO(b/184975123): replace with real implementation. + ExceptionOr Read(std::int64_t size) override { + return ExceptionOr(Exception::kFailed); + } + // throws Exception::kIo + // TODO(b/184975123): replace with real implementation. + Exception Close() override { return Exception{}; } +}; + +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPL_LINUX_INPUT_FILE_H_ diff --git a/internal/platform/implementation/linux/input_file_test.cc b/internal/platform/implementation/linux/input_file_test.cc new file mode 100644 index 00000000..e7cbdd87 --- /dev/null +++ b/internal/platform/implementation/linux/input_file_test.cc @@ -0,0 +1,135 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "internal/platform/implementation/linux/input_file.h" + +#include + +#include "gtest/gtest.h" +#include "internal/platform/exception.h" +#include "internal/platform/implementation/linux/test_utils.h" +#include "internal/platform/logging.h" +#include "internal/platform/payload_id.h" + +class InputFileTests : public testing::Test { + protected: + // You can define per-test set-up logic as usual. + void SetUp() override { + nearby::PayloadId payloadId(TEST_PAYLOAD_ID); + auto path = test_utils::GetPayloadPath(payloadId); + + file_.open(path, std::ios::out); + + if (!file_) { + NEARBY_LOG( + ERROR, "Failed to create OutputFile with payloadId: %s and error: %d", + test_utils::GetPayloadPath(payloadId).c_str(), std::strerror(errno)); + } + + const char* buffer = TEST_STRING; + + file_.write(buffer, std::strlen(buffer)); + + file_.close(); + } + + // You can define per-test tear-down logic as usual. + void TearDown() override { + nearby::PayloadId payloadId(TEST_PAYLOAD_ID); + if (std::filesystem::exists(test_utils::GetPayloadPath(payloadId))) { + std::filesystem::remove(test_utils::GetPayloadPath(payloadId)); + } + } + + private: + std::fstream file_; +}; + +TEST_F(InputFileTests, SuccessfulCreation) { + nearby::PayloadId payloadId(TEST_PAYLOAD_ID); + std::unique_ptr inputFile = nullptr; + + inputFile = nearby::api::ImplementationPlatform::CreateInputFile( + payloadId, strlen(TEST_STRING)); + + EXPECT_NE(inputFile, nullptr); + EXPECT_EQ(inputFile->Close(), nearby::Exception{nearby::Exception::kSuccess}); +} + +TEST_F(InputFileTests, SuccessfulGetFilePath) { + nearby::PayloadId payloadId(TEST_PAYLOAD_ID); + std::unique_ptr inputFile = nullptr; + std::string fileName; + + inputFile = nearby::api::ImplementationPlatform::CreateInputFile( + payloadId, strlen(TEST_STRING)); + + fileName = inputFile->GetFilePath(); + + EXPECT_EQ(inputFile->Close(), nearby::Exception{nearby::Exception::kSuccess}); + + EXPECT_EQ(fileName, test_utils::GetPayloadPath(payloadId).c_str()); +} + +TEST_F(InputFileTests, SuccessfulGetTotalSize) { + nearby::PayloadId payloadId(TEST_PAYLOAD_ID); + std::unique_ptr inputFile = nullptr; + int64_t size = -1; + + inputFile = nearby::api::ImplementationPlatform::CreateInputFile( + payloadId, strlen(TEST_STRING)); + + size = inputFile->GetTotalSize(); + + EXPECT_EQ(inputFile->Close(), nearby::Exception{nearby::Exception::kSuccess}); + + EXPECT_EQ(size, strlen(TEST_STRING)); +} + +TEST_F(InputFileTests, SuccessfulRead) { + nearby::PayloadId payloadId(TEST_PAYLOAD_ID); + std::unique_ptr inputFile = nullptr; + + inputFile = nearby::api::ImplementationPlatform::CreateInputFile( + payloadId, strlen(TEST_STRING)); + + auto fileSize = inputFile->GetTotalSize(); + auto dataRead = inputFile->Read(fileSize); + + EXPECT_TRUE(dataRead.ok()); + EXPECT_EQ(inputFile->Close(), nearby::Exception{nearby::Exception::kSuccess}); + + EXPECT_STREQ(std::string(dataRead.result()).c_str(), TEST_STRING); +} + +TEST_F(InputFileTests, FailedRead) { + nearby::PayloadId payloadId(TEST_PAYLOAD_ID); + std::unique_ptr inputFile = nullptr; + + inputFile = nearby::api::ImplementationPlatform::CreateInputFile( + payloadId, strlen(TEST_STRING)); + + auto fileSize = inputFile->GetTotalSize(); + EXPECT_NE(fileSize, -1); + + auto dataRead = inputFile->Read(fileSize); + EXPECT_TRUE(dataRead.ok()); + + dataRead = inputFile->Read(fileSize); + std::string data = std::string(dataRead.result()); + + inputFile->Close(); + + EXPECT_STREQ(data.c_str(), ""); +} diff --git a/internal/platform/implementation/linux/log_message.cc b/internal/platform/implementation/linux/log_message.cc new file mode 100644 index 00000000..bb29d634 --- /dev/null +++ b/internal/platform/implementation/linux/log_message.cc @@ -0,0 +1,83 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include +#include +#include + +#define SD_JOURNAL_SUPPRESS_LOCATION true +#include + +#include "absl/base/call_once.h" +#include "absl/synchronization/mutex.h" +#include "internal/platform/implementation/linux/dbus.h" +#include "internal/platform/implementation/linux/log_message.h" + +namespace nearby { +namespace linux { + +std::atomic min_log_severity_ = + api::LogMessage::Severity::kInfo; + +inline google::LogSeverity ConvertSeverity(api::LogMessage::Severity severity) { + switch (severity) { + case api::LogMessage::Severity::kWarning: + return google::GLOG_WARNING; + case api::LogMessage::Severity::kError: + return google::GLOG_ERROR; + case api::LogMessage::Severity::kFatal: + return google::GLOG_FATAL; + case api::LogMessage::Severity::kVerbose: + case api::LogMessage::Severity::kInfo: + default: + return google::GLOG_INFO; + } +} + +LogMessage::LogMessage(const char *file, int line, Severity severity) + : log_streamer_(file, line, ConvertSeverity(severity)) {} + +void LogMessage::Print(const char *format, ...) { + char *buf = nullptr; + + va_list ap; + va_start(ap, format); + auto ret = vasprintf(&buf, format, ap); + if (ret > 0) { + log_streamer_.stream() << std::string(buf, ret); + } + if (buf != nullptr) free(buf); + va_end(ap); +} + +std::ostream &LogMessage::Stream() { return log_streamer_.stream(); } + +} // namespace linux + +namespace api { + +void LogMessage::SetMinLogSeverity(Severity severity) { + linux::min_log_severity_ = severity; +} + +bool LogMessage::ShouldCreateLogMessage(Severity severity) { + return severity >= linux::min_log_severity_; +} +} // namespace api +} // namespace nearby diff --git a/internal/platform/implementation/linux/log_message.h b/internal/platform/implementation/linux/log_message.h new file mode 100644 index 00000000..c11b5d60 --- /dev/null +++ b/internal/platform/implementation/linux/log_message.h @@ -0,0 +1,45 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_LOG_MESSAGE_H_ +#define PLATFORM_IMPL_LINUX_LOG_MESSAGE_H_ + +#include +#include +#include + +#include "glog/logging.h" +#include "internal/platform/implementation/log_message.h" + +namespace nearby { +namespace linux { + +// See documentation in +// cpp/platform/api/log_message.h +class LogMessage : public api::LogMessage { + public: + LogMessage(const char *file, int line, Severity severity); + ~LogMessage() override{}; + + void Print(const char *format, ...) override; + + std::ostream &Stream() override; + + private: + google::LogMessage log_streamer_; +}; +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPL_LINUX_LOG_MESSAGE_H_ diff --git a/internal/platform/implementation/linux/mutex.h b/internal/platform/implementation/linux/mutex.h new file mode 100644 index 00000000..07df42aa --- /dev/null +++ b/internal/platform/implementation/linux/mutex.h @@ -0,0 +1,63 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_MUTEX_H_ +#define PLATFORM_IMPL_LINUX_MUTEX_H_ + +#include +#include + +#include "absl/synchronization/mutex.h" +#include "internal/platform/implementation/mutex.h" + +namespace nearby { +namespace linux { +class ABSL_LOCKABLE Mutex : public api::Mutex { + public: + explicit Mutex(Mode mode) : mode_(mode) {} + ~Mutex() override = default; + Mutex(Mutex&&) = delete; + Mutex& operator=(Mutex&&) = delete; + Mutex(const Mutex&) = delete; + Mutex& operator=(const Mutex&) = delete; + + void Lock() ABSL_EXCLUSIVE_LOCK_FUNCTION() override { + if (mode_ == Mode::kRegularNoCheck) mutex_.ForgetDeadlockInfo(); + if (mode_ == Mode::kRegular || mode_ == Mode::kRegularNoCheck) { + mutex_.Lock(); + } else { + recursive_mutex_.lock(); + } + } + + void Unlock() ABSL_UNLOCK_FUNCTION() override { + if (mode_ == Mode::kRegular || mode_ == Mode::kRegularNoCheck) { + mutex_.Unlock(); + } else { + recursive_mutex_.unlock(); + } + } + + absl::Mutex& GetMutex() { return mutex_; } + std::recursive_mutex& GetRecursiveMutex() { return recursive_mutex_; } + + private: + friend class ConditionVariable; + absl::Mutex mutex_; + std::recursive_mutex recursive_mutex_; // The actual mutex allocation + Mode mode_; +}; +} // namespace linux +} // namespace nearby +#endif diff --git a/internal/platform/implementation/linux/mutex_test.cc b/internal/platform/implementation/linux/mutex_test.cc new file mode 100644 index 00000000..466575d2 --- /dev/null +++ b/internal/platform/implementation/linux/mutex_test.cc @@ -0,0 +1,105 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "internal/platform/implementation/linux/mutex.h" + +#include // NOLINT + +#include "gtest/gtest.h" + +class MutexTests : public testing::Test { + public: + class MutexTest { + public: + MutexTest(nearby::linux::Mutex& mutex) : mutex_(mutex) {} + + std::future WaitForLock() { // NOLINT + return std::async(std::launch::async, + // for this lambda you need C++14 + [this]() mutable { + absl::MutexLock(&mutex_.GetMutex()); + return true; + }); + } + + void PostEvent() { + absl::MutexLock(&mutex_.GetMutex()); + mutex_.Unlock(); + } + + private: + nearby::linux::Mutex& mutex_; + }; +}; + +TEST_F(MutexTests, SuccessfulRecursiveCreation) { + // Arrange + nearby::linux::Mutex mutex = + nearby::linux::Mutex(nearby::linux::Mutex::Mode::kRecursive); + + // Act + std::recursive_mutex& actual = mutex.GetRecursiveMutex(); + + // Assert + ASSERT_TRUE(actual.native_handle() != nullptr); +} + +TEST_F(MutexTests, SuccessfulCreation) { + // Arrange + nearby::linux::Mutex mutex(nearby::linux::Mutex::Mode::kRegular); + + // Act + absl::Mutex& actual = mutex.GetMutex(); + + // Assert + ASSERT_TRUE(&actual != nullptr); +} + +TEST_F(MutexTests, SuccessfulSignal) { + // Arrange + nearby::linux::Mutex mutex(nearby::linux::Mutex::Mode::kRegular); + + nearby::linux::Mutex& mutexRef = mutex; + MutexTest mutexTest(mutexRef); + + mutex.Lock(); + + // Act + auto result = mutexTest.WaitForLock(); + mutex.Unlock(); + + // Assert + ASSERT_TRUE(result.get()); +} + +TEST_F(MutexTests, SuccessfulRecursiveSignal) { + // Arrange + nearby::linux::Mutex mutex(nearby::linux::Mutex::Mode::kRecursive); + + nearby::linux::Mutex& mutexRef = mutex; + MutexTest mutexTest(mutexRef); + + mutex.Lock(); + mutex.Lock(); + mutex.Lock(); + + // Act + auto result = mutexTest.WaitForLock(); + mutex.Unlock(); + mutex.Unlock(); + mutex.Unlock(); + + // Assert + ASSERT_TRUE(result.get()); +} diff --git a/internal/platform/implementation/linux/network_manager.cc b/internal/platform/implementation/linux/network_manager.cc new file mode 100644 index 00000000..5c046fa5 --- /dev/null +++ b/internal/platform/implementation/linux/network_manager.cc @@ -0,0 +1,93 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "internal/platform/implementation/linux/dbus.h" +#include "internal/platform/implementation/linux/network_manager.h" +#include "internal/platform/implementation/linux/network_manager_active_connection.h" + +namespace nearby { +namespace linux { +namespace networkmanager { +std::unique_ptr +ObjectManager::GetActiveConnectionForAccessPoint( + const sdbus::ObjectPath &access_point, + const sdbus::ObjectPath &device_path) { + std::map>> + objects; + try { + objects = GetManagedObjects(); + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(this, "GetManagedObjects", e); + return nullptr; + } + + for (auto &[object_path, interfaces] : objects) { + if (object_path.find("/org/freedesktop/NetworkManager/ActiveConnection/") == + 0) { + if (interfaces.count(org::freedesktop::NetworkManager::Connection:: + Active_proxy::INTERFACE_NAME) == 1) { + auto props = interfaces[org::freedesktop::NetworkManager::Connection:: + Active_proxy::INTERFACE_NAME]; + sdbus::ObjectPath specific_object = props["SpecificObject"]; + if (specific_object == access_point) { + std::vector devices = props["Devices"]; + for (auto &path : devices) { + if (path == device_path) { + return std::make_unique( + system_bus_, object_path); + } + } + } + } + } + } + return nullptr; +} + +std::unique_ptr ObjectManager::GetIp4Config( + const sdbus::ObjectPath &active_connection) { + std::map>> + objects; + try { + objects = GetManagedObjects(); + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(this, "GetManagedObjects", e); + return nullptr; + } + + for (auto &[object_path, interfaces] : objects) { + if (object_path.find("/org/freedesktop/NetworkManager/ActiveConnection/", + 0) == 0) { + if (interfaces.count(org::freedesktop::NetworkManager::Connection:: + Active_proxy::INTERFACE_NAME) == 1) { + auto props = interfaces[org::freedesktop::NetworkManager::Connection:: + Active_proxy::INTERFACE_NAME]; + sdbus::ObjectPath specific_object = props["SpecificObject"]; + if (specific_object == active_connection) { + sdbus::ObjectPath ip4config = props["Ip4Config"]; + return std::make_unique(system_bus_, ip4config); + } + } + } + } + + return nullptr; +} +} // namespace networkmanager +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/network_manager.h b/internal/platform/implementation/linux/network_manager.h new file mode 100644 index 00000000..b4b96cb7 --- /dev/null +++ b/internal/platform/implementation/linux/network_manager.h @@ -0,0 +1,183 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_NETWORK_MANAGER_H_ +#define PLATFORM_IMPL_LINUX_NETWORK_MANAGER_H_ + +#include + +#include +#include + +#include "internal/platform/implementation/linux/dbus.h" +#include "internal/platform/implementation/linux/generated/dbus/networkmanager/ip4config_client.h" +#include "internal/platform/implementation/linux/generated/dbus/networkmanager/networkmanager_client.h" +#include "internal/platform/implementation/linux/network_manager_active_connection.h" + +namespace nearby { +namespace linux { +namespace networkmanager { +class NetworkManager final + : public sdbus::ProxyInterfaces { + public: + NetworkManager(const NetworkManager &) = delete; + NetworkManager(NetworkManager &&) = delete; + NetworkManager &operator=(const NetworkManager &) = delete; + NetworkManager &operator=(NetworkManager &&) = delete; + explicit NetworkManager(std::shared_ptr system_bus) + : ProxyInterfaces(*system_bus, "org.freedesktop.NetworkManager", + "/org/freedesktop/NetworkManager"), + system_bus_(std::move(system_bus)), + state_(kNMStateUnknown) { + registerProxy(); + try { + setState(State()); + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_GET_ERROR(this, "State", e); + } + } + ~NetworkManager() { unregisterProxy(); } + + // https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMState + enum NMState { + kNMStateUnknown = 0, + kNMStateAsleep = 10, + kNMStateDisconnected = 20, + kNMStateDisconnecting = 30, + kNMStateConnecting = 40, + kNMStateConnectedLocal = 50, + kNMStateConnectedSite = 60, + kNMStateConnectedGlobal = 70, + }; + + NMState getState() const { return state_; } + std::shared_ptr GetConnection() { return system_bus_; } + + protected: + void onCheckPermissions() override {} + void onStateChanged(const uint32_t &state) override { setState(state); } + void onDeviceAdded(const sdbus::ObjectPath &device_path) override {} + void onDeviceRemoved(const sdbus::ObjectPath &device_path) override {} + + private: + void inline setState(std::uint32_t val) { +#define NM_STATE_CASE_SET(k) \ + case (k): \ + state_ = (k); \ + break + + switch (val) { + NM_STATE_CASE_SET(kNMStateAsleep); + NM_STATE_CASE_SET(kNMStateDisconnected); + NM_STATE_CASE_SET(kNMStateDisconnecting); + NM_STATE_CASE_SET(kNMStateConnecting); + NM_STATE_CASE_SET(kNMStateConnectedLocal); + NM_STATE_CASE_SET(kNMStateConnectedSite); + NM_STATE_CASE_SET(kNMStateConnectedGlobal); + default: + NEARBY_LOGS(ERROR) << __func__ << "invalid NMState value: " << val + << ", setting state to unknown"; + NM_STATE_CASE_SET(kNMStateUnknown); + } +#undef NM_STATE_CASE_SET + }; + + std::shared_ptr system_bus_; + std::atomic state_; +}; + +class IP4Config : public sdbus::ProxyInterfaces< + org::freedesktop::NetworkManager::IP4Config_proxy> { + public: + IP4Config(const IP4Config &) = delete; + IP4Config(IP4Config &&) = delete; + IP4Config &operator=(const IP4Config &) = delete; + IP4Config &operator=(IP4Config &&) = delete; + IP4Config(std::shared_ptr system_bus, + const sdbus::ObjectPath &config_object_path) + : ProxyInterfaces(*system_bus, "org.freedesktop.NetworkManager", + config_object_path), + system_bus_(std::move(system_bus)) { + registerProxy(); + } + ~IP4Config() { unregisterProxy(); } + + private: + std::shared_ptr system_bus_; +}; + +class ObjectManager final + : public sdbus::ProxyInterfaces { + public: + ObjectManager(const ObjectManager &) = delete; + ObjectManager(ObjectManager &&) = delete; + ObjectManager &operator=(const ObjectManager &) = delete; + ObjectManager &operator=(ObjectManager &&) = delete; + explicit ObjectManager(std::shared_ptr system_bus) + : ProxyInterfaces(*system_bus, "org.freedesktop.NetworkManager", + "/org/freedesktop"), + system_bus_(std::move(system_bus)) { + registerProxy(); + } + ~ObjectManager() { unregisterProxy(); } + + std::unique_ptr GetIp4Config( + const sdbus::ObjectPath &access_point); + std::unique_ptr GetActiveConnectionForAccessPoint( + const sdbus::ObjectPath &access_point_path, + const sdbus::ObjectPath &device_path); + + protected: + void onInterfacesAdded( + const sdbus::ObjectPath &objectPath, + const std::map> + &interfacesAndProperties) override {} + void onInterfacesRemoved( + const sdbus::ObjectPath &objectPath, + const std::vector &interfaces) override {} + + private: + std::shared_ptr system_bus_; +}; + +namespace constants { +// Indicates the 802.11 mode an access point or device is currently in. +enum NM80211Mode { + kNM80211ModeUnknown = 0, + kNM80211ModeAdHoc = 1, + kNM80211ModeInfra = 2, + kNM80211ModeAP = 3, + kNM80211ModeMesh = 4, +}; + +const int32_t kNMTernaryDefault = -1; +const int32_t kNMTernaryFalse = 0; +const int32_t kNMTernaryTrue = 1; + +namespace setting { +const int32_t kWirelessSecurityPMFDefaut = 0; +const int32_t kWirelessSecurityPMFDisable = 1; +const int32_t kWirelessSecurityPMFOptional = 2; +const int32_t kWirelessSecurityPMFRequired = 3; + +const int32_t kIP6ConfigAddrGenModeEUI64 = 0; +const int32_t kIP6ConfigAddrGenModeStablePrivacy = 1; +const int32_t kIP6ConfigAddrGenModeDefaultOrEUI64 = 2; +const int32_t kIP6ConfigAddrGenModeDefault = 3; +} // namespace setting +} // namespace constants +} // namespace networkmanager +} // namespace linux +} // namespace nearby +#endif diff --git a/internal/platform/implementation/linux/network_manager_access_point.h b/internal/platform/implementation/linux/network_manager_access_point.h new file mode 100644 index 00000000..131e4f6f --- /dev/null +++ b/internal/platform/implementation/linux/network_manager_access_point.h @@ -0,0 +1,43 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_NETWORK_MANAGER_ACCESS_POINT_H_ +#define PLATFORM_IMPL_LINUX_NETWORK_MANAGER_ACCESS_POINT_H_ +#include + +#include "internal/platform/implementation/linux/generated/dbus/networkmanager/access_point_client.h" + +namespace nearby { +namespace linux { +class NetworkManagerAccessPoint + : public sdbus::ProxyInterfaces< + org::freedesktop::NetworkManager::AccessPoint_proxy> { + public: + NetworkManagerAccessPoint(const NetworkManagerAccessPoint &) = delete; + NetworkManagerAccessPoint(NetworkManagerAccessPoint &&) = delete; + NetworkManagerAccessPoint &operator=(const NetworkManagerAccessPoint &) = + delete; + NetworkManagerAccessPoint &operator=(NetworkManagerAccessPoint &&) = delete; + NetworkManagerAccessPoint(sdbus::IConnection &system_bus, + sdbus::ObjectPath access_point_object_path) + : ProxyInterfaces(system_bus, "org.freedesktop.NetworkManager", + std::move(access_point_object_path)) { + registerProxy(); + } + ~NetworkManagerAccessPoint() { unregisterProxy(); } +}; +} // namespace linux +} // namespace nearby + +#endif diff --git a/internal/platform/implementation/linux/network_manager_active_connection.cc b/internal/platform/implementation/linux/network_manager_active_connection.cc new file mode 100644 index 00000000..10a364eb --- /dev/null +++ b/internal/platform/implementation/linux/network_manager_active_connection.cc @@ -0,0 +1,130 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include +#include + +#include "internal/platform/implementation/linux/dbus.h" +#include "internal/platform/implementation/linux/network_manager.h" +#include "internal/platform/implementation/linux/network_manager_active_connection.h" + +namespace nearby { +namespace linux { +namespace networkmanager { +std::string ActiveConnection::ActiveConnectionStateReason::ToString() const { + switch (value) { + case ActiveConnection::ActiveConnectionStateReason::kStateReasonUnknown: + return "The reason for the active connection state change is " + "unknown."; + case ActiveConnection::ActiveConnectionStateReason::kStateReasonNone: + return "No reason was given for the active connection state change."; + case ActiveConnection::ActiveConnectionStateReason:: + kStateReasonUserDisconnected: + return "The active connection changed state because the user " + "disconnected it."; + case ActiveConnection::ActiveConnectionStateReason:: + kStateReasonDeviceDisconnected: + return "The active connection changed state because the " + "device it was " + "using was disconnected."; + case ActiveConnection::ActiveConnectionStateReason:: + kStateReasonServiceStopped: + return "The service providing the VPN connection was stopped."; + case ActiveConnection::ActiveConnectionStateReason:: + kStateReasonIPConfigInvalid: + return "The IP config of the active connection was invalid."; + case ActiveConnection::ActiveConnectionStateReason:: + kStateReasonConnectTimeout: + return "The connection attempt to the VPN service timed out."; + case ActiveConnection::ActiveConnectionStateReason:: + kStateReasonServiceStartTimeout: + return "A timeout occurred while starting the service providing the " + "VPN connection."; + case ActiveConnection::ActiveConnectionStateReason:: + kStateReasonServiceStartFailed: + return "Starting the service providing the VPN connection failed."; + case ActiveConnection::ActiveConnectionStateReason::kStateReasonNoSecrets: + return "Necessary secrets for the connection were not provided."; + case ActiveConnection::ActiveConnectionStateReason::kStateReasonLoginFailed: + return "Authentication to the server failed."; + case ActiveConnection::ActiveConnectionStateReason:: + kStateReasonConnectionRemoved: + return "The connection was deleted from settings."; + case ActiveConnection::ActiveConnectionStateReason:: + kStateReasonDependencyFailed: + return "Master connection of this connection failed to activate."; + case ActiveConnection::ActiveConnectionStateReason:: + kStateReasonDeviceRealizeFailed: + return "Could not create the software device link."; + case ActiveConnection::ActiveConnectionStateReason:: + kStateReasonDeviceRemoved: + return "The device this connection depended on disappeared."; + } +} + +std::vector ActiveConnection::GetIP4Addresses() { + sdbus::ObjectPath ip4config_path; + try { + ip4config_path = Ip4Config(); + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_GET_ERROR(this, "Ip4Config", e); + return {}; + } + + IP4Config ip4config(system_bus_, ip4config_path); + std::vector> address_data; + try { + address_data = ip4config.AddressData(); + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_GET_ERROR(&ip4config, "AddressData", e); + return {}; + } + + std::vector ip4addresses; + for (auto &data : address_data) { + if (data.count("address") == 1) { + ip4addresses.push_back(data["address"]); + } + } + return ip4addresses; +} + +std::pair, bool> +ActiveConnection::WaitForConnection(absl::Duration timeout) { + NEARBY_LOGS(VERBOSE) << __func__ << ": Waiting for an update to " + << getObjectPath() << "'s state"; + + auto state_changed = [this]() { + this->state_mutex_.AssertReaderHeld(); + return this->state_ == kStateActivated || this->state_ == kStateDeactivated; + }; + + absl::Condition cond(&state_changed); + auto success = state_mutex_.ReaderLockWhenWithTimeout(cond, timeout); + auto reason = reason_; + auto state = state_; + state_mutex_.ReaderUnlock(); + + if (!success) { + return {reason, true}; + } + + return state == kStateActivated ? std::pair{std::nullopt, false} + : std::pair{std::optional(reason), false}; +} +} // namespace networkmanager +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/network_manager_active_connection.h b/internal/platform/implementation/linux/network_manager_active_connection.h new file mode 100644 index 00000000..7d1a3bc7 --- /dev/null +++ b/internal/platform/implementation/linux/network_manager_active_connection.h @@ -0,0 +1,125 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_NETWORK_MANAGER_ACTIVE_CONNECTION_H_ +#define PLATFORM_IMPL_LINUX_NETWORK_MANAGER_ACTIVE_CONNECTION_H_ + +#include + +#include +#include + +#include "absl/synchronization/mutex.h" +#include "internal/platform/implementation/linux/dbus.h" +#include "internal/platform/implementation/linux/generated/dbus/networkmanager/connection_active_client.h" + +namespace nearby { +namespace linux { +namespace networkmanager { +class ActiveConnection + : public sdbus::ProxyInterfaces< + org::freedesktop::NetworkManager::Connection::Active_proxy> { + public: + enum ActiveConnectionState { + kStateUnknown = 0, + kStateActivating = 1, + kStateActivated = 2, + kStateDeactivating = 3, + kStateDeactivated = 4 + }; + struct ActiveConnectionStateReason { + enum Value { + kStateReasonUnknown = 0, + kStateReasonNone = 1, + kStateReasonUserDisconnected = 2, + kStateReasonDeviceDisconnected = 3, + kStateReasonServiceStopped = 4, + kStateReasonIPConfigInvalid = 5, + kStateReasonConnectTimeout = 6, + kStateReasonServiceStartTimeout = 7, + kStateReasonServiceStartFailed = 8, + kStateReasonNoSecrets = 9, + kStateReasonLoginFailed = 10, + kStateReasonConnectionRemoved = 11, + kStateReasonDependencyFailed = 12, + kStateReasonDeviceRealizeFailed = 13, + kStateReasonDeviceRemoved = 14, + }; + + Value value{kStateReasonUnknown}; + std::string ToString() const; + }; + + ActiveConnection(const ActiveConnection &) = delete; + ActiveConnection(ActiveConnection &&) = delete; + ActiveConnection &operator=(const ActiveConnection &) = delete; + ActiveConnection &operator=(ActiveConnection &&) = delete; + explicit ActiveConnection(std::shared_ptr system_bus, + sdbus::ObjectPath active_connection_path) + : ProxyInterfaces(*system_bus, "org.freedesktop.NetworkManager", + std::move(active_connection_path)), + system_bus_(std::move(system_bus)), + state_(kStateUnknown), + reason_{ActiveConnection::ActiveConnectionStateReason:: + kStateReasonUnknown} { + registerProxy(); + try { + auto state = State(); + if (state >= kStateUnknown && state <= kStateDeactivated) { + state_ = static_cast(state); + } + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_GET_ERROR(this, "State", e); + } + } + virtual ~ActiveConnection() { unregisterProxy(); } + + protected: + void onStateChanged(const uint32_t &state, const uint32_t &reason) override + ABSL_LOCKS_EXCLUDED(state_mutex_) { + absl::MutexLock l(&state_mutex_); + if (state >= kStateUnknown && state <= kStateDeactivated) { + state_ = static_cast(state); + } + if (reason >= ActiveConnection::ActiveConnectionStateReason:: + kStateReasonUnknown && + reason <= ActiveConnection::ActiveConnectionStateReason:: + kStateReasonDeviceRemoved) { + reason_ = ActiveConnectionStateReason{ + static_cast(reason)}; + } + } + + public: + std::pair, bool> WaitForConnection( + absl::Duration timeout = absl::Seconds(10)) + ABSL_LOCKS_EXCLUDED(state_mutex_); + std::vector GetIP4Addresses(); + + private: + std::shared_ptr system_bus_; + + absl::Mutex state_mutex_; + ActiveConnectionState state_ ABSL_GUARDED_BY(state_mutex_); + ActiveConnectionStateReason reason_ ABSL_GUARDED_BY(state_mutex_); +}; + +extern std::ostream &operator<<( + std::ostream &stream, + const ActiveConnection::ActiveConnectionStateReason &reason); + +} // namespace networkmanager +} // namespace linux +} // namespace nearby +#endif diff --git a/internal/platform/implementation/linux/org.freedesktop.Accounts.User.xml b/internal/platform/implementation/linux/org.freedesktop.Accounts.User.xml new file mode 100644 index 00000000..d54ba441 --- /dev/null +++ b/internal/platform/implementation/linux/org.freedesktop.Accounts.User.xml @@ -0,0 +1,1003 @@ + + + + + + + + + + The new username. + + + + + + + Sets the userʼs username. Note that it is usually not allowed + to have multiple users with the same username. + + + + The caller needs one of the following PolicyKit authorizations: + + + org.freedesktop.accounts.user-administration + To change the username of any user + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + + + + + + + + + + The new name, typically in the form "Firstname Lastname". + + + + + + + Sets the userʼs real name. + + + + The caller needs one of the following PolicyKit authorizations: + + + org.freedesktop.accounts.change-own-user-data + To change their own name + + + org.freedesktop.accounts.user-administration + To change the name of another user + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + + + + + + + + + + The new email address. + + + + + + + Sets the userʼs email address. + + + Note that setting an email address in the AccountsService is + not the same as configuring a mail client. Mail clients might + default to email address that is configured here, though. + + + + The caller needs one of the following PolicyKit authorizations: + + + org.freedesktop.accounts.change-own-user-data + To change their own email address + + + org.freedesktop.accounts.user-administration + To change the email address of another user + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + + + + + + + + + + The new language, as a locale specification like "de_DE.UTF-8". + + + + + + + Sets the user's language. + + + The expectation is that display managers will start the + userʼs session with this locale. + + + + The caller needs one of the following PolicyKit authorizations: + + + org.freedesktop.accounts.change-own-user-data + To change their own language + + + org.freedesktop.accounts.user-administration + To change the language of another user + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + + + + + + + + + + The user's preferred languages, as an array of locale specification like "de_DE.UTF-8". + + + + + + + Sets the userʼs preferred languages. The first item in the list will + be used to set the Language property. + + + The expectation is that package installers will use + this to know which languages the user is interested in, so as + to install extra data, like translations, dictionaries, etc. + + + + The caller needs one of the following PolicyKit authorizations: + + + org.freedesktop.accounts.change-own-user-data + To change their own preferred languages + + + org.freedesktop.accounts.user-administration + To change the preferred languages of another user + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + + + + + + + + + + + The new xsession to start (e.g. "gnome") + + + + + + + Sets the userʼs x session. + + + The expectation is that display managers will log the user in to this + specified session, if available. + + Note this call is deprecated and has been superceded by SetSession since + not all graphical sessions use X as the display server. + + + + The caller needs one of the following PolicyKit authorizations: + + + org.freedesktop.accounts.change-own-user-data + To change their own language + + + org.freedesktop.accounts.user-administration + To change the language of another user + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + + + + + + + + + + + The new session to start (e.g. "gnome-xorg") + + + + + + + Sets the userʼs wayland or x session. + + + The expectation is that display managers will log the user in to this + specified session, if available. + + + + The caller needs one of the following PolicyKit authorizations: + + + org.freedesktop.accounts.change-own-user-data + To change their own language + + + org.freedesktop.accounts.user-administration + To change the language of another user + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + + + + + + + + + + + The type of the new session to start (e.g. "wayland" or "x11") + + + + + + + Sets the session type of the userʼs session. + + + Display managers may use this property to decide what type of display server to use when + loading the session + + + + The caller needs one of the following PolicyKit authorizations: + + + org.freedesktop.accounts.change-own-user-data + To change their own language + + + org.freedesktop.accounts.user-administration + To change the language of another user + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + + + + + + + + + + The new location as a freeform string. + + + + + + + Sets the userʼs location. + + + + The caller needs one of the following PolicyKit authorizations: + + + org.freedesktop.accounts.change-own-user-data + To change their own location + + + org.freedesktop.accounts.user-administration + To change the location of another user + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + + + + + + + + + + The new homedir as an absolute path. + + + + + + + Sets the userʼs home directory. + + + Note that changing the userʼs home directory moves all the content + from the old location to the new one, and is potentially an + expensive operation. + + + + The caller needs one of the following PolicyKit authorizations: + + + org.freedesktop.accounts.user-administration + To change the home directory of a user + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + + + + + + + + + + The new user shell. + + + + + + + Sets the userʼs shell. + + + Note that setting the shell to a non-allowed program may + prevent the user from logging in. + + + + The caller needs one of the following PolicyKit authorizations: + + + org.freedesktop.accounts.user-administration + To change the shell of a user + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + + + + + + + + + + The absolute filename of a png file to use as the userʼs icon. + + + + + + + Sets the userʼs icon. + + + + The caller needs one of the following PolicyKit authorizations: + + + org.freedesktop.accounts.change-own-user-data + To change their own icon + + + org.freedesktop.accounts.user-administration + To change the icon of another user + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + + + + + + + + + + Whether to lock or unlock the userʼs account. + + + + + + + Locks or unlocks a userʼs account. + + + Locking an account prevents the user from logging in. + + + + The caller needs one of the following PolicyKit authorizations: + + + org.freedesktop.accounts.user-administration + To lock or unlock user accounts + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + + + + + + + + + + The new account type, encoded as an integer: + + + 0 + Standard user + + + 1 + Administrator + + + + + + + + + Changes the userʼs account type. + + + + The caller needs one of the following PolicyKit authorizations: + + + org.freedesktop.accounts.user-administration + To change an account type + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + + + + + + + + + + The new password mode, encoded as an integer: + + + 0 + Regular password + + + 1 + Password must be set at next login + + + 2 + No password + + + + + + + + + Changes the userʼs password mode. + + + Note that changing the password mode has the side-effect of + unlocking the account. + + + + The caller needs one of the following PolicyKit authorizations: + + + org.freedesktop.accounts.user-administration + To change a userʼs password mode + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + + + + + + + + + + The crypted password. + + + + + + + The password hint. + + + + + + + Sets a new password for this user. + + + Note that setting a password has the side-effect of + unlocking the account. + + + + The caller needs one of the following PolicyKit authorizations: + + + org.freedesktop.accounts.user-administration + To change the password of a user + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + + + + + + + + + + The password hint. + + + + + + + Sets the userʼs password hint. + + + + The caller needs one of the following PolicyKit authorizations: + + + org.freedesktop.accounts.change-own-user-data + To change their own language + + + org.freedesktop.accounts.user-administration + To change the language of another user + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + + + + + + + + + + Whether to enable automatic login for this user. + + + + + + + Enables or disables automatic login for a user. + + + Note that usually only one user can have automatic login + enabled, so turning it on for a user will disable it for + the previously configured autologin user. + + + + The caller needs one of the following PolicyKit authorizations: + + + org.freedesktop.accounts.set-login-option + To change the login screen configuration + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The uid of the user. + + + + + + + + + + The username of the user. + + + + + + + + + + The userʼs real name. + + + + + + + + + + The userʼs account type, encoded as an integer: + + + 0 + Standard user + + + 1 + Administrator + + + + + + + + + + + + The userʼs home directory. + + + + + + + + + + The userʼs shell. + + + + + + + + + + The email address. + + + + + + + + + + The user's main language, as a locale specification like "de_DE.UTF-8". + + + + + + + + + + The user's other preferred languages, as a locale specification like "de_DE.UTF-8". + + + + + + + + + + The userʼs Wayland or X session. + + + + + + + + + + The type of session the user should use (e.g. "wayland" or "x11") + + + + + + + + + + The userʼs x session. + + + + + + + + + + The userʼs location. + + + + + + + + + + How often the user has logged in. + + + + + + + + + + The last login time. + + + + + + + + + + + The login history for this user. + Each entry in the array represents a login session. The first two + members are the login time and logout time, as timestamps (seconds since the epoch). If the session is still running, the logout time + is 0. + + + The a{sv} member is a dictionary containing additional information + about the session. Possible members include 'type' (with values like ':0', 'tty0', 'pts/0' etc). + + + + + + + + + + The filename of a png file containing the userʼs icon. + + + + + + + + + + Whether the userʼs account has retained state + + + + + + + + + + Whether the userʼs account is locked. + + + + + + + + + + The password mode for the user account, encoded as an integer: + + + 0 + Regular password + + + 1 + Password must be set at next login + + + 2 + No password + + + + + + + + + + + + The password hint for the user. + + + + + + + + + + Whether automatic login is enabled for the user. + + + + + + + + + + Whether this is a 'system' account, like 'root' or 'nobody'. + System accounts should normally not appear in lists of + users, and ListCachedUsers will not include such accounts. + + + + + + + + + + Whether the user is a local account or not. + + + + + + + + + + Emitted when the user is changed. + + + + + + + diff --git a/internal/platform/implementation/linux/output_file.h b/internal/platform/implementation/linux/output_file.h new file mode 100644 index 00000000..54ebe127 --- /dev/null +++ b/internal/platform/implementation/linux/output_file.h @@ -0,0 +1,45 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_OUTPUT_FILE_H_ +#define PLATFORM_IMPL_LINUX_OUTPUT_FILE_H_ + +#include "internal/platform/byte_array.h" +#include "internal/platform/exception.h" +#include "internal/platform/implementation/output_file.h" + +namespace nearby { +namespace linux { + +// An OutputFile represents a writable file on the system. +class OutputFile : public api::OutputFile { + public: + // TODO(b/184975123): replace with real implementation. + ~OutputFile() override = default; + + // throws Exception::kIo + // TODO(b/184975123): replace with real implementation. + Exception Write(const ByteArray& data) override { return Exception{}; } + // throws Exception::kIo + // TODO(b/184975123): replace with real implementation. + Exception Flush() override { return Exception{}; } + // throws Exception::kIo + // TODO(b/184975123): replace with real implementation. + Exception Close() override { return Exception{}; } +}; + +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPL_LINUX_OUTPUT_FILE_H_ diff --git a/internal/platform/implementation/linux/output_file_test.cc b/internal/platform/implementation/linux/output_file_test.cc new file mode 100644 index 00000000..95a87329 --- /dev/null +++ b/internal/platform/implementation/linux/output_file_test.cc @@ -0,0 +1,80 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "internal/platform/implementation/linux/output_file.h" +#include "gtest/gtest.h" +#include "internal/platform/exception.h" +#include "internal/platform/implementation/platform.h" +#include "internal/platform/implementation/windows/test_utils.h" +#include "internal/platform/payload_id.h" + +class OutputFileTests : public testing::Test { + protected: + // You can define per-test set-up logic as usual. + void SetUp() override { + nearby::PayloadId payloadId(TEST_PAYLOAD_ID); + if (std::filesystem::exists(test_utils::GetPayloadPath(payloadId))) { + std::filesystem::remove(test_utils::GetPayloadPath(payloadId)); + } + } + + // You can define per-test tear-down logic as usual. + void TearDown() override { + nearby::PayloadId payloadId(TEST_PAYLOAD_ID); + if (std::filesystem::exists( + test_utils::GetPayloadPath(payloadId).c_str())) { + std::filesystem::remove(test_utils::GetPayloadPath(payloadId).c_str()); + } + } +}; + +TEST_F(OutputFileTests, SuccessfulCreation) { + nearby::PayloadId payloadId(TEST_PAYLOAD_ID); + std::unique_ptr outputFile = nullptr; + + EXPECT_NO_THROW( + outputFile = + nearby::api::ImplementationPlatform::CreateOutputFile(payloadId)); + + EXPECT_NE(outputFile, nullptr); + EXPECT_NO_THROW(outputFile->Close()); +} + +TEST_F(OutputFileTests, SuccessfulClose) { + nearby::PayloadId payloadId(TEST_PAYLOAD_ID); + std::unique_ptr outputFile = nullptr; + + EXPECT_NO_THROW( + outputFile = + nearby::api::ImplementationPlatform::CreateOutputFile(payloadId)); + + EXPECT_NO_THROW(outputFile->Close()); + + std::filesystem::remove(test_utils::GetPayloadPath(payloadId).c_str()); +} + +TEST_F(OutputFileTests, SuccessfulWrite) { + nearby::PayloadId payloadId(TEST_PAYLOAD_ID); + nearby::ByteArray data(std::string(TEST_STRING)); + std::unique_ptr outputFile = nullptr; + + EXPECT_NO_THROW( + outputFile = + nearby::api::ImplementationPlatform::CreateOutputFile(payloadId)); + + EXPECT_NO_THROW(outputFile->Write(data)); + EXPECT_NO_THROW(outputFile->Close()); + + std::filesystem::remove(test_utils::GetPayloadPath(payloadId).c_str()); +} diff --git a/internal/platform/implementation/linux/platform.cc b/internal/platform/implementation/linux/platform.cc new file mode 100644 index 00000000..1829a717 --- /dev/null +++ b/internal/platform/implementation/linux/platform.cc @@ -0,0 +1,377 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include +#include +#include + +#include "device_info.h" +#include "internal/platform/implementation/atomic_boolean.h" +#include "internal/platform/implementation/atomic_reference.h" +#include "internal/platform/implementation/bluetooth_adapter.h" +#include "internal/platform/implementation/count_down_latch.h" +#include "internal/platform/implementation/http_loader.h" +#include "internal/platform/implementation/input_file.h" +#include "internal/platform/implementation/linux/atomic_boolean.h" +#include "internal/platform/implementation/linux/atomic_uint32.h" +#include "internal/platform/implementation/linux/ble_v2_medium.h" +#include "internal/platform/implementation/linux/bluetooth_adapter.h" +#include "internal/platform/implementation/linux/bluetooth_classic_medium.h" +#include "internal/platform/implementation/linux/bluez.h" +#include "internal/platform/implementation/linux/condition_variable.h" +#include "internal/platform/implementation/linux/dbus.h" +#include "internal/platform/implementation/linux/generated/dbus/bluez/adapter_client.h" +#include "internal/platform/implementation/linux/mutex.h" +#include "internal/platform/implementation/linux/preferences_manager.h" +#include "internal/platform/implementation/linux/submittable_executor.h" +#include "internal/platform/implementation/linux/timer.h" +#include "internal/platform/implementation/linux/wifi_direct.h" +#include "internal/platform/implementation/linux/wifi_hotspot.h" +#include "internal/platform/implementation/linux/wifi_lan.h" +#include "internal/platform/implementation/linux/wifi_medium.h" +#include "internal/platform/implementation/platform.h" +#include "internal/platform/implementation/shared/count_down_latch.h" +#include "internal/platform/implementation/shared/file.h" +#include "internal/platform/implementation/submittable_executor.h" +#include "internal/platform/implementation/wifi_hotspot.h" +#include "internal/platform/implementation/wifi_lan.h" +#include "internal/platform/payload_id.h" +#include "log_message.h" +#include "scheduled_executor.h" + +namespace nearby { +namespace api { +std::string ImplementationPlatform::GetCustomSavePath( + const std::string &parent_folder, const std::string &file_name) { + auto fs = std::filesystem::path(parent_folder); + return fs / file_name; +} + +std::string ImplementationPlatform::GetDownloadPath( + const std::string &parent_folder, const std::string &file_name) { + auto downloads = std::filesystem::path(getenv("XDG_DOWNLOAD_DIR")); + + return downloads / std::filesystem::path(parent_folder).filename() / + std::filesystem::path(file_name).filename(); +} + +std::string ImplementationPlatform::GetDownloadPath( + const std::string &file_name) { + auto downloads = std::filesystem::path(getenv("XDG_DOWNLOAD_DIR")); + return downloads / std::filesystem::path(file_name).filename(); +} + +std::string ImplementationPlatform::GetAppDataPath( + const std::string &file_name) { + auto state = std::filesystem::path(getenv("XDG_STATE_HOME")); + return state / std::filesystem::path(file_name).filename(); +} + +OSName ImplementationPlatform::GetCurrentOS() { return OSName::kWindows; } + +std::unique_ptr ImplementationPlatform::CreateAtomicBoolean( + bool initial_value) { + return std::make_unique(initial_value); +} + +std::unique_ptr ImplementationPlatform::CreateAtomicUint32( + std::uint32_t value) { + return std::make_unique(value); +} + +std::unique_ptr +ImplementationPlatform::CreateCountDownLatch(std::int32_t count) { + return std::make_unique(count); +} + +#pragma push_macro("CreateMutex") +#undef CreateMutex +std::unique_ptr ImplementationPlatform::CreateMutex( + Mutex::Mode mode) { + return std::make_unique(mode); +} +#pragma pop_macro("CreateMutex") + +std::unique_ptr +ImplementationPlatform::CreateConditionVariable(api::Mutex *mutex) { + return std::make_unique(mutex); +} + +std::unique_ptr ImplementationPlatform::CreateInputFile( + PayloadId id, std::int64_t total_size) { + auto path = GetDownloadPath(std::to_string(id)); + return nearby::shared::IOFile::CreateInputFile(path, total_size); +} + +std::unique_ptr ImplementationPlatform::CreateInputFile( + const std::string &file_path, size_t size) { + return nearby::shared::IOFile::CreateInputFile(file_path, size); +} + +std::unique_ptr ImplementationPlatform::CreateOutputFile( + PayloadId payload_id) { + return nearby::shared::IOFile::CreateOutputFile( + GetDownloadPath("", std::to_string(payload_id))); +} + +std::unique_ptr ImplementationPlatform::CreateOutputFile( + const std::string &file_path) { + std::filesystem::path path(file_path); + try { + std::filesystem::create_directories(path.parent_path()); + } catch (std::filesystem::filesystem_error const &err) { + NEARBY_LOGS(ERROR) << __func__ << ": error creating directory tree " + << path.parent_path() << ": " << err.what(); + } + + return nearby::shared::IOFile::CreateOutputFile(path.string()); +} + +std::unique_ptr ImplementationPlatform::CreateLogMessage( + const char *file, int line, LogMessage::Severity severity) { + return std::make_unique(file, line, severity); +} + +std::unique_ptr +ImplementationPlatform::CreateSingleThreadExecutor() { + return std::make_unique(); +} + +std::unique_ptr +ImplementationPlatform::CreateMultiThreadExecutor( + std::int32_t max_concurrency) { + return std::make_unique(max_concurrency); +} + +std::unique_ptr +ImplementationPlatform::CreateScheduledExecutor() { + return std::make_unique(); +} + +std::unique_ptr +ImplementationPlatform::CreateBluetoothAdapter() { + auto system_bus = linux::getSystemBusConnection(); + auto manager = linux::bluez::BluezObjectManager(*system_bus); + try { + auto interfaces = manager.GetManagedObjects(); + for (auto &[object, properties] : interfaces) { + if (properties.count(org::bluez::Adapter1_proxy::INTERFACE_NAME) == 1) { + NEARBY_LOGS(INFO) << __func__ << ": found bluetooth adapter " << object; + return std::make_unique(system_bus, object); + } + } + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(&manager, "GetManagedObjects", e); + return nullptr; + } + + NEARBY_LOGS(ERROR) << __func__ + << ": couldn't find a bluetooth adapter on this system"; + return nullptr; +} + +std::unique_ptr +ImplementationPlatform::CreateBluetoothClassicMedium( + BluetoothAdapter &adapter) { + return std::make_unique( + dynamic_cast(adapter)); +} + +std::unique_ptr ImplementationPlatform::CreateBleMedium( + BluetoothAdapter &adapter) { + return nullptr; +} + +std::unique_ptr +ImplementationPlatform::CreateBleV2Medium(api::BluetoothAdapter &adapter) { + return std::make_unique( + dynamic_cast(adapter)); +} + +namespace { +static std::unique_ptr createWifiMedium( + std::shared_ptr nm) { + std::vector device_paths; + + try { + device_paths = nm->GetAllDevices(); + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(nm, "GetAllDevices", e); + return nullptr; + } + + auto manager = linux::networkmanager::ObjectManager(nm->GetConnection()); + + std::map>> + objects; + try { + objects = manager.GetManagedObjects(); + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(nm, "GetManagedObjects", e); + return nullptr; + } + + for (auto &device_path : device_paths) { + if (objects.count(device_path) == 1) { + auto device = objects[device_path]; + if (device.count(org::freedesktop::NetworkManager::Device:: + Wireless_proxy::INTERFACE_NAME) == 1) { + NEARBY_LOGS(INFO) << __func__ + << ": Found a wireless device at :" << device_path; + return std::make_unique(nm, + device_path); + } + } + } + + NEARBY_LOGS(ERROR) << __func__ + << ": couldn't find a wireless device on this system"; + return nullptr; +} +} // namespace + +std::unique_ptr ImplementationPlatform::CreateWifiMedium() { + auto nm = + std::make_shared(linux::getSystemBusConnection()); + return createWifiMedium(nm); +} + +std::unique_ptr +ImplementationPlatform::CreateWifiLanMedium() { + auto nm = + std::make_shared(linux::getSystemBusConnection()); + return std::make_unique(nm); +} + +std::unique_ptr +ImplementationPlatform::CreateWifiHotspotMedium() { + auto nm = + std::make_shared(linux::getSystemBusConnection()); + auto wifiMedium = createWifiMedium(nm); + + if (wifiMedium == nullptr) { + NEARBY_LOGS(ERROR) << __func__ << ": Could not create a WiFi medium"; + return nullptr; + } + + return std::make_unique( + nm, std::move(wifiMedium)); +} + +std::unique_ptr +ImplementationPlatform::CreateWifiDirectMedium() { + auto nm = + std::make_shared(linux::getSystemBusConnection()); + auto wifiMedium = createWifiMedium(nm); + + if (wifiMedium == nullptr) { + NEARBY_LOGS(ERROR) << __func__ << ": Could not create a WiFi medium"; + return nullptr; + } + + return std::make_unique( + nm, std::move(wifiMedium)); +} + +std::unique_ptr ImplementationPlatform::CreateTimer() { + return std::make_unique(); +} + +std::unique_ptr ImplementationPlatform::CreateDeviceInfo() { + return std::make_unique(linux::getSystemBusConnection()); +} + +absl::StatusOr ImplementationPlatform::SendRequest( + const WebRequest &request) { + if (request.body.size() >= (8 * 1024 * 1024)) { + return absl::Status(absl::StatusCode::kResourceExhausted, + "request body too large"); + } + + CURL *handle = curl_easy_init(); + char errbuf[CURL_ERROR_SIZE]; + errbuf[0] = '\0'; + + curl_easy_setopt(handle, CURLOPT_URL, request.url.c_str()); + curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, errbuf); + + if (request.method == "GET") + curl_easy_setopt(handle, CURLOPT_HTTPGET, 1L); + else if (request.method == "POST") + curl_easy_setopt(handle, CURLOPT_POST, 1L); + else + curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, request.method.c_str()); + + curl_easy_setopt(handle, CURLOPT_UPLOAD, request.body.c_str()); + + struct curl_slist *headers_slist = nullptr; + + for (auto &[key, value] : request.headers) { + auto hdr = absl::StrCat(key, ": ", value); + auto temp = curl_slist_append(headers_slist, hdr.c_str()); + if (temp == nullptr) { + if (headers_slist != nullptr) { + curl_slist_free_all(headers_slist); + } + return absl::Status(absl::StatusCode::kResourceExhausted, + "failed to append header to slist"); + } + } + + curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers_slist); + + api::WebResponse response; + + if (curl_easy_perform(handle) != CURLE_OK) { + NEARBY_LOGS(ERROR) << __func__ + << ": Error performing HTTP request: " << errbuf; + return absl::Status(absl::StatusCode::kUnknown, errbuf); + } + + struct curl_header *prev = nullptr; + struct curl_header *h; + + h = curl_easy_nextheader(handle, CURLH_HEADER, 0, prev); + while (h != nullptr) { + response.headers.emplace(h->name, h->value); + } + + auto writefn = [](char *ptr, size_t size, size_t nmemb, void *userdata) { + std::string *body = static_cast(userdata); + body->append(ptr, size * nmemb); + }; + + curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, writefn); + curl_easy_setopt(handle, CURLOPT_WRITEDATA, + static_cast(&response.body)); + long status; + curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &status); + response.status_code = status; + return response; +} + +#ifndef NEARBY_CHROMIUM +std::unique_ptr +ImplementationPlatform::CreatePreferencesManager(absl::string_view path) { + return std::make_unique(path); +} +#endif + +} // namespace api +} // namespace nearby diff --git a/internal/platform/implementation/linux/preferences_manager.cc b/internal/platform/implementation/linux/preferences_manager.cc new file mode 100644 index 00000000..f0a8d8ee --- /dev/null +++ b/internal/platform/implementation/linux/preferences_manager.cc @@ -0,0 +1,281 @@ +// Copyright 2021-2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include // NOLINT(build/c++17) +#include +#include +#include +#include +#include + +#include "absl/strings/string_view.h" +#include "internal/platform/implementation/linux/preferences_manager.h" +#include "internal/platform/implementation/linux/preferences_repository.h" +#include "internal/platform/logging.h" +#include "nlohmann/json.hpp" +#include "nlohmann/json_fwd.hpp" + +namespace nearby { +namespace linux { +namespace { +using json = ::nlohmann::json; +} // namespace + +PreferencesManager::PreferencesManager(absl::string_view file_path) + : api::PreferencesManager(file_path) { + std::optional path = + nearby::api::ImplementationPlatform::CreateDeviceInfo() + ->GetLocalAppDataPath(); + if (!path.has_value()) { + path = std::filesystem::temp_directory_path(); + } + + std::filesystem::path full_path = *path / std::string(file_path); + preferences_repository_ = + std::make_unique(full_path.string()); + value_ = preferences_repository_->LoadPreferences(); +} + +bool PreferencesManager::Set(absl::string_view key, const json& value) { + absl::MutexLock lock(&mutex_); + return SetValue(key, value); +} + +bool PreferencesManager::SetBoolean(absl::string_view key, bool value) { + absl::MutexLock lock(&mutex_); + return SetValue(key, value); +} + +bool PreferencesManager::SetInteger(absl::string_view key, int value) { + absl::MutexLock lock(&mutex_); + return SetValue(key, value); +} + +bool PreferencesManager::SetInt64(absl::string_view key, int64_t value) { + absl::MutexLock lock(&mutex_); + return SetValue(key, value); +} + +bool PreferencesManager::SetString(absl::string_view key, + absl::string_view value) { + absl::MutexLock lock(&mutex_); + return SetValue(key, absl::StrCat(value)); +} + +bool PreferencesManager::SetBooleanArray(absl::string_view key, + absl::Span value) { + absl::MutexLock lock(&mutex_); + return SetArrayValue(key, value); +} + +bool PreferencesManager::SetIntegerArray(absl::string_view key, + absl::Span value) { + absl::MutexLock lock(&mutex_); + return SetArrayValue(key, value); +} + +bool PreferencesManager::SetInt64Array(absl::string_view key, + absl::Span value) { + absl::MutexLock lock(&mutex_); + return SetArrayValue(key, value); +} + +bool PreferencesManager::SetStringArray(absl::string_view key, + absl::Span value) { + absl::MutexLock lock(&mutex_); + return SetArrayValue(key, value); +} + +bool PreferencesManager::SetTime(absl::string_view key, absl::Time value) { + // Save time as nanos + absl::MutexLock lock(&mutex_); + int64_t tt = absl::ToUnixNanos(value); + if (value_[absl::StrCat(key)] == tt) { + return false; + } + + value_[absl::StrCat(key)] = tt; + return Commit(); +} + +// Get JSON value. +json PreferencesManager::Get(absl::string_view key, + const json& default_value) const { + absl::MutexLock lock(&mutex_); + return GetValue(key, default_value); +} + +bool PreferencesManager::GetBoolean(absl::string_view key, + bool default_value) const { + absl::MutexLock lock(&mutex_); + return GetValue(key, default_value); +} + +int PreferencesManager::GetInteger(absl::string_view key, + int default_value) const { + absl::MutexLock lock(&mutex_); + return GetValue(key, default_value); +} + +int64_t PreferencesManager::GetInt64(absl::string_view key, + int64_t default_value) const { + absl::MutexLock lock(&mutex_); + return GetValue(key, default_value); +} + +std::string PreferencesManager::GetString( + absl::string_view key, const std::string& default_value) const { + absl::MutexLock lock(&mutex_); + return GetValue(key, default_value); +} + +std::vector PreferencesManager::GetBooleanArray( + absl::string_view key, absl::Span default_value) const { + absl::MutexLock lock(&mutex_); + return GetArrayValue(key, default_value); +} + +std::vector PreferencesManager::GetIntegerArray( + absl::string_view key, absl::Span default_value) const { + absl::MutexLock lock(&mutex_); + return GetArrayValue(key, default_value); +} + +std::vector PreferencesManager::GetInt64Array( + absl::string_view key, absl::Span default_value) const { + absl::MutexLock lock(&mutex_); + return GetArrayValue(key, default_value); +} + +std::vector PreferencesManager::GetStringArray( + absl::string_view key, absl::Span default_value) const { + absl::MutexLock lock(&mutex_); + return GetArrayValue(key, default_value); +} + +absl::Time PreferencesManager::GetTime(absl::string_view key, + absl::Time default_value) const { + absl::MutexLock lock(&mutex_); + auto result = value_.find(absl::StrCat(key)); + if (result == value_.end()) { + return default_value; + } + + return absl::FromUnixNanos(result->get()); +} + +// Removes preferences +void PreferencesManager::Remove(absl::string_view key) { + absl::MutexLock lock(&mutex_); + value_.erase(absl::StrCat(key)); +} + +// Private methods + +// Writes data to storage. +bool PreferencesManager::Commit() { + if (!preferences_repository_->SavePreferences(value_)) { + NEARBY_LOGS(ERROR) << "Failed to save preference." << std::endl; + return false; + } + return true; +} + +bool PreferencesManager::SetValue(absl::string_view key, const json& value) { + if (!value_.is_object()) { + NEARBY_LOGS(ERROR) << "Preferences is no longer an object! value_=" + << value_.dump(4); + value_ = json::object(); + } + + if (value_[absl::StrCat(key)] == value) { + return false; + } + + value_[absl::StrCat(key)] = value; + return Commit(); +} + +template +T PreferencesManager::GetValue(absl::string_view key, + const T& default_value) const { + if (!value_.is_object()) { + NEARBY_LOGS(ERROR) << "Preferences is no longer an object! value_=" + << value_.dump(4); + return default_value; + } + + auto it = value_.find(absl::StrCat(key)); + if (it == value_.end()) { + return default_value; + } + return it->get(); +} + +template +bool PreferencesManager::SetArrayValue(absl::string_view key, + absl::Span value) { + if (!value_.is_object()) { + NEARBY_LOGS(ERROR) << "Preferences is no longer an object! value_=" + << value_.dump(4); + value_ = json::object(); + } + + json array_value = json::array(); + for (const T& item_value : value) { + array_value.push_back(item_value); + } + + if (value_[absl::StrCat(key)] == array_value) { + return false; + } + + value_[absl::StrCat(key)] = array_value; + return Commit(); +} + +template +std::vector PreferencesManager::GetArrayValue( + absl::string_view key, absl::Span default_value) const { + std::vector result; + + if (!value_.is_object()) { + NEARBY_LOGS(ERROR) << "Preferences is no longer an object! value_=" + << value_.dump(4); + + for (const T& value : default_value) { + result.push_back(value); + } + return result; + } + + auto array_value = value_.find(absl::StrCat(key)); + if (array_value == value_.end() || !array_value->is_array()) { + for (const T& value : default_value) { + result.push_back(value); + } + return result; + } + + auto it = array_value->begin(); + while (it != array_value->end()) { + result.push_back(it->get()); + ++it; + } + + return result; +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/preferences_manager.h b/internal/platform/implementation/linux/preferences_manager.h new file mode 100644 index 00000000..860fdb04 --- /dev/null +++ b/internal/platform/implementation/linux/preferences_manager.h @@ -0,0 +1,141 @@ +// Copyright 2021-2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPLEMENTATION_LINUX_PREFERENCES_MANAGER_H_ +#define PLATFORM_IMPLEMENTATION_LINUX_PREFERENCES_MANAGER_H_ + +#include + +#include +#include +#include + +#include "absl/base/thread_annotations.h" +#include "absl/strings/string_view.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/time.h" +#include "absl/types/span.h" +#include "internal/platform/implementation/linux/preferences_repository.h" +#include "internal/platform/implementation/preferences_manager.h" +#include "nlohmann/json.hpp" +#include "nlohmann/json_fwd.hpp" + +namespace nearby { +namespace linux { + +// Sets and gets preference settings from the application. +// Preferences are persistent storage for application settings, it is key/value +// based settings. Application components can observe the interested preference +// change by the observer. +class PreferencesManager : public api::PreferencesManager { + public: + explicit PreferencesManager(absl::string_view path); + + // Sets values + + bool Set(absl::string_view key, const nlohmann::json& value) override + ABSL_LOCKS_EXCLUDED(mutex_); + + bool SetBoolean(absl::string_view key, bool value) override + ABSL_LOCKS_EXCLUDED(mutex_); + bool SetInteger(absl::string_view key, int value) override + ABSL_LOCKS_EXCLUDED(mutex_); + bool SetInt64(absl::string_view key, int64_t value) override + ABSL_LOCKS_EXCLUDED(mutex_); + bool SetString(absl::string_view key, absl::string_view value) override + ABSL_LOCKS_EXCLUDED(mutex_); + + bool SetBooleanArray(absl::string_view key, + absl::Span value) override + ABSL_LOCKS_EXCLUDED(mutex_); + bool SetIntegerArray(absl::string_view key, + absl::Span value) override + ABSL_LOCKS_EXCLUDED(mutex_); + bool SetInt64Array(absl::string_view key, + absl::Span value) override + ABSL_LOCKS_EXCLUDED(mutex_); + bool SetStringArray(absl::string_view key, + absl::Span value) override + ABSL_LOCKS_EXCLUDED(mutex_); + + bool SetTime(absl::string_view key, absl::Time value) override + ABSL_LOCKS_EXCLUDED(mutex_); + + // Gets values + nlohmann::json Get(absl::string_view key, + const nlohmann::json& default_value) const override + ABSL_LOCKS_EXCLUDED(mutex_); + + bool GetBoolean(absl::string_view key, bool default_value) const override + ABSL_LOCKS_EXCLUDED(mutex_); + int GetInteger(absl::string_view key, int default_value) const override + ABSL_LOCKS_EXCLUDED(mutex_); + int64_t GetInt64(absl::string_view key, int64_t default_value) const override + ABSL_LOCKS_EXCLUDED(mutex_); + std::string GetString(absl::string_view key, + const std::string& default_value) const override + ABSL_LOCKS_EXCLUDED(mutex_); + + std::vector GetBooleanArray(absl::string_view key, + absl::Span default_value) + const override ABSL_LOCKS_EXCLUDED(mutex_); + std::vector GetIntegerArray( + absl::string_view key, absl::Span default_value) const override + ABSL_LOCKS_EXCLUDED(mutex_); + std::vector GetInt64Array(absl::string_view key, + absl::Span default_value) + const override ABSL_LOCKS_EXCLUDED(mutex_); + std::vector GetStringArray( + absl::string_view key, + absl::Span default_value) const override + ABSL_LOCKS_EXCLUDED(mutex_); + + absl::Time GetTime(absl::string_view key, + absl::Time default_value) const override + ABSL_LOCKS_EXCLUDED(mutex_); + + // Removes preferences + void Remove(absl::string_view key) override ABSL_LOCKS_EXCLUDED(mutex_); + + private: + // Writes data to storage. + bool Commit() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + bool SetValue(absl::string_view key, const nlohmann::json& value) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + template + T GetValue(absl::string_view key, const T& default_value) const + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + template + bool SetArrayValue(absl::string_view key, absl::Span value) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + template + std::vector GetArrayValue(absl::string_view key, + absl::Span default_value) const + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + nlohmann::json value_ ABSL_GUARDED_BY(mutex_); + std::unique_ptr preferences_repository_ + ABSL_GUARDED_BY(mutex_); + + mutable absl::Mutex mutex_; +}; + +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPLEMENTATION_LINUX_PREFERENCES_MANAGER_H_ diff --git a/internal/platform/implementation/linux/preferences_manager_test.cc b/internal/platform/implementation/linux/preferences_manager_test.cc new file mode 100644 index 00000000..d1aa5393 --- /dev/null +++ b/internal/platform/implementation/linux/preferences_manager_test.cc @@ -0,0 +1,185 @@ +// Copyright 2021-2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include +#include // NOLINT(build/c++17) +#include +#include +#include +#include +#include + +#include "absl/strings/string_view.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" +#include "absl/types/span.h" +#include "gtest/gtest.h" +#include "internal/platform/implementation/linux/preferences_manager.h" +#include "internal/platform/logging.h" +#include "nlohmann/json.hpp" +#include "nlohmann/json_fwd.hpp" + +namespace nearby { +namespace linux { +namespace { +using json = ::nlohmann::json; +constexpr absl::Duration kTimeOut = absl::Milliseconds(200); +constexpr char kPreferencesFilePath[] = "Google/Nearby/Sharing"; +} // namespace + +TEST(PreferencesManager, CorruptedConfigFile) { + std::filesystem::path settingsPath = std::filesystem::temp_directory_path(); + std::ofstream output_stream{settingsPath / "preferences.json"}; + output_stream << "CORRUPTED" << std::endl; + + NEARBY_LOGS(INFO) << "Loading preferences from: " << settingsPath.string(); + EXPECT_EQ(PreferencesManager(settingsPath.string()).GetInteger("data", 100), + 100); +} + +TEST(PreferencesManager, ValidConfigFile) { + std::filesystem::path settingsPath = std::filesystem::temp_directory_path(); + std::ofstream output_stream{settingsPath / "preferences.json"}; + output_stream << "{\"data\":8, \"name\": \"Valid\"}" << std::endl; + output_stream.close(); + + NEARBY_LOGS(INFO) << "Loading preferences from: " << settingsPath.string(); + EXPECT_EQ(PreferencesManager(settingsPath.string()).GetInteger("data", 100), + 8); +} + +TEST(PreferencesManager, SetAndGetBoolean) { + std::string bool_key = "bool_key"; + PreferencesManager pm(kPreferencesFilePath); + EXPECT_TRUE(pm.GetBoolean(bool_key, true)); + pm.SetBoolean(bool_key, true); + EXPECT_TRUE(pm.GetBoolean(bool_key, false)); +} + +TEST(PreferencesManager, SetAndGetInt) { + std::string int_key = "int_key"; + PreferencesManager pm(kPreferencesFilePath); + EXPECT_EQ(pm.GetInteger(int_key, 1234), 1234); + pm.SetInteger(int_key, 6789); + EXPECT_EQ(pm.GetInteger(int_key, 0), 6789); +} + +TEST(PreferencesManager, SetAndGetInt64) { + std::string int64_key = "int64_key"; + PreferencesManager pm(kPreferencesFilePath); + EXPECT_EQ(pm.GetInt64(int64_key, 1234), 1234); + pm.SetInt64(int64_key, 56789); + EXPECT_EQ(pm.GetInt64(int64_key, 0), 56789); +} + +TEST(PreferencesManager, SetAndGetString) { + std::string string_key = "string_key"; + PreferencesManager pm(kPreferencesFilePath); + EXPECT_EQ(pm.GetString(string_key, "abcd"), "abcd"); + pm.SetString(string_key, "this is a test string"); + EXPECT_EQ(pm.GetString(string_key, ""), "this is a test string"); +} + +TEST(PreferencesManager, SetAndGetTime) { + std::string time_key = "time_key"; + PreferencesManager pm(kPreferencesFilePath); + absl::Time time = absl::Now(); + EXPECT_EQ(pm.GetTime(time_key, time), time); + pm.SetTime(time_key, time); + absl::Time ret = pm.GetTime(time_key, absl::Now()); + EXPECT_EQ(absl::ToUnixNanos(ret), absl::ToUnixNanos(time)); +} + +TEST(PreferencesManager, MultipleSetAndGetString) { + std::string string1_key = "string1_key"; + PreferencesManager pm(kPreferencesFilePath); + pm.SetString(string1_key, "this is first string"); + pm.SetString(string1_key, "this is second string"); + EXPECT_EQ(pm.GetString(string1_key, ""), "this is second string"); +} + +TEST(PreferencesManager, SetAndGetValue) { + std::string value_key = "value_key"; + PreferencesManager pm(kPreferencesFilePath); + json value = {{"key1", "value1"}, {"key2", "value2"}}; + EXPECT_TRUE(pm.Get(value_key, json()).empty()); + pm.Set(value_key, value); + auto result = pm.Get(value_key, json()); + ASSERT_FALSE(result.empty()); + auto val = result["key2"]; + EXPECT_EQ(val.get(), "value2"); +} + +TEST(PreferencesManager, SetAndGetBooleanArray) { + std::string bool_array_key = "bool_array_key"; + auto pm = PreferencesManager(kPreferencesFilePath); + auto default_result = + pm.GetBooleanArray(bool_array_key, absl::Span({true})); + EXPECT_EQ(default_result[0], true); + pm.SetBooleanArray(bool_array_key, + absl::Span({true, false, false, true, true})); + auto result = + pm.GetBooleanArray(bool_array_key, absl::Span({true})); + EXPECT_EQ(result[2], false); + EXPECT_EQ(result[3], true); +} + +TEST(PreferencesManager, SetAndGetIntArray) { + std::string int_array_key = "int_array_key"; + auto pm = PreferencesManager(kPreferencesFilePath); + auto result = pm.GetIntegerArray(int_array_key, std::vector{5, 6}); + EXPECT_EQ(result[1], 6); + pm.SetIntegerArray(int_array_key, std::vector{1, 7, 4, 10, 12}); + result = pm.GetIntegerArray(int_array_key, std::vector{11, 17, 14, 110}); + EXPECT_EQ(result[3], 10); +} + +TEST(PreferencesManager, SetAndGetInt64Array) { + std::string int64_array_key = "int64_array_key"; + auto pm = PreferencesManager(kPreferencesFilePath); + auto result = pm.GetInt64Array(int64_array_key, std::vector{99}); + EXPECT_EQ(result[0], 99); + pm.SetInt64Array(int64_array_key, std::vector{16, 7, 64, 100, 12}); + result = pm.GetInt64Array(int64_array_key, std::vector{1, 5, 6, 12}); + EXPECT_EQ(result[3], 100); + EXPECT_EQ(result[4], 12); +} + +TEST(PreferencesManager, SetAndGetStringArray) { + std::string string_array_key = "string_array_key"; + auto pm = PreferencesManager(kPreferencesFilePath); + auto result = pm.GetStringArray(string_array_key, + std::vector{"value", "morning"}); + EXPECT_EQ(result[1], "morning"); + pm.SetStringArray( + string_array_key, + std::vector{"one", "two", "three", "four", "five"}); + result = pm.GetStringArray(string_array_key, + std::vector{"good", "morning"}); + EXPECT_EQ(result[3], "four"); +} + +TEST(PreferencesManager, RemoveKey) { + std::string string_key = "string_key"; + auto pm = PreferencesManager(kPreferencesFilePath); + pm.SetString(string_key, "remove key"); + pm.Remove(string_key); + auto result = pm.GetString(string_key, "default key"); + EXPECT_EQ(result, "default key"); +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/preferences_repository.cc b/internal/platform/implementation/linux/preferences_repository.cc new file mode 100644 index 00000000..5732414c --- /dev/null +++ b/internal/platform/implementation/linux/preferences_repository.cc @@ -0,0 +1,152 @@ +// Copyright 2023 Google LLC +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include // NOLINT(build/c++17) +#include +#include + +#include "internal/platform/implementation/linux/preferences_repository.h" +#include "internal/platform/logging.h" +#include "nlohmann/json.hpp" +#include "nlohmann/json_fwd.hpp" + +namespace nearby { +namespace linux { +namespace { +using json = ::nlohmann::json; + +constexpr char kPreferencesFileName[] = "preferences.json"; +constexpr char kPreferencesBackupFileName[] = "preferences_bak.json"; + +} // namespace + +json PreferencesRepository::LoadPreferences() { + absl::MutexLock lock(&mutex_); + std::optional preferences = AttemptLoad(); + if (preferences.has_value()) { + // The top level root should be an object, if it's not then something went + // wrong or the file was corrupted. + if (!preferences.value().is_object()) { + NEARBY_LOGS(ERROR) << "Preferences loaded was not a valid object: " + << preferences.value().dump(4); + + return json::object(); + } + + return preferences.value(); + } + + NEARBY_LOGS(ERROR) << "Could not load preferences file, trying backup."; + + // In the future we should switch to using a transaction log or another + // stable method which doesn't pose a risk of losing settings + preferences = RestoreFromBackup(); + if (preferences.has_value()) { + NEARBY_LOGS(ERROR) << "Successfully recovered from backup."; + return preferences.value(); + } + + NEARBY_LOGS(ERROR) << "Failed to load preferences file from back up."; + + return json::object(); +} + +bool PreferencesRepository::SavePreferences(json preferences) { + absl::MutexLock lock(&mutex_); + try { + std::filesystem::path path = path_; + if (!std::filesystem::exists(path) && + !std::filesystem::create_directories(path)) { + NEARBY_LOGS(ERROR) << "Failed to create preferences path."; + return false; + } + + std::filesystem::path full_name = path / kPreferencesFileName; + std::filesystem::path full_name_backup = path / kPreferencesBackupFileName; + + // Create a backup without moving the bytes on disk + if (std::filesystem::exists(full_name)) { + NEARBY_LOGS(INFO) << "Making backup of preferences file."; + std::filesystem::rename(full_name, full_name_backup); + } + + std::ofstream preferences_file(full_name.c_str()); + preferences_file << preferences; + preferences_file.close(); + + // Make sure the file wasn't saved in a corrupted state + if (!AttemptLoad().has_value()) { + NEARBY_LOGS(ERROR) << "Preferences saved to disk in corrupted state. " + "Restoring from backup."; + + if (!RestoreFromBackup().has_value()) { + NEARBY_LOGS(ERROR) << "Failed to restore preferences file."; + return false; + } + } + } catch (const std::exception& e) { + NEARBY_LOGS(ERROR) << "Failed to save preferences file: " << e.what(); + return false; + } + + return true; +} + +std::optional PreferencesRepository::AttemptLoad() { + std::filesystem::path path = path_; + std::filesystem::path full_name = path / kPreferencesFileName; + if (!std::filesystem::exists(path) || !std::filesystem::exists(full_name)) { + return std::nullopt; + } + + try { + std::ifstream preferences_file(full_name.c_str()); + if (!preferences_file.good()) { + return std::nullopt; + } + + json preferences = json::parse(preferences_file, nullptr, false); + preferences_file.close(); + + if (preferences.is_discarded()) { + NEARBY_LOGS(ERROR) << "Preferences file corrupted."; + return std::nullopt; + } + + return preferences; + } catch (const std::exception& e) { + NEARBY_LOGS(ERROR) << "Exception while loading preferences: " << e.what(); + return std::nullopt; + } +} + +std::optional PreferencesRepository::RestoreFromBackup() { + std::filesystem::path path = path_; + std::filesystem::path full_name = path / kPreferencesFileName; + std::filesystem::path full_name_backup = path / kPreferencesBackupFileName; + + if (!std::filesystem::exists(full_name_backup)) { + NEARBY_LOGS(WARNING) + << "Backup requested but no backup preferences file found."; + return std::nullopt; + } + + std::filesystem::rename(full_name_backup, full_name); + + NEARBY_LOGS(INFO) << "Attempting load from backup preferences."; + return AttemptLoad(); +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/preferences_repository.h b/internal/platform/implementation/linux/preferences_repository.h new file mode 100644 index 00000000..b0b23345 --- /dev/null +++ b/internal/platform/implementation/linux/preferences_repository.h @@ -0,0 +1,48 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPLEMENTATION_LINUX_PREFERENCES_REPOSITORY_H_ +#define PLATFORM_IMPLEMENTATION_LINUX_PREFERENCES_REPOSITORY_H_ + +#include +#include + +#include "absl/base/thread_annotations.h" +#include "absl/strings/string_view.h" +#include "absl/synchronization/mutex.h" +#include "nlohmann/json.hpp" +#include "nlohmann/json_fwd.hpp" + +namespace nearby { +namespace linux { + +class PreferencesRepository { + public: + explicit PreferencesRepository(absl::string_view path) : path_(path) {} + + nlohmann::json LoadPreferences() ABSL_LOCKS_EXCLUDED(&mutex_); + bool SavePreferences(nlohmann::json preferences) ABSL_LOCKS_EXCLUDED(&mutex_); + + std::optional AttemptLoad(); + std::optional RestoreFromBackup(); + + private: + absl::Mutex mutex_; + const std::string path_; +}; + +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPLEMENTATION_LINUX_PREFERENCES_REPOSITORY_H_ diff --git a/internal/platform/implementation/linux/preferences_repository_test.cc b/internal/platform/implementation/linux/preferences_repository_test.cc new file mode 100644 index 00000000..143d6a58 --- /dev/null +++ b/internal/platform/implementation/linux/preferences_repository_test.cc @@ -0,0 +1,160 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include // NOLINT(build/c++17) +#include +#include + +#include "gtest/gtest.h" +#include "internal/platform/implementation/device_info.h" +#include "internal/platform/implementation/platform.h" +#include "nlohmann/json.hpp" +#include "nlohmann/json_fwd.hpp" +#include "internal/platform/implementation/linux/preferences_repository.h" + +namespace nearby { +namespace linux { +namespace { + +using json = ::nlohmann::json; + +constexpr char kPreferencesFileName[] = "preferences.json"; +constexpr char kPreferencesBackupFileName[] = "preferences_bak.json"; +constexpr char kPreferencesPath[] = "Google/Nearby/Sharing"; + +TEST(PreferencesRepository, LoadWithBadPath) { + PreferencesRepository preferences_repository{"c:\\users\\a\\b\\c\\d\\e\\f"}; + json result = preferences_repository.LoadPreferences(); + EXPECT_TRUE(result.empty()); +} + +TEST(PreferencesRepository, RecoverFromBadPreferences) { + std::optional app_data_path = + api::ImplementationPlatform::CreateDeviceInfo()->GetLocalAppDataPath(); + ASSERT_TRUE(app_data_path.has_value()); + std::filesystem::path full_path = *app_data_path / kPreferencesPath; + std::filesystem::path full_name = full_path / kPreferencesFileName; + + if (std::filesystem::exists(full_name)) { + std::filesystem::remove(full_name); + } + + std::ofstream pref_file(full_name.c_str()); + pref_file << "\"Bad top level object\""; + pref_file.close(); + + PreferencesRepository preferences_repository{full_path.string()}; + EXPECT_EQ(preferences_repository.LoadPreferences(), json::object()); +} + +TEST(PreferencesRepository, SaveAndLoadPreferences) { + std::optional app_data_path = + api::ImplementationPlatform::CreateDeviceInfo()->GetLocalAppDataPath(); + ASSERT_TRUE(app_data_path.has_value()); + std::filesystem::path full_path = *app_data_path / kPreferencesPath; + std::filesystem::path full_name = full_path / kPreferencesFileName; + + if (std::filesystem::exists(full_name)) { + std::filesystem::remove(full_name); + } + + PreferencesRepository preferences_repository{full_path.string()}; + json data; + data["key1"] = "value1"; + data["key2"] = "value2"; + EXPECT_TRUE(preferences_repository.SavePreferences(data)); + json result = preferences_repository.LoadPreferences(); + EXPECT_EQ(result.size(), 2); + EXPECT_EQ(result["key1"], "value1"); + EXPECT_EQ(result["key2"], "value2"); + std::filesystem::remove(full_name); +} + +TEST(PreferencesRepository, LoadFromBackup) { + std::optional app_data_path = + api::ImplementationPlatform::CreateDeviceInfo()->GetLocalAppDataPath(); + ASSERT_TRUE(app_data_path.has_value()); + std::filesystem::path full_path = *app_data_path / kPreferencesPath; + std::filesystem::path full_name = full_path / kPreferencesFileName; + std::filesystem::path full_name_backup = + full_path / kPreferencesBackupFileName; + + if (std::filesystem::exists(full_name)) { + std::filesystem::remove(full_name); + } + + if (std::filesystem::exists(full_name_backup)) { + std::filesystem::remove(full_name_backup); + } + + PreferencesRepository preferences_repository{full_path.string()}; + json data; + data["key1"] = "value1"; + data["key2"] = "value2"; + + std::ofstream backup_file(full_name_backup.c_str()); + backup_file << data; + backup_file.close(); + + std::optional result; + result = preferences_repository.AttemptLoad(); + EXPECT_FALSE(result.has_value()); + result = preferences_repository.RestoreFromBackup(); + EXPECT_TRUE(result.has_value()); + EXPECT_EQ(result.value()["key1"], "value1"); + EXPECT_EQ(result.value()["key2"], "value2"); + std::filesystem::remove(full_name); + EXPECT_FALSE(std::filesystem::exists(full_name_backup)); +} + +TEST(PreferencesRepository, RecoverFromCorruption) { + std::optional app_data_path = + api::ImplementationPlatform::CreateDeviceInfo()->GetLocalAppDataPath(); + ASSERT_TRUE(app_data_path.has_value()); + std::filesystem::path full_path = *app_data_path / kPreferencesPath; + std::filesystem::path full_name = full_path / kPreferencesFileName; + std::filesystem::path full_name_backup = + full_path / kPreferencesBackupFileName; + + if (std::filesystem::exists(full_name)) { + std::filesystem::remove(full_name); + } + + if (std::filesystem::exists(full_name_backup)) { + std::filesystem::remove(full_name_backup); + } + + PreferencesRepository preferences_repository{full_path.string()}; + json data; + data["key1"] = "value1"; + data["key2"] = "value2"; + + std::ofstream preferences_file(full_name_backup.c_str()); + preferences_file << data; + preferences_file.close(); + + std::ofstream backup_file(full_name.c_str()); + backup_file << "[BAD JSON FILE]"; + backup_file.close(); + + std::optional result = preferences_repository.LoadPreferences(); + EXPECT_EQ(result.value()["key1"], "value1"); + EXPECT_EQ(result.value()["key2"], "value2"); + std::filesystem::remove(full_name); + EXPECT_FALSE(std::filesystem::exists(full_name_backup)); +} + +} // namespace +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/scheduled_executor.cc b/internal/platform/implementation/linux/scheduled_executor.cc new file mode 100644 index 00000000..57d153a3 --- /dev/null +++ b/internal/platform/implementation/linux/scheduled_executor.cc @@ -0,0 +1,84 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "internal/platform/implementation/linux/scheduled_executor.h" + +#include +#include +#include + +#include "absl/time/time.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { + +ScheduledExecutor::ScheduledExecutor() + : executor_(std::make_unique()), + shut_down_(false) {} + +// Cancelable is kept both in the executor context, and in the caller context. +// We want Cancelable to live until both caller and executor are done with it. +// Exclusive ownership model does not work for this case; +// using std:shared_ptr<> instead of std::unique_ptr<>. +std::shared_ptr ScheduledExecutor::Schedule( + Runnable &&runnable, absl::Duration duration) { + if (shut_down_) { + NEARBY_LOGS(ERROR) << __func__ + << ": Attempt to Schedule on a shut down executor."; + + return nullptr; + } + + // Cleans completed tasks + scheduled_tasks_.erase( + std::remove_if( + scheduled_tasks_.begin(), scheduled_tasks_.end(), + [](std::shared_ptr &task) { return task->IsDone(); }), + scheduled_tasks_.end()); + + std::shared_ptr task = + std::make_shared(std::move(runnable), duration); + + scheduled_tasks_.push_back(task); + executor_->Execute([task]() { task->Start(); }); + return task; +} + +void ScheduledExecutor::Execute(Runnable &&runnable) { + if (shut_down_) { + NEARBY_LOGS(ERROR) << __func__ + << ": Attempt to Execute on a shut down executor."; + return; + } + + executor_->Execute(std::move(runnable)); +} + +void ScheduledExecutor::Shutdown() { + if (!shut_down_) { + shut_down_ = true; + for (auto &task : scheduled_tasks_) { + task->Cancel(); + } + + scheduled_tasks_.clear(); + executor_->Shutdown(); + return; + } + NEARBY_LOGS(ERROR) << __func__ + << ": Attempt to Shutdown on a shut down executor."; +} +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/scheduled_executor.h b/internal/platform/implementation/linux/scheduled_executor.h new file mode 100644 index 00000000..90257c8c --- /dev/null +++ b/internal/platform/implementation/linux/scheduled_executor.h @@ -0,0 +1,100 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_SCHEDULED_EXECUTOR_H_ +#define PLATFORM_IMPL_LINUX_SCHEDULED_EXECUTOR_H_ + +#include +#include +#include + +#include "absl/synchronization/notification.h" +#include "absl/time/time.h" +#include "internal/platform/implementation/cancelable.h" +#include "internal/platform/implementation/linux/executor.h" +#include "internal/platform/implementation/scheduled_executor.h" + +namespace nearby { +namespace linux { + +#define TIMER_NAME_BUFFER_SIZE 64 + +// An Executor that can schedule commands to run after a given delay, or to +// execute periodically. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ScheduledExecutorService.html +class ScheduledExecutor : public api::ScheduledExecutor { + public: + ScheduledExecutor(); + + ~ScheduledExecutor() override = default; + + // Cancelable is kept both in the executor context, and in the caller context. + // We want Cancelable to live until both caller and executor are done with it. + // Exclusive ownership model does not work for this case; + // using std:shared_ptr<> instead if std::unique_ptr<>. + std::shared_ptr Schedule(Runnable&& runnable, + absl::Duration duration) override; + + // Executes the runnable task immedately. + void Execute(Runnable&& runnable) override; + + // Shutdowns the executor, all scheduled task will be cancelled. + void Shutdown() override; + + private: + class ScheduledTask : public api::Cancelable { + public: + explicit ScheduledTask(Runnable&& task, absl::Duration duration) + : task_(std::move(task)), duration_(duration) {} + + bool Cancel() override { + if (is_executed_ || is_cancelled_) { + return false; + } + + is_cancelled_ = true; + notification_.Notify(); + return true; + }; + + void Start() { + if (is_executed_ || + notification_.WaitForNotificationWithTimeout(duration_)) { + return; + } + + is_executed_ = true; + task_(); + } + + bool IsDone() const { return is_cancelled_ || is_executed_; } + + private: + Runnable task_; + absl::Duration duration_; + absl::Notification notification_; + bool is_cancelled_ = false; + bool is_executed_ = false; + }; + + std::unique_ptr executor_ = nullptr; + std::vector> scheduled_tasks_; + std::atomic_bool shut_down_ = false; +}; + +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPL_LINUX_SCHEDULED_EXECUTOR_H_ diff --git a/internal/platform/implementation/linux/scheduled_executor_test.cc b/internal/platform/implementation/linux/scheduled_executor_test.cc new file mode 100644 index 00000000..1b0a5722 --- /dev/null +++ b/internal/platform/implementation/linux/scheduled_executor_test.cc @@ -0,0 +1,178 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "absl/synchronization/notification.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" +#include "gtest/gtest.h" +#include "internal/platform/implementation/linux/test_data.h" +#include "internal/platform/implementation/linux/scheduled_executor.h" + +namespace nearby { +namespace linux { +namespace { + +TEST(ScheduledExecutorTests, ExecuteSucceeds) { + absl::Notification notification; + // Arrange + std::string expected(RUNNABLE_0_TEXT.c_str()); + + auto submittableExecutor = std::make_unique(); + std::string output = std::string(); + // Container to note threads that ran + std::unique_ptr> threadIds = + std::make_unique>(); + + threadIds->push_back(std::this_thread::get_id()); + + // Act + submittableExecutor->Execute([&]() { + threadIds->push_back(std::this_thread::get_id()); + output.append(RUNNABLE_0_TEXT.c_str()); + notification.Notify(); + }); + + ASSERT_TRUE( + notification.WaitForNotificationWithTimeout(absl::Milliseconds(200))); + submittableExecutor->Shutdown(); + + // Assert + // We should've run 1 time on the main thread, and 1 times on the + // workerThread + ASSERT_EQ(threadIds->size(), 2); + // We should still be on the main thread + ASSERT_EQ(std::this_thread::get_id(), threadIds->at(0)); + // We should've run all runnables on the worker thread + ASSERT_EQ(output, expected); +} + +TEST(ScheduledExecutorTests, ScheduleSucceeds) { + absl::Notification notification; + // Arrange + std::string expected(RUNNABLE_0_TEXT.c_str()); + + auto submittableExecutor = std::make_unique(); + std::string output = std::string(); + // Container to note threads that ran + std::unique_ptr> threadIds = + std::make_unique>(); + + threadIds->push_back(std::this_thread::get_id()); + + std::chrono::system_clock::time_point timeNow = + std::chrono::system_clock::now(); + std::chrono::system_clock::time_point timeExecuted; + + // Act + submittableExecutor->Schedule( + [&]() { + timeExecuted = std::chrono::system_clock::now(); + threadIds->push_back(std::this_thread::get_id()); + output.append(RUNNABLE_0_TEXT.c_str()); + notification.Notify(); + }, + absl::Milliseconds(50)); + + ASSERT_TRUE( + notification.WaitForNotificationWithTimeout(absl::Milliseconds(200))); + submittableExecutor->Shutdown(); + + ASSERT_EQ(threadIds->size(), 2); + // We should still be on the main thread + ASSERT_EQ(std::this_thread::get_id(), threadIds->at(0)); + // We should've run all runnables on the worker thread + ASSERT_EQ(output, expected); +} + +TEST(ScheduledExecutorTests, CancelSucceeds) { + absl::Notification notification; + // Arrange + std::string expected(""); + + auto submittableExecutor = std::make_unique(); + std::string output = std::string(); + // Container to note threads that ran + std::unique_ptr> threadIds = + std::make_unique>(); + + threadIds->push_back(std::this_thread::get_id()); + + // Act + auto cancelable = submittableExecutor->Schedule( + [&]() { + threadIds->push_back(std::this_thread::get_id()); + output.append(RUNNABLE_0_TEXT.c_str()); + notification.Notify(); + }, + absl::Milliseconds(1000)); + + auto actual = cancelable->Cancel(); + + EXPECT_FALSE( + notification.WaitForNotificationWithTimeout(absl::Milliseconds(2000))); + submittableExecutor->Shutdown(); + + // Assert + ASSERT_TRUE(actual); + ASSERT_EQ(threadIds->size(), 1); + // We should still be on the main thread + ASSERT_EQ(std::this_thread::get_id(), threadIds->at(0)); + // We should've run all runnables on the worker thread + ASSERT_EQ(output, expected); +} + +TEST(ScheduledExecutorTests, CancelAfterStartedFails) { + absl::Notification notification; + // Arrange + std::string expected(RUNNABLE_0_TEXT.c_str()); + + auto submittableExecutor = std::make_unique(); + std::string output = std::string(); + // Container to note threads that ran + std::unique_ptr> threadIds = + std::make_unique>(); + + threadIds->push_back(std::this_thread::get_id()); + + // Act + auto cancelable = submittableExecutor->Schedule( + [&]() { + threadIds->push_back(std::this_thread::get_id()); + output.append(RUNNABLE_0_TEXT.c_str()); + notification.Notify(); + }, + absl::Milliseconds(100)); + + absl::SleepFor(absl::Milliseconds(200)); + auto actual = cancelable->Cancel(); + + ASSERT_TRUE( + notification.WaitForNotificationWithTimeout(absl::Milliseconds(2000))); + submittableExecutor->Shutdown(); + + // Assert + ASSERT_FALSE(actual); + ASSERT_EQ(threadIds->size(), 2); + // We should still be on the main thread + ASSERT_EQ(std::this_thread::get_id(), threadIds->at(0)); + // We should've run all runnables on the worker thread + ASSERT_EQ(output, expected); +} + +} // namespace +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/server_sync.h b/internal/platform/implementation/linux/server_sync.h new file mode 100644 index 00000000..813aeefb --- /dev/null +++ b/internal/platform/implementation/linux/server_sync.h @@ -0,0 +1,88 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_SERVER_SYNC_H_ +#define PLATFORM_IMPL_LINUX_SERVER_SYNC_H_ + +#include "internal/platform/implementation/server_sync.h" + +namespace nearby { +namespace linux { + +// Abstraction that represents a Nearby endpoint exchanging data through +// ServerSync Medium. +class ServerSyncDevice : public api::ServerSyncDevice { + public: + // TODO(b/184975123): replace with real implementation. + ~ServerSyncDevice() override = default; + + // TODO(b/184975123): replace with real implementation. + std::string GetName() const override { return "Un-implemented"; } + // TODO(b/184975123): replace with real implementation. + std::string GetGuid() const override { return "Un-implemented"; } + // TODO(b/184975123): replace with real implementation. + std::string GetOwnGuid() const override { return "Un-implemented"; } +}; + +// Container of operations that can be performed over the Chrome Sync medium. +class ServerSyncMedium : public api::ServerSyncMedium { + public: + // TODO(b/184975123): replace with real implementation. + ~ServerSyncMedium() override = default; + + // TODO(b/184975123): replace with real implementation. + bool StartAdvertising(absl::string_view service_id, + absl::string_view endpoint_id, + const ByteArray& endpoint_info) override { + return false; + } + // TODO(b/184975123): replace with real implementation. + void StopAdvertising(absl::string_view service_id) override {} + + class DiscoveredDeviceCallback + : public api::ServerSyncMedium::DiscoveredDeviceCallback { + public: + // TODO(b/184975123): replace with real implementation. + ~DiscoveredDeviceCallback() override = default; + + // Called on a new ServerSyncDevice discovery. + // TODO(b/184975123): replace with real implementation. + void OnDeviceDiscovered(api::ServerSyncDevice* device, + absl::string_view service_id, + absl::string_view endpoint_id, + const ByteArray& endpoint_info) override {} + // Called when ServerSyncDevice is no longer reachable. + // TODO(b/184975123): replace with real implementation. + void OnDeviceLost(api::ServerSyncDevice* device, + absl::string_view service_id) override {} + }; + + // Returns true once the Chrome Sync scan has been initiated. + // TODO(b/184975123): replace with real implementation. + bool StartDiscovery(absl::string_view service_id, + const api::ServerSyncMedium::DiscoveredDeviceCallback& + discovered_device_callback) override { + return false; + } + // Returns true once Chrome Sync scan for service_id is well and truly + // stopped; after this returns, there must be no more invocations of the + // DiscoveredDeviceCallback passed in to startScanning() for service_id. + // TODO(b/184975123): replace with real implementation. + void StopDiscovery(absl::string_view service_id) override {} +}; + +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPL_LINUX_SERVER_SYNC_H_ diff --git a/internal/platform/implementation/linux/stream.cc b/internal/platform/implementation/linux/stream.cc new file mode 100644 index 00000000..edfb1da2 --- /dev/null +++ b/internal/platform/implementation/linux/stream.cc @@ -0,0 +1,82 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include + +#include "internal/platform/byte_array.h" +#include "internal/platform/exception.h" +#include "internal/platform/implementation/linux/stream.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { + +ExceptionOr InputStream::Read(std::int64_t size) { + if (!fd_.isValid()) return {Exception::kIo}; + + std::string buffer; + buffer.resize(size); + ssize_t ret = recv(fd_.get(), buffer.data(), buffer.size(), MSG_WAITALL); + if (ret == 0) { + return ExceptionOr(ByteArray()); + } + if (ret < 0) { + NEARBY_LOGS(ERROR) << __func__ + << ": error reading from fd: " << std::strerror(errno); + return {Exception::kIo}; + } + buffer.resize(ret); + + return ExceptionOr(ByteArray(std::move(buffer))); +} + +Exception InputStream::Close() { + if (!fd_.isValid()) return Exception{Exception::kIo}; + fd_.reset(); + return {}; +} + +Exception OutputStream::Write(const ByteArray &data) { + if (!fd_.isValid()) return Exception{Exception::kIo}; + + size_t written = 0; + while (written < data.size()) { + ssize_t ret = write(fd_.get(), data.data(), data.size()); + if (ret < 0) { + NEARBY_LOGS(ERROR) << __func__ + << ": error writing to fd: " << std::strerror(errno); + return Exception{Exception::kIo}; + } + written += ret; + } + return Exception{Exception::kSuccess}; +} + +Exception OutputStream::Flush() { return Exception{Exception::kSuccess}; } + +Exception OutputStream::Close() { + if (!fd_.isValid()) return Exception{Exception::kIo}; + + auto ret = close(fd_.get()) < 0 ? Exception{Exception::kIo} + : Exception{Exception::kSuccess}; + fd_.reset(); + return ret; +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/stream.h b/internal/platform/implementation/linux/stream.h new file mode 100644 index 00000000..8034fa87 --- /dev/null +++ b/internal/platform/implementation/linux/stream.h @@ -0,0 +1,54 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_STREAM_H_ +#define PLATFORM_IMPL_LINUX_STREAM_H_ + +#include + +#include + +#include "internal/platform/input_stream.h" +#include "internal/platform/output_stream.h" + +namespace nearby { +namespace linux { +class InputStream : public nearby::InputStream { + public: + explicit InputStream(sdbus::UnixFd fd) : fd_(std::move(fd)){}; + + ExceptionOr Read(std::int64_t size) override; + + Exception Close() override; + + private: + sdbus::UnixFd fd_; +}; + +class OutputStream : public nearby::OutputStream { + public: + explicit OutputStream(sdbus::UnixFd fd) : fd_(std::move(fd)){}; + + Exception Write(const ByteArray &data) override; + Exception Flush() override; + Exception Close() override; + + private: + sdbus::UnixFd fd_; +}; + +} // namespace linux +} // namespace nearby + +#endif diff --git a/internal/platform/implementation/linux/submittable_executor.cc b/internal/platform/implementation/linux/submittable_executor.cc new file mode 100644 index 00000000..ecf6e634 --- /dev/null +++ b/internal/platform/implementation/linux/submittable_executor.cc @@ -0,0 +1,62 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "internal/platform/implementation/linux/submittable_executor.h" + +#include "internal/platform/implementation/linux/executor.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { + +SubmittableExecutor::SubmittableExecutor(size_t max_concurrancy) + : executor_(std::make_unique(max_concurrancy)), + shut_down_(false) {} + +bool SubmittableExecutor::DoSubmit(Runnable&& wrapped_callable) { + if (!shut_down_) { + executor_->Execute(std::move(wrapped_callable)); + return true; + } + + NEARBY_LOGS(ERROR) << "Error: " << __func__ + << ": Attempt to DoSubmit on a shutdown executor."; + + return false; +} + +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executor.html#execute-java.lang.Runnable- +void SubmittableExecutor::Execute(Runnable&& runnable) { + if (!shut_down_) { + executor_->Execute(std::move(runnable)); + } else { + NEARBY_LOGS(ERROR) << "Error: " << __func__ + << ": Attempt to Execute on a shutdown executor."; + } +} + +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html#shutdown-- +void SubmittableExecutor::Shutdown() { + if (!shut_down_) { + executor_->Shutdown(); + shut_down_ = true; + return; + } + + NEARBY_LOGS(ERROR) << "Error: " << __func__ + << ": Attempt to Shutdown on a shutdown executor."; +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/submittable_executor.h b/internal/platform/implementation/linux/submittable_executor.h new file mode 100644 index 00000000..85d1a3ba --- /dev/null +++ b/internal/platform/implementation/linux/submittable_executor.h @@ -0,0 +1,52 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_SUBMITTABLE_EXECUTOR_H_ +#define PLATFORM_IMPL_LINUX_SUBMITTABLE_EXECUTOR_H_ + +#include "internal/platform/implementation/linux/executor.h" +#include "internal/platform/implementation/submittable_executor.h" + +namespace nearby { +namespace linux { + +// Main interface to be used by platform as a base class for +// - MultiThreadExecutorWrapper +// - SingleThreadExecutorWrapper +// Platform must override bool submit(absl::AnyInvocable) method. +class SubmittableExecutor : public api::SubmittableExecutor { + public: + SubmittableExecutor(size_t maxConcurrancy = 1); + ~SubmittableExecutor() override = default; + + // Submit a callable (with no delay). + // Returns true, if callable was submitted, false otherwise. + // Callable is not submitted if shutdown is in progress. + bool DoSubmit(Runnable&& wrapped_callable) override; + + // https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executor.html#execute-java.lang.Runnable- + void Execute(Runnable&& runnable) override; + + // https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html#shutdown-- + void Shutdown() override; + + private: + std::unique_ptr executor_; + std::atomic_bool shut_down_ = false; +}; + +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPL_LINUX_SUBMITTABLE_EXECUTOR_H_ diff --git a/internal/platform/implementation/linux/submittable_executor_test.cc b/internal/platform/implementation/linux/submittable_executor_test.cc new file mode 100644 index 00000000..c6162607 --- /dev/null +++ b/internal/platform/implementation/linux/submittable_executor_test.cc @@ -0,0 +1,250 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "internal/platform/implementation/linux/submittable_executor.h" + +#include +#include + +#include "absl/synchronization/blocking_counter.h" +#include "absl/synchronization/notification.h" +#include "absl/time/time.h" +#include "gtest/gtest.h" +#include "internal/platform/implementation/linux/test_data.h" + +namespace nearby { +namespace linux { +namespace { + +constexpr absl::Duration kWaitTimeout = absl::Milliseconds(200); + +TEST(SubmittableExecutorTests, SingleThreadedExecuteSucceeds) { + absl::Notification notification; + // Arrange + std::string expected(RUNNABLE_0_TEXT.c_str()); + + auto submittableExecutor = std::make_unique(); + std::string output = std::string(); + // Container to note threads that ran + auto threadIds = std::make_unique>(); + + threadIds->push_back(std::this_thread::get_id()); + + // Act + submittableExecutor->Execute([&]() { + threadIds->push_back(std::this_thread::get_id()); + output.append(RUNNABLE_0_TEXT.c_str()); + notification.Notify(); + }); + + ASSERT_TRUE(notification.WaitForNotificationWithTimeout(kWaitTimeout)); + submittableExecutor->Shutdown(); + + // Assert + // We should've run 1 time on the main thread, and 1 times on the + // workerThread + ASSERT_EQ(threadIds->size(), 2); + // We should still be on the main thread + ASSERT_EQ(std::this_thread::get_id(), threadIds->at(0)); + // We should've run all runnables on the worker thread + ASSERT_EQ(output, expected); +} + +TEST(SubmittableExecutorTests, SingleThreadedExecuteAfterShutdownFails) { + // Arrange + std::string expected(""); + + auto submittableExecutor = std::make_unique(); + std::string output = std::string(); + // Container to note threads that ran + auto threadIds = std::make_unique>(); + + threadIds->push_back(std::this_thread::get_id()); + + submittableExecutor->Shutdown(); + + // Act + submittableExecutor->Execute([&output, &threadIds]() { + threadIds->push_back(std::this_thread::get_id()); + output.append(RUNNABLE_0_TEXT.c_str()); + }); + + // Assert + // We should've run 1 time on the main thread, and 0 times on the + // workerThread + ASSERT_EQ(threadIds->size(), 1); + // We should still be on the main thread + ASSERT_EQ(std::this_thread::get_id(), threadIds->at(0)); + // We should've run all runnables on the worker thread + ASSERT_EQ(output, expected); +} + +TEST(SubmittableExecutorTests, SingleThreadedDoSubmitSucceeds) { + absl::Notification notification; + // Arrange + std::string expected(RUNNABLE_0_TEXT.c_str()); + + auto submittableExecutor = std::make_unique(); + std::string output = std::string(); + // Container to note threads that ran + auto threadIds = std::make_unique>(); + + threadIds->push_back(std::this_thread::get_id()); + + // Act + auto result = submittableExecutor->DoSubmit([&]() { + threadIds->push_back(std::this_thread::get_id()); + output.append(RUNNABLE_0_TEXT.c_str()); + notification.Notify(); + }); + + ASSERT_TRUE(notification.WaitForNotificationWithTimeout(kWaitTimeout)); + submittableExecutor->Shutdown(); + + // Assert + // We should've said we were going to run this one + ASSERT_TRUE(result); + // We should've run 1 time on the main thread, and 1 times on the + // workerThread + ASSERT_EQ(threadIds->size(), 2); + // We should still be on the main thread + ASSERT_EQ(std::this_thread::get_id(), threadIds->at(0)); + // We should've run all runnables on the worker thread + ASSERT_EQ(output, expected); +} + +TEST(SubmittableExecutorTests, + SingleThreadedDoSubmitAfterShutdownReturnsFalse) { + // Arrange + std::string expected(""); + + auto submittableExecutor = std::make_unique(); + std::unique_ptr output = std::make_unique(); + // Container to note threads that ran + auto threadIds = std::make_unique>(); + + threadIds->push_back(std::this_thread::get_id()); + + submittableExecutor->Shutdown(); + + // Act + auto result = submittableExecutor->DoSubmit([&output, &threadIds]() { + threadIds->push_back(std::this_thread::get_id()); + output->append(RUNNABLE_0_TEXT.c_str()); + }); + + // Assert + // We should've said we were going to run this one + ASSERT_FALSE(result); + // We should've run 1 time on the main thread, and 1 times on the + // workerThread + ASSERT_EQ(threadIds->size(), 1); + // We should still be on the main thread + ASSERT_EQ(std::this_thread::get_id(), threadIds->at(0)); + // We should've run all runnables on the worker thread + ASSERT_EQ(*output.get(), expected); +} + +TEST(SubmittableExecutorTests, SingleThreadedExecuteMultipleTasksSucceeds) { + absl::BlockingCounter blocking_counter(5); + + // Arrange + std::string expected(RUNNABLE_ALL_TEXT.c_str()); + + auto submittableExecutor = std::make_unique(); + std::unique_ptr output = std::make_unique(); + // Container to note threads that ran + auto threadIds = std::make_unique>(); + + threadIds->push_back(std::this_thread::get_id()); + + // Act + for (int index = 0; index < 5; index++) { + submittableExecutor->Execute([&, index]() { + threadIds->push_back(std::this_thread::get_id()); + char buffer[128]; + snprintf(buffer, sizeof(buffer), "%s%d, ", RUNNABLE_TEXT.c_str(), index); + output->append(std::string(buffer)); + blocking_counter.DecrementCount(); + }); + } + + blocking_counter.Wait(); + submittableExecutor->Shutdown(); + + // Assert + // We should've run 1 time on the main thread, and 5 times on the + // workerThread + ASSERT_EQ(threadIds->size(), 6); + // We should still be on the main thread + ASSERT_EQ(std::this_thread::get_id(), threadIds->at(0)); + // We should've run all runnables on the worker thread + auto workerThreadId = threadIds->at(1); + for (int index = 1; index < threadIds->size(); index++) { + ASSERT_EQ(threadIds->at(index), workerThreadId); + } + + // We should of run them in the order submitted + ASSERT_EQ(*output.get(), expected); +} + +TEST(SubmittableExecutorTests, SingleThreadedDoSubmitMultipleTasksSucceeds) { + absl::BlockingCounter blocking_counter(5); + + // Arrange + std::string expected(RUNNABLE_ALL_TEXT.c_str()); + + auto submittableExecutor = std::make_unique(); + std::unique_ptr output = std::make_unique(); + // Container to note threads that ran + auto threadIds = std::make_unique>(); + + threadIds->push_back(std::this_thread::get_id()); + + // Act + bool result = true; + for (int index = 0; index < 5; index++) { + result &= submittableExecutor->DoSubmit([&, index]() { + threadIds->push_back(std::this_thread::get_id()); + char buffer[128]; + snprintf(buffer, sizeof(buffer), "%s%d, ", RUNNABLE_TEXT.c_str(), index); + output->append(std::string(buffer)); + blocking_counter.DecrementCount(); + }); + } + + blocking_counter.Wait(); + submittableExecutor->Shutdown(); + + // Assert + // All of these should have submitted + ASSERT_TRUE(result); + // We should've run 1 time on the main thread, and 5 times on the + // workerThread + ASSERT_EQ(threadIds->size(), 6); + // We should still be on the main thread + ASSERT_EQ(std::this_thread::get_id(), threadIds->at(0)); + // We should've run all runnables on the worker thread + auto workerThreadId = threadIds->at(1); + for (int index = 1; index < threadIds->size(); index++) { + ASSERT_EQ(threadIds->at(index), workerThreadId); + } + + // We should of run them in the order submitted + ASSERT_EQ(*output.get(), expected); +} + +} // namespace +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/system_clock.cc b/internal/platform/implementation/linux/system_clock.cc new file mode 100644 index 00000000..f2cc2793 --- /dev/null +++ b/internal/platform/implementation/linux/system_clock.cc @@ -0,0 +1,41 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_SYSTEM_CLOCK_H_ +#define PLATFORM_IMPL_LINUX_SYSTEM_CLOCK_H_ + +#include "internal/platform/implementation/system_clock.h" + +namespace nearby { + +// Initialize global system state. +void SystemClock::Init() {} + +// Returns current absolute time. It is guaranteed to be monotonic. +absl::Time SystemClock::ElapsedRealtime() { + return absl::FromUnixNanos( + std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()) + .count()); +} + +// Pauses current thread for the specified duration. +Exception SystemClock::Sleep(absl::Duration duration) { + absl::SleepFor(duration); + return {Exception::kSuccess}; +} + +} // namespace nearby + +#endif // PLATFORM_IMPL_LINUX_SYSTEM_CLOCK_H_ diff --git a/internal/platform/implementation/linux/tcp_server_socket.h b/internal/platform/implementation/linux/tcp_server_socket.h new file mode 100644 index 00000000..764360c9 --- /dev/null +++ b/internal/platform/implementation/linux/tcp_server_socket.h @@ -0,0 +1,173 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_TCP_SERVER_SOCKET_H_ +#define PLATFORM_IMPL_LINUX_TCP_SERVER_SOCKET_H_ + +#include +#include +#include +#include + +#include + +#include "internal/platform/exception.h" +#include "internal/platform/implementation/linux/stream.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { +class TCPSocket { + public: + explicit TCPSocket(const sdbus::UnixFd& fd) + : closed_(false), output_stream_(fd), input_stream_(fd) {} + + static std::optional Connect(const std::string& ip_address, + int port) { + int sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock < 0) { + NEARBY_LOGS(ERROR) << __func__ + << ": Error opening socket: " << std::strerror(errno); + return std::nullopt; + } + + NEARBY_LOGS(VERBOSE) << __func__ << ": Connecting to " << ip_address << ":" + << port; + struct sockaddr_in addr; + addr.sin_addr.s_addr = inet_addr(ip_address.c_str()); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + + auto ret = + connect(sock, reinterpret_cast(&addr), sizeof(addr)); + if (ret < 0) { + NEARBY_LOGS(ERROR) << __func__ << ": Error connecting to socket: " + << std::strerror(errno); + return std::nullopt; + } + + return TCPSocket(sdbus::UnixFd(sock)); + } + + InputStream& GetInputStream() { return input_stream_; } + OutputStream& GetOutputStream() { return output_stream_; } + + Exception Close() { + if (closed_) return {Exception::kFailed}; + + closed_ = true; + input_stream_.Close(); + output_stream_.Close(); + + return {Exception::kSuccess}; + }; + + private: + bool closed_; + + OutputStream output_stream_; + InputStream input_stream_; +}; + +class TCPServerSocket { + public: + explicit TCPServerSocket(int fd) : fd_(fd) {} + + static std::optional Listen( + std::optional> ip_address, + int port) { + auto sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock < 0) { + NEARBY_LOGS(ERROR) << __func__ + << ": Error opening socket: " << std::strerror(errno); + return std::nullopt; + } + + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + + if (ip_address.has_value()) + addr.sin_addr.s_addr = inet_addr(ip_address->get().c_str()); + else + addr.sin_addr.s_addr = htonl(INADDR_ANY); + + auto ret = + bind(sock, reinterpret_cast(&addr), sizeof(addr)); + if (ret < 0) { + NEARBY_LOGS(ERROR) << __func__ << ": Error binding to socket: " + << std::strerror(errno); + return std::nullopt; + } + + ret = listen(sock, 0); + if (ret < 0) { + NEARBY_LOGS(ERROR) << __func__ << ": Error listening on socket: " + << std::strerror(errno); + return std::nullopt; + } + + return TCPServerSocket(sock); + } + std::optional Accept() { + struct sockaddr_in addr; + socklen_t len = sizeof(addr); + + auto conn = + accept(fd_.get(), reinterpret_cast(&addr), &len); + if (conn < 0) { + NEARBY_LOGS(ERROR) << __func__ + << ": Error accepting incoming connections on socket " + << fd_.get() << ": " << std::strerror(errno); + return std::nullopt; + } + + return TCPSocket(sdbus::UnixFd(conn)); + }; + + Exception Close() { + int fd = fd_.release(); + shutdown(fd, SHUT_RDWR); + auto ret = close(fd); + if (ret < 0) { + NEARBY_LOGS(ERROR) << __func__ << ": Error closing socket " << fd << ": " + << std::strerror(errno); + return {Exception::kFailed}; + } + + return {Exception::kSuccess}; + }; + + int GetPort() const { + struct sockaddr_in sin; + socklen_t len = sizeof(sin); + auto ret = + getsockname(fd_.get(), reinterpret_cast(&sin), &len); + if (ret < 0) { + NEARBY_LOGS(ERROR) << __func__ + << ": Error getting information for socket " + << fd_.get() << ": " << std::strerror(errno); + return 0; + } + + return ntohs(sin.sin_port); + } + + private: + sdbus::UnixFd fd_; +}; +} // namespace linux +} // namespace nearby + +#endif diff --git a/internal/platform/implementation/linux/test_data.h b/internal/platform/implementation/linux/test_data.h new file mode 100644 index 00000000..fe0189ce --- /dev/null +++ b/internal/platform/implementation/linux/test_data.h @@ -0,0 +1,33 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_TEST_DATA_H_ +#define PLATFORM_IMPL_LINUX_TEST_DATA_H_ + +#define INVALID_ARGUMENT_TEXT "max_concurrency" +#define THREADPOOL_MAX_SIZE_TEXT "Thread pool max size exceeded." +#define RUNNABLE_TEXT std::string("runnable ") +#define RUNNABLE_0_TEXT RUNNABLE_TEXT + std::string("0") +#define RUNNABLE_1_TEXT RUNNABLE_TEXT + std::string("1") +#define RUNNABLE_2_TEXT RUNNABLE_TEXT + std::string("2") +#define RUNNABLE_3_TEXT RUNNABLE_TEXT + std::string("3") +#define RUNNABLE_4_TEXT RUNNABLE_TEXT + std::string("4") +#define RUNNABLE_SEPARATOR_TEXT std::string(", ") +#define RUNNABLE_ALL_TEXT \ + (RUNNABLE_0_TEXT + RUNNABLE_SEPARATOR_TEXT + RUNNABLE_1_TEXT + \ + RUNNABLE_SEPARATOR_TEXT + RUNNABLE_2_TEXT + RUNNABLE_SEPARATOR_TEXT + \ + RUNNABLE_3_TEXT + RUNNABLE_SEPARATOR_TEXT + RUNNABLE_4_TEXT + \ + RUNNABLE_SEPARATOR_TEXT) + +#endif // PLATFORM_IMPL_LINUX_TEST_DATA_H_ diff --git a/internal/platform/implementation/linux/test_utils.cc b/internal/platform/implementation/linux/test_utils.cc new file mode 100644 index 00000000..91cc2447 --- /dev/null +++ b/internal/platform/implementation/linux/test_utils.cc @@ -0,0 +1,38 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include "absl/strings/str_format.h" +#include "absl/strings/str_replace.h" +#include "internal/platform/implementation/linux/device_info.h" +#include "internal/platform/implementation/linux/test_utils.h" + +namespace test_utils { +std::wstring StringToWideString(const std::string& s) { + std::wstring_convert> converter; + return converter.from_bytes(s); +} + +std::string GetPayloadPath(nearby::PayloadId payload_id) { + std::filesystem::path path = + nearby::linux::DeviceInfo().GetDownloadPath().value_or( + std::string(getenv("HOME")).append("Downloads")); + + return path.string(); +} + +} // namespace test_utils diff --git a/internal/platform/implementation/linux/test_utils.h b/internal/platform/implementation/linux/test_utils.h new file mode 100644 index 00000000..5abfb07f --- /dev/null +++ b/internal/platform/implementation/linux/test_utils.h @@ -0,0 +1,43 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_TEST_UTILS_H_ +#define PLATFORM_IMPL_LINUX_TEST_UTILS_H_ + +#include + +#include "internal/platform/payload_id.h" + +#define TEST_BUFFER_SIZE 256 +#define TEST_PAYLOAD_ID 64l +#define TEST_STRING \ + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas " \ + "eleifend nisl at magna maximus, id finibus mauris ultrices. Mauris " \ + "interdum efficitur turpis eget auctor. Nullam commodo metus et ante " \ + "bibendum molestie. Donec iaculis ante nec diam rutrum egestas. Proin " \ + "maximus metus luctus rutrum congue. Integer et eros nunc. Etiam purus " \ + "neque, tincidunt eu elementum in, pharetra sit amet magna. Quisque " \ + "consequat aliquam aliquam. Vestibulum ante ipsum primis in faucibus orci " \ + "luctus et ultrices posuere cubilia curae; Maecenas a semper eros, a " \ + "auctor mi. In luctus diam sem, eu pretium nisi porttitor ac. Sed cursus, " \ + "arcu in bibendum feugiat, leo erat finibus massa, ut tincidunt magna nunc " \ + "eu tellus. Cras feugiat ornare vestibulum. Nullam at ipsum vestibulum " \ + "sapien luctus dictum ac vel ligula." + +namespace test_utils { +std::wstring StringToWideString(const std::string& s); +std::string GetPayloadPath(nearby::PayloadId payload_id); +} // namespace test_utils + +#endif // PLATFORM_IMPL_LINUX_TEST_UTILS_H_ diff --git a/internal/platform/implementation/linux/thread_pool.cc b/internal/platform/implementation/linux/thread_pool.cc new file mode 100644 index 00000000..8c37dcd9 --- /dev/null +++ b/internal/platform/implementation/linux/thread_pool.cc @@ -0,0 +1,124 @@ +// Copyright 2021-2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include "absl/synchronization/mutex.h" +#include "internal/platform/implementation/linux/thread_pool.h" +#include "internal/platform/logging.h" +#include "internal/platform/runnable.h" + +namespace nearby { +namespace linux { +ThreadPool::ThreadPool(size_t max_pool_size) + : max_pool_size_(max_pool_size), shut_down_(false) { + threads_.reserve(max_pool_size); + Start(); +} + +ThreadPool::~ThreadPool() { ShutDown(); } + +bool ThreadPool::Start() { + shut_down_.store(false, std::memory_order_acquire); + + auto runner = [this]() { + while (true) { + auto task = NextTask(); + + if (task == nullptr) { + if (shut_down_) { + return; + } + NEARBY_LOGS(WARNING) << __func__ << ": Tried to run a null task."; + continue; + } + task(); + } + }; + + absl::MutexLock l(&threads_mutex_); + if (!threads_.empty()) { + NEARBY_LOGS(ERROR) << __func__ << "thread pool is already active"; + return false; + } + + NEARBY_LOGS(INFO) << __func__ << ": Starting thread pool with " + << max_pool_size_ << " threads"; + + for (size_t i = 0; i < max_pool_size_; i++) { + threads_.emplace_back(runner); + } + + return true; +} + +bool ThreadPool::Run(Runnable &&task) { + if (shut_down_) { + NEARBY_LOGS(ERROR) << __func__ << "thread pool has shut down"; + return false; + } + + { + absl::ReaderMutexLock l(&threads_mutex_); + if (threads_.empty()) { + NEARBY_LOGS(ERROR) << __func__ << ": thread pool is not active"; + return false; + } + } + + absl::MutexLock l(&tasks_mutex_); + tasks_.push(std::move(task)); + return true; +} + +void ThreadPool::ShutDown() { + { + absl::MutexLock l(&tasks_mutex_); + shut_down_.store(true, std::memory_order_acquire); + } + { + absl::ReaderMutexLock l(&threads_mutex_); + for (auto &thread : threads_) { + thread.join(); + } + } + + absl::MutexLock l(&threads_mutex_); + threads_.clear(); +} + +Runnable ThreadPool::NextTask() { + Runnable task; + auto task_available = [this]() { + this->tasks_mutex_.AssertReaderHeld(); + return !this->tasks_.empty() || this->shut_down_; + }; + + { + absl::MutexLock l(&tasks_mutex_, absl::Condition(&task_available)); + if (shut_down_) { + return nullptr; + } + + task = std::move(tasks_.front()); + tasks_.pop(); + } + + return task; +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/thread_pool.h b/internal/platform/implementation/linux/thread_pool.h new file mode 100644 index 00000000..45c0463c --- /dev/null +++ b/internal/platform/implementation/linux/thread_pool.h @@ -0,0 +1,63 @@ +// Copyright 2020-2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_THREAD_POOL_H_ +#define PLATFORM_IMPL_LINUX_THREAD_POOL_H_ + +#include +#include +#include +#include +#include +#include + +#include "absl/base/thread_annotations.h" +#include "absl/synchronization/mutex.h" +#include "internal/platform/runnable.h" + +namespace nearby { +namespace linux { + +class ThreadPool { + public: + ThreadPool(const ThreadPool &) = delete; + ThreadPool(ThreadPool &&) = delete; + ThreadPool &operator=(const ThreadPool &) = delete; + ThreadPool &operator=(ThreadPool &&) = delete; + explicit ThreadPool(size_t max_pool_size); + ~ThreadPool(); + + bool Start() ABSL_LOCKS_EXCLUDED(threads_mutex_); + + // Runs a task on thread pool. The result indicates whether the task is put + // into the thread pool. + bool Run(Runnable &&task) ABSL_LOCKS_EXCLUDED(tasks_mutex_); + + void ShutDown() ABSL_LOCKS_EXCLUDED(threads_mutex_); + + private: + Runnable NextTask() ABSL_LOCKS_EXCLUDED(tasks_mutex_); + + size_t max_pool_size_; + std::atomic_bool shut_down_; + + absl::Mutex threads_mutex_; + std::vector threads_ ABSL_GUARDED_BY(threads_mutex_); + absl::Mutex tasks_mutex_; + std::queue tasks_ ABSL_GUARDED_BY(tasks_mutex_); +}; +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPL_LINUX_THREAD_POOL_H_ diff --git a/internal/platform/implementation/linux/thread_pool_test.cc b/internal/platform/implementation/linux/thread_pool_test.cc new file mode 100644 index 00000000..b0ff996c --- /dev/null +++ b/internal/platform/implementation/linux/thread_pool_test.cc @@ -0,0 +1,70 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "absl/synchronization/blocking_counter.h" +#include "absl/synchronization/notification.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" +#include "gtest/gtest.h" +#include "internal/platform/implementation/linux/thread_pool.h" + +namespace nearby { +namespace linux { +namespace { + +constexpr int kTaskCount = 10; + +TEST(ThreadPool, TasksInSingleThreadRunInSequence) { + absl::BlockingCounter blocking_counter(kTaskCount); + auto pool = ThreadPool::Create(1); + std::vector completed_tasks; + std::vector expected_tasks; + + for (int i = 0; i < kTaskCount; ++i) { + expected_tasks.push_back(i); + pool->Run([&, i]() { + absl::SleepFor(absl::Milliseconds(200)); + completed_tasks.push_back(i); + blocking_counter.DecrementCount(); + }); + } + + blocking_counter.Wait(); + EXPECT_EQ(completed_tasks, expected_tasks); + pool->ShutDown(); +} + +TEST(ThreadPool, TasksInMultipleThreadsRunInParallel) { + absl::BlockingCounter blocking_counter(kTaskCount); + absl::Time start_time = absl::Now(); + + auto pool = ThreadPool::Create(2); + + for (int i = 0; i < kTaskCount; ++i) { + pool->Run([&]() { + absl::SleepFor(absl::Milliseconds(200)); + blocking_counter.DecrementCount(); + }); + } + + blocking_counter.Wait(); + EXPECT_TRUE(absl::Now() - start_time < absl::Milliseconds(1500)); + pool->ShutDown(); +} + +} // namespace +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/timer.cc b/internal/platform/implementation/linux/timer.cc new file mode 100644 index 00000000..ab4cf74e --- /dev/null +++ b/internal/platform/implementation/linux/timer.cc @@ -0,0 +1,134 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include + +#include "absl/synchronization/mutex.h" +#include "internal/platform/implementation/linux/submittable_executor.h" +#include "internal/platform/implementation/linux/timer.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { + +static void timer_callback(union sigval val) { + absl::AnyInvocable *callback = + reinterpret_cast *>(val.sival_ptr); + if (*callback != nullptr) (*callback)(); +} + +Timer::~Timer() { + absl::MutexLock l(&mutex_); + if (timerid_.has_value()) + if (timer_delete(*timerid_) < 0) { + NEARBY_LOGS(ERROR) << __func__ << ": Error deleting POSIX timer: " + << std::strerror(errno); + } +} + +bool Timer::Create(int delay, int interval, + absl::AnyInvocable callback) { + if (delay < 0 || interval < 0) { + NEARBY_LOGS(ERROR) << __func__ + << ": Delay and interval cannot be negative."; + return false; + } + + absl::MutexLock l(&mutex_); + if (timerid_.has_value()) { + NEARBY_LOGS(ERROR) << __func__ + << "Timer has already been created and armed."; + return false; + } + + callback_ = std::move(callback); + + struct sigevent ev; + ev.sigev_value.sival_ptr = &callback_; + ev.sigev_notify_function = timer_callback; + + timer_t timerid; + + struct itimerspec spec; + + spec.it_value.tv_nsec = delay * 1000000; + spec.it_value.tv_sec = 0; + + spec.it_interval.tv_nsec = interval * 1000000; + spec.it_interval.tv_sec = 0; + + if (timer_create(CLOCK_MONOTONIC, &ev, &timerid) < 0) { + NEARBY_LOGS(ERROR) << __func__ << ": Error creating POSIX timer: " + << std::strerror(errno); + return false; + } + + if (timer_settime(&timerid, 0, &spec, nullptr) < 0) { + NEARBY_LOGS(ERROR) << __func__ << ": Error arming POSIX timer: " + << std::strerror(errno); + if (!timer_delete(&timerid)) { + NEARBY_LOGS(ERROR) << __func__ << ": error deleting POSIX timer: " + << std::strerror(errno); + } + return false; + } + + timerid_ = timerid; + return true; +} + +bool Timer::Stop() { + absl::MutexLock l(&mutex_); + if (!timerid_.has_value()) { + NEARBY_LOGS(WARNING) << __func__ << ": no timer created"; + return true; + } + + if (!timer_delete(&*timerid_)) { + NEARBY_LOGS(ERROR) << __func__ << ": error deleting POSIX timer: " + << std::strerror(errno); + return false; + } + + timerid_.reset(); + + return true; +} + +bool Timer::FireNow() { + absl::MutexLock lock(&mutex_); + if (!timerid_.has_value()) { + NEARBY_LOGS(ERROR) << __func__ << ": No timer has been created"; + return false; + } + if (callback_ == nullptr) { + NEARBY_LOGS(ERROR) << __func__ << ": No callback has been set"; + return false; + } + if (task_executor_ == nullptr) { + task_executor_ = std::make_unique(); + } + + task_executor_->Execute([&]() { callback_(); }); + + return true; +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/timer.h b/internal/platform/implementation/linux/timer.h new file mode 100644 index 00000000..b19ecbe2 --- /dev/null +++ b/internal/platform/implementation/linux/timer.h @@ -0,0 +1,52 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_TIMER_H_ +#define PLATFORM_IMPL_LINUX_TIMER_H_ + +#include +#include +#include +#include + +#include "absl/base/thread_annotations.h" +#include "absl/synchronization/mutex.h" +#include "internal/platform/implementation/linux/submittable_executor.h" +#include "internal/platform/implementation/timer.h" + +namespace nearby { +namespace linux { + +class Timer : public api::Timer { + public: + Timer() : timerid_(nullptr){}; + ~Timer() override; + + bool Create(int delay, int interval, + absl::AnyInvocable callback) override + ABSL_LOCKS_EXCLUDED(mutex_); + bool Stop() override ABSL_LOCKS_EXCLUDED(mutex_); + bool FireNow() override ABSL_LOCKS_EXCLUDED(mutex_); + + private: + absl::Mutex mutex_; + std::optional timerid_ ABSL_GUARDED_BY(mutex_); + absl::AnyInvocable callback_; + std::unique_ptr task_executor_; +}; + +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPL_LINUX_TIMER_H_ diff --git a/internal/platform/implementation/linux/timer_test.cc b/internal/platform/implementation/linux/timer_test.cc new file mode 100644 index 00000000..60308d24 --- /dev/null +++ b/internal/platform/implementation/linux/timer_test.cc @@ -0,0 +1,68 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "internal/platform/implementation/timer.h" + +#include // NOLINT +// NOLINT +#include +#include // NOLINT + +#include "gtest/gtest.h" +#include "internal/platform/implementation/platform.h" + +namespace nearby { +namespace linux { +namespace { + +TEST(Timer, TestCreateTimer) { + int count = 0; + + std::unique_ptr timer = + nearby::api::ImplementationPlatform::CreateTimer(); + + ASSERT_TRUE(timer != nullptr); + EXPECT_FALSE(timer->Create(-100, 0, [&]() { ++count; })); + EXPECT_TRUE(timer->Stop()); +} + +// This test case cannot run on Google3 +TEST(Timer, DISABLED_TestRepeatTimer) { + int count = 0; + + std::unique_ptr timer = + nearby::api::ImplementationPlatform::CreateTimer(); + + ASSERT_TRUE(timer != nullptr); + EXPECT_TRUE(timer->Create(300, 300, [&]() { ++count; })); + std::this_thread::sleep_for(std::chrono::seconds(1)); + EXPECT_TRUE(timer->Stop()); + EXPECT_EQ(count, 3); +} + +TEST(Timer, DISABLED_TestFireNow) { + int count = 0; + + auto timer = nearby::api::ImplementationPlatform::CreateTimer(); + + EXPECT_TRUE(timer != nullptr); + EXPECT_TRUE(timer->Create(3000, 3000, [&]() { ++count; })); + EXPECT_TRUE(timer->FireNow()); + EXPECT_TRUE(timer->Stop()); + EXPECT_EQ(count, 1); +} + +} // namespace +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/utils.cc b/internal/platform/implementation/linux/utils.cc new file mode 100644 index 00000000..9de6ebd2 --- /dev/null +++ b/internal/platform/implementation/linux/utils.cc @@ -0,0 +1,102 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include + +#include "absl/strings/str_cat.h" +#include "internal/platform/implementation/linux/utils.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { +std::optional UuidFromString(const std::string &uuid_str) { + sd_id128_t uuid; + if (auto ret = sd_id128_from_string(uuid_str.c_str(), &uuid); ret < 0) + return std::nullopt; + + const int ONE = 1; + if (*(reinterpret_cast(&ONE)) == + 1) { // On a little endian platform + uint64_t msb = static_cast(uuid.bytes[7]) + + (static_cast(uuid.bytes[6]) << 8) + + (static_cast(uuid.bytes[5]) << 16) + + (static_cast(uuid.bytes[4]) << 24) + + (static_cast(uuid.bytes[3]) << 32) + + (static_cast(uuid.bytes[2]) << 40) + + (static_cast(uuid.bytes[1]) << 48) + + (static_cast(uuid.bytes[0]) << 56); + uint64_t lsb = static_cast(uuid.bytes[15]) + + (static_cast(uuid.bytes[14]) << 8) + + (static_cast(uuid.bytes[13]) << 16) + + (static_cast(uuid.bytes[12]) << 24) + + (static_cast(uuid.bytes[11]) << 32) + + (static_cast(uuid.bytes[10]) << 40) + + (static_cast(uuid.bytes[9]) << 48) + + (static_cast(uuid.bytes[8]) << 56); + return Uuid(msb, lsb); + } + + return Uuid(uuid.qwords[0], uuid.qwords[1]); +} + +std::optional NewUuidStr() { + sd_id128_t id; + char id_cstr[SD_ID128_UUID_STRING_MAX]; + if (auto ret = sd_id128_randomize(&id); ret < 0) { + NEARBY_LOGS(ERROR) << __func__ << ": could not generate a random UUID: " + << std::strerror(ret); + return std::nullopt; + } + + return std::string(sd_id128_to_uuid_string(id, id_cstr)); +} + +std::string RandString(std::string allowed_chars, size_t length) { + thread_local static std::random_device device{}; + + std::mt19937 gen{device()}; + std::uniform_int_distribution dist(0, allowed_chars.length() - 1); + + std::string s; + s.reserve(length); + + for (auto i = 0; i < length; i++) { + s += allowed_chars[dist(gen)]; + } + + return s; +} + +std::string RandSSID() { + std::string allowed_chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789"; + + return absl::StrCat("DIRECT-", RandString(allowed_chars, 25)); +} + +std::string RandWPAPassphrase() { + std::string allowed_chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "!\"#$%&'()*+,-./[\\]^_`~{|}"; + + return RandString(allowed_chars, 63); +} +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/utils.h b/internal/platform/implementation/linux/utils.h new file mode 100644 index 00000000..3f251f4a --- /dev/null +++ b/internal/platform/implementation/linux/utils.h @@ -0,0 +1,38 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_UTILS_H_ +#define PLATFORM_IMPL_LINUX_UTILS_H_ + +#include +#include +#include +#include + +#include "absl/strings/string_view.h" +#include "internal/platform/uuid.h" + +namespace nearby { +namespace linux { + +std::optional UuidFromString(const std::string &uuid_str); +std::optional NewUuidStr(); + +std::string RandSSID(); +std::string RandWPAPassphrase(); + +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPL_LINUX_UTILS_H_ diff --git a/internal/platform/implementation/linux/utils_test.cc b/internal/platform/implementation/linux/utils_test.cc new file mode 100644 index 00000000..9e8e6529 --- /dev/null +++ b/internal/platform/implementation/linux/utils_test.cc @@ -0,0 +1,55 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "absl/strings/ascii.h" +#include "internal/platform/implementation/linux/utils.h" + +#include "gtest/gtest.h" + +namespace nearby { +namespace linux { + +TEST(UtilsTests, UuidFromStringRoundTrip) { + std::string input = "b5209043-f493-4b38-8c34-810aa3cd1407"; + + auto nearby_uuid = UuidFromString(input); + EXPECT_TRUE(nearby_uuid.has_value()); + + EXPECT_EQ(absl::AsciiStrToLower(std::string{*nearby_uuid}), + "b5209043-f493-4b38-8c34-810aa3cd1407"); +} + +TEST(UtilsTests, GenNewUuid) { + auto uuid_str = NewUuidStr(); + EXPECT_TRUE(uuid_str.has_value()); + auto uuid = UuidFromString(*uuid_str); + EXPECT_TRUE(uuid.has_value()); + EXPECT_EQ(absl::AsciiStrToLower(std::string{*uuid}), + *uuid_str); +} + +TEST(UtilsTests, GenRandSSID) { + std::string ssid = RandSSID(); + EXPECT_EQ(ssid.length(), 32); + EXPECT_EQ(ssid.find("DIRECT-"), 0); +} + +TEST(UtilsTests, GenRandRandWPAPassphrase) { + std::string password = RandWPAPassphrase(); + EXPECT_EQ(password.length(), 63); +} +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/wifi_direct.cc b/internal/platform/implementation/linux/wifi_direct.cc new file mode 100644 index 00000000..37ef709b --- /dev/null +++ b/internal/platform/implementation/linux/wifi_direct.cc @@ -0,0 +1,137 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include + +#include "internal/platform/implementation/linux/tcp_server_socket.h" +#include "internal/platform/implementation/linux/wifi_direct.h" +#include "internal/platform/implementation/linux/wifi_direct_server_socket.h" +#include "internal/platform/implementation/linux/wifi_direct_socket.h" +#include "internal/platform/implementation/linux/wifi_hotspot.h" +#include "internal/platform/implementation/linux/wifi_medium.h" +#include "internal/platform/implementation/wifi_direct.h" +#include "internal/platform/wifi_credential.h" + +namespace nearby { +namespace linux { +std::unique_ptr +NetworkManagerWifiDirectMedium::ConnectToService( + absl::string_view ip_address, int port, + CancellationFlag *cancellation_flag) { + auto socket = TCPSocket::Connect(std::string(ip_address), port); + if (!socket.has_value()) return nullptr; + + return std::make_unique(std::move(*socket)); +} + +std::unique_ptr +NetworkManagerWifiDirectMedium::ListenForService(int port) { + auto active_connection = wireless_device_->GetActiveConnection(); + if (active_connection == nullptr) { + return nullptr; + } + + auto ip4addresses = active_connection->GetIP4Addresses(); + if (ip4addresses.empty()) { + NEARBY_LOGS(ERROR) + << __func__ + << "Could not find any IPv4 addresses for active connection " + << active_connection->getObjectPath(); + return nullptr; + } + + auto socket = TCPServerSocket::Listen(std::ref(ip4addresses[0]), port); + if (!socket.has_value()) return nullptr; + + return std::make_unique( + std::move(*socket), std::move(active_connection), network_manager_); +} + +bool NetworkManagerWifiDirectMedium::ConnectWifiDirect( + WifiDirectCredentials *wifi_direct_credentials) { + if (wifi_direct_credentials == nullptr) { + NEARBY_LOGS(ERROR) << __func__ << ": hotspot_credentials cannot be null"; + return false; + } + + auto ssid = wifi_direct_credentials->GetSSID(); + auto password = wifi_direct_credentials->GetPassword(); + + return wireless_device_->ConnectToNetwork(ssid, password, + api::WifiAuthType::kWpaPsk) == + api::WifiConnectionStatus::kConnected; +} + +bool NetworkManagerWifiDirectMedium::DisconnectWifiDirect() { + if (!ConnectedToWifi()) { + NEARBY_LOGS(ERROR) << __func__ << ": Not connected to a WiFi hotspot"; + return false; + } + + auto active_connection = wireless_device_->GetActiveConnection(); + if (active_connection == nullptr) { + return false; + } + + try { + network_manager_->DeactivateConnection(active_connection->getObjectPath()); + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(network_manager_, "DeactivateConnection", e); + return false; + } + + return true; +} + +bool NetworkManagerWifiDirectMedium::ConnectedToWifi() { + try { + auto mode = wireless_device_->Mode(); + return mode == 2; // NM_802_11_MODE_INFRA + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_GET_ERROR(wireless_device_, "Mode", e); + return false; + } +} + +bool NetworkManagerWifiDirectMedium::StartWifiDirect( + WifiDirectCredentials *wifi_direct_credentials) { + // According to the comments in the windows implementation, the wifi direct + // medium is currently just a regular wifi hotspot. + auto wireless_device = std::make_unique( + network_manager_, wireless_device_->getObjectPath()); + auto hotspot = NetworkManagerWifiHotspotMedium(network_manager_, + std::move(wireless_device)); + + HotspotCredentials hotspot_creds; + if (!hotspot.StartWifiHotspot(&hotspot_creds)) return false; + + wifi_direct_credentials->SetSSID(hotspot_creds.GetSSID()); + wifi_direct_credentials->SetPassword(hotspot_creds.GetPassword()); + return true; +} + +bool NetworkManagerWifiDirectMedium::StopWifiDirect() { + auto wireless_device = std::make_unique( + network_manager_, wireless_device_->getObjectPath()); + auto hotspot = NetworkManagerWifiHotspotMedium(network_manager_, + std::move(wireless_device)); + + return hotspot.DisconnectWifiHotspot(); +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/wifi_direct.h b/internal/platform/implementation/linux/wifi_direct.h new file mode 100644 index 00000000..42d773e8 --- /dev/null +++ b/internal/platform/implementation/linux/wifi_direct.h @@ -0,0 +1,66 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_WIFI_DIRECT_H_ +#define PLATFORM_IMPL_LINUX_WIFI_DIRECT_H_ +#include + +#include + +#include + +#include "internal/platform/implementation/linux/network_manager.h" +#include "internal/platform/implementation/linux/wifi_medium.h" +#include "internal/platform/implementation/wifi_direct.h" + +namespace nearby { +namespace linux { +class NetworkManagerWifiDirectMedium : public api::WifiDirectMedium { + public: + NetworkManagerWifiDirectMedium( + std::shared_ptr network_manager, + std::unique_ptr wireless_device) + : system_bus_(network_manager->GetConnection()), + network_manager_(std::move(network_manager)), + wireless_device_(std::move(wireless_device)) {} + + bool IsInterfaceValid() const override { return true; } + std::unique_ptr ConnectToService( + absl::string_view ip_address, int port, + CancellationFlag *cancellation_flag) override; + std::unique_ptr ListenForService( + int port) override; + bool ConnectWifiDirect( + WifiDirectCredentials *wifi_direct_credentials) override; + bool DisconnectWifiDirect() override; + + bool StartWifiDirect(WifiDirectCredentials *wifi_direct_credentials) override; + bool StopWifiDirect() override; + + absl::optional> GetDynamicPortRange() + override { + return std::nullopt; + } + + private: + bool ConnectedToWifi(); + + std::shared_ptr system_bus_; + std::shared_ptr network_manager_; + std::unique_ptr wireless_device_; +}; +} // namespace linux +} // namespace nearby + +#endif diff --git a/internal/platform/implementation/linux/wifi_direct_server_socket.cc b/internal/platform/implementation/linux/wifi_direct_server_socket.cc new file mode 100644 index 00000000..395f412b --- /dev/null +++ b/internal/platform/implementation/linux/wifi_direct_server_socket.cc @@ -0,0 +1,51 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "internal/platform/exception.h" +#include "internal/platform/implementation/linux/wifi_direct_server_socket.h" +#include "internal/platform/implementation/linux/wifi_direct_socket.h" + +namespace nearby { +namespace linux { +std::string NetworkManagerWifiDirectServerSocket::GetIPAddress() const { + auto ip4addresses = active_conn_->GetIP4Addresses(); + if (ip4addresses.empty()) { + NEARBY_LOGS(ERROR) + << __func__ + << ": Could not find any IPv4 addresses for active connection " + << active_conn_->getObjectPath(); + return std::string(); + } + return ip4addresses[0]; +} + +int NetworkManagerWifiDirectServerSocket::GetPort() const { + return server_socket_.GetPort(); +} + +std::unique_ptr +NetworkManagerWifiDirectServerSocket::Accept() { + auto sock = server_socket_.Accept(); + if (!sock.has_value()) return nullptr; + return std::make_unique(std::move(*sock)); +} + +Exception NetworkManagerWifiDirectServerSocket::Close() { + return server_socket_.Close(); +} +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/wifi_direct_server_socket.h b/internal/platform/implementation/linux/wifi_direct_server_socket.h new file mode 100644 index 00000000..03077626 --- /dev/null +++ b/internal/platform/implementation/linux/wifi_direct_server_socket.h @@ -0,0 +1,49 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_WIFI_DIRECT_SERVER_SOCKET_H_ +#define PLATFORM_IMPL_LINUX_WIFI_DIRECT_SERVER_SOCKET_H_ + +#include +#include "internal/platform/implementation/linux/network_manager_active_connection.h" +#include "internal/platform/implementation/linux/tcp_server_socket.h" +#include "internal/platform/implementation/linux/wifi_medium.h" +#include "internal/platform/implementation/wifi_direct.h" +namespace nearby { +namespace linux { +class NetworkManagerWifiDirectServerSocket + : public api::WifiDirectServerSocket { + public: + NetworkManagerWifiDirectServerSocket( + TCPServerSocket socket, + std::unique_ptr active_conn, + std::shared_ptr network_manager) + : server_socket_(std::move(socket)), + active_conn_(std::move(active_conn)), + network_manager_(std::move(network_manager)) {} + + std::string GetIPAddress() const override; + int GetPort() const override; + std::unique_ptr Accept() override; + Exception Close() override; + + private: + TCPServerSocket server_socket_; + std::unique_ptr active_conn_; + std::shared_ptr network_manager_; +}; +} // namespace linux +} // namespace nearby + +#endif diff --git a/internal/platform/implementation/linux/wifi_direct_socket.h b/internal/platform/implementation/linux/wifi_direct_socket.h new file mode 100644 index 00000000..293d1789 --- /dev/null +++ b/internal/platform/implementation/linux/wifi_direct_socket.h @@ -0,0 +1,40 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_WIFI_DIRECT_SOCKET_H_ +#define PLATFORM_IMPL_LINUX_WIFI_DIRECT_SOCKET_H_ + +#include "internal/platform/exception.h" +#include "internal/platform/implementation/linux/stream.h" +#include "internal/platform/implementation/linux/tcp_server_socket.h" +#include "internal/platform/implementation/wifi_direct.h" + +namespace nearby { +namespace linux { +class WifiDirectSocket : public api::WifiDirectSocket { + public: + explicit WifiDirectSocket(TCPSocket socket) : socket_(std::move(socket)) {} + + InputStream &GetInputStream() override { return socket_.GetInputStream(); } + OutputStream &GetOutputStream() override { return socket_.GetOutputStream(); } + + Exception Close() override { return socket_.Close(); }; + + private: + TCPSocket socket_; +}; +} // namespace linux +} // namespace nearby + +#endif diff --git a/internal/platform/implementation/linux/wifi_hotspot.cc b/internal/platform/implementation/linux/wifi_hotspot.cc new file mode 100644 index 00000000..1ce8059d --- /dev/null +++ b/internal/platform/implementation/linux/wifi_hotspot.cc @@ -0,0 +1,310 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include + +#include "internal/platform/implementation/linux/dbus.h" +#include "internal/platform/implementation/linux/network_manager.h" +#include "internal/platform/implementation/linux/utils.h" +#include "internal/platform/implementation/linux/wifi_hotspot.h" +#include "internal/platform/implementation/linux/wifi_hotspot_server_socket.h" +#include "internal/platform/implementation/linux/wifi_hotspot_socket.h" +#include "internal/platform/implementation/linux/wifi_medium.h" +#include "internal/platform/implementation/wifi.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { +std::unique_ptr +NetworkManagerWifiHotspotMedium::ConnectToService( + absl::string_view ip_address, int port, + CancellationFlag *cancellation_flag) { + if (!ConnectedToWifi()) { + NEARBY_LOGS(ERROR) + << __func__ + << ": Cannot connect to service without an active WiFi hotspot"; + return nullptr; + } + + int sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock < 0) { + NEARBY_LOGS(ERROR) << __func__ + << ": Error opening socket: " << std::strerror(errno); + return nullptr; + } + + NEARBY_LOGS(VERBOSE) << __func__ << ": Connecting to " << ip_address << ":" + << port; + struct sockaddr_in addr {}; + addr.sin_addr.s_addr = inet_addr(std::string(ip_address).c_str()); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + + auto ret = + connect(sock, reinterpret_cast(&addr), sizeof(addr)); + if (ret < 0) { + NEARBY_LOGS(ERROR) << __func__ << ": Error connecting to socket: " + << std::strerror(errno); + return nullptr; + } + + return std::make_unique(sock); +} + +std::unique_ptr +NetworkManagerWifiHotspotMedium::ListenForService(int port) { + if (!WifiHotspotActive()) { + NEARBY_LOGS(ERROR) + << __func__ + << ": Cannot connect to service without an active WiFi hotspot"; + return nullptr; + } + + auto active_connection = wireless_device_->GetActiveConnection(); + if (active_connection == nullptr) { + return nullptr; + } + + auto ip4addresses = active_connection->GetIP4Addresses(); + if (ip4addresses.empty()) { + NEARBY_LOGS(ERROR) + << __func__ + << "Could not find any IPv4 addresses for active connection " + << active_connection->getObjectPath(); + return nullptr; + } + + auto sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock < 0) { + NEARBY_LOGS(ERROR) << __func__ + << ": Error opening socket: " << std::strerror(errno); + return nullptr; + } + + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = inet_addr(ip4addresses[0].c_str()); + addr.sin_port = htons(port); + + auto ret = + bind(sock, reinterpret_cast(&addr), sizeof(addr)); + if (ret < 0) { + NEARBY_LOGS(ERROR) << __func__ + << ": Error binding to socket: " << std::strerror(errno); + return nullptr; + } + + NEARBY_LOGS(VERBOSE) << __func__ << ": Listening for services on " + << ip4addresses[0] << ":" << port << " on device " + << wireless_device_->getObjectPath(); + + ret = listen(sock, 0); + if (ret < 0) { + NEARBY_LOGS(ERROR) << __func__ << ": Error listening on socket: " + << std::strerror(errno); + return nullptr; + } + + return std::make_unique( + sock, std::move(active_connection), network_manager_); +} + +bool NetworkManagerWifiHotspotMedium::StartWifiHotspot( + HotspotCredentials *hotspot_credentials) { + if (WifiHotspotActive()) { + NEARBY_LOGS(ERROR) << __func__ << ": " << wireless_device_->getObjectPath() + << ": cannot start WiFi hotspot, a hotspot is already " + "active on this device"; + return false; + } + + std::string ssid = RandSSID(); + hotspot_credentials->SetSSID(ssid); + + std::string password = RandWPAPassphrase(); + hotspot_credentials->SetPassword(password); + + auto connection_id = NewUuidStr(); + if (!connection_id.has_value()) { + NEARBY_LOGS(ERROR) << __func__ << ": could not generate a connection UUID"; + return false; + } + + std::map> + connection_settings{ + { + "connection", + {{"uuid", *connection_id}, + {"id", "Google Nearby Hotspot"}, + {"type", "802-11-wireless"}, + {"zone", "Public"}}, + }, + {"802-11-wireless", + {{"assigned-mac-address", "random"}, + {"ap-isolation", networkmanager::constants::kNMTernaryFalse}, + {"mode", "ap"}, + {"ssid", std::vector(ssid.begin(), ssid.end())}, + {"security", "802-11-wireless-security"}}}, + {"802-11-wireless-security", + {{"pmf", + networkmanager::constants::setting::kWirelessSecurityPMFDisable}, + {"key-mgmt", "wpa-psk"}, + {"psk", password}}}, + {"ipv4", {{"method", "shared"}}}, + {"ipv6", + { + {"addr-gen-mode", networkmanager::constants::setting:: + kIP6ConfigAddrGenModeStablePrivacy}, + {"method", "shared"}, + }}}; + std::unique_ptr active_conn; + try { + auto [path, active_path, result] = + network_manager_->AddAndActivateConnection2( + connection_settings, wireless_device_->getObjectPath(), "/", + {{"persist", "volatile"}, {"bind-activation", "dbus-client"}}); + active_conn = std::make_unique( + system_bus_, active_path); + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(network_manager_, "AddAndActivateConnection2", + e); + return false; + } + + auto [reason, timeout] = active_conn->WaitForConnection(); + if (timeout) { + NEARBY_LOGS(ERROR) + << __func__ << ": " + << ": timed out while waiting for connection " + << active_conn->getObjectPath() + << " to be activated, last NMActiveConnectionStateReason: " + << reason->ToString(); + DisconnectWifiHotspot(); + return false; + } + + NEARBY_LOGS(INFO) << __func__ << ": Started a WiFi hotspot on device " + << wireless_device_->getObjectPath() << " at " + << active_conn->getObjectPath(); + return true; +} + +bool NetworkManagerWifiHotspotMedium::StopWifiHotspot() { + if (!WifiHotspotActive()) { + NEARBY_LOGS(ERROR) + << __func__ << ": " << wireless_device_->getObjectPath() + << ": Cannot stop WiFi hotspot as a WiFi hotspot is not active"; + } + + // Get the active connection object for the hotspot AP. + sdbus::ObjectPath active_ap_path; + + try { + active_ap_path = wireless_device_->ActiveAccessPoint(); + if (active_ap_path.empty()) { + NEARBY_LOGS(ERROR) << __func__ << ": No active access points on " + << wireless_device_->getObjectPath(); + return false; + } + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_GET_ERROR(wireless_device_, "ActiveAccessPoint", e); + } + + auto object_manager = networkmanager::ObjectManager(system_bus_); + auto active_connection = wireless_device_->GetActiveConnection(); + if (active_connection == nullptr) { + NEARBY_LOGS(ERROR) + << __func__ + << ": Could not find an active connection using the access point " + << active_ap_path; + return false; + } + + NEARBY_LOGS(INFO) << __func__ << ": " << wireless_device_->getObjectPath() + << ": Deactivating active connection " + << active_connection->getObjectPath(); + + try { + network_manager_->DeactivateConnection(active_connection->getObjectPath()); + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(network_manager_, "DeactivateConnection", e); + return false; + } + + return true; +} + +bool NetworkManagerWifiHotspotMedium::ConnectWifiHotspot( + HotspotCredentials *hotspot_credentials) { + if (hotspot_credentials == nullptr) { + NEARBY_LOGS(ERROR) << __func__ << ": hotspot_credentials cannot be null"; + return false; + } + + auto ssid = hotspot_credentials->GetSSID(); + auto password = hotspot_credentials->GetPassword(); + + return wireless_device_->ConnectToNetwork(ssid, password, + api::WifiAuthType::kWpaPsk) == + api::WifiConnectionStatus::kConnected; +} + +bool NetworkManagerWifiHotspotMedium::DisconnectWifiHotspot() { + if (!ConnectedToWifi()) { + NEARBY_LOGS(ERROR) << __func__ << ": Not connected to a WiFi hotspot"; + return false; + } + + auto active_connection = wireless_device_->GetActiveConnection(); + if (active_connection == nullptr) { + return false; + } + + try { + network_manager_->DeactivateConnection(active_connection->getObjectPath()); + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(network_manager_, "DeactivateConnection", e); + return false; + } + + return true; +} + +bool NetworkManagerWifiHotspotMedium::WifiHotspotActive() { + try { + auto mode = wireless_device_->Mode(); + return mode == networkmanager::constants::kNM80211ModeAP; + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_GET_ERROR(wireless_device_, "Mode", e); + return false; + } +} + +bool NetworkManagerWifiHotspotMedium::ConnectedToWifi() { + try { + auto mode = wireless_device_->Mode(); + return mode == networkmanager::constants::kNM80211ModeInfra; + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_GET_ERROR(wireless_device_, "Mode", e); + return false; + } +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/wifi_hotspot.h b/internal/platform/implementation/linux/wifi_hotspot.h new file mode 100644 index 00000000..2eed28cb --- /dev/null +++ b/internal/platform/implementation/linux/wifi_hotspot.h @@ -0,0 +1,71 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_WIFI_HOTSPOT_H_ +#define PLATFORM_IMPL_LINUX_WIFI_HOTSPOT_H_ + +#include +#include + +#include "internal/platform/implementation/linux/wifi_medium.h" +#include "internal/platform/implementation/wifi_hotspot.h" + +namespace nearby { +namespace linux { +class NetworkManagerWifiHotspotMedium : public api::WifiHotspotMedium { + public: + NetworkManagerWifiHotspotMedium( + std::shared_ptr network_manager, + sdbus::ObjectPath wireless_device_object_path) + : system_bus_(network_manager->GetConnection()), + wireless_device_(std::make_unique( + network_manager, std::move(wireless_device_object_path))), + network_manager_(std::move(network_manager)) {} + NetworkManagerWifiHotspotMedium( + std::shared_ptr network_manager, + std::unique_ptr wireless_device) + : system_bus_(network_manager->GetConnection()), + wireless_device_(std::move(wireless_device)), + network_manager_(std::move(network_manager)) {} + + bool IsInterfaceValid() const override { return true; } + std::unique_ptr ConnectToService( + absl::string_view ip_address, int port, + CancellationFlag *cancellation_flag) override; + std::unique_ptr ListenForService( + int port) override; + + bool StartWifiHotspot(HotspotCredentials *hotspot_credentials) override; + bool StopWifiHotspot() override; + + bool ConnectWifiHotspot(HotspotCredentials *hotspot_credentials) override; + bool DisconnectWifiHotspot() override; + + absl::optional> GetDynamicPortRange() + override { + return absl::nullopt; + } + + private: + bool WifiHotspotActive(); + bool ConnectedToWifi(); + + std::shared_ptr system_bus_; + std::unique_ptr wireless_device_; + std::shared_ptr network_manager_; +}; +} // namespace linux +} // namespace nearby + +#endif diff --git a/internal/platform/implementation/linux/wifi_hotspot_server_socket.cc b/internal/platform/implementation/linux/wifi_hotspot_server_socket.cc new file mode 100644 index 00000000..88d7e5d0 --- /dev/null +++ b/internal/platform/implementation/linux/wifi_hotspot_server_socket.cc @@ -0,0 +1,80 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "internal/platform/implementation/linux/wifi_hotspot_server_socket.h" +#include "internal/platform/implementation/linux/wifi_hotspot_socket.h" +#include "internal/platform/implementation/linux/wifi_medium.h" + +namespace nearby { +namespace linux { +std::string NetworkManagerWifiHotspotServerSocket::GetIPAddress() const { + auto ip4addresses = active_conn_->GetIP4Addresses(); + if (ip4addresses.empty()) { + NEARBY_LOGS(ERROR) + << __func__ + << ": Could not find any IPv4 addresses for active connection " + << active_conn_->getObjectPath(); + return {}; + } + return ip4addresses[0]; +} + +int NetworkManagerWifiHotspotServerSocket::GetPort() const { + struct sockaddr_in sin {}; + socklen_t len = sizeof(sin); + auto ret = + getsockname(fd_.get(), reinterpret_cast(&sin), &len); + if (ret < 0) { + NEARBY_LOGS(ERROR) << __func__ << ": Error getting information for socket " + << fd_.get() << ": " << std::strerror(errno); + return 0; + } + + return ntohs(sin.sin_port); +} + +std::unique_ptr +NetworkManagerWifiHotspotServerSocket::Accept() { + struct sockaddr_in addr {}; + socklen_t len = sizeof(addr); + + auto conn = + accept(fd_.get(), reinterpret_cast(&addr), &len); + if (conn < 0) { + NEARBY_LOGS(ERROR) << __func__ + << ": Error accepting incoming connections on socket " + << fd_.get() << ": " << std::strerror(errno); + return nullptr; + } + + return std::make_unique(conn); +} + +Exception NetworkManagerWifiHotspotServerSocket::Close() { + int fd = fd_.release(); + shutdown(fd, SHUT_RDWR); + auto ret = close(fd_.release()); + if (ret < 0) { + NEARBY_LOGS(ERROR) << __func__ + << ": Error closing socket: " << std::strerror(errno); + return {Exception::kFailed}; + } + + return {Exception::kSuccess}; +} +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/wifi_hotspot_server_socket.h b/internal/platform/implementation/linux/wifi_hotspot_server_socket.h new file mode 100644 index 00000000..d6130aeb --- /dev/null +++ b/internal/platform/implementation/linux/wifi_hotspot_server_socket.h @@ -0,0 +1,49 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_WIFI_SERVER_SOCKET_H_ +#define PLATFORM_IMPL_LINUX_WIFI_SERVER_SOCKET_H_ + +#include + +#include "internal/platform/implementation/linux/network_manager.h" +#include "internal/platform/implementation/linux/network_manager_active_connection.h" +#include "internal/platform/implementation/wifi_hotspot.h" + +namespace nearby { +namespace linux { +class NetworkManagerWifiHotspotServerSocket + : public api::WifiHotspotServerSocket { + public: + NetworkManagerWifiHotspotServerSocket( + int socket, std::unique_ptr active_conn, + std::shared_ptr network_manager) + : fd_(socket), + active_conn_(std::move(active_conn)), + network_manager_(std::move(network_manager)) {} + + std::string GetIPAddress() const override; + int GetPort() const override; + std::unique_ptr Accept() override; + Exception Close() override; + + private: + sdbus::UnixFd fd_; + std::unique_ptr active_conn_; + std::shared_ptr network_manager_; +}; +} // namespace linux +} // namespace nearby + +#endif diff --git a/internal/platform/implementation/linux/wifi_hotspot_socket.h b/internal/platform/implementation/linux/wifi_hotspot_socket.h new file mode 100644 index 00000000..e94587ff --- /dev/null +++ b/internal/platform/implementation/linux/wifi_hotspot_socket.h @@ -0,0 +1,47 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_WIFI_HOTSPOT_SOCKET_H_ +#define PLATFORM_IMPL_LINUX_WIFI_HOTSPOT_SOCKET_H_ + +#include "internal/platform/implementation/linux/stream.h" +#include "internal/platform/implementation/wifi_hotspot.h" + +namespace nearby { +namespace linux { +class WifiHotspotSocket : public api::WifiHotspotSocket { + public: + explicit WifiHotspotSocket(int connection_fd) + : fd_(sdbus::UnixFd(connection_fd)), + output_stream_(fd_), + input_stream_(fd_) {} + + nearby::InputStream &GetInputStream() override { return input_stream_; }; + nearby::OutputStream &GetOutputStream() override { return output_stream_; }; + Exception Close() override { + input_stream_.Close(); + output_stream_.Close(); + + return Exception{Exception::kSuccess}; + }; + + private: + sdbus::UnixFd fd_; + OutputStream output_stream_; + InputStream input_stream_; +}; +} // namespace linux +} // namespace nearby + +#endif diff --git a/internal/platform/implementation/linux/wifi_lan.cc b/internal/platform/implementation/linux/wifi_lan.cc new file mode 100644 index 00000000..7e825497 --- /dev/null +++ b/internal/platform/implementation/linux/wifi_lan.cc @@ -0,0 +1,227 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "absl/strings/substitute.h" +#include "internal/platform/implementation/linux/avahi.h" +#include "internal/platform/implementation/linux/dbus.h" +#include "internal/platform/implementation/linux/tcp_server_socket.h" +#include "internal/platform/implementation/linux/wifi_lan.h" +#include "internal/platform/implementation/linux/wifi_lan_server_socket.h" +#include "internal/platform/implementation/linux/wifi_lan_socket.h" +#include "internal/platform/implementation/wifi_lan.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { +WifiLanMedium::WifiLanMedium( + std::shared_ptr network_manager) + : system_bus_(network_manager->GetConnection()), + network_manager_(std::move(network_manager)), + avahi_(std::make_shared(*system_bus_)) {} + +bool WifiLanMedium::IsNetworkConnected() const { + auto state = network_manager_->getState(); + return state == networkmanager::NetworkManager::kNMStateConnectedLocal || + state == networkmanager::NetworkManager::kNMStateConnectedSite || + state == networkmanager::NetworkManager::kNMStateConnectedGlobal; +} + +std::optional> entry_group_key( + const NsdServiceInfo &nsd_service_info) { + auto name = nsd_service_info.GetServiceName(); + if (name.empty()) { + NEARBY_LOGS(ERROR) << __func__ << ": service name cannot be empty"; + return std::nullopt; + } + + auto type = nsd_service_info.GetServiceType(); + if (type.empty()) { + NEARBY_LOGS(ERROR) << __func__ << ": service type cannot be empty"; + return std::nullopt; + } + + return std::make_pair(std::move(name), std::move(type)); +} + +bool WifiLanMedium::StartAdvertising(const NsdServiceInfo &nsd_service_info) { + auto key = entry_group_key(nsd_service_info); + if (!key.has_value()) { + return false; + } + + { + absl::ReaderMutexLock l(&entry_groups_mutex_); + if (entry_groups_.count(*key) == 1) { + NEARBY_LOGS(ERROR) << __func__ + << ": advertising is already active for this service"; + return false; + } + } + + auto txt_records_map = nsd_service_info.GetTxtRecords(); + std::vector> txt_records(txt_records_map.size()); + std::size_t i = 0; + + for (auto [key, value] : nsd_service_info.GetTxtRecords()) { + std::string entry = absl::Substitute("$0=$1", key, value); + txt_records[i++] = std::vector(entry.begin(), entry.end()); + } + + sdbus::ObjectPath entry_group_path; + try { + entry_group_path = avahi_->EntryGroupNew(); + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(avahi_, "EntryGroupNew", e); + return false; + } + + auto entry_group = + std::make_unique(*system_bus_, entry_group_path); + + try { + entry_group->AddService( + -1, // AVAHI_IF_UNSPEC + -1, // AVAHI_PROTO_UNSPED + 0, nsd_service_info.GetServiceName(), nsd_service_info.GetServiceType(), + std::string(), std::string(), nsd_service_info.GetPort(), txt_records); + entry_group->Commit(); + } catch (const sdbus::Error &e) { + NEARBY_LOGS(ERROR) << __func__ << ": Got error '" << e.getName() + << "' with message '" << e.getMessage() + << "' while adding service"; + return false; + } + + absl::MutexLock l(&entry_groups_mutex_); + entry_groups_.insert({*key, std::move(entry_group)}); + + return true; +} + +bool WifiLanMedium::StopAdvertising(const NsdServiceInfo &nsd_service_info) { + auto key = entry_group_key(nsd_service_info); + if (!key.has_value()) { + return false; + } + + absl::MutexLock l(&entry_groups_mutex_); + if (entry_groups_.count(*key) == 0) { + NEARBY_LOGS(ERROR) << __func__ + << ": Advertising is already inactive for this service."; + return false; + } + + entry_groups_.erase(*key); + return true; +} + +bool WifiLanMedium::StartDiscovery( + const std::string &service_type, + api::WifiLanMedium::DiscoveredServiceCallback callback) { + { + absl::ReaderMutexLock l(&service_browsers_mutex_); + if (service_browsers_.count(service_type) != 0) { + auto &object = service_browsers_[service_type]; + NEARBY_LOGS(ERROR) << __func__ << ": A service browser for service type " + << service_type << " already exists at " + << object->getObjectPath(); + return false; + } + } + + try { + sdbus::ObjectPath browser_object_path = + avahi_->ServiceBrowserPrepare(-1, // AVAHI_IF_UNSPEC + -1, // AVAHI_PROTO_UNSPED + service_type, std::string(), 0); + NEARBY_LOGS(VERBOSE) + << __func__ + << ": Created a new org.freedesktop.Avahi.ServiceBrowser object at " + << browser_object_path; + + absl::MutexLock l(&service_browsers_mutex_); + service_browsers_.emplace( + service_type, + std::make_unique( + *system_bus_, browser_object_path, std::move(callback), avahi_)); + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(avahi_, "ServiceBrowserPrepare", e); + return false; + } + + service_browsers_mutex_.ReaderLock(); + auto &browser = service_browsers_[service_type]; + service_browsers_mutex_.ReaderUnlock(); + + try { + NEARBY_LOGS(VERBOSE) << __func__ << ": Starting service discovery for " + << browser->getObjectPath(); + browser->Start(); + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(browser, "Start", e); + return false; + } + + return true; +} + +bool WifiLanMedium::StopDiscovery(const std::string &service_type) { + absl::MutexLock l(&service_browsers_mutex_); + + if (service_browsers_.count(service_type) == 0) { + NEARBY_LOGS(ERROR) << __func__ << ": Service type " << service_type + << " has not been registered for discovery"; + return false; + } + service_browsers_.erase(service_type); + + return true; +} + +std::unique_ptr WifiLanMedium::ConnectToService( + const std::string &ip_address, int port, + CancellationFlag *cancellation_flag) { + auto socket = TCPSocket::Connect(ip_address, port); + if (!socket.has_value()) return nullptr; + return std::make_unique(*socket); +} + +std::unique_ptr WifiLanMedium::ListenForService( + int port) { + auto socket = TCPServerSocket::Listen(std::nullopt, port); + if (!socket.has_value()) return nullptr; + + return std::make_unique(std::move(*socket), + network_manager_); +} + +absl::optional> GetDynamicPortRange() { + return absl::nullopt; +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/wifi_lan.h b/internal/platform/implementation/linux/wifi_lan.h new file mode 100644 index 00000000..5b4b278c --- /dev/null +++ b/internal/platform/implementation/linux/wifi_lan.h @@ -0,0 +1,80 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_WIFI_LAN_H_ +#define PLATFORM_IMPL_LINUX_WIFI_LAN_H_ +#include +#include + +#include "absl/container/flat_hash_map.h" +#include "absl/synchronization/mutex.h" +#include "internal/platform/implementation/linux/avahi.h" +#include "internal/platform/implementation/linux/wifi_medium.h" +#include "internal/platform/implementation/wifi_lan.h" +#include "internal/platform/nsd_service_info.h" + +namespace nearby { +namespace linux { +class WifiLanMedium : public api::WifiLanMedium { + public: + explicit WifiLanMedium(std::shared_ptr network_manager); + + bool IsNetworkConnected() const override; + + bool StartAdvertising(const NsdServiceInfo &nsd_service_info) override + ABSL_LOCKS_EXCLUDED(entry_groups_mutex_); + bool StopAdvertising(const NsdServiceInfo &nsd_service_info) override + ABSL_LOCKS_EXCLUDED(entry_groups_mutex_); + + bool StartDiscovery(const std::string &service_type, + DiscoveredServiceCallback callback) override + ABSL_LOCKS_EXCLUDED(service_browsers_mutex_); + bool StopDiscovery(const std::string &service_type) override + ABSL_LOCKS_EXCLUDED(service_browsers_mutex_); + + std::unique_ptr ConnectToService( + const NsdServiceInfo &remote_service_info, + CancellationFlag *cancellation_flag) override { + return ConnectToService(remote_service_info.GetIPAddress(), + remote_service_info.GetPort(), cancellation_flag); + }; + std::unique_ptr ConnectToService( + const std::string &ip_address, int port, + CancellationFlag *cancellation_flag) override; + std::unique_ptr ListenForService( + int port = 0) override; + absl::optional> GetDynamicPortRange() + override { + return std::nullopt; + } + + private: + std::shared_ptr system_bus_; + std::shared_ptr network_manager_; + + std::shared_ptr avahi_; + + absl::Mutex entry_groups_mutex_; + absl::flat_hash_map, + std::unique_ptr> + entry_groups_ ABSL_GUARDED_BY(entry_groups_mutex_); + + absl::Mutex service_browsers_mutex_; + absl::flat_hash_map> + service_browsers_ ABSL_GUARDED_BY(service_browsers_mutex_); +}; +} // namespace linux +} // namespace nearby + +#endif diff --git a/internal/platform/implementation/linux/wifi_lan_server_socket.cc b/internal/platform/implementation/linux/wifi_lan_server_socket.cc new file mode 100644 index 00000000..cffde75c --- /dev/null +++ b/internal/platform/implementation/linux/wifi_lan_server_socket.cc @@ -0,0 +1,91 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "internal/platform/exception.h" +#include "internal/platform/implementation/linux/dbus.h" +#include "internal/platform/implementation/linux/wifi_lan_server_socket.h" +#include "internal/platform/implementation/linux/wifi_lan_socket.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { +std::string WifiLanServerSocket::GetIPAddress() const { + std::vector connection_paths; + try { + connection_paths = network_manager_->ActiveConnections(); + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_GET_ERROR(network_manager_, "ActiveConnections", e); + return std::string(); + } + + for (auto &path : connection_paths) { + auto active_connection = + std::make_unique(system_bus_, path); + std::string conn_type; + try { + conn_type = active_connection->Type(); + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_GET_ERROR(active_connection, "Type", e); + continue; + } + if (conn_type == "802-11-wireless" || conn_type == "802-3-ethernet") { + auto ip4config_path = active_connection->Ip4Config(); + networkmanager::IP4Config ip4config(system_bus_, ip4config_path); + std::vector> address_data; + + try { + address_data = ip4config.AddressData(); + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_GET_ERROR(&ip4config, "IP4Config", e); + continue; + } + + if (address_data.size() > 0) { + return address_data[0]["address"]; + } + } + } + + NEARBY_LOGS(ERROR) + << __func__ << ": Could not find any active IP addresses for this device"; + return std::string(); +} + +int WifiLanServerSocket::GetPort() const { + return server_socket_.GetPort(); +} + +std::unique_ptr WifiLanServerSocket::Accept() { + auto sock = server_socket_.Accept(); + if (!sock.has_value()) return nullptr; + + return std::make_unique(std::move(*sock)); +} + +Exception WifiLanServerSocket::Close() { + return server_socket_.Close(); +} +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/wifi_lan_server_socket.h b/internal/platform/implementation/linux/wifi_lan_server_socket.h new file mode 100644 index 00000000..00730bac --- /dev/null +++ b/internal/platform/implementation/linux/wifi_lan_server_socket.h @@ -0,0 +1,52 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_WIFI_LAN_SERVER_SOCKET_H_ +#define PLATFORM_IMPL_LINUX_WIFI_LAN_SERVER_SOCKET_H_ + +#include + +#include +#include + +#include "internal/platform/exception.h" +#include "internal/platform/implementation/linux/network_manager.h" +#include "internal/platform/implementation/linux/tcp_server_socket.h" +#include "internal/platform/implementation/wifi_lan.h" + +namespace nearby { +namespace linux { +class WifiLanServerSocket : public api::WifiLanServerSocket { + public: + explicit WifiLanServerSocket( + TCPServerSocket socket, + std::shared_ptr network_manager) + : server_socket_(std::move(socket)), + network_manager_(std::move(network_manager)), + system_bus_(network_manager_->GetConnection()) {} + + std::string GetIPAddress() const override; + int GetPort() const override; + + std::unique_ptr Accept() override; + Exception Close() override; + + private: + TCPServerSocket server_socket_; + std::shared_ptr network_manager_; + std::shared_ptr system_bus_; +}; +} // namespace linux +} // namespace nearby +#endif diff --git a/internal/platform/implementation/linux/wifi_lan_socket.h b/internal/platform/implementation/linux/wifi_lan_socket.h new file mode 100644 index 00000000..816fa065 --- /dev/null +++ b/internal/platform/implementation/linux/wifi_lan_socket.h @@ -0,0 +1,48 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_WIFI_LAN_SOCKET_H_ +#define PLATFORM_IMPL_LINUX_WIFI_LAN_SOCKET_H_ + +#include + +#include + +#include "internal/platform/implementation/linux/stream.h" +#include "internal/platform/implementation/linux/tcp_server_socket.h" +#include "internal/platform/implementation/wifi_lan.h" +#include "internal/platform/input_stream.h" +#include "internal/platform/output_stream.h" + +namespace nearby { +namespace linux { +class WifiLanSocket : public api::WifiLanSocket { + public: + explicit WifiLanSocket(TCPSocket sock) : socket_(std::move(sock)) {} + + nearby::InputStream &GetInputStream() override { + return socket_.GetInputStream(); + } + nearby::OutputStream &GetOutputStream() override { + return socket_.GetOutputStream(); + } + Exception Close() override { return socket_.Close(); } + + private: + TCPSocket socket_; +}; +} // namespace linux +} // namespace nearby + +#endif diff --git a/internal/platform/implementation/linux/wifi_medium.cc b/internal/platform/implementation/linux/wifi_medium.cc new file mode 100644 index 00000000..e9c33d2d --- /dev/null +++ b/internal/platform/implementation/linux/wifi_medium.cc @@ -0,0 +1,355 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "absl/synchronization/mutex.h" +#include "internal/platform/implementation/linux/dbus.h" +#include "internal/platform/implementation/linux/generated/dbus/networkmanager/device_wireless_client.h" +#include "internal/platform/implementation/linux/network_manager_active_connection.h" +#include "internal/platform/implementation/linux/utils.h" +#include "internal/platform/implementation/linux/wifi_medium.h" +#include "internal/platform/implementation/wifi.h" + +namespace nearby { +namespace linux { +api::WifiCapability &NetworkManagerWifiMedium::GetCapability() { + try { + auto cap_mask = WirelessCapabilities(); + // https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMDeviceWifiCapabilities + capability_.supports_5_ghz = (cap_mask & 0x00000400) != 0; + capability_.supports_6_ghz = false; + capability_.support_wifi_direct = true; + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_GET_ERROR(&getProxy(), "WirelessCapabilities", e); + } + + return capability_; +} + +inline std::int32_t to_signed(std::uint32_t v) { + if (v <= INT_MAX) return static_cast(v); + if (v >= INT_MIN) return static_cast(v - INT_MIN) + INT_MIN; + + return INT_MAX; +} + +api::WifiInformation &NetworkManagerWifiMedium::GetInformation() { + std::unique_ptr active_access_point; + + try { + auto ap_path = ActiveAccessPoint(); + if (ap_path.empty()) { + information_ = api::WifiInformation{false}; + return information_; + } + active_access_point = + std::make_unique(*system_bus_, ap_path); + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_GET_ERROR(this, "ActiveAccessPoint", e); + } + + try { + auto ssid_vec = active_access_point->Ssid(); + std::string ssid{ssid_vec.begin(), ssid_vec.end()}; + + information_ = + api::WifiInformation{true, ssid, active_access_point->HwAddress(), + to_signed(active_access_point->Frequency())}; + networkmanager::ObjectManager manager(system_bus_); + auto ip4config = manager.GetIp4Config(active_access_point->getObjectPath()); + + if (ip4config != nullptr) { + auto address_data = ip4config->AddressData(); + if (!address_data.empty()) { + std::string address = address_data[0]["address"]; + information_.ip_address_dot_decimal = address; + + struct in_addr addr {}; + inet_aton(address.c_str(), &addr); + + char addr_bytes[4]; + memcpy(addr_bytes, &addr.s_addr, sizeof(addr_bytes)); + information_.ip_address_4_bytes = std::string(addr_bytes, 4); + } + } else { + NEARBY_LOGS(ERROR) << __func__ << ": " << getObjectPath() + << ": Could not find the Ip4Config object for " + << active_access_point->getObjectPath(); + } + } catch (const sdbus::Error &e) { + NEARBY_LOGS(ERROR) + << __func__ << ": " << getObjectPath() << ": Got error '" << e.getName() + << "' with message '" << e.getMessage() + << "' while populating network information for access point " + << active_access_point->getObjectPath(); + } + + return information_; +} + +void NetworkManagerWifiMedium::onPropertiesChanged( + const std::string &interfaceName, + const std::map &changedProperties, + const std::vector &invalidatedProperties) { + if (interfaceName != org::freedesktop::NetworkManager::Device:: + Wireless_proxy::INTERFACE_NAME) { + return; + } + + if (changedProperties.count("LastScan") == 1) { + absl::MutexLock l(&last_scan_lock_); + last_scan_ = changedProperties.at("LastScan"); + } +} + +bool NetworkManagerWifiMedium::Scan( + const api::WifiMedium::ScanResultCallback &scan_result_callback) { + // absl::MutexLock l(&scan_result_callback_lock_); + // scan_result_callback_ = scan_result_callback; + + try { + RequestScan({}); + } catch (const sdbus::Error &e) { + scan_result_callback_ = std::nullopt; + DBUS_LOG_METHOD_CALL_ERROR(&getProxy(), "RequestScan", e); + return false; + } + return false; +} + +std::shared_ptr +NetworkManagerWifiMedium::SearchBySSIDNoScan( + std::vector &ssid_bytes) { + absl::ReaderMutexLock l(&known_access_points_lock_); + for (auto &[object_path, ap] : known_access_points_) { + try { + if (ap->Ssid() == ssid_bytes) { + return ap; + } + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_GET_ERROR(ap, "Ssid", e); + } + } + + return nullptr; +} + +std::shared_ptr +NetworkManagerWifiMedium::SearchBySSID(absl::string_view ssid, + absl::Duration scan_timeout) { + std::vector ssid_bytes(ssid.begin(), ssid.end()); + // First, try to see if we already know an AP with this SSID. + auto ap = SearchBySSIDNoScan(ssid_bytes); + if (ap != nullptr) { + return ap; + } + + NEARBY_LOGS(INFO) << __func__ << ": " << getObjectPath() << ": SSID " << ssid + << " not currently known by device " << getObjectPath() + << ", requesting a scan"; + + std::int64_t cur_last_scan; + { + absl::ReaderMutexLock l(&last_scan_lock_); + cur_last_scan = last_scan_; + } + + // Otherwise, request a Scan first and wait for it to finish. + try { + RequestScan( + {{"ssids", std::vector>{ssid_bytes}}}); + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(this, "RequestScan", e); + } + + auto scan_finish = [&, cur_last_scan]() { + last_scan_lock_.AssertReaderHeld(); + return cur_last_scan != last_scan_; + }; + + absl::Condition cond(&scan_finish); + bool success = last_scan_lock_.ReaderLockWhenWithTimeout(cond, scan_timeout); + last_scan_lock_.ReaderUnlock(); + + if (!success) { + NEARBY_LOGS(WARNING) << __func__ << ": " << getObjectPath() + << ": timed out waiting for scan to finish"; + } + + ap = SearchBySSIDNoScan(ssid_bytes); + if (ap == nullptr) { + NEARBY_LOGS(WARNING) << __func__ << ": " << getObjectPath() + << ": Couldn't find SSID " << ssid; + } + + return ap; +} + +static inline std::pair, std::string> +AuthAlgAndKeyMgmt(api::WifiAuthType auth_type) { + switch (auth_type) { + case api::WifiAuthType::kUnknown: + case api::WifiAuthType::kOpen: + return {"open", "none"}; + case api::WifiAuthType::kWpaPsk: + return {std::nullopt, "wpa-psk"}; + case api::WifiAuthType::kWep: + return {"none", "wep"}; + } +} + +api::WifiConnectionStatus NetworkManagerWifiMedium::ConnectToNetwork( + absl::string_view ssid, absl::string_view password, + api::WifiAuthType auth_type) { + auto ap = SearchBySSID(ssid); + if (ap == nullptr) { + NEARBY_LOGS(ERROR) << __func__ << ": " << getObjectPath() + << ": Couldn't find SSID " << ssid; + return api::WifiConnectionStatus::kConnectionFailure; + } + + auto connection_id = NewUuidStr(); + if (!connection_id.has_value()) { + NEARBY_LOGS(ERROR) << __func__ << ": could not generate a connection UUID"; + return api::WifiConnectionStatus::kUnknown; + } + + auto [auth_alg, key_mgmt] = AuthAlgAndKeyMgmt(auth_type); + + std::map> + connection_settings{ + {"connection", + { + {"uuid", *connection_id}, + {"autoconnect", true}, + {"id", std::string(ssid)}, + {"type", "802-11-wireless"}, + {"zone", "Public"}, + }}, + {"802-11-wireless", + { + {"ssid", std::vector(ssid.begin(), ssid.end())}, + {"mode", "infrastructure"}, + {"security", "802-11-wireless-security"}, + {"assigned-mac-address", "random"}, + }}, + {"802-11-wireless-security", {{"key-mgmt", key_mgmt}}}}; + if (!password.empty()) { + connection_settings["802-11-wireless-security"]["psk"] = + std::string(password); + } + if (auth_alg.has_value()) { + connection_settings["802-11-wireless-security"]["auth-alg"] = *auth_alg; + } + + sdbus::ObjectPath connection_path, active_conn_path; + try { + auto [cp, acp, _r] = network_manager_->AddAndActivateConnection2( + connection_settings, getObjectPath(), ap->getObjectPath(), + {{"persist", "volatile"}, {"bind-activation", "dbus-client"}}); + connection_path = std::move(cp); + active_conn_path = std::move(acp); + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(this, "AddAndActivateConnection2", e); + return api::WifiConnectionStatus::kUnknown; + } + + NEARBY_LOGS(INFO) << __func__ << ": " << getObjectPath() + << ": Added a new connection at " << connection_path; + auto active_connection = + networkmanager::ActiveConnection(system_bus_, active_conn_path); + auto [reason, timeout] = active_connection.WaitForConnection(); + if (timeout) { + NEARBY_LOGS(ERROR) + << __func__ << ": " << getObjectPath() + << ": timed out while waiting for connection " << active_conn_path + << " to be activated, last NMActiveConnectionStateReason: " + << reason->ToString(); + return api::WifiConnectionStatus::kUnknown; + } + + if (reason.has_value()) { + NEARBY_LOGS(ERROR) << __func__ << ": " << getObjectPath() << ": connection " + << active_conn_path + << " failed to activate, NMActiveConnectionStateReason:" + << reason->ToString(); + if (reason->value == + networkmanager::ActiveConnection::ActiveConnectionStateReason:: + kStateReasonNoSecrets || + reason->value == + networkmanager::ActiveConnection::ActiveConnectionStateReason:: + kStateReasonLoginFailed) + return api::WifiConnectionStatus::kAuthFailure; + } + + NEARBY_LOGS(INFO) << __func__ << ": Activated connection " << connection_path; + return api::WifiConnectionStatus::kConnected; +} + +bool NetworkManagerWifiMedium::VerifyInternetConnectivity() { + try { + std::uint32_t connectivity = network_manager_->CheckConnectivity(); + return connectivity == 4; // NM_CONNECTIVITY_FULL + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(network_manager_, "CheckConnectivity", e); + return false; + } +} + +std::string NetworkManagerWifiMedium::GetIpAddress() { + GetInformation(); + return information_.ip_address_dot_decimal; +} + +std::unique_ptr +NetworkManagerWifiMedium::GetActiveConnection() { + sdbus::ObjectPath active_ap_path; + + try { + active_ap_path = ActiveAccessPoint(); + if (active_ap_path.empty()) { + NEARBY_LOGS(ERROR) << __func__ << ": No active access points on " + << getObjectPath(); + return nullptr; + } + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_GET_ERROR(this, "ActiveAccessPoint", e); + return nullptr; + } + + auto object_manager = networkmanager::ObjectManager(system_bus_); + auto conn = object_manager.GetActiveConnectionForAccessPoint(active_ap_path, + getObjectPath()); + + if (conn == nullptr) { + NEARBY_LOGS(ERROR) + << __func__ + << ": Could not find an active connection using the access point " + << active_ap_path << " and device " << getObjectPath(); + } + return conn; +} +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/wifi_medium.h b/internal/platform/implementation/linux/wifi_medium.h new file mode 100644 index 00000000..4c393a55 --- /dev/null +++ b/internal/platform/implementation/linux/wifi_medium.h @@ -0,0 +1,139 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_WIFI_MEDIUM_H_ +#define PLATFORM_IMPL_LINUX_WIFI_MEDIUM_H_ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "absl/synchronization/mutex.h" +#include "internal/platform/implementation/linux/generated/dbus/networkmanager/device_wireless_client.h" +#include "internal/platform/implementation/linux/network_manager.h" +#include "internal/platform/implementation/linux/network_manager_access_point.h" +#include "internal/platform/implementation/linux/network_manager_active_connection.h" +#include "internal/platform/implementation/wifi.h" + +namespace nearby { +namespace linux { +class NetworkManagerWifiMedium + : public api::WifiMedium, + public sdbus::ProxyInterfaces< + org::freedesktop::NetworkManager::Device::Wireless_proxy, + sdbus::Properties_proxy> { + public: + NetworkManagerWifiMedium(const NetworkManagerWifiMedium &) = delete; + NetworkManagerWifiMedium(NetworkManagerWifiMedium &&) = delete; + NetworkManagerWifiMedium &operator=(const NetworkManagerWifiMedium &) = + delete; + NetworkManagerWifiMedium &operator=(NetworkManagerWifiMedium &&) = delete; + NetworkManagerWifiMedium( + std::shared_ptr network_manager, + const sdbus::ObjectPath &wireless_device_object_path) + : ProxyInterfaces(*network_manager->GetConnection(), + "org.freedesktop.NetworkManager", + wireless_device_object_path), + system_bus_(network_manager->GetConnection()), + network_manager_(std::move(network_manager)), + last_scan_(-1) { + registerProxy(); + } + + ~NetworkManagerWifiMedium() override { unregisterProxy(); } + + class ScanResultCallback : public api::WifiMedium::ScanResultCallback { + public: + void OnScanResults( + const std::vector &scan_results) override { + // TODO: Add implementation at some point + } + }; + + bool IsInterfaceValid() const override { return true; }; + api::WifiCapability &GetCapability() override; + api::WifiInformation &GetInformation() override; + bool Scan( + const api::WifiMedium::ScanResultCallback &scan_result_callback) override; + + std::shared_ptr SearchBySSID( + absl::string_view ssid, absl::Duration scan_timeout = absl::Seconds(15)) + ABSL_LOCKS_EXCLUDED(known_access_points_lock_); + + api::WifiConnectionStatus ConnectToNetwork( + absl::string_view ssid, absl::string_view password, + api::WifiAuthType auth_type) override; + + bool VerifyInternetConnectivity() override; + std::string GetIpAddress() override; + + std::unique_ptr GetActiveConnection(); + + protected: + void onPropertiesChanged( + const std::string &interfaceName, + const std::map &changedProperties, + const std::vector &invalidatedProperties) override; + + void onAccessPointAdded(const sdbus::ObjectPath &access_point) override + ABSL_LOCKS_EXCLUDED(known_access_points_lock_) { + absl::MutexLock l(&known_access_points_lock_); + known_access_points_.erase(access_point); + known_access_points_.emplace(access_point, + std::make_shared( + getProxy().getConnection(), access_point)); + } + void onAccessPointRemoved(const sdbus::ObjectPath &access_point) override + ABSL_LOCKS_EXCLUDED(known_access_points_lock_) { + absl::MutexLock l(&known_access_points_lock_); + known_access_points_.erase(access_point); + } + + private: + std::shared_ptr SearchBySSIDNoScan( + std::vector &ssid) + ABSL_LOCKS_EXCLUDED(known_access_points_lock_); + + std::shared_ptr system_bus_; + std::shared_ptr network_manager_; + + api::WifiCapability capability_; + api::WifiInformation information_{false}; + + absl::Mutex known_access_points_lock_; + absl::flat_hash_map> + known_access_points_ ABSL_GUARDED_BY(known_access_points_lock_); + + absl::Mutex scan_result_callback_lock_; + std::optional< + std::reference_wrapper> + scan_result_callback_ ABSL_GUARDED_BY(scan_result_callback_lock_); + + absl::Mutex last_scan_lock_; + std::int64_t last_scan_ ABSL_GUARDED_BY(last_scan_lock_); +}; + +} // namespace linux +} // namespace nearby + +#endif diff --git a/internal/platform/implementation/linux/wifi_socket.h b/internal/platform/implementation/linux/wifi_socket.h new file mode 100644 index 00000000..fbc0e9a4 --- /dev/null +++ b/internal/platform/implementation/linux/wifi_socket.h @@ -0,0 +1,30 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_WIFI_LAN_SOCKET_H_ +#define PLATFORM_IMPL_LINUX_WIFI_LAN_SOCKET_H_ + +namespace nearby { +namespace api { +class WifiLanSocket { + public: + ~WifiLanSocket() = default; + + private: + int fd; +}; +} // namespace api +} // namespace nearby + +#endif