mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-14 14:46:12 -04:00
Merge pull request #2 from kidfromjupiter/import-abandoned-linux
Import abandoned linux
This commit is contained in:
@@ -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
|
||||
Executable
+50
@@ -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 <your-port-name-here>"
|
||||
|
||||
# [Optional] Uncomment this section to install additional packages.
|
||||
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
||||
# && apt-get -y install --no-install-recommends <your-package-list-here>
|
||||
Executable
+20
@@ -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"
|
||||
]
|
||||
}
|
||||
Executable
+50
@@ -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 <your-port-name-here>"
|
||||
|
||||
# [Optional] Uncomment this section to install additional packages.
|
||||
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
||||
# && apt-get -y install --no-install-recommends <your-package-list-here>
|
||||
Executable
+55
@@ -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"
|
||||
]
|
||||
}
|
||||
Executable
+59
@@ -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
|
||||
+2
-1
@@ -59,5 +59,6 @@ bazel-*
|
||||
/.clwb/
|
||||
|
||||
# Devcontainers
|
||||
/.devcontainer/
|
||||
# /.devcontainer/
|
||||
/connections/walkietalkie/
|
||||
/third_party/
|
||||
|
||||
@@ -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.
|
||||
+12
-1
@@ -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,
|
||||
)""",
|
||||
|
||||
@@ -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`.
|
||||
|
||||
+14
-11
@@ -15,6 +15,7 @@
|
||||
#include "connections/core.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@@ -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<v3::ConnectionListener>(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) {
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,555 @@
|
||||
#include <signal.h>
|
||||
|
||||
#include <cctype>
|
||||
#include <cstdlib>
|
||||
#include <ctime>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#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<std::string> 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<unsigned char>(input[start]))) {
|
||||
++start;
|
||||
}
|
||||
while (end > start &&
|
||||
std::isspace(static_cast<unsigned char>(input[end - 1]))) {
|
||||
--end;
|
||||
}
|
||||
return std::string(input.substr(start, end - start));
|
||||
}
|
||||
|
||||
std::vector<std::string> SplitCommaList(std::string_view input) {
|
||||
std::vector<std::string> 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<nearby::connections::ServiceControllerRouter>()),
|
||||
core_(std::make_unique<nearby::connections::Core>(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<std::mutex> 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<std::mutex> 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<nearby::connections::Payload> 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<std::int64_t>(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<std::mutex> 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<nearby::connections::v3::ConnectionsDevice>
|
||||
CacheDiscoveredDevice(const nearby::NearbyDevice& remote_device) {
|
||||
const std::string endpoint_id = remote_device.GetEndpointId();
|
||||
std::lock_guard<std::mutex> 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<const nearby::connections::v3::ConnectionsDevice*>(
|
||||
&remote_device);
|
||||
if (connections_device != nullptr) {
|
||||
endpoint_info = connections_device->GetEndpointInfo();
|
||||
}
|
||||
}
|
||||
auto device = std::make_shared<nearby::connections::v3::ConnectionsDevice>(
|
||||
endpoint_id, endpoint_info, remote_device.GetConnectionInfos());
|
||||
discovered_devices_.emplace(endpoint_id, device);
|
||||
return device;
|
||||
}
|
||||
|
||||
Options options_;
|
||||
std::unique_ptr<nearby::connections::ServiceControllerRouter> router_;
|
||||
std::unique_ptr<nearby::connections::Core> core_;
|
||||
nearby::connections::v3::ConnectionsDevice local_device_;
|
||||
std::mutex discovered_mutex_;
|
||||
std::unordered_map<std::string,
|
||||
std::shared_ptr<nearby::connections::v3::ConnectionsDevice>>
|
||||
discovered_devices_;
|
||||
std::mutex incoming_mutex_;
|
||||
std::unordered_map<std::int64_t, std::string> incoming_files_;
|
||||
std::mutex sent_mutex_;
|
||||
std::unordered_set<std::string> sent_to_;
|
||||
};
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
std::srand(static_cast<unsigned int>(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;
|
||||
}
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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 <atomic>
|
||||
#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_
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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 <atomic>
|
||||
|
||||
#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_
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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 <atomic>
|
||||
#include <cstdint>
|
||||
#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
|
||||
@@ -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 <atomic>
|
||||
#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<std::uint32_t> atomic_uint32_{0};
|
||||
};
|
||||
} // namespace api
|
||||
} // namespace nearby
|
||||
#endif //LINUX_ATOMIC_BOOLEAN_H
|
||||
@@ -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
|
||||
@@ -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 <memory>
|
||||
|
||||
#include <sdbus-c++/IConnection.h>
|
||||
#include <sdbus-c++/ProxyInterfaces.h>
|
||||
|
||||
#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<org::freedesktop::Avahi::Server2_proxy> {
|
||||
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<org::freedesktop::Avahi::EntryGroup_proxy> {
|
||||
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<Server> 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> server_;
|
||||
};
|
||||
} // namespace avahi
|
||||
} // namespace linux
|
||||
} // namespace nearby
|
||||
|
||||
#endif
|
||||
@@ -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 <algorithm>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
|
||||
#include <sdbus-c++/Types.h>
|
||||
|
||||
#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<Uuid> &characteristic_uuids) {
|
||||
return gatt_discovery_->DiscoverServiceAndCharacteristics(
|
||||
peripheral_object_path_, service_uuid, characteristic_uuids,
|
||||
discovery_cancel_);
|
||||
}
|
||||
|
||||
absl::optional<api::ble_v2::GattCharacteristic> 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<std::string> 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<std::string> 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>{
|
||||
std::string(value_bytes.begin(), value_bytes.end())};
|
||||
} catch (const sdbus::Error &e) {
|
||||
DBUS_LOG_METHOD_CALL_ERROR(chr, "ReadValue", e);
|
||||
return std::optional<std::string>();
|
||||
}
|
||||
},
|
||||
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<uint8_t> 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<void(absl::string_view value)>
|
||||
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<bluez::SubscribedGattCharacteristicClient>>(
|
||||
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<sdbus::ObjectPath,
|
||||
std::map<std::string, std::map<std::string, sdbus::Variant>>>
|
||||
objects;
|
||||
try {
|
||||
objects = GetManagedObjects();
|
||||
} catch (const sdbus::Error &e) {
|
||||
DBUS_LOG_METHOD_CALL_ERROR(this, "GetManagedObjects", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
absl::flat_hash_map<sdbus::ObjectPath, GattServiceClient> cached_services;
|
||||
absl::MutexLock lock(&mutex_);
|
||||
auto chr_it = std::find_if(
|
||||
objects.cbegin(), objects.cend(),
|
||||
[](std::pair<sdbus::ObjectPath,
|
||||
std::map<std::string, std::map<std::string, sdbus::Variant>>>
|
||||
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<void()> 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<absl::AnyInvocable<void()>>{});
|
||||
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<Uuid> &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<bluez::GattCharacteristicClient>
|
||||
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<bluez::GattCharacteristicClient>(system_bus_,
|
||||
path_it->second);
|
||||
}
|
||||
|
||||
std::unique_ptr<bluez::GattCharacteristicClient>
|
||||
BluezGattDiscovery::GetSubscribedCharacteristic(
|
||||
const sdbus::ObjectPath &device_object_path, const Uuid &service_uuid,
|
||||
const Uuid &characteristic_uuid,
|
||||
absl::AnyInvocable<void(absl::string_view value)>
|
||||
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<bluez::SubscribedGattCharacteristicClient>(
|
||||
system_bus_, device_object_path, std::move(on_characteristic_changed_cb));
|
||||
}
|
||||
|
||||
std::optional<std::tuple<Uuid, Uuid, sdbus::ObjectPath>>
|
||||
BluezGattDiscovery::characteristicProperties(
|
||||
const sdbus::ObjectPath &char_path,
|
||||
const std::map<std::string, sdbus::Variant> &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<GattServiceClient>(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<std::string, std::map<std::string, sdbus::Variant>>
|
||||
&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<std::string> &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
|
||||
@@ -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 <list>
|
||||
|
||||
#include <sdbus-c++/IConnection.h>
|
||||
#include <sdbus-c++/Types.h>
|
||||
|
||||
#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<sdbus::IConnection> 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<absl::AnyInvocable<void()>>::iterator;
|
||||
CallbackIter AddPeripheralConnection(
|
||||
const sdbus::ObjectPath &device_object_path,
|
||||
absl::AnyInvocable<void()> 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<Uuid> &characteristic_uuids, CancellationFlag &cancel)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
std::unique_ptr<bluez::GattCharacteristicClient> GetCharacteristic(
|
||||
const sdbus::ObjectPath &device_object_path, const Uuid &service_uuid,
|
||||
const Uuid &characteristic_uuid) ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
std::unique_ptr<bluez::GattCharacteristicClient> GetSubscribedCharacteristic(
|
||||
const sdbus::ObjectPath &device_object_path, const Uuid &service_uuid,
|
||||
const Uuid &characteristic_uuid,
|
||||
absl::AnyInvocable<void(absl::string_view value)>
|
||||
on_characteristic_changed_cb) ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
protected:
|
||||
void onInterfacesAdded(
|
||||
const sdbus::ObjectPath &objectPath,
|
||||
const std::map<std::string, std::map<std::string, sdbus::Variant>>
|
||||
&interfacesAndProperties) override ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
void onInterfacesRemoved(const sdbus::ObjectPath &objectPath,
|
||||
const std::vector<std::string> &interfaces) override
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
private:
|
||||
std::optional<std::tuple<Uuid, Uuid, sdbus::ObjectPath>>
|
||||
characteristicProperties(
|
||||
const sdbus::ObjectPath &char_path,
|
||||
const std::map<std::string, sdbus::Variant> &properties)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
void Shutdown() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
std::shared_ptr<sdbus::IConnection> system_bus_;
|
||||
|
||||
absl::Mutex peripheral_disconnected_callbacks_mutex_;
|
||||
absl::flat_hash_map<sdbus::ObjectPath, std::list<absl::AnyInvocable<void()>>>
|
||||
peripheral_disconnected_callbacks_
|
||||
ABSL_GUARDED_BY(peripheral_disconnected_callbacks_mutex_);
|
||||
|
||||
absl::Mutex mutex_;
|
||||
absl::flat_hash_map<sdbus::ObjectPath, std::unique_ptr<GattServiceClient>>
|
||||
cached_services_ ABSL_GUARDED_BY(mutex_);
|
||||
// Tuple order: service uuid, characteristic uuid, device object path
|
||||
absl::flat_hash_map<std::tuple<Uuid, Uuid, sdbus::ObjectPath>,
|
||||
sdbus::ObjectPath>
|
||||
discovered_characteristics_ ABSL_GUARDED_BY(mutex_);
|
||||
absl::flat_hash_map<sdbus::ObjectPath,
|
||||
std::tuple<Uuid, Uuid, sdbus::ObjectPath>>
|
||||
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<sdbus::IConnection> system_bus,
|
||||
const sdbus::ObjectPath &peripheral_object_path,
|
||||
std::shared_ptr<BluezGattDiscovery> gatt_discovery,
|
||||
absl::AnyInvocable<void()> 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<Uuid> &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<api::ble_v2::GattCharacteristic> 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<std::string> 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<void(absl::string_view value)>
|
||||
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<sdbus::IConnection> system_bus_;
|
||||
sdbus::ObjectPath peripheral_object_path_;
|
||||
std::shared_ptr<BluezGattDiscovery> 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<bluez::GattCharacteristicClient>,
|
||||
std::unique_ptr<bluez::SubscribedGattCharacteristicClient>>;
|
||||
absl::Mutex characteristics_mutex_;
|
||||
absl::flat_hash_map<api::ble_v2::GattCharacteristic, CharacteristicProxy>
|
||||
characteristics_ ABSL_GUARDED_BY(characteristics_mutex_);
|
||||
};
|
||||
|
||||
} // namespace linux
|
||||
} // namespace nearby
|
||||
|
||||
#endif
|
||||
@@ -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<api::ble_v2::GattCharacteristic>
|
||||
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<bluez::GattServiceServer>(
|
||||
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<bluez::GattProfile> (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<bluez::GattCharacteristicServer> 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<bluez::GattCharacteristicServer> 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
|
||||
@@ -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 <memory>
|
||||
#include <regex>
|
||||
|
||||
#include <sdbus-c++/IConnection.h>
|
||||
|
||||
#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<BluetoothDevices> 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<RootObjectManager>(system_bus_, "/com/google/nearby/medium/ble/gatt")),
|
||||
gatt_manager_(std::make_unique<bluez::GattManager>(system_bus_, adapter_.GetObjectPath())),
|
||||
server_cb_(std::make_shared<api::ble_v2::ServerGattConnectionCallback>(
|
||||
std::move(server_cb))) {}
|
||||
~GattServer() override = default;
|
||||
|
||||
absl::optional<api::ble_v2::GattCharacteristic> 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<BluetoothDevices> devices_;
|
||||
BluetoothAdapter adapter_;
|
||||
LocalBlePeripheral local_peripheral_;
|
||||
|
||||
std::unique_ptr<RootObjectManager> gatt_service_root_object_manager;
|
||||
absl::Mutex profiles_mutex_;
|
||||
absl::flat_hash_map<Uuid, std::unique_ptr<bluez::GattProfile>> gatt_profiles_;
|
||||
ABSL_GUARDED_BY(profiles_mutex_)
|
||||
std::unique_ptr<bluez::GattManager> gatt_manager_;
|
||||
std::shared_ptr<api::ble_v2::ServerGattConnectionCallback> server_cb_;
|
||||
absl::Mutex services_mutex_;
|
||||
absl::flat_hash_map<Uuid, std::unique_ptr<bluez::GattServiceServer>> services_
|
||||
ABSL_GUARDED_BY(services_mutex_);
|
||||
};
|
||||
|
||||
} // namespace linux
|
||||
} // namespace nearby
|
||||
#endif
|
||||
@@ -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 <bluetooth/bluetooth.h>
|
||||
#include <bluetooth/l2cap.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
|
||||
#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<api::ble_v2::BleL2capSocket> 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<BleL2capSocket>(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<BleL2capSocket>(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<void()> notifier) {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
close_notifier_ = std::move(notifier);
|
||||
}
|
||||
|
||||
} // namespace linux
|
||||
} // namespace nearby
|
||||
@@ -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 <memory>
|
||||
#include <sdbus-c++/Types.h>
|
||||
|
||||
#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<api::ble_v2::BleL2capSocket> Accept() override
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
Exception Close() override ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
void SetCloseNotifier(absl::AnyInvocable<void()> notifier)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
private:
|
||||
Exception DoClose() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
mutable absl::Mutex mutex_;
|
||||
absl::CondVar cond_;
|
||||
absl::AnyInvocable<void()> 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_;
|
||||
// <server_fd, <client_fd, peripheral_id>>
|
||||
absl::flat_hash_map<int, std::pair<int,api::ble_v2::BlePeripheral::UniqueId>> accepted_fds_ ABSL_GUARDED_BY(mutex_);
|
||||
};
|
||||
|
||||
} // namespace linux
|
||||
} // namespace nearby
|
||||
|
||||
#endif // PLATFORM_IMPL_LINUX_BLE_L2CAP_SERVER_SOCKET_H_
|
||||
@@ -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 <bluetooth/bluetooth.h>
|
||||
#include <bluetooth/l2cap.h>
|
||||
#include <sys/poll.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <bluetooth/bluetooth.h>
|
||||
|
||||
#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 <sys/ioctl.h>
|
||||
|
||||
#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<size_t>(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<size_t>(pending);
|
||||
}
|
||||
return 0; // unknown
|
||||
}
|
||||
ExceptionOr<ByteArray> 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<size_t>(msg_len);
|
||||
size_t cap = static_cast<size_t>(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<size_t>(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<size_t>(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<size_t>(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<BleL2capInputStream>(fd)),
|
||||
output_stream_(std::make_unique<BleL2capOutputStream>(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<void()> notifier) {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
close_notifier_ = std::move(notifier);
|
||||
}
|
||||
|
||||
bool BleL2capSocket::IsClosed() const {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return closed_;
|
||||
}
|
||||
|
||||
} // namespace linux
|
||||
} // namespace nearby
|
||||
@@ -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 <atomic>
|
||||
#include <memory>
|
||||
|
||||
#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<ByteArray> Read(std::int64_t size) override;
|
||||
Exception Close() override;
|
||||
|
||||
private:
|
||||
std::atomic<int> 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<int> 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<void()> 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<BleL2capInputStream> input_stream_;
|
||||
std::unique_ptr<BleL2capOutputStream> output_stream_;
|
||||
api::ble_v2::BlePeripheral::UniqueId peripheral_id_;
|
||||
absl::AnyInvocable<void()> close_notifier_ ABSL_GUARDED_BY(mutex_);
|
||||
};
|
||||
|
||||
} // namespace linux
|
||||
} // namespace nearby
|
||||
|
||||
#endif // PLATFORM_IMPL_LINUX_BLE_L2CAP_SOCKET_H_
|
||||
@@ -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 <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#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<ByteArray> 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<ByteArray> 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
|
||||
@@ -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 <memory>
|
||||
#include <string>
|
||||
|
||||
#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
|
||||
@@ -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<void(
|
||||
api::BleSocket &socket, const std::string &service_id)>;
|
||||
|
||||
// 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<api::BleSocket> Connect(
|
||||
api::BlePeripheral &peripheral, const std::string &service_id,
|
||||
CancellationFlag *cancellation_flag) override {
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
} // namespace linux
|
||||
} // namespace nearby
|
||||
|
||||
#endif
|
||||
@@ -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 <algorithm>
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
|
||||
#include <bluetooth/bluetooth.h>
|
||||
#include <bluetooth/l2cap.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#include <sdbus-c++/IProxy.h>
|
||||
#include <sdbus-c++/Types.h>
|
||||
|
||||
#include "absl/synchronization/mutex.h"
|
||||
#include "internal/platform/implementation/ble_v2.h"
|
||||
// #include "internal/platform/implementation/linux/ble_gatt_client.h"
|
||||
// #include "internal/platform/implementation/linux/ble_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<BluezGattDiscovery>(system_bus_)),
|
||||
observers_(std::make_shared<ObserverList<api::BluetoothClassicMedium::Observer>>()),
|
||||
devices_(std::make_unique<BluetoothDevices>(
|
||||
system_bus_, adapter_.GetObjectPath(), *observers_)),
|
||||
root_object_manager_(std::make_unique<RootObjectManager>(*system_bus_, "/com/google/nearby/medium/ble/advertisement/monitor")),
|
||||
adv_monitor_manager_(
|
||||
bluez::AdvertisementMonitorManager::
|
||||
DiscoverAdvertisementMonitorManager(*system_bus_, adapter_)),
|
||||
adv_manager_(std::make_unique<bluez::LEAdvertisementManager>(*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<api::ble_v2::BleMedium::AdvertisingSession>
|
||||
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<sdbus::IProxy> proxy =
|
||||
sdbus::createProxy(*system_bus_, "org.bluez", adapter_.GetObjectPath());
|
||||
proxy->finishRegistration();
|
||||
|
||||
std::shared_ptr<AdvertisingCallback> shared_cb =
|
||||
std::make_shared<AdvertisingCallback>(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<std::string, sdbus::Variant>{})
|
||||
.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<absl::Status()> 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>(
|
||||
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<bluez::AdvertisementMonitor>(
|
||||
*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<DeviceWatcher>(
|
||||
*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<api::ble_v2::BleMedium::ScanningSession>
|
||||
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<bluez::AdvertisementMonitor>(
|
||||
*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<DeviceWatcher>(
|
||||
*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>(
|
||||
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<api::ble_v2::GattServer> BleV2Medium::StartGattServer(
|
||||
api::ble_v2::ServerGattConnectionCallback callback) {
|
||||
(void)callback;
|
||||
return nullptr;
|
||||
|
||||
return std::make_unique<GattServer>(
|
||||
*system_bus_, adapter_, devices_,std::move(callback)
|
||||
);
|
||||
}
|
||||
|
||||
std::unique_ptr<api::ble_v2::GattClient> 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<api::ble_v2::BleServerSocket> BleV2Medium::OpenServerSocket(
|
||||
const std::string &service_id) {
|
||||
LOG(INFO) << __func__ << ": Opening BLE server socket for service "
|
||||
<< service_id;
|
||||
return std::make_unique<BleV2ServerSocket>(service_id);
|
||||
}
|
||||
|
||||
std::unique_ptr<api::ble_v2::BleL2capServerSocket>
|
||||
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<linux::BleL2capServerSocket>(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<api::ble_v2::BleSocket> 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<std::string, sdbus::Variant> 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<api::ble_v2::BleSocket> 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<api::ble_v2::BleL2capSocket> 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<BleL2capSocket>(fd, peripheral_id);
|
||||
}
|
||||
|
||||
bool BleV2Medium::StartMultipleServicesScanning(
|
||||
const std::vector<Uuid> &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<api::ble_v2::BlePeripheral::UniqueId>
|
||||
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
|
||||
@@ -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 <memory>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include <sdbus-c++/IConnection.h>
|
||||
|
||||
#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<AdvertisingSession> 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<ScanningSession> StartScanning(
|
||||
const Uuid &service_uuid, api::ble_v2::TxPowerLevel tx_power_level,
|
||||
ScanningCallback callback) override;
|
||||
|
||||
std::unique_ptr<api::ble_v2::GattServer> StartGattServer(
|
||||
api::ble_v2::ServerGattConnectionCallback callback) override;
|
||||
|
||||
std::unique_ptr<api::ble_v2::GattClient> ConnectToGattServer(
|
||||
api::ble_v2::BlePeripheral::UniqueId peripheral_id,
|
||||
api::ble_v2::TxPowerLevel tx_power_level,
|
||||
api::ble_v2::ClientGattConnectionCallback callback) override;
|
||||
|
||||
std::unique_ptr<api::ble_v2::BleServerSocket> OpenServerSocket(
|
||||
const std::string &service_id) override;
|
||||
|
||||
std::unique_ptr<api::ble_v2::BleL2capServerSocket> OpenL2capServerSocket(
|
||||
const std::string &service_id) override;
|
||||
|
||||
std::unique_ptr<api::ble_v2::BleSocket> 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<api::ble_v2::BleL2capSocket> 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<Uuid> &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<api::ble_v2::BlePeripheral::UniqueId>
|
||||
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<std::string> 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<sdbus::IConnection> system_bus_;
|
||||
BluetoothAdapter adapter_;
|
||||
// Why do we have observers her
|
||||
std::shared_ptr<ObserverList<api::BluetoothClassicMedium::Observer>>
|
||||
observers_;
|
||||
std::shared_ptr<BluetoothDevices> devices_;
|
||||
std::shared_ptr<BluezGattDiscovery> gatt_discovery_;
|
||||
|
||||
std::unique_ptr<RootObjectManager> root_object_manager_;
|
||||
std::unique_ptr<bluez::AdvertisementMonitorManager> adv_monitor_manager_;
|
||||
absl::Mutex active_adv_monitors_mutex_;
|
||||
absl::flat_hash_map<
|
||||
Uuid,
|
||||
std::pair<std::unique_ptr<bluez::AdvertisementMonitor>, std::unique_ptr<DeviceWatcher>>>
|
||||
active_adv_monitors_ ABSL_GUARDED_BY(active_adv_monitors_mutex_);
|
||||
// Used by the synchronous variant of StartScanning
|
||||
std::optional<Uuid> cur_monitored_service_uuid_;
|
||||
|
||||
std::unique_ptr<bluez::LEAdvertisementManager> adv_manager_;
|
||||
|
||||
absl::Mutex cur_adv_mutex_;
|
||||
std::unique_ptr<bluez::LEAdvertisement> cur_adv_
|
||||
ABSL_GUARDED_BY(cur_adv_mutex_);
|
||||
|
||||
absl::Mutex advs_mutex_;
|
||||
std::list<std::unique_ptr<bluez::LEAdvertisement>> advs_
|
||||
ABSL_GUARDED_BY(advs_mutex_);
|
||||
};
|
||||
} // namespace linux
|
||||
} // namespace nearby
|
||||
|
||||
#endif
|
||||
@@ -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 <memory>
|
||||
|
||||
#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<api::ble_v2::BleSocket> 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<BleV2Socket> 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<BleV2Socket> 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
|
||||
@@ -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 <deque>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#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<api::ble_v2::BleSocket> Accept() override
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
Exception Close() override ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
void AddPendingSocket(std::unique_ptr<BleV2Socket> 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<std::unique_ptr<BleV2Socket>> pending_sockets_
|
||||
ABSL_GUARDED_BY(mutex_);
|
||||
};
|
||||
|
||||
} // namespace linux
|
||||
} // namespace nearby
|
||||
|
||||
#endif
|
||||
@@ -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 <cstdint>
|
||||
|
||||
#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<ByteArray> BleV2Socket::BleInputStream::Read(std::int64_t size) {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
|
||||
while (buffer_.Empty() && !closed_) {
|
||||
cond_.Wait(&mutex_);
|
||||
}
|
||||
|
||||
if (closed_ && buffer_.Empty()) {
|
||||
return ExceptionOr<ByteArray>(Exception::kIo);
|
||||
}
|
||||
|
||||
if (size < 0 || static_cast<size_t>(size) >= buffer_.size()) {
|
||||
ByteArray result = buffer_;
|
||||
buffer_ = ByteArray();
|
||||
return ExceptionOr<ByteArray>(result);
|
||||
}
|
||||
|
||||
ByteArray result(buffer_.data(), size);
|
||||
buffer_ = ByteArray(buffer_.data() + size, buffer_.size() - size);
|
||||
return ExceptionOr<ByteArray>(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<api::ble_v2::GattServer> 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<api::ble_v2::GattClient> 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
|
||||
@@ -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 <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#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<bool(const ByteArray& data)> callback) {
|
||||
output_stream_.SetWriteCallback(std::move(callback));
|
||||
}
|
||||
|
||||
// Set the GATT server and characteristics for server-side socket
|
||||
void SetGattServer(std::unique_ptr<api::ble_v2::GattServer> 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<api::ble_v2::GattClient> 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<ByteArray> 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<bool(const ByteArray& data)>;
|
||||
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<api::ble_v2::GattServer> gatt_server_ ABSL_GUARDED_BY(mutex_);
|
||||
std::unique_ptr<api::ble_v2::GattClient> 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_
|
||||
@@ -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
|
||||
@@ -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 <memory>
|
||||
#include <string>
|
||||
|
||||
#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<api::ble_v2::BlePeripheral::UniqueId, BleV2Socket*>
|
||||
device_sockets_ ABSL_GUARDED_BY(mutex_);
|
||||
};
|
||||
|
||||
} // namespace linux
|
||||
} // namespace nearby
|
||||
|
||||
#endif // PLATFORM_IMPL_LINUX_API_BLE_V2_SOCKET_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,80 +12,110 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <sdbus-c++/ProxyInterfaces.h>
|
||||
#include <sdbus-c++/Types.h>
|
||||
|
||||
|
||||
#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
|
||||
|
||||
|
||||
@@ -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 <string>
|
||||
#include <sdbus-c++/AdaptorInterfaces.h>
|
||||
#ifndef PLATFORM_IMPL_LINUX_BLUETOOTH_ADAPTER_H_
|
||||
#define PLATFORM_IMPL_LINUX_BLUETOOTH_ADAPTER_H_
|
||||
#include <sdbus-c++/IConnection.h>
|
||||
#include <sdbus-c++/ProxyInterfaces.h>
|
||||
|
||||
#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<org::bluez::Adapter1_proxy> {
|
||||
class BluezAdapter : public sdbus::ProxyInterfaces<org::bluez::Adapter1_proxy> {
|
||||
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<sdbus::IConnection> system_bus,
|
||||
const sdbus::ObjectPath &adapter_object_path)
|
||||
: system_bus_(std::move(system_bus)),
|
||||
bluez_adapter_(std::make_shared<BluezAdapter>(*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<sdbus::IConnection> GetConnection() { return system_bus_; }
|
||||
|
||||
private:
|
||||
std::shared_ptr<sdbus::IConnection> system_bus_;
|
||||
std::shared_ptr<BluezAdapter> bluez_adapter_;
|
||||
};
|
||||
} // namespace linux
|
||||
} // namespace nearby
|
||||
|
||||
#endif // PLATFORM_LINUX_IMPL_BLUETOOTH_ADAPTER_H_
|
||||
#endif // PLATFORM_IMPL_LINUX_BLUETOOTH_ADAPTER_H_
|
||||
|
||||
@@ -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 <memory>
|
||||
#include <string>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/synchronization/notification.h"
|
||||
#include "internal/platform/implementation/bluetooth_adapter.h"
|
||||
#include <sdbus-c++/sdbus-c++.h>
|
||||
|
||||
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
|
||||
@@ -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 <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
#include <sdbus-c++/AdaptorInterfaces.h>
|
||||
#include <sdbus-c++/Error.h>
|
||||
#include <sdbus-c++/IConnection.h>
|
||||
#include <sdbus-c++/IObject.h>
|
||||
#include <sdbus-c++/IProxy.h>
|
||||
#include <sdbus-c++/Types.h>
|
||||
|
||||
#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<std::string, sdbus::Variant> &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<absl::string_view> 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<Profile>(
|
||||
getProxy().getConnection(), bluez::profile_object_path(service_uuid),
|
||||
devices_);
|
||||
|
||||
try {
|
||||
std::map<std::string, sdbus::Variant> options;
|
||||
if (name.has_value()) {
|
||||
options["Name"] = std::string(*name);
|
||||
}
|
||||
options["RequireAuthorization"] = false;
|
||||
options["RequireAuthentication"] = false;
|
||||
options["Channel"] = static_cast<uint16_t>(0);
|
||||
options["PSM"] = static_cast<uint16_t>(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<sdbus::UnixFd> ProfileManager::GetServiceRecordFD(
|
||||
api::BluetoothDevice &remote_device, absl::string_view service_uuid,
|
||||
CancellationFlag *cancellation_flag) {
|
||||
std::shared_ptr<Profile> 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<CancellationFlagListener> cancel_listener;
|
||||
if (cancellation_flag != nullptr)
|
||||
cancel_listener = std::make_unique<CancellationFlagListener>(
|
||||
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<std::pair<std::shared_ptr<BluetoothDevice>, sdbus::UnixFd>>
|
||||
ProfileManager::GetServiceRecordFD(absl::string_view service_uuid,
|
||||
CancellationFlag *cancellation_flag) {
|
||||
std::shared_ptr<Profile> 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<CancellationFlagListener> cancel_listener;
|
||||
if (cancellation_flag != nullptr)
|
||||
cancel_listener = std::make_unique<CancellationFlagListener>(
|
||||
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
|
||||
@@ -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 <atomic>
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
#include <sdbus-c++/AdaptorInterfaces.h>
|
||||
#include <sdbus-c++/IConnection.h>
|
||||
#include <sdbus-c++/IObject.h>
|
||||
#include <sdbus-c++/IProxy.h>
|
||||
#include <sdbus-c++/ProxyInterfaces.h>
|
||||
#include <sdbus-c++/StandardInterfaces.h>
|
||||
#include <sdbus-c++/Types.h>
|
||||
|
||||
#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<org::bluez::Profile1_adaptor,
|
||||
sdbus::ManagedObject_adaptor> {
|
||||
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<std::string, sdbus::Variant> &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<uint16_t> version;
|
||||
std::optional<uint16_t> features;
|
||||
};
|
||||
|
||||
void Release() override;
|
||||
void NewConnection(const sdbus::ObjectPath &, const sdbus::UnixFd &,
|
||||
const std::map<std::string, sdbus::Variant> &) 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<std::string, std::vector<std::pair<sdbus::UnixFd, FDProperties>>>
|
||||
connections_ ABSL_GUARDED_BY(connections_lock_);
|
||||
|
||||
// Track pending outgoing connection attempts to avoid race with incoming
|
||||
std::set<std::string> pending_outgoing_ ABSL_GUARDED_BY(connections_lock_);
|
||||
|
||||
BluetoothDevices &devices_;
|
||||
};
|
||||
|
||||
class ProfileManager final
|
||||
: private sdbus::ProxyInterfaces<org::bluez::ProfileManager1_proxy> {
|
||||
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<absl::string_view> 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<sdbus::UnixFd> GetServiceRecordFD(
|
||||
api::BluetoothDevice &remote_device, absl::string_view service_uuid,
|
||||
CancellationFlag *cancellation_flag)
|
||||
ABSL_LOCKS_EXCLUDED(registered_service_uuids_mutex_);
|
||||
std::optional<std::pair<std::shared_ptr<BluetoothDevice>, 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<std::string, std::shared_ptr<Profile>> registered_services_
|
||||
ABSL_GUARDED_BY(registered_service_uuids_mutex_);
|
||||
};
|
||||
|
||||
} // namespace linux
|
||||
} // namespace nearby
|
||||
#endif
|
||||
@@ -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 <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#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<BluetoothSocket*>(GetRemoteSocket());
|
||||
if (remote_socket == nullptr || remote_socket->adapter_ == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return &remote_socket->adapter_->GetDevice();
|
||||
}
|
||||
|
||||
std::unique_ptr<api::BluetoothSocket> 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<BluetoothSocket>(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<void()> 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<absl::string_view> 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<BluetoothAdapter*>(&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<api::BluetoothSocket> 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<BluetoothDevice*>(device)->GetAdapter();
|
||||
auto* medium =
|
||||
down_cast<BluetoothClassicMedium*>(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<BluetoothSocket>(&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<api::BluetoothServerSocket>
|
||||
BluetoothClassicMedium::ListenForService(const std::string& service_name,
|
||||
const std::string& service_uuid) {
|
||||
auto socket = std::make_unique<BluetoothServerSocket>(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<api::BluetoothPairing> BluetoothClassicMedium::CreatePairing(
|
||||
api::BluetoothDevice& remote_device) {
|
||||
return std::make_unique<BluetoothPairing>(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
|
||||
@@ -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 <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#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<api::BluetoothSocket> 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<void()> 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<BluetoothSocket*> pending_sockets_
|
||||
ABSL_GUARDED_BY(mutex_);
|
||||
absl::AnyInvocable<void()> 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<absl::string_view> 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<api::BluetoothSocket> 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<api::BluetoothServerSocket> 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<api::BluetoothPairing> 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<std::string, BluetoothServerSocket*> sockets_
|
||||
ABSL_GUARDED_BY(mutex_);
|
||||
};
|
||||
|
||||
} // namespace linux
|
||||
} // namespace nearby
|
||||
|
||||
#endif // PLATFORM_IMPL_G3_BLUETOOTH_CLASSIC_H_
|
||||
@@ -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 <memory>
|
||||
|
||||
#include <sdbus-c++/IObject.h>
|
||||
#include <sdbus-c++/ProxyInterfaces.h>
|
||||
|
||||
#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<bluez::Device> 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<sdbus::IConnection> system_bus,
|
||||
std::shared_ptr<bluez::Device> device,
|
||||
ObserverList<api::BluetoothClassicMedium::Observer> &observers)
|
||||
: BluetoothDevice(device),
|
||||
ProxyInterfaces<sdbus::Properties_proxy>(*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<std::string, sdbus::Variant> &changedProperties,
|
||||
const std::vector<std::string> &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
|
||||
@@ -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 <atomic>
|
||||
#include <memory>
|
||||
|
||||
#include <sdbus-c++/Error.h>
|
||||
#include <sdbus-c++/IConnection.h>
|
||||
#include <sdbus-c++/IProxy.h>
|
||||
#include <sdbus-c++/ProxyInterfaces.h>
|
||||
#include <sdbus-c++/StandardInterfaces.h>
|
||||
#include <sdbus-c++/Types.h>
|
||||
|
||||
#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<bluez::Device> 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<std::map<std::string, sdbus::Variant>> 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<sdbus::PendingAsyncCall> 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<void(const sdbus::Error *)> 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<bluez::Device> device_;
|
||||
};
|
||||
|
||||
class MonitoredBluetoothDevice final
|
||||
: public BluetoothDevice,
|
||||
public sdbus::ProxyInterfaces<sdbus::Properties_proxy> {
|
||||
public:
|
||||
using sdbus::ProxyInterfaces<sdbus::Properties_proxy>::registerProxy;
|
||||
using sdbus::ProxyInterfaces<sdbus::Properties_proxy>::unregisterProxy;
|
||||
using sdbus::ProxyInterfaces<sdbus::Properties_proxy>::getObjectPath;
|
||||
|
||||
MonitoredBluetoothDevice(const MonitoredBluetoothDevice &) = delete;
|
||||
MonitoredBluetoothDevice(MonitoredBluetoothDevice &&) = delete;
|
||||
MonitoredBluetoothDevice &operator=(const MonitoredBluetoothDevice &) =
|
||||
delete;
|
||||
MonitoredBluetoothDevice &operator=(MonitoredBluetoothDevice &&) = delete;
|
||||
MonitoredBluetoothDevice(
|
||||
std::shared_ptr<sdbus::IConnection> system_bus,
|
||||
std::shared_ptr<bluez::Device> device,
|
||||
ObserverList<api::BluetoothClassicMedium::Observer> &observers);
|
||||
~MonitoredBluetoothDevice() override { unregisterProxy(); }
|
||||
|
||||
void SetDiscoveryCallback(
|
||||
std::shared_ptr<api::BluetoothClassicMedium::DiscoveryCallback> &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<std::string, sdbus::Variant> &changedProperties,
|
||||
const std::vector<std::string> &invalidatedProperties) override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<sdbus::IConnection> system_bus_;
|
||||
std::shared_ptr<api::BluetoothClassicMedium::DiscoveryCallback>
|
||||
GetDiscoveryCallback() ABSL_LOCKS_EXCLUDED(discovery_cb_mutex_) {
|
||||
discovery_cb_mutex_.ReaderLock();
|
||||
auto callback = discovery_cb_.lock();
|
||||
discovery_cb_mutex_.ReaderUnlock();
|
||||
|
||||
return callback;
|
||||
}
|
||||
|
||||
ObserverList<api::BluetoothClassicMedium::Observer> &observers_;
|
||||
absl::Mutex discovery_cb_mutex_;
|
||||
std::weak_ptr<api::BluetoothClassicMedium::DiscoveryCallback> discovery_cb_
|
||||
ABSL_GUARDED_BY(discovery_cb_mutex_);
|
||||
};
|
||||
|
||||
} // namespace linux
|
||||
} // namespace nearby
|
||||
|
||||
#endif
|
||||
@@ -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 <cstring>
|
||||
#include <memory>
|
||||
|
||||
#include <sdbus-c++/IProxy.h>
|
||||
#include <sdbus-c++/Types.h>
|
||||
|
||||
#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<AgentManager>(*system_bus_)),
|
||||
profile_manager_(nullptr) {
|
||||
auto shared =
|
||||
GetSharedBluetoothDevices(system_bus_, adapter_.GetObjectPath());
|
||||
observers_ = shared->observers;
|
||||
devices_ = shared->devices;
|
||||
profile_manager_ =
|
||||
std::make_unique<ProfileManager>(*system_bus_, *devices_);
|
||||
}
|
||||
|
||||
bool BluetoothClassicMedium::StartDiscovery(
|
||||
DiscoveryCallback discovery_callback) {
|
||||
device_watcher_ = std::make_unique<DeviceWatcher>(
|
||||
*system_bus_, adapter_.GetObjectPath(), adapter_, devices_,
|
||||
std::make_unique<DiscoveryCallback>(std::move(discovery_callback)),
|
||||
observers_);
|
||||
|
||||
std::map<std::string, sdbus::Variant> 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<api::BluetoothSocket> 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<api::BluetoothSocket>(
|
||||
new BluetoothSocket(device, fd.value()));
|
||||
}
|
||||
|
||||
std::unique_ptr<api::BluetoothServerSocket>
|
||||
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<api::BluetoothServerSocket>(
|
||||
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<api::BluetoothPairing> 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<api::BluetoothPairing>(
|
||||
new BluetoothPairing(adapter_, device));
|
||||
}
|
||||
|
||||
} // namespace linux
|
||||
} // namespace nearby
|
||||
@@ -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 <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
#include <sdbus-c++/IConnection.h>
|
||||
#include <sdbus-c++/IProxy.h>
|
||||
#include <sdbus-c++/ProxyInterfaces.h>
|
||||
#include <sdbus-c++/StandardInterfaces.h>
|
||||
#include <sdbus-c++/Types.h>
|
||||
|
||||
// #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<api::BluetoothSocket> 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<api::BluetoothServerSocket> 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<api::BluetoothPairing> 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<sdbus::IConnection> system_bus_;
|
||||
|
||||
BluetoothAdapter adapter_;
|
||||
std::shared_ptr<ObserverList<Observer>> observers_;
|
||||
std::shared_ptr<BluetoothDevices> devices_;
|
||||
std::unique_ptr<DeviceWatcher> device_watcher_;
|
||||
|
||||
// std::unique_ptr<AgentManager> agent_manager_;
|
||||
std::unique_ptr<ProfileManager> profile_manager_;
|
||||
};
|
||||
|
||||
} // namespace linux
|
||||
} // namespace nearby
|
||||
|
||||
#endif
|
||||
@@ -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<api::BluetoothSocket> 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<BluetoothSocket>(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
|
||||
@@ -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<api::BluetoothSocket> 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
|
||||
@@ -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 <sys/poll.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#include <array>
|
||||
#include <cerrno>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#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 <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#include <cerrno>
|
||||
#include <cstddef>
|
||||
#include <algorithm>
|
||||
|
||||
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<ByteArray> 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<size_t>(msg_len);
|
||||
size_t cap = static_cast<size_t>(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<size_t>(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_t>(size));
|
||||
char* data = buffer.data();
|
||||
|
||||
size_t total_read = 0;
|
||||
while (total_read < static_cast<size_t>(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_t>(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<size_t>(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<size_t>(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<size_t>(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
|
||||
@@ -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 <atomic>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
#include <sdbus-c++/Types.h>
|
||||
#include <sys/poll.h>
|
||||
#include <systemd/sd-bus.h>
|
||||
|
||||
#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<ByteArray> Read(std::int64_t size) override;
|
||||
Exception Close() override;
|
||||
|
||||
private:
|
||||
sdbus::UnixFd fd_;
|
||||
std::atomic<int> 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<int> 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<BluetoothDevice> 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<BluetoothDevice> device_;
|
||||
BluetoothOutputStream output_stream_;
|
||||
BluetoothInputStream input_stream_;
|
||||
};
|
||||
} // namespace linux
|
||||
} // namespace nearby
|
||||
#endif
|
||||
@@ -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 <algorithm>
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include <sdbus-c++/Types.h>
|
||||
|
||||
#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<std::string, std::weak_ptr<SharedBluetoothDevices>>
|
||||
g_shared_devices ABSL_GUARDED_BY(g_shared_devices_lock);
|
||||
|
||||
std::shared_ptr<SharedBluetoothDevices> GetSharedBluetoothDevices(
|
||||
std::shared_ptr<sdbus::IConnection> 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<SharedBluetoothDevices>();
|
||||
shared->observers =
|
||||
std::make_shared<ObserverList<api::BluetoothClassicMedium::Observer>>();
|
||||
shared->devices = std::make_shared<BluetoothDevices>(
|
||||
std::move(system_bus), adapter_object_path, *shared->observers);
|
||||
g_shared_devices[key] = shared;
|
||||
return shared;
|
||||
}
|
||||
|
||||
std::shared_ptr<BluetoothDevice> 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<BluetoothDevice> 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<BluetoothDevice> 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<MonitoredBluetoothDevice> 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<MonitoredBluetoothDevice>(
|
||||
system_bus_,
|
||||
std::make_shared<bluez::Device>(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<std::string, std::map<std::string, sdbus::Variant>>
|
||||
&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<std::string> &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<sdbus::ObjectPath,
|
||||
std::map<std::string, std::map<std::string, sdbus::Variant>>>
|
||||
objects;
|
||||
try {
|
||||
objects = GetManagedObjects();
|
||||
} catch (const sdbus::Error &e) {
|
||||
DBUS_LOG_METHOD_CALL_ERROR(this, "GetManagedObjects", e);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<sdbus::ObjectPath> 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<bool>();
|
||||
} 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
|
||||
@@ -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 <chrono>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <sdbus-c++/AdaptorInterfaces.h>
|
||||
#include <sdbus-c++/IConnection.h>
|
||||
#include <sdbus-c++/IProxy.h>
|
||||
#include <sdbus-c++/ProxyInterfaces.h>
|
||||
#include <sdbus-c++/StandardInterfaces.h>
|
||||
#include <sdbus-c++/Types.h>
|
||||
|
||||
#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<sdbus::IConnection> system_bus,
|
||||
sdbus::ObjectPath adapter_object_path,
|
||||
ObserverList<api::BluetoothClassicMedium::Observer> &observers)
|
||||
: system_bus_(std::move(system_bus)),
|
||||
observers_(observers),
|
||||
adapter_object_path_(std::move(adapter_object_path)) {}
|
||||
|
||||
std::shared_ptr<BluetoothDevice> get_device_by_path(const sdbus::ObjectPath &)
|
||||
ABSL_LOCKS_EXCLUDED(devices_by_path_lock_);
|
||||
std::shared_ptr<BluetoothDevice> get_device_by_address(const std::string &);
|
||||
std::shared_ptr<BluetoothDevice> get_device_by_unique_id(
|
||||
api::ble_v2::BlePeripheral::UniqueId id);
|
||||
|
||||
std::shared_ptr<MonitoredBluetoothDevice> 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<sdbus::IConnection> system_bus_;
|
||||
ObserverList<api::BluetoothClassicMedium::Observer> &observers_;
|
||||
sdbus::ObjectPath adapter_object_path_;
|
||||
|
||||
absl::Mutex devices_by_path_lock_;
|
||||
absl::flat_hash_map<std::string, std::shared_ptr<MonitoredBluetoothDevice>>
|
||||
devices_by_path_ ABSL_GUARDED_BY(devices_by_path_lock_);
|
||||
std::chrono::time_point<std::chrono::steady_clock> last_cleanup_
|
||||
ABSL_GUARDED_BY(devices_by_path_lock_);
|
||||
};
|
||||
|
||||
struct SharedBluetoothDevices {
|
||||
std::shared_ptr<BluetoothDevices> devices;
|
||||
std::shared_ptr<ObserverList<api::BluetoothClassicMedium::Observer>> observers;
|
||||
};
|
||||
|
||||
std::shared_ptr<SharedBluetoothDevices> GetSharedBluetoothDevices(
|
||||
std::shared_ptr<sdbus::IConnection> system_bus,
|
||||
const sdbus::ObjectPath& adapter_object_path);
|
||||
|
||||
class DeviceWatcher final : sdbus::ProxyInterfaces<sdbus::ObjectManager_proxy> {
|
||||
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<BluetoothDevices> devices,
|
||||
std::unique_ptr<api::BluetoothClassicMedium::DiscoveryCallback>
|
||||
discovery_callback,
|
||||
std::shared_ptr<ObserverList<api::BluetoothClassicMedium::Observer>>
|
||||
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<BluetoothDevices> devices)
|
||||
: DeviceWatcher(system_bus, adapter_object_path, adapter, std::move(devices),
|
||||
nullptr, nullptr) {}
|
||||
~DeviceWatcher() { unregisterProxy(); }
|
||||
|
||||
void onInterfacesAdded(
|
||||
const sdbus::ObjectPath &object,
|
||||
const std::map<std::string, std::map<std::string, sdbus::Variant>>
|
||||
&interfaces) override;
|
||||
void onInterfacesRemoved(const sdbus::ObjectPath &object,
|
||||
const std::vector<std::string> &interfaces) override;
|
||||
|
||||
private:
|
||||
void notifyExistingDevices();
|
||||
|
||||
sdbus::ObjectPath adapter_object_path_;
|
||||
BluetoothAdapter &adapter_;
|
||||
std::shared_ptr<BluetoothDevices> devices_;
|
||||
std::shared_ptr<api::BluetoothClassicMedium::DiscoveryCallback> discovery_cb_;
|
||||
std::shared_ptr<ObserverList<api::BluetoothClassicMedium::Observer>>
|
||||
observers_;
|
||||
};
|
||||
|
||||
} // namespace linux
|
||||
} // namespace nearby
|
||||
|
||||
#endif
|
||||
@@ -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 <utility>
|
||||
|
||||
#include <sdbus-c++/Error.h>
|
||||
#include <sdbus-c++/IConnection.h>
|
||||
#include <sdbus-c++/IProxy.h>
|
||||
|
||||
#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<BluetoothDevice> 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<absl::string_view> 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
|
||||
@@ -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 <memory>
|
||||
#include <string>
|
||||
|
||||
#include <sdbus-c++/Error.h>
|
||||
#include <sdbus-c++/IConnection.h>
|
||||
#include <sdbus-c++/IProxy.h>
|
||||
#include <systemd/sd-bus.h>
|
||||
|
||||
#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<BluetoothDevice> remote_device);
|
||||
|
||||
bool InitiatePairing(api::BluetoothPairingCallback pairing_cb) override;
|
||||
bool FinishPairing(std::optional<absl::string_view> 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<BluetoothDevice> device_;
|
||||
sdbus::ObjectPath device_object_path_;
|
||||
linux::BluetoothAdapter adapter_;
|
||||
|
||||
api::BluetoothPairingCallback pairing_cb_;
|
||||
};
|
||||
} // namespace linux
|
||||
} // namespace nearby
|
||||
|
||||
#endif
|
||||
@@ -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 <sdbus-c++/Types.h>
|
||||
|
||||
#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
|
||||
@@ -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 <sdbus-c++/ProxyInterfaces.h>
|
||||
#include <sdbus-c++/StandardInterfaces.h>
|
||||
#include <sdbus-c++/Types.h>
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "internal/platform/implementation/ble_v2.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#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<sdbus::ObjectManager_proxy> {
|
||||
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<std::string, std::map<std::string, sdbus::Variant>>
|
||||
&interfacesAndProperties) override {}
|
||||
void onInterfacesRemoved(
|
||||
const sdbus::ObjectPath &objectPath,
|
||||
const std::vector<std::string> &interfaces) override {}
|
||||
};
|
||||
|
||||
} // namespace bluez
|
||||
} // namespace linux
|
||||
} // namespace nearby
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,73 @@
|
||||
#include "internal/platform/implementation/linux/bluez_advertisement_monitor.h"
|
||||
|
||||
#include <regex>
|
||||
|
||||
#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<BluetoothDevices> 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<BluetoothDevices> 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<uint8_t> 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
|
||||
@@ -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 <sdbus-c++/AdaptorInterfaces.h>
|
||||
#include <sdbus-c++/IConnection.h>
|
||||
#include <sdbus-c++/StandardInterfaces.h>
|
||||
|
||||
#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<org::bluez::AdvertisementMonitor1_adaptor,
|
||||
sdbus::ManagedObject_adaptor> {
|
||||
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<BluetoothDevices> 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<BluetoothDevices> 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<sdbus::Struct<uint8_t, uint8_t, std::vector<uint8_t>>> Patterns()
|
||||
override {
|
||||
std::array<char, 16> service_id_data = service_uuid_.data();
|
||||
return {{0,
|
||||
0x16,
|
||||
{static_cast<uint8_t>(service_id_data[3] & 0xFF),
|
||||
static_cast<uint8_t>(service_id_data[2] & 0xFF)}
|
||||
}};
|
||||
};
|
||||
|
||||
std::shared_ptr<BluetoothDevices> devices_;
|
||||
api::ble_v2::BleMedium::ScanCallback scan_callback_;
|
||||
absl::AnyInvocable<void(absl::Status)> start_scanning_result_callback_;
|
||||
|
||||
std::string type_;
|
||||
Uuid service_uuid_;
|
||||
api::ble_v2::TxPowerLevel tx_power_level_;
|
||||
};
|
||||
} // namespace bluez
|
||||
} // namespace linux
|
||||
} // namespace nearby
|
||||
|
||||
#endif
|
||||
@@ -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 <sdbus-c++/IConnection.h>
|
||||
#include <sdbus-c++/ProxyInterfaces.h>
|
||||
|
||||
#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<AdvertisementMonitorManager>
|
||||
std::make_unique<AdvertisementMonitorManager>(
|
||||
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<AdvertisementMonitorManager>
|
||||
DiscoverAdvertisementMonitorManager(
|
||||
sdbus::IConnection &system_bus,
|
||||
const ::nearby::linux::BluetoothAdapter &adapter) {
|
||||
bluez::BluezObjectManager manager(system_bus);
|
||||
std::map<sdbus::ObjectPath,
|
||||
std::map<std::string, std::map<std::string, sdbus::Variant>>>
|
||||
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<AdvertisementMonitorManager>(system_bus, adapter);
|
||||
}
|
||||
};
|
||||
} // namespace bluez
|
||||
} // namespace linux
|
||||
} // namespace nearby
|
||||
|
||||
#endif
|
||||
@@ -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 <sdbus-c++/IConnection.h>
|
||||
#include <sdbus-c++/ProxyInterfaces.h>
|
||||
#include <sdbus-c++/Types.h>
|
||||
|
||||
#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<org::bluez::Device1_proxy> {
|
||||
public:
|
||||
Device(std::shared_ptr<sdbus::IConnection> 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<void(const sdbus::Error *)> 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<sdbus::IConnection> system_bus;
|
||||
absl::Mutex pair_callback_lock_;
|
||||
absl::AnyInvocable<void(const sdbus::Error *)> on_pair_reply_cb_
|
||||
ABSL_GUARDED_BY(pair_callback_lock_) = nullptr;
|
||||
};
|
||||
} // namespace bluez
|
||||
} // namespace linux
|
||||
} // namespace nearby
|
||||
#endif
|
||||
@@ -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 <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#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<std::string, sdbus::Variant>& changedProperties,
|
||||
const std::vector<std::string>& invalidatedProperties) {
|
||||
if (interfaceName != org::bluez::GattCharacteristic1_proxy::INTERFACE_NAME)
|
||||
return;
|
||||
|
||||
if (changedProperties.count("Value") == 1) {
|
||||
std::vector<uint8_t> 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
|
||||
@@ -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 <sdbus-c++/IConnection.h>
|
||||
#include <sdbus-c++/ProxyInterfaces.h>
|
||||
#include <sdbus-c++/StandardInterfaces.h>
|
||||
|
||||
#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<org::bluez::GattCharacteristic1_proxy,
|
||||
sdbus::Properties_proxy> {
|
||||
public:
|
||||
GattCharacteristicClient(std::shared_ptr<sdbus::IConnection> 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<std::string, sdbus::Variant>& changedProperties,
|
||||
const std::vector<std::string>& invalidatedProperties) override {}
|
||||
|
||||
std::shared_ptr<sdbus::IConnection> system_bus_;
|
||||
};
|
||||
|
||||
class SubscribedGattCharacteristicClient : public GattCharacteristicClient {
|
||||
public:
|
||||
SubscribedGattCharacteristicClient(
|
||||
std::shared_ptr<sdbus::IConnection> system_bus, sdbus::ObjectPath path,
|
||||
absl::AnyInvocable<void(absl::string_view value)> 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<std::string, sdbus::Variant>& changedProperties,
|
||||
const std::vector<std::string>& invalidatedProperties) override;
|
||||
|
||||
private:
|
||||
absl::AnyInvocable<void(absl::string_view value)> notify_callback_;
|
||||
};
|
||||
} // namespace bluez
|
||||
} // namespace linux
|
||||
} // namespace nearby
|
||||
|
||||
#endif
|
||||
@@ -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 <sdbus-c++/Error.h>
|
||||
#include <sdbus-c++/MethodResult.h>
|
||||
#include <sdbus-c++/Types.h>
|
||||
|
||||
#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<uint8_t> 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<uint8_t> 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<std::vector<uint8_t>> &&result,
|
||||
std::map<std::string, sdbus::Variant> 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<int>(offset),
|
||||
// [result = std::move(result),
|
||||
// this](absl::StatusOr<absl::string_view> data) {
|
||||
// const auto &status = data.status();
|
||||
// if (status.ok()) {
|
||||
// auto str = data.value();
|
||||
// std::vector<uint8_t> 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<uint8_t> value,
|
||||
std::map<std::string, sdbus::Variant> 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<int>(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<std::string> GattCharacteristicServer::Flags() {
|
||||
auto characteristic = characteristic_;
|
||||
std::vector<std::string> 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
|
||||
@@ -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 <atomic>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <sdbus-c++/AdaptorInterfaces.h>
|
||||
#include <sdbus-c++/IConnection.h>
|
||||
#include <sdbus-c++/MethodResult.h>
|
||||
#include <sdbus-c++/StandardInterfaces.h>
|
||||
#include <sdbus-c++/Types.h>
|
||||
|
||||
#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<org::bluez::GattCharacteristic1_adaptor,
|
||||
sdbus::Properties_adaptor,
|
||||
sdbus::ManagedObject_adaptor> {
|
||||
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<api::ble_v2::ServerGattConnectionCallback> server_cb,
|
||||
std::shared_ptr<BluetoothDevices> 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<std::vector<uint8_t>> &&result,
|
||||
std::map<std::string, sdbus::Variant> options) override
|
||||
ABSL_LOCKS_EXCLUDED(cached_value_mutex_, static_value_mutex_);
|
||||
void WriteValue(sdbus::Result<> &&result, std::vector<uint8_t> value,
|
||||
std::map<std::string, sdbus::Variant> 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<std::string> Flags() override;
|
||||
std::vector<uint8_t> Value() override
|
||||
ABSL_LOCKS_EXCLUDED(cached_value_mutex_) {
|
||||
absl::ReaderMutexLock lock(&cached_value_mutex_);
|
||||
return cached_value_;
|
||||
}
|
||||
|
||||
std::shared_ptr<BluetoothDevices> devices_;
|
||||
std::shared_ptr<api::ble_v2::ServerGattConnectionCallback> server_cb_;
|
||||
api::ble_v2::GattCharacteristic characteristic_;
|
||||
|
||||
// Set by `GattServer::UpdateCharacteristic()`
|
||||
absl::Mutex static_value_mutex_;
|
||||
std::optional<std::vector<uint8_t>> static_value_
|
||||
ABSL_GUARDED_BY(static_value_mutex_);
|
||||
|
||||
sdbus::ObjectPath service_object_path_;
|
||||
std::atomic_bool notifying_;
|
||||
absl::Mutex cached_value_mutex_;
|
||||
std::vector<uint8_t> 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
|
||||
@@ -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 <sdbus-c++/IConnection.h>
|
||||
#include <sdbus-c++/ProxyInterfaces.h>
|
||||
#include <sdbus-c++/Types.h>
|
||||
|
||||
#include "internal/platform/implementation/linux/generated/dbus/bluez/gatt_manager_client.h"
|
||||
namespace nearby {
|
||||
namespace linux {
|
||||
namespace bluez {
|
||||
class GattManager
|
||||
: public sdbus::ProxyInterfaces<org::bluez::GattManager1_proxy> {
|
||||
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
|
||||
@@ -0,0 +1,63 @@
|
||||
//
|
||||
// Created by root on 1/11/26.
|
||||
//
|
||||
|
||||
#ifndef WORKSPACE_BLUEZ_GATT_PROFILE_H
|
||||
#define WORKSPACE_BLUEZ_GATT_PROFILE_H
|
||||
|
||||
#include <sdbus-c++/IConnection.h>
|
||||
#include <sdbus-c++/ProxyInterfaces.h>
|
||||
#include <sdbus-c++/Types.h>
|
||||
|
||||
#include "generated/dbus/bluez/gatt_profile_server.h"
|
||||
#include "internal/platform/logging.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <string>
|
||||
|
||||
namespace nearby {
|
||||
namespace linux {
|
||||
namespace bluez {
|
||||
class GattProfile
|
||||
: public sdbus::AdaptorInterfaces<org::bluez::GattProfile1_adaptor, sdbus::ManagedObject_adaptor> {
|
||||
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<char>(std::tolower(c)); });
|
||||
return s;
|
||||
}
|
||||
std::vector<std::string> UUIDs() override
|
||||
{
|
||||
LOG(INFO)<< __func__ << ": UUIDs called, returned: " << ToLowerAscii(uuids_[0]);
|
||||
return uuids_;
|
||||
};
|
||||
|
||||
std::vector<std::string> uuids_;
|
||||
|
||||
};
|
||||
} // namespace bluez
|
||||
} // namespace linux
|
||||
} // namespace nearby
|
||||
|
||||
#endif //WORKSPACE_BLUEZ_GATT_PROFILE_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 <memory>
|
||||
|
||||
#include <sdbus-c++/AdaptorInterfaces.h>
|
||||
#include <sdbus-c++/IConnection.h>
|
||||
|
||||
#include "internal/platform/implementation/linux/generated/dbus/bluez/gatt_service_client.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace linux {
|
||||
class GattServiceClient final
|
||||
: public sdbus::ProxyInterfaces<org::bluez::GattService1_proxy> {
|
||||
public:
|
||||
GattServiceClient(std::shared_ptr<sdbus::IConnection> 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
|
||||
@@ -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<GattCharacteristicServer> chr =
|
||||
std::make_shared<GattCharacteristicServer>(
|
||||
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<GattCharacteristicServer> 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
|
||||
@@ -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 <sdbus-c++/AdaptorInterfaces.h>
|
||||
#include <sdbus-c++/IConnection.h>
|
||||
#include <sdbus-c++/IObject.h>
|
||||
#include <sdbus-c++/StandardInterfaces.h>
|
||||
#include <sdbus-c++/Types.h>
|
||||
|
||||
#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<org::bluez::GattService1_adaptor,
|
||||
sdbus::ManagedObject_adaptor,
|
||||
sdbus::Properties_adaptor> {
|
||||
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<api::ble_v2::ServerGattConnectionCallback> server_cb,
|
||||
std::shared_ptr<BluetoothDevices> 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<GattCharacteristicServer> 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<sdbus::ObjectPath> Includes() override { return {}; }
|
||||
|
||||
absl::Mutex characterstics_mutex_;
|
||||
absl::flat_hash_map<Uuid, std::shared_ptr<GattCharacteristicServer>>
|
||||
characteristics_ ABSL_GUARDED_BY(characterstics_mutex_);
|
||||
|
||||
std::shared_ptr<BluetoothDevices> devices_;
|
||||
std::shared_ptr<api::ble_v2::ServerGattConnectionCallback> server_cb_;
|
||||
|
||||
const std::string uuid_;
|
||||
const bool primary_;
|
||||
};
|
||||
} // namespace bluez
|
||||
} // namespace linux
|
||||
} // namespace nearby
|
||||
|
||||
#endif
|
||||
@@ -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 <vector>
|
||||
|
||||
#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<uint8_t>& bytes) {
|
||||
std::ostringstream oss;
|
||||
oss << std::hex << std::setfill('0');
|
||||
for (uint8_t b : bytes) {
|
||||
oss << std::setw(2) << static_cast<int>(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<uint8_t> 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
|
||||
@@ -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 <sdbus-c++/AdaptorInterfaces.h>
|
||||
#include <sdbus-c++/IConnection.h>
|
||||
#include <sdbus-c++/ProxyInterfaces.h>
|
||||
#include <sdbus-c++/StandardInterfaces.h>
|
||||
#include <sdbus-c++/Types.h>
|
||||
|
||||
#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<org::bluez::LEAdvertisement1_adaptor,
|
||||
sdbus::ObjectManager_adaptor> {
|
||||
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<LEAdvertisement> CreateLEAdvertisement(
|
||||
sdbus::IConnection& system_bus,
|
||||
const api::ble_v2::BleAdvertisementData& advertising_data,
|
||||
api::ble_v2::AdvertiseParameters advertising_parameters) {
|
||||
static std::atomic<size_t> adv_count = 0;
|
||||
auto object_path = bluez::ble_advertisement_path(adv_count++);
|
||||
return std::make_unique<LEAdvertisement>(
|
||||
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<std::string> ServiceUUIDs() override { return service_uuids_; }
|
||||
std::map<std::string, sdbus::Variant> ManufacturerData() override {
|
||||
return {};
|
||||
}
|
||||
std::vector<std::string> SolicitUUIDs() override { return {}; }
|
||||
std::map<std::string, sdbus::Variant> ServiceData() override {
|
||||
return service_data_;
|
||||
}
|
||||
std::map<std::string, sdbus::Variant> ScanResponseServiceData() override {
|
||||
return {};
|
||||
}
|
||||
std::vector<std::string> 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<std::string> service_uuids_;
|
||||
std::map<std::string, sdbus::Variant> service_data_;
|
||||
api::ble_v2::AdvertiseParameters advertise_set_parameters_;
|
||||
};
|
||||
|
||||
class LEAdvertisementManager final
|
||||
: public sdbus::ProxyInterfaces<org::bluez::LEAdvertisingManager1_proxy> {
|
||||
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
|
||||
@@ -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 <memory>
|
||||
#include <string>
|
||||
|
||||
#include <sdbus-c++/IConnection.h>
|
||||
#include <sdbus-c++/ProxyInterfaces.h>
|
||||
#include <sdbus-c++/Types.h>
|
||||
|
||||
#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<org::bluez::Bearer::LE1_proxy> {
|
||||
public:
|
||||
LEBearerClient(std::shared_ptr<sdbus::IConnection> 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<void(const std::string& reason,
|
||||
const std::string& message)> 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<sdbus::IConnection> system_bus_;
|
||||
absl::Mutex disconnect_callback_lock_;
|
||||
absl::AnyInvocable<void(const std::string&, const std::string&)>
|
||||
on_disconnected_cb_ ABSL_GUARDED_BY(disconnect_callback_lock_) = nullptr;
|
||||
};
|
||||
|
||||
} // namespace bluez
|
||||
} // namespace linux
|
||||
} // namespace nearby
|
||||
|
||||
#endif // PLATFORM_IMPL_LINUX_LE_BEARER_CLIENT_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<linux::Mutex*>(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_
|
||||
@@ -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 <future> // 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<bool> 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
|
||||
}
|
||||
@@ -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 <atomic>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
class CountDownLatchTests : public testing::Test {
|
||||
public:
|
||||
class TestData {
|
||||
public:
|
||||
std::unique_ptr<nearby::api::CountDownLatch>& countDownLatch;
|
||||
long volatile& count;
|
||||
};
|
||||
|
||||
class CountDownLatchTest {
|
||||
public:
|
||||
static unsigned int ThreadProcCountDown(void* lpParam) {
|
||||
TestData* testData = static_cast<TestData*>(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<TestData*>(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<nearby::api::CountDownLatch> countDownLatch =
|
||||
nearby::api::ImplementationPlatform::CreateCountDownLatch(3);
|
||||
|
||||
std::vector<std::thread> 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<nearby::api::CountDownLatch> countDownLatch =
|
||||
nearby::api::ImplementationPlatform::CreateCountDownLatch(3);
|
||||
|
||||
// Act
|
||||
nearby::ExceptionOr<bool> 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<nearby::api::CountDownLatch> countDownLatch =
|
||||
nearby::api::ImplementationPlatform::CreateCountDownLatch(3);
|
||||
|
||||
TestData testData{countDownLatch, count};
|
||||
|
||||
std::vector<std::thread> 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<bool> 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<nearby::api::CountDownLatch> 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);
|
||||
}
|
||||
@@ -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 <memory>
|
||||
|
||||
#include <sdbus-c++/IConnection.h>
|
||||
#include <sdbus-c++/IProxy.h>
|
||||
#include <sdbus-c++/sdbus-c++.h>
|
||||
|
||||
#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<LocalCredential> &Local_credentials,
|
||||
const std::vector<SharedCredential> &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<sdbus::IProxy> proxy;
|
||||
};
|
||||
} // namespace linux
|
||||
} // namespace nearby
|
||||
|
||||
#endif
|
||||
@@ -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 <openssl/evp.h>
|
||||
|
||||
// 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...
|
||||
|
||||
@@ -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 <string>
|
||||
|
||||
#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
|
||||
@@ -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 <cassert>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
|
||||
#include <sdbus-c++/IConnection.h>
|
||||
|
||||
#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<sdbus::IConnection> global_system_bus_connection
|
||||
ABSL_GUARDED_BY(global_system_bus_mutex);
|
||||
} // namespace
|
||||
|
||||
std::shared_ptr<sdbus::IConnection> getSystemBusConnection() {
|
||||
absl::MutexLock lock(&global_system_bus_mutex);
|
||||
auto bus = global_system_bus_connection.lock();
|
||||
if (bus == nullptr) {
|
||||
bus =
|
||||
std::shared_ptr<sdbus::IConnection>(sdbus::createSystemBusConnection());
|
||||
bus->enterEventLoopAsync();
|
||||
global_system_bus_connection = bus;
|
||||
}
|
||||
|
||||
return bus;
|
||||
}
|
||||
} // namespace linux
|
||||
} // namespace nearby
|
||||
@@ -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 <sdbus-c++/AdaptorInterfaces.h>
|
||||
#include <sdbus-c++/IConnection.h>
|
||||
#include <sdbus-c++/StandardInterfaces.h>
|
||||
#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<sdbus::IConnection> getSystemBusConnection();
|
||||
class RootObjectManager final
|
||||
: public sdbus::AdaptorInterfaces<sdbus::ObjectManager_adaptor,
|
||||
sdbus::Properties_adaptor> {
|
||||
public:
|
||||
explicit RootObjectManager(sdbus::IConnection &system_bus, sdbus::ObjectPath path)
|
||||
: AdaptorInterfaces(system_bus, path) {
|
||||
registerAdaptor();
|
||||
}
|
||||
~RootObjectManager() { unregisterAdaptor(); }
|
||||
};
|
||||
|
||||
} // namespace linux
|
||||
} // namespace nearby
|
||||
#endif
|
||||
@@ -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 <pwd.h>
|
||||
#include <sys/types.h>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include <sdbus-c++/IConnection.h>
|
||||
#include <sdbus-c++/IProxy.h>
|
||||
|
||||
#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<void(api::DeviceInfo::ScreenStatus)> callback) {
|
||||
absl::MutexLock l(&screen_lock_listeners_mutex_);
|
||||
screen_lock_listeners_[listener_name] = std::move(callback);
|
||||
}
|
||||
|
||||
std::optional<std::string> DeviceInfo::GetOsDeviceName() const
|
||||
{
|
||||
return "TestLinux" ;
|
||||
};
|
||||
api::DeviceInfo::DeviceType DeviceInfo::GetDeviceType() const
|
||||
{
|
||||
return api::DeviceInfo::DeviceType::kLaptop;
|
||||
};
|
||||
std::optional<FilePath> 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<FilePath> 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<FilePath> DeviceInfo::GetCommonAppDataPath() const
|
||||
{
|
||||
return GetLocalAppDataPath();
|
||||
};
|
||||
std::optional<FilePath> DeviceInfo::GetTemporaryPath() const
|
||||
{
|
||||
return FilePath("/tmp");
|
||||
}
|
||||
std::optional<FilePath> DeviceInfo::GetLogPath() const
|
||||
{
|
||||
return FilePath("/tmp/nearby/logs");
|
||||
};
|
||||
std::optional<FilePath> 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<sdbus::IConnection> system_bus)
|
||||
: system_bus_(std::move(system_bus)),
|
||||
current_user_session_(std::make_unique<CurrentUserSession>(*system_bus_)),
|
||||
login_manager_(std::make_unique<LoginManager>(*system_bus_)) {}
|
||||
|
||||
std::optional<std::string> 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<FilePath> DeviceInfo::GetDownloadPath() const {
|
||||
char *dir = getenv("XDG_DOWNLOAD_DIR");
|
||||
return FilePath(std::string(dir));
|
||||
}
|
||||
|
||||
std::optional<FilePath> 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<FilePath> 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<FilePath> 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<FilePath> 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
|
||||
|
||||
@@ -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 <functional>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <systemd/sd-bus.h>
|
||||
|
||||
#include <sdbus-c++/IConnection.h>
|
||||
#include <sdbus-c++/IProxy.h>
|
||||
#include <sdbus-c++/ProxyInterfaces.h>
|
||||
#include <sdbus-c++/Types.h>
|
||||
|
||||
#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<org::freedesktop::login1::Session_proxy> {
|
||||
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<void(api::DeviceInfo::ScreenStatus)> 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<std::string,
|
||||
std::function<void(api::DeviceInfo::ScreenStatus)>>
|
||||
screen_lock_listeners_ ABSL_GUARDED_BY(screen_lock_listeners_mutex_);
|
||||
};
|
||||
|
||||
class Hostnamed
|
||||
: public sdbus::ProxyInterfaces<org::freedesktop::hostname1_proxy> {
|
||||
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<org::freedesktop::login1::Manager_proxy> {
|
||||
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<sdbus::IConnection> system_bus);
|
||||
|
||||
// Gets device name.
|
||||
std::optional<std::string> 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<FilePath> GetDownloadPath() const override;
|
||||
std::optional<FilePath> GetLocalAppDataPath() const override;
|
||||
std::optional<FilePath> GetCommonAppDataPath() const override;
|
||||
std::optional<FilePath> GetCommonAppDataPath() const override {
|
||||
return std::nullopt;
|
||||
};
|
||||
std::optional<FilePath> GetTemporaryPath() const override;
|
||||
std::optional<FilePath> GetLogPath() const override;
|
||||
std::optional<FilePath> 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<void(api::DeviceInfo::ScreenStatus)> callback) override {return;};
|
||||
std::function<void(api::DeviceInfo::ScreenStatus)> 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<sdbus::IConnection> system_bus_;
|
||||
std::unique_ptr<CurrentUserSession> current_user_session_;
|
||||
std::unique_ptr<LoginManager> login_manager_;
|
||||
std::optional<sdbus::UnixFd> inhibit_fd_;
|
||||
};
|
||||
|
||||
} // namespace linux
|
||||
} // namespace nearby
|
||||
|
||||
#endif // PLATFORM_IMPL_LINUX_INFO_H_
|
||||
#endif // PLATFORM_IMPL_LINUX_DEVICE_INFO_H_
|
||||
|
||||
@@ -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 <functional>
|
||||
#include <string>
|
||||
|
||||
#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<void(api::DeviceInfo::ScreenStatus)> listener_1 =
|
||||
[](api::DeviceInfo::ScreenStatus) {};
|
||||
std::function<void(api::DeviceInfo::ScreenStatus)> 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<void(api::DeviceInfo::ScreenStatus)> listener_1 =
|
||||
[](api::DeviceInfo::ScreenStatus) {};
|
||||
std::function<void(api::DeviceInfo::ScreenStatus)> 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<void(api::DeviceInfo::ScreenStatus)> 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
|
||||
@@ -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 <cassert>
|
||||
|
||||
#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<ThreadPool>(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
|
||||
@@ -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 <atomic>
|
||||
#include <memory>
|
||||
|
||||
#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<linux::ThreadPool> thread_pool_ = nullptr;
|
||||
std::atomic<bool> shut_down_ = false;
|
||||
};
|
||||
|
||||
} // namespace linux
|
||||
} // namespace nearby
|
||||
|
||||
#endif // PLATFORM_IMPL_LINUX_EXECUTOR_H_
|
||||
@@ -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 <algorithm>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
#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<Executor>();
|
||||
std::string output = std::string();
|
||||
// Container to note threads that ran
|
||||
std::unique_ptr<std::vector<std::thread::id>> threadIds =
|
||||
std::make_unique<std::vector<std::thread::id>>();
|
||||
|
||||
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> executor = std::make_unique<Executor>();
|
||||
std::unique_ptr<std::string> output = std::make_unique<std::string>();
|
||||
// Container to note threads that ran
|
||||
std::unique_ptr<std::vector<std::thread::id>> threadIds =
|
||||
std::make_unique<std::vector<std::thread::id>>();
|
||||
|
||||
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<Executor>();
|
||||
std::string output = std::string();
|
||||
// Container to note threads that ran
|
||||
std::unique_ptr<std::vector<std::thread::id>> threadIds =
|
||||
std::make_unique<std::vector<std::thread::id>>();
|
||||
|
||||
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<Executor>();
|
||||
std::string output = std::string();
|
||||
// Container to note threads that ran
|
||||
std::unique_ptr<std::vector<std::thread::id>> threadIds =
|
||||
std::make_unique<std::vector<std::thread::id>>();
|
||||
|
||||
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<Executor>(2);
|
||||
|
||||
// Container to note threads that ran
|
||||
std::unique_ptr<std::vector<std::thread::id>> threadIds =
|
||||
std::make_unique<std::vector<std::thread::id>>();
|
||||
|
||||
std::shared_ptr<std::string> output = std::make_shared<std::string>();
|
||||
|
||||
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<Executor>(2);
|
||||
|
||||
// Container to note threads that ran
|
||||
std::unique_ptr<std::vector<std::thread::id>> threadIds =
|
||||
std::make_unique<std::vector<std::thread::id>>();
|
||||
|
||||
std::shared_ptr<std::string> output = std::make_shared<std::string>();
|
||||
|
||||
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<Executor>(2);
|
||||
|
||||
// Container to note threads that ran
|
||||
std::unique_ptr<std::vector<std::thread::id>> threadIds =
|
||||
std::make_unique<std::vector<std::thread::id>>();
|
||||
|
||||
std::shared_ptr<std::string> output = std::make_shared<std::string>();
|
||||
|
||||
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<Executor>(32);
|
||||
|
||||
// Container to note threads that ran
|
||||
std::vector<std::thread::id> threadIds = std::vector<std::thread::id>();
|
||||
|
||||
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
|
||||
@@ -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 <algorithm>
|
||||
#include <codecvt>
|
||||
#include <cstddef>
|
||||
#include <filesystem>
|
||||
#include <ios>
|
||||
#include <locale>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#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> 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> IOFile::CreateOutputFile(const absl::string_view path) {
|
||||
return std::unique_ptr<IOFile>(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<std::codecvt_utf8_utf16<wchar_t>> converter;
|
||||
file_.open(std::filesystem::path(converter.from_bytes(path_)),
|
||||
std::ios::binary | std::ios::out);
|
||||
}
|
||||
|
||||
ExceptionOr<ByteArray> IOFile::Read(std::int64_t size) {
|
||||
if (!file_.is_open()) {
|
||||
return ExceptionOr<ByteArray>{Exception::kIo};
|
||||
}
|
||||
|
||||
if (file_.peek() == EOF) {
|
||||
return ExceptionOr<ByteArray>{ByteArray{}};
|
||||
}
|
||||
|
||||
if (!file_.good()) {
|
||||
return ExceptionOr<ByteArray>{Exception::kIo};
|
||||
}
|
||||
|
||||
ByteArray bytes(size);
|
||||
std::unique_ptr<char[]> read_bytes{new char[size]};
|
||||
file_.read(read_bytes.get(), static_cast<ptrdiff_t>(size));
|
||||
auto num_bytes_read = file_.gcount();
|
||||
if (num_bytes_read == 0) {
|
||||
return ExceptionOr<ByteArray>{Exception::kIo};
|
||||
}
|
||||
|
||||
return ExceptionOr<ByteArray>(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
|
||||
@@ -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 <cstdint>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#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<IOFile> CreateInputFile(
|
||||
const absl::string_view file_path, size_t size);
|
||||
|
||||
static std::unique_ptr<IOFile> CreateOutputFile(const absl::string_view path);
|
||||
|
||||
ExceptionOr<ByteArray> 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_
|
||||
@@ -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 <algorithm>
|
||||
#include <cctype>
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#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<std::filesystem::path> 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
|
||||
@@ -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 <string>
|
||||
|
||||
#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_
|
||||
@@ -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 <algorithm>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <internal/platform/implementation/linux/device_info.h>
|
||||
#include <internal/platform/implementation/linux/utils.h>
|
||||
#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"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_IllegalFileNameCharacterGreaterThan\
|
||||
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_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
|
||||
@@ -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 <typename T>
|
||||
class Future : public api::Future<T> {
|
||||
public:
|
||||
// TODO(b/184975123): replace with real implementation.
|
||||
~Future() override = default;
|
||||
|
||||
// throws Exception::kInterrupted, Exception::kExecution
|
||||
// TODO(b/184975123): replace with real implementation.
|
||||
ExceptionOr<T> Get() override { return ExceptionOr<T>{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<T> Get(absl::Duration timeout) override {
|
||||
return ExceptionOr<T>{Exception::kFailed};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace linux
|
||||
} // namespace nearby
|
||||
|
||||
#endif // PLATFORM_IMPL_LINUX_FUTURE_H_
|
||||
@@ -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__",
|
||||
],
|
||||
)
|
||||
@@ -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 <sdbus-c++/sdbus-c++.h>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
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<int32_t, int32_t, std::string, std::string, std::string, std::string, int32_t, std::string, uint16_t, std::vector<std::vector<uint8_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)
|
||||
{
|
||||
std::tuple<int32_t, int32_t, std::string, std::string, std::string, std::string, int32_t, std::string, uint16_t, std::vector<std::vector<uint8_t>>> 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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user