Files
nearby/fastpair/internal/mediums/robust_gatt_client.h
T
Janusz Sobczak ca60afa007 Add robust gatt client
PiperOrigin-RevId: 539817203
2023-06-12 18:29:59 -07:00

273 lines
11 KiB
C++

// 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_