diff --git a/.bazelproject b/.bazelproject new file mode 100644 index 00000000..dc8814f2 --- /dev/null +++ b/.bazelproject @@ -0,0 +1,17 @@ +directories: + . + -bazel-* + +targets: + //internal/platform/implementation/linux/test:all + //internal/platform/implementation/linux:all + //internal/platform/implementation/windows:all + +derive_targets_from_directories: true + +additional_languages: + c++ + +# Ensure external dependencies are indexed +build_flags: + --keep_going diff --git a/.devcontainer/bazel/Dockerfile b/.devcontainer/bazel/Dockerfile new file mode 100755 index 00000000..a2f9f54c --- /dev/null +++ b/.devcontainer/bazel/Dockerfile @@ -0,0 +1,50 @@ +FROM ubuntu:24.04 + +RUN apt-get update && apt-get install -y \ + build-essential \ + clang \ + cmake \ + git \ + curl \ + libsdbus-c++-dev \ + libssl-dev \ + libsystemd-dev \ + libcurlpp-dev \ + apt-transport-https \ + gnupg \ + && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Installing bazel +RUN curl -fsSL https://bazel.build/bazel-release.pub.gpg | gpg --dearmor >bazel-archive-keyring.gpg && mv bazel-archive-keyring.gpg /usr/share/keyrings \ + && echo "deb [arch=amd64 signed-by=/usr/share/keyrings/bazel-archive-keyring.gpg] https://storage.googleapis.com/bazel-apt stable jdk1.8" | tee /etc/apt/sources.list.d/bazel.list \ + && apt update && apt install -y bazel + +RUN apt-get install -y pkg-config libasound2-dev + +RUN apt-get install -y libunistring-dev libldap-dev libkrb5-dev libgpg-error-dev gdb libc6-dbg libgtest-dev libbluetooth-dev clangd + +#gtest +RUN apt-get update && apt-get install -y \ + googletest cmake g++ make \ + && rm -rf /var/lib/apt/lists/* \ + && cd /usr/src/googletest \ + && cmake -S . -B build \ + && cmake --build build -j"$(nproc)" \ + && cmake --install build + +# bash history +RUN SNIPPET="export PROMPT_COMMAND='history -a' && export HISTFILE=/commandhistory/.bash_history" \ + && echo "$SNIPPET" >> "/root/.bashrc" + +RUN apt-get install -y libunistring-dev libldap-dev libkrb5-dev libgpg-error-dev ca-certificates tzdata curl && update-ca-certificates && date + +WORKDIR /workspace + + +ENTRYPOINT ["/bin/bash"] +# [Optional] Uncomment this section to install additional vcpkg ports. +# RUN su vscode -c "${VCPKG_ROOT}/vcpkg install " + +# [Optional] Uncomment this section to install additional packages. +# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ +# && apt-get -y install --no-install-recommends diff --git a/.devcontainer/bazel/devcontainer.json b/.devcontainer/bazel/devcontainer.json new file mode 100755 index 00000000..2e1782a8 --- /dev/null +++ b/.devcontainer/bazel/devcontainer.json @@ -0,0 +1,20 @@ +{ + "name": "Bazel Dev Container", + "dockerFile": "Dockerfile", + "customizations": { + "vscode": { + "settings": { + "C_Cpp.intelliSenseEngine": "Default" + }, + "extensions": ["bazelbuild.vscode-bazel"] + } + }, + "mounts": [ + "source=${localWorkspaceFolder},target=/workspace,type=bind", + "source=bashhistory,target=/commandhistory,type=volume", + "source=${localWorkspaceFolder}/bazel-output,target=/bazel-output,type=bind", + "source=/run/dbus,target=/run/dbus,type=bind" + // "source=${localWorkspaceFolder}/build-bin,target=/workspace/bazel-bin,type=bind", + // "source=${localWorkspaceFolder}/build-out,target=/workspace/bazel-out,type=bind" + ] +} diff --git a/.devcontainer/bazelclion/Dockerfile b/.devcontainer/bazelclion/Dockerfile new file mode 100755 index 00000000..a2f9f54c --- /dev/null +++ b/.devcontainer/bazelclion/Dockerfile @@ -0,0 +1,50 @@ +FROM ubuntu:24.04 + +RUN apt-get update && apt-get install -y \ + build-essential \ + clang \ + cmake \ + git \ + curl \ + libsdbus-c++-dev \ + libssl-dev \ + libsystemd-dev \ + libcurlpp-dev \ + apt-transport-https \ + gnupg \ + && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Installing bazel +RUN curl -fsSL https://bazel.build/bazel-release.pub.gpg | gpg --dearmor >bazel-archive-keyring.gpg && mv bazel-archive-keyring.gpg /usr/share/keyrings \ + && echo "deb [arch=amd64 signed-by=/usr/share/keyrings/bazel-archive-keyring.gpg] https://storage.googleapis.com/bazel-apt stable jdk1.8" | tee /etc/apt/sources.list.d/bazel.list \ + && apt update && apt install -y bazel + +RUN apt-get install -y pkg-config libasound2-dev + +RUN apt-get install -y libunistring-dev libldap-dev libkrb5-dev libgpg-error-dev gdb libc6-dbg libgtest-dev libbluetooth-dev clangd + +#gtest +RUN apt-get update && apt-get install -y \ + googletest cmake g++ make \ + && rm -rf /var/lib/apt/lists/* \ + && cd /usr/src/googletest \ + && cmake -S . -B build \ + && cmake --build build -j"$(nproc)" \ + && cmake --install build + +# bash history +RUN SNIPPET="export PROMPT_COMMAND='history -a' && export HISTFILE=/commandhistory/.bash_history" \ + && echo "$SNIPPET" >> "/root/.bashrc" + +RUN apt-get install -y libunistring-dev libldap-dev libkrb5-dev libgpg-error-dev ca-certificates tzdata curl && update-ca-certificates && date + +WORKDIR /workspace + + +ENTRYPOINT ["/bin/bash"] +# [Optional] Uncomment this section to install additional vcpkg ports. +# RUN su vscode -c "${VCPKG_ROOT}/vcpkg install " + +# [Optional] Uncomment this section to install additional packages. +# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ +# && apt-get -y install --no-install-recommends diff --git a/.devcontainer/bazelclion/devcontainer.json b/.devcontainer/bazelclion/devcontainer.json new file mode 100755 index 00000000..62755804 --- /dev/null +++ b/.devcontainer/bazelclion/devcontainer.json @@ -0,0 +1,55 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/cpp +{ + "name": "C++ Latest", + "build": { + "dockerfile": "Dockerfile" + }, + "mounts": [ + { + "source": "bashhistory", + "target": "/commandhistory", + "type": "volume" + }, + { + "source": "/run/dbus", + "target": "/run/dbus", + "type": "bind" + }, + { + "source": "${localWorkspaceFolder}/bazel-output", + "target": "/bazel-output", + "type": "bind" + } + ], + "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind", + "workspaceFolder": "/workspace", + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "gcc -v", + + // Configure tool-specific properties. + "customizations" : { + "jetbrains" : { + "backend" : "CLion", + //"plugins":[ + // "com.google.idea.bazel.clwb", + // "com.github.copilot" + //] + } + }, + "runArgs": [ + "--network=host", + "--dns=1.1.1.1", + "--dns=8.8.8.8", + "--memory=8g", + "--cpus=6", + "--shm-size=2g" + ] +} diff --git a/.devcontainer/bazelclion/reinstall-cmake.sh b/.devcontainer/bazelclion/reinstall-cmake.sh new file mode 100755 index 00000000..408b81d2 --- /dev/null +++ b/.devcontainer/bazelclion/reinstall-cmake.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +#------------------------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. +#------------------------------------------------------------------------------------------------------------- +# +set -e + +CMAKE_VERSION=${1:-"none"} + +if [ "${CMAKE_VERSION}" = "none" ]; then + echo "No CMake version specified, skipping CMake reinstallation" + exit 0 +fi + +# Cleanup temporary directory and associated files when exiting the script. +cleanup() { + EXIT_CODE=$? + set +e + if [[ -n "${TMP_DIR}" ]]; then + echo "Executing cleanup of tmp files" + rm -Rf "${TMP_DIR}" + fi + exit $EXIT_CODE +} +trap cleanup EXIT + + +echo "Installing CMake..." +apt-get -y purge --auto-remove cmake +mkdir -p /opt/cmake + +architecture=$(dpkg --print-architecture) +case "${architecture}" in + arm64) + ARCH=aarch64 ;; + amd64) + ARCH=x86_64 ;; + *) + echo "Unsupported architecture ${architecture}." + exit 1 + ;; +esac + +CMAKE_BINARY_NAME="cmake-${CMAKE_VERSION}-linux-${ARCH}.sh" +CMAKE_CHECKSUM_NAME="cmake-${CMAKE_VERSION}-SHA-256.txt" +TMP_DIR=$(mktemp -d -t cmake-XXXXXXXXXX) + +echo "${TMP_DIR}" +cd "${TMP_DIR}" + +curl -sSL "https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/${CMAKE_BINARY_NAME}" -O +curl -sSL "https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/${CMAKE_CHECKSUM_NAME}" -O + +sha256sum -c --ignore-missing "${CMAKE_CHECKSUM_NAME}" +sh "${TMP_DIR}/${CMAKE_BINARY_NAME}" --prefix=/opt/cmake --skip-license + +ln -s /opt/cmake/bin/cmake /usr/local/bin/cmake +ln -s /opt/cmake/bin/ctest /usr/local/bin/ctest diff --git a/.gitignore b/.gitignore index c0299bed..202779b3 100644 --- a/.gitignore +++ b/.gitignore @@ -59,5 +59,6 @@ bazel-* /.clwb/ # Devcontainers -/.devcontainer/ +# /.devcontainer/ /connections/walkietalkie/ +/third_party/ diff --git a/LINUX_CONTRIBUTING.md b/LINUX_CONTRIBUTING.md new file mode 100644 index 00000000..92f9d3af --- /dev/null +++ b/LINUX_CONTRIBUTING.md @@ -0,0 +1,69 @@ +# Linux Contributor Guide + +This guide is for contributors working on the Linux platform implementation in this repo. It focuses on local development workflow, platform layout, and where to make changes for new or missing platform features. + +For general project contribution rules, see `CONTRIBUTING.md`. + +## Recommended development environment + +The fastest path to a consistent Linux development setup is to use the devcontainer at `.devcontainers/bazelclion` with CLion and Bazel. + +Notes: + +- Use CLion devcontainers. +- You do not have to use a devcontainer, but it avoids environment drift. +- The devcontainer mounts `/run/dbus`, and the Linux implementation primarily uses D-Bus for platform communication. +- Inside the devcontainer, install: + - `libbluetooth-dev` + - `libgtest-dev` + - `gdb` (optional but helpful) + +## CLion + Bazel setup + +Once the Bazel plugin loads in CLion, run a Bazel sync from the toolbar (top-right). This can take a while on first run. + +After the sync completes, you are ready to develop. + +## Source layout for Linux + +Linux platform implementation lives here: + +- `internal/platform/implementation/linux` + +Key entry points: + +- Platform contract: `internal/platform/implementation/platform.h` +- Linux implementation: `internal/platform/implementation/linux/platform.cc` + +The Linux implementation largely mirrors the Windows platform layout and behavior, so it is useful to compare with the Windows implementation for parity. + +## What the platform layer does + +The Linux platform layer provides abstractions over local network and Bluetooth hardware so Nearby Connections can perform radio operations. These abstractions are organized around Media and Mediums. Each Medium defines a set of functions that the platform must implement. + +If you are adding a feature or fixing a missing capability: + +- Start from `internal/platform/implementation/platform.h`. +- Follow the Medium definitions to understand the required interface. +- Implement or extend the Linux counterparts under `internal/platform/implementation/linux`. + +## About Nearby Sharing on Linux + +Nearby Sharing builds on top of Nearby Connections. The Linux implementation still uses the same platform abstractions described above. + +The example application for Nearby Sharing is located at: + +- `sharing/linux` + +## Nearby Connections examples + +Example applications for Nearby Connections are located at: + +- Walkie-talkie: `nearby/connection/walkietalkie` +- File share: `nearby/connections/file_share` + +## CLion project visibility + +The `.bazelproject` configuration limits which directories CLion shows by default. If you want all directories visible, change the project root setting to `.`. + +Be aware that enabling all directories can significantly impact performance and is not recommended for most machines. diff --git a/MODULE.bazel b/MODULE.bazel index 87aa443b..8d609ece 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -18,6 +18,17 @@ git_repository( remote = "https://beto-core.googlesource.com/beto-core", ) +# Hedron's Compile Commands Extractor for Bazel +# https://github.com/hedronvision/bazel-compile-commands-extractor +# Hedron's Compile Commands Extractor for Bazel +# https://github.com/hedronvision/bazel-compile-commands-extractor +bazel_dep(name = "hedron_compile_commands", dev_dependency = True) +git_override( + module_name = "hedron_compile_commands", + remote = "https://github.com/mikael-s-persson/bazel-compile-commands-extractor", + commit = "02d15621b528efd877f5d5657c4b738523a0eb17" +) + rust = use_extension("@rules_rust//rust:extensions.bzl", "rust") rust.toolchain( edition = "2021", @@ -71,7 +82,7 @@ cc_library( hdrs = glob([ "include/nlohmann/**/*.hpp", ]), - includes = ["include"], + strip_include_prefix = "include", visibility = ["//visibility:public"], alwayslink = True, )""", diff --git a/README.md b/README.md index 859c3483..7626abf6 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,40 @@ -# Nearby +# Unofficial Linux Nearby -Nearby is a collection of projects focused on connectivity that enable building cross-device experiences. +This repository is an unofficial Linux implementation of Google Nearby, forked from the official Nearby codebase. It focuses on Linux platform support for Nearby Connections, Nearby Sharing, and Nearby Presence. This is not an officially supported Google product. -## Projects +## What is included -### [Nearby Connections](connections/) +- Nearby Connections +- Nearby Sharing +- Nearby Presence -A peer-to-peer networking API that allows apps to easily discover, connect to, and exchange data with nearby devices in real-time, regardless of network connectivity. +## Linux platform support -### [Nearby Presence](presence/) +The Linux platform implementation provides abstraction layers over local networking and Bluetooth hardware to support Nearby radio operations. The current Linux implementation supports: -An extension to Nearby Connections that features an extensible identity model for authentication and restricted visibility, resource management for system health, and proximity detection through sensor fusion. +- BLE discovery and advertising +- GATT advertising and discovery +- Data transfer over Bluetooth Classic +- Wi-Fi LAN +- Wi-Fi Hotspot +- Wi-Fi Direct +- Wi-Fi LAN advertising and discovery -### [Nearby for Embedded Systems](embedded/) +## Example applications -A lightweight implementation of Fast Pair intended for embedded systems. +- Nearby Sharing service example: `sharing/linux` +- Nearby Connections examples: + - Walkie-talkie: `nearby/connection/walkietalkie` + - File share: `nearby/connections/file_share` ## Contributing -We encourage you to contribute to Nearby! Please check out the [Contributing to Nearby guide](CONTRIBUTING.md) for guidelines about how to proceed. +General contribution guidelines are in `CONTRIBUTING.md`. + +If you are working on the Linux platform, start with `LINUX_CONTRIBUTING.md`. ## License -Nearby is released under the [Apache License 2.0](LICENSE) +Nearby is released under the `LICENSE`. diff --git a/connections/core.cc b/connections/core.cc index 61b77422..84e733c8 100644 --- a/connections/core.cc +++ b/connections/core.cc @@ -15,6 +15,7 @@ #include "connections/core.h" #include +#include #include #include #include @@ -215,13 +216,15 @@ void Core::StartAdvertisingV3(absl::string_view service_id, const NearbyDevice& local_device, v3::ConnectionListener listener, ResultCallback callback) { + auto listener_ptr = + std::make_shared(std::move(listener)); ConnectionListener old_listener = { .initiated_cb = - [&listener](const std::string& endpoint_id, - const ConnectionResponseInfo& info) { + [listener_ptr](const std::string& endpoint_id, + const ConnectionResponseInfo& info) { auto remote_device = v3::ConnectionsDevice( endpoint_id, info.remote_endpoint_info.AsStringView(), {}); - listener.initiated_cb( + listener_ptr->initiated_cb( remote_device, v3::InitialConnectionInfo{ .authentication_digits = info.authentication_token, @@ -231,7 +234,7 @@ void Core::StartAdvertisingV3(absl::string_view service_id, }); }, .accepted_cb = - [v3_cb = listener.result_cb](const std::string& endpoint_id) { + [v3_cb = listener_ptr->result_cb](const std::string& endpoint_id) { auto remote_device = v3::ConnectionsDevice(endpoint_id, "", {}); v3_cb(remote_device, v3::ConnectionResult{.status = Status{ @@ -239,23 +242,23 @@ void Core::StartAdvertisingV3(absl::string_view service_id, }}); }, .rejected_cb = - [v3_cb = listener.result_cb](const std::string& endpoint_id, - Status status) { + [v3_cb = listener_ptr->result_cb](const std::string& endpoint_id, + Status status) { auto remote_device = v3::ConnectionsDevice(endpoint_id, "", {}); v3_cb(remote_device, v3::ConnectionResult{ .status = status, }); }, .disconnected_cb = - [&listener](const std::string& endpoint_id) { + [listener_ptr](const std::string& endpoint_id) { auto remote_device = v3::ConnectionsDevice(endpoint_id, "", {}); - listener.disconnected_cb(remote_device); + listener_ptr->disconnected_cb(remote_device); }, .bandwidth_changed_cb = - [&listener](const std::string& endpoint_id, Medium medium) { + [listener_ptr](const std::string& endpoint_id, Medium medium) { auto remote_device = v3::ConnectionsDevice(endpoint_id, "", {}); - listener.bandwidth_changed_cb(remote_device, - v3::BandwidthInfo{.medium = medium}); + listener_ptr->bandwidth_changed_cb( + remote_device, v3::BandwidthInfo{.medium = medium}); }}; ByteArray local_endpoint_info; if (local_device.GetType() == NearbyDevice::kConnectionsDevice) { diff --git a/connections/file_share/BUILD b/connections/file_share/BUILD new file mode 100644 index 00000000..701e91ba --- /dev/null +++ b/connections/file_share/BUILD @@ -0,0 +1,27 @@ +# Copyright 2024 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_binary( + name = "file_share", + srcs = ["main.cc"], + deps = [ + "//connections:core", + "//connections:core_types", + "//internal/crypto_cros:crypto_cros", + "//internal/platform/implementation/linux:linux", + "@com_google_protobuf//:protobuf", + ], +) diff --git a/connections/file_share/main.cc b/connections/file_share/main.cc new file mode 100644 index 00000000..edeb3970 --- /dev/null +++ b/connections/file_share/main.cc @@ -0,0 +1,555 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "absl/log/log.h" +#include "absl/synchronization/notification.h" +#include "connections/connection_options.h" +#include "connections/core.h" +#include "connections/payload_type.h" +#include "connections/v3/advertising_options.h" +#include "connections/v3/connections_device.h" +#include "connections/v3/discovery_options.h" +#include "connections/v3/listeners.h" +#include "connections/implementation/service_controller_router.h" +#include "internal/platform/file.h" + +namespace { +constexpr char kDefaultServiceId[] = "com.google.nearby.fileshare.cli"; + +absl::Notification g_shutdown; + +void QuitHandler(int, siginfo_t*, void*) { + if (!g_shutdown.HasBeenNotified()) { + g_shutdown.Notify(); + } +} + +struct Options { + bool advertise = false; + bool discover = false; + std::string service_id = kDefaultServiceId; + std::string save_dir; + std::vector send_paths; + nearby::connections::BooleanMediumSelector mediums = + nearby::connections::BooleanMediumSelector().SetAll(true); + nearby::connections::BooleanMediumSelector upgrade_mediums = + nearby::connections::BooleanMediumSelector().SetAll(true); + bool upgrade_mediums_set = false; +}; + +void PrintUsage(const char* prog) { + std::cerr + << "Usage: " << prog + << " [--advertise] [--discover] [--mediums=LIST]\n" + << " [--upgrade_mediums=LIST] [--send=PATH]\n" + << " [--save_dir=DIR] [--service_id=ID]\n" + << "Flags:\n" + << " --advertise Enable advertising\n" + << " --discover Enable discovery\n" + << " --mediums=LIST Comma-separated list of mediums to use for\n" + << " advertising + discovery\n" + << " --upgrade_mediums=LIST Comma-separated list of mediums to use for\n" + << " upgrade (defaults to --mediums)\n" + << " (bluetooth,ble,wifi_lan,wifi_hotspot,wifi_direct,\n" + << " web_rtc,web_rtc_non_cellular,awdl,all)\n" + << " --send=PATH File to send after connection (repeatable)\n" + << " --save_dir=DIR Directory for received files\n" + << " --service_id=ID Override service ID\n" + << " -h, --help Show this help\n" + << "Examples:\n" + << " " << prog + << " --advertise --discover --mediums=ble --upgrade_mediums=wifi_lan\n" + << " --send=/tmp/hello.txt\n" + << " " << prog << " --discover --mediums=wifi_lan --save_dir=/tmp\n"; +} + +bool StartsWith(std::string_view input, std::string_view prefix) { + return input.size() >= prefix.size() && + input.substr(0, prefix.size()) == prefix; +} + +std::string Trim(std::string_view input) { + size_t start = 0; + size_t end = input.size(); + while (start < end && std::isspace(static_cast(input[start]))) { + ++start; + } + while (end > start && + std::isspace(static_cast(input[end - 1]))) { + --end; + } + return std::string(input.substr(start, end - start)); +} + +std::vector SplitCommaList(std::string_view input) { + std::vector tokens; + size_t start = 0; + while (start <= input.size()) { + size_t comma = input.find(',', start); + if (comma == std::string_view::npos) comma = input.size(); + tokens.push_back(Trim(input.substr(start, comma - start))); + start = comma + 1; + } + return tokens; +} + +bool ApplyMediumToken(nearby::connections::BooleanMediumSelector& selector, + const std::string& token, std::string* error) { + if (token.empty()) { + return true; + } + if (token == "all") { + selector.SetAll(true); + return true; + } + if (token == "bluetooth") { + selector.bluetooth = true; + return true; + } + if (token == "ble") { + selector.ble = true; + return true; + } + if (token == "wifi_lan") { + selector.wifi_lan = true; + return true; + } + if (token == "wifi_hotspot") { + selector.wifi_hotspot = true; + return true; + } + if (token == "wifi_direct") { + selector.wifi_direct = true; + return true; + } + if (token == "web_rtc") { + selector.web_rtc = true; + selector.web_rtc_no_cellular = true; + return true; + } + if (token == "web_rtc_non_cellular") { + selector.web_rtc_no_cellular = true; + return true; + } + if (token == "awdl") { + selector.awdl = true; + return true; + } + if (error != nullptr) { + *error = "Unknown medium: " + token; + } + return false; +} + +bool ParseMediums(const std::string& list, + nearby::connections::BooleanMediumSelector* selector, + std::string* error) { + selector->SetAll(false); + for (const auto& token : SplitCommaList(list)) { + if (!ApplyMediumToken(*selector, token, error)) { + return false; + } + } + if (!selector->Any(true)) { + if (error != nullptr) { + *error = "No valid mediums specified"; + } + return false; + } + return true; +} + +bool ReadValueFlag(std::string_view arg, std::string_view name, int* index, + int argc, char** argv, std::string* out) { + if (arg == name) { + if (*index + 1 >= argc) { + return false; + } + *out = argv[++(*index)]; + return true; + } + std::string prefix = std::string(name) + "="; + if (StartsWith(arg, prefix)) { + *out = std::string(arg.substr(prefix.size())); + return true; + } + return false; +} + +std::string MakeEndpointInfo() { + std::string name; + name.reserve(5); + for (int i = 0; i < 5; ++i) { + name.push_back('0' + (std::rand() % 10)); + } + return name; +} + +} // namespace + +class FileShareApp { + public: + explicit FileShareApp(Options options) + : options_(std::move(options)), + router_(std::make_unique()), + core_(std::make_unique(router_.get())), + local_device_(nearby::connections::v3::ConnectionsDevice( + MakeEndpointInfo(), {})) {} + + void Start() { + if (!options_.save_dir.empty()) { + std::error_code error; + std::filesystem::create_directories(options_.save_dir, error); + if (error) { + LOG(WARNING) << "Failed to create save dir: " << options_.save_dir + << " error=" << error.message(); + } + core_->SetCustomSavePath( + options_.save_dir, + [](nearby::connections::Status status) { + LOG(INFO) << "SetCustomSavePath status: " << status.ToString(); + }); + } + + if (options_.advertise) { + StartAdvertising(); + } + if (options_.discover) { + StartDiscovery(); + } + } + + private: + void StartAdvertising() { + nearby::connections::v3::AdvertisingOptions advertising; + advertising.strategy = nearby::connections::Strategy::kP2pCluster; + advertising.advertising_mediums = options_.mediums; + advertising.upgrade_mediums = + options_.upgrade_mediums_set ? options_.upgrade_mediums + : options_.mediums; + + core_->StartAdvertisingV3( + options_.service_id, advertising, local_device_, + MakeConnectionListener(), + [](nearby::connections::Status status) { + LOG(INFO) << "Advertising status: " << status.ToString(); + }); + } + + void StartDiscovery() { + nearby::connections::v3::DiscoveryOptions discovery; + discovery.strategy = nearby::connections::Strategy::kP2pCluster; + discovery.discovery_mediums = options_.mediums; + + core_->StartDiscoveryV3( + options_.service_id, discovery, MakeDiscoveryListener(), + [](nearby::connections::Status status) { + LOG(INFO) << "Discovery status: " << status.ToString(); + }); + } + + nearby::connections::v3::ConnectionListener MakeConnectionListener() { + nearby::connections::v3::ConnectionListener listener; + listener.initiated_cb = + [this](const nearby::NearbyDevice& remote_device, + const nearby::connections::v3::InitialConnectionInfo& info) { + LOG(INFO) << "Connection initiated with " + << remote_device.GetEndpointId() + << " auth_digits=" << info.authentication_digits; + core_->AcceptConnectionV3( + remote_device, MakePayloadListener(), + [](nearby::connections::Status status) { + LOG(INFO) << "AcceptConnection status: " << status.ToString(); + }); + }; + + listener.result_cb = + [this](const nearby::NearbyDevice& remote_device, + nearby::connections::v3::ConnectionResult result) { + LOG(INFO) << "Connection result for " + << remote_device.GetEndpointId() + << ": " << result.status.ToString(); + if (result.status.Ok()) { + SendFilesTo(remote_device); + } + }; + + listener.disconnected_cb = + [this](const nearby::NearbyDevice& remote_device) { + LOG(INFO) << "Disconnected from " << remote_device.GetEndpointId(); + }; + return listener; + } + + nearby::connections::v3::DiscoveryListener MakeDiscoveryListener() { + nearby::connections::v3::DiscoveryListener listener; + listener.endpoint_found_cb = + [this](const nearby::NearbyDevice& remote_device, + const absl::string_view service_id) { + LOG(INFO) << "Found endpoint " << remote_device.GetEndpointId() + << " service_id=" << service_id; + nearby::connections::ConnectionOptions options; + options.strategy = nearby::connections::Strategy::kP2pCluster; + options.allowed = options_.upgrade_mediums_set + ? options_.upgrade_mediums + : options_.mediums; + options.auto_upgrade_bandwidth = true; + auto device = CacheDiscoveredDevice(remote_device); + // disable advertising + core_ -> StopAdvertisingV3( + [](nearby::connections::Status status) { + LOG(INFO) << "StopAdvertising status: " << status.ToString(); + }); + core_->RequestConnectionV3( + local_device_, *device, options, MakeConnectionListener(), + [](nearby::connections::Status status) { + LOG(INFO) << "RequestConnection status: " << status.ToString(); + }); + }; + listener.endpoint_lost_cb = + [](const nearby::NearbyDevice& remote_device) { + LOG(INFO) << "Lost endpoint " << remote_device.GetEndpointId(); + }; + return listener; + } + + nearby::connections::v3::PayloadListener MakePayloadListener() { + nearby::connections::v3::PayloadListener listener; + listener.payload_received_cb = + [this](const nearby::NearbyDevice& remote_device, + nearby::connections::Payload payload) { + LOG(INFO) << "Payload received from " + << remote_device.GetEndpointId() + << " id=" << payload.GetId(); + if (payload.GetType() == nearby::connections::PayloadType::kFile) { + if (auto* file = payload.AsFile()) { + std::lock_guard lock(incoming_mutex_); + incoming_files_[payload.GetId()] = file->GetFilePath(); + } + } + }; + + listener.payload_progress_cb = + [this](const nearby::NearbyDevice& remote_device, + const nearby::connections::PayloadProgressInfo& info) { + if (info.status == + nearby::connections::PayloadProgressInfo::Status::kSuccess) { + std::string path; + { + std::lock_guard lock(incoming_mutex_); + auto it = incoming_files_.find(info.payload_id); + if (it != incoming_files_.end()) { + path = it->second; + incoming_files_.erase(it); + } + } + if (!path.empty()) { + LOG(INFO) << "Received file from " + << remote_device.GetEndpointId() << " path=" << path; + } else { + LOG(INFO) << "Received file from " + << remote_device.GetEndpointId() + << " payload_id=" << info.payload_id; + } + } else if (info.status == + nearby::connections::PayloadProgressInfo::Status::kFailure) { + LOG(WARNING) << "Payload failed from " + << remote_device.GetEndpointId() + << " payload_id=" << info.payload_id; + } + }; + return listener; + } + + std::optional BuildFilePayload( + const std::string& path) { + std::error_code error; + std::filesystem::path fs_path(path); + if (!std::filesystem::exists(fs_path, error)) { + LOG(ERROR) << "File does not exist: " << path; + return std::nullopt; + } + auto size = std::filesystem::file_size(fs_path, error); + if (error) { + LOG(ERROR) << "Failed to get file size: " << path + << " error=" << error.message(); + return std::nullopt; + } + std::string file_name = fs_path.filename().string(); + if (file_name.empty()) { + LOG(ERROR) << "Invalid file name: " << path; + return std::nullopt; + } + nearby::InputFile input_file(path, static_cast(size)); + return nearby::connections::Payload("", file_name, std::move(input_file)); + } + + void SendFilesTo(const nearby::NearbyDevice& remote_device) { + if (options_.send_paths.empty()) { + return; + } + const std::string endpoint_id = remote_device.GetEndpointId(); + { + std::lock_guard lock(sent_mutex_); + if (!sent_to_.insert(endpoint_id).second) { + return; + } + } + for (const auto& path : options_.send_paths) { + auto payload = BuildFilePayload(path); + if (!payload.has_value()) { + continue; + } + core_->SendPayloadV3( + remote_device, std::move(payload.value()), + [endpoint_id](nearby::connections::Status status) { + LOG(INFO) << "SendPayload to " << endpoint_id + << " status=" << status.ToString(); + }); + } + } + + std::shared_ptr + CacheDiscoveredDevice(const nearby::NearbyDevice& remote_device) { + const std::string endpoint_id = remote_device.GetEndpointId(); + std::lock_guard lock(discovered_mutex_); + auto it = discovered_devices_.find(endpoint_id); + if (it != discovered_devices_.end()) { + return it->second; + } + std::string endpoint_info; + if (remote_device.GetType() == + nearby::NearbyDevice::Type::kConnectionsDevice) { + auto* connections_device = + dynamic_cast( + &remote_device); + if (connections_device != nullptr) { + endpoint_info = connections_device->GetEndpointInfo(); + } + } + auto device = std::make_shared( + endpoint_id, endpoint_info, remote_device.GetConnectionInfos()); + discovered_devices_.emplace(endpoint_id, device); + return device; + } + + Options options_; + std::unique_ptr router_; + std::unique_ptr core_; + nearby::connections::v3::ConnectionsDevice local_device_; + std::mutex discovered_mutex_; + std::unordered_map> + discovered_devices_; + std::mutex incoming_mutex_; + std::unordered_map incoming_files_; + std::mutex sent_mutex_; + std::unordered_set sent_to_; +}; + +int main(int argc, char** argv) { + std::srand(static_cast(std::time(nullptr))); + + Options options; + bool mediums_override = false; + + for (int i = 1; i < argc; ++i) { + std::string_view arg(argv[i]); + if (arg == "--advertise" || arg == "--advert") { + options.advertise = true; + continue; + } + if (arg == "--discover" || arg == "--scan") { + options.discover = true; + continue; + } + if (arg == "-h" || arg == "--help") { + PrintUsage(argv[0]); + return 0; + } + std::string value; + if (ReadValueFlag(arg, "--mediums", &i, argc, argv, &value)) { + std::string error; + nearby::connections::BooleanMediumSelector selector; + if (!ParseMediums(value, &selector, &error)) { + std::cerr << "Error: " << error << "\n"; + return 2; + } + options.mediums = selector; + mediums_override = true; + continue; + } + if (ReadValueFlag(arg, "--upgrade_mediums", &i, argc, argv, &value)) { + std::string error; + nearby::connections::BooleanMediumSelector selector; + if (!ParseMediums(value, &selector, &error)) { + std::cerr << "Error: " << error << "\n"; + return 2; + } + options.upgrade_mediums = selector; + options.upgrade_mediums_set = true; + continue; + } + if (ReadValueFlag(arg, "--send", &i, argc, argv, &value)) { + if (value.empty()) { + std::cerr << "Error: --send requires a path\n"; + return 2; + } + options.send_paths.push_back(value); + continue; + } + if (ReadValueFlag(arg, "--save_dir", &i, argc, argv, &value)) { + options.save_dir = value; + continue; + } + if (ReadValueFlag(arg, "--service_id", &i, argc, argv, &value)) { + options.service_id = value; + continue; + } + std::cerr << "Unknown argument: " << arg << "\n"; + PrintUsage(argv[0]); + return 2; + } + + if (!options.advertise && !options.discover) { + std::cerr << "Error: specify at least one of --advertise or --discover\n"; + PrintUsage(argv[0]); + return 2; + } + + if (mediums_override && !options.mediums.Any(true)) { + std::cerr << "Error: no mediums enabled\n"; + return 2; + } + if (options.upgrade_mediums_set && !options.upgrade_mediums.Any(true)) { + std::cerr << "Error: no upgrade mediums enabled\n"; + return 2; + } + + struct sigaction action {}; + action.sa_sigaction = QuitHandler; + action.sa_flags = SA_SIGINFO; + sigaction(SIGINT, &action, nullptr); + sigaction(SIGTERM, &action, nullptr); + + FileShareApp app(std::move(options)); + app.Start(); + + g_shutdown.WaitForNotification(); + LOG(INFO) << "Shutting down"; + return 0; +} diff --git a/internal/platform/implementation/linux/BUILD b/internal/platform/implementation/linux/BUILD index 3b20885a..8eec001e 100644 --- a/internal/platform/implementation/linux/BUILD +++ b/internal/platform/implementation/linux/BUILD @@ -1,66 +1,135 @@ +# 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"]) + +load("@hedron_compile_commands//:refresh_compile_commands.bzl", "refresh_compile_commands") + +refresh_compile_commands( + name = "refresh_compile_commands", + + # Specify the targets of interest. + # For example, specify a dict of targets and any flags required to build. + targets = { + ":linux": "-s --check_visibility=false --spawn_strategy=standalone --verbose_failures --strip=never --copt=-O0 --copt=-g --copt=-fno-omit-frame-pointer", + "//connections:core": "-s --check_visibility=false --spawn_strategy=standalone --verbose_failures --strip=never --copt=-O0 --copt=-g --copt=-fno-omit-frame-pointer", + "//connections/file_share:file_share": "-s --check_visibility=false --spawn_strategy=standalone --verbose_failures --strip=never --copt=-O0 --copt=-g --copt=-fno-omit-frame-pointer", + }, + # No need to add flags already in .bazelrc. They're automatically picked up. + # If you don't need flags, a list of targets is also okay, as is a single target string. + # Wildcard patterns, like //... for everything, *are* allowed here, just like a build. + # As are additional targets (+) and subtractions (-), like in bazel query https://docs.bazel.build/versions/main/query.html#expressions + # And if you're working on a header-only library, specify a test or binary target that compiles it. +) + 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", ], - hdrs = [ - "atomics.h", - "bluetooth_adapter.h", - "device_info.h", - "generated/bluez_adapter_client_glue.h", - "multi_thread_executor.h", - "platform.h", - "scheduled_executor.h", - ], - visibility = ["//visibility:private"], + copts = ["-lrt"], + visibility = ["//third_party/nearby/sharing/internal/impl/linux:__pkg__"], deps = [ - "//internal/base:file_path", - "//internal/platform:base", - "//internal/platform:logging", + ":comm", "//internal/platform/implementation:types", "@com_google_absl//absl/strings", - "@sdbus_cpp", "@sdbus_cpp//:libsystemd", + "@sdbus_cpp//:sdbus_cpp", ], ) cc_library( - name = "linux", - srcs = [ - "bluetooth_adapter.cc", - "platform.cc", - "scheduled_executor.cc", - "system_clock.cc", - ], + 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", + "ble_v2_socket.h", "bluetooth_adapter.h", - "device_info.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_device.h", +# "bluez_agent.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_profile.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", ], - visibility = ["//visibility:public"], deps = [ - ":crypto_impl", - ":types", - "//connections/implementation/flags:connections_flags", - "//internal/base:file_path", - "//internal/base:files", - "//internal/flags:nearby_flags", + ":crypto", "//internal/platform:base", - "//internal/platform:cancellation_flag", - "//internal/platform:logging", - "//internal/platform:mac_address", + "//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:wifi_utils", - "//internal/platform/implementation/shared:count_down_latch", - "//internal/platform/implementation/shared:posix_condition_variable", - "//internal/platform/implementation/shared:posix_mutex", + "//internal/platform/implementation/linux/generated:types", "@com_google_absl//absl/base:core_headers", - "@com_google_absl//absl/base:nullability", "@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", @@ -69,75 +138,173 @@ cc_library( "@com_google_absl//absl/synchronization", "@com_google_absl//absl/time", "@com_google_absl//absl/types:optional", - "@com_google_absl//absl/types:span", - "@nlohmann_json//:json", - "@sdbus_cpp", "@sdbus_cpp//:libsystemd", + "@sdbus_cpp//:sdbus_cpp", ], -) - -# The existing target that other BUILD files select on; keep for compatibility. -cc_library( - name = "linux_platform_impl", - srcs = [], - hdrs = [], - visibility = ["//visibility:public"], - deps = [ - ":linux", - ":types", - ], + visibility = ["//visibility:private"], ) cc_library( - name = "multi_thread_executor_hdrs", - hdrs = ["multi_thread_executor.h"], - visibility = ["//visibility:public"], - deps = [ - "//internal/platform/implementation:types", - ], -) - -cc_test( - name = "bluetooth_adapter_test", + name = "crypto", srcs = [ - "bluetooth_adapter_test.cc", + "crypto.cc", ], + visibility = ["//visibility:public"], deps = [ - ":linux", - ":types", "//internal/platform:base", - "//internal/platform/implementation:platform", + "//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_medium.cc", + "ble_v2_medium.cc", + "ble_v2_server_socket.cc", + "ble_v2_socket.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_agent.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", + ], + linkopts = ["-lcurl"], + 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_googletest//:gtest_main", - "@sdbus_cpp", + "@com_google_absl//absl/types:optional", + "@nlohmann_json//:json", "@sdbus_cpp//:libsystemd", + "@sdbus_cpp//:sdbus_cpp", ], ) cc_library( - name = "crypto_impl", - srcs = ["crypto.cc"], - linkopts = ["-lcrypto"], # usually enough; -lssl not needed for hashing + 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/implementation:types", - "@com_google_absl//absl/strings", + "//internal/platform:base", + "@nlohmann_json//:json", + ":types", ], ) cc_test( - name = "multi_thread_executor_test", + name = "impl_test", + size = "small", srcs = [ - "multi_thread_executor_test.cc", + "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 = [ - # Depend on the header-only target to pick up the linux header without - # pulling system libraries. - ":multi_thread_executor_hdrs", + ":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/atomics.h b/internal/platform/implementation/linux/atomics.h deleted file mode 100644 index 2c6f1f1d..00000000 --- a/internal/platform/implementation/linux/atomics.h +++ /dev/null @@ -1,49 +0,0 @@ -// filepath: /workspace/internal/platform/implementation/linux/atomics.h -// -// Created by root on 10/2/25. -// -#ifndef LINUX_ATOMIC_BOOLEAN_H -#define LINUX_ATOMIC_BOOLEAN_H -#include -#include "internal/platform/implementation/atomic_boolean.h" -#include "internal/platform/implementation/atomic_reference.h" -namespace nearby -{ - namespace linux - { - // A boolean value that may be updated atomically. - class AtomicBoolean : public api::AtomicBoolean - { - public: - explicit AtomicBoolean(bool value = false) : atomic_boolean_(value) - { - } - ~AtomicBoolean() override = default; - // Atomically read and return current value. - [[nodiscard]] 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; - }; - - class AtomicUint32 : public api::AtomicUint32 - { - public: - explicit AtomicUint32(std::uint32_t value = 0) : atomic_uint32_(value) {} - ~AtomicUint32() override = default; - - // Atomically reads and returns stored value. - [[nodiscard]] std::uint32_t Get() const override { return atomic_uint32_.load(); } - - // Atomically stores value. - void Set(std::uint32_t value) override { atomic_uint32_.store(value); } - - private: - std::atomic atomic_uint32_{0}; - }; - } // namespace api -} // namespace nearby -#endif //LINUX_ATOMIC_BOOLEAN_H diff --git a/internal/platform/implementation/linux/avahi.cc b/internal/platform/implementation/linux/avahi.cc new file mode 100644 index 00000000..86517920 --- /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) { + LOG(INFO) << __func__ << ": " << getObjectPath() + << ": Found new item through the ServiceBrowser: " + << "interface: " << interface << ", protocol: " + << protocol << ", name: '" << name << "', type: '" + << type << "', domain: '" << domain + << "', flags: " << flags; + if (flags & kAvahiLookupResultLocal) { + LOG(INFO) << __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 + "."); // discovery callback expects an extra period at t + 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) { + LOG(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? + LOG(INFO) << __func__ << ": " << getObjectPath() + << ": Item removed through the ServiceBrowser: " + << "interface: " << interface << ", protocol: " + << protocol << ", name: '" << name << "', type: '" + << type << "', domain: '" << domain + << "', flags: " << flags; + if (flags & kAvahiLookupResultLocal) { + LOG(INFO) << __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) { + LOG(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) { + LOG(ERROR) << __func__ << ": " << getObjectPath() + << ": ServiceBrowser reported a failure: " << error; +} + +void ServiceBrowser::onAllForNow() { + LOG(INFO) << __func__ << ": " << getObjectPath() + << ": notified via ServiceBrowser that all records have " + "been added for now"; +} + +void ServiceBrowser::onCacheExhausted() { + LOG(INFO) << __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..4dc61db9 --- /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() { + LOG(INFO) << __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() { + LOG(INFO) << __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..e7b02732 --- /dev/null +++ b/internal/platform/implementation/linux/ble_gatt_client.cc @@ -0,0 +1,456 @@ +// 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) { + LOG(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) { + LOG(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) { + LOG(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; + + auto iface_it = ifaces.find(org::bluez::GattCharacteristic1_proxy::INTERFACE_NAME); + if (iface_it == ifaces.end()) { + // Not a GattCharacteristic1 object (or interfaces map incomplete) -> skip + continue; + } + + const auto& properties = iface_it->second; + + 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)); + + LOG(INFO) << __func__ << ": Finished discovering gatt services and characteristics"; + 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()) { + LOG(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()) { + LOG(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 &char_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()) { + LOG(ERROR) << ": Couldn't parse UUID '" << chr_uuid_str + << "' in characteristic " << char_path; + return std::nullopt; + } + + const sdbus::ObjectPath &service_path = properties.at("Service"); + if (cached_services_.count(service_path) == 0) { + cached_services_.emplace( + service_path, std::make_unique(system_bus_, service_path)); + } + +auto it = cached_services_.find(service_path); +if (it == cached_services_.end() || it->second == nullptr) { + LOG(ERROR) << ": cached_services_ missing service " << service_path + << " (from characteristic " << char_path << ")"; + return std::nullopt; +} + + LOG(INFO) << ": Found service path " << service_path + << " (from characteristic " << char_path << ")"; + auto* service = it->second.get(); // service is GattServiceClient* +nearby::Uuid service_uuid; +try { + std::string service_uuid_str = service->UUID(); // copy (safe) + auto service_uuid_maybe = UuidFromString(service_uuid_str); + + if (!service_uuid_maybe.has_value()) { + LOG(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 it = characteristics_properties_.find(objectPath); + if (it == characteristics_properties_.end()) { + // Not tracked / already removed / never added. + // return; // or just `break;` / `continue;` depending on your context + return; + } + + auto &props = it->second; + 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..452b79a0 --- /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 &char_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..1c94361c --- /dev/null +++ b/internal/platform/implementation/linux/ble_gatt_server.cc @@ -0,0 +1,156 @@ +// 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) { + LOG(ERROR) + << __func__ + << ": error emitting InterfacesAdded signal for object path " + << service->getObjectPath() << " with name '" << e.getName() + << "' and message '" << e.getMessage() << "'"; + return std::nullopt; + } + auto profile = std::make_unique (system_bus_, bluez::gatt_profile_object_path( + std::string(service_uuid)), std::string(service_uuid)); + profile -> emitInterfacesAddedSignal(); + + if (service->AddCharacteristic(service_uuid, characteristic_uuid, permission, + property)) { + try { + LOG(INFO)<< __func__ << ": Registering service on gattmanager with characteristic_uuid: " + << std::string(characteristic_uuid) << " and service_uuid: " << std::string(service_uuid); + + gatt_manager_ -> RegisterApplication(gatt_service_root_object_manager -> getObjectPath(), {}); + } catch (const sdbus::Error& e) { + LOG(ERROR) + << __func__ + << ": error calling RegisterAplication for GattManager with object path " + << gatt_manager_->getObjectPath() << " with name '" << e.getName() + << "' and message '" << e.getMessage() << "'"; + 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) { + LOG(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) { + LOG(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_) { + LOG(INFO) << __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..65a64390 --- /dev/null +++ b/internal/platform/implementation/linux/ble_gatt_server.h @@ -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. + +#ifndef PLATFORM_IMPL_LINUX_API_BLE_GATT_SERVER_H_ +#define PLATFORM_IMPL_LINUX_API_BLE_GATT_SERVER_H_ + +#include +#include + +#include + +#include "bluez_gatt_manager.h" +#include "bluez_gatt_profile.h" +#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) { + // temp fix till everything transitions to GGetAddress() + unique_id_ = std::stoull(std::regex_replace(adapter_.GetMacAddress(), + std::regex("[:\\-]"), ""), nullptr, 16); + } + + 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_), + gatt_service_root_object_manager(std::make_unique(system_bus_, "/com/google/nearby/medium/ble/gatt")), + gatt_manager_(std::make_unique(system_bus_, adapter_.GetObjectPath())), + server_cb_(std::make_shared( + std::move(server_cb))) {} + ~GattServer() override = default; + + 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::unique_ptr gatt_service_root_object_manager; + absl::Mutex profiles_mutex_; + absl::flat_hash_map> gatt_profiles_; + ABSL_GUARDED_BY(profiles_mutex_) + std::unique_ptr gatt_manager_; + 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_l2cap_server_socket.cc b/internal/platform/implementation/linux/ble_l2cap_server_socket.cc new file mode 100644 index 00000000..e6c252ed --- /dev/null +++ b/internal/platform/implementation/linux/ble_l2cap_server_socket.cc @@ -0,0 +1,184 @@ +// Copyright 2024 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_l2cap_server_socket.h" + +#include +#include +#include +#include + +#include +#include + +#include "internal/platform/logging.h" +#include "internal/platform/prng.h" + +namespace nearby { +namespace linux { + +BleL2capServerSocket::BleL2capServerSocket() : psm_(0) {} + +BleL2capServerSocket::BleL2capServerSocket(int psm) : psm_(psm) { +} + +BleL2capServerSocket::~BleL2capServerSocket() { Close(); } + +void BleL2capServerSocket::SetPSM(int psm) { psm_ = psm; } + +std::unique_ptr BleL2capServerSocket::Accept() { + if (stopped_.Cancelled()) { + LOG(ERROR) << __func__ << ": server socket has been stopped"; + return nullptr; + } + + absl::MutexLock lock(&mutex_); + + Prng prng; + psm_ = 0x80 + (prng.NextUint32() % 0x80); + + server_fd_ = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); + int rcv = 1 << 20; // 1 MiB (kernel may clamp) + int snd = 1 << 20; + int err2 = setsockopt(server_fd_, SOL_SOCKET, SO_RCVBUF, &rcv, sizeof(rcv)); + int err3 = setsockopt(server_fd_, SOL_SOCKET, SO_SNDBUF, &snd, sizeof(snd)); + + LOG(INFO) << __func__ << ": Using server_fd: " << server_fd_; + if (server_fd_ < 0 or err2 == -1 or err3 == -1) { + LOG(ERROR) << "Failed to create L2CAP server socket: " + << std::strerror(errno); + return nullptr; + } + + struct sockaddr_l2 addr; + std::memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + addr.l2_psm = htobs(psm_); + addr.l2_cid = 0; + addr.l2_bdaddr_type = BDADDR_LE_PUBLIC; + // Set BDADDR_ANY (all zeros) + std::memset(&addr.l2_bdaddr, 0, sizeof(addr.l2_bdaddr)); + + if (bind(server_fd_, (struct sockaddr*)&addr, sizeof(addr)) < 0) { + LOG(ERROR) << "Failed to bind L2CAP server socket: " + << std::strerror(errno) << " (errno: " << errno << ")"; + close(server_fd_); + server_fd_ = -1; + return nullptr; + } + + if (listen(server_fd_, 5) < 0) { + LOG(ERROR) << "Failed to listen on L2CAP server socket: " + << std::strerror(errno); + close(server_fd_); + server_fd_ = -1; + return nullptr; + } + + socklen_t addr_len = sizeof(addr); + if (getsockname(server_fd_, (struct sockaddr*)&addr, &addr_len) == 0) { + psm_ = btohs(addr.l2_psm); + LOG(INFO) << "L2CAP server socket listening on PSM: " << psm_; + } else { + LOG(ERROR) << "Failed to get socket name: " << std::strerror(errno); + } + // // Return cached socket if it exists + // auto it = accepted_fds_.find(server_fd_); + // if (it != accepted_fds_.end()) { + // LOG(INFO) << __func__ << ": Socket exists. Returning cached socket"; + // return std::make_unique(it->second.first, it->second.second); // I hate how this looks + // } + + if (server_fd_ < 0) { + LOG(ERROR) << "Server socket not initialized"; + return nullptr; + } + + // Release the mutex while waiting for incoming connection + mutex_.Unlock(); + + struct sockaddr_l2 client_addr; + socklen_t client_len = sizeof(client_addr); + std::memset(&client_addr, 0, sizeof(client_addr)); + + LOG(INFO) << "Waiting for L2CAP connection on PSM " << psm_ << "..."; + int client_fd = accept(server_fd_, (struct sockaddr*)&client_addr, &client_len); + + // Re-acquire the mutex + mutex_.Lock(); + + if (client_fd < 0) { + if (errno == EINTR || errno == EAGAIN) { + LOG(WARNING) << "Accept interrupted, returning nullptr"; + return nullptr; + } + LOG(ERROR) << "Failed to accept L2CAP connection: " << std::strerror(errno); + return nullptr; + } + + if (closed_) { + close(client_fd); + return nullptr; + } + + char client_addr_str[18]; + ba2str(&client_addr.l2_bdaddr, client_addr_str); + LOG(INFO) << "Accepted L2CAP connection from " << client_addr_str + << " on PSM " << btohs(client_addr.l2_psm); + + LOG(INFO) << __func__ << ": Connected to client_fd: " << client_fd; + // Create a unique ID from the MAC address + api::ble_v2::BlePeripheral::UniqueId peripheral_id = 0; + for (int i = 0; i < 6; i++) { + peripheral_id = (peripheral_id << 8) | client_addr.l2_bdaddr.b[i]; + } + + accepted_fds_.emplace(server_fd_, std::pair(client_fd, peripheral_id)); + return std::make_unique(client_fd, peripheral_id); +} + +Exception BleL2capServerSocket::Close() { + LOG(ERROR) << __func__ << ": closing bluetooth server socket"; + stopped_.Cancel(); + + return DoClose(); +} + +Exception BleL2capServerSocket::DoClose() { + closed_ = true; + + if (server_fd_ >= 0) { + shutdown(server_fd_, SHUT_RDWR); + close(server_fd_); + server_fd_ = -1; + } + + if (close_notifier_) { + auto notifier = std::move(close_notifier_); + mutex_.Unlock(); + notifier(); + mutex_.Lock(); + } + + return {Exception::kSuccess}; +} + +void BleL2capServerSocket::SetCloseNotifier( + absl::AnyInvocable notifier) { + absl::MutexLock lock(&mutex_); + close_notifier_ = std::move(notifier); +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/ble_l2cap_server_socket.h b/internal/platform/implementation/linux/ble_l2cap_server_socket.h new file mode 100644 index 00000000..8fb42433 --- /dev/null +++ b/internal/platform/implementation/linux/ble_l2cap_server_socket.h @@ -0,0 +1,65 @@ +// Copyright 2024 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_BLE_L2CAP_SERVER_SOCKET_H_ +#define PLATFORM_IMPL_LINUX_BLE_L2CAP_SERVER_SOCKET_H_ + +#include +#include + +#include "absl/functional/any_invocable.h" +#include "absl/synchronization/mutex.h" +#include "internal/platform/exception.h" +#include "internal/platform/implementation/ble.h" +#include "internal/platform/implementation/ble_v2.h" +#include "internal/platform/implementation/linux/ble_l2cap_socket.h" +#include "absl/container/flat_hash_map.h" +namespace nearby { +namespace linux { + +class BleL2capServerSocket final : public api::ble_v2::BleL2capServerSocket { + public: + BleL2capServerSocket(); + explicit BleL2capServerSocket(int psm); + ~BleL2capServerSocket() override; + + int GetPSM() const override { return psm_; } + void SetPSM(int psm); + + std::unique_ptr Accept() override + ABSL_LOCKS_EXCLUDED(mutex_); + Exception Close() override ABSL_LOCKS_EXCLUDED(mutex_); + + void SetCloseNotifier(absl::AnyInvocable notifier) + ABSL_LOCKS_EXCLUDED(mutex_); + + private: + Exception DoClose() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + mutable absl::Mutex mutex_; + absl::CondVar cond_; + absl::AnyInvocable close_notifier_ ABSL_GUARDED_BY(mutex_); + bool closed_ ABSL_GUARDED_BY(mutex_) = false; + int psm_ = 0; + int server_fd_ ABSL_GUARDED_BY(mutex_) = -1; + + CancellationFlag stopped_; + // > + absl::flat_hash_map> accepted_fds_ ABSL_GUARDED_BY(mutex_); +}; + +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPL_LINUX_BLE_L2CAP_SERVER_SOCKET_H_ diff --git a/internal/platform/implementation/linux/ble_l2cap_socket.cc b/internal/platform/implementation/linux/ble_l2cap_socket.cc new file mode 100644 index 00000000..555cdbc5 --- /dev/null +++ b/internal/platform/implementation/linux/ble_l2cap_socket.cc @@ -0,0 +1,364 @@ +// Copyright 2024 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_l2cap_socket.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "absl/strings/escaping.h" +#include "absl/strings/string_view.h" +#include "internal/platform/byte_array.h" +#include "internal/platform/exception.h" +#include "internal/platform/logging.h" +#include + +#include "bluetooth_classic_socket.h" + +namespace nearby { +namespace linux { + +namespace { +constexpr size_t kLogMaxBytes = 64; +constexpr size_t kDefaultBleL2capMtu = 23; + +size_t GetSocketMtu(int fd, int option_name) { + uint16_t mtu = 0; + socklen_t len = sizeof(mtu); + if (getsockopt(fd, SOL_BLUETOOTH, option_name, &mtu, &len) == 0 && + mtu > 0) { + return static_cast(mtu); + } + return 0; +} + +std::string HexPreview(const char* data, size_t size) { + size_t count = std::min(size, kLogMaxBytes); + std::string hex = + absl::BytesToHexString(absl::string_view(data, count)); + if (size > count) { + hex.append("..."); + } + return hex; +} + +size_t GetL2capOutputMtu(int fd) { + struct l2cap_options opts; + size_t mtu = GetSocketMtu(fd, BT_SNDMTU); + if (mtu > 0) { + return mtu; + } + return kDefaultBleL2capMtu; +} + +Exception PollSocket(int fd, short event) { + struct pollfd fds[1]; + fds[0].fd = fd; + fds[0].events = event; + + while (true) { + int ret = poll(fds, 1, -1); + if (ret < 0) { + if (errno == EINTR) continue; + LOG(ERROR) << "Error polling L2CAP socket: " << std::strerror(errno); + return {Exception::kIo}; + } + if ((fds[0].revents & event) != 0) { + return {Exception::kSuccess}; + } + if ((fds[0].revents & POLLHUP) != 0) { + LOG(ERROR) << "L2CAP socket disconnected"; + return {Exception::kIo}; + } + if ((fds[0].revents & (POLLERR | POLLNVAL)) != 0) { + LOG(ERROR) << "Error occurred on L2CAP socket"; + return {Exception::kIo}; + } + } +} +} // namespace + +BleL2capInputStream::BleL2capInputStream(int fd) : fd_(fd) {} + +BleL2capInputStream::~BleL2capInputStream() { Close(); } + +static size_t GetBleCocRcvMtu(int fd) { + // Prefer BT_RCVMTU for LE CoC, but fall back to L2CAP_OPTIONS if needed. + uint16_t mtu = 0; + socklen_t len = sizeof(mtu); + if (getsockopt(fd, SOL_BLUETOOTH, BT_RCVMTU, &mtu, &len) == 0 && mtu > 0) + return mtu; + + // Fallback ONLY (avoid 23 unless you're sure; 23 causes truncation if peer sends bigger SDUs). + return 512; +} + + static size_t NextPacketSize(int fd) { + int pending = 0; + if (ioctl(fd, FIONREAD, &pending) == 0 && pending > 0) { + return static_cast(pending); + } + return 0; // unknown +} +ExceptionOr BleL2capInputStream::Read(std::int64_t size) { + int fd = fd_.load(); + if (fd < 0) return Exception{Exception::kIo}; + + auto poller = Poller::CreateInputPoller(fd); + + // Wait for readability + while (true) { + if (fd_.load() != fd) return {Exception::kIo}; + auto result = poller.Ready(); + if (result.Raised()) return result; + if (fd_.load() != fd) return {Exception::kIo}; + + // Peek the next message length without consuming it. + // For seqpacket/dgram, MSG_TRUNC makes recv() return the *full* message length + // even if the buffer is smaller. + ssize_t msg_len = ::recv(fd, nullptr, 0, MSG_PEEK | MSG_TRUNC); + if (msg_len < 0) { + if (errno == EINTR) continue; + if (errno == EAGAIN || errno == EWOULDBLOCK) continue; + if (errno == EBADF) { + LOG(INFO) << __func__ << ": socket was closed during read"; + return {Exception::kIo}; + } + LOG(ERROR) << __func__ << ": error peeking message length: " + << std::strerror(errno); + return {Exception::kIo}; + } + if (msg_len == 0) { + LOG(INFO) << __func__ << ": socket closed (EOF)"; + return {Exception::kIo}; + } + + // Decide how much we will actually read/return. + // If caller asked for 'size', cap to that. + size_t want = static_cast(msg_len); + size_t cap = static_cast(size); + size_t to_read = std::min(want, cap); + + std::string buffer; + buffer.resize(to_read); + + // Now read/consume the message. If the message is larger than to_read, + // the remainder will be discarded by the kernel for seqpacket/dgram. + // We can detect that and treat it as an error (or choose a different policy). + if (fd_.load() != fd) return {Exception::kIo}; + ssize_t n = ::recv(fd, buffer.data(), to_read, 0); + if (n < 0) { + if (errno == EINTR) continue; + if (errno == EAGAIN || errno == EWOULDBLOCK) continue; + if (errno == EBADF) { + LOG(INFO) << __func__ << ": socket was closed during read"; + return {Exception::kIo}; + } + LOG(ERROR) << __func__ << ": error reading data on bluetooth socket: " + << std::strerror(errno); + return {Exception::kIo}; + } + if (n == 0) { + LOG(INFO) << __func__ << ": socket closed (EOF)"; + return {Exception::kIo}; + } + + buffer.resize(static_cast(n)); + + // Detect truncation: if msg_len > size, we truncated/discarded remainder. + if (want > cap) { + LOG(ERROR) << __func__ + << ": incoming packet (" << want + << " bytes) exceeds requested size (" << cap + << "). Packet truncated."; + return {Exception::kIo}; + } + + return ExceptionOr{ByteArray(std::move(buffer))}; + } +} + +Exception BleL2capInputStream::Close() { + int fd = fd_.exchange(-1); + if (fd < 0) return {Exception::kSuccess}; // Already closed + ::shutdown(fd, SHUT_RDWR); + return {Exception::kSuccess}; +} + +BleL2capOutputStream::BleL2capOutputStream(int fd) : fd_(fd) {} + +BleL2capOutputStream::~BleL2capOutputStream() { Close(); } + +Exception BleL2capOutputStream::Write(const ByteArray& data) { + int fd = fd_.load(); + if (fd < 0) return Exception{Exception::kIo}; + + auto poller = Poller::CreateOutputPoller(fd); + + size_t total_wrote = 0; + + if (data.Empty()) { + return {Exception::kSuccess}; + } + size_t max_chunk_size = GetL2capOutputMtu(fd); + if (max_chunk_size == 0) { + max_chunk_size = kDefaultBleL2capMtu; + } + LOG(INFO) << "BleL2capOutputStream::Write bytes=" << data.size() + << " mtu=" << max_chunk_size << " data=0x" + << HexPreview(data.data(), data.size()); + while (total_wrote < data.size()) { + if (fd_.load() != fd) return {Exception::kIo}; + auto result = poller.Ready(); // should wait for POLLOUT/EPOLLOUT + if (result.Raised()) return result; + if (fd_.load() != fd) return {Exception::kIo}; + + const char *buf = data.data(); + size_t remaining = data.size() - total_wrote; + + size_t to_write = remaining; + { + // For SEQPACKET/DGRAM, one send() == one packet. + // Cap to discovered “MTU-like” limit to avoid EMSGSIZE. + absl::MutexLock lock(&fd_mutex_); + to_write = std::min(to_write, max_chunk_size); + } + + // Prefer send() to avoid SIGPIPE (MSG_NOSIGNAL is Linux). + ssize_t wrote = ::send(fd, + buf + total_wrote, + to_write, +#ifdef MSG_NOSIGNAL + MSG_NOSIGNAL +#else + 0 +#endif + ); + + // If send() isn’t appropriate in your environment, you can swap back to write(). + // ssize_t wrote = ::write(fd_.get(), buf + total_wrote, to_write); + + if (wrote < 0) { + if (errno == EINTR) continue; + if (errno == EAGAIN || errno == EWOULDBLOCK) continue; + + if (errno == EMSGSIZE) { + // Our packet is too large; shrink max_chunk_ and retry. + { + absl::MutexLock lock(&fd_mutex_); + if (max_chunk_size > 1) { + max_chunk_size = std::max(1, max_chunk_size / 2); + LOG(INFO) << __func__ << ": EMSGSIZE; reducing max_chunk_ to " + << max_chunk_size; + continue; // retry with smaller chunk + } + } + LOG(ERROR) << __func__ << ": EMSGSIZE even at 1 byte"; + return {Exception::kIo}; + } + + if (errno == EBADF || errno == EPIPE) { + LOG(INFO) << __func__ << ": socket was closed during write"; + return {Exception::kIo}; + } + + LOG(ERROR) << __func__ + << ": error writing data on bluetooth socket: " + << std::strerror(errno); + return {Exception::kIo}; + } + + if (wrote == 0) { + // For sockets, 0 usually means peer closed. + LOG(INFO) << __func__ << ": peer closed during write"; + return {Exception::kIo}; + } + + total_wrote += static_cast(wrote); + } + + return {Exception::kSuccess}; +} + +Exception BleL2capOutputStream::Close() { + int fd = fd_.exchange(-1); + if (fd < 0) return {Exception::kSuccess}; // Already closed + ::shutdown(fd, SHUT_RDWR); + return {Exception::kSuccess}; +} + +BleL2capSocket::BleL2capSocket(int fd, + api::ble_v2::BlePeripheral::UniqueId peripheral_id) + : peripheral_id_(peripheral_id), + input_stream_(std::make_unique(fd)), + output_stream_(std::make_unique(fd)) +{ + LOG(INFO) << "fd_ " << fd; + LOG(INFO) << "input_stream_ :" << input_stream_.get(); + LOG(INFO) << "output_stream_ :" << output_stream_.get(); + struct l2cap_options opts; + size_t snd_mtu = GetSocketMtu(fd, BT_SNDMTU); + size_t rcv_mtu = GetSocketMtu(fd, BT_RCVMTU); + LOG(INFO) << "BleL2capSocket MTU fallback snd_mtu=" << snd_mtu + << " rcv_mtu=" << rcv_mtu; +} + +BleL2capSocket::~BleL2capSocket() { Close(); } + +Exception BleL2capSocket::Close() { + absl::MutexLock lock(&mutex_); + if (closed_) { + return {Exception::kSuccess}; + } + DoClose(); + return {Exception::kSuccess}; +} + +void BleL2capSocket::DoClose() { + closed_ = true; + + if (input_stream_) { + input_stream_->Close(); + } + if (output_stream_) { + output_stream_->Close(); + } + + if (close_notifier_) { + auto notifier = std::move(close_notifier_); + mutex_.Unlock(); + notifier(); + mutex_.Lock(); + } +} + +void BleL2capSocket::SetCloseNotifier(absl::AnyInvocable notifier) { + absl::MutexLock lock(&mutex_); + close_notifier_ = std::move(notifier); +} + +bool BleL2capSocket::IsClosed() const { + absl::MutexLock lock(&mutex_); + return closed_; +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/ble_l2cap_socket.h b/internal/platform/implementation/linux/ble_l2cap_socket.h new file mode 100644 index 00000000..c5bd4489 --- /dev/null +++ b/internal/platform/implementation/linux/ble_l2cap_socket.h @@ -0,0 +1,88 @@ +// Copyright 2024 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_BLE_L2CAP_SOCKET_H_ +#define PLATFORM_IMPL_LINUX_BLE_L2CAP_SOCKET_H_ + +#include +#include + +#include "absl/functional/any_invocable.h" +#include "absl/synchronization/mutex.h" +#include "internal/platform/exception.h" +#include "internal/platform/implementation/ble_v2.h" +#include "internal/platform/input_stream.h" +#include "internal/platform/output_stream.h" + +namespace nearby { +namespace linux { + +class BleL2capInputStream final : public InputStream { + public: + explicit BleL2capInputStream(int fd); + ~BleL2capInputStream() override; + + ExceptionOr Read(std::int64_t size) override; + Exception Close() override; + + private: + std::atomic fd_{-1}; + std::string pending_; // holds unread bytes from full SDUs + }; + +class BleL2capOutputStream final : public OutputStream { + public: + explicit BleL2capOutputStream(int fd); + ~BleL2capOutputStream() override; + + Exception Write(const ByteArray& data) override; + Exception Flush() override { return {Exception::kSuccess}; } + Exception Close() override; + + private: + mutable absl::Mutex fd_mutex_; + std::atomic fd_{-1}; +}; + +class BleL2capSocket final : public api::ble_v2::BleL2capSocket { + public: + BleL2capSocket(int fd, api::ble_v2::BlePeripheral::UniqueId peripheral_id); + ~BleL2capSocket() override; + + InputStream& GetInputStream() override { return *input_stream_; } + OutputStream& GetOutputStream() override { return *output_stream_; } + Exception Close() override ABSL_LOCKS_EXCLUDED(mutex_); + void SetCloseNotifier(absl::AnyInvocable notifier) override + ABSL_LOCKS_EXCLUDED(mutex_); + api::ble_v2::BlePeripheral::UniqueId GetRemotePeripheralId() override { + return peripheral_id_; + } + + bool IsClosed() const ABSL_LOCKS_EXCLUDED(mutex_); + + private: + void DoClose() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + mutable absl::Mutex mutex_; + bool closed_ ABSL_GUARDED_BY(mutex_) = false; + std::unique_ptr input_stream_; + std::unique_ptr output_stream_; + api::ble_v2::BlePeripheral::UniqueId peripheral_id_; + absl::AnyInvocable close_notifier_ ABSL_GUARDED_BY(mutex_); +}; + +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPL_LINUX_BLE_L2CAP_SOCKET_H_ diff --git a/internal/platform/implementation/linux/ble_l2cap_socket_test.cc b/internal/platform/implementation/linux/ble_l2cap_socket_test.cc new file mode 100644 index 00000000..07107972 --- /dev/null +++ b/internal/platform/implementation/linux/ble_l2cap_socket_test.cc @@ -0,0 +1,117 @@ +// Copyright 2025 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_l2cap_socket.h" + +#include +#include + +#include + +#include "internal/platform/byte_array.h" +#include "internal/platform/exception.h" +#include "gtest/gtest.h" + +namespace nearby { +namespace linux { +namespace { + +class SocketPair final { + public: + SocketPair() { + int fds[2] = {-1, -1}; + ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, fds)); + left_ = fds[0]; + right_ = fds[1]; + } + + ~SocketPair() { + if (left_ >= 0) close(left_); + if (right_ >= 0) close(right_); + } + + int left() const { return left_; } + int right() const { return right_; } + + void CloseRight() { + if (right_ >= 0) { + close(right_); + right_ = -1; + } + } + + private: + int left_ = -1; + int right_ = -1; +}; + +TEST(BleL2capSocketTest, OutputStreamWritesData) { + SocketPair pair; + BleL2capSocket socket(pair.left(), /*peripheral_id=*/1); + + ByteArray payload("hello"); + Exception write_result = socket.GetOutputStream().Write(payload); + EXPECT_TRUE(write_result.Ok()); + + char buffer[5]; + ssize_t bytes_read = recv(pair.right(), buffer, sizeof(buffer), 0); + ASSERT_EQ(bytes_read, sizeof(buffer)); + EXPECT_EQ(std::string(buffer, sizeof(buffer)), "hello"); +} + +TEST(BleL2capSocketTest, InputStreamReadsData) { + SocketPair pair; + BleL2capSocket socket(pair.left(), /*peripheral_id=*/1); + + const char* payload = "world"; + ASSERT_EQ(send(pair.right(), payload, 5, 0), 5); + + ExceptionOr read_result = socket.GetInputStream().Read(5); + ASSERT_TRUE(read_result.ok()); + EXPECT_EQ(read_result.result().string_data(), "world"); +} + +TEST(BleL2capSocketTest, InputStreamReturnsEmptyOnPeerClose) { + SocketPair pair; + BleL2capSocket socket(pair.left(), /*peripheral_id=*/1); + + pair.CloseRight(); + ExceptionOr read_result = socket.GetInputStream().Read(4); + ASSERT_TRUE(read_result.ok()); + EXPECT_TRUE(read_result.result().Empty()); +} + +TEST(BleL2capSocketTest, OutputStreamWriteFailsAfterClose) { + SocketPair pair; + BleL2capSocket socket(pair.left(), /*peripheral_id=*/1); + + ASSERT_TRUE(socket.GetOutputStream().Close().Ok()); + Exception write_result = + socket.GetOutputStream().Write(ByteArray("data")); + EXPECT_EQ(write_result.value, Exception::kIo); +} + +TEST(BleL2capSocketTest, CloseNotifierInvoked) { + SocketPair pair; + BleL2capSocket socket(pair.left(), /*peripheral_id=*/1); + + bool notified = false; + socket.SetCloseNotifier([¬ified]() { notified = true; }); + ASSERT_TRUE(socket.Close().Ok()); + EXPECT_TRUE(notified); +} + +} // namespace +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/ble_medium.cc b/internal/platform/implementation/linux/ble_medium.cc new file mode 100644 index 00000000..2f4a3604 --- /dev/null +++ b/internal/platform/implementation/linux/ble_medium.cc @@ -0,0 +1,33 @@ +// 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_medium.h" + +#include +#include + +#include "absl/synchronization/mutex.h" +#include "internal/platform/implementation/ble.h" +#include "internal/platform/implementation/ble_v2.h" +#include "internal/platform/implementation/linux/ble_v2_medium.h" +#include "internal/platform/implementation/linux/bluetooth_adapter.h" +#include "internal/platform/logging.h" +#include "internal/platform/uuid.h" + +namespace nearby { +namespace linux { + + +} // namespace linux +} // namespace nearby \ No newline at end of file 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..31a50420 --- /dev/null +++ b/internal/platform/implementation/linux/ble_v2_medium.cc @@ -0,0 +1,612 @@ +// 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/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 "ble_gatt_client.h" +#include "ble_gatt_server.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/mac_address.h" +#include "internal/platform/prng.h" +#include "absl/types/span.h" +#include "internal/base/observer_list.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), + // gatt_discovery_(std::make_shared(system_bus_)), + observers_(std::make_shared>()), + devices_(std::make_unique( + system_bus_, adapter_.GetObjectPath(), *observers_)), + root_object_manager_(std::make_unique(*system_bus_, "/com/google/nearby/medium/ble/advertisement/monitor")), + adv_monitor_manager_( + bluez::AdvertisementMonitorManager:: + DiscoverAdvertisementMonitorManager(*system_bus_, adapter_)), + adv_manager_(std::make_unique(*system_bus_, + adapter)), + cur_adv_(nullptr) { + if (adv_monitor_manager_) { + LOG(INFO) + << __func__ + << ": Registering path /com/google/nearby/medium/ble/advertisement/monitor with AdvertisementMonitorManager at " + << adv_monitor_manager_->getObjectPath(); + try { + adv_monitor_manager_->RegisterMonitor(root_object_manager_->getObjectPath()); + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(adv_monitor_manager_, "RegisterMonitor", e); + } + } + // if (gatt_discovery_->InitializeKnownServices()) { + // LOG(ERROR) << __func__ + // << ": Could not initialize known GATT services"; + // } +} + + // sync api + // called twice. Once with extended regular advertisement ( when IsExtendedAdvertisementsAvailable() == true ) + // and another for GATT-backed header advertisement for legacy devices + bool BleV2Medium::StartAdvertising( + const api::ble_v2::BleAdvertisementData &advertising_data, + api::ble_v2::AdvertiseParameters advertise_set_parameters) { + if (!advertising_data.is_extended_advertisement) + { + // can't send two LE advertisements at the same + return true; + } + if (!adapter_.IsEnabled()) { + LOG(WARNING) << "BLE cannot start advertising because the " + "bluetooth adapter is not enabled."; + return false; + } + + if (advertising_data.service_data.empty()) { + LOG(WARNING) + << "BLE cannot start to advertise due to invalid service data."; + return false; + } + + absl::MutexLock l (&advs_mutex_); + advs_.push_front(bluez::LEAdvertisement::CreateLEAdvertisement( + *system_bus_, advertising_data, advertise_set_parameters)); + auto it = advs_.begin(); + + + LOG(INFO) << __func__ << ": Registering advertisement, is_extended: " << advertising_data.is_extended_advertisement + << " " << (*it) -> getObjectPath() << " on bluetooth adapter " + << adapter_.GetObjectPath(); + + try { + adv_manager_->RegisterAdvertisement((*it)->getObjectPath(), {}); + } catch (const sdbus::Error &e) { + advs_.erase(it); + DBUS_LOG_METHOD_CALL_ERROR(adv_manager_, "RegisterAdvertisement", e); + return false; + } + + return true; + } + +//async api +// this doesn't run. wonder why +std::unique_ptr +BleV2Medium::StartAdvertising( + const api::ble_v2::BleAdvertisementData &advertising_data, + api::ble_v2::AdvertiseParameters advertise_set_parameters, + AdvertisingCallback callback) { + if (!adapter_.IsEnabled()) { + LOG(WARNING) << ": BLE cannot start advertising because the " + "bluetooth adapter is not enabled."; + return nullptr; + } + + if (advertising_data.service_data.empty()) { + LOG(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]() { + LOG(INFO) << __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)}); +} + + bool BleV2Medium::StopAdvertising() { + absl::MutexLock l(&advs_mutex_); + try { + for (auto& adv: advs_) + { + adv_manager_->UnregisterAdvertisement(adv->getObjectPath()); + } + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(adv_manager_, "UnregisterAdvertisement", e); + return false; + } + + advs_.clear(); + 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()) { + LOG(ERROR) << __func__ + << ": A sync scanning session is already active for " + << std::string{*cur_monitored_service_uuid_}; + return false; + } + + if (adv_monitor_manager_ == nullptr) { + LOG(WARNING) << __func__ + << ": Advertising monitor not supported by BlueZ"; + // TODO: Implement manual monitoring. + return false; + } + + if (!MonitorManagerSupportsOr()) { + LOG(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) { + LOG(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 { + // why is this emitted? + monitor->emitInterfacesAddedSignal( + {org::bluez::AdvertisementMonitor1_adaptor::INTERFACE_NAME}); + + // adv_monitor_manager_ -> RegisterMonitor(monitor -> getObjectPath()); + LOG(INFO)<< __func__ << ": Registered advertisement monitor with path " << monitor -> getObjectPath(); + } catch (const sdbus::Error &e) { + LOG(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(), adapter_, devices_); + if (!StartLEDiscovery()) { + LOG(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) { + LOG(ERROR) + << __func__ + << ": error emitting InterfacesRemoved signal for object path " + << monitor->getObjectPath() << " with name '" << e.getName() + << "' and message '" << e.getMessage() << "'"; + } + return false; + } + LOG(INFO) << __func__ << " :Started monitoring for service UUID: " << std::string(service_uuid); + + 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()) { + LOG(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(); + LOG(INFO) << __func__ << ": Stopping discovery for adapter " + << adapter.getObjectPath(); + try { + adapter.StopDiscovery(); // this will stop bluetooth classic discovery as well. do we want this? + } 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; + + LOG(INFO) << __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) { + LOG(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) { + LOG(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(),adapter_, devices_); + if (!StartLEDiscovery()) { + LOG(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) { + LOG(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) { + LOG(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) { + LOG(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; + }}); + } + +std::unique_ptr BleV2Medium::StartGattServer( + api::ble_v2::ServerGattConnectionCallback callback) { + (void)callback; + return nullptr; + + return std::make_unique( + *system_bus_, adapter_, devices_,std::move(callback) + ); +} + +std::unique_ptr BleV2Medium::ConnectToGattServer( + api::ble_v2::BlePeripheral::UniqueId peripheral_id, + api::ble_v2::TxPowerLevel tx_power_level, + api::ble_v2::ClientGattConnectionCallback callback) { + (void)peripheral_id; + (void)tx_power_level; + (void)callback; + LOG(WARNING) << __func__ + << ": GATT client connection is not supported on Linux yet."; + return nullptr; +} + +std::unique_ptr BleV2Medium::OpenServerSocket( + const std::string &service_id) { + LOG(INFO) << __func__ << ": Opening BLE server socket for service " + << service_id; + return std::make_unique(service_id); +} + +std::unique_ptr +BleV2Medium::OpenL2capServerSocket(const std::string &service_id) { + return nullptr; + // LOG(INFO) << __func__ << ": Opening L2CAP server socket for service " + // << service_id; + // + // Prng prng; + // auto psm = 0x80 + (prng.NextUint32() % 0x80); + // auto server_socket = std::make_unique(psm); + // + // LOG(INFO) << __func__ << ": L2CAP server socket created with PSM: " + // << server_socket->GetPSM(); + // return server_socket; +} + +// This is supposed to be for a socket on top of Weave protocol. +std::unique_ptr BleV2Medium::Connect( + const std::string &service_id, api::ble_v2::TxPowerLevel tx_power_level, + api::ble_v2::BlePeripheral::UniqueId peripheral_id, + CancellationFlag *cancellation_flag) { + LOG(INFO) << __func__ << ": Not implemented on linux "; + return nullptr; +} + +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"; + filter["DuplicateData"] = true; + auto &adapter = adapter_.GetBluezAdapterObject(); + + try { + adapter.SetDiscoveryFilter(filter); + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(&adapter, "SetDiscoveryFilter", e); + return false; + } + + try { + LOG(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; +} + +// std::unique_ptr BleV2Medium::Connect( +// const std::string &service_id, api::ble_v2::TxPowerLevel tx_power_level, +// api::ble_v2::BlePeripheral &peripheral, +// CancellationFlag *cancellation_flag) { +// LOG(WARNING) << __func__ << ": BLE socket connections not implemented on Linux"; +// return nullptr; +// } + +std::unique_ptr BleV2Medium::ConnectOverL2cap( + int psm, const std::string &service_id, + api::ble_v2::TxPowerLevel tx_power_level, + api::ble_v2::BlePeripheral::UniqueId peripheral_id, + CancellationFlag *cancellation_flag) { + return nullptr; + // auto device = devices_->get_device_by_unique_id(peripheral_id); + // if (!device) { + // LOG(ERROR) << __func__ << ": Failed to find device with unique ID " + // << peripheral_id; + // return nullptr; + // } + // + // LOG(INFO) << __func__ << ": Connecting to L2CAP PSM " << psm + // << " on device " << device->GetMacAddress(); + // + // + // int fd = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); + // if (fd < 0) { + // LOG(ERROR) << __func__ << ": Failed to create L2CAP socket: " + // << std::strerror(errno); + // return nullptr; + // } + // + // struct sockaddr_l2 addr; + // std::memset(&addr, 0, sizeof(addr)); + // addr.l2_family = AF_BLUETOOTH; + // addr.l2_psm = htobs(psm); + // addr.l2_cid = 0; + // addr.l2_bdaddr_type = BDADDR_LE_PUBLIC; + // + // std::string mac_addr = device->GetMacAddress(); + // if (str2ba(mac_addr.c_str(), &addr.l2_bdaddr) < 0) { + // LOG(ERROR) << __func__ << ": Invalid Bluetooth address: " << mac_addr; + // close(fd); + // return nullptr; + // } + // + // if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { + // LOG(ERROR) << __func__ << ": Failed to connect to L2CAP socket: " + // << std::strerror(errno); + // close(fd); + // return nullptr; + // } + // + // LOG(INFO) << __func__ << ": Successfully connected to L2CAP socket"; + // return std::make_unique(fd, peripheral_id); +} + +bool BleV2Medium::StartMultipleServicesScanning( + const std::vector &service_uuids, + api::ble_v2::TxPowerLevel tx_power_level, ScanCallback callback) { + LOG(WARNING) << __func__ + << ": Multiple services scanning not implemented on Linux. " + << "Use single service scanning instead."; + return false; +} + +bool BleV2Medium::PauseMediumScanning() { + LOG(INFO) << __func__ << ": Pause scanning not implemented, returning success"; + return true; +} + +bool BleV2Medium::ResumeMediumScanning() { + LOG(INFO) << __func__ << ": Resume scanning not implemented, returning success"; + return true; +} + +void BleV2Medium::AddAlternateUuidForService(uint16_t uuid, + const std::string &service_id) { + LOG(INFO) << __func__ << ": Alternate UUID mapping not implemented. UUID: " + << uuid << ", service_id: " << service_id; +} + +std::optional +BleV2Medium::RetrieveBlePeripheralIdFromNativeId( + const std::string &ble_peripheral_native_id) { + LOG(WARNING) << __func__ + << ": Retrieval from native ID not implemented. Native ID: " + << ble_peripheral_native_id; + return std::nullopt; +} + +} // 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..5d3af920 --- /dev/null +++ b/internal/platform/implementation/linux/ble_v2_medium.h @@ -0,0 +1,172 @@ +// 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 + +#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 "ble_gatt_client.h" +#include "bluez_gatt_manager.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::UniqueId peripheral_id, + api::ble_v2::TxPowerLevel tx_power_level, + api::ble_v2::ClientGattConnectionCallback callback) override; + + std::unique_ptr OpenServerSocket( + const std::string &service_id) override; + + std::unique_ptr OpenL2capServerSocket( + const std::string &service_id) override; + + std::unique_ptr Connect( + const std::string &service_id, api::ble_v2::TxPowerLevel tx_power_level, + api::ble_v2::BlePeripheral::UniqueId peripheral_id, + CancellationFlag *cancellation_flag) override; + + std::unique_ptr ConnectOverL2cap( + int psm, const std::string &service_id, + api::ble_v2::TxPowerLevel tx_power_level, + api::ble_v2::BlePeripheral::UniqueId peripheral_id, + CancellationFlag *cancellation_flag) override; + + bool StartMultipleServicesScanning(const std::vector &service_uuids, + api::ble_v2::TxPowerLevel tx_power_level, + ScanCallback callback) override; + + bool PauseMediumScanning() override; + + bool ResumeMediumScanning() override; + + bool IsExtendedAdvertisementsAvailable() override; + + void AddAlternateUuidForService(uint16_t uuid, + const std::string &service_id) override; + + std::optional + RetrieveBlePeripheralIdFromNativeId( + const std::string &ble_peripheral_native_id) 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_; + // Why do we have observers her + std::shared_ptr> + 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.cc b/internal/platform/implementation/linux/ble_v2_server_socket.cc new file mode 100644 index 00000000..240de1e0 --- /dev/null +++ b/internal/platform/implementation/linux/ble_v2_server_socket.cc @@ -0,0 +1,86 @@ +// Copyright 2024 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_v2_server_socket.h" + +#include + +#include "absl/synchronization/mutex.h" +#include "internal/platform/exception.h" +#include "internal/platform/implementation/ble_v2.h" +#include "internal/platform/implementation/linux/ble_v2_socket.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { + +std::unique_ptr BleV2ServerSocket::Accept() { + absl::MutexLock lock(&mutex_); + LOG(INFO) << "BleV2ServerSocket::Accept waiting for connection"; + + while (!closed_ && pending_sockets_.empty()) { + cond_.Wait(&mutex_); + } + + if (closed_) { + LOG(INFO) << "BleV2ServerSocket::Accept socket is closed"; + return nullptr; + } + + std::unique_ptr socket = std::move(pending_sockets_.front()); + pending_sockets_.pop_front(); + + LOG(INFO) << "BleV2ServerSocket::Accept accepted connection"; + return socket; +} + +Exception BleV2ServerSocket::Close() { + absl::MutexLock lock(&mutex_); + LOG(INFO) << "BleV2ServerSocket::Close for service " << service_id_; + + if (closed_) { + return {Exception::kSuccess}; + } + + closed_ = true; + + // Close all pending sockets + for (auto& socket : pending_sockets_) { + if (socket) { + socket->Close(); + } + } + pending_sockets_.clear(); + + cond_.SignalAll(); + + return {Exception::kSuccess}; +} + +void BleV2ServerSocket::AddPendingSocket(std::unique_ptr socket) { + absl::MutexLock lock(&mutex_); + if (closed_) { + LOG(WARNING) + << "BleV2ServerSocket::AddPendingSocket socket is closed"; + return; + } + + pending_sockets_.push_back(std::move(socket)); + cond_.SignalAll(); + LOG(INFO) << "BleV2ServerSocket::AddPendingSocket added socket, " + << "pending count: " << pending_sockets_.size(); +} + +} // namespace linux +} // namespace nearby 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..2ff2322b --- /dev/null +++ b/internal/platform/implementation/linux/ble_v2_server_socket.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_API_BLE_V2_SERVER_SOCKET_H_ +#define PLATFORM_IMPL_LINUX_API_BLE_V2_SERVER_SOCKET_H_ + +#include +#include +#include + +#include "absl/synchronization/mutex.h" +#include "internal/platform/implementation/ble_v2.h" +#include "internal/platform/implementation/linux/ble_v2_socket.h" + +namespace nearby { +namespace linux { + +class BleV2ServerSocket final : public api::ble_v2::BleServerSocket { + public: + explicit BleV2ServerSocket(const std::string& service_id) + : service_id_(service_id) {} + ~BleV2ServerSocket() override = default; + + std::unique_ptr Accept() override + ABSL_LOCKS_EXCLUDED(mutex_); + + Exception Close() override ABSL_LOCKS_EXCLUDED(mutex_); + + void AddPendingSocket(std::unique_ptr socket) + ABSL_LOCKS_EXCLUDED(mutex_); + + std::string GetServiceId() const { return service_id_; } + + private: + std::string service_id_; + absl::Mutex mutex_; + absl::CondVar cond_; + bool closed_ ABSL_GUARDED_BY(mutex_) = false; + std::deque> pending_sockets_ + ABSL_GUARDED_BY(mutex_); +}; + +} // namespace linux +} // namespace nearby + +#endif diff --git a/internal/platform/implementation/linux/ble_v2_socket.cc b/internal/platform/implementation/linux/ble_v2_socket.cc new file mode 100644 index 00000000..b58303e2 --- /dev/null +++ b/internal/platform/implementation/linux/ble_v2_socket.cc @@ -0,0 +1,264 @@ +// Copyright 2024 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_v2_socket.h" + +#include + +#include "absl/synchronization/mutex.h" +#include "internal/platform/byte_array.h" +#include "internal/platform/exception.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { + +InputStream& BleV2Socket::GetInputStream() { return input_stream_; } + +OutputStream& BleV2Socket::GetOutputStream() { return output_stream_; } + +Exception BleV2Socket::Close() { + absl::MutexLock lock(&mutex_); + if (closed_) { + return {Exception::kSuccess}; + } + closed_ = true; + + // Close streams + input_stream_.NotifyClose(); + output_stream_.Close(); + + // Cleanup GATT resources + if (gatt_client_) { + LOG(INFO) << "Disconnecting GATT client for peripheral " + << peripheral_id_; + gatt_client_->Disconnect(); + gatt_client_.reset(); + } + + if (gatt_server_) { + LOG(INFO) << "Stopping GATT server for peripheral " + << peripheral_id_; + // Server cleanup is handled by the server itself + gatt_server_.reset(); + } + + return {Exception::kSuccess}; +} + +bool BleV2Socket::IsClosed() const { + absl::MutexLock lock(&mutex_); + return closed_; +} + +// BleInputStream implementation +ExceptionOr BleV2Socket::BleInputStream::Read(std::int64_t size) { + absl::MutexLock lock(&mutex_); + + while (buffer_.Empty() && !closed_) { + cond_.Wait(&mutex_); + } + + if (closed_ && buffer_.Empty()) { + return ExceptionOr(Exception::kIo); + } + + if (size < 0 || static_cast(size) >= buffer_.size()) { + ByteArray result = buffer_; + buffer_ = ByteArray(); + return ExceptionOr(result); + } + + ByteArray result(buffer_.data(), size); + buffer_ = ByteArray(buffer_.data() + size, buffer_.size() - size); + return ExceptionOr(result); +} + +Exception BleV2Socket::BleInputStream::Close() { + absl::MutexLock lock(&mutex_); + if (closed_) { + return {Exception::kSuccess}; + } + closed_ = true; + cond_.SignalAll(); + return {Exception::kSuccess}; +} + +void BleV2Socket::BleInputStream::ReceiveData(const ByteArray& data) { + absl::MutexLock lock(&mutex_); + if (closed_) { + return; + } + + if (buffer_.Empty()) { + buffer_ = data; + } else { + ByteArray combined(buffer_.size() + data.size()); + std::memcpy(combined.data(), buffer_.data(), buffer_.size()); + std::memcpy(combined.data() + buffer_.size(), data.data(), data.size()); + buffer_ = std::move(combined); + } + cond_.SignalAll(); +} + +void BleV2Socket::BleInputStream::NotifyClose() { + absl::MutexLock lock(&mutex_); + closed_ = true; + cond_.SignalAll(); +} + +// BleOutputStream implementation +Exception BleV2Socket::BleOutputStream::Write(const ByteArray& data) { + absl::MutexLock lock(&mutex_); + if (closed_) { + return {Exception::kIo}; + } + + if (!write_callback_) { + LOG(WARNING) << "BleOutputStream: No write callback set"; + return {Exception::kIo}; + } + + bool success = write_callback_(data); + return {success ? Exception::kSuccess : Exception::kIo}; +} + +Exception BleV2Socket::BleOutputStream::Flush() { + return {Exception::kSuccess}; +} + +Exception BleV2Socket::BleOutputStream::Close() { + absl::MutexLock lock(&mutex_); + if (closed_) { + return {Exception::kSuccess}; + } + closed_ = true; + write_callback_ = nullptr; + return {Exception::kSuccess}; +} + +void BleV2Socket::BleOutputStream::SetWriteCallback(WriteCallback callback) { + absl::MutexLock lock(&mutex_); + write_callback_ = std::move(callback); +} + +void BleV2Socket::SetGattServer( + std::unique_ptr gatt_server, + const api::ble_v2::GattCharacteristic& rx_char, + const api::ble_v2::GattCharacteristic& tx_char) { + absl::MutexLock lock(&mutex_); + + if (closed_) { + LOG(WARNING) << "Cannot set GATT server on closed socket"; + return; + } + + gatt_server_ = std::move(gatt_server); + rx_char_ = rx_char; + tx_char_ = tx_char; + + // Set up write callback to use GATT server notifications + output_stream_.SetWriteCallback( + [this](const ByteArray& data) -> bool { + absl::MutexLock lock(&mutex_); + if (!gatt_server_) { + LOG(ERROR) << "GATT server not available for write"; + return false; + } + + if (closed_) { + LOG(WARNING) << "Socket is closed, cannot write"; + return false; + } + + // Notify remote device via TX characteristic + absl::Status status = gatt_server_->NotifyCharacteristicChanged( + tx_char_, /*confirm=*/false, data); + + if (!status.ok()) { + LOG(WARNING) << "Failed to notify TX characteristic: " + << status.message(); + return false; + } + return true; + }); + + LOG(INFO) << "BLE socket configured with GATT server, RX: " + << std::string(rx_char.uuid) + << ", TX: " << std::string(tx_char.uuid); +} + +void BleV2Socket::SetGattClient( + std::unique_ptr gatt_client, + const api::ble_v2::GattCharacteristic& rx_char, + const api::ble_v2::GattCharacteristic& tx_char) { + absl::MutexLock lock(&mutex_); + + if (closed_) { + LOG(WARNING) << "Cannot set GATT client on closed socket"; + return; + } + + gatt_client_ = std::move(gatt_client); + rx_char_ = rx_char; + tx_char_ = tx_char; + + // Set up write callback to use GATT client writes + output_stream_.SetWriteCallback( + [this](const ByteArray& data) -> bool { + absl::MutexLock lock(&mutex_); + if (!gatt_client_) { + LOG(ERROR) << "GATT client not available for write"; + return false; + } + + if (closed_) { + LOG(WARNING) << "Socket is closed, cannot write"; + return false; + } + + // Write to TX characteristic on remote device + std::string data_str(data.data(), data.size()); + bool success = gatt_client_->WriteCharacteristic( + tx_char_, data_str, + api::ble_v2::GattClient::WriteType::kWithoutResponse); + + if (!success) { + LOG(WARNING) << "Failed to write to TX characteristic"; + return false; + } + return true; + }); + + // Subscribe to RX characteristic to receive data + bool subscribed = gatt_client_->SetCharacteristicSubscription( + rx_char_, /*enable=*/true, + [this](absl::string_view value) { + if (!IsClosed()) { + ByteArray data(value.data(), value.size()); + input_stream_.ReceiveData(data); + } + }); + + if (!subscribed) { + LOG(ERROR) << "Failed to subscribe to RX characteristic"; + } + + LOG(INFO) << "BLE socket configured with GATT client, RX: " + << std::string(rx_char.uuid) + << ", TX: " << std::string(tx_char.uuid); +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/ble_v2_socket.h b/internal/platform/implementation/linux/ble_v2_socket.h new file mode 100644 index 00000000..82ea93c2 --- /dev/null +++ b/internal/platform/implementation/linux/ble_v2_socket.h @@ -0,0 +1,138 @@ +// Copyright 2024 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_SOCKET_H_ +#define PLATFORM_IMPL_LINUX_API_BLE_V2_SOCKET_H_ + +#include +#include + +#include "absl/synchronization/mutex.h" +#include "internal/platform/byte_array.h" +#include "internal/platform/exception.h" +#include "internal/platform/implementation/ble_v2.h" +#include "internal/platform/input_stream.h" +#include "internal/platform/output_stream.h" + +namespace nearby { +namespace linux { + +// BLE v2 Socket implementation using GATT characteristics for data transfer +// +// Data flow: +// - Server side: +// * RX characteristic: Remote writes -> our InputStream reads +// * TX characteristic: Our OutputStream writes -> remote reads via notifications +// - Client side: +// * TX characteristic: Our OutputStream writes -> remote reads +// * RX characteristic: Remote writes (notifications) -> our InputStream reads +class BleV2Socket : public api::ble_v2::BleSocket { + public: + BleV2Socket() = default; + explicit BleV2Socket(api::ble_v2::BlePeripheral::UniqueId peripheral_id) + : peripheral_id_(peripheral_id) {} + ~BleV2Socket() override { Close(); } + + InputStream& GetInputStream() override; + OutputStream& GetOutputStream() override; + Exception Close() override ABSL_LOCKS_EXCLUDED(mutex_); + + api::ble_v2::BlePeripheral::UniqueId GetRemotePeripheralId() override { + return peripheral_id_; + } + + bool IsClosed() const ABSL_LOCKS_EXCLUDED(mutex_); + + // GATT integration: Allow external code to feed data to input stream + // Called when remote device writes to RX characteristic + void ReceiveData(const ByteArray& data) { input_stream_.ReceiveData(data); } + + // GATT integration: Set callback for output stream writes + // Callback should write to TX characteristic (notify remote) + void SetWriteCallback( + absl::AnyInvocable callback) { + output_stream_.SetWriteCallback(std::move(callback)); + } + + // Set the GATT server and characteristics for server-side socket + void SetGattServer(std::unique_ptr gatt_server, + const api::ble_v2::GattCharacteristic& rx_char, + const api::ble_v2::GattCharacteristic& tx_char) + ABSL_LOCKS_EXCLUDED(mutex_); + + // Set the GATT client and characteristics for client-side socket + void SetGattClient(std::unique_ptr gatt_client, + const api::ble_v2::GattCharacteristic& rx_char, + const api::ble_v2::GattCharacteristic& tx_char) + ABSL_LOCKS_EXCLUDED(mutex_); + + private: + class BleInputStream : public InputStream { + public: + BleInputStream() = default; + ~BleInputStream() override = default; + + ExceptionOr Read(std::int64_t size) override + ABSL_LOCKS_EXCLUDED(mutex_); + Exception Close() override ABSL_LOCKS_EXCLUDED(mutex_); + + void ReceiveData(const ByteArray& data) ABSL_LOCKS_EXCLUDED(mutex_); + void NotifyClose() ABSL_LOCKS_EXCLUDED(mutex_); + + private: + absl::Mutex mutex_; + absl::CondVar cond_; + ByteArray buffer_ ABSL_GUARDED_BY(mutex_); + bool closed_ ABSL_GUARDED_BY(mutex_) = false; + }; + + class BleOutputStream : public OutputStream { + public: + BleOutputStream() = default; + ~BleOutputStream() override = default; + + Exception Write(const ByteArray& data) override + ABSL_LOCKS_EXCLUDED(mutex_); + Exception Flush() override; + Exception Close() override ABSL_LOCKS_EXCLUDED(mutex_); + + using WriteCallback = absl::AnyInvocable; + void SetWriteCallback(WriteCallback callback) + ABSL_LOCKS_EXCLUDED(mutex_); + + private: + absl::Mutex mutex_; + WriteCallback write_callback_ ABSL_GUARDED_BY(mutex_); + bool closed_ ABSL_GUARDED_BY(mutex_) = false; + }; + + mutable absl::Mutex mutex_; + bool closed_ ABSL_GUARDED_BY(mutex_) = false; + BleInputStream input_stream_; + BleOutputStream output_stream_; + api::ble_v2::BlePeripheral::UniqueId peripheral_id_ = 0; + + // GATT resources (only one of these will be set) + std::unique_ptr gatt_server_ ABSL_GUARDED_BY(mutex_); + std::unique_ptr gatt_client_ ABSL_GUARDED_BY(mutex_); + + // Characteristics for data transfer + api::ble_v2::GattCharacteristic rx_char_ ABSL_GUARDED_BY(mutex_); + api::ble_v2::GattCharacteristic tx_char_ ABSL_GUARDED_BY(mutex_); +}; + +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPL_LINUX_API_BLE_V2_SOCKET_H_ diff --git a/internal/platform/implementation/linux/ble_v2_socket_adapter.cc b/internal/platform/implementation/linux/ble_v2_socket_adapter.cc new file mode 100644 index 00000000..e7245285 --- /dev/null +++ b/internal/platform/implementation/linux/ble_v2_socket_adapter.cc @@ -0,0 +1,130 @@ +// Copyright 2024 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_v2_socket_adapter.h" + +#include "absl/status/status.h" +#include "absl/synchronization/mutex.h" +#include "internal/platform/byte_array.h" +#include "internal/platform/implementation/ble_v2.h" +#include "internal/platform/implementation/linux/ble_v2_socket.h" +#include "internal/platform/logging.h" +#include "internal/platform/uuid.h" + +namespace nearby { +namespace linux { + +// Standard UUIDs for Nearby Connections GATT socket service +// RX: Remote writes to this, we read from it +constexpr uint64_t kRxCharMsb = 0x0000FE2C00001000ULL; +constexpr uint64_t kRxCharLsb = 0x800000805F9B34FBULL; + +// TX: We write to this (notify), remote reads from it +constexpr uint64_t kTxCharMsb = 0x0000FE2C00002000ULL; +constexpr uint64_t kTxCharLsb = 0x800000805F9B34FBULL; + +Uuid BleV2SocketAdapter::GetRxCharacteristicUuid() { + return Uuid(kRxCharMsb, kRxCharLsb); +} + +Uuid BleV2SocketAdapter::GetTxCharacteristicUuid() { + return Uuid(kTxCharMsb, kTxCharLsb); +} + +void BleV2SocketAdapter::RegisterSocket( + api::ble_v2::BlePeripheral::UniqueId device_id, BleV2Socket* socket) { + absl::MutexLock lock(&mutex_); + device_sockets_[device_id] = socket; + LOG(INFO) << "Registered socket for device " << device_id; +} + +void BleV2SocketAdapter::UnregisterSocket( + api::ble_v2::BlePeripheral::UniqueId device_id) { + absl::MutexLock lock(&mutex_); + device_sockets_.erase(device_id); + LOG(INFO) << "Unregistered socket for device " << device_id; +} + +api::ble_v2::ServerGattConnectionCallback +BleV2SocketAdapter::CreateServerCallbacks() { + api::ble_v2::ServerGattConnectionCallback callbacks; + + // Handle subscription to TX characteristic (remote wants to receive data) + callbacks.characteristic_subscription_cb = + [](const api::ble_v2::GattCharacteristic& characteristic) { + LOG(INFO) << "Remote subscribed to characteristic: " + << std::string(characteristic.uuid); + }; + + // Handle unsubscription + callbacks.characteristic_unsubscription_cb = + [](const api::ble_v2::GattCharacteristic& characteristic) { + LOG(INFO) << "Remote unsubscribed from characteristic: " + << std::string(characteristic.uuid); + }; + + // Handle read requests (not typically used for socket data transfer) + callbacks.on_characteristic_read_cb = + [](api::ble_v2::BlePeripheral::UniqueId remote_device_id, + const api::ble_v2::GattCharacteristic& characteristic, int offset, + api::ble_v2::ServerGattConnectionCallback::ReadValueCallback + callback) { + LOG(INFO) << "Read request from device " << remote_device_id + << " on characteristic " + << std::string(characteristic.uuid); + // Return empty data for reads + callback(absl::string_view("")); + }; + + // Handle write requests - THIS IS WHERE DATA COMES IN + callbacks.on_characteristic_write_cb = + [this](api::ble_v2::BlePeripheral::UniqueId remote_device_id, + const api::ble_v2::GattCharacteristic& characteristic, int offset, + absl::string_view data, + api::ble_v2::ServerGattConnectionCallback::WriteValueCallback + callback) { + LOG(INFO) << "Write request from device " << remote_device_id + << " on characteristic " + << std::string(characteristic.uuid) << ", data size: " + << data.size(); + + // Find the socket for this device + BleV2Socket* socket = nullptr; + { + absl::MutexLock lock(&mutex_); + auto it = device_sockets_.find(remote_device_id); + if (it != device_sockets_.end()) { + socket = it->second; + } + } + + if (socket) { + // Route the data to the socket's input stream + ByteArray byte_data(data.data(), data.size()); + socket->ReceiveData(byte_data); + callback(absl::OkStatus()); + LOG(INFO) << "Routed " << data.size() + << " bytes to socket input stream"; + } else { + LOG(WARNING) << "No socket registered for device " + << remote_device_id; + callback(absl::NotFoundError("No socket for device")); + } + }; + + return callbacks; +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/ble_v2_socket_adapter.h b/internal/platform/implementation/linux/ble_v2_socket_adapter.h new file mode 100644 index 00000000..1654d4fd --- /dev/null +++ b/internal/platform/implementation/linux/ble_v2_socket_adapter.h @@ -0,0 +1,66 @@ +// Copyright 2024 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_SOCKET_ADAPTER_H_ +#define PLATFORM_IMPL_LINUX_API_BLE_V2_SOCKET_ADAPTER_H_ + +#include +#include + +#include "absl/container/flat_hash_map.h" +#include "absl/synchronization/mutex.h" +#include "internal/platform/byte_array.h" +#include "internal/platform/implementation/ble_v2.h" +#include "internal/platform/implementation/linux/ble_v2_socket.h" +#include "internal/platform/uuid.h" + +namespace nearby { +namespace linux { + +// Helper class to adapt GATT server callbacks to socket data streams +// This wires up the ServerGattConnectionCallback to feed data into BleV2Socket +class BleV2SocketAdapter { + public: + BleV2SocketAdapter() = default; + ~BleV2SocketAdapter() = default; + + // Create GATT server callbacks that will route data to/from sockets + api::ble_v2::ServerGattConnectionCallback CreateServerCallbacks(); + + // Register a socket for a specific remote device + // When GATT writes come from this device, data is routed to this socket + void RegisterSocket(api::ble_v2::BlePeripheral::UniqueId device_id, + BleV2Socket* socket) ABSL_LOCKS_EXCLUDED(mutex_); + + // Unregister a socket + void UnregisterSocket(api::ble_v2::BlePeripheral::UniqueId device_id) + ABSL_LOCKS_EXCLUDED(mutex_); + + // Get the RX characteristic UUID (for receiving data from remote) + static Uuid GetRxCharacteristicUuid(); + + // Get the TX characteristic UUID (for sending data to remote) + static Uuid GetTxCharacteristicUuid(); + + private: + absl::Mutex mutex_; + // Map of device ID to socket for routing incoming GATT writes + absl::flat_hash_map + device_sockets_ ABSL_GUARDED_BY(mutex_); +}; + +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPL_LINUX_API_BLE_V2_SOCKET_ADAPTER_H_ diff --git a/internal/platform/implementation/linux/bluetooth_adapter.cc b/internal/platform/implementation/linux/bluetooth_adapter.cc index 9c0c95f7..99f1bb0f 100644 --- a/internal/platform/implementation/linux/bluetooth_adapter.cc +++ b/internal/platform/implementation/linux/bluetooth_adapter.cc @@ -1,4 +1,4 @@ -// Copyright 2020 Google LLC +// 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. @@ -12,80 +12,110 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include +#include - -#include "absl/base/attributes.h" -#include "absl/strings/string_view.h" +#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) - { - if (status == Status::kEnabled) - { - Powered(true); - } - else - { - Powered(false); - } + +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 - { - return Powered(); +} + +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; } - api::BluetoothAdapter::ScanMode BluetoothAdapter::GetScanMode() const - { - if (!IsEnabled()) - { - return ScanMode::kNone; - } - if (Discoverable()) - { - return ScanMode::kConnectableDiscoverable; - } - return ScanMode::kConnectable; +} + +BluetoothAdapter::ScanMode BluetoothAdapter::GetScanMode() const { + bool powered = IsEnabled(); + if (!powered) { + return ScanMode::kNone; } - bool BluetoothAdapter::SetScanMode(ScanMode scan_mode) - { - if (!IsEnabled()) return false; - switch (scan_mode) - { + + 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: - Discoverable(false); - return true; - case ScanMode::kConnectableDiscoverable: - Discoverable(true); + 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::GetMacAddress() const - { - return Address(); - } - std::string BluetoothAdapter::GetName() const - { - return Alias(); - } - bool BluetoothAdapter::SetName(absl::string_view name) - { - try { - Alias(std::string(name)); - return true; - } catch (const sdbus::Error&) {return false;} } +} - bool BluetoothAdapter::SetName(absl::string_view name,bool persist) - { - return BluetoothAdapter::SetName(name); +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 index 28f5048d..810cae8f 100644 --- a/internal/platform/implementation/linux/bluetooth_adapter.h +++ b/internal/platform/implementation/linux/bluetooth_adapter.h @@ -1,4 +1,4 @@ -// Copyright 2020 Google LLC +// 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. @@ -12,114 +12,73 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef PLATFORM_LINUX_IMPL_BLUETOOTH_ADAPTER_H_ -#define PLATFORM_LINUX_IMPL_BLUETOOTH_ADAPTER_H_ - -#include -#include +#ifndef PLATFORM_IMPL_LINUX_BLUETOOTH_ADAPTER_H_ +#define PLATFORM_IMPL_LINUX_BLUETOOTH_ADAPTER_H_ +#include #include -#include "absl/base/attributes.h" #include "absl/strings/string_view.h" #include "internal/platform/implementation/bluetooth_adapter.h" -#include "internal/platform/implementation/linux/generated/bluez_adapter_client_glue.h" - -#include "internal/platform/mac_address.h" -#include "internal/platform/implementation/bluetooth_classic.h" -constexpr uint8_t kAndroidDiscoverableBluetoothNameMaxLength = 37; // bytes +#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 { - -// https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html -class BluetoothAdapter : public api::BluetoothAdapter, public sdbus::ProxyInterfaces { +class BluezAdapter : public sdbus::ProxyInterfaces { public: - BluetoothAdapter(sdbus::IConnection& system_bus, - const sdbus::ObjectPath& object_path): ProxyInterfaces( - system_bus, - "org.bluez", object_path ) - { + BluezAdapter(sdbus::IConnection &system_bus, + const sdbus::ObjectPath &adapter_object_path) + : ProxyInterfaces(system_bus, bluez::SERVICE_DEST, adapter_object_path) { registerProxy(); - }; - ~BluetoothAdapter() override - { - unregisterProxy(); - }; - - //// Eligible statuses of the BluetoothAdapter. - //enum class Status { - // kDisabled, - // kEnabled, - //}; - - // Synchronously sets the status of the BluetoothAdapter to 'status', and - // returns true if the operation was a success. - bool SetStatus(Status status) override; - // Returns true if the BluetoothAdapter's current status is - // Status::Value::kEnabled. - bool IsEnabled() const override; - - // Scan modes of a BluetoothAdapter, as described at - // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getScanMode(). - //enum class ScanMode { - // kUnknown, - // kNone, - // kConnectable, - // kConnectableDiscoverable, - //}; - - // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getScanMode() - // - // Returns ScanMode::kUnknown on error. - ScanMode GetScanMode() const override; - // Synchronously sets the scan mode of the adapter, and returns true if the - // operation was a success. - bool SetScanMode(ScanMode scan_mode) override; - - // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getName() - // Returns an empty string on error - std::string GetName() const override; - // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#setName(java.lang.String) - bool SetName(absl::string_view name) override; - bool SetName(absl::string_view name, bool persist) override; - - // Returns BT MAC address assigned to this adapter. - ABSL_DEPRECATED("Use GetAddress() instead.") - std::string GetMacAddress() const override; - - // Implementation for migration only. Once subclasses implement this, the - // above GetMacAddress() can be removed. - MacAddress GetAddress() const override { - std::string mac_address = GetMacAddress(); - if (mac_address.empty()) { - return {}; - } - MacAddress address; - MacAddress::FromString(mac_address, address); - return address; } + ~BluezAdapter() { unregisterProxy(); } }; - // https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html. - class BluetoothDevice : public api::BluetoothDevice { - public: - ~BluetoothDevice() override = default; +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)) {} - // https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#getName() - std::string GetName() const override; - std::string GetMacAddress() const override; - MacAddress GetAddress() const override; - BluetoothAdapter& GetAdapter() { return adapter_; } + ~BluetoothAdapter() override = default; - private: - // Only BluetoothAdapter may instantiate BluetoothDevice. - friend class BluetoothAdapter; + bool SetStatus(Status status) override; + bool IsEnabled() const override; - explicit BluetoothDevice(BluetoothAdapter* adapter); + ScanMode GetScanMode() const override; - BluetoothAdapter& adapter_; - }; + 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_LINUX_IMPL_BLUETOOTH_ADAPTER_H_ +#endif // PLATFORM_IMPL_LINUX_BLUETOOTH_ADAPTER_H_ diff --git a/internal/platform/implementation/linux/bluetooth_adapter_test.cc b/internal/platform/implementation/linux/bluetooth_adapter_test.cc deleted file mode 100644 index 7550aa77..00000000 --- a/internal/platform/implementation/linux/bluetooth_adapter_test.cc +++ /dev/null @@ -1,94 +0,0 @@ -// filepath: /workspace/internal/platform/implementation/linux/bluetooth_adapter_test.cc -// Copyright 2024 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_adapter.h" - -#include -#include - -#include "gtest/gtest.h" -#include "absl/strings/string_view.h" -#include "absl/synchronization/notification.h" -#include "internal/platform/implementation/bluetooth_adapter.h" -#include - -namespace nearby { -namespace linux { -namespace { - -constexpr absl::string_view kName = "Test Radio Name"; - -// Tests are disabled because they may interact with the system DBus and -// modify adapter state; they are intended as compile-time and manual-run -// validations to mirror the Windows test suite. - -TEST(BluetoothAdapter, DISABLED_SetStatusReturnsTrue) { - auto connection = sdbus::createSystemBusConnection(); - connection -> enterEventLoopAsync(); - sdbus::ObjectPath object_path{"/org/bluez/hci0"}; - BluetoothAdapter adapter(*connection, object_path); - - // Our implementation returns bool; assert it returns true on attempted - // enable. This test is disabled by default to avoid changing system state. - EXPECT_TRUE(adapter.SetStatus(api::BluetoothAdapter::Status::kEnabled)); -} - -TEST(BluetoothAdapter, DISABLED_SetAndGetName) { - auto connection = sdbus::createSystemBusConnection(); - connection -> enterEventLoopAsync(); - sdbus::ObjectPath object_path{"/org/bluez/hci0"}; - BluetoothAdapter adapter(*connection, object_path); - - const std::string new_name = "nearby-linux-test-name"; - bool ok = adapter.SetName(new_name); - EXPECT_TRUE(ok); - if (ok) { - // If SetName succeeded, GetName should reflect the set value. - EXPECT_EQ(adapter.GetName(), new_name); - } -} - -TEST(BluetoothAdapter, DISABLED_GetMacAddressNotEmpty) { - auto connection = sdbus::createSystemBusConnection(); - connection -> enterEventLoopAsync(); - sdbus::ObjectPath object_path{"/org/bluez/hci0"}; - BluetoothAdapter adapter(*connection, object_path); - - EXPECT_FALSE(adapter.GetMacAddress().empty()); -} - -TEST(BluetoothAdapter, DISABLED_SetScanModeWhenEnabled) { - auto connection = sdbus::createSystemBusConnection(); - connection -> enterEventLoopAsync(); - sdbus::ObjectPath object_path{"/org/bluez/hci0"}; - BluetoothAdapter adapter(*connection, object_path); - - if (!adapter.IsEnabled()) { - GTEST_SKIP() << "Adapter not enabled on this machine; skipping scan-mode test."; - } - - // Try to set discoverable connectable; may be disallowed by system policy. - bool set_ok = adapter.SetScanMode(api::BluetoothAdapter::ScanMode::kConnectableDiscoverable); - if (!set_ok) { - GTEST_SKIP() << "SetScanMode returned false; skipping further scan-mode checks."; - } - - auto scan_mode = adapter.GetScanMode(); - EXPECT_EQ(scan_mode, api::BluetoothAdapter::ScanMode::kConnectableDiscoverable); -} - -} // namespace -} // namespace linux -} // namespace nearby 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..edfe81e0 --- /dev/null +++ b/internal/platform/implementation/linux/bluetooth_bluez_profile.cc @@ -0,0 +1,363 @@ +// 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" +#include "absl/strings/str_cat.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; + LOG(INFO) << __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_) { + LOG(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(); + LOG(INFO) << __func__ << ": " << getObjectPath() + << ": Connected to " << mac_addr.ToString(); + + FDProperties props(fd_props); + + LOG(INFO) << "PUSH key(GetAddress.ToString)=" << mac_addr.ToString() + << " alias=" << alias; + LOG(INFO) << "PUSH_ENTER profile=" << this + << " mutex=" << &connections_lock_ + << " obj=" << getObjectPath() + << " path=" << device_object_path; + { + absl::MutexLock l(&connections_lock_); + connections_[mac_addr.ToString()].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) { + LOG(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(); + LOG(INFO) << __func__ << ": Disconnection requested for device " + << device_object_path; + + absl::MutexLock l(&connections_lock_); + if (connections_.count(mac_addr) == 0) { + LOG(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) { + LOG(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); + + LOG(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) { + LOG(WARNING) + << __func__ + << ": attempted to unregister a profile that is not registered"; + return; + } + + auto profile_object_path = bluez::profile_object_path(service_uuid); + LOG(INFO) << __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. Only fires when we're requesting a new connection. i.e: we're the client +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) { + LOG(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]() { + if (profile->connections_lock_.TryLock()) { + profile->connections_lock_.Unlock(); + } +} +); + + LOG(INFO) << __func__ << ": " << profile->getObjectPath() + << ": Attempting to get a FD for service " + << service_uuid << " on device " << mac_addr; + + LOG(INFO) << "WAIT profile=" << profile.get() + << " mutex=" << &profile->connections_lock_ + << " obj=" << profile->getObjectPath() + << " key=" << mac_addr; + auto cond = [mac_addr, profile, cancellation_flag]() { + profile->connections_lock_.AssertHeld(); + LOG(INFO) << "connections_lock_ is held by: " << mac_addr << " with ptr: " << &profile -> connections_lock_; + return profile->connections_.count(mac_addr) != 0 || + (cancellation_flag != nullptr && cancellation_flag->Cancelled()); + }; + + // BUG: Race condition. Hangs here + LOG(INFO) << "WAIT key(GetMacAddress)=" << mac_addr; + LOG(INFO) << "connections_ size" << profile -> connections_.size(); + absl::MutexLock connections_lock(&profile->connections_lock_, + absl::Condition(&cond)); + LOG(INFO) << "WAIT_ACQUIRED " + << " map_size=" << profile->connections_.size(); + + // Clean up pending tracking + profile->pending_outgoing_.erase(mac_addr); + + if (cancellation_flag != nullptr && cancellation_flag->Cancelled()) { + LOG(INFO) + << __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. Only fires when another device requests connection from us. i.e. we're the server +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)]; + } + + LOG(INFO) << __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(); + + // Only accept connections that DON'T have pending outgoing attempts + for (const auto& [mac, fds] : profile->connections_) { + if (profile->pending_outgoing_.count(mac) == 0) { + return true; // Found a connection without pending outgoing + } + } + + return cancellation_flag != nullptr && cancellation_flag->Cancelled(); + }; + profile->connections_lock_.Await(absl::Condition(&cond)); + + if (cancellation_flag != nullptr && cancellation_flag->Cancelled()) { + LOG(INFO) + << __func__ << ": Cancelled waiting for new connections on profile " + << profile->getObjectPath(); + profile->connections_lock_.Unlock(); + return std::nullopt; + } + + // Find first connection without pending outgoing + std::string mac_addr; + sdbus::UnixFd fd; + bool found = false; + + for (auto it = profile->connections_.begin(); it != profile->connections_.end(); ++it) { + if (profile->pending_outgoing_.count(it->first) == 0) { + mac_addr = it->first; + auto& fds = it->second; + // Use auto to avoid accessing private FDProperties type + auto [fd_tmp, properties] = fds.back(); + fd = std::move(fd_tmp); + fds.pop_back(); + if (fds.empty()) { + profile->connections_.erase(it); + } + found = true; + break; + } + } + + LOG(INFO) << __func__ << " Cleared connections"; + profile->connections_lock_.Unlock(); + + if (!found) { + LOG(ERROR) << __func__ << ": No eligible connection found"; + return std::nullopt; + } + + auto device = devices_.get_device_by_address(mac_addr); + if (device == nullptr) { + LOG(ERROR) << __func__ << ": Device " << mac_addr + << " is no longer available"; + return std::nullopt; + } + + return std::pair(device, std::move(fd)); +} + void ProfileManager::MarkPendingOutgoing(absl::string_view service_uuid, + const std::string& mac_address) { + absl::ReaderMutexLock lock(®istered_service_uuids_mutex_); + if (registered_services_.count(std::string(service_uuid)) == 0) { + return; + } + auto profile = registered_services_[std::string(service_uuid)]; + absl::MutexLock l(&profile->connections_lock_); + profile->pending_outgoing_.insert(mac_address); + LOG(INFO) << __func__ << ": Marked " << mac_address + << " as pending outgoing for " << service_uuid; +} + + void ProfileManager::ClearPendingOutgoing(absl::string_view service_uuid, + const std::string& mac_address) { + absl::ReaderMutexLock lock(®istered_service_uuids_mutex_); + if (registered_services_.count(std::string(service_uuid)) == 0) { + return; + } + auto profile = registered_services_[std::string(service_uuid)]; + absl::MutexLock l(&profile->connections_lock_); + profile->pending_outgoing_.erase(mac_address); + LOG(INFO) << __func__ << ": Cleared " << mac_address + << " as pending outgoing for " << service_uuid; +} +} // 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..d0f83995 --- /dev/null +++ b/internal/platform/implementation/linux/bluetooth_bluez_profile.h @@ -0,0 +1,155 @@ +// 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(); + LOG(INFO) << __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_); + + // Track pending outgoing connection attempts to avoid race with incoming + std::set pending_outgoing_ 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_); + void MarkPendingOutgoing(absl::string_view service_uuid, + const std::string& mac_address) + ABSL_LOCKS_EXCLUDED(registered_service_uuids_mutex_); + void ClearPendingOutgoing(absl::string_view service_uuid, + const std::string& mac_address) + 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.cc b/internal/platform/implementation/linux/bluetooth_classic.cc deleted file mode 100644 index e97266ed..00000000 --- a/internal/platform/implementation/linux/bluetooth_classic.cc +++ /dev/null @@ -1,277 +0,0 @@ -// 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/bluetooth_classic.h" - -#include -#include -#include -#include - -#include "absl/functional/any_invocable.h" -#include "absl/log/check.h" -#include "absl/strings/string_view.h" -#include "absl/synchronization/mutex.h" -#include "internal/platform/cancellation_flag.h" -#include "internal/platform/cancellation_flag_listener.h" -#include "internal/platform/exception.h" -#include "internal/platform/implementation/bluetooth_adapter.h" -#include "internal/platform/implementation/bluetooth_classic.h" -#include "internal/platform/implementation/g3/bluetooth_adapter.h" -#include "internal/platform/logging.h" -#include "internal/platform/mac_address.h" -#include "internal/platform/medium_environment.h" -#include "internal/platform/types.h" - -namespace nearby { -namespace linux { - -BluetoothDevice* BluetoothSocket::GetRemoteDevice() { - BluetoothSocket* remote_socket = - static_cast(GetRemoteSocket()); - if (remote_socket == nullptr || remote_socket->adapter_ == nullptr) { - return nullptr; - } - return &remote_socket->adapter_->GetDevice(); -} - -std::unique_ptr BluetoothServerSocket::Accept() { - absl::MutexLock lock(mutex_); - while (!closed_ && pending_sockets_.empty()) { - cond_.Wait(&mutex_); - } - // whether or not we were running in the wait loop, return early if closed. - if (closed_) return {}; - auto* remote_socket = - pending_sockets_.extract(pending_sockets_.begin()).value(); - CHECK(remote_socket); - auto local_socket = std::make_unique(adapter_); - local_socket->Connect(*remote_socket); - remote_socket->Connect(*local_socket); - cond_.SignalAll(); - return local_socket; -} - -bool BluetoothServerSocket::Connect(BluetoothSocket& socket) { - absl::MutexLock lock(mutex_); - if (closed_) return false; - if (socket.IsConnected()) { - LOG(ERROR) << "Failed to connect to BT server socket: already connected"; - return true; // already connected. - } - // add client socket to the pending list - pending_sockets_.emplace(&socket); - cond_.SignalAll(); - while (!socket.IsConnected()) { - cond_.Wait(&mutex_); - if (closed_) return false; - } - return true; -} - -void BluetoothServerSocket::SetCloseNotifier( - absl::AnyInvocable notifier) { - absl::MutexLock lock(mutex_); - close_notifier_ = std::move(notifier); -} - -BluetoothServerSocket::~BluetoothServerSocket() { - absl::MutexLock lock(mutex_); - DoClose(); -} - -Exception BluetoothServerSocket::Close() { - absl::MutexLock lock(mutex_); - return DoClose(); -} - -Exception BluetoothServerSocket::DoClose() { - bool should_notify = !closed_; - closed_ = true; - if (should_notify) { - cond_.SignalAll(); - if (close_notifier_) { - auto notifier = std::move(close_notifier_); - mutex_.unlock(); - // Notifier may contain calls to public API, and may cause deadlock, if - // mutex_ is held during the call. - notifier(); - mutex_.lock(); - } - } - return {Exception::kSuccess}; -} - -BluetoothPairing::BluetoothPairing(api::BluetoothDevice& remote_device) - : remote_device_(remote_device) {} - -BluetoothPairing::~BluetoothPairing() { - MediumEnvironment::Instance().ClearBluetoothDevicesForPairing(); -} - -bool BluetoothPairing::InitiatePairing( - api::BluetoothPairingCallback pairing_cb) { - return MediumEnvironment::Instance().InitiatePairing(&remote_device_, - std::move(pairing_cb)); -} - -bool BluetoothPairing::FinishPairing( - std::optional pin_code) { - return MediumEnvironment::Instance().FinishPairing(&remote_device_); -} - -bool BluetoothPairing::CancelPairing() { - return MediumEnvironment::Instance().CancelPairing(&remote_device_); -} - -bool BluetoothPairing::Unpair() { - return MediumEnvironment::Instance().SetPairingState(&remote_device_, false); -} - -bool BluetoothPairing::IsPaired() { - return MediumEnvironment::Instance().IsPaired(&remote_device_); -} - -BluetoothClassicMedium::BluetoothClassicMedium(api::BluetoothAdapter& adapter) - : adapter_(static_cast(&adapter)) { - adapter_->SetBluetoothClassicMedium(this); - auto& env = MediumEnvironment::Instance(); - env.RegisterBluetoothMedium(*this, GetAdapter()); -} - -BluetoothClassicMedium::~BluetoothClassicMedium() { - adapter_->SetBluetoothClassicMedium(nullptr); - auto& env = MediumEnvironment::Instance(); - env.UnregisterBluetoothMedium(*this); -} - -bool BluetoothClassicMedium::StartDiscovery(DiscoveryCallback callback) { - auto& env = MediumEnvironment::Instance(); - env.UpdateBluetoothMedium(*this, std::move(callback)); - return true; -} - -bool BluetoothClassicMedium::StopDiscovery() { - auto& env = MediumEnvironment::Instance(); - env.UpdateBluetoothMedium(*this, {}); - return true; -} - -std::unique_ptr BluetoothClassicMedium::ConnectToService( - api::BluetoothDevice& remote_device, const std::string& service_uuid, - CancellationFlag* cancellation_flag) { - LOG(INFO) << "G3 ConnectToService [self]: medium=" << this - << ", adapter=" << &GetAdapter() - << ", device=" << &GetAdapter().GetDevice(); - - // Find the device in the MediumEnvironment, so that injected devices are - // supported in tests. - api::BluetoothDevice* device = - MediumEnvironment::Instance().FindBluetoothDevice( - remote_device.GetAddress()); - if (device == nullptr) { - LOG(ERROR) << "G3 ConnectToService [peer]: device=" << &remote_device - << " not found"; - return {}; - } - - auto& adapter = down_cast(device)->GetAdapter(); - auto* medium = - down_cast(adapter.GetBluetoothClassicMedium()); - - if (!medium) return {}; // Adapter is not bound to medium. Bail out. - - BluetoothServerSocket* server_socket = nullptr; - LOG(INFO) << "G3 ConnectToService [peer]: medium=" << medium - << ", adapter=" << &adapter << ", device=" << &remote_device - << ", uuid=" << service_uuid; - // Then, find our server socket context in this medium. - { - absl::MutexLock medium_lock(medium->mutex_); - auto item = medium->sockets_.find(service_uuid); - server_socket = item != medium->sockets_.end() ? item->second : nullptr; - if (server_socket == nullptr) { - LOG(ERROR) << "Failed to find BT Server socket: uuid=" << service_uuid; - return {}; - } - } - - if (cancellation_flag->Cancelled()) { - LOG(ERROR) << "G3 Bluetooth Connect: Has been cancelled: " - "service_uuid=" - << service_uuid; - return {}; - } - - CancellationFlagListener listener(cancellation_flag, [&server_socket]() { - LOG(INFO) << "G3 Bluetooth Cancel Connect."; - if (server_socket != nullptr) server_socket->Close(); - }); - - auto socket = std::make_unique(&GetAdapter()); - // Finally, Request to connect to this socket. - if (!server_socket->Connect(*socket)) { - LOG(ERROR) << "Failed to connect to existing BT Server socket: uuid=" - << service_uuid; - return {}; - } - - if (cancellation_flag->Cancelled()) { - LOG(ERROR) << "G3 Bluetooth Connect: Has been cancelled after connected: " - "service_uuid=" - << service_uuid; - socket->Close(); - return {}; - } - - LOG(INFO) << "G3 ConnectToService: connected: socket=" << socket.get(); - return socket; -} - -std::unique_ptr -BluetoothClassicMedium::ListenForService(const std::string& service_name, - const std::string& service_uuid) { - auto socket = std::make_unique(GetAdapter()); - socket->SetCloseNotifier([this, uuid = service_uuid]() { - absl::MutexLock lock(mutex_); - sockets_.erase(uuid); - }); - LOG(INFO) << "Adding service: medium=" << this << ", uuid=" << service_uuid; - absl::MutexLock lock(mutex_); - sockets_.emplace(service_uuid, socket.get()); - return socket; -} - -std::unique_ptr BluetoothClassicMedium::CreatePairing( - api::BluetoothDevice& remote_device) { - return std::make_unique(remote_device); -} - -api::BluetoothDevice* BluetoothClassicMedium::GetRemoteDevice( - MacAddress mac_address) { - return MediumEnvironment::Instance().FindBluetoothDevice(mac_address); -} - -void BluetoothClassicMedium::AddObserver( - api::BluetoothClassicMedium::Observer* observer) { - MediumEnvironment::Instance().AddObserver(observer); -} - -void BluetoothClassicMedium::RemoveObserver( - api::BluetoothClassicMedium::Observer* observer) { - MediumEnvironment::Instance().RemoveObserver(observer); -} - -} // namespace linux -} // namespace nearby diff --git a/internal/platform/implementation/linux/bluetooth_classic.h b/internal/platform/implementation/linux/bluetooth_classic.h deleted file mode 100644 index fef9dc3e..00000000 --- a/internal/platform/implementation/linux/bluetooth_classic.h +++ /dev/null @@ -1,222 +0,0 @@ -// 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_BLUETOOTH_CLASSIC_H_ -#define PLATFORM_IMPL_LINUX_BLUETOOTH_CLASSIC_H_ - -#include -#include -#include - -#include "absl/container/flat_hash_map.h" -#include "absl/container/flat_hash_set.h" -#include "absl/synchronization/mutex.h" -#include "internal/platform/exception.h" -#include "internal/platform/implementation/bluetooth_classic.h" -#include "internal/platform/implementation/g3/bluetooth_adapter.h" -#include "internal/platform/implementation/g3/socket_base.h" -#include "internal/platform/input_stream.h" -#include "internal/platform/mac_address.h" -#include "internal/platform/output_stream.h" - -namespace nearby { -namespace linux { - -// https://developer.android.com/reference/android/bluetooth/BluetoothSocket.html. -class BluetoothSocket : public api::BluetoothSocket, public SocketBase { - public: - BluetoothSocket() = default; - explicit BluetoothSocket(BluetoothAdapter* adapter) : adapter_(adapter) {} - - // Returns the InputStream of this connected BluetoothSocket. - InputStream& GetInputStream() override { - return SocketBase::GetInputStream(); - } - - // Returns the OutputStream of this connected BluetoothSocket. - // This stream is for local side to write. - OutputStream& GetOutputStream() override { - return SocketBase::GetOutputStream(); - } - - // Closes both input and output streams, marks Socket as closed. - // After this call object should be treated as not connected. - // Returns Exception::kIo on error, Exception::kSuccess otherwise. - Exception Close() override { return SocketBase::Close(); } - - // https://developer.android.com/reference/android/bluetooth/BluetoothSocket.html#getRemoteDevice() - // Returns valid BluetoothDevice pointer if there is a connection, and - // nullptr otherwise. - BluetoothDevice* GetRemoteDevice() override; - - private: - BluetoothAdapter* adapter_ = nullptr; // Our Adapter. Read only. -}; - -// https://developer.android.com/reference/android/bluetooth/BluetoothServerSocket.html. -class BluetoothServerSocket : public api::BluetoothServerSocket { - public: - explicit BluetoothServerSocket(BluetoothAdapter& adapter) - : adapter_(&adapter) {} - ~BluetoothServerSocket() override; - - // 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. - // - // Called by the server side of a connection. - // Returns BluetoothSocket to the server side. - // If not null, returned socket is connected to its remote (client-side) peer. - std::unique_ptr Accept() override - ABSL_LOCKS_EXCLUDED(mutex_); - - // Blocks until either: - // - connection is available, or - // - server socket is closed, or - // - error happens. - // - // Called by the client side of a connection. - // socket is an initialized BluetoothSocket, associated with a client - // BluetoothAdapter. - // Returns true, if socket is successfully connected. - bool Connect(BluetoothSocket& socket) ABSL_LOCKS_EXCLUDED(mutex_); - - // Called by the server side of a connection before passing ownership of - // BluetoothServerSocker to user, to track validity of a pointer to this - // server socket, - void SetCloseNotifier(absl::AnyInvocable notifier) - ABSL_LOCKS_EXCLUDED(mutex_); - - // Returns Exception::kIo on error, Exception::kSuccess otherwise. - // Calls close_notifier if it was previously set, and marks socket as closed. - Exception Close() override ABSL_LOCKS_EXCLUDED(mutex_); - - private: - Exception DoClose() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); - - absl::Mutex mutex_; - absl::CondVar cond_; - BluetoothAdapter* adapter_ = nullptr; // Our Adapter. Read only. - absl::flat_hash_set pending_sockets_ - ABSL_GUARDED_BY(mutex_); - absl::AnyInvocable close_notifier_ ABSL_GUARDED_BY(mutex_); - bool closed_ ABSL_GUARDED_BY(mutex_) = false; -}; - -// A concrete implementation for BluetoothPairing. -class BluetoothPairing : public api::BluetoothPairing { - public: - explicit BluetoothPairing(api::BluetoothDevice& remote_device); - BluetoothPairing(const BluetoothPairing&) = default; - BluetoothPairing& operator=(const BluetoothPairing&) = default; - ~BluetoothPairing() override; - - bool InitiatePairing(api::BluetoothPairingCallback pairing_cb) override; - bool FinishPairing(std::optional pin_code) override; - bool CancelPairing() override; - bool Unpair() override; - bool IsPaired() override; - - private: - api::BluetoothDevice& remote_device_; -}; - -// Container of operations that can be performed over the Bluetooth Classic -// medium. -class BluetoothClassicMedium : public api::BluetoothClassicMedium { - public: - explicit BluetoothClassicMedium(api::BluetoothAdapter& adapter); - ~BluetoothClassicMedium() override; - - // NOTE(DiscoveryCallback): - // BluetoothDevice is a proxy object created as a result of BT discovery. - // Its lifetime spans between calls to device_discovered_cb and - // device_lost_cb. - // It is safe to use BluetoothDevice in device_discovered_cb() callback - // and at any time afterwards, until device_lost_cb() is called. - // It is not safe to use BluetoothDevice after returning from - // device_lost_cb() callback. - // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#startDiscovery() - // - // Returns true once the process of discovery has been initiated. - bool StartDiscovery(DiscoveryCallback callback) override - ABSL_LOCKS_EXCLUDED(mutex_); - - // 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 ABSL_LOCKS_EXCLUDED(mutex_); - - // Connects to existing remote BT service. - // - // 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 ABSL_LOCKS_EXCLUDED(mutex_); - - BluetoothAdapter& GetAdapter() { return *adapter_; } - - // Creates BT service, and begins listening for remote attempts to connect. - // - // 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 on error. - std::unique_ptr ListenForService( - const std::string& service_name, const std::string& service_uuid) override - ABSL_LOCKS_EXCLUDED(mutex_); - - // 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(MacAddress mac_address) override; - - void AddObserver(Observer* observer) override; - void RemoveObserver(Observer* observer) override; - - private: - absl::Mutex mutex_; - BluetoothAdapter* adapter_; // Our device adapter; read-only. - absl::flat_hash_map sockets_ - ABSL_GUARDED_BY(mutex_); -}; - -} // namespace linux -} // namespace nearby - -#endif // PLATFORM_IMPL_G3_BLUETOOTH_CLASSIC_H_ 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..007b1909 --- /dev/null +++ b/internal/platform/implementation/linux/bluetooth_classic_device.cc @@ -0,0 +1,176 @@ +// 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) { + LOG(INFO) << "Created BluetoothDevice for: " << device -> Address(); + try { + last_known_name_ = device->Alias(); + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_GET_ERROR(device, "Alias", e); + } + try { + MacAddress::FromString(device -> Address(), last_known_address_); + unique_id_ = last_known_address_.address(); + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_GET_ERROR(device, "Address", e); + } +} + +std::string BluetoothDevice::GetName() const { + auto device = device_; + 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_; + if (device == nullptr) { + absl::ReaderMutexLock l(&properties_mutex_); + return last_known_name_; + } + + try { + std::string addr = device->Address(); + { + absl::MutexLock l(&properties_mutex_); + MacAddress::FromString(addr, last_known_address_); + } + return addr; + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_GET_ERROR(device, "Address", e); + return std::string(); + } +} + +std::string BluetoothDevice::GetAddressType() const { + auto device = device_; + if (device == nullptr) return "public"; + + try { + return device->AddressType(); + } catch (const sdbus::Error& e) { + DBUS_LOG_PROPERTY_GET_ERROR(device, "AddressType", e); + return "public"; + } +} + +bool BluetoothDevice::ConnectToProfile(absl::string_view service_uuid) { + auto device = device_; + 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; + } +} + + bool BluetoothDevice::Connect() { + auto device = device_; + if (device == nullptr) return false; + try { + device->Connect(); + return true; + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(device, "Connect", e); + return false; + } +} + MonitoredBluetoothDevice::MonitoredBluetoothDevice( + std::shared_ptr system_bus, + std::shared_ptr device, + ObserverList &observers) + : BluetoothDevice(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) { + LOG(INFO) << __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) { + LOG(INFO) << __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) { + LOG(INFO) + << __func__ << ": " << getObjectPath() + << "Notifying observers about connected status change"; + for (const auto &observer : observers_.GetObservers()) { + observer->DeviceConnectedStateChanged(*this, it->second); + } + } else if ( it -> first == "ServicesResolved"){ + LOG(INFO) << ": ServicesResolved :" << std::string(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..27d0b7e7 --- /dev/null +++ b/internal/platform/implementation/linux/bluetooth_classic_device.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_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: + 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); + + std::string GetName() const override; + std::string GetMacAddress() const override; + std::string GetAddressType() const; + + MacAddress GetAddress() const override { return last_known_address_; } + + std::optional> ServiceData() { + auto device = device_; + if (!device) 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_; + if (!device) 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_; + if (!device) 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_; + if (!device) 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_; + if (device) device->SetPairReplyCallback(std::move(cb)); + } + + bool ConnectToProfile(absl::string_view service_uuid); + bool Connect(); + void MarkLost() { lost_ = true; } + void UnmarkLost() { lost_ = false; } + bool Lost() const { return lost_; } + sdbus::ObjectPath GetObjectPath() {return device_->getObjectPath();} + + 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 MacAddress last_known_address_ ABSL_GUARDED_BY(properties_mutex_); + + std::shared_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..cd854b22 --- /dev/null +++ b/internal/platform/implementation/linux/bluetooth_classic_medium.cc @@ -0,0 +1,185 @@ +// 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 "bluez_agent.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_(nullptr), + devices_(nullptr), + device_watcher_(nullptr), + // agent_manager_(std::make_unique(*system_bus_)), + profile_manager_(nullptr) { + auto shared = + GetSharedBluetoothDevices(system_bus_, adapter_.GetObjectPath()); + observers_ = shared->observers; + devices_ = shared->devices; + profile_manager_ = + std::make_unique(*system_bus_, *devices_); +} + +bool BluetoothClassicMedium::StartDiscovery( + DiscoveryCallback discovery_callback) { + device_watcher_ = std::make_unique( + *system_bus_, adapter_.GetObjectPath(), adapter_, 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 { + LOG(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(); + LOG(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)) { + LOG(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) { + LOG(ERROR) << __func__ << ": Device " << address + << " is no longer known"; + return nullptr; + } + if (!device -> Bonded()) + { + + LOG(ERROR) << __func__ << ": Device " << address + << " is not Bonded"; + } + // Mark as pending BEFORE calling ConnectToProfile to win the race + profile_manager_->MarkPendingOutgoing(service_uuid, address); + + if (!device->ConnectToProfile(service_uuid)) { + profile_manager_->ClearPendingOutgoing(service_uuid, address); + return nullptr; + } + + auto fd = profile_manager_->GetServiceRecordFD(remote_device, service_uuid, + cancellation_flag); + if (!fd.has_value()) { + LOG(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) { + LOG(INFO) << __func__ << ": Creating bluez agent on path: " << "/com/example/bluez_agent" ; + + if (!profile_manager_->ProfileRegistered(service_uuid)) { + if (!profile_manager_->Register(service_name, service_uuid)) { + LOG(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( +MacAddress mac_address) { + // When BLE is discovering, it looks for remote devices to connect to using BT classic. If only + auto device = devices_->get_device_by_address(mac_address.ToString()); + 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..42edf168 --- /dev/null +++ b/internal/platform/implementation/linux/bluetooth_classic_medium.h @@ -0,0 +1,117 @@ +// 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 "bluez_agent.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_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(MacAddress 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 agent_manager_; + 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..22bfad0a --- /dev/null +++ b/internal/platform/implementation/linux/bluetooth_classic_server_socket.cc @@ -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. + +#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()) { + LOG(ERROR) << __func__ << ": server socket has been stopped"; + return nullptr; + } + + LOG(INFO) << __func__ + << ": accepting new connections for service uuid " + << service_uuid_; + + auto pair = profile_manager_.GetServiceRecordFD(service_uuid_, &stopped_); + if (!pair.has_value()) { + if (!stopped_.Cancelled()) + LOG(ERROR) << __func__ + << ": Failed to get a new connection for profile " + << service_uuid_; + return nullptr; + } + + auto [device, fd] = *pair; + LOG(INFO) << __func__ << ": accepted incoming connection for service uuid " << service_uuid_; + return std::make_unique(device, std::move(fd)); +} + +Exception BluetoothServerSocket::Close() { + LOG(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..f3ef9eaa --- /dev/null +++ b/internal/platform/implementation/linux/bluetooth_classic_socket.cc @@ -0,0 +1,332 @@ +// 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" + +#include +#include +#include +#include +#include + +struct SocketWriteCaps { + int so_type = 0; // SOCK_STREAM / SOCK_SEQPACKET / SOCK_DGRAM + size_t max_chunk = 0; // 0 => unknown/unlimited + bool packet_based = false; +}; + +static SocketWriteCaps DetectCapsNoBtHeaders(int fd) { + SocketWriteCaps caps{}; + + socklen_t len = sizeof(caps.so_type); + if (::getsockopt(fd, SOL_SOCKET, SO_TYPE, &caps.so_type, &len) != 0) { + // If we can't detect, behave conservatively like stream. + caps.so_type = SOCK_STREAM; + } + + caps.packet_based = (caps.so_type == SOCK_SEQPACKET || caps.so_type == SOCK_DGRAM); + + // Initial guess for packet-based sockets. This will be refined on EMSGSIZE. + if (caps.packet_based) caps.max_chunk = 1024; // start guess + else caps.max_chunk = 0; // unlimited/stream + + return caps; +} + +namespace nearby { +namespace linux { +Exception Poller::Ready() { + while (true) { + auto ret = poll(fds_, 1, -1); + if (ret < 0) { + if (errno == EAGAIN) continue; + LOG(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) { + LOG(ERROR) << __func__ << ": socket disconnected"; + return {Exception::kIo}; + } + if ((fds_[0].revents & (POLLERR | POLLNVAL)) != 0) { + LOG(ERROR) << __func__ << ": an error occured on the socket"; + return {Exception::kIo}; + } + } +} + + +ExceptionOr BluetoothInputStream::Read(std::int64_t size) { + int fd = fd_raw_.load(); + if (fd < 0) return Exception{Exception::kIo}; + + auto poller = Poller::CreateInputPoller(fd); + + int so_type = 0; + socklen_t sl = sizeof(so_type); + if (::getsockopt(fd, SOL_SOCKET, SO_TYPE, &so_type, &sl) != 0) { + // If unknown, default to stream-ish behavior. + so_type = SOCK_STREAM; + } + const bool packet_based = (so_type == SOCK_SEQPACKET || so_type == SOCK_DGRAM); + + // Sanity: avoid negative / zero sizes + if (size <= 0) return ExceptionOr{ByteArray(std::string())}; + + // ---- Packet-based: read ONE message (recommended) ---- + if (packet_based) { + // Wait for readability + while (true) { + if (fd_raw_.load() != fd) return {Exception::kIo}; + auto result = poller.Ready(); + if (result.Raised()) return result; + if (fd_raw_.load() != fd) return {Exception::kIo}; + + // Peek the next message length without consuming it. + // For seqpacket/dgram, MSG_TRUNC makes recv() return the *full* message length + // even if the buffer is smaller. + ssize_t msg_len = ::recv(fd, nullptr, 0, MSG_PEEK | MSG_TRUNC); + if (msg_len < 0) { + if (errno == EINTR) continue; + if (errno == EAGAIN || errno == EWOULDBLOCK) continue; + if (errno == EBADF) { + LOG(INFO) << __func__ << ": socket was closed during read"; + return {Exception::kIo}; + } + LOG(ERROR) << __func__ << ": error peeking message length: " + << std::strerror(errno); + return {Exception::kIo}; + } + if (msg_len == 0) { + LOG(INFO) << __func__ << ": socket closed (EOF)"; + return {Exception::kIo}; + } + + // Decide how much we will actually read/return. + // If caller asked for 'size', cap to that. + size_t want = static_cast(msg_len); + size_t cap = static_cast(size); + size_t to_read = std::min(want, cap); + + std::string buffer; + buffer.resize(to_read); + + // Now read/consume the message. If the message is larger than to_read, + // the remainder will be discarded by the kernel for seqpacket/dgram. + // We can detect that and treat it as an error (or choose a different policy). + if (fd_raw_.load() != fd) return {Exception::kIo}; + ssize_t n = ::recv(fd, buffer.data(), to_read, 0); + if (n < 0) { + if (errno == EINTR) continue; + if (errno == EAGAIN || errno == EWOULDBLOCK) continue; + if (errno == EBADF) { + LOG(INFO) << __func__ << ": socket was closed during read"; + return {Exception::kIo}; + } + LOG(ERROR) << __func__ << ": error reading data on bluetooth socket: " + << std::strerror(errno); + return {Exception::kIo}; + } + if (n == 0) { + LOG(INFO) << __func__ << ": socket closed (EOF)"; + return {Exception::kIo}; + } + + buffer.resize(static_cast(n)); + + // Detect truncation: if msg_len > size, we truncated/discarded remainder. + if (want > cap) { + LOG(ERROR) << __func__ + << ": incoming packet (" << want + << " bytes) exceeds requested size (" << cap + << "). Packet truncated."; + return {Exception::kIo}; + } + + return ExceptionOr{ByteArray(std::move(buffer))}; + } + } + + // ---- Stream-based: read exactly 'size' bytes (your original behavior) ---- + std::string buffer; + buffer.resize(static_cast(size)); + char* data = buffer.data(); + + size_t total_read = 0; + while (total_read < static_cast(size)) { + if (fd_raw_.load() != fd) return {Exception::kIo}; + auto result = poller.Ready(); + if (result.Raised()) return result; + if (fd_raw_.load() != fd) return {Exception::kIo}; + + ssize_t bytes_read = ::read(fd, + data + total_read, + static_cast(size) - total_read); + if (bytes_read < 0) { + if (errno == EINTR) continue; + if (errno == EAGAIN || errno == EWOULDBLOCK) continue; + if (errno == EBADF) { + LOG(INFO) << __func__ << ": socket was closed during read"; + return {Exception::kIo}; + } + LOG(ERROR) << __func__ << ": error reading data on bluetooth socket: " + << std::strerror(errno); + return {Exception::kIo}; + } + if (bytes_read == 0) { + LOG(INFO) << __func__ << ": socket closed (EOF)"; + return {Exception::kIo}; + } + total_read += static_cast(bytes_read); + } + + return ExceptionOr{ByteArray(std::move(buffer))}; +} + +Exception BluetoothInputStream::Close() { + int fd = fd_raw_.exchange(-1); + if (fd < 0) return {Exception::kSuccess}; // Already closed + ::shutdown(fd, SHUT_RDWR); + fd_.reset(); + return {Exception::kSuccess}; +} + +Exception BluetoothOutputStream::Write(const ByteArray &data) { + int fd = fd_raw_.load(); + if (fd < 0) return Exception{Exception::kIo}; + + auto poller = Poller::CreateOutputPoller(fd); + + size_t total_wrote = 0; + + int so_type = 0; + socklen_t sl = sizeof(so_type); + if (::getsockopt(fd, SOL_SOCKET, SO_TYPE, &so_type, &sl) != 0) { + // If we can’t detect, assume stream semantics (no per-message MTU). + so_type = SOCK_STREAM; + } + + const bool packet_based = (so_type == SOCK_SEQPACKET || so_type == SOCK_DGRAM); + + // Initialize a reasonable starting guess for packet-based sockets. + // This will be refined down on EMSGSIZE. + if (packet_based) { + absl::MutexLock lock(&fd_mutex_); + if (max_chunk_ == 0) max_chunk_ = 1024; + } + + size_t max_chunk = 0; + if (packet_based) { + absl::MutexLock lock(&fd_mutex_); + max_chunk = max_chunk_; + } + + + while (total_wrote < data.size()) { + if (fd_raw_.load() != fd) return {Exception::kIo}; + auto result = poller.Ready(); // should wait for POLLOUT/EPOLLOUT + if (result.Raised()) return result; + if (fd_raw_.load() != fd) return {Exception::kIo}; + + const char *buf = data.data(); + size_t remaining = data.size() - total_wrote; + + size_t to_write = remaining; + if (packet_based) { + // For SEQPACKET/DGRAM, one send() == one packet. + // Cap to discovered “MTU-like” limit to avoid EMSGSIZE. + absl::MutexLock lock(&fd_mutex_); + to_write = std::min(to_write, max_chunk_); + } + + // Prefer send() to avoid SIGPIPE (MSG_NOSIGNAL is Linux). + ssize_t wrote = ::send(fd, + buf + total_wrote, + to_write, +#ifdef MSG_NOSIGNAL + MSG_NOSIGNAL +#else + 0 +#endif + ); + + // If send() isn’t appropriate in your environment, you can swap back to write(). + // ssize_t wrote = ::write(fd_.get(), buf + total_wrote, to_write); + + if (wrote < 0) { + if (errno == EINTR) continue; + if (errno == EAGAIN || errno == EWOULDBLOCK) continue; + + if (errno == EMSGSIZE && packet_based) { + // Our packet is too large; shrink max_chunk_ and retry. + { + absl::MutexLock lock(&fd_mutex_); + if (max_chunk_ > 1) { + max_chunk_ = std::max(1, max_chunk_ / 2); + LOG(INFO) << __func__ << ": EMSGSIZE; reducing max_chunk_ to " + << max_chunk_; + continue; // retry with smaller chunk + } + } + LOG(ERROR) << __func__ << ": EMSGSIZE even at 1 byte"; + return {Exception::kIo}; + } + + if (errno == EBADF || errno == EPIPE) { + LOG(INFO) << __func__ << ": socket was closed during write"; + return {Exception::kIo}; + } + + LOG(ERROR) << __func__ + << ": error writing data on bluetooth socket: " + << std::strerror(errno); + return {Exception::kIo}; + } + + if (wrote == 0) { + // For sockets, 0 usually means peer closed. + LOG(INFO) << __func__ << ": peer closed during write"; + return {Exception::kIo}; + } + + total_wrote += static_cast(wrote); + } + + return {Exception::kSuccess}; +} + +Exception BluetoothOutputStream::Close() { + int fd = fd_raw_.exchange(-1); + if (fd < 0) return {Exception::kSuccess}; // Already closed + ::shutdown(fd, SHUT_RDWR); + 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..96ec49d8 --- /dev/null +++ b/internal/platform/implementation/linux/bluetooth_classic_socket.h @@ -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. + +#ifndef PLATFORM_IMPL_LINUX_BLUETOOTH_SOCKET_H_ +#define PLATFORM_IMPL_LINUX_BLUETOOTH_SOCKET_H_ + +#include +#include +#include + +#include +#include +#include + +#include "absl/synchronization/mutex.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/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); + } + + static Poller CreateInputPoller(int fd) { return Poller(fd, POLLIN); } + + static Poller CreateOutputPoller(int 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; + } + + Poller(int fd, short event) : poll_event_(event) { + fds_[0].fd = fd; + 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)), fd_raw_(fd_.get()) {} + + ExceptionOr Read(std::int64_t size) override; + Exception Close() override; + + private: + sdbus::UnixFd fd_; + std::atomic fd_raw_{-1}; +}; + +class BluetoothOutputStream : public nearby::OutputStream { + public: + explicit BluetoothOutputStream(sdbus::UnixFd fd) + : fd_(std::move(fd)), fd_raw_(fd_.get()) {} + + Exception Write(const ByteArray &data) override; + Exception Flush() override { return {Exception::kSuccess}; } + Exception Close() override; + + private: + mutable absl::Mutex fd_mutex_; + sdbus::UnixFd fd_; + std::atomic fd_raw_{-1}; + + // For packet sockets, discovered max payload per send/write. + // 0 means "unknown", we’ll initialize on first packet write. + size_t max_chunk_ ABSL_GUARDED_BY(fd_mutex_) = 0; +}; + +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..b702556c --- /dev/null +++ b/internal/platform/implementation/linux/bluetooth_devices.cc @@ -0,0 +1,264 @@ +// 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/container/flat_hash_map.h" +#include "absl/strings/substitute.h" +#include "absl/synchronization/mutex.h" +#include "internal/platform/implementation/linux/bluetooth_adapter.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); +absl::Mutex g_shared_devices_lock; +absl::flat_hash_map> + g_shared_devices ABSL_GUARDED_BY(g_shared_devices_lock); + +std::shared_ptr GetSharedBluetoothDevices( + std::shared_ptr system_bus, + const sdbus::ObjectPath& adapter_object_path) { + const std::string key = adapter_object_path; + absl::MutexLock lock(&g_shared_devices_lock); + auto it = g_shared_devices.find(key); + if (it != g_shared_devices.end()) { + if (auto existing = it->second.lock()) { + return existing; + } + } + auto shared = std::make_shared(); + shared->observers = + std::make_shared>(); + shared->devices = std::make_shared( + std::move(system_bus), adapter_object_path, *shared->observers); + g_shared_devices[key] = shared; + return shared; +} + +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_unique_id( + api::ble_v2::BlePeripheral::UniqueId id) +{ + // converting from stoull to mac again + id &= 0x0000FFFFFFFFFFFFULL; // keep 48 bits + std::ostringstream oss; + oss << std::hex << std::setfill('0') << std::setw(12) << id; + std::string hex = oss.str(); // e.g. "aabbccddeeff" + + std::string mac; + for (int i = 0; i < 6; ++i) { + if (i) mac.push_back(':'); + mac.append(hex.substr(i * 2, 2)); + } + return get_device_by_address(mac); +} +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) { + LOG(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) { + LOG(WARNING) << __func__ + << ": received InterfacesRemoved for a device " + "we don't know about: " + << object; + return; + } + + LOG(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; + } + + std::vector existing_device_paths; + + for (const auto& [device_path, interfaces] : objects) { + if (device_path.find(absl::Substitute("$0/dev_", adapter_object_path_)) == 0 && + interfaces.count(org::bluez::Device1_proxy::INTERFACE_NAME) == 1) { + + // Don't remove bonded, paired, connected, or trusted devices + bool should_skip = false; + auto device_interface_it = interfaces.find(org::bluez::Device1_proxy::INTERFACE_NAME); + if (device_interface_it != interfaces.end()) { + const auto& properties = device_interface_it->second; + + auto check_bool_property = [&properties](const std::string& prop_name) -> bool { + auto it = properties.find(prop_name); + if (it != properties.end()) { + try { + return it->second.get(); + } catch (...) { + return false; + } + } + return false; + }; + + if (check_bool_property("Bonded") || + check_bool_property("Paired") || + check_bool_property("Connected") || + check_bool_property("Trusted")) { + should_skip = true; + LOG(INFO) << __func__ << ": Skipping device " << device_path + << " (bonded/paired/connected/trusted)"; + } + } + + if (!should_skip) { + existing_device_paths.push_back(device_path); + } + } + } + + // Remove existing devices - they will be immediately re-discovered + // This triggers InterfacesAdded signals which properly invoke discovery callbacks + for (const auto& device_path : existing_device_paths) { + LOG(INFO) << __func__ << ": Refreshing existing device " << device_path; + if (!adapter_.RemoveDeviceByObjectPath(device_path)) { + LOG(WARNING) << __func__ << ": Failed to remove device " << device_path; + } + } +} + +} // 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..9454ed37 --- /dev/null +++ b/internal/platform/implementation/linux/bluetooth_devices.h @@ -0,0 +1,150 @@ +// 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 + +#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 BluetoothAdapter; + +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); + + 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_); + + // DEBUG + void dump_devices() ABSL_LOCKS_EXCLUDED(devices_by_path_lock_) { + absl::ReaderMutexLock lock(&devices_by_path_lock_); + LOG(INFO) << "Dumping BluetoothDevices:"; + for (const auto& [path, device] : devices_by_path_) { + LOG(INFO) << " - Device path: " << path << " , Name: " << device->GetName(); + } + } + + + 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_); +}; + +struct SharedBluetoothDevices { + std::shared_ptr devices; + std::shared_ptr> observers; +}; + +std::shared_ptr GetSharedBluetoothDevices( + std::shared_ptr system_bus, + const sdbus::ObjectPath& adapter_object_path); + +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, + BluetoothAdapter &adapter, + std::shared_ptr devices, + std::unique_ptr + discovery_callback, + std::shared_ptr> + observers) + : ProxyInterfaces(system_bus, "org.bluez", "/"), + adapter_object_path_(adapter_object_path), + adapter_(adapter), + 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, + BluetoothAdapter &adapter, + std::shared_ptr devices) + : DeviceWatcher(system_bus, adapter_object_path, adapter, 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_; + BluetoothAdapter &adapter_; + 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..607798c0 --- /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; + + LOG(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().ToString())), + 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..48c3532d --- /dev/null +++ b/internal/platform/implementation/linux/bluez.cc @@ -0,0 +1,85 @@ +// 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 gatt_profile_object_path(absl::string_view service_uuid) { + return absl::Substitute( + "$0/profile_$1", + NEARBY_BLE_GATT_PROFILE_PATH_ROOT, + 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 5; + case api::ble_v2::TxPowerLevel::kHigh: + return 10; // Increased from 6 to 10 dBm (maximum for most adapters) + } +} + +} // 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..cd037f7b --- /dev/null +++ b/internal/platform/implementation/linux/bluez.h @@ -0,0 +1,89 @@ +// 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 { \ + LOG(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"; + + static constexpr const char *NEARBY_BLE_GATT_PROFILE_PATH_ROOT = + "/com/google/nearby/medium/ble/gatt/profile"; + 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_profile_object_path(absl::string_view service_uuid); +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..2bc71f36 --- /dev/null +++ b/internal/platform/implementation/linux/bluez_advertisement_monitor.cc @@ -0,0 +1,73 @@ +#include "internal/platform/implementation/linux/bluez_advertisement_monitor.h" + +#include + +#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()) { + LOG(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())); + } + auto id = std::stoull(std::regex_replace(peripheral->GetMacAddress(), + std::regex("[:\\-]"), ""), nullptr, 16); + scan_callback_.advertisement_found_cb(id, 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..ea251f85 --- /dev/null +++ b/internal/platform/implementation/linux/bluez_advertisement_monitor.h @@ -0,0 +1,97 @@ +// 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 { + LOG(INFO) <<__func__ << ": bluez advertisement monitor activated at path: " << getObjectPath(); + 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 127; }; + int16_t RSSIHighThreshold() override { + return 127; + } + 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..196b5e96 --- /dev/null +++ b/internal/platform/implementation/linux/bluez_advertisement_monitor_manager.h @@ -0,0 +1,89 @@ +// 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 ::nearby::linux::BluetoothAdapter &); + AdvertisementMonitorManager( + sdbus::IConnection &system_bus, + const ::nearby::linux::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 ::nearby::linux::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) { + LOG(ERROR) << __func__ << ": Adapter object no longer exists " + << adapter.GetObjectPath(); + return nullptr; + } + + if (objects[adapter.GetObjectPath()].count( + org::bluez::AdvertisementMonitorManager1_proxy::INTERFACE_NAME) == + 0) { + LOG(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..e3dc8130 --- /dev/null +++ b/internal/platform/implementation/linux/bluez_gatt_characteristic_server.cc @@ -0,0 +1,240 @@ +// 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) { + LOG(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_; + // TODO: enable the callback + // 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: enable the callback + // 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..b6613755 --- /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(); + LOG(INFO) + << __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_profile.h b/internal/platform/implementation/linux/bluez_gatt_profile.h new file mode 100644 index 00000000..0c34063f --- /dev/null +++ b/internal/platform/implementation/linux/bluez_gatt_profile.h @@ -0,0 +1,63 @@ +// +// Created by root on 1/11/26. +// + +#ifndef WORKSPACE_BLUEZ_GATT_PROFILE_H +#define WORKSPACE_BLUEZ_GATT_PROFILE_H + +#include +#include +#include + +#include "generated/dbus/bluez/gatt_profile_server.h" +#include "internal/platform/logging.h" + +#include +#include +#include + +namespace nearby { + namespace linux { + namespace bluez { + class GattProfile + : public sdbus::AdaptorInterfaces { + public: + GattProfile(const GattProfile &) = delete; + GattProfile(GattProfile &&) = delete; + GattProfile &operator=(const GattProfile &) = delete; + GattProfile &operator=(GattProfile &&) = delete; + + GattProfile(sdbus::IConnection &system_bus, + sdbus::ObjectPath profile_path, std::string service_uuid) + : AdaptorInterfaces(system_bus, profile_path), + uuids_({service_uuid}) + + { + registerAdaptor(); + } + ~GattProfile() { unregisterAdaptor(); } + + void Release() override + { + LOG(INFO) << __func__ << ": Gatt profile released"; + }; + private: + std::string ToLowerAscii(std::string s) { + std::transform(s.begin(), s.end(), s.begin(), + [](unsigned char c) { return static_cast(std::tolower(c)); }); + return s; + } + std::vector UUIDs() override + { + LOG(INFO)<< __func__ << ": UUIDs called, returned: " << ToLowerAscii(uuids_[0]); + return uuids_; + }; + + std::vector uuids_; + + }; + } // namespace bluez + } // namespace linux +} // namespace nearby + +#endif //WORKSPACE_BLUEZ_GATT_PROFILE_H \ No newline at end of file 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..28700bca --- /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) { + LOG(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..582d6abb --- /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(); + LOG(INFO) << __func__ << ": Created a " + << org::bluez::GattService1_adaptor::INTERFACE_NAME + << " object at " << getObjectPath(); + } + + ~GattServiceServer() { + absl::MutexLock lock(&characterstics_mutex_); + for (auto &[_uuid, characteristic] : characteristics_) { + LOG(INFO) << __func__ << ": Removing characteristic " + << characteristic->getObjectPath(); + try { + characteristic->emitInterfacesRemovedSignal( + {org::bluez::GattCharacteristic1_adaptor::INTERFACE_NAME}); + } catch (const sdbus::Error &e) { + LOG(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..ddf3264d --- /dev/null +++ b/internal/platform/implementation/linux/bluez_le_advertisement.cc @@ -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. + +#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 { + std::string BytesToHexString(const std::vector& bytes) { + std::ostringstream oss; + oss << std::hex << std::setfill('0'); + for (uint8_t b : bytes) { + oss << std::setw(2) << static_cast(b); + } + return oss.str(); + } +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); + // service_uuids_.push_back("0000FE2C-0000-1000-8000-00805F9B34FB"); + // service_uuids_.push_back("0000FE2C-0000-1000-8000-00805F9B34FB"); + for (size_t i = 0; i < data.size(); i++) { + data_bytes[i] = bytes[i]; + } + // LOG(INFO)<< __func__ << ": " << uuid_string; + // LOG(INFO)<< __func__ << ": " << BytesToHexString(data_bytes); + service_data_.insert({uuid_string, std::move(data_bytes)}); + // service_data_.insert({"0000FE2C-0000-1000-8000-00805F9B34FB", std::move(data_bytes)}); + } + + registerAdaptor(); + + LOG(INFO) << __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..219e48a8 --- /dev/null +++ b/internal/platform/implementation/linux/bluez_le_advertisement.h @@ -0,0 +1,117 @@ +// 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 { + LOG(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::map ScanResponseServiceData() override { + return {}; + } + 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/bluez_le_bearer_client.h b/internal/platform/implementation/linux/bluez_le_bearer_client.h new file mode 100644 index 00000000..571bcf6d --- /dev/null +++ b/internal/platform/implementation/linux/bluez_le_bearer_client.h @@ -0,0 +1,79 @@ +// Copyright 2024 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_LE_BEARER_CLIENT_H_ +#define PLATFORM_IMPL_LINUX_LE_BEARER_CLIENT_H_ + +#include +#include + +#include +#include +#include + +#include "absl/functional/any_invocable.h" +#include "absl/synchronization/mutex.h" +#include "internal/platform/implementation/linux/generated/dbus/bluez/le_bearer_client.h" + +namespace nearby { +namespace linux { +namespace bluez { + +class LEBearerClient + : public sdbus::ProxyInterfaces { + public: + LEBearerClient(std::shared_ptr system_bus, + sdbus::ObjectPath bearer_path) + : ProxyInterfaces(*system_bus, "org.bluez", std::move(bearer_path)), + system_bus_(std::move(system_bus)) { + registerProxy(); + } + ~LEBearerClient() { unregisterProxy(); } + + void SetDisconnectedCallback( + absl::AnyInvocable cb) + ABSL_LOCKS_EXCLUDED(disconnect_callback_lock_) { + absl::MutexLock l(&disconnect_callback_lock_); + on_disconnected_cb_ = std::move(cb); + } + + void ResetDisconnectedCallback() + ABSL_LOCKS_EXCLUDED(disconnect_callback_lock_) { + absl::MutexLock l(&disconnect_callback_lock_); + on_disconnected_cb_ = nullptr; + } + + protected: + void onDisconnected(const std::string& reason, + const std::string& message) override + ABSL_LOCKS_EXCLUDED(disconnect_callback_lock_) { + absl::ReaderMutexLock l(&disconnect_callback_lock_); + if (on_disconnected_cb_ != nullptr) { + on_disconnected_cb_(reason, message); + } + } + + private: + std::shared_ptr system_bus_; + absl::Mutex disconnect_callback_lock_; + absl::AnyInvocable + on_disconnected_cb_ ABSL_GUARDED_BY(disconnect_callback_lock_) = nullptr; +}; + +} // namespace bluez +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPL_LINUX_LE_BEARER_CLIENT_H_ 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 index 334aeceb..bdd1d926 100644 --- a/internal/platform/implementation/linux/crypto.cc +++ b/internal/platform/implementation/linux/crypto.cc @@ -1,5 +1,3 @@ -// ...existing code... -// filepath: /workspace/internal/platform/implementation/linux/crypto.cc // Copyright 2020 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,9 +19,10 @@ #include "absl/strings/string_view.h" #include "internal/platform/byte_array.h" + #include -// Function implementations for platform/api/crypto.h. +// Function implementations for platform/implementation/crypto.h. namespace nearby { @@ -53,5 +52,3 @@ ByteArray Crypto::Sha256(absl::string_view input) { } } // namespace nearby - -// ...existing code... 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..ac48f5dd --- /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 { \ + LOG(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 { \ + LOG(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 { \ + LOG(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, sdbus::ObjectPath path) + : AdaptorInterfaces(system_bus, path) { + 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 index bf2ab9a5..d0955a71 100644 --- a/internal/platform/implementation/linux/device_info.cc +++ b/internal/platform/implementation/linux/device_info.cc @@ -1,60 +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 #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/base/file_path.h" +#include "internal/platform/logging.h" -namespace nearby -{ - namespace linux - { - // TODO: Add proper implementations to grab device names and types from D-bus +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); +} - std::optional DeviceInfo::GetOsDeviceName() const - { - return "TestLinux" ; - }; - api::DeviceInfo::DeviceType DeviceInfo::GetDeviceType() const - { - return api::DeviceInfo::DeviceType::kLaptop; - }; - std::optional DeviceInfo::GetDownloadPath() const - { - char* download_path = getenv("XDG_DOWNLOAD_DIR"); - if (download_path == nullptr) - { - download_path = getenv("HOME"); - if (download_path != nullptr) - { - std::string path = std::string(download_path) + "/Downloads"; - return FilePath(path); - } - } - return FilePath(std::string(download_path)); - }; - std::optional DeviceInfo::GetLocalAppDataPath() const - { - char* dir = getenv("XDG_STATE_HOME"); - if (dir == nullptr) - { - return FilePath("/tmp"); - } - return FilePath(std::string(dir)).append(FilePath("com.google.nearby")) ; - } - std::optional DeviceInfo::GetCommonAppDataPath() const - { - return GetLocalAppDataPath(); - }; - std::optional DeviceInfo::GetTemporaryPath() const - { - return FilePath("/tmp"); - } - std::optional DeviceInfo::GetLogPath() const - { - return FilePath("/tmp/nearby/logs"); - }; - std::optional DeviceInfo::GetCrashDumpPath() const - { - return FilePath("/tmp/nearby/crashdump"); - } +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 { + return avahi.GetHostNameFqdn(); + } 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::GetDownloadPath() const { + char *dir = getenv("XDG_DOWNLOAD_DIR"); + return FilePath(std::string(dir)); +} + +std::optional DeviceInfo::GetLocalAppDataPath() const { + char *dir = getenv("XDG_CONFIG_HOME"); + if (dir == nullptr) { + return FilePath("/tmp"); + } + return FilePath(std::string((std::filesystem::path(std::string(dir)) / "Google Nearby"))); +} + +std::optional DeviceInfo::GetTemporaryPath() const { + char *dir = getenv("XDG_RUNTIME_PATH"); + if (dir == nullptr) { + return FilePath("/tmp"); + } + return FilePath(std::string(std::filesystem::path(std::string(dir)) / "Google Nearby")); +} + +std::optional DeviceInfo::GetLogPath() const { + char *dir = getenv("XDG_STATE_HOME"); + if (dir == nullptr) { + return FilePath("/tmp"); + } + return FilePath(std::string(std::filesystem::path(std::string(dir)) / "Google Nearby" / "logs")); +} + +std::optional DeviceInfo::GetCrashDumpPath() const { + char *dir = getenv("XDG_STATE_HOME"); + if (dir == nullptr) { + return FilePath("/tmp"); + } + return FilePath(std::string(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()) { + LOG(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 index d1982788..a3e256f8 100644 --- a/internal/platform/implementation/linux/device_info.h +++ b/internal/platform/implementation/linux/device_info.h @@ -1,4 +1,4 @@ -// Copyright 2022 Google LLC +// 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. @@ -12,58 +12,152 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef PLATFORM_IMPL_LINUX_INFO_H_ -#define PLATFORM_IMPL_LINUX_INFO_H_ +#ifndef PLATFORM_IMPL_LINUX_DEVICE_INFO_H_ +#define PLATFORM_IMPL_LINUX_DEVICE_INFO_H_ -#include #include #include -#include +#include +#include +#include +#include + +#include "absl/container/flat_hash_map.h" #include "absl/strings/string_view.h" -#include "internal/base/file_path.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 DeviceInfo: public api::DeviceInfo { +class CurrentUserSession final + : public sdbus::ProxyInterfaces { public: - ~DeviceInfo() = default; + 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); - // Gets device name. std::optional GetOsDeviceName() const override; api::DeviceInfo::DeviceType GetDeviceType() const override; - api::DeviceInfo::OsType GetOsType() const override - { - return api::DeviceInfo::OsType::kWindows; //TODO: should probably change to linux - }; + api::DeviceInfo::OsType GetOsType() const override { + return api::DeviceInfo::OsType::kWindows; // Or ChromeOS? + } - // Gets known paths of current user. std::optional GetDownloadPath() const override; std::optional GetLocalAppDataPath() const override; - std::optional GetCommonAppDataPath() 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; - // Monitor screen status - bool IsScreenLocked() const override - { - return false; - }; + bool IsScreenLocked() const override; void RegisterScreenLockedListener( absl::string_view listener_name, - std::function callback) override {return;}; + std::function callback) override { + current_user_session_->RegisterScreenLockedListener(listener_name, + std::move(callback)); + } void UnregisterScreenLockedListener( - absl::string_view listener_name) override { return;}; + absl::string_view listener_name) override { + current_user_session_->UnregisterScreenLockedListener(listener_name); + } - // Control device sleep - bool PreventSleep() override {return true;}; - bool AllowSleep() override { return true;}; + 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_INFO_H_ +#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..e8962239 --- /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_) { + LOG(INFO) << "Warning: " << __func__ + << ": Attempt to execute on a shut down pool."; + return; + } + + if (runnable == nullptr) { + LOG(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..1cc1d1cd --- /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) { + LOG(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) { + LOG(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) { + LOG(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/avahi-proxy.h b/internal/platform/implementation/linux/generated/avahi-proxy.h deleted file mode 100644 index b59d99f1..00000000 --- a/internal/platform/implementation/linux/generated/avahi-proxy.h +++ /dev/null @@ -1,95 +0,0 @@ - -/* - * This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT! - */ - -#ifndef __sdbuscpp___home_lasan_Dev_nearby_latest_internal_platform_implementation_linux_generated_avahi_proxy_h__proxy__H__ -#define __sdbuscpp___home_lasan_Dev_nearby_latest_internal_platform_implementation_linux_generated_avahi_proxy_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) - { - } - - Server_proxy(const Server_proxy&) = delete; - Server_proxy& operator=(const Server_proxy&) = delete; - Server_proxy(Server_proxy&&) = default; - Server_proxy& operator=(Server_proxy&&) = default; - - ~Server_proxy() = default; - -public: - sdbus::ObjectPath ServiceBrowserNew(const int32_t& interface, const int32_t& protocol, const std::string& type, const std::string& domain) - { - sdbus::ObjectPath result; - proxy_->callMethod("ServiceBrowserNew").onInterface(INTERFACE_NAME).withArguments(interface, protocol, type, domain).storeResultsTo(result); - return result; - } - - std::tuple>> 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) - { - std::tuple>> result; - proxy_->callMethod("ResolveService").onInterface(INTERFACE_NAME).withArguments(interface, protocol, name, type, domain, aprotocol).storeResultsTo(result); - return result; - } - -private: - sdbus::IProxy* proxy_; -}; - -}}} // namespaces - -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){ this->onItemNew(interface, protocol, name, type, domain); }); - 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){ this->onItemRemove(interface, protocol, name, type, domain); }); - } - - ServiceBrowser_proxy(const ServiceBrowser_proxy&) = delete; - ServiceBrowser_proxy& operator=(const ServiceBrowser_proxy&) = delete; - ServiceBrowser_proxy(ServiceBrowser_proxy&&) = default; - ServiceBrowser_proxy& operator=(ServiceBrowser_proxy&&) = default; - - ~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) = 0; - virtual void onItemRemove(const int32_t& interface, const int32_t& protocol, const std::string& name, const std::string& type, const std::string& domain) = 0; - -public: - void Free() - { - proxy_->callMethod("Free").onInterface(INTERFACE_NAME); - } - -private: - sdbus::IProxy* proxy_; -}; - -}}} // namespaces - -#endif diff --git a/internal/platform/implementation/linux/generated/avahi.xml b/internal/platform/implementation/linux/generated/avahi.xml deleted file mode 100644 index 9d5a3d81..00000000 --- a/internal/platform/implementation/linux/generated/avahi.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/internal/platform/implementation/linux/generated/bluez_adapter_client_glue.h b/internal/platform/implementation/linux/generated/bluez_adapter_client_glue.h deleted file mode 100644 index b2198797..00000000 --- a/internal/platform/implementation/linux/generated/bluez_adapter_client_glue.h +++ /dev/null @@ -1,184 +0,0 @@ - -/* - * This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT! - */ - -#ifndef __sdbuscpp___home_lasan_Dev_nearby_latest_internal_platform_implementation_linux_generated_bluez_adapter_client_glue_h__proxy__H__ -#define __sdbuscpp___home_lasan_Dev_nearby_latest_internal_platform_implementation_linux_generated_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(const Adapter1_proxy&) = delete; - Adapter1_proxy& operator=(const Adapter1_proxy&) = delete; - Adapter1_proxy(Adapter1_proxy&&) = default; - Adapter1_proxy& operator=(Adapter1_proxy&&) = default; - - ~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() const - { - 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() const - { - 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() const - { - 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() const - { - 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/bluez_client_glue.h b/internal/platform/implementation/linux/generated/bluez_client_glue.h deleted file mode 100644 index 44e253b3..00000000 --- a/internal/platform/implementation/linux/generated/bluez_client_glue.h +++ /dev/null @@ -1,46 +0,0 @@ - -/* - * This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT! - */ - -#ifndef __sdbuscpp___home_lasan_Dev_nearby_latest_internal_platform_implementation_linux_generated_bluez_client_glue_h__proxy__H__ -#define __sdbuscpp___home_lasan_Dev_nearby_latest_internal_platform_implementation_linux_generated_bluez_client_glue_h__proxy__H__ - -#include -#include -#include - -namespace org { -namespace freedesktop { -namespace DBus { - -class ObjectManager_proxy -{ -public: - static constexpr const char* INTERFACE_NAME = "org.freedesktop.DBus.ObjectManager"; - -protected: - ObjectManager_proxy(sdbus::IProxy& proxy) - : proxy_(&proxy) - { - proxy_->uponSignal("InterfacesAdded").onInterface(INTERFACE_NAME).call([this](const sdbus::ObjectPath& object_path, const std::map>& interfaces_and_properties){ this->onInterfacesAdded(object_path, interfaces_and_properties); }); - proxy_->uponSignal("InterfacesRemoved").onInterface(INTERFACE_NAME).call([this](const sdbus::ObjectPath& object_path, const std::vector& interfaces){ this->onInterfacesRemoved(object_path, interfaces); }); - } - - ObjectManager_proxy(const ObjectManager_proxy&) = delete; - ObjectManager_proxy& operator=(const ObjectManager_proxy&) = delete; - ObjectManager_proxy(ObjectManager_proxy&&) = default; - ObjectManager_proxy& operator=(ObjectManager_proxy&&) = default; - - ~ObjectManager_proxy() = default; - - virtual void onInterfacesAdded(const sdbus::ObjectPath& object_path, const std::map>& interfaces_and_properties) = 0; - virtual void onInterfacesRemoved(const sdbus::ObjectPath& object_path, const std::vector& interfaces) = 0; - -private: - sdbus::IProxy* proxy_; -}; - -}}} // namespaces - -#endif diff --git a/internal/platform/implementation/linux/generated/bluez_device_client_glue.h b/internal/platform/implementation/linux/generated/bluez_device_client_glue.h deleted file mode 100644 index 76c69bb1..00000000 --- a/internal/platform/implementation/linux/generated/bluez_device_client_glue.h +++ /dev/null @@ -1,225 +0,0 @@ - -/* - * This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT! - */ - -#ifndef __sdbuscpp___home_lasan_Dev_nearby_latest_internal_platform_implementation_linux_generated_bluez_device_client_glue_h__proxy__H__ -#define __sdbuscpp___home_lasan_Dev_nearby_latest_internal_platform_implementation_linux_generated_bluez_device_client_glue_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) - { - proxy_->uponSignal("Disconnected").onInterface(INTERFACE_NAME).call([this](const std::string& name, const std::string& message){ this->onDisconnected(name, message); }); - } - - Device1_proxy(const Device1_proxy&) = delete; - Device1_proxy& operator=(const Device1_proxy&) = delete; - Device1_proxy(Device1_proxy&&) = default; - Device1_proxy& operator=(Device1_proxy&&) = default; - - ~Device1_proxy() = default; - - virtual void onDisconnected(const std::string& name, const std::string& message) = 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); - } - - void Pair() - { - proxy_->callMethod("Pair").onInterface(INTERFACE_NAME); - } - - 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); - } - - bool CablePairing() - { - return proxy_->getProperty("CablePairing").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/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_profile_server.h b/internal/platform/implementation/linux/generated/dbus/bluez/gatt_profile_server.h new file mode 100644 index 00000000..865e76bd --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/bluez/gatt_profile_server.h @@ -0,0 +1,48 @@ + +/* + * This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT! + */ + +#ifndef __sdbuscpp___home_lasan_Dev_nearby_latest_internal_platform_implementation_linux_generated_dbus_bluez_gatt_profile_server_h__adaptor__H__ +#define __sdbuscpp___home_lasan_Dev_nearby_latest_internal_platform_implementation_linux_generated_dbus_bluez_gatt_profile_server_h__adaptor__H__ + +#include +#include +#include + +namespace org { +namespace bluez { + +class GattProfile1_adaptor +{ +public: + static constexpr const char* INTERFACE_NAME = "org.bluez.GattProfile1"; + +protected: + GattProfile1_adaptor(sdbus::IObject& object) + : object_(&object) + { + object_->registerMethod("Release").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->Release(); }); + object_->registerProperty("UUIDs").onInterface(INTERFACE_NAME).withGetter([this](){ return this->UUIDs(); }); + } + + GattProfile1_adaptor(const GattProfile1_adaptor&) = delete; + GattProfile1_adaptor& operator=(const GattProfile1_adaptor&) = delete; + GattProfile1_adaptor(GattProfile1_adaptor&&) = default; + GattProfile1_adaptor& operator=(GattProfile1_adaptor&&) = default; + + ~GattProfile1_adaptor() = default; + +private: + virtual void Release() = 0; + +private: + virtual std::vector UUIDs() = 0; + +private: + sdbus::IObject* object_; +}; + +}} // 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..9838e71a --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/bluez/le_advertisement_server.h @@ -0,0 +1,67 @@ + +/* + * 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("ScanResponseServiceData").onInterface(INTERFACE_NAME).withGetter([this](){ return this->ScanResponseServiceData(); }); + 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::map ScanResponseServiceData() = 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/le_bearer_client.h b/internal/platform/implementation/linux/generated/dbus/bluez/le_bearer_client.h new file mode 100644 index 00000000..55d2d46b --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/bluez/le_bearer_client.h @@ -0,0 +1,76 @@ + +/* + * This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT! + */ + +#ifndef __sdbuscpp___home_lasan_Dev_nearby_latest_internal_platform_implementation_linux_generated_dbus_bluez_le_bearer_client_h__proxy__H__ +#define __sdbuscpp___home_lasan_Dev_nearby_latest_internal_platform_implementation_linux_generated_dbus_bluez_le_bearer_client_h__proxy__H__ + +#include +#include +#include + +namespace org { +namespace bluez { +namespace Bearer { + +class LE1_proxy +{ +public: + static constexpr const char* INTERFACE_NAME = "org.bluez.Bearer.LE1"; + +protected: + LE1_proxy(sdbus::IProxy& proxy) + : proxy_(&proxy) + { + proxy_->uponSignal("Disconnected").onInterface(INTERFACE_NAME).call([this](const std::string& reason, const std::string& message){ this->onDisconnected(reason, message); }); + } + + LE1_proxy(const LE1_proxy&) = delete; + LE1_proxy& operator=(const LE1_proxy&) = delete; + LE1_proxy(LE1_proxy&&) = default; + LE1_proxy& operator=(LE1_proxy&&) = default; + + ~LE1_proxy() = default; + + virtual void onDisconnected(const std::string& reason, const std::string& message) = 0; + +public: + void Connect() + { + proxy_->callMethod("Connect").onInterface(INTERFACE_NAME); + } + + void Disconnect() + { + proxy_->callMethod("Disconnect").onInterface(INTERFACE_NAME); + } + +public: + sdbus::ObjectPath Adapter() + { + return proxy_->getProperty("Adapter").onInterface(INTERFACE_NAME); + } + + bool Paired() + { + return proxy_->getProperty("Paired").onInterface(INTERFACE_NAME); + } + + bool Bonded() + { + return proxy_->getProperty("Bonded").onInterface(INTERFACE_NAME); + } + + bool Connected() + { + return proxy_->getProperty("Connected").onInterface(INTERFACE_NAME); + } + +private: + sdbus::IProxy* proxy_; +}; + +}}} // 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.GattProfile1.xml b/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.GattProfile1.xml new file mode 100644 index 00000000..83c46531 --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.GattProfile1.xml @@ -0,0 +1,8 @@ + + + + + + + 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/generated/org.bluez.Adapter1.xml b/internal/platform/implementation/linux/generated/org.bluez.Adapter1.xml deleted file mode 100644 index 1984689b..00000000 --- a/internal/platform/implementation/linux/generated/org.bluez.Adapter1.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/internal/platform/implementation/linux/generated/org.bluez.Device1.xml b/internal/platform/implementation/linux/generated/org.bluez.Device1.xml deleted file mode 100644 index a56ec7dc..00000000 --- a/internal/platform/implementation/linux/generated/org.bluez.Device1.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/internal/platform/implementation/linux/generated/org.bluez.xml b/internal/platform/implementation/linux/generated/org.bluez.xml deleted file mode 100644 index 4071a17c..00000000 --- a/internal/platform/implementation/linux/generated/org.bluez.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/internal/platform/implementation/linux/http_loader.cc b/internal/platform/implementation/linux/http_loader.cc new file mode 100644 index 00000000..06d03c96 --- /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 { + LOG(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) { + LOG(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) { + LOG(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 { + LOG(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/multi_thread_executor.h b/internal/platform/implementation/linux/multi_thread_executor.h deleted file mode 100644 index 606f4ca8..00000000 --- a/internal/platform/implementation/linux/multi_thread_executor.h +++ /dev/null @@ -1,90 +0,0 @@ -#ifndef PLATFORM_IMPL_LINUX_MULTI_THREAD_EXECUTOR_H_ -#define PLATFORM_IMPL_LINUX_MULTI_THREAD_EXECUTOR_H_ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "internal/platform/implementation/submittable_executor.h" -#include "internal/platform/runnable.h" - -namespace nearby { -namespace linux { - -class MultiThreadExecutor : public api::SubmittableExecutor { - public: - explicit MultiThreadExecutor(int max_parallelism) - : shutdown_(false) { - for (int i = 0; i < max_parallelism; ++i) { - workers_.emplace_back([this]() { WorkerLoop(); }); - } - } - - ~MultiThreadExecutor() override { - Shutdown(); - for (auto& worker : workers_) { - if (worker.joinable()) worker.join(); - } - } - void Schedule(Runnable&& runnable, absl::Duration delay) { - if (shutdown_) return; - std::thread([this, runnable = std::move(runnable), delay]() mutable { - std::this_thread::sleep_for(std::chrono::nanoseconds( - absl::ToInt64Nanoseconds(delay))); // delaying execution - DoSubmit(std::move(runnable)); - }).detach(); - } - - void Execute(Runnable&& runnable) override { - DoSubmit(std::move(runnable)); - } - - bool DoSubmit(Runnable&& runnable) override { - { - std::lock_guard lock(mutex_); - if (shutdown_) return false; - tasks_.emplace(std::move(runnable)); - } - cv_.notify_one(); - return true; - } - - void Shutdown() override { - { - std::lock_guard lock(mutex_); - shutdown_ = true; - } - cv_.notify_all(); - } - - private: - void WorkerLoop() { - while (true) { - Runnable task; - { - std::unique_lock lock(mutex_); - cv_.wait(lock, [this] { return shutdown_ || !tasks_.empty(); }); - if (shutdown_ && tasks_.empty()) return; - task = std::move(tasks_.front()); - tasks_.pop(); - } - if (task) task(); - } - } - - std::vector workers_; - std::queue tasks_; - std::mutex mutex_; - std::condition_variable cv_; - std::atomic shutdown_; -}; - -} // namespace linux -} // namespace nearby - -#endif // PLATFORM_IMPL_LINUX_MULTI_THREAD_EXECUTOR_H_ diff --git a/internal/platform/implementation/linux/multi_thread_executor_test.cc b/internal/platform/implementation/linux/multi_thread_executor_test.cc deleted file mode 100644 index 5ce20ed4..00000000 --- a/internal/platform/implementation/linux/multi_thread_executor_test.cc +++ /dev/null @@ -1,114 +0,0 @@ -// filepath: /workspace/internal/platform/implementation/linux/multi_thread_executor_test.cc -// Copyright 2025 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/multi_thread_executor.h" - -#include - -#include "gtest/gtest.h" -#include "absl/synchronization/mutex.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" - -namespace nearby { -namespace linux { - -namespace { -const int kMaxThreads = 4; -} - -TEST(LinuxMultiThreadExecutorTest, ConstructorDestructorWorks) { - MultiThreadExecutor executor(kMaxThreads); -} - -TEST(LinuxMultiThreadExecutorTest, CanExecute) { - absl::CondVar cond; - std::atomic_bool done = false; - MultiThreadExecutor executor(kMaxThreads); - executor.Execute([&done, &cond]() { - done = true; - cond.SignalAll(); - }); - absl::Mutex mutex; - { - absl::MutexLock lock(&mutex); - if (!done) { - cond.WaitWithTimeout(&mutex, absl::Seconds(1)); - } - } - EXPECT_TRUE(done); -} - -TEST(LinuxMultiThreadExecutorTest, JobsExecuteInParallel) { - absl::Mutex mutex; - absl::CondVar thread_cond; - absl::CondVar test_cond; - MultiThreadExecutor executor(kMaxThreads); - int count = 0; - - for (int i = 0; i < kMaxThreads; ++i) { - executor.Execute([&]() { - absl::MutexLock lock(&mutex); - count++; - test_cond.Signal(); - thread_cond.Wait(&mutex); - count--; - test_cond.Signal(); - }); - } - - { - absl::MutexLock lock(&mutex); - while (count < kMaxThreads) { - if (test_cond.WaitWithTimeout(&mutex, absl::Seconds(30))) break; - } - } - - EXPECT_EQ(count, kMaxThreads); - thread_cond.SignalAll(); - - { - absl::MutexLock lock(&mutex); - while (count > 0) { - if (test_cond.WaitWithTimeout(&mutex, absl::Seconds(30))) break; - } - } - EXPECT_EQ(count, 0); -} - -TEST(LinuxMultiThreadExecutorTest, CanScheduleDelayedTask) { - MultiThreadExecutor executor(kMaxThreads); - std::atomic_bool ran = false; - auto start = absl::Now(); - executor.Schedule([&ran]() { ran = true; }, absl::Milliseconds(100)); - // Busy-wait using absl sleep to allow scheduled task to run - for (int i = 0; i < 20 && !ran; ++i) { - absl::SleepFor(absl::Milliseconds(20)); - } - EXPECT_TRUE(ran); - auto elapsed = absl::Now() - start; - EXPECT_GE(absl::ToInt64Milliseconds(elapsed), 80); -} - -TEST(LinuxMultiThreadExecutorTest, ShutdownPreventsSubmit) { - MultiThreadExecutor executor(kMaxThreads); - executor.Shutdown(); - // After Shutdown, DoSubmit should return false when trying to submit work. - bool submitted = executor.DoSubmit([]() {}); - EXPECT_FALSE(submitted); -} - -} // namespace linux -} // namespace nearby 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..bad70919 --- /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: + LOG(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..d8ee63d0 --- /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) { + LOG(INFO) << __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 index 522641fd..91381dec 100644 --- a/internal/platform/implementation/linux/platform.cc +++ b/internal/platform/implementation/linux/platform.cc @@ -1,148 +1,432 @@ -// filepath: /workspace/internal/platform/implementation/linux/platform.cc -// Minimal Linux implementation of ImplementationPlatform. - -#include "internal/platform/implementation/platform.h" +// 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 "bluetooth_adapter.h" -#include "absl/status/status.h" -#include "absl/status/statusor.h" -#include "absl/strings/str_cat.h" +#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 "ble_v2_medium.h" +#include "absl/strings/str_cat.h" + #include "internal/platform/implementation/shared/count_down_latch.h" -#include "internal/platform/implementation/linux/atomics.h" -#include "internal/platform/implementation/linux/multi_thread_executor.h" -#include "internal/platform/implementation/linux/scheduled_executor.h" -#include "internal/platform/implementation/linux/device_info.h" -#include "internal/platform/implementation/shared/posix_mutex.h" -#include "internal/platform/implementation/shared/posix_condition_variable.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 "scheduled_executor.h" +#include "wifi_direct.h" +#include "wifi_hotspot.h" - -#include namespace nearby { namespace api { - -std::string ImplementationPlatform::GetCustomSavePath(const std::string& parent_folder, - const std::string& file_name) { - return absl::StrCat(parent_folder, "/", file_name); +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).string(); } -std::string ImplementationPlatform::GetDownloadPath(const std::string& parent_folder, - const std::string& file_name) { - return absl::StrCat("/tmp/", file_name); +std::string ImplementationPlatform::GetDownloadPath( + const std::string &parent_folder, const std::string &file_name) { + std::filesystem::path downloads; + const char* download_dir = getenv("XDG_DOWNLOAD_DIR"); + + if (download_dir != nullptr) { + downloads = std::filesystem::path(download_dir); + } else { + // Fallback to ~/Downloads if XDG_DOWNLOAD_DIR is not set + const char* home = getenv("HOME"); + if (home != nullptr) { + downloads = std::filesystem::path(home) / "Downloads"; + } else { + downloads = "/tmp/Downloads"; + } + } + + return (downloads / std::filesystem::path(parent_folder).filename() / + std::filesystem::path(file_name).filename()).string(); } -std::string ImplementationPlatform::GetDownloadPath(const std::string& file_name) { - return absl::StrCat("/tmp/", file_name); +std::string ImplementationPlatform::GetDownloadPath( + const std::string &file_name) { + std::filesystem::path downloads; + const char* download_dir = getenv("XDG_DOWNLOAD_DIR"); + + if (download_dir != nullptr) { + downloads = std::filesystem::path(download_dir); + } else { + // Fallback to ~/Downloads if XDG_DOWNLOAD_DIR is not set + const char* home = getenv("HOME"); + if (home != nullptr) { + downloads = std::filesystem::path(home) / "Downloads"; + } else { + downloads = "/tmp/Downloads"; + } + } + + return (downloads / std::filesystem::path(file_name).filename()).string(); } -std::string ImplementationPlatform::GetAppDataPath(const std::string& file_name) { - return absl::StrCat("/tmp/", file_name); +std::string ImplementationPlatform::GetAppDataPath( + const std::string &file_name) { + std::filesystem::path state; + const char* state_home = getenv("XDG_STATE_HOME"); + + if (state_home != nullptr) { + state = std::filesystem::path(state_home); + } else { + // Fallback to ~/.local/state if XDG_STATE_HOME is not set + const char* home = getenv("HOME"); + if (home != nullptr) { + state = std::filesystem::path(home) / ".local" / "state"; + } else { + state = "/tmp/state"; + } + } + + return (state / std::filesystem::path(file_name).filename()).string(); } -OSName ImplementationPlatform::GetCurrentOS() { return OSName::kLinux; } +OSName ImplementationPlatform::GetCurrentOS() { return OSName::kWindows; } -std::unique_ptr ImplementationPlatform::CreateAtomicBoolean(bool initial_value) { - return std::make_unique(initial_value); +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::CreateAtomicUint32( + std::uint32_t value) { + return std::make_unique(value); } -std::unique_ptr ImplementationPlatform::CreateCountDownLatch(std::int32_t count) { +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) { - // Use the shared POSIX mutex implementation. The posix::Mutex is recursive by - // design (uses PTHREAD_MUTEX_RECURSIVE), so return it for both regular and - // recursive modes to use a consistent POSIX implementation across Linux. - return std::make_unique(); +std::unique_ptr ImplementationPlatform::CreateMutex( + Mutex::Mode mode) { + return std::make_unique(mode); } #pragma pop_macro("CreateMutex") -std::unique_ptr ImplementationPlatform::CreateConditionVariable(Mutex* mutex) { - if (mutex == nullptr) return nullptr; - // Expect a posix::Mutex instance here; if it's not, return nullptr. - auto* derived = dynamic_cast(mutex); - if (!derived) return nullptr; - return std::make_unique(derived); +std::unique_ptr +ImplementationPlatform::CreateConditionVariable(api::Mutex *mutex) { + return std::make_unique(mutex); } -std::unique_ptr ImplementationPlatform::CreateInputFile(PayloadId, std::int64_t) { +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) { + LOG(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 nullptr; + // Disabled LogMessage + // 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) { + LOG(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; + } + + LOG(ERROR) << __func__ + << ": couldn't find a bluetooth adapter on this system"; return nullptr; } -std::unique_ptr ImplementationPlatform::CreateInputFile(const std::string&, size_t) { +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::CreateOutputFile(PayloadId) { +std::unique_ptr +ImplementationPlatform::CreateBleV2Medium(api::BluetoothAdapter &adapter) { + // return nullptr; + // TODO: Enable BLEv2 once BlueZ support is added. + return std::make_unique( + dynamic_cast(adapter)); +} + +namespace { +static std::unique_ptr createWifiMedium( + std::shared_ptr nm) { + // return nullptr; + 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) { + LOG(INFO) << __func__ + << ": Found a wireless device at :" << device_path; + return std::make_unique(nm, + device_path); + } + } + } + + LOG(ERROR) << __func__ + << ": couldn't find a wireless device on this system"; + return nullptr; +} +} // namespace + +std::unique_ptr ImplementationPlatform::CreateWifiMedium() { + // return nullptr; + 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) { + LOG(ERROR) << __func__ << ": Could not create a WiFi medium"; + return nullptr; + } + + return std::make_unique( + nm, std::move(wifiMedium)); +} + +std::unique_ptr +ImplementationPlatform::CreateWifiDirectMedium() { + // return nullptr; + auto nm = + std::make_shared(linux::getSystemBusConnection()); + auto wifiMedium = createWifiMedium(nm); + + if (wifiMedium == nullptr) { + LOG(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()); +} + + std::unique_ptr ImplementationPlatform::CreateAwdlMedium() { return nullptr; } -std::unique_ptr ImplementationPlatform::CreateOutputFile(const std::string&) { - return nullptr; -} +absl::StatusOr ImplementationPlatform::SendRequest( + const WebRequest &request) { + if (request.body.size() >= (8 * 1024 * 1024)) { + return absl::Status(absl::StatusCode::kResourceExhausted, + "request body too large"); + } -std::unique_ptr ImplementationPlatform::CreateLogMessage(const char* file, int line, LogMessage::Severity severity) { - return nullptr; -} + CURL *handle = curl_easy_init(); + char errbuf[CURL_ERROR_SIZE]; + errbuf[0] = '\0'; -std::unique_ptr ImplementationPlatform::CreateSingleThreadExecutor() { - return std::make_unique(1); -} + curl_easy_setopt(handle, CURLOPT_URL, request.url.c_str()); + curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, errbuf); -std::unique_ptr ImplementationPlatform::CreateMultiThreadExecutor(std::int32_t max_concurrency) { - return std::make_unique(static_cast(max_concurrency)); -} + 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()); -std::unique_ptr ImplementationPlatform::CreateScheduledExecutor() { - return std::unique_ptr(new linux::ScheduledExecutor()); -} + curl_easy_setopt(handle, CURLOPT_UPLOAD, request.body.c_str()); -std::unique_ptr ImplementationPlatform::CreateAwdlMedium() { return nullptr; } -std::unique_ptr ImplementationPlatform::CreateBluetoothAdapter() -{ - static auto connection = sdbus::createConnection(); - connection -> enterEventLoopAsync(); - return std::make_unique(*connection, "/org/bluez/hci0"); -} -std::unique_ptr ImplementationPlatform::CreateBluetoothClassicMedium(BluetoothAdapter&) { return nullptr; } -std::unique_ptr ImplementationPlatform::CreateBleMedium(BluetoothAdapter&) { return nullptr; } -std::unique_ptr ImplementationPlatform::CreateBleV2Medium(api::BluetoothAdapter&) { return nullptr; } -std::unique_ptr ImplementationPlatform::CreateCredentialStorage() { return nullptr; } -std::unique_ptr ImplementationPlatform::CreateServerSyncMedium() { return nullptr; } -std::unique_ptr ImplementationPlatform::CreateWifiMedium() { return nullptr; } -std::unique_ptr ImplementationPlatform::CreateWifiLanMedium() { return nullptr; } -std::unique_ptr ImplementationPlatform::CreateWifiHotspotMedium() { return nullptr; } -std::unique_ptr ImplementationPlatform::CreateWifiDirectMedium() { return nullptr; } + struct curl_slist *headers_slist = nullptr; -std::unique_ptr ImplementationPlatform::CreateTimer() { return 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"); + } + } -std::unique_ptr ImplementationPlatform::CreateDeviceInfo() { - return std::make_unique(); -} + curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers_slist); -#ifndef NO_WEBRTC -std::unique_ptr ImplementationPlatform::CreateWebRtcMedium() { return nullptr; } -#endif + api::WebResponse response; -absl::StatusOr ImplementationPlatform::SendRequest(const WebRequest& request) { - return absl::UnimplementedError("HTTP loader not implemented on this minimal linux platform"); + if (curl_easy_perform(handle) != CURLE_OK) { + LOG(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 nullptr; +std::unique_ptr +ImplementationPlatform::CreatePreferencesManager(absl::string_view path) { + return std::make_unique(path); } #endif diff --git a/internal/platform/implementation/linux/platform.h b/internal/platform/implementation/linux/platform.h deleted file mode 100644 index 35868ea7..00000000 --- a/internal/platform/implementation/linux/platform.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef WORKSPACE_PLATFORM_H -#define WORKSPACE_PLATFORM_H -#include "internal/platform/implementation/platform.h" - -namespace nearby -{ - namespace linux - { - class Platform: public api::ImplementationPlatform - { - }; - - } - - -} -#endif //WORKSPACE_PLATFORM_H \ No newline at end of file diff --git a/internal/platform/implementation/linux/preferences_manager.cc b/internal/platform/implementation/linux/preferences_manager.cc new file mode 100644 index 00000000..392ee425 --- /dev/null +++ b/internal/platform/implementation/linux/preferences_manager.cc @@ -0,0 +1,285 @@ +// 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 "absl/strings/str_cat.h" + +#include "internal/platform/implementation/linux/preferences_repository.h" +#include "internal/platform/logging.h" +#include "internal/platform/implementation/platform.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() { + std::optional path = + nearby::api::ImplementationPlatform::CreateDeviceInfo() + ->GetLocalAppDataPath(); + if (!path.has_value()) { + path = FilePath("/tmp"); + } + + std::filesystem::path full_path = std::filesystem::path(path->ToString()) / 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_)) { + LOG(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()) { + LOG(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()) { + LOG(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()) { + LOG(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()) { + LOG(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..e549eb02 --- /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; + + LOG(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(); + + LOG(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..e92a6a58 --- /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()) { + LOG(ERROR) << "Preferences loaded was not a valid object: " + << preferences.value().dump(4); + + return json::object(); + } + + return preferences.value(); + } + + LOG(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()) { + LOG(ERROR) << "Successfully recovered from backup."; + return preferences.value(); + } + + LOG(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)) { + LOG(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)) { + LOG(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()) { + LOG(ERROR) << "Preferences saved to disk in corrupted state. " + "Restoring from backup."; + + if (!RestoreFromBackup().has_value()) { + LOG(ERROR) << "Failed to restore preferences file."; + return false; + } + } + } catch (const std::exception& e) { + LOG(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()) { + LOG(ERROR) << "Preferences file corrupted."; + return std::nullopt; + } + + return preferences; + } catch (const std::exception& e) { + LOG(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)) { + LOG(WARNING) + << "Backup requested but no backup preferences file found."; + return std::nullopt; + } + + std::filesystem::rename(full_name_backup, full_name); + + LOG(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 index e69de29b..c044d780 100644 --- a/internal/platform/implementation/linux/scheduled_executor.cc +++ 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_) { + LOG(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_) { + LOG(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; + } + LOG(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 index 6012d0da..90257c8c 100644 --- a/internal/platform/implementation/linux/scheduled_executor.h +++ b/internal/platform/implementation/linux/scheduled_executor.h @@ -1,97 +1,100 @@ +// Copyright 2020 Google LLC // -// Created by root on 10/8/25. +// 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 WORKSPACE_SCHEDULED_EXECUTOR_H -#define WORKSPACE_SCHEDULED_EXECUTOR_H +#ifndef PLATFORM_IMPL_LINUX_SCHEDULED_EXECUTOR_H_ +#define PLATFORM_IMPL_LINUX_SCHEDULED_EXECUTOR_H_ -#include "internal/platform/implementation/scheduled_executor.h" -#include "internal/platform/runnable.h" -#include "internal/platform/implementation/cancelable.h" -#include "internal/platform/implementation/linux/multi_thread_executor.h" -#include -#include +#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 - { - // Minimal ScheduledExecutor implementation for linux. - class ScheduledExecutor : public api::ScheduledExecutor - { - public: - ~ScheduledExecutor() override = default; - ScheduledExecutor() : shutdown_(false), executor_(1) {} +namespace nearby { +namespace linux { - // Schedule a runnable to run after `duration`. Returns a Cancelable which - // can be used to cancel the scheduled task before it runs. - std::shared_ptr Schedule(Runnable&& runnable, - absl::Duration duration) override - { - class ScheduledCancelable : public api::Cancelable { - public: - enum Status { kNotRun, kExecuted, kCanceled }; - ScheduledCancelable() : status_(kNotRun) {} - bool Cancel() override { - Status expected = kNotRun; - return status_.compare_exchange_strong(expected, kCanceled); - } - [[nodiscard]] bool IsCanceled() const { return status_.load() == kCanceled; } - [[nodiscard]] bool MarkExecuted() { - Status expected = kNotRun; - return status_.compare_exchange_strong(expected, kExecuted); - } +#define TIMER_NAME_BUFFER_SIZE 64 - private: - std::atomic status_; - }; +// 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(); - auto cancelable = std::make_shared(); - if (shutdown_.load()) return cancelable; + ~ScheduledExecutor() override = default; - // Move runnable into the thread task. - Runnable task = [this, cancelable, runnable = std::move(runnable)]() mutable { - if (shutdown_.load()) return; - if (cancelable->IsCanceled()) return; - if (!cancelable->MarkExecuted()) return; - // Use executor_ to run the actual runnable. - executor_.Execute(std::move(runnable)); - }; + // 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; - // Spawn a detached thread that sleeps for the duration then runs the task - // through executor_. Using a detached thread is simple and sufficient for - // a minimal implementation. - std::thread([d = duration, t = std::move(task), cancelable, this]() mutable { - if (absl::ToInt64Nanoseconds(d) > 0) { - std::this_thread::sleep_for(std::chrono::nanoseconds( - absl::ToInt64Nanoseconds(d))); - } - if (shutdown_.load()) return; - if (cancelable->IsCanceled()) return; - if (t) t(); - }).detach(); + // Executes the runnable task immedately. + void Execute(Runnable&& runnable) override; - return cancelable; - }; + // Shutdowns the executor, all scheduled task will be cancelled. + void Shutdown() override; - void Execute(Runnable&& runnable) override { - if (shutdown_.load()) return; - executor_.Execute(std::move(runnable)); + 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; } - void Shutdown() override { - if (!shutdown_.exchange(true)) { - executor_.Shutdown(); - } - } - - private: - std::atomic shutdown_; - // Reuse the multi-thread executor implementation for running tasks. - linux::MultiThreadExecutor executor_; + is_cancelled_ = true; + notification_.Notify(); + return true; }; - } -} -#endif //WORKSPACE_SCHEDULED_EXECUTOR_H + + 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..1b75daca --- /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) { + LOG(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) { + LOG(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..9c8ee920 --- /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; + } + + LOG(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 { + LOG(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; + } + + LOG(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 index c6a07f98..f2cc2793 100644 --- a/internal/platform/implementation/linux/system_clock.cc +++ b/internal/platform/implementation/linux/system_clock.cc @@ -1,6 +1,4 @@ -// ...existing code... -// filepath: /workspace/internal/platform/implementation/linux/system_clock.cc -// Copyright 2020 Google LLC +// 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. @@ -14,28 +12,22 @@ // 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" -#include - -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "internal/platform/exception.h" - namespace nearby { // Initialize global system state. void SystemClock::Init() {} -// Returns current elapsed (monotonic) time. +// Returns current absolute time. It is guaranteed to be monotonic. absl::Time SystemClock::ElapsedRealtime() { - const auto now = std::chrono::steady_clock::now().time_since_epoch(); - const auto nanos = - std::chrono::duration_cast(now).count(); - - // Represent monotonic time as an absl::Time value. - // (Anchor is arbitrary; only differences matter for elapsed time.) - return absl::FromUnixNanos(static_cast(nanos)); + return absl::FromUnixNanos( + std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()) + .count()); } // Pauses current thread for the specified duration. @@ -46,4 +38,4 @@ Exception SystemClock::Sleep(absl::Duration duration) { } // 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..a2e983d7 --- /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) { + LOG(ERROR) << __func__ + << ": Error opening socket: " << std::strerror(errno); + return std::nullopt; + } + + LOG(INFO) << __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) { + LOG(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) { + LOG(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) { + LOG(ERROR) << __func__ << ": Error binding to socket: " + << std::strerror(errno); + return std::nullopt; + } + + ret = listen(sock, 0); + if (ret < 0) { + LOG(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) { + LOG(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) { + LOG(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) { + LOG(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..7f6ccd6c --- /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; + } + LOG(WARNING) << __func__ << ": Tried to run a null task."; + continue; + } + task(); + } + }; + + absl::MutexLock l(&threads_mutex_); + if (!threads_.empty()) { + LOG(ERROR) << __func__ << "thread pool is already active"; + return false; + } + + LOG(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_) { + LOG(ERROR) << __func__ << "thread pool has shut down"; + return false; + } + + { + absl::ReaderMutexLock l(&threads_mutex_); + if (threads_.empty()) { + LOG(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..6f5fce5f --- /dev/null +++ b/internal/platform/implementation/linux/timer.cc @@ -0,0 +1,115 @@ +// 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) { + LOG(ERROR) << __func__ << ": Error deleting POSIX timer: " + << std::strerror(errno); + } +} + +bool Timer::Create(int delay, int interval, + absl::AnyInvocable callback) { + if (delay < 0 || interval < 0) { + LOG(ERROR) << __func__ + << ": Delay and interval cannot be negative."; + return false; + } + + absl::MutexLock l(&mutex_); + if (timerid_.has_value()) { + LOG(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) { + LOG(ERROR) << __func__ << ": Error creating POSIX timer: " + << std::strerror(errno); + return false; + } + + if (timer_settime(&timerid, 0, &spec, nullptr) < 0) { + LOG(ERROR) << __func__ << ": Error arming POSIX timer: " + << std::strerror(errno); + if (!timer_delete(&timerid)) { + LOG(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()) { + LOG(WARNING) << __func__ << ": no timer created"; + return true; + } + + if (!timer_delete(&*timerid_)) { + LOG(ERROR) << __func__ << ": error deleting POSIX timer: " + << std::strerror(errno); + return false; + } + + timerid_.reset(); + + 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..3a2e24c3 --- /dev/null +++ b/internal/platform/implementation/linux/timer.h @@ -0,0 +1,51 @@ +// 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_); + + 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..f882741b --- /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) { + LOG(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..712e45e0 --- /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()) { + LOG(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) { + LOG(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()) { + LOG(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..36c7ac46 --- /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()) { + LOG(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..03c27dba --- /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()) { + LOG(ERROR) + << __func__ + << ": Cannot connect to service without an active WiFi hotspot"; + return nullptr; + } + + int sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock < 0) { + LOG(ERROR) << __func__ + << ": Error opening socket: " << std::strerror(errno); + return nullptr; + } + + LOG(INFO) << __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) { + LOG(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()) { + LOG(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()) { + LOG(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) { + LOG(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) { + LOG(ERROR) << __func__ + << ": Error binding to socket: " << std::strerror(errno); + return nullptr; + } + + LOG(INFO) << __func__ << ": Listening for services on " + << ip4addresses[0] << ":" << port << " on device " + << wireless_device_->getObjectPath(); + + ret = listen(sock, 0); + if (ret < 0) { + LOG(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()) { + LOG(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()) { + LOG(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) { + LOG(ERROR) + << __func__ << ": " + << ": timed out while waiting for connection " + << active_conn->getObjectPath() + << " to be activated, last NMActiveConnectionStateReason: " + << reason->ToString(); + DisconnectWifiHotspot(); + return false; + } + + LOG(INFO) << __func__ << ": Started a WiFi hotspot on device " + << wireless_device_->getObjectPath() << " at " + << active_conn->getObjectPath(); + return true; +} + +bool NetworkManagerWifiHotspotMedium::StopWifiHotspot() { + if (!WifiHotspotActive()) { + LOG(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()) { + LOG(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) { + LOG(ERROR) + << __func__ + << ": Could not find an active connection using the access point " + << active_ap_path; + return false; + } + + LOG(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) { + LOG(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()) { + LOG(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..f2a6a150 --- /dev/null +++ b/internal/platform/implementation/linux/wifi_hotspot_server_socket.cc @@ -0,0 +1,114 @@ +// 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/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()) { + LOG(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) { + LOG(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); + + // Poll with timeout to allow checking the closed flag periodically + while (!closed_.load()) { + struct pollfd pfd; + pfd.fd = fd_.get(); + pfd.events = POLLIN; + + // Poll with 1 second timeout + int poll_result = poll(&pfd, 1, 1000); + + if (poll_result < 0) { + if (errno == EINTR) { + continue; // Interrupted, try again + } + LOG(ERROR) << __func__ << ": Error polling socket " << fd_.get() << ": " + << std::strerror(errno); + return nullptr; + } + + if (poll_result == 0) { + // Timeout - check closed flag and continue + continue; + } + + // Data available, try to accept + auto conn = + accept(fd_.get(), reinterpret_cast(&addr), &len); + if (conn < 0) { + if (errno == EBADF || errno == EINVAL) { + // Socket was closed + return nullptr; + } + LOG(ERROR) << __func__ + << ": Error accepting incoming connections on socket " + << fd_.get() << ": " << std::strerror(errno); + return nullptr; + } + + return std::make_unique(conn); + } + + // Socket was closed + return nullptr; +} + +Exception NetworkManagerWifiHotspotServerSocket::Close() { + closed_.store(true); + int fd = fd_.release(); + shutdown(fd, SHUT_RDWR); + auto ret = close(fd); + if (ret < 0) { + LOG(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..3fbd62d7 --- /dev/null +++ b/internal/platform/implementation/linux/wifi_hotspot_server_socket.h @@ -0,0 +1,53 @@ +// 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 + +#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)), + closed_(false) {} + + 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_; + std::atomic closed_; +}; +} // 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..1415a06c --- /dev/null +++ b/internal/platform/implementation/linux/wifi_lan.cc @@ -0,0 +1,228 @@ +// 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()) { + LOG(ERROR) << __func__ << ": service name cannot be empty"; + return std::nullopt; + } + + auto type = nsd_service_info.GetServiceType(); + if (type.empty()) { + LOG(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) { + LOG(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); + LOG(INFO) << __func__ << ": Adding avahi service with service type: " << nsd_service_info.GetServiceType(); + + 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) { + LOG(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) { + LOG(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]; + LOG(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); + LOG(INFO) + << __func__ + << ": Created a new org.freedesktop.Avahi.ServiceBrowser object at " + << browser_object_path << " for service_type: " << service_type; + + 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 { + LOG(INFO) << __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) { + LOG(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..3e5accff --- /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"]; + } + } + } + + LOG(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..454ccdaa --- /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 { + LOG(ERROR) << __func__ << ": " << getObjectPath() + << ": Could not find the Ip4Config object for " + << active_access_point->getObjectPath(); + } + } catch (const sdbus::Error &e) { + LOG(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; + } + + LOG(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) { + LOG(WARNING) << __func__ << ": " << getObjectPath() + << ": timed out waiting for scan to finish"; + } + + ap = SearchBySSIDNoScan(ssid_bytes); + if (ap == nullptr) { + LOG(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) { + LOG(ERROR) << __func__ << ": " << getObjectPath() + << ": Couldn't find SSID " << ssid; + return api::WifiConnectionStatus::kConnectionFailure; + } + + auto connection_id = NewUuidStr(); + if (!connection_id.has_value()) { + LOG(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; + } + + LOG(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) { + LOG(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()) { + LOG(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; + } + + LOG(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()) { + LOG(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) { + LOG(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..a9d43da8 --- /dev/null +++ b/internal/platform/implementation/linux/wifi_medium.h @@ -0,0 +1,140 @@ +// 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 "absl/container/flat_hash_map.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 diff --git a/internal/platform/wifi_lan.cc b/internal/platform/wifi_lan.cc index 92b760b4..6098ba67 100644 --- a/internal/platform/wifi_lan.cc +++ b/internal/platform/wifi_lan.cc @@ -155,7 +155,10 @@ bool WifiLanMedium::StartDiscovery(const std::string& service_id, service_type_to_services_map_.insert( {service_type, absl::flat_hash_set()}); } - + LOG(INFO)<< " : Before calling Start discovery"; + for (const auto& [key, value] : service_type_to_callback_map_) { + LOG(INFO) << "key=" << key << " value=" << value; + } bool success = impl_->StartDiscovery(service_type, std::move(api_callback)); if (!success) { // If failed, then revert back the insertion. diff --git a/sharing/linux/BUILD b/sharing/linux/BUILD new file mode 100644 index 00000000..b5942abe --- /dev/null +++ b/sharing/linux/BUILD @@ -0,0 +1,104 @@ +# Copyright 2025 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 = "nearby_sharing_service_linux", + srcs = [ + "nearby_sharing_service_linux.cc", + "//sharing:nearby_sharing_service.cc", + "//sharing:transfer_metadata.cc", + "//sharing:transfer_metadata_builder.cc", + '//sharing/certificates:nearby_share_certificate_manager.cc', + '//sharing/certificates:nearby_share_decrypted_public_certificate.cc', + '//sharing/certificates:nearby_share_encrypted_metadata_key.cc', + '//sharing/certificates:nearby_share_private_certificate.cc', + '//sharing/local_device_data:nearby_share_local_device_data_manager.cc', + '//sharing:nearby_sharing_settings.cc', + '//sharing/analytics:analytics_recorder.cc', + '//sharing:thread_timer.cc', + '//sharing/certificates:common.cc', + '//sharing/common:nearby_share_prefs.cc', + ], + hdrs = [ + "nearby_sharing_service_linux.h", + "//sharing:transfer_metadata.h", + "//sharing:transfer_metadata_builder.h", + '//sharing:nearby_sharing_service.h', + '//sharing/common:nearby_share_prefs.h', + '//sharing/certificates:nearby_share_certificate_manager.h', + '//sharing/certificates:nearby_share_decrypted_public_certificate.h', + '//sharing/certificates:nearby_share_encrypted_metadata_key.h', + '//sharing/certificates:nearby_share_private_certificate.h', + '//sharing/internal/api:private_certificate_data.h', + '//sharing/local_device_data:nearby_share_local_device_data_manager.h', + '//sharing:nearby_sharing_settings.h', + '//sharing/analytics:analytics_recorder.h', + '//sharing/analytics:analytics_device_settings.h', + '//sharing/analytics:analytics_information.h', + '//sharing/internal/api:preference_manager.h', + '//sharing/internal/public:context.h', + '//sharing/internal/public:pref_names.h', + '//sharing/internal/api:bluetooth_adapter.h', + '//sharing/internal/api:fast_initiation_manager.h', + '//sharing/internal/api:fast_init_ble_beacon.h', + '//sharing/internal/public:connectivity_manager.h', + '//sharing:thread_timer.h', + '//sharing:share_target_discovered_callback.h', + '//sharing:transfer_update_callback.h', + '//sharing/certificates:common.h', + '//sharing/certificates:constants.h', + ], + visibility = ["//visibility:public"], + deps = [ + "//connections:core", + "//connections:core_types", + "//internal/platform:base", + "//internal/platform:comm", + "//internal/platform:types", + "//internal/platform/implementation:platform", + "//internal/platform/implementation/linux:linux", + "//sharing:attachments", + "//sharing:types", + "//sharing/proto:share_cc_proto", + "@com_google_absl//absl/strings", + ], +) + +cc_binary( + name = "nearby_sharing_app", + srcs = ["nearby_sharing_app.cc"], + deps = [ + ":nearby_sharing_service_linux", + "//sharing:attachments", + "//sharing:types", + "//internal/platform:base", + "@com_google_absl//absl/strings", + ], + visibility = ["//visibility:public"], +) + +cc_binary( + name = "simple_example", + srcs = ["simple_example.cc"], + deps = [ + ":nearby_sharing_service_linux", + "//sharing:attachments", + "//sharing:types", + "//internal/platform:base", + "@com_google_absl//absl/strings", + ], + visibility = ["//visibility:public"], +) diff --git a/sharing/linux/IMPLEMENTATION_GUIDE.md b/sharing/linux/IMPLEMENTATION_GUIDE.md new file mode 100644 index 00000000..02b69089 --- /dev/null +++ b/sharing/linux/IMPLEMENTATION_GUIDE.md @@ -0,0 +1,592 @@ +# Nearby Sharing Service Linux - Architecture & Implementation Guide + +## Table of Contents +1. [Architecture Overview](#architecture-overview) +2. [How It Works](#how-it-works) +3. [Implementation Guide](#implementation-guide) +4. [Code Examples](#code-examples) +5. [Best Practices](#best-practices) + +## Architecture Overview + +### Component Hierarchy + +``` +NearbySharingServiceLinux +├── Connections Core (nearby connections layer) +│ ├── ServiceControllerRouter +│ └── Medium Management (BLE, WiFi) +├── Observers (UI/App notifications) +├── Send Surfaces (outgoing transfers) +│ ├── Transfer Callbacks +│ └── Discovery Callbacks +├── Receive Surfaces (incoming transfers) +│ └── Transfer Callbacks +└── Active Transfers + ├── Endpoint Mapping + ├── Transfer State + └── Attachment Container +``` + +### Key Classes + +**NearbySharingServiceLinux**: Main service class +- Manages discovery, advertising, and transfers +- Built on top of Nearby Connections Core +- Handles lifecycle of send/receive surfaces + +**TransferUpdateCallback**: Interface for transfer notifications +- Called on status changes (connecting, in-progress, complete) +- Provides progress information +- Reports errors and completion + +**ShareTargetDiscoveredCallback**: Interface for discovery notifications +- Called when devices are found +- Called when devices are lost +- Called when device info updates + +**AttachmentContainer**: Container for files and text +- Manages multiple attachments +- Supports files, text, and WiFi credentials +- Handles attachment lifecycle + +## How It Works + +### 1. Discovery & Advertising Flow + +#### Sender (Discovers devices): +``` +RegisterSendSurface (Foreground) + ↓ +StartDiscoveryIfNeeded() + ↓ +core_->StartDiscovery() + ↓ +[BLE Scanning Starts] + ↓ +endpoint_found_cb → ParseAdvertisement() + ↓ +ShareTarget created + ↓ +OnShareTargetDiscovered() callback +``` + +#### Receiver (Advertises availability): +``` +RegisterReceiveSurface (Foreground) + ↓ +StartAdvertisingIfNeeded() + ↓ +BuildAdvertisement() + ↓ +core_->StartAdvertising() + ↓ +[BLE Advertising Starts] + ↓ +[Visible to nearby senders] +``` + +### 2. Connection Establishment + +``` +Sender Receiver + | | + | RequestConnection() | + |------------------------------->| + | | connection_initiated_cb + | | (auto or manual accept) + | connection_initiated_cb | + |<-------------------------------| + | | + | AcceptConnection() | AcceptConnection() + |------------------------------->| + |<-------------------------------| + | | + | connection_accepted_cb | connection_accepted_cb + | | + [Connected - Ready for transfer] +``` + +### 3. File Transfer Flow + +``` +Sender Receiver + | | + | SendAttachments() | + | - Create AttachmentContainer | + | - Add FileAttachment | + | | + | RequestConnection() | + |------------------------------->| + | Status: kAwaitingLocalConfirmation + | | + | | Accept() + | | + | AcceptConnection() | AcceptConnection() + | + PayloadListener | + PayloadListener + | | + | Status: kConnecting | + | | + | Send Payloads | + |=============================> | + | (File data chunks) | + | | + | Status: kInProgress | + | Progress: 0% → 100% | + | | + | payload_progress_cb | payload_progress_cb + | | + | Status: kComplete | + | | +``` + +### 4. Advertisement Format + +The service creates custom BLE advertisements with device information: + +``` +Byte Layout: +[0] Header Byte + - Bits 7-5: Version (3 bits) + - Bit 4: Visibility (0=visible, 1=hidden) + - Bits 3-1: Device Type (3 bits) + - Bit 0: Reserved + +[1-2] Salt (2 random bytes) + +[3-16] Metadata Key (14 bytes - for encryption) + +[17+] TLV Fields (Type-Length-Value) + - Vendor ID (1 byte) + - QR Code data (variable) + - Other metadata + +[N+] Device Name (optional, UTF-8) +``` + +**Device Types:** +- 0: Unknown +- 1: Phone +- 2: Tablet +- 3: Laptop +- 4: Unknown + +### 5. State Management + +```cpp +struct TransferState { + AttachmentContainer attachments; // Files/text being transferred + TransferUpdateCallback* callback; // Where to send updates + bool is_incoming; // Direction of transfer +}; + +// Mappings +endpoint_to_target_ // endpoint_id → ShareTarget +target_id_to_endpoint_ // share_target_id → endpoint_id +active_transfers_ // endpoint_id → TransferState +``` + +## Implementation Guide + +### Step 1: Create Service Instance + +```cpp +#include "sharing/linux/nearby_sharing_service_linux.h" + +// Create service with custom device name +NearbySharingServiceLinux service("MyLinuxDevice"); + +// Or let it auto-detect from system +NearbySharingServiceLinux service; +``` + +### Step 2: Implement Callbacks + +```cpp +class MyTransferCallback : public TransferUpdateCallback { + public: + void OnTransferUpdate(const ShareTarget& share_target, + const AttachmentContainer& attachment_container, + const TransferMetadata& transfer_metadata) override { + // Handle transfer status changes + switch (transfer_metadata.status()) { + case TransferMetadata::Status::kAwaitingLocalConfirmation: + // Incoming transfer - need to accept/reject + HandleIncomingRequest(share_target); + break; + + case TransferMetadata::Status::kInProgress: + // Show progress + UpdateProgress(transfer_metadata.progress()); + break; + + case TransferMetadata::Status::kComplete: + // Transfer done - access attachments + HandleCompletedTransfer(attachment_container); + break; + + case TransferMetadata::Status::kFailed: + // Handle error + HandleError(); + break; + } + } +}; + +class MyDiscoveryCallback : public ShareTargetDiscoveredCallback { + public: + void OnShareTargetDiscovered(const ShareTarget& share_target) override { + // New device found + devices_.push_back(share_target); + NotifyUI(); + } + + void OnShareTargetLost(const ShareTarget& share_target) override { + // Device went away + RemoveDevice(share_target.id); + } + + void OnShareTargetUpdated(const ShareTarget& share_target) override { + // Device info changed + UpdateDevice(share_target); + } +}; +``` + +### Step 3: Register Surfaces + +```cpp +MyTransferCallback transfer_callback; +MyDiscoveryCallback discovery_callback; + +// To receive files +service.RegisterReceiveSurface( + &transfer_callback, + NearbySharingService::ReceiveSurfaceState::kForeground, + Advertisement::BlockedVendorId::kNone, + [](auto status) { + if (status == NearbySharingService::StatusCodes::kOk) { + std::cout << "Now advertising to nearby devices" << std::endl; + } + }); + +// To send files +service.RegisterSendSurface( + &transfer_callback, + &discovery_callback, + NearbySharingService::SendSurfaceState::kForeground, + Advertisement::BlockedVendorId::kNone, + false, // don't disable wifi hotspot + [](auto status) { + if (status == NearbySharingService::StatusCodes::kOk) { + std::cout << "Now scanning for nearby devices" << std::endl; + } + }); +``` + +### Step 4: Send Content + +```cpp +// Send a file +void SendFile(int64_t target_id, const std::string& file_path) { + auto container = std::make_unique(); + + FileAttachment attachment(FilePath(file_path)); + container->AddFileAttachment(std::move(attachment)); + + service.SendAttachments(target_id, std::move(container), + [](auto status) { + std::cout << "Send status: " + << NearbySharingService::StatusCodeToString(status) + << std::endl; + }); +} + +// Send text +void SendText(int64_t target_id, const std::string& text) { + auto container = std::make_unique(); + + TextAttachment attachment( + TextAttachment::Type::TEXT, + text, + std::nullopt, // no title + std::nullopt // no mime type + ); + container->AddTextAttachment(std::move(attachment)); + + service.SendAttachments(target_id, std::move(container), + [](auto status) { /* ... */ }); +} +``` + +### Step 5: Handle Incoming Transfers + +```cpp +void HandleIncomingRequest(const ShareTarget& target) { + // Show confirmation dialog to user + std::cout << "Accept file from " << target.device_name << "? (y/n): "; + char choice; + std::cin >> choice; + + if (choice == 'y') { + service.Accept(target.id, [](auto status) { + std::cout << "Accepted!" << std::endl; + }); + } else { + service.Reject(target.id, [](auto status) { + std::cout << "Rejected!" << std::endl; + }); + } +} + +void HandleCompletedTransfer(const AttachmentContainer& container) { + // Process received files + for (const auto& file : container.GetFileAttachments()) { + std::cout << "Received: " << file.file_name() << std::endl; + if (file.file_path().has_value()) { + std::cout << "Saved to: " << file.file_path()->string() << std::endl; + } + } + + // Process received text + for (const auto& text : container.GetTextAttachments()) { + std::cout << "Received text: " << text.text_body() << std::endl; + } +} +``` + +## Code Examples + +### Example 1: Simple File Sender + +```cpp +#include "sharing/linux/nearby_sharing_service_linux.h" +#include "sharing/file_attachment.h" +#include + +int main() { + NearbySharingServiceLinux service("FileSender"); + + // Setup callbacks + class SimpleCallback : public TransferUpdateCallback { + void OnTransferUpdate(...) override { + std::cout << "Progress: " << transfer_metadata.progress() * 100 << "%" << std::endl; + } + } transfer_cb; + + class SimpleDiscovery : public ShareTargetDiscoveredCallback { + int64_t target_id = -1; + void OnShareTargetDiscovered(const ShareTarget& t) override { + target_id = t.id; + std::cout << "Found: " << t.device_name << std::endl; + } + void OnShareTargetLost(...) override {} + void OnShareTargetUpdated(...) override {} + } discovery_cb; + + // Start scanning + service.RegisterSendSurface(&transfer_cb, &discovery_cb, + NearbySharingService::SendSurfaceState::kForeground, + Advertisement::BlockedVendorId::kNone, false, [](auto) {}); + + // Wait for discovery + std::this_thread::sleep_for(std::chrono::seconds(5)); + + if (discovery_cb.target_id != -1) { + // Send file + auto container = std::make_unique(); + container->AddFileAttachment(FileAttachment(FilePath("/path/to/file.txt"))); + service.SendAttachments(discovery_cb.target_id, std::move(container), [](auto) {}); + + // Wait for completion + std::this_thread::sleep_for(std::chrono::seconds(10)); + } + + return 0; +} +``` + +### Example 2: Auto-Accepting Receiver + +```cpp +class AutoAcceptCallback : public TransferUpdateCallback { + public: + AutoAcceptCallback(NearbySharingServiceLinux* service) : service_(service) {} + + void OnTransferUpdate(const ShareTarget& share_target, + const AttachmentContainer& attachment_container, + const TransferMetadata& transfer_metadata) override { + // Auto-accept all incoming transfers + if (transfer_metadata.status() == TransferMetadata::Status::kAwaitingLocalConfirmation) { + service_->Accept(share_target.id, [](auto) {}); + } + + // Save received files + if (transfer_metadata.status() == TransferMetadata::Status::kComplete) { + for (const auto& file : attachment_container.GetFileAttachments()) { + std::cout << "Saved: " << file.file_name() << std::endl; + } + } + } + + private: + NearbySharingServiceLinux* service_; +}; + +int main() { + NearbySharingServiceLinux service("AutoReceiver"); + AutoAcceptCallback callback(&service); + + service.RegisterReceiveSurface(&callback, + NearbySharingService::ReceiveSurfaceState::kForeground, + Advertisement::BlockedVendorId::kNone, [](auto) {}); + + // Keep running + while (true) { + std::this_thread::sleep_for(std::chrono::seconds(1)); + } +} +``` + +## Best Practices + +### 1. Callback Lifetime Management + +```cpp +// DON'T: Callbacks going out of scope +void BadExample() { + MyTransferCallback callback; // Stack allocated + service.RegisterSendSurface(&callback, ...); + // callback destroyed when function exits! +} + +// DO: Keep callbacks alive +class App { + MyTransferCallback callback_; // Member variable + + void Setup() { + service.RegisterSendSurface(&callback_, ...); + } +}; +``` + +### 2. Error Handling + +```cpp +service.SendAttachments(target_id, container, + [this](NearbySharingService::StatusCodes status) { + switch (status) { + case StatusCodes::kOk: + // Success + break; + case StatusCodes::kInvalidArgument: + // Bad target_id or empty container + LogError("Invalid arguments"); + break; + case StatusCodes::kNoAvailableConnectionMedium: + // Bluetooth/WiFi not available + NotifyUserToEnableBluetooth(); + break; + default: + LogError("Transfer failed"); + break; + } + }); +``` + +### 3. Resource Cleanup + +```cpp +class ProperCleanup { + public: + ~ProperCleanup() { + // Unregister surfaces before destroying callbacks + service_.UnregisterSendSurface(&transfer_callback_, [](auto) {}); + service_.UnregisterReceiveSurface(&transfer_callback_, [](auto) {}); + + // Shutdown service + service_.Shutdown([](auto) {}); + } + + private: + NearbySharingServiceLinux service_; + MyTransferCallback transfer_callback_; +}; +``` + +### 4. Thread Safety + +```cpp +// The service is NOT thread-safe +// All calls should be from the same thread or synchronized + +class ThreadSafeApp { + public: + void SendFromAnyThread(int64_t target_id, const std::string& file) { + task_runner_.PostTask([this, target_id, file]() { + // All service calls happen on same thread + auto container = std::make_unique(); + container->AddFileAttachment(FileAttachment(FilePath(file))); + service_.SendAttachments(target_id, std::move(container), [](auto) {}); + }); + } + + private: + NearbySharingServiceLinux service_; + TaskRunner task_runner_; // Your threading implementation +}; +``` + +### 5. State Tracking + +```cpp +class StatefulApp { + public: + void OnTransferUpdate(...) override { + current_state_ = transfer_metadata.status(); + + // Track progress + if (transfer_metadata.status() == Status::kInProgress) { + progress_map_[share_target.id] = transfer_metadata.progress(); + } + + // Cleanup on completion + if (TransferMetadata::IsFinalStatus(transfer_metadata.status())) { + progress_map_.erase(share_target.id); + } + } + + private: + TransferMetadata::Status current_state_; + std::unordered_map progress_map_; +}; +``` + +## Troubleshooting + +### Discovery Not Working +- Check Bluetooth is enabled: `IsBluetoothPowered()` +- Verify sender is in foreground state +- Ensure receiver is advertising +- Check for permission issues + +### Transfers Failing +- Verify file paths are valid and accessible +- Check available disk space on receiver +- Ensure stable Bluetooth connection +- Monitor transfer callbacks for specific error status + +### Connection Issues +- Devices must be within Bluetooth range (~10m) +- Minimize interference from other BLE devices +- Ensure both devices support required BLE features +- Check firewall settings for WiFi Direct + +## Performance Tips + +1. **Use appropriate surface states**: Background mode when not actively transferring +2. **Unregister when not needed**: Stop scanning/advertising to save battery +3. **Batch small files**: Combine into zip for better efficiency +4. **Monitor transfer progress**: Cancel stalled transfers +5. **Handle errors gracefully**: Retry with exponential backoff + diff --git a/sharing/linux/QUICK_REFERENCE.md b/sharing/linux/QUICK_REFERENCE.md new file mode 100644 index 00000000..22e17efa --- /dev/null +++ b/sharing/linux/QUICK_REFERENCE.md @@ -0,0 +1,300 @@ +# Nearby Sharing Linux - Quick Reference + +## Quick Start + +### Build +```bash +bazel build //sharing/linux:simple_example +bazel build //sharing/linux:nearby_sharing_app +``` + +### Run Simple Example +```bash +# Terminal 1 (Receiver) +./bazel-bin/sharing/linux/simple_example receiver + +# Terminal 2 (Sender) +./bazel-bin/sharing/linux/simple_example sender "Hello World!" +``` + +### Run Full App +```bash +./bazel-bin/sharing/linux/nearby_sharing_app [device_name] +``` + +## API Cheat Sheet + +### Include Headers +```cpp +#include "sharing/linux/nearby_sharing_service_linux.h" +#include "sharing/attachment_container.h" +#include "sharing/file_attachment.h" +#include "sharing/text_attachment.h" +#include "sharing/share_target.h" +#include "sharing/transfer_metadata.h" +``` + +### Create Service +```cpp +using namespace nearby::sharing::linux; +NearbySharingServiceLinux service("DeviceName"); +``` + +### Implement Callbacks +```cpp +// Transfer updates +class MyCallback : public TransferUpdateCallback { + void OnTransferUpdate(const ShareTarget& target, + const AttachmentContainer& attachments, + const TransferMetadata& metadata) override { + // Handle status changes + } +}; + +// Device discovery +class MyDiscovery : public ShareTargetDiscoveredCallback { + void OnShareTargetDiscovered(const ShareTarget& target) override { } + void OnShareTargetLost(const ShareTarget& target) override { } + void OnShareTargetUpdated(const ShareTarget& target) override { } +}; +``` + +### Register to Receive +```cpp +service.RegisterReceiveSurface( + &transfer_callback, + NearbySharingService::ReceiveSurfaceState::kForeground, + Advertisement::BlockedVendorId::kNone, + [](auto status) { /* callback */ }); +``` + +### Register to Send +```cpp +service.RegisterSendSurface( + &transfer_callback, + &discovery_callback, + NearbySharingService::SendSurfaceState::kForeground, + Advertisement::BlockedVendorId::kNone, + false, // disable_wifi_hotspot + [](auto status) { /* callback */ }); +``` + +### Send File +```cpp +auto container = std::make_unique(); +container->AddFileAttachment(FileAttachment(FilePath("/path/to/file"))); +service.SendAttachments(target_id, std::move(container), [](auto) {}); +``` + +### Send Text +```cpp +auto container = std::make_unique(); +container->AddTextAttachment(TextAttachment( + TextAttachment::Type::TEXT, "message", std::nullopt, std::nullopt)); +service.SendAttachments(target_id, std::move(container), [](auto) {}); +``` + +### Accept/Reject/Cancel +```cpp +service.Accept(target_id, [](auto status) {}); +service.Reject(target_id, [](auto status) {}); +service.Cancel(target_id, [](auto status) {}); +``` + +### Check Status +```cpp +bool scanning = service.IsScanning(); +bool transferring = service.IsTransferring(); +bool bt_present = service.IsBluetoothPresent(); +bool bt_powered = service.IsBluetoothPowered(); +``` + +### Shutdown +```cpp +service.Shutdown([](auto status) {}); +``` + +## Transfer Statuses + +| Status | Meaning | Action | +|--------|---------|--------| +| `kConnecting` | Establishing connection | Wait | +| `kAwaitingLocalConfirmation` | Need to accept/reject | Call Accept() or Reject() | +| `kAwaitingRemoteAcceptance` | Waiting for remote | Wait | +| `kInProgress` | Transferring data | Show progress | +| `kComplete` | Success | Access attachments | +| `kFailed` | Error occurred | Check logs | +| `kRejected` | User rejected | Retry or cancel | +| `kCancelled` | Transfer cancelled | Cleanup | +| `kTimedOut` | Connection timeout | Retry | + +## Status Codes + +| Code | Meaning | +|------|---------| +| `kOk` | Success | +| `kError` | General error | +| `kOutOfOrderApiCall` | API called incorrectly | +| `kTransferAlreadyInProgress` | Can't start new transfer | +| `kNoAvailableConnectionMedium` | No Bluetooth/WiFi | +| `kInvalidArgument` | Bad parameters | + +## Common Patterns + +### Auto-Accept Pattern +```cpp +class AutoAccept : public TransferUpdateCallback { + void OnTransferUpdate(...) override { + if (metadata.status() == Status::kAwaitingLocalConfirmation) { + service_->Accept(target.id, [](auto) {}); + } + } +}; +``` + +### Progress Tracking Pattern +```cpp +void OnTransferUpdate(...) override { + if (metadata.status() == Status::kInProgress) { + int percent = metadata.progress() * 100; + uint64_t bytes = metadata.transferred_bytes(); + std::cout << percent << "% (" << bytes << " bytes)" << std::endl; + } +} +``` + +### Device Selection Pattern +```cpp +std::vector devices; + +void OnShareTargetDiscovered(const ShareTarget& target) override { + devices.push_back(target); + std::cout << devices.size() << ". " << target.device_name << std::endl; +} + +void SendToDevice(size_t index) { + if (index < devices.size()) { + SendFile(devices[index].id, file_path); + } +} +``` + +### Error Handling Pattern +```cpp +service.SendAttachments(target_id, container, + [](NearbySharingService::StatusCodes status) { + if (status != StatusCodes::kOk) { + std::cerr << "Error: " + << NearbySharingService::StatusCodeToString(status) + << std::endl; + return; + } + std::cout << "Transfer initiated" << std::endl; + }); +``` + +## Debugging Tips + +### Enable Verbose Logging +```cpp +// Set environment variable +export NEARBY_LOGS=VERBOSE +``` + +### Check Bluetooth +```cpp +if (!service.IsBluetoothPresent()) { + std::cout << "No Bluetooth adapter found" << std::endl; +} +if (!service.IsBluetoothPowered()) { + std::cout << "Bluetooth is off" << std::endl; +} +``` + +### Dump Service State +```cpp +std::cout << service.Dump() << std::endl; +// Output: "NearbySharingServiceLinux advertising=true scanning=false ..." +``` + +### Monitor Callbacks +```cpp +void OnTransferUpdate(...) override { + std::cout << "[Transfer] " << target.device_name + << " - " << TransferMetadata::StatusToString(metadata.status()) + << " - " << (metadata.progress() * 100) << "%" << std::endl; +} +``` + +## File Locations + +- **Service**: `sharing/linux/nearby_sharing_service_linux.{h,cc}` +- **Simple Example**: `sharing/linux/simple_example.cc` +- **Full App**: `sharing/linux/nearby_sharing_app.cc` +- **README**: `sharing/linux/README.md` +- **Implementation Guide**: `sharing/linux/IMPLEMENTATION_GUIDE.md` +- **BUILD**: `sharing/linux/BUILD` + +## Common Issues + +### "No devices found" +- Ensure receiver is running and advertising +- Check Bluetooth is enabled on both devices +- Verify devices are within range (~10m) +- Try restarting Bluetooth + +### "Transfer failed" +- Check file permissions +- Verify disk space +- Ensure stable connection +- Check firewall settings + +### "Invalid argument" +- Verify target_id is valid +- Ensure container has attachments +- Check surface is registered + +### Callback not called +- Verify callback lifetime (must outlive service) +- Check registration was successful +- Ensure main thread/event loop is running + +## Example Workflows + +### Send File Workflow +``` +1. Create service +2. Create callbacks +3. RegisterSendSurface (foreground) +4. Wait for OnShareTargetDiscovered +5. Create AttachmentContainer +6. Add FileAttachment +7. SendAttachments(target_id, container) +8. Wait for kComplete in OnTransferUpdate +``` + +### Receive File Workflow +``` +1. Create service +2. Create callback +3. RegisterReceiveSurface (foreground) +4. Wait for kAwaitingLocalConfirmation +5. Call Accept(target_id) +6. Wait for kInProgress updates +7. Wait for kComplete +8. Access files from AttachmentContainer +``` + +## Performance Notes + +- **Scanning**: Consumes battery, stop when not needed +- **Advertising**: Minimal impact +- **Transfer**: WiFi Direct faster than Bluetooth +- **File Size**: Large files (>100MB) benefit from WiFi +- **Small Files**: Bluetooth sufficient for <10MB + +## Links + +- [README.md](README.md) - Overview and features +- [IMPLEMENTATION_GUIDE.md](IMPLEMENTATION_GUIDE.md) - Detailed architecture +- [nearby_sharing_service.h](../nearby_sharing_service.h) - Base interface diff --git a/sharing/linux/README.md b/sharing/linux/README.md new file mode 100644 index 00000000..9545e0a1 --- /dev/null +++ b/sharing/linux/README.md @@ -0,0 +1,348 @@ +# Nearby Sharing Linux Implementation + +This directory contains the Linux-specific implementation of Nearby Sharing and a sample application demonstrating its usage. + +## Overview + +Nearby Sharing is a feature that allows users to share files, text, and other content between nearby devices using Bluetooth Low Energy (BLE) and Wi-Fi Direct. This implementation provides a simplified Linux interface built on top of the Nearby Connections API. + +## Components + +### NearbySharingServiceLinux + +The main service class that provides nearby sharing functionality: + +- **Discovery & Advertising**: Find nearby devices and advertise your device's availability +- **File Transfer**: Send and receive files +- **Text Transfer**: Send and receive text messages +- **Connection Management**: Handle connection lifecycle (accept, reject, cancel) + +### Key Concepts + +#### 1. Send Surface +Represents the sending side of a transfer: +- **Foreground**: Actively scans for nearby devices +- **Background**: Only listens for transfer updates without scanning + +#### 2. Receive Surface +Represents the receiving side of a transfer: +- **Foreground**: Advertises to everyone, visible to all nearby devices +- **Background**: Advertises only to contacts (limited visibility) + +#### 3. Callbacks + +**TransferUpdateCallback**: Receives updates about ongoing transfers +- Status changes (connecting, in progress, complete, failed) +- Progress updates +- Transfer metadata (speed, bytes transferred, etc.) + +**ShareTargetDiscoveredCallback**: Receives notifications about discovered devices +- Device discovered +- Device lost (out of range) +- Device updated + +#### 4. Attachments + +**FileAttachment**: Represents a file to be transferred +- Requires a file path +- Automatically determines MIME type and size + +**TextAttachment**: Represents text content to be transferred +- Supports plain text, URLs, addresses, and phone numbers +- Includes optional title and MIME type + +## Sample Application + +The `nearby_sharing_app.cc` demonstrates how to use the service: + +### Building + +```bash +# Build the sample application +bazel build //sharing/linux:nearby_sharing_app +``` + +### Running + +```bash +# Run with default device name +./bazel-bin/sharing/linux/nearby_sharing_app + +# Run with custom device name +./bazel-bin/sharing/linux/nearby_sharing_app "MyCustomName" +``` + +### Features + +1. **Start as Receiver**: Advertise your device to receive files +2. **Start as Sender**: Discover nearby devices to send files +3. **List Discovered Devices**: View all devices found during scanning +4. **Send File**: Transfer a file to a discovered device +5. **Send Text**: Send text content to a discovered device +6. **Accept/Reject**: Handle incoming transfer requests +7. **Cancel Transfer**: Cancel an ongoing transfer +8. **Status Info**: View Bluetooth and service status + +## Usage Examples + +### Example 1: Send a File + +**Device A (Sender)**: +```cpp +NearbySharingApp app("Sender-Device"); + +// Start scanning for devices +app.StartAsSender(); + +// Wait for discovery... +std::this_thread::sleep_for(std::chrono::seconds(3)); + +// List discovered devices +app.ListDiscoveredDevices(); + +// Send file to target with ID 1 +app.SendFile(1, "/path/to/file.txt"); +``` + +**Device B (Receiver)**: +```cpp +NearbySharingApp app("Receiver-Device"); + +// Start advertising +app.StartAsReceiver(); + +// When connection is initiated (via callback), accept it +// This happens automatically when you see OnTransferUpdate with +// Status::kAwaitingLocalConfirmation +app.AcceptIncomingShare(target_id); +``` + +### Example 2: Send Text + +```cpp +NearbySharingApp app("Text-Sender"); + +// Start as sender +app.StartAsSender(); + +// Wait for device discovery +std::this_thread::sleep_for(std::chrono::seconds(2)); + +// Send text to discovered device +app.SendText(1, "Hello from Nearby Sharing!"); +``` + +### Example 3: Custom Callbacks + +```cpp +class CustomTransferCallback : public TransferUpdateCallback { + public: + void OnTransferUpdate(const ShareTarget& share_target, + const AttachmentContainer& attachment_container, + const TransferMetadata& transfer_metadata) override { + switch (transfer_metadata.status()) { + case TransferMetadata::Status::kAwaitingLocalConfirmation: + // Auto-accept incoming transfers + service_->Accept(share_target.id, [](auto status) { + std::cout << "Auto-accepted" << std::endl; + }); + break; + + case TransferMetadata::Status::kComplete: + std::cout << "Transfer completed!" << std::endl; + // Handle completed files from attachment_container + break; + + case TransferMetadata::Status::kFailed: + std::cout << "Transfer failed!" << std::endl; + break; + + default: + break; + } + } +}; +``` + +## Architecture + +### Service Initialization + +```cpp +NearbySharingServiceLinux service("DeviceName"); +``` + +The service initializes: +1. **Device Info**: Gets OS device name and type +2. **Bluetooth Adapter**: Checks BT availability +3. **Connections Core**: Sets up the Nearby Connections layer +4. **Service Controller Router**: Manages connection routing + +### Discovery Flow + +1. **Register Send Surface** (Foreground) +2. Service starts **scanning** for nearby devices +3. When device found: **OnShareTargetDiscovered** callback +4. Advertisement is **parsed** to extract device info +5. **ShareTarget** created with device details +6. User can **select target** and initiate transfer + +### Advertising Flow + +1. **Register Receive Surface** (Foreground/Background) +2. Service **builds advertisement** with device info +3. Service starts **advertising** via Bluetooth/Wi-Fi +4. When connection requested: **OnTransferUpdate** callback +5. User can **accept/reject** the incoming transfer + +### Transfer Flow + +#### Sending: +1. **SendAttachments()** with target ID and attachments +2. Service creates **connection request** +3. **Connection initiated** → Status: kConnecting +4. Connection **accepted** → Sends file/text payloads +5. **Payload transfer** → Status: kInProgress +6. **Transfer complete** → Status: kComplete + +#### Receiving: +1. **Incoming connection** → Status: kAwaitingLocalConfirmation +2. **Accept()** called → Connection accepted +3. **Receive payloads** → Status: kInProgress +4. **Payloads saved** to local storage +5. **Transfer complete** → Status: kComplete + +## Implementation Details + +### Advertisement Format + +The service uses a custom advertisement format: +- **Header byte**: Version, visibility, device type +- **Salt**: 2 random bytes +- **Metadata key**: 14 bytes (for encryption) +- **TLV fields**: Vendor ID, QR code, etc. +- **Device name** (optional): UTF-8 device name + +### Connection Strategy + +Uses `P2P_POINT_TO_POINT` strategy: +- Direct peer-to-peer connections +- Supports Bluetooth and Wi-Fi Direct +- Automatic medium selection based on availability + +### Medium Selection + +The service attempts to use available media: +1. **Bluetooth LE**: For discovery and initial connection +2. **Wi-Fi Direct**: For high-speed file transfer +3. **Wi-Fi LAN**: If devices on same network + +### Payload Types + +1. **BYTES**: For text content and metadata +2. **FILE**: For file transfers +3. **STREAM**: For real-time data + +## API Reference + +### Core Methods + +#### RegisterSendSurface +```cpp +void RegisterSendSurface( + TransferUpdateCallback* transfer_callback, + ShareTargetDiscoveredCallback* discovery_callback, + SendSurfaceState state, + Advertisement::BlockedVendorId blocked_vendor_id, + bool disable_wifi_hotspot, + std::function status_codes_callback); +``` + +#### RegisterReceiveSurface +```cpp +void RegisterReceiveSurface( + TransferUpdateCallback* transfer_callback, + ReceiveSurfaceState state, + Advertisement::BlockedVendorId vendor_id, + std::function status_codes_callback); +``` + +#### SendAttachments +```cpp +void SendAttachments( + int64_t share_target_id, + std::unique_ptr attachment_container, + std::function status_codes_callback); +``` + +#### Accept/Reject/Cancel +```cpp +void Accept(int64_t share_target_id, + std::function status_codes_callback); + +void Reject(int64_t share_target_id, + std::function status_codes_callback); + +void Cancel(int64_t share_target_id, + std::function status_codes_callback); +``` + +### Status Codes + +- **kOk**: Operation successful +- **kError**: General error +- **kOutOfOrderApiCall**: API called in wrong order +- **kTransferAlreadyInProgress**: Transfer already active +- **kNoAvailableConnectionMedium**: No BT/Wi-Fi available +- **kInvalidArgument**: Invalid parameter provided + +## Limitations + +Current implementation limitations: +- No settings persistence +- No contact management +- No certificate management +- No account integration +- Limited visibility control +- No Wi-Fi LAN detection +- No extended advertising support + +## Future Enhancements + +Potential improvements: +1. Add settings persistence (device name, visibility) +2. Implement contact management +3. Add certificate-based authentication +4. Support visibility time limits +5. Add Wi-Fi LAN connectivity detection +6. Implement file path updates during transfer +7. Add QR code generation for pairing +8. Support for extended advertising + +## Troubleshooting + +### Bluetooth Issues +```cpp +if (!service.IsBluetoothPresent()) { + std::cout << "Bluetooth adapter not found" << std::endl; +} +if (!service.IsBluetoothPowered()) { + std::cout << "Bluetooth is disabled" << std::endl; +} +``` + +### Discovery Not Working +- Ensure Bluetooth is enabled +- Check that sender is in foreground mode +- Verify receiver is advertising +- Check for Bluetooth permissions + +### Transfer Failures +- Verify file paths are accessible +- Check available disk space +- Ensure stable Bluetooth connection +- Monitor transfer callbacks for errors + +## License + +Copyright 2025 Google LLC. Licensed under Apache 2.0. diff --git a/sharing/linux/nearby_sharing_app.cc b/sharing/linux/nearby_sharing_app.cc new file mode 100644 index 00000000..828ea2c7 --- /dev/null +++ b/sharing/linux/nearby_sharing_app.cc @@ -0,0 +1,394 @@ +// Copyright 2025 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 "sharing/linux/nearby_sharing_service_linux.h" +#include "sharing/attachment_container.h" +#include "sharing/file_attachment.h" +#include "sharing/text_attachment.h" +#include "sharing/share_target.h" +#include "sharing/transfer_metadata.h" +#include "sharing/transfer_update_callback.h" +#include "sharing/share_target_discovered_callback.h" +#include "internal/base/file_path.h" + +using namespace nearby::sharing; +using namespace nearby::sharing::linux; + +class MyTransferUpdateCallback : public TransferUpdateCallback { + public: + void OnTransferUpdate(const ShareTarget& share_target, + const AttachmentContainer& attachment_container, + const TransferMetadata& transfer_metadata) override { + std::cout << "\n=== Transfer Update ===" << std::endl; + std::cout << "Device: " << share_target.device_name << std::endl; + std::cout << "Status: " << TransferMetadata::StatusToString(transfer_metadata.status()) << std::endl; + std::cout << "Progress: " << (transfer_metadata.progress() * 100) << "%" << std::endl; + std::cout << "Transferred: " << transfer_metadata.transferred_bytes() << " bytes" << std::endl; + std::cout << "Total attachments: " << transfer_metadata.total_attachments_count() << std::endl; + std::cout << "======================" << std::endl; + } +}; + +class MyShareTargetDiscoveredCallback : public ShareTargetDiscoveredCallback { + public: + void OnShareTargetDiscovered(const ShareTarget& share_target) override { + std::cout << "\n*** Device Discovered ***" << std::endl; + std::cout << "ID: " << share_target.id << std::endl; + std::cout << "Name: " << share_target.device_name << std::endl; + std::cout << "Vendor ID: " << static_cast(share_target.vendor_id) << std::endl; + std::cout << "*************************" << std::endl; + + discovered_targets_.push_back(share_target); + } + + void OnShareTargetLost(const ShareTarget& share_target) override { + std::cout << "\n*** Device Lost ***" << std::endl; + std::cout << "Name: " << share_target.device_name << std::endl; + std::cout << "*******************" << std::endl; + + for (auto it = discovered_targets_.begin(); it != discovered_targets_.end(); ++it) { + if (it->id == share_target.id) { + discovered_targets_.erase(it); + break; + } + } + } + + void OnShareTargetUpdated(const ShareTarget& share_target) override { + std::cout << "\n*** Device Updated ***" << std::endl; + std::cout << "Name: " << share_target.device_name << std::endl; + std::cout << "**********************" << std::endl; + } + + const std::vector& GetDiscoveredTargets() const { + return discovered_targets_; + } + + private: + std::vector discovered_targets_; +}; + +class NearbySharingApp { + public: + NearbySharingApp(const std::string& device_name) + : service_(std::make_unique(device_name)), + transfer_callback_(std::make_unique()), + discovery_callback_(std::make_unique()) { + std::cout << "Nearby Sharing Application initialized with device name: " + << device_name << std::endl; + } + + ~NearbySharingApp() { + Shutdown(); + } + + void StartAsReceiver() { + std::cout << "\n=== Starting as Receiver (Foreground) ===" << std::endl; + + service_->RegisterReceiveSurface( + transfer_callback_.get(), + NearbySharingService::ReceiveSurfaceState::kForeground, + Advertisement::BlockedVendorId::kNone, + [](NearbySharingService::StatusCodes status) { + if (status == NearbySharingService::StatusCodes::kOk) { + std::cout << "Successfully registered as receiver!" << std::endl; + } else { + std::cout << "Failed to register as receiver: " + << NearbySharingService::StatusCodeToString(status) << std::endl; + } + }); + + std::cout << "Advertising enabled. Waiting for incoming connections..." << std::endl; + } + + void StartAsSender() { + std::cout << "\n=== Starting as Sender (Foreground) ===" << std::endl; + + service_->RegisterSendSurface( + transfer_callback_.get(), + discovery_callback_.get(), + NearbySharingService::SendSurfaceState::kForeground, + Advertisement::BlockedVendorId::kNone, + false, // disable_wifi_hotspot + [](NearbySharingService::StatusCodes status) { + if (status == NearbySharingService::StatusCodes::kOk) { + std::cout << "Successfully registered as sender!" << std::endl; + } else { + std::cout << "Failed to register as sender: " + << NearbySharingService::StatusCodeToString(status) << std::endl; + } + }); + + std::cout << "Scanning for nearby devices..." << std::endl; + } + + void SendFile(int64_t target_id, const std::string& file_path) { + std::cout << "\n=== Sending File ===" << std::endl; + std::cout << "Target ID: " << target_id << std::endl; + std::cout << "File: " << file_path << std::endl; + + auto attachment_container = std::make_unique(); + + attachment_container->AddFileAttachment(FileAttachment(nearby::FilePath(file_path))); + + service_->SendAttachments( + target_id, + std::move(attachment_container), + [](NearbySharingService::StatusCodes status) { + if (status == NearbySharingService::StatusCodes::kOk) { + std::cout << "File send initiated successfully!" << std::endl; + } else { + std::cout << "Failed to send file: " + << NearbySharingService::StatusCodeToString(status) << std::endl; + } + }); + } + + void SendText(int64_t target_id, const std::string& text) { + std::cout << "\n=== Sending Text ===" << std::endl; + std::cout << "Target ID: " << target_id << std::endl; + std::cout << "Text: " << text << std::endl; + + auto attachment_container = std::make_unique(); + + attachment_container->AddTextAttachment(TextAttachment( + nearby::sharing::service::proto::TextMetadata::TEXT, + text, + std::nullopt, // text_title + std::nullopt // mime_type + )); + + service_->SendAttachments( + target_id, + std::move(attachment_container), + [](NearbySharingService::StatusCodes status) { + if (status == NearbySharingService::StatusCodes::kOk) { + std::cout << "Text send initiated successfully!" << std::endl; + } else { + std::cout << "Failed to send text: " + << NearbySharingService::StatusCodeToString(status) << std::endl; + } + }); + } + + void AcceptIncomingShare(int64_t target_id) { + std::cout << "\n=== Accepting Incoming Share ===" << std::endl; + std::cout << "Target ID: " << target_id << std::endl; + + service_->Accept( + target_id, + [](NearbySharingService::StatusCodes status) { + if (status == NearbySharingService::StatusCodes::kOk) { + std::cout << "Share accepted!" << std::endl; + } else { + std::cout << "Failed to accept share: " + << NearbySharingService::StatusCodeToString(status) << std::endl; + } + }); + } + + void RejectIncomingShare(int64_t target_id) { + std::cout << "\n=== Rejecting Incoming Share ===" << std::endl; + std::cout << "Target ID: " << target_id << std::endl; + + service_->Reject( + target_id, + [](NearbySharingService::StatusCodes status) { + if (status == NearbySharingService::StatusCodes::kOk) { + std::cout << "Share rejected!" << std::endl; + } else { + std::cout << "Failed to reject share: " + << NearbySharingService::StatusCodeToString(status) << std::endl; + } + }); + } + + void CancelTransfer(int64_t target_id) { + std::cout << "\n=== Canceling Transfer ===" << std::endl; + std::cout << "Target ID: " << target_id << std::endl; + + service_->Cancel( + target_id, + [](NearbySharingService::StatusCodes status) { + if (status == NearbySharingService::StatusCodes::kOk) { + std::cout << "Transfer cancelled!" << std::endl; + } else { + std::cout << "Failed to cancel transfer: " + << NearbySharingService::StatusCodeToString(status) << std::endl; + } + }); + } + + void ListDiscoveredDevices() { + std::cout << "\n=== Discovered Devices ===" << std::endl; + const auto& targets = discovery_callback_->GetDiscoveredTargets(); + + if (targets.empty()) { + std::cout << "No devices found." << std::endl; + } else { + for (const auto& target : targets) { + std::cout << "ID: " << target.id + << " | Name: " << target.device_name + << " | Vendor: " << static_cast(target.vendor_id) << std::endl; + } + } + std::cout << "==========================" << std::endl; + } + + void PrintStatus() { + std::cout << "\n=== Service Status ===" << std::endl; + std::cout << "Bluetooth Present: " << (service_->IsBluetoothPresent() ? "Yes" : "No") << std::endl; + std::cout << "Bluetooth Powered: " << (service_->IsBluetoothPowered() ? "Yes" : "No") << std::endl; + std::cout << "Is Scanning: " << (service_->IsScanning() ? "Yes" : "No") << std::endl; + std::cout << "Is Transferring: " << (service_->IsTransferring() ? "Yes" : "No") << std::endl; + std::cout << "======================" << std::endl; + } + + void Shutdown() { + std::cout << "\n=== Shutting Down ===" << std::endl; + service_->Shutdown([](NearbySharingService::StatusCodes status) { + std::cout << "Shutdown complete: " + << NearbySharingService::StatusCodeToString(status) << std::endl; + }); + } + + private: + std::unique_ptr service_; + std::unique_ptr transfer_callback_; + std::unique_ptr discovery_callback_; +}; + +void PrintMenu() { + std::cout << "\n========== Nearby Sharing Menu ==========" << std::endl; + std::cout << "1. Start as Receiver (advertise)" << std::endl; + std::cout << "2. Start as Sender (discover)" << std::endl; + std::cout << "3. List discovered devices" << std::endl; + std::cout << "4. Send file to device" << std::endl; + std::cout << "5. Send text to device" << std::endl; + std::cout << "6. Accept incoming share" << std::endl; + std::cout << "7. Reject incoming share" << std::endl; + std::cout << "8. Cancel transfer" << std::endl; + std::cout << "9. Print status" << std::endl; + std::cout << "0. Exit" << std::endl; + std::cout << "=========================================" << std::endl; + std::cout << "Enter choice: "; +} + +int main(int argc, char* argv[]) { + std::string device_name = "MyLinuxDevice"; + + if (argc > 1) { + device_name = argv[1]; + } + + std::cout << "========================================" << std::endl; + std::cout << " Nearby Sharing Linux Application" << std::endl; + std::cout << "========================================" << std::endl; + + NearbySharingApp app(device_name); + + bool running = true; + while (running) { + PrintMenu(); + + int choice; + std::cin >> choice; + + switch (choice) { + case 1: + app.StartAsReceiver(); + break; + + case 2: + app.StartAsSender(); + std::this_thread::sleep_for(std::chrono::seconds(2)); + app.ListDiscoveredDevices(); + break; + + case 3: + app.ListDiscoveredDevices(); + break; + + case 4: { + int64_t target_id; + std::string file_path; + std::cout << "Enter target ID: "; + std::cin >> target_id; + std::cout << "Enter file path: "; + std::cin.ignore(); + std::getline(std::cin, file_path); + app.SendFile(target_id, file_path); + break; + } + + case 5: { + int64_t target_id; + std::string text; + std::cout << "Enter target ID: "; + std::cin >> target_id; + std::cout << "Enter text to send: "; + std::cin.ignore(); + std::getline(std::cin, text); + app.SendText(target_id, text); + break; + } + + case 6: { + int64_t target_id; + std::cout << "Enter target ID to accept: "; + std::cin >> target_id; + app.AcceptIncomingShare(target_id); + break; + } + + case 7: { + int64_t target_id; + std::cout << "Enter target ID to reject: "; + std::cin >> target_id; + app.RejectIncomingShare(target_id); + break; + } + + case 8: { + int64_t target_id; + std::cout << "Enter target ID to cancel: "; + std::cin >> target_id; + app.CancelTransfer(target_id); + break; + } + + case 9: + app.PrintStatus(); + break; + + case 0: + running = false; + break; + + default: + std::cout << "Invalid choice. Please try again." << std::endl; + break; + } + } + + std::cout << "\nGoodbye!" << std::endl; + return 0; +} diff --git a/sharing/linux/nearby_sharing_app_BUILD b/sharing/linux/nearby_sharing_app_BUILD new file mode 100644 index 00000000..80252d43 --- /dev/null +++ b/sharing/linux/nearby_sharing_app_BUILD @@ -0,0 +1,28 @@ +# Copyright 2025 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_binary( + name = "nearby_sharing_app", + srcs = ["nearby_sharing_app.cc"], + deps = [ + ":nearby_sharing_service_linux", + "//sharing:attachments", + "//sharing:types", + "//internal/platform:base", + "@com_google_absl//absl/strings", + ], + visibility = ["//visibility:public"], +) diff --git a/sharing/linux/nearby_sharing_service_linux.cc b/sharing/linux/nearby_sharing_service_linux.cc new file mode 100644 index 00000000..42af1563 --- /dev/null +++ b/sharing/linux/nearby_sharing_service_linux.cc @@ -0,0 +1,1002 @@ +// Copyright 2025 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 "nearby_sharing_service_linux.h" +#include "sharing/proto/enums.pb.h" + +#include +#include +#include +#include +#include + +#include "absl/strings/string_view.h" +#include "absl/time/time.h" +#include "connections/advertising_options.h" +#include "connections/connection_options.h" +#include "connections/discovery_options.h" +#include "connections/listeners.h" +#include "connections/medium_selector.h" +#include "connections/payload.h" +#include "connections/status.h" +#include "connections/strategy.h" +#include "internal/platform/byte_array.h" +#include "internal/platform/file.h" +#include "internal/platform/logging.h" +#include "sharing/certificates/common.h" + +namespace nearby::sharing::linux { +namespace { +constexpr char kServiceId[] = "NearbySharing"; +const connections::Strategy kStrategy = connections::Strategy::kP2pPointToPoint; +constexpr uint8_t kAdvertisementSaltSize = 2; +constexpr uint8_t kAdvertisementMetadataKeySize = 14; +constexpr uint8_t kAdvertisementVersion = 0; +constexpr uint8_t kVersionBitmask = 0b111; +constexpr uint8_t kDeviceTypeBitmask = 0b111; +constexpr uint8_t kVisibilityBitmask = 0b1; +constexpr uint8_t kTlvMinLength = 2; +constexpr uint8_t kVendorIdLength = 1; + +enum class TlvTypes : uint8_t { + kUnknown = 0, + kQrCode = 1, + kVendorId = 2, +}; + +uint8_t EncodeHeaderByte(bool has_device_name, ShareTargetType device_type) { + uint8_t version = static_cast((kAdvertisementVersion & kVersionBitmask) << 5); + uint8_t visibility = static_cast(((has_device_name ? 0 : 1) & kVisibilityBitmask) << 4); + uint8_t type = static_cast((static_cast(device_type) & kDeviceTypeBitmask) << 1); + return static_cast(version | visibility | type); +} + +bool ShouldIncludeDeviceName(const std::optional& device_name) { + return device_name.has_value() && !device_name->empty(); +} + +TransferMetadata::Status StatusFromPayloadStatus( + connections::PayloadProgressInfo::Status status) { + switch (status) { + case connections::PayloadProgressInfo::Status::kInProgress: + return TransferMetadata::Status::kInProgress; + case connections::PayloadProgressInfo::Status::kSuccess: + return TransferMetadata::Status::kComplete; + case connections::PayloadProgressInfo::Status::kFailure: + return TransferMetadata::Status::kFailed; + case connections::PayloadProgressInfo::Status::kCanceled: + return TransferMetadata::Status::kCancelled; + } + return TransferMetadata::Status::kUnknown; +} + +} // namespace + +NearbySharingServiceLinux::NearbySharingServiceLinux() + : device_info_(::nearby::api::ImplementationPlatform::CreateDeviceInfo()), + router_(std::make_unique()), + core_(std::make_unique(router_.get())) { + if (device_info_) { + auto name = device_info_->GetOsDeviceName(); + if (name.has_value()) { + device_name_override_ = *name; + } + } +} + +NearbySharingServiceLinux::NearbySharingServiceLinux( + std::string device_name_override) + : device_name_override_(std::move(device_name_override)), + device_info_(::nearby::api::ImplementationPlatform::CreateDeviceInfo()), + router_(std::make_unique()), + core_(std::make_unique(router_.get())) {} + +NearbySharingServiceLinux::~NearbySharingServiceLinux() = default; + +void NearbySharingServiceLinux::AddObserver(Observer* observer) { + if (!observer) { + return; + } + observers_.insert(observer); +} + +void NearbySharingServiceLinux::RemoveObserver(Observer* observer) { + observers_.erase(observer); +} + +void NearbySharingServiceLinux::Shutdown( + std::function + status_codes_callback) { + StopDiscovery(); + StopAdvertising(); + endpoint_to_target_.clear(); + target_id_to_endpoint_.clear(); + active_transfers_.clear(); + is_transferring_ = false; + std::move(status_codes_callback)(StatusCodes::kOk); +} + +void NearbySharingServiceLinux::RegisterSendSurface( + TransferUpdateCallback* transfer_callback, + ShareTargetDiscoveredCallback* discovery_callback, SendSurfaceState state, + Advertisement::BlockedVendorId blocked_vendor_id, + bool disable_wifi_hotspot, + std::function + status_codes_callback) { + static_cast(blocked_vendor_id); + if (!transfer_callback) { + std::move(status_codes_callback)(StatusCodes::kInvalidArgument); + return; + } + + send_surfaces_[transfer_callback] = SendSurface{ + .discovery_callback = discovery_callback, + .state = state, + .disable_wifi_hotspot = disable_wifi_hotspot, + }; + + StartDiscoveryIfNeeded(); + std::move(status_codes_callback)(StatusCodes::kOk); +} + +void NearbySharingServiceLinux::UnregisterSendSurface( + TransferUpdateCallback* transfer_callback, + std::function + status_codes_callback) { + if (!transfer_callback) { + std::move(status_codes_callback)(StatusCodes::kInvalidArgument); + return; + } + + send_surfaces_.erase(transfer_callback); + if (send_surfaces_.empty()) { + StopDiscovery(); + } + + std::move(status_codes_callback)(StatusCodes::kOk); +} + +void NearbySharingServiceLinux::RegisterReceiveSurface( + TransferUpdateCallback* transfer_callback, ReceiveSurfaceState state, + Advertisement::BlockedVendorId vendor_id, + std::function + status_codes_callback) { + if (!transfer_callback) { + std::move(status_codes_callback)(StatusCodes::kInvalidArgument); + return; + } + + receive_surfaces_[transfer_callback] = ReceiveSurface{ + .state = state, + .vendor_id = vendor_id, + }; + + StartAdvertisingIfNeeded(); + std::move(status_codes_callback)(StatusCodes::kOk); +} + +void NearbySharingServiceLinux::UnregisterReceiveSurface( + TransferUpdateCallback* transfer_callback, + std::function + status_codes_callback) { + if (!transfer_callback) { + std::move(status_codes_callback)(StatusCodes::kInvalidArgument); + return; + } + + receive_surfaces_.erase(transfer_callback); + if (receive_surfaces_.empty()) { + StopAdvertising(); + } else { + StartAdvertisingIfNeeded(); + } + + std::move(status_codes_callback)(StatusCodes::kOk); +} + +void NearbySharingServiceLinux::ClearForegroundReceiveSurfaces( + std::function + status_codes_callback) { + for (auto it = receive_surfaces_.begin(); it != receive_surfaces_.end();) { + if (it->second.state == ReceiveSurfaceState::kForeground) { + it = receive_surfaces_.erase(it); + } else { + ++it; + } + } + + if (receive_surfaces_.empty()) { + StopAdvertising(); + } else { + StartAdvertisingIfNeeded(); + } + + std::move(status_codes_callback)(StatusCodes::kOk); +} + +bool NearbySharingServiceLinux::IsTransferring() const { + return is_transferring_; +} + +bool NearbySharingServiceLinux::IsScanning() const { return is_scanning_; } + +bool NearbySharingServiceLinux::IsBluetoothPresent() const { + return bluetooth_adapter_.IsValid(); +} + +bool NearbySharingServiceLinux::IsBluetoothPowered() const { + return bluetooth_adapter_.IsValid() && bluetooth_adapter_.IsEnabled(); +} + +bool NearbySharingServiceLinux::IsExtendedAdvertisingSupported() const { + return false; +} + +bool NearbySharingServiceLinux::IsLanConnected() const { return false; } + +std::string NearbySharingServiceLinux::GetQrCodeUrl() const { return ""; } + +void NearbySharingServiceLinux::SendAttachments( + int64_t share_target_id, + std::unique_ptr + attachment_container, + std::function + status_codes_callback) { + auto endpoint_id = GetEndpointIdForTarget(share_target_id); + if (!endpoint_id.has_value() || !attachment_container || + !attachment_container->HasAttachments()) { + std::move(status_codes_callback)(StatusCodes::kInvalidArgument); + return; + } + + TransferUpdateCallback* callback = PickSendTransferCallback(); + if (!callback) { + std::move(status_codes_callback)(StatusCodes::kOutOfOrderApiCall); + return; + } + + TransferState transfer_state; + transfer_state.attachments = *attachment_container; + transfer_state.callback = callback; + transfer_state.is_incoming = false; + active_transfers_[*endpoint_id] = transfer_state; + + TransferMetadata metadata = + TransferMetadataBuilder().set_status(TransferMetadata::Status::kConnecting) + .set_progress(0) + .set_total_attachments_count( + attachment_container->GetAttachmentCount()) + .build(); + if (auto share_target = GetShareTarget(*endpoint_id)) { + NotifyTransferUpdate(*share_target, transfer_state, metadata); + } + + connections::ConnectionOptions options; + options.strategy = kStrategy; + options.allowed.SetAll(true); + + std::optional device_name = + device_name_override_.empty() + ? std::optional(std::nullopt) + : std::optional(device_name_override_); + if (!device_name.has_value() && device_info_) { + auto name = device_info_->GetOsDeviceName(); + if (name.has_value()) { + device_name = *name; + } + } + + ShareTargetType device_type = ShareTargetType::kUnknown; + if (device_info_) { + device_type = + static_cast(device_info_->GetDeviceType()); + } + + std::vector endpoint_info = + BuildAdvertisement(device_name, device_type, + static_cast( + Advertisement::BlockedVendorId::kNone)); + connections::ConnectionRequestInfo request_info; + request_info.endpoint_info = + ByteArray(std::string(endpoint_info.begin(), endpoint_info.end())); + request_info.listener.initiated_cb = + [this](const std::string& id, + const connections::ConnectionResponseInfo& info) { + HandleOutgoingConnectionInitiated(id, info); + }; + request_info.listener.accepted_cb = [this](const std::string& id) { + HandleConnectionAccepted(id, /*is_incoming=*/false); + }; + request_info.listener.rejected_cb = + [this](const std::string& id, connections::Status status) { + HandleConnectionRejected(id, status, /*is_incoming=*/false); + }; + request_info.listener.disconnected_cb = [this](const std::string& id) { + HandleConnectionDisconnected(id); + }; + + core_->RequestConnection(*endpoint_id, request_info, options, + [this, cb = std::move(status_codes_callback)]( + connections::Status status) mutable { + cb(StatusFromConnections(status)); + }); +} + +void NearbySharingServiceLinux::Accept( + int64_t share_target_id, + std::function + status_codes_callback) { + auto endpoint_id = GetEndpointIdForTarget(share_target_id); + if (!endpoint_id.has_value()) { + std::move(status_codes_callback)(StatusCodes::kInvalidArgument); + return; + } + + core_->AcceptConnection(*endpoint_id, MakePayloadListener(true), + [this, cb = std::move(status_codes_callback)]( + connections::Status status) mutable { + cb(StatusFromConnections(status)); + }); +} + +void NearbySharingServiceLinux::Reject( + int64_t share_target_id, + std::function + status_codes_callback) { + auto endpoint_id = GetEndpointIdForTarget(share_target_id); + if (!endpoint_id.has_value()) { + std::move(status_codes_callback)(StatusCodes::kInvalidArgument); + return; + } + + core_->RejectConnection(*endpoint_id, + [this, cb = std::move(status_codes_callback)]( + connections::Status status) mutable { + cb(StatusFromConnections(status)); + }); +} + +void NearbySharingServiceLinux::Cancel( + int64_t share_target_id, + std::function + status_codes_callback) { + auto endpoint_id = GetEndpointIdForTarget(share_target_id); + if (!endpoint_id.has_value()) { + std::move(status_codes_callback)(StatusCodes::kInvalidArgument); + return; + } + + core_->DisconnectFromEndpoint( + *endpoint_id, + [this, cb = std::move(status_codes_callback)](connections::Status status) mutable { + cb(StatusFromConnections(status)); + }); +} + +void NearbySharingServiceLinux::SetVisibility( + proto::DeviceVisibility visibility, absl::Duration expiration, + absl::AnyInvocable callback) { + static_cast(visibility); + static_cast(expiration); + std::move(callback)(StatusCodes::kOk); +} + +std::string NearbySharingServiceLinux::Dump() const { + std::stringstream ss; + ss << "NearbySharingServiceLinux"; + ss << " advertising=" << (is_advertising_ ? "true" : "false"); + ss << " scanning=" << (is_scanning_ ? "true" : "false"); + ss << " transfers=" << active_transfers_.size(); + ss << " targets=" << endpoint_to_target_.size(); + return ss.str(); +} + +void NearbySharingServiceLinux::UpdateFilePathsInProgress( + bool update_file_paths) {} + +NearbyShareSettings* NearbySharingServiceLinux::GetSettings() { + return nullptr; +} + +NearbyShareLocalDeviceDataManager* +NearbySharingServiceLinux::GetLocalDeviceDataManager() { + return nullptr; +} + +NearbyShareContactManager* NearbySharingServiceLinux::GetContactManager() { + return nullptr; +} + +NearbyShareCertificateManager* +NearbySharingServiceLinux::GetCertificateManager() { + return nullptr; +} + +AccountManager* NearbySharingServiceLinux::GetAccountManager() { + return nullptr; +} + +Clock& NearbySharingServiceLinux::GetClock() { return clock_; } + +void NearbySharingServiceLinux::SetAlternateServiceUuidForDiscovery( + uint16_t alternate_service_uuid) { + alternate_service_uuid_ = alternate_service_uuid; + if (is_scanning_) { + StopDiscovery(); + StartDiscoveryIfNeeded(); + } +} + +void NearbySharingServiceLinux::StartAdvertisingIfNeeded() { + if (receive_surfaces_.empty()) { + StopAdvertising(); + return; + } + + bool has_foreground = false; + uint8_t vendor_id = 0; + for (const auto& [callback, surface] : receive_surfaces_) { + if (surface.state == ReceiveSurfaceState::kForeground) { + has_foreground = true; + vendor_id = static_cast(surface.vendor_id); + break; + } + } + + std::optional device_name = std::nullopt; + if (has_foreground) { + if (!device_name_override_.empty()) { + device_name = device_name_override_; + } else if (device_info_) { + auto name = device_info_->GetOsDeviceName(); + if (name.has_value()) { + device_name = *name; + } + } + } + + ShareTargetType device_type = ShareTargetType::kUnknown; + if (device_info_) { + device_type = + static_cast(device_info_->GetDeviceType()); + } + + if (is_advertising_ && has_foreground == last_advertise_with_name_ && + vendor_id == last_advertise_vendor_id_) { + return; + } + + if (is_advertising_) { + StopAdvertising(); + } + + std::vector endpoint_info = + BuildAdvertisement(device_name, device_type, vendor_id); + + connections::AdvertisingOptions options; + options.strategy = kStrategy; + options.allowed.SetAll(true); + options.use_stable_endpoint_id = has_foreground; + + connections::ConnectionRequestInfo request_info; + request_info.endpoint_info = + ByteArray(std::string(endpoint_info.begin(), endpoint_info.end())); + request_info.listener.initiated_cb = + [this](const std::string& id, + const connections::ConnectionResponseInfo& info) { + HandleIncomingConnectionInitiated(id, info); + }; + request_info.listener.accepted_cb = [this](const std::string& id) { + HandleConnectionAccepted(id, /*is_incoming=*/true); + }; + request_info.listener.rejected_cb = + [this](const std::string& id, connections::Status status) { + HandleConnectionRejected(id, status, /*is_incoming=*/true); + }; + request_info.listener.disconnected_cb = [this](const std::string& id) { + HandleConnectionDisconnected(id); + }; + + core_->StartAdvertising( + kServiceId, options, std::move(request_info), + [this, has_foreground, vendor_id](connections::Status status) { + is_advertising_ = status.Ok(); + if (is_advertising_) { + last_advertise_with_name_ = has_foreground; + last_advertise_vendor_id_ = vendor_id; + } + }); +} + +void NearbySharingServiceLinux::StopAdvertising() { + if (!is_advertising_) { + return; + } + is_advertising_ = false; + core_->StopAdvertising([this](connections::Status status) { + static_cast(status); + }); +} + +void NearbySharingServiceLinux::StartDiscoveryIfNeeded() { + bool needs_scanning = false; + for (const auto& [callback, surface] : send_surfaces_) { + if (surface.state == SendSurfaceState::kForeground) { + needs_scanning = true; + break; + } + } + + if (!needs_scanning) { + StopDiscovery(); + return; + } + + if (is_scanning_) { + return; + } + + connections::DiscoveryOptions options; + options.strategy = kStrategy; + options.allowed.SetAll(true); + if (alternate_service_uuid_.has_value()) { + options.ble_options.alternate_uuid = *alternate_service_uuid_; + } + + connections::DiscoveryListener listener; + listener.endpoint_found_cb = + [this](const std::string& endpoint_id, const ByteArray& endpoint_info, + const std::string& service_id) { + static_cast(service_id); + std::string info_string = std::string(endpoint_info); + std::vector info_bytes(info_string.begin(), info_string.end()); + ParsedAdvertisement parsed; + if (auto parsed_opt = ParseAdvertisement(info_bytes)) { + parsed = *parsed_opt; + } + + ShareTarget target; + target.id = next_share_target_id_++; + if (parsed.device_name.has_value()) { + target.device_name = *parsed.device_name; + } else { + target.device_name = endpoint_id; + } + target.type = parsed.device_type; + target.is_incoming = false; + target.vendor_id = parsed.vendor_id; + + auto existing = endpoint_to_target_.find(endpoint_id); + if (existing == endpoint_to_target_.end()) { + endpoint_to_target_[endpoint_id] = target; + target_id_to_endpoint_[target.id] = endpoint_id; + NotifyShareTargetDiscovered(target); + } else { + target.id = existing->second.id; + endpoint_to_target_[endpoint_id] = target; + NotifyShareTargetUpdated(target); + } + }; + listener.endpoint_lost_cb = [this](const std::string& endpoint_id) { + auto it = endpoint_to_target_.find(endpoint_id); + if (it == endpoint_to_target_.end()) { + return; + } + ShareTarget target = it->second; + endpoint_to_target_.erase(it); + target_id_to_endpoint_.erase(target.id); + NotifyShareTargetLost(target); + }; + + core_->StartDiscovery( + kServiceId, options, std::move(listener), + [this](connections::Status status) { is_scanning_ = status.Ok(); }); +} + +void NearbySharingServiceLinux::StopDiscovery() { + if (!is_scanning_) { + return; + } + core_->StopDiscovery([this](connections::Status status) { + if (status.Ok()) { + is_scanning_ = false; + } + }); +} + +std::vector NearbySharingServiceLinux::BuildAdvertisement( + const std::optional& device_name, ShareTargetType device_type, + uint8_t vendor_id) const { + const bool has_device_name = ShouldIncludeDeviceName(device_name); + std::vector salt = GenerateRandomBytes(kAdvertisementSaltSize); + std::vector metadata_key = + GenerateRandomBytes(kAdvertisementMetadataKeySize); + + size_t size = 1 + salt.size() + metadata_key.size(); + if (has_device_name) { + size += 1 + device_name->size(); + } + if (vendor_id != 0) { + size += kTlvMinLength + kVendorIdLength; + } + + std::vector endpoint_info; + endpoint_info.reserve(size); + endpoint_info.push_back(EncodeHeaderByte(has_device_name, device_type)); + endpoint_info.insert(endpoint_info.end(), salt.begin(), salt.end()); + endpoint_info.insert(endpoint_info.end(), metadata_key.begin(), + metadata_key.end()); + + if (has_device_name) { + endpoint_info.push_back( + static_cast(device_name->size() & 0xff)); + endpoint_info.insert(endpoint_info.end(), device_name->begin(), + device_name->end()); + } + + if (vendor_id != 0) { + endpoint_info.push_back(static_cast(TlvTypes::kVendorId)); + endpoint_info.push_back(kVendorIdLength); + endpoint_info.push_back(vendor_id); + } + + return endpoint_info; +} + +std::optional +NearbySharingServiceLinux::ParseAdvertisement( + absl::Span endpoint_info) const { + ParsedAdvertisement parsed; + const size_t minimum_size = + 1 + kAdvertisementSaltSize + kAdvertisementMetadataKeySize; + if (endpoint_info.size() < minimum_size) { + return std::nullopt; + } + + size_t offset = 0; + uint8_t header = endpoint_info[offset++]; + bool has_device_name = ((header >> 4) & kVisibilityBitmask) == 0; + uint8_t type = (header >> 1) & kDeviceTypeBitmask; + if (type <= static_cast(ShareTargetType::kXR)) { + parsed.device_type = static_cast(type); + } else { + parsed.device_type = ShareTargetType::kUnknown; + } + + offset += kAdvertisementSaltSize + kAdvertisementMetadataKeySize; + if (has_device_name) { + if (offset >= endpoint_info.size()) { + return parsed; + } + uint8_t name_length = endpoint_info[offset++]; + if (name_length == 0 || offset + name_length > endpoint_info.size()) { + return parsed; + } + parsed.device_name = std::string( + reinterpret_cast(endpoint_info.data() + offset), + name_length); + offset += name_length; + } + + while (offset + kTlvMinLength <= endpoint_info.size()) { + uint8_t tlv_type = endpoint_info[offset++]; + uint8_t tlv_length = endpoint_info[offset++]; + if (offset + tlv_length > endpoint_info.size()) { + break; + } + if (tlv_type == static_cast(TlvTypes::kVendorId) && + tlv_length == kVendorIdLength) { + parsed.vendor_id = endpoint_info[offset]; + } + offset += tlv_length; + } + + return parsed; +} + +void NearbySharingServiceLinux::NotifyShareTargetDiscovered( + const ShareTarget& share_target) { + for (const auto& [transfer_callback, surface] : send_surfaces_) { + if (surface.state != SendSurfaceState::kForeground || + surface.discovery_callback == nullptr) { + continue; + } + surface.discovery_callback->OnShareTargetDiscovered(share_target); + } +} + +void NearbySharingServiceLinux::NotifyShareTargetUpdated( + const ShareTarget& share_target) { + for (const auto& [transfer_callback, surface] : send_surfaces_) { + if (surface.state != SendSurfaceState::kForeground || + surface.discovery_callback == nullptr) { + continue; + } + surface.discovery_callback->OnShareTargetUpdated(share_target); + } +} + +void NearbySharingServiceLinux::NotifyShareTargetLost( + const ShareTarget& share_target) { + for (const auto& [transfer_callback, surface] : send_surfaces_) { + if (surface.state != SendSurfaceState::kForeground || + surface.discovery_callback == nullptr) { + continue; + } + surface.discovery_callback->OnShareTargetLost(share_target); + } +} + +void NearbySharingServiceLinux::NotifyTransferUpdate( + const ShareTarget& share_target, const TransferState& transfer_state, + const TransferMetadata& metadata) { + if (!transfer_state.callback) { + return; + } + transfer_state.callback->OnTransferUpdate(share_target, + transfer_state.attachments, + metadata); +} + +TransferUpdateCallback* NearbySharingServiceLinux::PickSendTransferCallback() + const { + if (send_surfaces_.empty()) { + return nullptr; + } + return send_surfaces_.begin()->first; +} + +TransferUpdateCallback* NearbySharingServiceLinux::PickReceiveTransferCallback() + const { + if (receive_surfaces_.empty()) { + return nullptr; + } + return receive_surfaces_.begin()->first; +} + +std::optional NearbySharingServiceLinux::GetEndpointIdForTarget( + int64_t share_target_id) const { + auto it = target_id_to_endpoint_.find(share_target_id); + if (it == target_id_to_endpoint_.end()) { + return std::nullopt; + } + return it->second; +} + +std::optional NearbySharingServiceLinux::GetShareTarget( + absl::string_view endpoint_id) const { + auto it = endpoint_to_target_.find(std::string(endpoint_id)); + if (it == endpoint_to_target_.end()) { + return std::nullopt; + } + return it->second; +} + +void NearbySharingServiceLinux::HandleIncomingConnectionInitiated( + const std::string& endpoint_id, + const connections::ConnectionResponseInfo& info) { + static_cast(info); + std::vector info_bytes(info.remote_endpoint_info.begin(), + info.remote_endpoint_info.end()); + ParsedAdvertisement parsed; + if (auto parsed_opt = ParseAdvertisement(info_bytes)) { + parsed = *parsed_opt; + } + + ShareTarget target; + target.id = next_share_target_id_++; + target.device_name = parsed.device_name.value_or(endpoint_id); + target.type = parsed.device_type; + target.is_incoming = true; + target.vendor_id = parsed.vendor_id; + + auto existing = endpoint_to_target_.find(endpoint_id); + if (existing == endpoint_to_target_.end()) { + endpoint_to_target_[endpoint_id] = target; + target_id_to_endpoint_[target.id] = endpoint_id; + } else { + target.id = existing->second.id; + endpoint_to_target_[endpoint_id] = target; + } + + TransferState transfer_state; + transfer_state.attachments = AttachmentContainer(); + transfer_state.callback = PickReceiveTransferCallback(); + transfer_state.is_incoming = true; + active_transfers_[endpoint_id] = transfer_state; + + TransferMetadata metadata = TransferMetadataBuilder() + .set_status(TransferMetadata::Status::kAwaitingLocalConfirmation) + .set_progress(0) + .build(); + NotifyTransferUpdate(target, transfer_state, metadata); +} + +void NearbySharingServiceLinux::HandleOutgoingConnectionInitiated( + const std::string& endpoint_id, + const connections::ConnectionResponseInfo& info) { + static_cast(info); + core_->AcceptConnection(endpoint_id, MakePayloadListener(false), + [this, endpoint_id](connections::Status status) { + if (!status.Ok()) { + HandleConnectionRejected(endpoint_id, status, + /*is_incoming=*/false); + } + }); + + auto share_target = GetShareTarget(endpoint_id); + auto transfer_it = active_transfers_.find(endpoint_id); + if (share_target && transfer_it != active_transfers_.end()) { + TransferMetadata metadata = + TransferMetadataBuilder() + .set_status(TransferMetadata::Status::kAwaitingRemoteAcceptance) + .set_progress(0) + .build(); + NotifyTransferUpdate(*share_target, transfer_it->second, metadata); + } +} + +void NearbySharingServiceLinux::HandleConnectionAccepted( + const std::string& endpoint_id, bool is_incoming) { + auto transfer_it = active_transfers_.find(endpoint_id); + if (transfer_it == active_transfers_.end()) { + return; + } + + auto share_target = GetShareTarget(endpoint_id); + if (share_target) { + TransferMetadata metadata = TransferMetadataBuilder() + .set_status(TransferMetadata::Status::kInProgress) + .set_progress(0) + .set_total_attachments_count( + transfer_it->second.attachments.GetAttachmentCount()) + .build(); + NotifyTransferUpdate(*share_target, transfer_it->second, metadata); + } + + if (!is_incoming) { + const AttachmentContainer& attachments = transfer_it->second.attachments; + std::unique_ptr payload; + if (!attachments.GetTextAttachments().empty()) { + std::string text = + std::string(attachments.GetTextAttachments()[0].text_body()); + payload = std::make_unique(ByteArray(text)); + } else if (!attachments.GetFileAttachments().empty()) { + const auto& file_attachment = attachments.GetFileAttachments()[0]; + if (file_attachment.file_path().has_value()) { + std::string file_path = file_attachment.file_path()->ToString(); + nearby::InputFile input_file(file_path, file_attachment.size()); + payload = std::make_unique( + std::string(file_attachment.parent_folder()), + std::string(file_attachment.file_name()), std::move(input_file)); + } + } + + if (payload) { + std::vector endpoints; + endpoints.push_back(endpoint_id); + core_->SendPayload( + endpoints, std::move(*payload), + [this](connections::Status status) { + if (!status.Ok()) { + is_transferring_ = false; + } + }); + } + } + + is_transferring_ = true; +} + +void NearbySharingServiceLinux::HandleConnectionRejected( + const std::string& endpoint_id, connections::Status status, + bool is_incoming) { + static_cast(status); + static_cast(is_incoming); + auto transfer_it = active_transfers_.find(endpoint_id); + if (transfer_it == active_transfers_.end()) { + return; + } + auto share_target = GetShareTarget(endpoint_id); + if (share_target) { + TransferMetadata metadata = TransferMetadataBuilder() + .set_status(TransferMetadata::Status::kRejected) + .set_progress(0) + .build(); + NotifyTransferUpdate(*share_target, transfer_it->second, metadata); + } + active_transfers_.erase(transfer_it); + is_transferring_ = false; +} + +void NearbySharingServiceLinux::HandleConnectionDisconnected( + const std::string& endpoint_id) { + active_transfers_.erase(endpoint_id); + if (active_transfers_.empty()) { + is_transferring_ = false; + } +} + +connections::PayloadListener NearbySharingServiceLinux::MakePayloadListener( + bool is_incoming) { + static_cast(is_incoming); + connections::PayloadListener listener; + listener.payload_cb = + [this, is_incoming](absl::string_view endpoint_id, + connections::Payload payload) { + auto transfer_it = active_transfers_.find(std::string(endpoint_id)); + if (transfer_it == active_transfers_.end()) { + return; + } + auto share_target = GetShareTarget(endpoint_id); + if (!share_target) { + return; + } + + TransferMetadata metadata = TransferMetadataBuilder() + .set_status(TransferMetadata::Status::kInProgress) + .set_progress(0) + .build(); + NotifyTransferUpdate(*share_target, transfer_it->second, metadata); + }; + + listener.payload_progress_cb = + [this, is_incoming](absl::string_view endpoint_id, + const connections::PayloadProgressInfo& info) { + auto transfer_it = active_transfers_.find(std::string(endpoint_id)); + if (transfer_it == active_transfers_.end()) { + return; + } + auto share_target = GetShareTarget(endpoint_id); + if (!share_target) { + return; + } + + float progress = 0.0f; + if (info.total_bytes > 0) { + progress = static_cast(info.bytes_transferred) / + static_cast(info.total_bytes); + } + + TransferMetadata metadata = + TransferMetadataBuilder() + .set_status(StatusFromPayloadStatus(info.status)) + .set_progress(progress) + .set_transferred_bytes(info.bytes_transferred) + .build(); + + NotifyTransferUpdate(*share_target, transfer_it->second, metadata); + + if (TransferMetadata::IsFinalStatus(metadata.status())) { + active_transfers_.erase(transfer_it); + if (active_transfers_.empty()) { + is_transferring_ = false; + } + } + }; + return listener; +} + +NearbySharingService::StatusCodes NearbySharingServiceLinux::StatusFromConnections( + connections::Status status) const { + if (status.Ok()) { + return StatusCodes::kOk; + } + if (status.value == connections::Status::kOutOfOrderApiCall) { + return StatusCodes::kOutOfOrderApiCall; + } + return StatusCodes::kError; +} + +} // namespace nearby::sharing::linux diff --git a/sharing/linux/nearby_sharing_service_linux.h b/sharing/linux/nearby_sharing_service_linux.h new file mode 100644 index 00000000..bbfc0beb --- /dev/null +++ b/sharing/linux/nearby_sharing_service_linux.h @@ -0,0 +1,229 @@ +// Copyright 2025 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_SHARING_LINUX_NEARBY_SHARING_SERVICE_LINUX_H_ +#define THIRD_PARTY_NEARBY_SHARING_LINUX_NEARBY_SHARING_SERVICE_LINUX_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "absl/functional/any_invocable.h" +#include "absl/strings/string_view.h" +#include "absl/types/span.h" +#include "connections/advertising_options.h" +#include "connections/connection_options.h" +#include "connections/core.h" +#include "connections/discovery_options.h" +#include "connections/implementation/service_controller_router.h" +#include "connections/listeners.h" +#include "connections/payload.h" +#include "connections/status.h" +#include "connections/strategy.h" +#include "internal/platform/bluetooth_adapter.h" +#include "internal/platform/clock_impl.h" +#include "internal/platform/implementation/platform.h" +#include "sharing/attachment_container.h" +#include "sharing/nearby_sharing_service.h" +#include "sharing/share_target.h" +#include "sharing/share_target_discovered_callback.h" +#include "sharing/transfer_metadata.h" +#include "sharing/transfer_metadata_builder.h" + +namespace nearby::sharing::linux { + +class NearbySharingServiceLinux : public NearbySharingService { + public: + using StatusCodes = NearbySharingService::StatusCodes; + + NearbySharingServiceLinux(); + explicit NearbySharingServiceLinux(std::string device_name_override); + ~NearbySharingServiceLinux() override; + + void AddObserver(Observer* observer) override; + void RemoveObserver(Observer* observer) override; + + void Shutdown( + std::function status_codes_callback) override; + + void RegisterSendSurface( + TransferUpdateCallback* transfer_callback, + ShareTargetDiscoveredCallback* discovery_callback, SendSurfaceState state, + Advertisement::BlockedVendorId blocked_vendor_id, + bool disable_wifi_hotspot, + std::function status_codes_callback) override; + + void UnregisterSendSurface( + TransferUpdateCallback* transfer_callback, + std::function status_codes_callback) override; + + void RegisterReceiveSurface( + TransferUpdateCallback* transfer_callback, ReceiveSurfaceState state, + Advertisement::BlockedVendorId vendor_id, + std::function status_codes_callback) override; + + void UnregisterReceiveSurface( + TransferUpdateCallback* transfer_callback, + std::function status_codes_callback) override; + + void ClearForegroundReceiveSurfaces( + std::function status_codes_callback) override; + + bool IsTransferring() const override; + bool IsScanning() const override; + bool IsBluetoothPresent() const override; + bool IsBluetoothPowered() const override; + bool IsExtendedAdvertisingSupported() const override; + bool IsLanConnected() const override; + std::string GetQrCodeUrl() const override; + + void SendAttachments( + int64_t share_target_id, + std::unique_ptr + attachment_container, + std::function status_codes_callback) override; + + void Accept(int64_t share_target_id, + std::function + status_codes_callback) override; + + void Reject(int64_t share_target_id, + std::function + status_codes_callback) override; + + void Cancel(int64_t share_target_id, + std::function + status_codes_callback) override; + + void SetVisibility(proto::DeviceVisibility visibility, + absl::Duration expiration, + absl::AnyInvocable + callback) override; + + std::string Dump() const override; + void UpdateFilePathsInProgress(bool update_file_paths) override; + + NearbyShareSettings* GetSettings() override; + NearbyShareLocalDeviceDataManager* GetLocalDeviceDataManager() override; + NearbyShareContactManager* GetContactManager() override; + NearbyShareCertificateManager* GetCertificateManager() override; + AccountManager* GetAccountManager() override; + Clock& GetClock() override; + void SetAlternateServiceUuidForDiscovery( + uint16_t alternate_service_uuid) override; + + private: + struct SendSurface { + ShareTargetDiscoveredCallback* discovery_callback = nullptr; + SendSurfaceState state = SendSurfaceState::kUnknown; + bool disable_wifi_hotspot = false; + }; + + struct ReceiveSurface { + ReceiveSurfaceState state = ReceiveSurfaceState::kUnknown; + Advertisement::BlockedVendorId vendor_id = + Advertisement::BlockedVendorId::kNone; + }; + + struct TransferState { + nearby::sharing::AttachmentContainer attachments; + TransferUpdateCallback* callback = nullptr; + bool is_incoming = false; + }; + + struct ParsedAdvertisement { + ShareTargetType device_type = ShareTargetType::kUnknown; + std::optional device_name; + uint8_t vendor_id = 0; + }; + + void StartAdvertisingIfNeeded(); + void StopAdvertising(); + void StartDiscoveryIfNeeded(); + void StopDiscovery(); + + std::vector BuildAdvertisement( + const std::optional& device_name, + ShareTargetType device_type, uint8_t vendor_id) const; + + std::optional ParseAdvertisement( + absl::Span endpoint_info) const; + + void NotifyShareTargetDiscovered(const ShareTarget& share_target); + void NotifyShareTargetUpdated(const ShareTarget& share_target); + void NotifyShareTargetLost(const ShareTarget& share_target); + void NotifyTransferUpdate(const ShareTarget& share_target, + const TransferState& transfer_state, + const TransferMetadata& metadata); + + TransferUpdateCallback* PickSendTransferCallback() const; + TransferUpdateCallback* PickReceiveTransferCallback() const; + + std::optional GetEndpointIdForTarget( + int64_t share_target_id) const; + + std::optional GetShareTarget( + absl::string_view endpoint_id) const; + + void HandleIncomingConnectionInitiated( + const std::string& endpoint_id, + const connections::ConnectionResponseInfo& info); + + void HandleOutgoingConnectionInitiated( + const std::string& endpoint_id, + const connections::ConnectionResponseInfo& info); + + void HandleConnectionAccepted(const std::string& endpoint_id, + bool is_incoming); + void HandleConnectionRejected(const std::string& endpoint_id, + connections::Status status, bool is_incoming); + void HandleConnectionDisconnected(const std::string& endpoint_id); + + connections::PayloadListener MakePayloadListener(bool is_incoming); + + StatusCodes StatusFromConnections(connections::Status status) const; + + std::string device_name_override_; + std::unique_ptr<::nearby::api::DeviceInfo> device_info_; + BluetoothAdapter bluetooth_adapter_; + ClockImpl clock_; + + std::unique_ptr router_; + std::unique_ptr core_; + + std::unordered_set observers_; + std::unordered_map send_surfaces_; + std::unordered_map receive_surfaces_; + + std::unordered_map endpoint_to_target_; + std::unordered_map target_id_to_endpoint_; + std::unordered_map active_transfers_; + + std::optional alternate_service_uuid_; + bool is_scanning_ = false; + bool is_advertising_ = false; + bool is_transferring_ = false; + int64_t next_share_target_id_ = 1; + bool last_advertise_with_name_ = false; + uint8_t last_advertise_vendor_id_ = 0; +}; + +} // namespace nearby::sharing::linux + +#endif // THIRD_PARTY_NEARBY_SHARING_LINUX_NEARBY_SHARING_SERVICE_LINUX_H_ diff --git a/sharing/linux/simple_example.cc b/sharing/linux/simple_example.cc new file mode 100644 index 00000000..04a46151 --- /dev/null +++ b/sharing/linux/simple_example.cc @@ -0,0 +1,251 @@ +// Copyright 2025 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. + +// Simple example: Send a text message to nearby device +// +// Usage: +// Terminal 1 (Receiver): ./simple_receiver +// Terminal 2 (Sender): ./simple_sender "Hello World" + +#include +#include +#include +#include +#include + +#include "sharing/linux/nearby_sharing_service_linux.h" +#include "sharing/attachment_container.h" +#include "sharing/text_attachment.h" +#include "sharing/share_target.h" +#include "sharing/transfer_metadata.h" +#include "sharing/transfer_update_callback.h" +#include "sharing/share_target_discovered_callback.h" + +using namespace nearby::sharing; +using namespace nearby::sharing::linux; + +// Simple callback that automatically accepts incoming transfers +class SimpleReceiverCallback : public TransferUpdateCallback { + public: + explicit SimpleReceiverCallback(NearbySharingServiceLinux* service) + : service_(service) {} + + void OnTransferUpdate(const ShareTarget& share_target, + const AttachmentContainer& attachment_container, + const TransferMetadata& transfer_metadata) override { + std::cout << "\n[Receiver] Transfer Update from: " << share_target.device_name << std::endl; + std::cout << "[Receiver] Status: " + << TransferMetadata::StatusToString(transfer_metadata.status()) << std::endl; + + // Auto-accept incoming transfers + if (transfer_metadata.status() == TransferMetadata::Status::kAwaitingLocalConfirmation) { + std::cout << "[Receiver] Auto-accepting transfer..." << std::endl; + service_->Accept(share_target.id, [](auto status) { + std::cout << "[Receiver] Accept status: " + << NearbySharingService::StatusCodeToString(status) << std::endl; + }); + } + + // Show received content when complete + if (transfer_metadata.status() == TransferMetadata::Status::kComplete) { + std::cout << "\n[Receiver] ✓ Transfer Complete!" << std::endl; + + // Display received text + for (const auto& text : attachment_container.GetTextAttachments()) { + std::cout << "[Receiver] Received text: \"" << text.text_body() << "\"" << std::endl; + } + + // Display received files + for (const auto& file : attachment_container.GetFileAttachments()) { + std::cout << "[Receiver] Received file: " << file.file_name() << std::endl; + } + } + } + + private: + NearbySharingServiceLinux* service_; +}; + +// Callback for discovering nearby devices +class SimpleSenderCallback : public ShareTargetDiscoveredCallback { + public: + void OnShareTargetDiscovered(const ShareTarget& share_target) override { + std::cout << "\n[Sender] Found device: " << share_target.device_name + << " (ID: " << share_target.id << ")" << std::endl; + discovered_target_ = share_target; + has_target_ = true; + } + + void OnShareTargetLost(const ShareTarget& share_target) override { + std::cout << "[Sender] Lost device: " << share_target.device_name << std::endl; + if (has_target_ && discovered_target_.id == share_target.id) { + has_target_ = false; + } + } + + void OnShareTargetUpdated(const ShareTarget& share_target) override {} + + bool HasTarget() const { return has_target_; } + const ShareTarget& GetTarget() const { return discovered_target_; } + + private: + ShareTarget discovered_target_; + bool has_target_ = false; +}; + +// Transfer progress callback for sender +class SimpleSenderTransferCallback : public TransferUpdateCallback { + public: + void OnTransferUpdate(const ShareTarget& share_target, + const AttachmentContainer& attachment_container, + const TransferMetadata& transfer_metadata) override { + std::cout << "[Sender] Transfer to " << share_target.device_name + << ": " << TransferMetadata::StatusToString(transfer_metadata.status()) + << " (" << (transfer_metadata.progress() * 100) << "%)" << std::endl; + + if (transfer_metadata.status() == TransferMetadata::Status::kComplete) { + std::cout << "[Sender] ✓ Transfer Complete!" << std::endl; + transfer_complete_ = true; + } else if (transfer_metadata.status() == TransferMetadata::Status::kFailed) { + std::cout << "[Sender] ✗ Transfer Failed!" << std::endl; + transfer_complete_ = true; + } + } + + bool IsTransferComplete() const { return transfer_complete_; } + + private: + bool transfer_complete_ = false; +}; + +int main(int argc, char* argv[]) { + if (argc < 2) { + std::cout << "Usage: " << argv[0] << " [message]" << std::endl; + std::cout << "Examples:" << std::endl; + std::cout << " " << argv[0] << " receiver # Start as receiver" << std::endl; + std::cout << " " << argv[0] << " sender \"Hello!\" # Send text message" << std::endl; + return 1; + } + + std::string mode = argv[1]; + + if (mode == "receiver") { + // ============= RECEIVER MODE ============= + std::cout << "=== Nearby Sharing Receiver ===" << std::endl; + + NearbySharingServiceLinux service("Receiver-Device"); + SimpleReceiverCallback callback(&service); + + // Register as a receiver (advertise to nearby devices) + service.RegisterReceiveSurface( + &callback, + NearbySharingService::ReceiveSurfaceState::kForeground, + Advertisement::BlockedVendorId::kNone, + [](auto status) { + if (status == NearbySharingService::StatusCodes::kOk) { + std::cout << "[Receiver] ✓ Advertising started" << std::endl; + std::cout << "[Receiver] Waiting for incoming transfers..." << std::endl; + } else { + std::cout << "[Receiver] ✗ Failed to start: " + << NearbySharingService::StatusCodeToString(status) << std::endl; + } + }); + + // Keep running to receive transfers + std::cout << "\nPress Ctrl+C to exit..." << std::endl; + while (true) { + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + + } else if (mode == "sender") { + // ============= SENDER MODE ============= + std::string message = "Hello from Nearby Sharing!"; + if (argc > 2) { + message = argv[2]; + } + + std::cout << "=== Nearby Sharing Sender ===" << std::endl; + std::cout << "[Sender] Message to send: \"" << message << "\"" << std::endl; + + NearbySharingServiceLinux service("Sender-Device"); + SimpleSenderCallback discovery_callback; + SimpleSenderTransferCallback transfer_callback; + + // Register as sender (scan for nearby devices) + service.RegisterSendSurface( + &transfer_callback, + &discovery_callback, + NearbySharingService::SendSurfaceState::kForeground, + Advertisement::BlockedVendorId::kNone, + false, + [](auto status) { + if (status == NearbySharingService::StatusCodes::kOk) { + std::cout << "[Sender] ✓ Scanning started" << std::endl; + } else { + std::cout << "[Sender] ✗ Failed to start: " + << NearbySharingService::StatusCodeToString(status) << std::endl; + } + }); + + // Wait for device discovery + std::cout << "[Sender] Scanning for nearby devices..." << std::endl; + int wait_time = 0; + while (!discovery_callback.HasTarget() && wait_time < 10) { + std::this_thread::sleep_for(std::chrono::seconds(1)); + wait_time++; + std::cout << "." << std::flush; + } + std::cout << std::endl; + + if (!discovery_callback.HasTarget()) { + std::cout << "[Sender] ✗ No devices found. Make sure receiver is running!" << std::endl; + return 1; + } + + // Create text attachment + auto attachment_container = std::make_unique(); + attachment_container->AddTextAttachment(TextAttachment( + nearby::sharing::service::proto::TextMetadata::TEXT, + message, + std::nullopt, + std::nullopt + )); + + // Send to discovered device + const auto& target = discovery_callback.GetTarget(); + std::cout << "\n[Sender] Sending to: " << target.device_name << std::endl; + + service.SendAttachments( + target.id, + std::move(attachment_container), + [](auto status) { + std::cout << "[Sender] Send initiated: " + << NearbySharingService::StatusCodeToString(status) << std::endl; + }); + + // Wait for transfer to complete + std::cout << "[Sender] Transferring..." << std::endl; + while (!transfer_callback.IsTransferComplete()) { + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + } + + std::cout << "\n[Sender] Done!" << std::endl; + + } else { + std::cout << "Invalid mode. Use 'receiver' or 'sender'" << std::endl; + return 1; + } + + return 0; +}