Add robust gatt client

PiperOrigin-RevId: 539817203
This commit is contained in:
Janusz Sobczak
2023-06-12 18:29:59 -07:00
committed by Copybara-Service
parent 5aeceb2034
commit ca60afa007
6 changed files with 1338 additions and 1 deletions
+26 -1
View File
@@ -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
+2
View File
@@ -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();