mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-14 22:56:12 -04:00
Add robust gatt client
PiperOrigin-RevId: 539817203
This commit is contained in:
committed by
Copybara-Service
parent
5aeceb2034
commit
ca60afa007
@@ -21,6 +21,7 @@ cc_library(
|
||||
"ble_v2.cc",
|
||||
"bluetooth_classic.cc",
|
||||
"bluetooth_radio.cc",
|
||||
"robust_gatt_client.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"ble.h",
|
||||
@@ -28,15 +29,20 @@ cc_library(
|
||||
"bluetooth_classic.h",
|
||||
"bluetooth_radio.h",
|
||||
"mediums.h",
|
||||
"robust_gatt_client.h",
|
||||
],
|
||||
visibility = [
|
||||
"//fastpair:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//internal/platform:base",
|
||||
"//fastpair/common",
|
||||
"//internal/platform:comm",
|
||||
"//internal/platform:logging",
|
||||
"//internal/platform:types",
|
||||
"//internal/platform/implementation:comm",
|
||||
"@com_google_absl//absl/base:core_headers",
|
||||
"@com_google_absl//absl/functional:any_invocable",
|
||||
"@com_google_absl//absl/status",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/time",
|
||||
],
|
||||
@@ -125,3 +131,22 @@ cc_test(
|
||||
"@com_google_googletest//:gtest_main",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "robust_gatt_client_test",
|
||||
size = "small",
|
||||
srcs = [
|
||||
"robust_gatt_client_test.cc",
|
||||
],
|
||||
deps = [
|
||||
":mediums",
|
||||
"//internal/platform:comm",
|
||||
"//internal/platform:test_util",
|
||||
"//internal/platform:types",
|
||||
"//internal/platform/implementation/g3", # build_cleaner: keep
|
||||
"@com_github_protobuf_matchers//protobuf-matchers",
|
||||
"@com_google_absl//absl/status",
|
||||
"@com_google_absl//absl/time",
|
||||
"@com_google_googletest//:gtest_main",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -0,0 +1,425 @@
|
||||
// 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 "fastpair/internal/mediums/robust_gatt_client.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/base/thread_annotations.h"
|
||||
#include "absl/functional/any_invocable.h"
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/time/clock.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "internal/platform/single_thread_executor.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace fastpair {
|
||||
namespace {} // namespace
|
||||
|
||||
RobustGattClient::~RobustGattClient() {
|
||||
Stop();
|
||||
executor_.Shutdown();
|
||||
}
|
||||
|
||||
void RobustGattClient::Stop() {
|
||||
NEARBY_LOGS(INFO) << "Stopping gatt client";
|
||||
stopped_ = true;
|
||||
executor_.Execute("cleanup", [this]() ABSL_EXCLUSIVE_LOCKS_REQUIRED(
|
||||
executor_) { Cleanup(); });
|
||||
}
|
||||
|
||||
void RobustGattClient::Connect() {
|
||||
executor_.Execute("connect-gatt",
|
||||
[this]() ABSL_EXCLUSIVE_LOCKS_REQUIRED(executor_) {
|
||||
NEARBY_LOGS(INFO) << "Connecting to Gatt server";
|
||||
status_ = TryConnect();
|
||||
if (!status_.ok()) {
|
||||
NEARBY_LOGS(INFO) << status_;
|
||||
NotifyClient(status_);
|
||||
return;
|
||||
}
|
||||
NEARBY_LOGS(INFO) << "Discovering services";
|
||||
status_ = TryDiscoverServices();
|
||||
if (!status_.ok()) {
|
||||
NEARBY_LOGS(INFO) << status_;
|
||||
NotifyClient(status_);
|
||||
return;
|
||||
}
|
||||
NEARBY_LOGS(INFO) << "Gatt connection ready";
|
||||
NotifyClient(absl::OkStatus());
|
||||
});
|
||||
}
|
||||
|
||||
absl::Status RobustGattClient::TryConnect() {
|
||||
ExpBackOff back_off(params_);
|
||||
absl::Time start_time = SystemClock().ElapsedRealtime();
|
||||
while (!stopped_ && SystemClock().ElapsedRealtime() - start_time <
|
||||
params_.connect_timeout) {
|
||||
gatt_client_ = medium_.ConnectToGattServer(
|
||||
peripheral_, params_.tx_power_level, {.disconnected_cb = [this]() {
|
||||
if (stopped_) return;
|
||||
NEARBY_LOGS(INFO) << "Gatt server disconnected. Reconnecting...";
|
||||
Connect();
|
||||
}});
|
||||
if (gatt_client_ != nullptr && gatt_client_->IsValid()) {
|
||||
return absl::OkStatus();
|
||||
}
|
||||
SystemClock().Sleep(back_off.NextBackOff());
|
||||
}
|
||||
return absl::DeadlineExceededError("gatt connection time-out");
|
||||
}
|
||||
|
||||
absl::Status RobustGattClient::TryDiscoverServices() {
|
||||
ExpBackOff back_off(params_);
|
||||
absl::Time start_time = SystemClock().ElapsedRealtime();
|
||||
while (!stopped_ && SystemClock().ElapsedRealtime() - start_time <
|
||||
params_.discovery_timeout) {
|
||||
if (DiscoverServices(params_.service_uuid,
|
||||
GetPrimaryCharacteristicList()) ||
|
||||
DiscoverServices(params_.service_uuid,
|
||||
GetFallbackCharacteristicList())) {
|
||||
return absl::OkStatus();
|
||||
}
|
||||
SystemClock().Sleep(back_off.NextBackOff());
|
||||
}
|
||||
return absl::DeadlineExceededError("gatt discovery time-out");
|
||||
}
|
||||
|
||||
bool RobustGattClient::DiscoverServices(
|
||||
const Uuid& service_uuid, const std::vector<Uuid>& characteristic_uuids) {
|
||||
if (service_uuid.IsEmpty() || characteristic_uuids.empty()) {
|
||||
return false;
|
||||
}
|
||||
return gatt_client_->DiscoverServiceAndCharacteristics(service_uuid,
|
||||
characteristic_uuids);
|
||||
}
|
||||
|
||||
std::optional<RobustGattClient::GattCharacteristic>
|
||||
RobustGattClient::GetCharacteristic(const Uuid& service_id,
|
||||
const UuidPair& characteristic_uuid_pair) {
|
||||
if (stopped_) return std::nullopt;
|
||||
std::optional<GattCharacteristic> characteristic =
|
||||
gatt_client_->GetCharacteristic(service_id,
|
||||
characteristic_uuid_pair.primary_uuid);
|
||||
if (characteristic.has_value()) {
|
||||
return characteristic;
|
||||
}
|
||||
if (characteristic_uuid_pair.fallback_uuid.IsEmpty() || stopped_) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return gatt_client_->GetCharacteristic(
|
||||
service_id, characteristic_uuid_pair.fallback_uuid);
|
||||
}
|
||||
|
||||
const RobustGattClient::GattCharacteristic* RobustGattClient::GetCharacteristic(
|
||||
int uuid_pair_index) {
|
||||
auto it = characteristics_.find(uuid_pair_index);
|
||||
if (it != characteristics_.end()) {
|
||||
return &it->second;
|
||||
}
|
||||
const UuidPair& uuid_pair = params_.characteristic_uuids[uuid_pair_index];
|
||||
std::optional<GattCharacteristic> characteristic =
|
||||
GetCharacteristic(params_.service_uuid, uuid_pair);
|
||||
if (!characteristic.has_value()) {
|
||||
NEARBY_LOGS(WARNING) << absl::StrFormat(
|
||||
"Characteristic (%s, %s) not found on service %s",
|
||||
std::string(uuid_pair.primary_uuid),
|
||||
std::string(uuid_pair.fallback_uuid),
|
||||
std::string(params_.service_uuid));
|
||||
}
|
||||
characteristics_[uuid_pair_index] = *characteristic;
|
||||
return &characteristics_[uuid_pair_index];
|
||||
}
|
||||
|
||||
std::vector<Uuid> RobustGattClient::GetPrimaryCharacteristicList() {
|
||||
std::vector<Uuid> result;
|
||||
result.reserve(params_.characteristic_uuids.size());
|
||||
for (auto& uuid_pair : params_.characteristic_uuids) {
|
||||
result.push_back(uuid_pair.primary_uuid);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<Uuid> RobustGattClient::GetFallbackCharacteristicList() {
|
||||
bool has_fallbacks = false;
|
||||
std::vector<Uuid> result;
|
||||
result.reserve(params_.characteristic_uuids.size());
|
||||
for (auto& uuid_pair : params_.characteristic_uuids) {
|
||||
if (uuid_pair.fallback_uuid.IsEmpty()) {
|
||||
// Some characteristics may have only one, primary UUID.
|
||||
result.push_back(uuid_pair.primary_uuid);
|
||||
} else {
|
||||
has_fallbacks = true;
|
||||
result.push_back(uuid_pair.fallback_uuid);
|
||||
}
|
||||
}
|
||||
if (!has_fallbacks) result.clear();
|
||||
return result;
|
||||
}
|
||||
|
||||
void RobustGattClient::WriteCharacteristic(
|
||||
int uuid_pair_index, absl::string_view value,
|
||||
api::ble_v2::GattClient::WriteType write_type, WriteCallback callback) {
|
||||
CHECK_LT(uuid_pair_index, params_.characteristic_uuids.size());
|
||||
Write({.uuid_pair_index = uuid_pair_index,
|
||||
.value = std::string(value),
|
||||
.write_type = write_type,
|
||||
.callback = std::move(callback),
|
||||
.time_left = params_.gatt_operation_timeout,
|
||||
.back_off = ExpBackOff(params_)});
|
||||
}
|
||||
|
||||
void RobustGattClient::CallRemoteFunction(int uuid_pair_index,
|
||||
absl::string_view request,
|
||||
NotifyCallback response) {
|
||||
CHECK_LT(uuid_pair_index, params_.characteristic_uuids.size());
|
||||
Subscribe(uuid_pair_index, std::move(response), /*call_once=*/true);
|
||||
WriteCharacteristic(uuid_pair_index, request,
|
||||
api::ble_v2::GattClient::WriteType::kWithResponse,
|
||||
[this, uuid_pair_index](absl::Status result) {
|
||||
if (!result.ok()) {
|
||||
NotifySubscriber(uuid_pair_index, result);
|
||||
} else {
|
||||
StartNotifyTimer(uuid_pair_index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void RobustGattClient::StartNotifyTimer(int uuid_pair_index) {
|
||||
if (stopped_) return;
|
||||
MutexLock lock(&mutex_);
|
||||
auto it = notify_callbacks_.find(uuid_pair_index);
|
||||
if (it == notify_callbacks_.end()) return;
|
||||
it->second.timer = std::make_unique<TimerImpl>();
|
||||
it->second.timer->Start(
|
||||
absl::ToInt64Milliseconds(params_.gatt_operation_timeout), 0,
|
||||
[this, uuid_pair_index]() {
|
||||
NotifySubscriber(uuid_pair_index,
|
||||
absl::DeadlineExceededError("gatt operation timeout"));
|
||||
});
|
||||
}
|
||||
|
||||
void RobustGattClient::Write(WriteRequest request) {
|
||||
executor_.Execute(
|
||||
"write-gatt",
|
||||
[this, request = std::move(request)]()
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(executor_) mutable {
|
||||
absl::Time start_time = SystemClock::ElapsedRealtime();
|
||||
if (stopped_) return;
|
||||
if (!status_.ok()) {
|
||||
NEARBY_LOGS(WARNING)
|
||||
<< "Cannot write due to connection error " << status_;
|
||||
std::move(request.callback)(status_);
|
||||
return;
|
||||
}
|
||||
SystemClock().Sleep(request.back_off.NextBackOff());
|
||||
const GattCharacteristic* characteristic =
|
||||
GetCharacteristic(request.uuid_pair_index);
|
||||
bool result = false;
|
||||
if (characteristic != nullptr) {
|
||||
result = gatt_client_->WriteCharacteristic(
|
||||
*characteristic, request.value, request.write_type);
|
||||
}
|
||||
if (stopped_) return;
|
||||
if (result) {
|
||||
std::move(request.callback)(absl::OkStatus());
|
||||
} else {
|
||||
request.time_left -= SystemClock::ElapsedRealtime() - start_time;
|
||||
if (request.time_left > absl::ZeroDuration()) {
|
||||
Write(std::move(request));
|
||||
} else {
|
||||
std::move(request.callback)(
|
||||
absl::UnavailableError("gatt write failed"));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void RobustGattClient::Subscribe(int uuid_pair_index, NotifyCallback callback,
|
||||
bool call_once) {
|
||||
CHECK_LT(uuid_pair_index, params_.characteristic_uuids.size());
|
||||
MutexLock lock(&mutex_);
|
||||
notify_callbacks_[uuid_pair_index] = NotifyCallbackInfo{
|
||||
.callback = std::move(callback),
|
||||
.call_once = call_once,
|
||||
};
|
||||
NEARBY_LOGS(INFO) << "Subscribe to characteristic no: " << uuid_pair_index;
|
||||
Subscribe(SubscribeRequest{
|
||||
.uuid_pair_index = uuid_pair_index,
|
||||
.time_left = params_.gatt_operation_timeout,
|
||||
.back_off = ExpBackOff(params_),
|
||||
});
|
||||
}
|
||||
|
||||
void RobustGattClient::Subscribe(SubscribeRequest request) {
|
||||
executor_.Execute(
|
||||
"subscribe",
|
||||
[this, request = std::move(request)]()
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(executor_) mutable {
|
||||
if (stopped_) return;
|
||||
if (!HasSubsriberCallback(request.uuid_pair_index)) return;
|
||||
if (!status_.ok()) {
|
||||
NotifySubscriber(request.uuid_pair_index, status_);
|
||||
return;
|
||||
}
|
||||
absl::Time start_time = SystemClock::ElapsedRealtime();
|
||||
SystemClock().Sleep(request.back_off.NextBackOff());
|
||||
const GattCharacteristic* characteristic =
|
||||
GetCharacteristic(request.uuid_pair_index);
|
||||
bool result = false;
|
||||
if (characteristic != nullptr) {
|
||||
result = gatt_client_->SetCharacteristicSubscription(
|
||||
*characteristic, true,
|
||||
[this, uuid_pair_index =
|
||||
request.uuid_pair_index](absl::string_view value) {
|
||||
NotifySubscriber(uuid_pair_index, value);
|
||||
});
|
||||
}
|
||||
if (stopped_) return;
|
||||
if (!result) {
|
||||
request.time_left -= SystemClock::ElapsedRealtime() - start_time;
|
||||
if (request.time_left > absl::ZeroDuration()) {
|
||||
Subscribe(std::move(request));
|
||||
} else {
|
||||
NotifySubscriber(
|
||||
request.uuid_pair_index,
|
||||
absl::UnavailableError("gatt subscription failed"));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void RobustGattClient::Unsubscribe(int uuid_pair_index) {
|
||||
CHECK_LT(uuid_pair_index, params_.characteristic_uuids.size());
|
||||
MutexLock lock(&mutex_);
|
||||
int removed = notify_callbacks_.erase(uuid_pair_index);
|
||||
if (removed == 0) {
|
||||
NEARBY_LOGS(VERBOSE) << "Not subscribed for characteristic no: "
|
||||
<< uuid_pair_index;
|
||||
}
|
||||
UnsubscribeInternal(uuid_pair_index);
|
||||
}
|
||||
|
||||
void RobustGattClient::UnsubscribeInternal(int uuid_pair_index) {
|
||||
executor_.Execute(
|
||||
"unsubscribe",
|
||||
[this, uuid_pair_index]() ABSL_EXCLUSIVE_LOCKS_REQUIRED(executor_) {
|
||||
if (stopped_) return;
|
||||
if (!status_.ok()) {
|
||||
return;
|
||||
}
|
||||
NEARBY_LOGS(INFO) << "Unsubscribe from characteristic no: "
|
||||
<< uuid_pair_index;
|
||||
const GattCharacteristic* characteristic =
|
||||
GetCharacteristic(uuid_pair_index);
|
||||
if (characteristic != nullptr) {
|
||||
gatt_client_->SetCharacteristicSubscription(*characteristic, false,
|
||||
[](absl::string_view) {});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool RobustGattClient::HasSubsriberCallback(int uuid_pair_index) {
|
||||
MutexLock lock(&mutex_);
|
||||
return notify_callbacks_.find(uuid_pair_index) != notify_callbacks_.end();
|
||||
}
|
||||
|
||||
void RobustGattClient::NotifySubscriber(
|
||||
int uuid_pair_index, absl::StatusOr<absl::string_view> value) {
|
||||
if (stopped_) return;
|
||||
MutexLock lock(&mutex_);
|
||||
auto it = notify_callbacks_.find(uuid_pair_index);
|
||||
if (it != notify_callbacks_.end()) {
|
||||
it->second.callback(value);
|
||||
if (it->second.timer) {
|
||||
it->second.timer->Stop();
|
||||
// NotifySubscriber could be called from the timer. We can't destroy the
|
||||
// timer directly from the timer callback.
|
||||
DestroyOnExecutor(std::move(it->second.timer), &executor_);
|
||||
}
|
||||
if (it->second.call_once) {
|
||||
notify_callbacks_.erase(it);
|
||||
UnsubscribeInternal(uuid_pair_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RobustGattClient::ReadCharacteristic(int uuid_pair_index,
|
||||
ReadCallback callback) {
|
||||
CHECK_LT(uuid_pair_index, params_.characteristic_uuids.size());
|
||||
Read(ReadRequest{
|
||||
.uuid_pair_index = uuid_pair_index,
|
||||
.callback = std::move(callback),
|
||||
.time_left = params_.gatt_operation_timeout,
|
||||
.back_off = ExpBackOff(params_),
|
||||
});
|
||||
}
|
||||
void RobustGattClient::Read(ReadRequest request) {
|
||||
executor_.Execute(
|
||||
"read-gatt",
|
||||
[this, request = std::move(request)]()
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(executor_) mutable {
|
||||
absl::Time start_time = SystemClock::ElapsedRealtime();
|
||||
if (stopped_) return;
|
||||
if (!status_.ok()) {
|
||||
std::move(request.callback)(status_);
|
||||
return;
|
||||
}
|
||||
SystemClock().Sleep(request.back_off.NextBackOff());
|
||||
const GattCharacteristic* characteristic =
|
||||
GetCharacteristic(request.uuid_pair_index);
|
||||
absl::optional<std::string> value;
|
||||
if (characteristic != nullptr) {
|
||||
value = gatt_client_->ReadCharacteristic(*characteristic);
|
||||
}
|
||||
|
||||
if (stopped_) return;
|
||||
if (value.has_value()) {
|
||||
std::move(request.callback)(value.value());
|
||||
} else {
|
||||
request.time_left -= SystemClock::ElapsedRealtime() - start_time;
|
||||
if (request.time_left > absl::ZeroDuration()) {
|
||||
Read(std::move(request));
|
||||
} else {
|
||||
std::move(request.callback)(
|
||||
absl::UnavailableError("gatt read failed"));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void RobustGattClient::Cleanup() {
|
||||
if (gatt_client_ != nullptr && gatt_client_->IsValid()) {
|
||||
gatt_client_->Disconnect();
|
||||
}
|
||||
gatt_client_.reset();
|
||||
characteristics_.clear();
|
||||
MutexLock lock(&mutex_);
|
||||
notify_callbacks_.clear();
|
||||
}
|
||||
|
||||
void RobustGattClient::NotifyClient(absl::Status status) {
|
||||
if (stopped_) return;
|
||||
if (connection_status_callback_ != nullptr)
|
||||
connection_status_callback_(status);
|
||||
}
|
||||
|
||||
} // namespace fastpair
|
||||
} // namespace nearby
|
||||
@@ -0,0 +1,272 @@
|
||||
// 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 THIRD_PARTY_NEARBY_FASTPAIR_INTERNAL_ROBUST_GATT_CLIENT_H_
|
||||
#define THIRD_PARTY_NEARBY_FASTPAIR_INTERNAL_ROBUST_GATT_CLIENT_H_
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/base/thread_annotations.h"
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "internal/platform/ble_v2.h"
|
||||
#include "internal/platform/implementation/ble_v2.h"
|
||||
#include "internal/platform/single_thread_executor.h"
|
||||
namespace nearby {
|
||||
namespace fastpair {
|
||||
|
||||
// Gatt client on top of nearby::GattClient with higher level API and built-in
|
||||
// retry mechanism. All methods are not blocking unless stated otherwise.
|
||||
//
|
||||
// Callbacks and timeouts.
|
||||
// The blocking platform calls are running on a dedicated thread. The callbacks
|
||||
// are called when the platform calls have completed. The
|
||||
// timeouts define for how long we will retry the platform calls before giving
|
||||
// up.
|
||||
// Example 1:
|
||||
// `connect_timeout` is 10 seconds, `BleV2Medium::ConnectToGattServer()` fails
|
||||
// after 15 seconds. In this case, we will not retry connecting, the client
|
||||
// ConnectionStatusCallback will be called after 15 seconds.
|
||||
// Example 2:
|
||||
// `connect_timeout` is 10 seconds, `BleV2Medium::ConnectToGattServer()` fails
|
||||
// after 6 seconds each time. In this case, we will retry once, the client
|
||||
// ConnectionStatusCallback will be called after 12 seconds (6 + 6).
|
||||
// Example 3:
|
||||
// `BleV2Medium::ConnectToGattServer()` never returns. The callback will not be
|
||||
// called.
|
||||
class RobustGattClient {
|
||||
public:
|
||||
using WriteCallback = absl::AnyInvocable<void(absl::Status result) &&>;
|
||||
using ReadCallback =
|
||||
absl::AnyInvocable<void(absl::StatusOr<absl::string_view> value) &&>;
|
||||
using NotifyCallback =
|
||||
absl::AnyInvocable<void(absl::StatusOr<absl::string_view> value)>;
|
||||
using ConnectionStatusCallback = absl::AnyInvocable<void(absl::Status)>;
|
||||
// Defines a characteristic on the server.
|
||||
struct UuidPair {
|
||||
// Preferred UUID, for example Fast Pair V1.
|
||||
Uuid primary_uuid;
|
||||
// Optional fallback UUID if the primary is not present on the server, for
|
||||
// example Fast Pair V0.
|
||||
Uuid fallback_uuid;
|
||||
};
|
||||
struct ConnectionParams {
|
||||
api::ble_v2::TxPowerLevel tx_power_level =
|
||||
api::ble_v2::TxPowerLevel::kUnknown;
|
||||
Uuid service_uuid;
|
||||
std::vector<UuidPair> characteristic_uuids;
|
||||
// Timeout for retrying connection attempts.
|
||||
absl::Duration connect_timeout = absl::Seconds(10);
|
||||
// Timeout for retrying service discovery.
|
||||
absl::Duration discovery_timeout = absl::Seconds(10);
|
||||
// Timeout for retrying read/write/subscribe operations.
|
||||
// Note, this timeout does not include the time needed to connect to the
|
||||
// gatt server and discover services.
|
||||
absl::Duration gatt_operation_timeout = absl::Seconds(15);
|
||||
// Exponential back off parameters. They describe how long we will wait
|
||||
// before retrying an operation.
|
||||
absl::Duration initial_back_off_step = absl::Milliseconds(100);
|
||||
absl::Duration max_back_off = absl::Seconds(3);
|
||||
float back_off_multiplier = 1.5;
|
||||
};
|
||||
|
||||
// Creates the GATT client, connects to the server, and discovers
|
||||
// characteristics. The connection is established in the background. If the
|
||||
// connection is interrupted, we will try to reconnect. The caller does not
|
||||
// need to wait for connection before using the client. Instead, the caller
|
||||
// should create an instance of `RobustGattClient` and immediately start
|
||||
// calling other methods such as `CallRemoteFunction()`.
|
||||
RobustGattClient(BleV2Medium& medium, BleV2Peripheral peripheral,
|
||||
const ConnectionParams& params,
|
||||
ConnectionStatusCallback callback = nullptr)
|
||||
: medium_(medium),
|
||||
peripheral_(peripheral),
|
||||
params_(params),
|
||||
connection_status_callback_(std::move(callback)) {
|
||||
DCHECK_GT(params.initial_back_off_step, absl::ZeroDuration());
|
||||
DCHECK_GE(params.back_off_multiplier, 1.0);
|
||||
DCHECK_GE(params.max_back_off, params.initial_back_off_step);
|
||||
|
||||
Connect();
|
||||
}
|
||||
|
||||
// The destructor will block if there are any ongoing platform BLE blocking
|
||||
// calls.
|
||||
~RobustGattClient();
|
||||
|
||||
// Writes to the remote characteristic.
|
||||
//
|
||||
// `uuid_pair_index` is the index to `ConnectionParams::characteristic_uuids`.
|
||||
// `callback` is called asynchronously with the result of the write call. If
|
||||
// write fails with a status other than `absl::StatusCode::kUnavailable`, then
|
||||
// it is a permanent failure. All future calls will likely fail too.
|
||||
void WriteCharacteristic(int uuid_pair_index, absl::string_view value,
|
||||
api::ble_v2::GattClient::WriteType write_type,
|
||||
WriteCallback callback);
|
||||
|
||||
// Performs write-and-response exchange.
|
||||
//
|
||||
// The call:
|
||||
// * subscribes to the characteristic notifications,
|
||||
// * writes to the remote characteristic,
|
||||
// * waits for the response via a gatt notify call,
|
||||
// * unsubsribes from the notification.
|
||||
//
|
||||
// `uuid_pair_index` is the index to `ConnectionParams::characteristic_uuids`.
|
||||
// `response` is called asynchronously with the remote server response. If
|
||||
// write fails with a status other than `absl::StatusCode::kUnavailable` or
|
||||
// `absl::StatusCode::kDeadlineExceeded`, then it is a permanent failure. All
|
||||
// future calls will likely fail too.
|
||||
// The timeouts accumulate. If the gatt client is still connecting to the
|
||||
// remote service, the call with wait for connection, discovery,
|
||||
// characteristic subscription, write operation and gatt notification. Each of
|
||||
// them have their own timeouts. Example: if connection takes 2 seconds,
|
||||
// discovery takes 5 seconds, subscribing to the characteristic takes 3,
|
||||
// writing to the characteristic takes 6 seconds but the provider never sends
|
||||
// a response, then the call will time out no sooner than 2s + 5s + 3s + 6s +
|
||||
// gatt_operation_timeout.
|
||||
void CallRemoteFunction(int uuid_pair_index, absl::string_view request,
|
||||
NotifyCallback response);
|
||||
|
||||
// Reads remote characteristic.
|
||||
//
|
||||
// `uuid_pair_index` is the index to `ConnectionParams::characteristic_uuids`.
|
||||
void ReadCharacteristic(int uuid_pair_index, ReadCallback callback);
|
||||
|
||||
// Subscribes for remote characteristic updates.
|
||||
//
|
||||
// `uuid_pair_index` is the index to `ConnectionParams::characteristic_uuids`.
|
||||
// If `call_once` is true, then the callback will be automatically
|
||||
// unsubscribed after being called.
|
||||
//
|
||||
// Do not call `Subscribe()` or `Unsubscribe()` from the `callback`. It will
|
||||
// lock up.
|
||||
// Do not use `Subscribe()` and `CallRemoteFunction()` calls for the same
|
||||
// characteristic at the same time.
|
||||
void Subscribe(int uuid_pair_index, NotifyCallback callback,
|
||||
bool call_once = false);
|
||||
|
||||
// Unsubscribes from remote characteristic updates.
|
||||
//
|
||||
// `uuid_pair_index` is the index to `ConnectionParams::characteristic_uuids`.
|
||||
void Unsubscribe(int uuid_pair_index);
|
||||
|
||||
// Disables the gatt client and disconnects from the gatt server if
|
||||
// connected. None of the callbacks will be triggered after `Stop()`, but if
|
||||
// a callback is currently running, it may continue running after `Stop()` has
|
||||
// returned.
|
||||
void Stop();
|
||||
|
||||
private:
|
||||
class ExpBackOff {
|
||||
public:
|
||||
explicit ExpBackOff(const ConnectionParams& params)
|
||||
: back_off_step_(params.initial_back_off_step),
|
||||
multiplier_(params.back_off_multiplier),
|
||||
max_back_off_(params.max_back_off) {}
|
||||
absl::Duration NextBackOff() {
|
||||
absl::Duration result = back_off_;
|
||||
back_off_ += std::min(back_off_ + back_off_step_, max_back_off_);
|
||||
back_off_step_ *= multiplier_;
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
absl::Duration back_off_step_;
|
||||
float multiplier_;
|
||||
absl::Duration max_back_off_;
|
||||
absl::Duration back_off_ = absl::ZeroDuration();
|
||||
};
|
||||
struct WriteRequest {
|
||||
int uuid_pair_index;
|
||||
std::string value;
|
||||
api::ble_v2::GattClient::WriteType write_type;
|
||||
WriteCallback callback;
|
||||
absl::Duration time_left;
|
||||
ExpBackOff back_off;
|
||||
};
|
||||
struct SubscribeRequest {
|
||||
int uuid_pair_index;
|
||||
absl::Duration time_left;
|
||||
ExpBackOff back_off;
|
||||
};
|
||||
struct ReadRequest {
|
||||
int uuid_pair_index;
|
||||
ReadCallback callback;
|
||||
absl::Duration time_left;
|
||||
ExpBackOff back_off;
|
||||
};
|
||||
|
||||
using GattCharacteristic = api::ble_v2::GattCharacteristic;
|
||||
void Connect();
|
||||
absl::Status TryConnect() ABSL_EXCLUSIVE_LOCKS_REQUIRED(executor_);
|
||||
absl::Status TryDiscoverServices() ABSL_EXCLUSIVE_LOCKS_REQUIRED(executor_);
|
||||
std::vector<Uuid> GetPrimaryCharacteristicList();
|
||||
std::vector<Uuid> GetFallbackCharacteristicList();
|
||||
bool DiscoverServices(const Uuid& service_uuid,
|
||||
const std::vector<Uuid>& characteristic_uuids)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(executor_);
|
||||
std::optional<GattCharacteristic> GetCharacteristic(
|
||||
const Uuid& service_id, const UuidPair& characteristic_uuid_pair)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(executor_);
|
||||
// May return nullptr.
|
||||
const GattCharacteristic* GetCharacteristic(int uuid_pair_index)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(executor_);
|
||||
void NotifySubscriber(int uuid_pair_index,
|
||||
absl::StatusOr<absl::string_view> value);
|
||||
void Cleanup() ABSL_EXCLUSIVE_LOCKS_REQUIRED(executor_);
|
||||
void Write(WriteRequest request);
|
||||
void Read(ReadRequest request);
|
||||
void Subscribe(SubscribeRequest request);
|
||||
void UnsubscribeInternal(int uuid_pair_index);
|
||||
bool HasSubsriberCallback(int uuid_pair_index);
|
||||
void NotifyClient(absl::Status status);
|
||||
void StartNotifyTimer(int uuid_pair_index);
|
||||
|
||||
// A thread for running blocking tasks.
|
||||
SingleThreadExecutor executor_;
|
||||
Mutex mutex_;
|
||||
BleV2Medium& medium_;
|
||||
BleV2Peripheral peripheral_;
|
||||
ConnectionParams params_;
|
||||
ConnectionStatusCallback connection_status_callback_;
|
||||
std::unique_ptr<GattClient> gatt_client_ ABSL_GUARDED_BY(executor_);
|
||||
// Mapping from `uuid_pair_index` to subscription callbacks.
|
||||
// The entries are lazily initialized.
|
||||
absl::flat_hash_map<int, GattCharacteristic> characteristics_
|
||||
ABSL_GUARDED_BY(executor_);
|
||||
// Mapping from `uuid_pair_index` to subscription callbacks.
|
||||
struct NotifyCallbackInfo {
|
||||
NotifyCallback callback;
|
||||
// If `call_once` is true, then the callback will be called only once, and
|
||||
// then automatically unregistered.
|
||||
bool call_once;
|
||||
std::unique_ptr<TimerImpl> timer;
|
||||
};
|
||||
absl::flat_hash_map<int, NotifyCallbackInfo> notify_callbacks_
|
||||
ABSL_GUARDED_BY(mutex_);
|
||||
std::atomic_bool stopped_ = false;
|
||||
absl::Status status_ = absl::OkStatus();
|
||||
};
|
||||
|
||||
} // namespace fastpair
|
||||
} // namespace nearby
|
||||
|
||||
#endif // THIRD_PARTY_NEARBY_FASTPAIR_INTERNAL_ROBUST_GATT_CLIENT_H_
|
||||
@@ -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 "fastpair/internal/mediums/robust_gatt_client.h"
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "protobuf-matchers/protocol-buffer-matchers.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/time/clock.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "internal/platform/ble_v2.h"
|
||||
#include "internal/platform/bluetooth_adapter.h"
|
||||
#include "internal/platform/count_down_latch.h"
|
||||
#include "internal/platform/medium_environment.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace fastpair {
|
||||
namespace {
|
||||
|
||||
using ::testing::status::StatusIs;
|
||||
using Property = nearby::api::ble_v2::GattCharacteristic::Property;
|
||||
using Permission = nearby::api::ble_v2::GattCharacteristic::Permission;
|
||||
using GattCharacteristic = nearby::api::ble_v2::GattCharacteristic;
|
||||
|
||||
// Short timeout for operation that we expect to timeout.
|
||||
constexpr absl::Duration kFailureTimeout = absl::Milliseconds(100);
|
||||
constexpr absl::string_view kModelId = "123456";
|
||||
constexpr Uuid kFastPairServiceUuid(0x0000FE2C00001000, 0x800000805F9B34FB);
|
||||
constexpr Uuid kKeyBasedCharacteristicUuidV1(0x0000123400001000,
|
||||
0x800000805F9B34FB);
|
||||
constexpr Uuid kKeyBasedCharacteristicUuidV2(0xFE2C123483664814,
|
||||
0x8EB001DE32100BEA);
|
||||
constexpr Uuid kPasskeyCharacteristicUuidV1(0x0000123500001000,
|
||||
0x800000805F9B34FB);
|
||||
constexpr Uuid kPasskeyCharacteristicUuidV2(0xFE2C123583664814,
|
||||
0x8EB001DE32100BEA);
|
||||
constexpr Uuid kAccountKeyCharacteristicUuidV1(0x0000123600001000,
|
||||
0x800000805F9B34FB);
|
||||
constexpr Uuid kAccountKeyCharacteristicUuidV2(0xFE2C123683664814,
|
||||
0x8EB001DE32100BEA);
|
||||
|
||||
constexpr Uuid kModelIdCharacteristics(0xFE2C123383664814, 0x8EB001DE32100BEA);
|
||||
class MediumEnvironmentStarter {
|
||||
public:
|
||||
MediumEnvironmentStarter() { MediumEnvironment::Instance().Start({}); }
|
||||
~MediumEnvironmentStarter() { MediumEnvironment::Instance().Stop(); }
|
||||
};
|
||||
|
||||
struct CharacteristicData {
|
||||
// Write result returned to the gatt client.
|
||||
absl::Status write_result =
|
||||
absl::PermissionDeniedError("can't write characteristic");
|
||||
std::string written_data;
|
||||
absl::StatusOr<std::string> read_result =
|
||||
absl::PermissionDeniedError("can't read characteristic");
|
||||
std::optional<std::string> notify_response;
|
||||
};
|
||||
|
||||
class RobustGattClientTest : public testing::Test {
|
||||
protected:
|
||||
void SetUp() override { StartGattServer(); }
|
||||
|
||||
void StartGattServer() {
|
||||
gatt_server_ =
|
||||
provider_ble_.StartGattServer(/*ServerGattConnectionCallback=*/{
|
||||
.on_characteristic_read_cb =
|
||||
[&](const api::ble_v2::BlePeripheral& remote_device,
|
||||
const api::ble_v2::GattCharacteristic& characteristic,
|
||||
int offset,
|
||||
BleV2Medium::ServerGattConnectionCallback::ReadValueCallback
|
||||
callback) {
|
||||
auto it = characteristics_.find(characteristic);
|
||||
if (it == characteristics_.end()) {
|
||||
callback(absl::NotFoundError("characteristic not found"));
|
||||
return;
|
||||
}
|
||||
callback(it->second.read_result);
|
||||
},
|
||||
.on_characteristic_write_cb =
|
||||
[&](const api::ble_v2::BlePeripheral& remote_device,
|
||||
const api::ble_v2::GattCharacteristic& characteristic,
|
||||
int offset, absl::string_view data,
|
||||
BleV2Medium::ServerGattConnectionCallback::
|
||||
WriteValueCallback callback) {
|
||||
auto it = characteristics_.find(characteristic);
|
||||
if (it == characteristics_.end()) {
|
||||
callback(absl::NotFoundError("characteristic not found"));
|
||||
return;
|
||||
}
|
||||
it->second.written_data = data;
|
||||
if (it->second.write_result.code() ==
|
||||
absl::StatusCode::kDeadlineExceeded) {
|
||||
absl::SleepFor(kFailureTimeout);
|
||||
}
|
||||
callback(it->second.write_result);
|
||||
if (it->second.notify_response.has_value()) {
|
||||
auto ignored = gatt_server_->NotifyCharacteristicChanged(
|
||||
characteristic, false,
|
||||
ByteArray(*it->second.notify_response));
|
||||
}
|
||||
},
|
||||
});
|
||||
provider_address_ = *gatt_server_->GetBlePeripheral().GetAddress();
|
||||
}
|
||||
|
||||
void InsertCorrectV2GattCharacteristics() {
|
||||
key_based_characteristic_ = gatt_server_->CreateCharacteristic(
|
||||
kFastPairServiceUuid, kKeyBasedCharacteristicUuidV2, permissions_,
|
||||
properties_);
|
||||
characteristics_[*key_based_characteristic_].write_result =
|
||||
absl::OkStatus();
|
||||
|
||||
passkey_characteristic_ = gatt_server_->CreateCharacteristic(
|
||||
kFastPairServiceUuid, kPasskeyCharacteristicUuidV2, permissions_,
|
||||
properties_);
|
||||
characteristics_[*passkey_characteristic_].write_result = absl::OkStatus();
|
||||
|
||||
accountkey_characteristic_ = gatt_server_->CreateCharacteristic(
|
||||
kFastPairServiceUuid, kAccountKeyCharacteristicUuidV2, permissions_,
|
||||
properties_);
|
||||
characteristics_[*accountkey_characteristic_].write_result =
|
||||
absl::OkStatus();
|
||||
model_id_characteristic_ = gatt_server_->CreateCharacteristic(
|
||||
kFastPairServiceUuid, kModelIdCharacteristics, Permission::kRead,
|
||||
Property::kRead);
|
||||
characteristics_[*model_id_characteristic_].read_result = kModelId;
|
||||
}
|
||||
|
||||
void InsertCorrectV1GattCharacteristics() {
|
||||
key_based_characteristic_ = gatt_server_->CreateCharacteristic(
|
||||
kFastPairServiceUuid, kKeyBasedCharacteristicUuidV1, permissions_,
|
||||
properties_);
|
||||
characteristics_[*key_based_characteristic_].write_result =
|
||||
absl::OkStatus();
|
||||
|
||||
passkey_characteristic_ = gatt_server_->CreateCharacteristic(
|
||||
kFastPairServiceUuid, kPasskeyCharacteristicUuidV1, permissions_,
|
||||
properties_);
|
||||
characteristics_[*passkey_characteristic_].write_result = absl::OkStatus();
|
||||
|
||||
accountkey_characteristic_ = gatt_server_->CreateCharacteristic(
|
||||
kFastPairServiceUuid, kAccountKeyCharacteristicUuidV1, permissions_,
|
||||
properties_);
|
||||
characteristics_[*accountkey_characteristic_].write_result =
|
||||
absl::OkStatus();
|
||||
}
|
||||
|
||||
MediumEnvironmentStarter env_;
|
||||
BluetoothAdapter provider_adapter_;
|
||||
BleV2Medium provider_ble_{provider_adapter_};
|
||||
BluetoothAdapter seeker_adapter_;
|
||||
BleV2Medium seeker_ble_{seeker_adapter_};
|
||||
std::unique_ptr<GattServer> gatt_server_;
|
||||
std::string provider_address_;
|
||||
absl::flat_hash_map<GattCharacteristic, CharacteristicData> characteristics_;
|
||||
Property properties_ = Property::kWrite | Property::kNotify;
|
||||
Permission permissions_ = Permission::kWrite;
|
||||
std::optional<GattCharacteristic> key_based_characteristic_;
|
||||
std::optional<GattCharacteristic> passkey_characteristic_;
|
||||
std::optional<GattCharacteristic> accountkey_characteristic_;
|
||||
std::optional<GattCharacteristic> model_id_characteristic_;
|
||||
};
|
||||
|
||||
TEST_F(RobustGattClientTest, Constructor) {
|
||||
BleV2Peripheral provider = seeker_ble_.GetRemotePeripheral(provider_address_);
|
||||
RobustGattClient gatt_client(seeker_ble_, provider, {});
|
||||
}
|
||||
|
||||
TEST_F(RobustGattClientTest, ConnectToProviderWithoutServiceFails) {
|
||||
CountDownLatch latch(1);
|
||||
BleV2Peripheral provider = seeker_ble_.GetRemotePeripheral(provider_address_);
|
||||
RobustGattClient::ConnectionParams params;
|
||||
params.tx_power_level = api::ble_v2::TxPowerLevel::kMedium;
|
||||
params.service_uuid = kFastPairServiceUuid;
|
||||
params.characteristic_uuids.push_back(
|
||||
{kKeyBasedCharacteristicUuidV2, kKeyBasedCharacteristicUuidV1});
|
||||
params.connect_timeout = absl::Seconds(1);
|
||||
params.discovery_timeout = absl::Seconds(1);
|
||||
|
||||
RobustGattClient gatt_client(seeker_ble_, provider, params);
|
||||
gatt_client.WriteCharacteristic(
|
||||
0, "hello", api::ble_v2::GattClient::WriteType::kWithResponse,
|
||||
[&](absl::Status status) {
|
||||
EXPECT_THAT(status, StatusIs(absl::StatusCode::kDeadlineExceeded));
|
||||
latch.CountDown();
|
||||
});
|
||||
EXPECT_TRUE(latch.Await().Ok());
|
||||
}
|
||||
|
||||
TEST_F(RobustGattClientTest, DiscoveryRetryWorks) {
|
||||
CountDownLatch latch(1);
|
||||
BleV2Peripheral provider = seeker_ble_.GetRemotePeripheral(provider_address_);
|
||||
RobustGattClient::ConnectionParams params;
|
||||
params.tx_power_level = api::ble_v2::TxPowerLevel::kMedium;
|
||||
params.service_uuid = kFastPairServiceUuid;
|
||||
params.characteristic_uuids.push_back(
|
||||
{kKeyBasedCharacteristicUuidV2, kKeyBasedCharacteristicUuidV1});
|
||||
|
||||
RobustGattClient gatt_client(seeker_ble_, provider, params);
|
||||
gatt_client.WriteCharacteristic(
|
||||
0, "hello", api::ble_v2::GattClient::WriteType::kWithResponse,
|
||||
[&](absl::Status status) {
|
||||
EXPECT_OK(status);
|
||||
latch.CountDown();
|
||||
});
|
||||
// The Gatt client is re-trying to discover the characteristics.
|
||||
InsertCorrectV2GattCharacteristics();
|
||||
|
||||
EXPECT_TRUE(latch.Await().Ok());
|
||||
}
|
||||
|
||||
TEST_F(RobustGattClientTest, SuccessfulWriteToPrimaryUuid) {
|
||||
constexpr absl::string_view kData = "hello";
|
||||
CountDownLatch latch(1);
|
||||
BleV2Peripheral provider = seeker_ble_.GetRemotePeripheral(provider_address_);
|
||||
InsertCorrectV2GattCharacteristics();
|
||||
RobustGattClient::ConnectionParams params;
|
||||
params.tx_power_level = api::ble_v2::TxPowerLevel::kMedium;
|
||||
params.service_uuid = kFastPairServiceUuid;
|
||||
params.characteristic_uuids.push_back(
|
||||
{kKeyBasedCharacteristicUuidV2, kKeyBasedCharacteristicUuidV1});
|
||||
|
||||
RobustGattClient gatt_client(seeker_ble_, provider, params);
|
||||
gatt_client.WriteCharacteristic(
|
||||
0, kData, api::ble_v2::GattClient::WriteType::kWithResponse,
|
||||
[&](absl::Status status) {
|
||||
EXPECT_OK(status);
|
||||
latch.CountDown();
|
||||
});
|
||||
|
||||
EXPECT_TRUE(latch.Await().Ok());
|
||||
EXPECT_EQ(characteristics_[*key_based_characteristic_].written_data, kData);
|
||||
}
|
||||
|
||||
TEST_F(RobustGattClientTest, SuccessfulWriteToFallbackUuid) {
|
||||
constexpr absl::string_view kData = "hello";
|
||||
CountDownLatch latch(1);
|
||||
BleV2Peripheral provider = seeker_ble_.GetRemotePeripheral(provider_address_);
|
||||
InsertCorrectV1GattCharacteristics();
|
||||
RobustGattClient::ConnectionParams params;
|
||||
params.tx_power_level = api::ble_v2::TxPowerLevel::kMedium;
|
||||
params.service_uuid = kFastPairServiceUuid;
|
||||
params.characteristic_uuids.push_back(
|
||||
{kKeyBasedCharacteristicUuidV2, kKeyBasedCharacteristicUuidV1});
|
||||
|
||||
RobustGattClient gatt_client(seeker_ble_, provider, params);
|
||||
gatt_client.WriteCharacteristic(
|
||||
0, kData, api::ble_v2::GattClient::WriteType::kWithResponse,
|
||||
[&](absl::Status status) {
|
||||
EXPECT_OK(status);
|
||||
latch.CountDown();
|
||||
});
|
||||
|
||||
EXPECT_TRUE(latch.Await().Ok());
|
||||
EXPECT_EQ(characteristics_[*key_based_characteristic_].written_data, kData);
|
||||
}
|
||||
|
||||
TEST_F(RobustGattClientTest, RejectedWrite) {
|
||||
constexpr absl::string_view kData = "hello";
|
||||
CountDownLatch latch(1);
|
||||
BleV2Peripheral provider = seeker_ble_.GetRemotePeripheral(provider_address_);
|
||||
InsertCorrectV2GattCharacteristics();
|
||||
characteristics_[*key_based_characteristic_].write_result =
|
||||
absl::UnauthenticatedError("write rejected");
|
||||
RobustGattClient::ConnectionParams params;
|
||||
params.tx_power_level = api::ble_v2::TxPowerLevel::kMedium;
|
||||
params.service_uuid = kFastPairServiceUuid;
|
||||
params.characteristic_uuids.push_back(
|
||||
{kKeyBasedCharacteristicUuidV2, kKeyBasedCharacteristicUuidV1});
|
||||
params.gatt_operation_timeout = kFailureTimeout;
|
||||
|
||||
RobustGattClient gatt_client(seeker_ble_, provider, params);
|
||||
gatt_client.WriteCharacteristic(
|
||||
0, kData, api::ble_v2::GattClient::WriteType::kWithResponse,
|
||||
[&](absl::Status status) {
|
||||
EXPECT_THAT(status, StatusIs(absl::StatusCode::kUnavailable));
|
||||
latch.CountDown();
|
||||
});
|
||||
|
||||
EXPECT_TRUE(latch.Await().Ok());
|
||||
}
|
||||
|
||||
TEST_F(RobustGattClientTest, SuccessfulCallRemoteFunctionToPrimaryUuid) {
|
||||
constexpr absl::string_view kRequest = "request";
|
||||
constexpr absl::string_view kResponse = "response";
|
||||
CountDownLatch latch(1);
|
||||
BleV2Peripheral provider = seeker_ble_.GetRemotePeripheral(provider_address_);
|
||||
InsertCorrectV2GattCharacteristics();
|
||||
characteristics_[*key_based_characteristic_].notify_response = kResponse;
|
||||
RobustGattClient::ConnectionParams params;
|
||||
params.tx_power_level = api::ble_v2::TxPowerLevel::kMedium;
|
||||
params.service_uuid = kFastPairServiceUuid;
|
||||
params.characteristic_uuids.push_back(
|
||||
{kKeyBasedCharacteristicUuidV2, kKeyBasedCharacteristicUuidV1});
|
||||
|
||||
RobustGattClient gatt_client(seeker_ble_, provider, params);
|
||||
gatt_client.CallRemoteFunction(
|
||||
0, kRequest, [&](absl::StatusOr<absl::string_view> response) {
|
||||
EXPECT_OK(response);
|
||||
EXPECT_EQ(*response, kResponse);
|
||||
latch.CountDown();
|
||||
});
|
||||
|
||||
EXPECT_TRUE(latch.Await().Ok());
|
||||
EXPECT_EQ(characteristics_[*key_based_characteristic_].written_data,
|
||||
kRequest);
|
||||
}
|
||||
|
||||
TEST_F(RobustGattClientTest, SuccessfulCallRemoteFunctionToFallbackUuid) {
|
||||
constexpr absl::string_view kRequest = "request";
|
||||
constexpr absl::string_view kResponse = "response";
|
||||
CountDownLatch latch(1);
|
||||
BleV2Peripheral provider = seeker_ble_.GetRemotePeripheral(provider_address_);
|
||||
InsertCorrectV1GattCharacteristics();
|
||||
characteristics_[*key_based_characteristic_].notify_response = kResponse;
|
||||
RobustGattClient::ConnectionParams params;
|
||||
params.tx_power_level = api::ble_v2::TxPowerLevel::kMedium;
|
||||
params.service_uuid = kFastPairServiceUuid;
|
||||
params.characteristic_uuids.push_back(
|
||||
{kKeyBasedCharacteristicUuidV2, kKeyBasedCharacteristicUuidV1});
|
||||
|
||||
RobustGattClient gatt_client(seeker_ble_, provider, params);
|
||||
gatt_client.CallRemoteFunction(
|
||||
0, kRequest, [&](absl::StatusOr<absl::string_view> response) {
|
||||
EXPECT_OK(response);
|
||||
EXPECT_EQ(*response, kResponse);
|
||||
latch.CountDown();
|
||||
});
|
||||
|
||||
EXPECT_TRUE(latch.Await().Ok());
|
||||
EXPECT_EQ(characteristics_[*key_based_characteristic_].written_data,
|
||||
kRequest);
|
||||
}
|
||||
|
||||
TEST_F(RobustGattClientTest, CallRemoteFunctionRejectedWrite) {
|
||||
constexpr absl::string_view kRequest = "request";
|
||||
CountDownLatch latch(1);
|
||||
BleV2Peripheral provider = seeker_ble_.GetRemotePeripheral(provider_address_);
|
||||
InsertCorrectV2GattCharacteristics();
|
||||
characteristics_[*key_based_characteristic_].write_result =
|
||||
absl::InternalError("write rejected");
|
||||
RobustGattClient::ConnectionParams params;
|
||||
params.tx_power_level = api::ble_v2::TxPowerLevel::kMedium;
|
||||
params.service_uuid = kFastPairServiceUuid;
|
||||
params.characteristic_uuids.push_back(
|
||||
{kKeyBasedCharacteristicUuidV2, kKeyBasedCharacteristicUuidV1});
|
||||
params.gatt_operation_timeout = kFailureTimeout;
|
||||
|
||||
RobustGattClient gatt_client(seeker_ble_, provider, params);
|
||||
gatt_client.CallRemoteFunction(
|
||||
0, kRequest, [&](absl::StatusOr<absl::string_view> response) {
|
||||
EXPECT_THAT(response.status(),
|
||||
StatusIs(absl::StatusCode::kUnavailable));
|
||||
latch.CountDown();
|
||||
});
|
||||
|
||||
EXPECT_TRUE(latch.Await().Ok());
|
||||
}
|
||||
|
||||
TEST_F(RobustGattClientTest, CallRemoteFunctionNoResponseTimesOut) {
|
||||
constexpr absl::string_view kRequest = "request";
|
||||
CountDownLatch latch(1);
|
||||
BleV2Peripheral provider = seeker_ble_.GetRemotePeripheral(provider_address_);
|
||||
InsertCorrectV2GattCharacteristics();
|
||||
RobustGattClient::ConnectionParams params;
|
||||
params.tx_power_level = api::ble_v2::TxPowerLevel::kMedium;
|
||||
params.service_uuid = kFastPairServiceUuid;
|
||||
params.characteristic_uuids.push_back(
|
||||
{kKeyBasedCharacteristicUuidV2, kKeyBasedCharacteristicUuidV1});
|
||||
params.gatt_operation_timeout = kFailureTimeout;
|
||||
|
||||
RobustGattClient gatt_client(seeker_ble_, provider, params);
|
||||
gatt_client.CallRemoteFunction(
|
||||
0, kRequest, [&](absl::StatusOr<absl::string_view> response) {
|
||||
EXPECT_THAT(response.status(),
|
||||
StatusIs(absl::StatusCode::kDeadlineExceeded));
|
||||
latch.CountDown();
|
||||
});
|
||||
|
||||
EXPECT_TRUE(latch.Await().Ok());
|
||||
}
|
||||
|
||||
TEST_F(RobustGattClientTest, WriteTimeout) {
|
||||
constexpr absl::string_view kData = "hello";
|
||||
CountDownLatch latch(1);
|
||||
BleV2Peripheral provider = seeker_ble_.GetRemotePeripheral(provider_address_);
|
||||
InsertCorrectV2GattCharacteristics();
|
||||
characteristics_[*key_based_characteristic_].write_result =
|
||||
absl::DeadlineExceededError("write time out");
|
||||
RobustGattClient::ConnectionParams params;
|
||||
params.tx_power_level = api::ble_v2::TxPowerLevel::kMedium;
|
||||
params.service_uuid = kFastPairServiceUuid;
|
||||
params.characteristic_uuids.push_back(
|
||||
{kKeyBasedCharacteristicUuidV2, kKeyBasedCharacteristicUuidV1});
|
||||
params.gatt_operation_timeout = kFailureTimeout;
|
||||
|
||||
RobustGattClient gatt_client(seeker_ble_, provider, params);
|
||||
gatt_client.WriteCharacteristic(
|
||||
0, kData, api::ble_v2::GattClient::WriteType::kWithResponse,
|
||||
[&](absl::Status status) {
|
||||
EXPECT_THAT(status, StatusIs(absl::StatusCode::kUnavailable));
|
||||
latch.CountDown();
|
||||
});
|
||||
|
||||
EXPECT_TRUE(latch.Await().Ok());
|
||||
}
|
||||
|
||||
TEST_F(RobustGattClientTest, ReconnectWorks) {
|
||||
constexpr absl::string_view kData = "hello";
|
||||
CountDownLatch latch(1);
|
||||
BleV2Peripheral provider = seeker_ble_.GetRemotePeripheral(provider_address_);
|
||||
InsertCorrectV2GattCharacteristics();
|
||||
RobustGattClient::ConnectionParams params;
|
||||
params.tx_power_level = api::ble_v2::TxPowerLevel::kMedium;
|
||||
params.service_uuid = kFastPairServiceUuid;
|
||||
params.characteristic_uuids.push_back(
|
||||
{kKeyBasedCharacteristicUuidV2, kKeyBasedCharacteristicUuidV1});
|
||||
|
||||
RobustGattClient gatt_client(seeker_ble_, provider, params);
|
||||
gatt_client.WriteCharacteristic(
|
||||
0, "first", api::ble_v2::GattClient::WriteType::kWithResponse,
|
||||
[&](absl::Status status) {
|
||||
NEARBY_LOGS(INFO) << "First write completed with: " << status;
|
||||
EXPECT_OK(status);
|
||||
latch.CountDown();
|
||||
});
|
||||
EXPECT_TRUE(latch.Await().Ok());
|
||||
CountDownLatch write_after_reconnect(1);
|
||||
gatt_client.WriteCharacteristic(
|
||||
0, kData, api::ble_v2::GattClient::WriteType::kWithResponse,
|
||||
[&](absl::Status status) {
|
||||
NEARBY_LOGS(INFO) << "Write after reconnect completed with: " << status;
|
||||
EXPECT_OK(status);
|
||||
write_after_reconnect.CountDown();
|
||||
});
|
||||
gatt_server_->Stop();
|
||||
gatt_server_.reset();
|
||||
StartGattServer();
|
||||
InsertCorrectV2GattCharacteristics();
|
||||
EXPECT_TRUE(write_after_reconnect.Await().Ok());
|
||||
EXPECT_EQ(characteristics_[*key_based_characteristic_].written_data, kData);
|
||||
}
|
||||
|
||||
TEST_F(RobustGattClientTest, SuccessfulSubscribeToPrimaryUuid) {
|
||||
constexpr absl::string_view kData = "hello";
|
||||
constexpr int kKeyBasedCharacteristicIndex = 0;
|
||||
CountDownLatch write_latch(1);
|
||||
CountDownLatch notify_latch(1);
|
||||
absl::StatusOr<std::string> notify_data;
|
||||
BleV2Peripheral provider = seeker_ble_.GetRemotePeripheral(provider_address_);
|
||||
InsertCorrectV2GattCharacteristics();
|
||||
RobustGattClient::ConnectionParams params;
|
||||
params.tx_power_level = api::ble_v2::TxPowerLevel::kMedium;
|
||||
params.service_uuid = kFastPairServiceUuid;
|
||||
params.characteristic_uuids.push_back(
|
||||
{kKeyBasedCharacteristicUuidV2, kKeyBasedCharacteristicUuidV1});
|
||||
|
||||
RobustGattClient gatt_client(seeker_ble_, provider, params);
|
||||
gatt_client.Subscribe(kKeyBasedCharacteristicIndex,
|
||||
[&](absl::StatusOr<absl::string_view> data) {
|
||||
notify_data = data;
|
||||
notify_latch.CountDown();
|
||||
});
|
||||
gatt_client.WriteCharacteristic(
|
||||
0, "", api::ble_v2::GattClient::WriteType::kWithResponse,
|
||||
[&](absl::Status status) { write_latch.CountDown(); });
|
||||
EXPECT_TRUE(write_latch.Await().Ok());
|
||||
EXPECT_OK(gatt_server_->NotifyCharacteristicChanged(
|
||||
*key_based_characteristic_,
|
||||
/*confirm=*/true, ByteArray(std::string(kData))));
|
||||
|
||||
EXPECT_TRUE(notify_latch.Await().Ok());
|
||||
EXPECT_OK(notify_data);
|
||||
EXPECT_EQ(*notify_data, kData);
|
||||
}
|
||||
|
||||
TEST_F(RobustGattClientTest, SuccessfulSubscribeToFallbackUuid) {
|
||||
constexpr absl::string_view kData = "hello";
|
||||
constexpr int kKeyBasedCharacteristicIndex = 0;
|
||||
CountDownLatch write_latch(1);
|
||||
CountDownLatch notify_latch(1);
|
||||
absl::StatusOr<std::string> notify_data;
|
||||
BleV2Peripheral provider = seeker_ble_.GetRemotePeripheral(provider_address_);
|
||||
InsertCorrectV1GattCharacteristics();
|
||||
RobustGattClient::ConnectionParams params;
|
||||
params.tx_power_level = api::ble_v2::TxPowerLevel::kMedium;
|
||||
params.service_uuid = kFastPairServiceUuid;
|
||||
params.characteristic_uuids.push_back(
|
||||
{kKeyBasedCharacteristicUuidV2, kKeyBasedCharacteristicUuidV1});
|
||||
|
||||
RobustGattClient gatt_client(seeker_ble_, provider, params);
|
||||
gatt_client.Subscribe(kKeyBasedCharacteristicIndex,
|
||||
[&](absl::StatusOr<absl::string_view> data) {
|
||||
notify_data = data;
|
||||
notify_latch.CountDown();
|
||||
});
|
||||
gatt_client.WriteCharacteristic(
|
||||
0, "", api::ble_v2::GattClient::WriteType::kWithResponse,
|
||||
[&](absl::Status status) { write_latch.CountDown(); });
|
||||
EXPECT_TRUE(write_latch.Await().Ok());
|
||||
EXPECT_OK(gatt_server_->NotifyCharacteristicChanged(
|
||||
*key_based_characteristic_,
|
||||
/*confirm=*/true, ByteArray(std::string(kData))));
|
||||
|
||||
EXPECT_TRUE(notify_latch.Await().Ok());
|
||||
EXPECT_OK(notify_data);
|
||||
EXPECT_EQ(*notify_data, kData);
|
||||
}
|
||||
|
||||
TEST_F(RobustGattClientTest, NoNotifyAfterUnsubscribe) {
|
||||
constexpr absl::string_view kData = "hello";
|
||||
constexpr int kKeyBasedCharacteristicIndex = 0;
|
||||
CountDownLatch write_latch(1);
|
||||
CountDownLatch notify_latch(1);
|
||||
BleV2Peripheral provider = seeker_ble_.GetRemotePeripheral(provider_address_);
|
||||
InsertCorrectV2GattCharacteristics();
|
||||
RobustGattClient::ConnectionParams params;
|
||||
params.tx_power_level = api::ble_v2::TxPowerLevel::kMedium;
|
||||
params.service_uuid = kFastPairServiceUuid;
|
||||
params.characteristic_uuids.push_back(
|
||||
{kKeyBasedCharacteristicUuidV2, kKeyBasedCharacteristicUuidV1});
|
||||
RobustGattClient gatt_client(seeker_ble_, provider, params);
|
||||
gatt_client.Subscribe(kKeyBasedCharacteristicIndex,
|
||||
[&](absl::StatusOr<absl::string_view> data) {
|
||||
NEARBY_LOGS(INFO)
|
||||
<< "Notified " << data.status() << ", " << *data;
|
||||
notify_latch.CountDown();
|
||||
});
|
||||
gatt_client.WriteCharacteristic(
|
||||
0, "", api::ble_v2::GattClient::WriteType::kWithResponse,
|
||||
[&](absl::Status status) { write_latch.CountDown(); });
|
||||
EXPECT_TRUE(write_latch.Await().Ok());
|
||||
|
||||
gatt_client.Unsubscribe(kKeyBasedCharacteristicIndex);
|
||||
// Depending on the timing, the characteristic may still be subscribed on the
|
||||
// server. `NotifyCharacteristicChanged()` may be successful or may fail.
|
||||
// Neither is an error.
|
||||
auto ignored = gatt_server_->NotifyCharacteristicChanged(
|
||||
*key_based_characteristic_,
|
||||
/*confirm=*/true, ByteArray(std::string(kData)));
|
||||
|
||||
EXPECT_FALSE(notify_latch.Await(kFailureTimeout).result());
|
||||
}
|
||||
|
||||
TEST_F(RobustGattClientTest, SuccessfulRead) {
|
||||
CountDownLatch latch(1);
|
||||
BleV2Peripheral provider = seeker_ble_.GetRemotePeripheral(provider_address_);
|
||||
InsertCorrectV2GattCharacteristics();
|
||||
RobustGattClient::ConnectionParams params;
|
||||
params.tx_power_level = api::ble_v2::TxPowerLevel::kMedium;
|
||||
params.service_uuid = kFastPairServiceUuid;
|
||||
params.characteristic_uuids.push_back(
|
||||
{kKeyBasedCharacteristicUuidV2, kKeyBasedCharacteristicUuidV1});
|
||||
params.characteristic_uuids.push_back({kModelIdCharacteristics, Uuid()});
|
||||
constexpr int kModelIdIndex = 1;
|
||||
|
||||
RobustGattClient gatt_client(seeker_ble_, provider, params);
|
||||
gatt_client.ReadCharacteristic(kModelIdIndex,
|
||||
[&](absl::StatusOr<absl::string_view> value) {
|
||||
EXPECT_OK(value);
|
||||
EXPECT_EQ(*value, kModelId);
|
||||
latch.CountDown();
|
||||
});
|
||||
|
||||
EXPECT_TRUE(latch.Await().Ok());
|
||||
}
|
||||
|
||||
TEST_F(RobustGattClientTest, ReadFromWrongCharacteristicFails) {
|
||||
CountDownLatch latch(1);
|
||||
BleV2Peripheral provider = seeker_ble_.GetRemotePeripheral(provider_address_);
|
||||
InsertCorrectV2GattCharacteristics();
|
||||
RobustGattClient::ConnectionParams params;
|
||||
params.tx_power_level = api::ble_v2::TxPowerLevel::kMedium;
|
||||
params.service_uuid = kFastPairServiceUuid;
|
||||
params.characteristic_uuids.push_back(
|
||||
{kKeyBasedCharacteristicUuidV2, kKeyBasedCharacteristicUuidV1});
|
||||
params.characteristic_uuids.push_back({kModelIdCharacteristics, Uuid()});
|
||||
params.gatt_operation_timeout = kFailureTimeout;
|
||||
constexpr int kKeyBasedCharacteristicIndex = 0;
|
||||
|
||||
RobustGattClient gatt_client(seeker_ble_, provider, params);
|
||||
// Key based pairing characteristic is write only, so read will fail.
|
||||
gatt_client.ReadCharacteristic(
|
||||
kKeyBasedCharacteristicIndex,
|
||||
[&](absl::StatusOr<absl::string_view> value) {
|
||||
EXPECT_THAT(value.status(), StatusIs(absl::StatusCode::kUnavailable));
|
||||
latch.CountDown();
|
||||
});
|
||||
|
||||
EXPECT_TRUE(latch.Await().Ok());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace fastpair
|
||||
} // namespace nearby
|
||||
@@ -83,6 +83,8 @@ cc_library(
|
||||
copts = ["-DNO_WEBRTC"],
|
||||
visibility = [
|
||||
"//connections/implementation:__subpackages__",
|
||||
"//fastpair/internal:__pkg__",
|
||||
"//fastpair/internal/mediums:__pkg__",
|
||||
"//internal/network:__subpackages__",
|
||||
"//internal/platform:__pkg__",
|
||||
"//internal/platform/implementation:__subpackages__",
|
||||
|
||||
@@ -581,6 +581,7 @@ bool BleV2Medium::GattServer::HasCharacteristic(
|
||||
}
|
||||
|
||||
void BleV2Medium::GattServer::Stop() {
|
||||
if (stopped_) return;
|
||||
NEARBY_LOGS(INFO) << "G3 Ble GattServer Stop";
|
||||
stopped_ = true;
|
||||
characteristics_.clear();
|
||||
|
||||
Reference in New Issue
Block a user