blob: 090f34f1c8ba447a06fa2de0aeada81b0c49134a [file] [log] [blame]
/*
* Copyright 2021 HIMSA II K/S - www.himsa.com.
* Represented by EHIMA - www.ehima.com
*
* 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
*
* http://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 <base/functional/bind.h>
#include <base/functional/callback.h>
#include <base/logging.h>
#include <base/strings/string_number_conversions.h>
#include <bluetooth/log.h>
#include <hardware/bt_csis.h>
#include <hardware/bt_gatt_types.h>
#include <list>
#include <mutex>
#include <string>
#include <vector>
#include "advertise_data_parser.h"
#include "bta_api.h"
#include "bta_csis_api.h"
#include "bta_gatt_api.h"
#include "bta_gatt_queue.h"
#include "bta_groups.h"
#include "bta_le_audio_uuids.h"
#include "bta_sec_api.h"
#include "btif/include/btif_storage.h"
#include "common/init_flags.h"
#include "crypto_toolbox/crypto_toolbox.h"
#include "csis_types.h"
#include "gap_api.h"
#include "gatt_api.h"
#include "include/check.h"
#include "internal_include/bt_target.h"
#include "internal_include/bt_trace.h"
#include "main/shim/le_scanning_manager.h"
#include "osi/include/osi.h"
#include "osi/include/stack_power_telemetry.h"
#include "stack/btm/btm_sec.h"
#include "stack/gatt/gatt_int.h"
#include "stack/include/bt_types.h"
#include "stack/include/btm_ble_sec_api.h"
using base::Closure;
using bluetooth::Uuid;
using bluetooth::csis::ConnectionState;
using bluetooth::csis::CsisClient;
using bluetooth::csis::CsisDevice;
using bluetooth::csis::CsisDiscoveryState;
using bluetooth::csis::CsisGroup;
using bluetooth::csis::CsisGroupLockStatus;
using bluetooth::csis::CsisInstance;
using bluetooth::csis::CsisLockCb;
using bluetooth::csis::CsisLockState;
using bluetooth::csis::kCsisLockUuid;
using bluetooth::csis::kCsisRankUuid;
using bluetooth::csis::kCsisServiceUuid;
using bluetooth::csis::kCsisSirkUuid;
using bluetooth::csis::kCsisSizeUuid;
using bluetooth::groups::DeviceGroups;
using bluetooth::groups::DeviceGroupsCallbacks;
using namespace bluetooth;
namespace {
class CsisClientImpl;
CsisClientImpl* instance;
std::mutex instance_mutex;
DeviceGroupsCallbacks* device_group_callbacks;
/**
* -----------------------------------------------------------------------------
* Coordinated Set Service - Client role
* -----------------------------------------------------------------------------
*
* CSIP allows to organize audio servers into sets e.g. Stereo Set, 5.1 Set
* and speed up connecting it.
*
* Since leaudio has already grouping API it was decided to integrate here CSIS
* and allow it to group devices semi-automatically.
*
* Flow:
* If connected device contains CSIS services, and it is included into CAP
* service or is not included at all, implementation reads all its
* characteristisc. The only mandatory characteristic is Set Identity Resolving
* Key (SIRK) and once this is read implementation assumes there is at least 2
* devices in the set and start to search for other members by looking for new
* Advertising Type (RSI Type) and Resolvable Set Identifier (RSI) in it.
* In the meantime other CSIS characteristics are read and Set Size might be
* updated. When new set member is found, there is callback called to upper
* layer with the address and group id for which member has been found. During
* this time Search is stopped. Upper layers bonds new devices and connect Le
* Audio profile. If there are other members to find, implementations repeats
* the procedure.
*
*/
class CsisClientImpl : public CsisClient {
static constexpr uint8_t CSIS_STORAGE_CURRENT_LAYOUT_MAGIC = 0x10;
static constexpr size_t CSIS_STORAGE_HEADER_SZ =
sizeof(CSIS_STORAGE_CURRENT_LAYOUT_MAGIC) +
sizeof(uint8_t); /* num_of_sets */
static constexpr size_t CSIS_STORAGE_ENTRY_SZ =
sizeof(uint8_t) /* set_id */ + sizeof(uint8_t) /* desired_size */ +
sizeof(uint8_t) /* rank */ + Octet16().size();
public:
CsisClientImpl(bluetooth::csis::CsisClientCallbacks* callbacks,
Closure initCb)
: gatt_if_(0), callbacks_(callbacks) {
BTA_GATTC_AppRegister(
[](tBTA_GATTC_EVT event, tBTA_GATTC* p_data) {
if (instance && p_data) instance->GattcCallback(event, p_data);
},
base::Bind(
[](Closure initCb, uint8_t client_id, uint8_t status) {
if (status != GATT_SUCCESS) {
log::error(
"Can't start Coordinated Set Service client profile - no "
"gatt clients left!");
return;
}
instance->gatt_if_ = client_id;
initCb.Run();
DeviceGroups::Initialize(device_group_callbacks);
instance->dev_groups_ = DeviceGroups::Get();
},
initCb),
true);
BTA_DmSirkSecCbRegister([](tBTA_DM_SEC_EVT event, tBTA_DM_SEC* p_data) {
if (event != BTA_DM_SIRK_VERIFICATION_REQ_EVT) {
log::error("Invalid event received by CSIP: {}",
static_cast<int>(event));
return;
}
instance->VerifySetMember(p_data->ble_req.bd_addr);
});
log::debug("Background scan enabled");
CsisObserverSetBackground(true);
}
~CsisClientImpl() override = default;
std::shared_ptr<bluetooth::csis::CsisGroup> AssignCsisGroup(
const RawAddress& address, int group_id,
bool create_group_if_non_existing, const bluetooth::Uuid& uuid) {
log::debug("Device: {}, group_id: {}", ADDRESS_TO_LOGGABLE_CSTR(address),
group_id);
auto csis_group = FindCsisGroup(group_id);
if (!csis_group) {
if (create_group_if_non_existing) {
/* Let's create a group */
log::debug(": Create a new group {}", group_id);
auto g = std::make_shared<CsisGroup>(group_id, uuid);
csis_groups_.push_back(g);
csis_group = FindCsisGroup(group_id);
} else {
log::error(": Missing group - that shall not happen");
return nullptr;
}
}
auto device = FindDeviceByAddress(address);
if (device == nullptr) {
auto dev = std::make_shared<CsisDevice>(address, false);
devices_.push_back(dev);
device = FindDeviceByAddress(address);
}
if (!csis_group->IsDeviceInTheGroup(device)) csis_group->AddDevice(device);
return csis_group;
}
void OnGroupAddedCb(const RawAddress& address, const bluetooth::Uuid& uuid,
int group_id) {
log::debug("address: {}, uuid: {}, group_id: {}",
ADDRESS_TO_LOGGABLE_CSTR(address), uuid.ToString(), group_id);
AssignCsisGroup(address, group_id, true, uuid);
}
void OnGroupMemberAddedCb(const RawAddress& address, int group_id) {
log::debug("{}, group_id: {}", ADDRESS_TO_LOGGABLE_CSTR(address), group_id);
AssignCsisGroup(address, group_id, false, Uuid::kEmpty);
}
void OnGroupRemovedCb(const bluetooth::Uuid& uuid, int group_id) {
RemoveCsisGroup(group_id);
}
void OnGroupMemberRemovedCb(const RawAddress& address, int group_id) {
log::debug("{}, group_id: {}", ADDRESS_TO_LOGGABLE_CSTR(address), group_id);
auto device = FindDeviceByAddress(address);
if (device) RemoveCsisDevice(device, group_id);
}
void OnGroupAddFromStorageCb(const RawAddress& address,
const bluetooth::Uuid& uuid, int group_id) {
auto device = FindDeviceByAddress(address);
if (device == nullptr) return;
auto csis_group = FindCsisGroup(group_id);
if (csis_group == nullptr) {
log::error("the csis group (id: {} ) does not exist", group_id);
return;
}
if (!csis_group->IsDeviceInTheGroup(device)) {
log::error("the csis group (id: {} ) does contain the device: {}",
group_id, ADDRESS_TO_LOGGABLE_CSTR(address));
return;
}
if (csis_group->GetUuid() == Uuid::kEmpty) {
csis_group->SetUuid(uuid);
}
int rank = bluetooth::csis::CSIS_RANK_INVALID;
auto csis_instance = device->GetCsisInstanceByGroupId(group_id);
if (csis_instance) {
rank = csis_instance->GetRank();
}
callbacks_->OnDeviceAvailable(device->addr, csis_group->GetGroupId(),
csis_group->GetDesiredSize(), rank, uuid);
}
void Connect(const RawAddress& address) override {
log::debug("{}", ADDRESS_TO_LOGGABLE_CSTR(address));
auto device = FindDeviceByAddress(address);
if (device == nullptr) {
devices_.emplace_back(std::make_shared<CsisDevice>(address, true));
} else {
device->connecting_actively = true;
}
BTA_GATTC_Open(gatt_if_, address, BTM_BLE_DIRECT_CONNECTION, false);
}
void Disconnect(const RawAddress& addr) override {
log::debug("{}", ADDRESS_TO_LOGGABLE_CSTR(addr));
auto device = FindDeviceByAddress(addr);
if (device == nullptr) {
log::warn("Device not connected to profile {}",
ADDRESS_TO_LOGGABLE_CSTR(addr));
callbacks_->OnConnectionState(addr, ConnectionState::DISCONNECTED);
return;
}
/* Removes all active connections or registrations for connection */
if (device->IsConnected()) {
BTA_GATTC_Close(device->conn_id);
} else {
BTA_GATTC_CancelOpen(gatt_if_, addr, false);
DoDisconnectCleanUp(device);
callbacks_->OnConnectionState(addr, ConnectionState::DISCONNECTED);
}
}
void RemoveDevice(const RawAddress& addr) override {
log::debug("{}", ADDRESS_TO_LOGGABLE_CSTR(addr));
auto device = FindDeviceByAddress(addr);
if (!device) return;
Disconnect(addr);
dev_groups_->RemoveDevice(addr);
}
int GetGroupId(const RawAddress& addr, Uuid uuid) override {
auto device = FindDeviceByAddress(addr);
if (device == nullptr) return bluetooth::groups::kGroupUnknown;
int group_id = dev_groups_->GetGroupId(addr, uuid);
auto csis_group = FindCsisGroup(group_id);
if (csis_group == nullptr) return bluetooth::groups::kGroupUnknown;
return csis_group->GetGroupId();
}
void HandleCsisLockProcedureError(
std::shared_ptr<CsisGroup>& csis_group,
std::shared_ptr<CsisDevice>& csis_device,
CsisGroupLockStatus status =
CsisGroupLockStatus::FAILED_LOCKED_BY_OTHER) {
/* Clear information about ongoing lock procedure */
CsisLockCb cb = csis_group->GetLockCb();
csis_group->SetTargetLockState(CsisLockState::CSIS_STATE_UNSET);
int group_id = csis_group->GetGroupId();
/* Send unlock to previous devices. It shall be done in reverse order. */
auto prev_dev = csis_group->GetPrevDevice(csis_device);
while (prev_dev) {
if (prev_dev->IsConnected()) {
auto prev_csis_instance = prev_dev->GetCsisInstanceByGroupId(group_id);
LOG_ASSERT(prev_csis_instance) << " prev_csis_instance does not exist!";
SetLock(prev_dev, prev_csis_instance,
CsisLockState::CSIS_STATE_UNLOCKED);
}
prev_dev = csis_group->GetPrevDevice(prev_dev);
}
/* Call application callback */
NotifyGroupStatus(group_id, false, status, std::move(cb));
}
void OnGattCsisWriteLockRsp(uint16_t conn_id, tGATT_STATUS status,
uint16_t handle, void* data) {
auto device = FindDeviceByConnId(conn_id);
if (device == nullptr) {
log::error("Device not there for conn_id: 0x{:04x}", conn_id);
return;
}
int group_id = PTR_TO_UINT(data);
auto csis_group = FindCsisGroup(group_id);
if (csis_group == nullptr) {
log::error("There is no group: {}", group_id);
return;
}
CsisLockState target_lock_state = csis_group->GetTargetLockState();
log::debug("Device {}, target lock: {}, status: 0x{:02x}",
ADDRESS_TO_LOGGABLE_CSTR(device->addr), (int)target_lock_state,
(int)status);
if (target_lock_state == CsisLockState::CSIS_STATE_UNSET) return;
if (status != GATT_SUCCESS &&
status != bluetooth::csis::kCsisErrorCodeLockAlreadyGranted) {
if (target_lock_state == CsisLockState::CSIS_STATE_UNLOCKED) {
/* When unlocking just drop the counter on error and that is it */
csis_group->UpdateLockTransitionCnt(-1);
return;
}
/* In case of GATT ERROR */
log::error("Incorrect write status=0x{:02x}", (int)(status));
/* Unlock previous devices */
HandleCsisLockProcedureError(csis_group, device);
if (status == GATT_DATABASE_OUT_OF_SYNC) {
log::info("Database out of sync for {}",
ADDRESS_TO_LOGGABLE_CSTR(device->addr));
ClearDeviceInformationAndStartSearch(device);
}
return;
}
/* All is good, continue. Try to send lock to other devices.*/
auto csis_instance = device->GetCsisInstanceByGroupId(group_id);
LOG_ASSERT(csis_instance) << " csis_instance does not exist!";
csis_instance->SetLockState(target_lock_state);
if (csis_group->GetLockTransitionCnt() == 0) {
log::error("Not expected lock state");
return;
}
if (csis_group->UpdateLockTransitionCnt(-1) == 0) {
csis_group->SetCurrentLockState(csis_group->GetTargetLockState());
CsisLockCompleted(
csis_group,
csis_group->GetCurrentLockState() == CsisLockState::CSIS_STATE_LOCKED,
CsisGroupLockStatus::SUCCESS);
return;
}
if (target_lock_state == CsisLockState::CSIS_STATE_LOCKED) {
std::shared_ptr<CsisDevice> next_dev;
do {
next_dev = csis_group->GetNextDevice(device);
if (!next_dev) break;
} while (!next_dev->IsConnected());
if (next_dev) {
auto next_csis_inst = next_dev->GetCsisInstanceByGroupId(group_id);
LOG_ASSERT(csis_instance) << " csis_instance does not exist!";
#if CSIP_UPPER_TESTER_FORCE_TO_SEND_LOCK == FALSE
if (next_csis_inst->GetLockState() ==
CsisLockState::CSIS_STATE_LOCKED) {
/* Somebody else managed to lock it.
* Unlock previous devices
*/
HandleCsisLockProcedureError(csis_group, next_dev);
return;
}
#endif
SetLock(next_dev, next_csis_inst, CsisLockState::CSIS_STATE_LOCKED);
}
}
}
void SetLock(std::shared_ptr<CsisDevice>& device,
std::shared_ptr<CsisInstance>& csis_instance,
CsisLockState lock) {
std::vector<uint8_t> value = {
(std::underlying_type<CsisLockState>::type)lock};
log::info("{}, rank: {}, conn_id: 0x{:04x}, handle: 0x{:04x}",
ADDRESS_TO_LOGGABLE_CSTR(device->addr), csis_instance->GetRank(),
device->conn_id, csis_instance->svc_data.lock_handle.val_hdl);
BtaGattQueue::WriteCharacteristic(
device->conn_id, csis_instance->svc_data.lock_handle.val_hdl, value,
GATT_WRITE,
[](uint16_t conn_id, tGATT_STATUS status, uint16_t handle, uint16_t len,
const uint8_t* value, void* data) {
if (instance)
instance->OnGattCsisWriteLockRsp(conn_id, status, handle, data);
},
UINT_TO_PTR(csis_instance->GetGroupId()));
}
void NotifyGroupStatus(int group_id, bool lock, CsisGroupLockStatus status,
CsisLockCb cb) {
callbacks_->OnGroupLockChanged(group_id, lock, status);
if (cb) std::move(cb).Run(group_id, lock, status);
}
std::vector<RawAddress> GetDeviceList(int group_id) override {
std::vector<RawAddress> result;
auto csis_group = FindCsisGroup(group_id);
if (!csis_group || csis_group->IsEmpty()) return result;
auto csis_device = csis_group->GetFirstDevice();
while (csis_device) {
result.push_back(csis_device->addr);
csis_device = csis_group->GetNextDevice(csis_device);
}
return result;
}
void LockGroup(int group_id, bool lock, CsisLockCb cb) override {
if (lock) {
log::debug("Locking group: {}", group_id);
} else {
log::debug("Unlocking group: {}", group_id);
}
/* For now we try to lock only connected devices in the group
* TODO: We can consider reconnected to not connected devices and then
* locked them
*/
auto csis_group = FindCsisGroup(group_id);
if (csis_group == nullptr) {
log::error("Group not found: {}", group_id);
NotifyGroupStatus(group_id, false,
CsisGroupLockStatus::FAILED_INVALID_GROUP,
std::move(cb));
return;
}
if (csis_group->IsEmpty()) {
NotifyGroupStatus(group_id, false,
CsisGroupLockStatus::FAILED_GROUP_EMPTY, std::move(cb));
return;
}
if (csis_group->GetTargetLockState() != CsisLockState::CSIS_STATE_UNSET) {
/* CSIS operation ongoing */
log::debug(
"Lock operation ongoing: group id: {}, target state {}", group_id,
(csis_group->GetTargetLockState() == CsisLockState::CSIS_STATE_LOCKED
? "lock"
: "unlock"));
return;
}
CsisLockState new_lock_state = lock ? CsisLockState::CSIS_STATE_LOCKED
: CsisLockState::CSIS_STATE_UNLOCKED;
if (csis_group->GetCurrentLockState() == new_lock_state) {
log::debug("Nothing to do as requested lock is there");
NotifyGroupStatus(group_id, lock, CsisGroupLockStatus::SUCCESS,
std::move(cb));
return;
}
#if CSIP_UPPER_TESTER_FORCE_TO_SEND_LOCK == FALSE
if (lock && !csis_group->IsAvailableForCsisLockOperation()) {
log::debug("Group {} locked by other", group_id);
NotifyGroupStatus(group_id, false,
CsisGroupLockStatus::FAILED_LOCKED_BY_OTHER,
std::move(cb));
return;
}
#endif
csis_group->SetTargetLockState(new_lock_state, std::move(cb));
if (lock) {
/* In locking case we need to make sure we lock all the device
* and that in case of error on the way to lock the group, we
* can revert lock previously locked devices as per specification.
*/
auto csis_device = csis_group->GetFirstDevice();
while (!csis_device->IsConnected()) {
csis_device = csis_group->GetNextDevice(csis_device);
}
auto csis_instance = csis_device->GetCsisInstanceByGroupId(group_id);
LOG_ASSERT(csis_instance) << " csis_instance does not exist!";
SetLock(csis_device, csis_instance, new_lock_state);
} else {
/* For unlocking, we don't have to monitor status of unlocking device,
* therefore, we can just send unlock to all of them, in oposite rank
* order and check if we get new state notification.
*/
auto csis_device = csis_group->GetLastDevice();
auto csis_instance = csis_device->GetCsisInstanceByGroupId(group_id);
LOG_ASSERT(csis_instance) << " csis_instance does not exist!";
while (csis_device) {
if ((csis_device->IsConnected()) &&
((csis_instance->GetLockState() != new_lock_state))) {
csis_group->UpdateLockTransitionCnt(1);
SetLock(csis_device, csis_instance, new_lock_state);
}
csis_device = csis_group->GetPrevDevice(csis_device);
}
}
}
int GetDesiredSize(int group_id) override {
auto csis_group = FindCsisGroup(group_id);
if (!csis_group) {
log::info("Unknown group {}", group_id);
return -1;
}
return csis_group->GetDesiredSize();
}
bool SerializeSets(const RawAddress& addr, std::vector<uint8_t>& out) const {
auto device = FindDeviceByAddress(addr);
if (device == nullptr) {
log::warn("Skipping unknown device addr= {}",
ADDRESS_TO_LOGGABLE_CSTR(addr));
return false;
}
if (device->GetNumberOfCsisInstances() == 0) {
log::warn("No CSIS instances for addr= {}",
ADDRESS_TO_LOGGABLE_CSTR(addr));
return false;
}
log::debug(": device= {}", ADDRESS_TO_LOGGABLE_CSTR(device->addr));
auto num_sets = device->GetNumberOfCsisInstances();
if ((num_sets == 0) || (num_sets > std::numeric_limits<uint8_t>::max()))
return false;
out.resize(CSIS_STORAGE_HEADER_SZ + (num_sets * CSIS_STORAGE_ENTRY_SZ));
auto* ptr = out.data();
/* header */
UINT8_TO_STREAM(ptr, CSIS_STORAGE_CURRENT_LAYOUT_MAGIC);
UINT8_TO_STREAM(ptr, num_sets);
/* set entries */
device->ForEachCsisInstance(
[&](const std::shared_ptr<CsisInstance>& csis_inst) {
auto gid = csis_inst->GetGroupId();
auto csis_group = FindCsisGroup(gid);
if (csis_group == nullptr) {
log::error("SerializeSets: No matching group found!");
return;
}
UINT8_TO_STREAM(ptr, gid);
UINT8_TO_STREAM(ptr, csis_group->GetDesiredSize());
UINT8_TO_STREAM(ptr, csis_inst->GetRank());
Octet16 sirk = csis_group->GetSirk();
memcpy(ptr, sirk.data(), sirk.size());
ptr += sirk.size();
});
return true;
}
std::map<uint8_t, uint8_t> DeserializeSets(const RawAddress& addr,
const std::vector<uint8_t>& in) {
std::map<uint8_t, uint8_t> group_rank_map;
if (in.size() < CSIS_STORAGE_HEADER_SZ + CSIS_STORAGE_ENTRY_SZ)
return group_rank_map;
auto* ptr = in.data();
uint8_t magic;
STREAM_TO_UINT8(magic, ptr);
if (magic == CSIS_STORAGE_CURRENT_LAYOUT_MAGIC) {
uint8_t num_sets;
STREAM_TO_UINT8(num_sets, ptr);
if (in.size() <
CSIS_STORAGE_HEADER_SZ + (num_sets * CSIS_STORAGE_ENTRY_SZ)) {
log::error("Invalid persistent storage data");
return group_rank_map;
}
/* sets entries */
while (num_sets--) {
uint8_t gid;
Octet16 sirk;
uint8_t size;
uint8_t rank;
STREAM_TO_UINT8(gid, ptr);
STREAM_TO_UINT8(size, ptr);
STREAM_TO_UINT8(rank, ptr);
STREAM_TO_ARRAY(sirk.data(), ptr, (int)sirk.size());
// Set grouping and SIRK
auto csis_group = AssignCsisGroup(addr, gid, true, Uuid::kEmpty);
if (csis_group == nullptr) {
continue;
}
csis_group->SetDesiredSize(size);
csis_group->SetSirk(sirk);
// TODO: Save it for later, so we won't have to read it using GATT
group_rank_map[gid] = rank;
}
}
return group_rank_map;
}
void StartOpportunisticConnect(const RawAddress& address) {
/* Oportunistic works only for direct connect,
* but in fact this is background connect
*/
log::info(": {}", ADDRESS_TO_LOGGABLE_CSTR(address));
BTA_GATTC_Open(gatt_if_, address, BTM_BLE_DIRECT_CONNECTION, true);
}
void AddFromStorage(const RawAddress& addr, const std::vector<uint8_t>& in) {
auto group_rank_map = DeserializeSets(addr, in);
log::debug("{}, number of groups {}", ADDRESS_TO_LOGGABLE_CSTR(addr),
static_cast<int>(csis_groups_.size()));
auto device = FindDeviceByAddress(addr);
if (device == nullptr) {
device = std::make_shared<CsisDevice>(addr, false);
devices_.push_back(device);
}
for (const auto& csis_group : csis_groups_) {
if (!csis_group->IsDeviceInTheGroup(device)) continue;
if (csis_group->GetUuid() != Uuid::kEmpty) {
auto group_id = csis_group->GetGroupId();
uint8_t rank = bluetooth::csis::CSIS_RANK_INVALID;
if (group_rank_map.count(group_id) != 0) {
rank = group_rank_map.at(group_id);
}
callbacks_->OnDeviceAvailable(device->addr, group_id,
csis_group->GetDesiredSize(), rank,
csis_group->GetUuid());
}
}
/* For bonded devices, CSIP can be always opportunistic service */
StartOpportunisticConnect(addr);
}
void CleanUp() {
LOG_DEBUG();
BTA_GATTC_AppDeregister(gatt_if_);
for (auto& device : devices_) {
if (device->IsConnected()) BTA_GATTC_Close(device->conn_id);
DoDisconnectCleanUp(device);
}
devices_.clear();
csis_groups_.clear();
CsisObserverSetBackground(false);
dev_groups_->CleanUp(device_group_callbacks);
}
void Dump(int fd) {
std::stringstream stream;
stream << " APP ID: " << +gatt_if_ << "\n"
<< " Groups:\n";
for (const auto& g : csis_groups_) {
stream << " == id: " << g->GetGroupId() << " ==\n"
<< " uuid: " << g->GetUuid() << "\n"
<< " desired size: " << g->GetDesiredSize() << "\n"
<< " discoverable state: "
<< static_cast<int>(g->GetDiscoveryState()) << "\n"
<< " current lock state: "
<< static_cast<int>(g->GetCurrentLockState()) << "\n"
<< " target lock state: "
<< static_cast<int>(g->GetTargetLockState()) << "\n"
<< " devices: \n";
for (auto& device : devices_) {
if (!g->IsDeviceInTheGroup(device)) {
if (device->GetExpectedGroupIdMember() == g->GetGroupId()) {
stream << " == candidate addr: "
<< ADDRESS_TO_LOGGABLE_STR(device->addr) << "\n";
}
continue;
}
stream << " == addr: " << ADDRESS_TO_LOGGABLE_STR(device->addr)
<< " ==\n"
<< " csis instance: data:"
<< "\n";
auto instance = device->GetCsisInstanceByGroupId(g->GetGroupId());
if (!instance) {
stream << " No csis instance available\n";
} else {
stream << " service handle: "
<< loghex(instance->svc_data.start_handle)
<< " rank: " << +instance->GetRank() << "\n";
}
if (!device->IsConnected()) {
stream << " Not connected\n";
} else {
stream << " Connected conn_id = "
<< std::to_string(device->conn_id) << "\n";
}
}
}
dprintf(fd, "%s", stream.str().c_str());
}
private:
std::shared_ptr<CsisDevice> FindDeviceByConnId(uint16_t conn_id) {
auto it = find_if(devices_.begin(), devices_.end(),
CsisDevice::MatchConnId(conn_id));
if (it != devices_.end()) return (*it);
return nullptr;
}
void RemoveCsisDevice(std::shared_ptr<CsisDevice>& device, int group_id) {
auto it = find_if(devices_.begin(), devices_.end(),
CsisDevice::MatchAddress(device->addr));
if (it == devices_.end()) return;
if (group_id != bluetooth::groups::kGroupUnknown) {
auto csis_group = FindCsisGroup(group_id);
if (!csis_group) {
/* This could happen when remove device is called when bonding is
* removed */
log::debug("group not found {}", group_id);
return;
}
csis_group->RemoveDevice(device->addr);
if (csis_group->IsEmpty()) {
RemoveCsisGroup(group_id);
/* Remove cached candidate devices for group */
devices_.erase(
std::remove_if(devices_.begin(), devices_.end(),
[group_id](auto& dev) {
if (dev->GetNumberOfCsisInstances() == 0 &&
dev->GetExpectedGroupIdMember() == group_id &&
dev->GetPairingSirkReadFlag() == false) {
return true;
}
return false;
}),
devices_.end());
}
device->RemoveCsisInstance(group_id);
}
if (device->GetNumberOfCsisInstances() == 0) {
devices_.erase(it);
}
}
std::shared_ptr<CsisDevice> FindDeviceByAddress(
const RawAddress& addr) const {
auto it = find_if(devices_.cbegin(), devices_.cend(),
CsisDevice::MatchAddress(addr));
if (it != devices_.end()) return (*it);
return nullptr;
}
std::shared_ptr<CsisGroup> FindCsisGroup(int group_id) const {
auto it =
find_if(csis_groups_.cbegin(), csis_groups_.cend(),
[group_id](auto& g) { return (group_id == g->GetGroupId()); });
if (it == csis_groups_.end()) return nullptr;
return (*it);
}
void RemoveCsisGroup(int group_id) {
for (auto it = csis_groups_.begin(); it != csis_groups_.end(); it++) {
if ((*it)->GetGroupId() == group_id) {
csis_groups_.erase(it);
return;
}
}
}
/* Handle encryption */
void OnEncrypted(std::shared_ptr<CsisDevice>& device) {
log::debug("{}", ADDRESS_TO_LOGGABLE_CSTR(device->addr));
if (device->is_gatt_service_valid) {
NotifyCsisDeviceValidAndStoreIfNeeded(device);
} else {
BTA_GATTC_ServiceSearchRequest(device->conn_id, &kCsisServiceUuid);
}
}
void NotifyCsisDeviceValidAndStoreIfNeeded(
std::shared_ptr<CsisDevice>& device) {
/* Notify that we are ready to go. Notice that multiple callback calls
* for a single device address can be called if device is in more than one
* CSIS group.
*/
bool notify_connected = false;
int group_id_to_discover = bluetooth::groups::kGroupUnknown;
for (const auto& csis_group : csis_groups_) {
if (!csis_group->IsDeviceInTheGroup(device)) continue;
int group_id = csis_group->GetGroupId();
auto csis_instance = device->GetCsisInstanceByGroupId(group_id);
log::debug("group id {}", group_id);
if (!csis_instance) {
/* This can happen when some other user added device to group in the
* context which is not existing on the peer side. e.g. LeAudio added it
* in the CAP context, but CSIS exist on the peer device without a
* context. We will endup in having device in 2 groups. One in generic
* context with valid csis_instance, and one in CAP context without csis
* instance */
log::info("csis_instance does not exist for group {}", group_id);
continue;
}
callbacks_->OnDeviceAvailable(
device->addr, group_id, csis_group->GetDesiredSize(),
csis_instance->GetRank(), csis_instance->GetUuid());
notify_connected = true;
if (group_id_to_discover == bluetooth::groups::kGroupUnknown) {
group_id_to_discover = group_id;
}
}
if (notify_connected) {
callbacks_->OnConnectionState(device->addr, ConnectionState::CONNECTED);
log::debug("group_id {}", group_id_to_discover);
if (group_id_to_discover != bluetooth::groups::kGroupUnknown) {
/* Start active search for the other device
* b/281120322
*/
auto g = FindCsisGroup(group_id_to_discover);
log::debug("Group size {} target size {}", g->GetDesiredSize(),
g->GetCurrentSize());
auto dev_waiting_for_bonding_cnt =
GetNumOfKnownExpectedDevicesWaitingForBonding(g->GetGroupId());
log::debug("Group size: {}, desired size: {}, waiting for bonding: {}",
g->GetCurrentSize(), g->GetDesiredSize(),
dev_waiting_for_bonding_cnt);
if (g->GetDesiredSize() >
g->GetCurrentSize() + dev_waiting_for_bonding_cnt) {
CsisActiveDiscovery(g);
}
}
}
}
void OnGattWriteCcc(uint16_t conn_id, tGATT_STATUS status, uint16_t handle,
void* user_data) {
auto device = FindDeviceByConnId(conn_id);
if (device == nullptr) {
log::info("unknown conn_id= 0x{:04x}", conn_id);
BtaGattQueue::Clean(conn_id);
return;
}
if (status == GATT_DATABASE_OUT_OF_SYNC) {
log::info("Database out of sync for {}",
ADDRESS_TO_LOGGABLE_CSTR(device->addr));
ClearDeviceInformationAndStartSearch(device);
return;
}
if (status == GATT_SUCCESS) {
log::info("Successfully registered on ccc: 0x{:04x}, device: {}", handle,
ADDRESS_TO_LOGGABLE_CSTR(device->addr));
return;
}
log::error(
"Failed to register for indications: 0x{:04x}, device: {}, status: "
"0x{:02x}",
handle, ADDRESS_TO_LOGGABLE_CSTR(device->addr), status);
auto val_handle = device->FindValueHandleByCccHandle(handle);
if (!val_handle) {
log::error("Unknown ccc handle: 0x{:04x}, device: {}", handle,
ADDRESS_TO_LOGGABLE_CSTR(device->addr));
return;
}
if (val_handle != GAP_INVALID_HANDLE) {
BTA_GATTC_DeregisterForNotifications(gatt_if_, device->addr, val_handle);
}
}
void OnCsisNotification(uint16_t conn_id, uint16_t handle, uint16_t len,
const uint8_t* value) {
auto device = FindDeviceByConnId(conn_id);
if (device == nullptr) {
log::warn("Skipping unknown device, conn_id= 0x{:04x}", conn_id);
return;
}
auto csis_instance = device->GetCsisInstanceByOwningHandle(handle);
if (csis_instance == nullptr) {
log::error("unknown notification handle: 0x{:04x} for conn_id: 0x{:04x}",
handle, conn_id);
return;
}
if (handle == csis_instance->svc_data.sirk_handle.val_hdl) {
OnCsisSirkValueUpdate(conn_id, GATT_SUCCESS, handle, len, value);
} else if (handle == csis_instance->svc_data.lock_handle.val_hdl) {
OnCsisLockNotifications(device, csis_instance, len, value);
} else if (handle == csis_instance->svc_data.size_handle.val_hdl) {
OnCsisSizeValueUpdate(conn_id, GATT_SUCCESS, handle, len, value);
} else {
log::warn("unknown notification handle 0x{:04x} for conn_id= 0x{:04x}",
handle, conn_id);
}
}
static CsisGroupLockStatus LockError2GroupLockStatus(tGATT_STATUS status) {
switch (status) {
case bluetooth::csis::kCsisErrorCodeLockDenied:
return CsisGroupLockStatus::FAILED_LOCKED_BY_OTHER;
case bluetooth::csis::kCsisErrorCodeReleaseNotAllowed:
return CsisGroupLockStatus::FAILED_LOCKED_BY_OTHER;
case bluetooth::csis::kCsisErrorCodeInvalidValue:
return CsisGroupLockStatus::FAILED_OTHER_REASON;
default:
return CsisGroupLockStatus::FAILED_OTHER_REASON;
}
}
void CsisLockCompleted(std::shared_ptr<CsisGroup>& csis_group, bool lock,
CsisGroupLockStatus status) {
log::debug("group id: {}, target state {}", csis_group->GetGroupId(),
(lock ? "lock" : "unlock"));
NotifyGroupStatus(csis_group->GetGroupId(), lock, status,
std::move(csis_group->GetLockCb()));
csis_group->SetTargetLockState(CsisLockState::CSIS_STATE_UNSET);
}
void OnCsisLockNotifications(std::shared_ptr<CsisDevice>& device,
std::shared_ptr<CsisInstance>& csis_instance,
uint16_t len, const uint8_t* value) {
if (len != 1) {
log::error("invalid notification len: {}", len);
return;
}
CsisLockState new_lock = (CsisLockState)(value[0]);
log::debug("New lock state: {}, device rank: {}",
static_cast<int>(new_lock), csis_instance->GetRank());
csis_instance->SetLockState(new_lock);
auto csis_group = FindCsisGroup(csis_instance->GetGroupId());
if (!csis_group) return;
CsisLockCb cb = csis_group->GetLockCb();
if (csis_group->GetTargetLockState() == CsisLockState::CSIS_STATE_UNSET) {
if (csis_group->GetCurrentLockState() ==
CsisLockState::CSIS_STATE_LOCKED &&
new_lock == CsisLockState::CSIS_STATE_UNLOCKED) {
/* We are here when members fires theirs lock timeout.
* Not sure what to do with our current lock state. For now we will
* change local lock state after first set member removes its lock. Then
* we count that others will do the same
*/
csis_group->SetCurrentLockState(CsisLockState::CSIS_STATE_UNLOCKED);
NotifyGroupStatus(csis_group->GetGroupId(), false,
CsisGroupLockStatus::SUCCESS, std::move(cb));
}
return;
}
if (csis_group->GetCurrentLockState() != csis_group->GetTargetLockState()) {
/* We are in process of changing lock state. If new device lock
* state is what is targeted that means all is good, we don't need
* to do here nothing, as state will be changed once all the
* characteristics are written. If new device state is not what is
* targeted, that means, device changed stated unexpectedly and locking
* procedure is broken
*/
if (new_lock != csis_group->GetTargetLockState()) {
/* Device changed back the lock state from what we expected, skip
* locking and notify user about that
*/
CsisLockCompleted(csis_group, false,
CsisGroupLockStatus::FAILED_OTHER_REASON);
}
}
}
void OnCsisSizeValueUpdate(uint16_t conn_id, tGATT_STATUS status,
uint16_t handle, uint16_t len,
const uint8_t* value,
bool notify_valid_services = false) {
auto device = FindDeviceByConnId(conn_id);
if (device == nullptr) {
log::warn("Skipping unknown device, conn_id=0x{:04x}", conn_id);
return;
}
log::debug("{}, status: 0x{:02x}", ADDRESS_TO_LOGGABLE_CSTR(device->addr),
status);
if (status != GATT_SUCCESS) {
if (status == GATT_DATABASE_OUT_OF_SYNC) {
log::info("Database out of sync for {}",
ADDRESS_TO_LOGGABLE_CSTR(device->addr));
ClearDeviceInformationAndStartSearch(device);
} else {
log::error("Could not read characteristic at handle=0x{:04x}", handle);
BTA_GATTC_Close(device->conn_id);
}
return;
}
if (len != 1) {
log::error("Invalid size value length={} at handle= 0x{:04x}", len,
handle);
BTA_GATTC_Close(device->conn_id);
return;
}
auto csis_instance = device->GetCsisInstanceByOwningHandle(handle);
if (csis_instance == nullptr) {
log::error("Unknown csis instance");
BTA_GATTC_Close(device->conn_id);
return;
}
auto csis_group = FindCsisGroup(csis_instance->GetGroupId());
if (!csis_group) {
log::error("Unknown group id yet");
return;
}
auto new_size = value[0];
csis_group->SetDesiredSize(new_size);
if (notify_valid_services) NotifyCsisDeviceValidAndStoreIfNeeded(device);
}
void OnCsisLockReadRsp(uint16_t conn_id, tGATT_STATUS status, uint16_t handle,
uint16_t len, const uint8_t* value,
bool notify_valid_services = false) {
auto device = FindDeviceByConnId(conn_id);
if (device == nullptr) {
log::warn("Skipping unknown device, conn_id=0x{:04x}", conn_id);
return;
}
log::info("{}, status 0x{:02x}", ADDRESS_TO_LOGGABLE_CSTR(device->addr),
status);
if (status != GATT_SUCCESS) {
if (status == GATT_DATABASE_OUT_OF_SYNC) {
log::info("Database out of sync for {}",
ADDRESS_TO_LOGGABLE_CSTR(device->addr));
ClearDeviceInformationAndStartSearch(device);
} else {
log::error("Could not read characteristic at handle=0x{:04x}", handle);
BTA_GATTC_Close(device->conn_id);
}
return;
}
if (len != 1) {
log::error("Invalid lock value length={}, at handle=0x{:04x}", len,
handle);
BTA_GATTC_Close(device->conn_id);
return;
}
auto csis_instance = device->GetCsisInstanceByOwningHandle(handle);
if (csis_instance == nullptr) {
log::error("Unknown csis instance");
BTA_GATTC_Close(device->conn_id);
return;
}
csis_instance->SetLockState((CsisLockState)(value[0]));
if (notify_valid_services) NotifyCsisDeviceValidAndStoreIfNeeded(device);
}
void OnCsisRankReadRsp(uint16_t conn_id, tGATT_STATUS status, uint16_t handle,
uint16_t len, const uint8_t* value,
bool notify_valid_services) {
auto device = FindDeviceByConnId(conn_id);
if (device == nullptr) {
log::warn("Skipping unknown device, conn_id= 0x{:04x}", conn_id);
return;
}
log::debug("{}, status: 0x{:02x}, rank: {}",
ADDRESS_TO_LOGGABLE_CSTR(device->addr), status, value[0]);
if (status != GATT_SUCCESS) {
if (status == GATT_DATABASE_OUT_OF_SYNC) {
log::info("Database out of sync for {}",
ADDRESS_TO_LOGGABLE_CSTR(device->addr));
ClearDeviceInformationAndStartSearch(device);
} else {
log::error("Could not read characteristic at handle=0x{:04x}", handle);
BTA_GATTC_Close(device->conn_id);
}
return;
}
if (len != 1) {
log::error("Invalid rank value length= {}, at handle= 0x{:04x}", len,
handle);
BTA_GATTC_Close(device->conn_id);
return;
}
auto csis_instance = device->GetCsisInstanceByOwningHandle(handle);
if (csis_instance == nullptr) {
log::error("Unknown csis instance handle 0x{:04x}", handle);
BTA_GATTC_Close(device->conn_id);
return;
}
csis_instance->SetRank((value[0]));
auto csis_group = FindCsisGroup(csis_instance->GetGroupId());
if (!csis_group) {
log::error("Unknown group id yet");
return;
}
csis_group->SortByCsisRank();
if (notify_valid_services) NotifyCsisDeviceValidAndStoreIfNeeded(device);
}
void OnCsisObserveCompleted(void) {
log::info("Group_id: {}", discovering_group_);
if (discovering_group_ == bluetooth::groups::kGroupUnknown) {
log::error("No ongoing CSIS discovery - disable scan");
return;
}
auto csis_group = FindCsisGroup(discovering_group_);
discovering_group_ = bluetooth::groups::kGroupUnknown;
if (!csis_group) {
log::warn("Group_id {} is not existing", discovering_group_);
discovering_group_ = bluetooth::groups::kGroupUnknown;
return;
}
discovering_group_ = bluetooth::groups::kGroupUnknown;
if (csis_group->IsGroupComplete()) {
csis_group->SetDiscoveryState(
CsisDiscoveryState::CSIS_DISCOVERY_COMPLETED);
} else {
csis_group->SetDiscoveryState(CsisDiscoveryState::CSIS_DISCOVERY_IDLE);
}
}
/*
* Sirk shall be in LE order
* encrypted_sirk: LE order
*/
bool sdf(const RawAddress& address, const Octet16& encrypted_sirk,
Octet16& sirk) {
auto pltk = BTM_BleGetPeerLTK(address);
if (!pltk.has_value()) {
log::error("No security for {}", ADDRESS_TO_LOGGABLE_CSTR(address));
return false;
}
#ifdef CSIS_DEBUG
auto irk = BTM_BleGetPeerIRK(address);
log::info("LTK {}", (base::HexEncode(*pltk.data(), 16)));
log::info("IRK {}", base::HexEncode(*irk.data(), 16));
#endif
/* Calculate salt CSIS d1.0r05 4.3 */
Octet16 zero_key;
memset(zero_key.data(), 0, 16);
std::string msg1 = "SIRKenc";
std::reverse(msg1.begin(), msg1.end());
Octet16 s1 = crypto_toolbox::aes_cmac(zero_key, (uint8_t*)(msg1.c_str()),
msg1.size());
#ifdef CSIS_DEBUG
log::info("s1 (le) {}", base::HexEncode(s1.data(), 16));
/* Create K = LTK */
log::info("K (le) {}", base::HexEncode(*pltk.data(), 16));
#endif
Octet16 T = crypto_toolbox::aes_cmac(s1, *pltk);
#ifdef CSIS_DEBUG
log::info("T (le) {}", base::HexEncode(T.data(), 16));
#endif
std::string msg2 = "csis";
std::reverse(msg2.begin(), msg2.end());
Octet16 k1 =
crypto_toolbox::aes_cmac(T, (uint8_t*)(msg2.c_str()), msg2.size());
#ifdef CSIS_DEBUG
log::info("K1 (le) {}", base::HexEncode(k1.data(), 16));
#endif
for (int i = 0; i < 16; i++) sirk[i] = encrypted_sirk[i] ^ k1[i];
#ifdef CSIS_DEBUG
log::info("SIRK (le){}", base::HexEncode(sirk.data(), 16));
#endif
return true;
}
std::vector<RawAddress> GetAllRsiFromAdvertising(
const tBTA_DM_INQ_RES* result) {
const uint8_t* p_service_data = result->p_eir;
std::vector<RawAddress> devices;
uint8_t service_data_len = 0;
while ((p_service_data = AdvertiseDataParser::GetFieldByType(
p_service_data + service_data_len,
result->eir_len - (p_service_data - result->p_eir) -
service_data_len,
BTM_BLE_AD_TYPE_RSI, &service_data_len))) {
RawAddress bda;
const uint8_t* p_bda = p_service_data;
if (service_data_len < RawAddress::kLength) {
continue;
}
STREAM_TO_BDADDR(bda, p_bda);
devices.push_back(std::move(bda));
}
return std::move(devices);
}
int GetNumOfKnownExpectedDevicesWaitingForBonding(int group_id) {
return std::count_if(
devices_.begin(), devices_.end(), [group_id](const auto& device) {
return device->GetExpectedGroupIdMember() == group_id &&
!device->GetCsisInstanceByGroupId(group_id);
});
}
void CacheAndAdvertiseExpectedMember(const RawAddress& address,
int group_id) {
auto device = FindDeviceByAddress(address);
if (device == nullptr) {
device = std::make_shared<CsisDevice>(address, false);
devices_.push_back(device);
}
/*
* Expected group ID will be checked while reading SIRK if this device
* truly is member of group.
*/
device.get()->SetExpectedGroupIdMember(group_id);
callbacks_->OnSetMemberAvailable(address,
device.get()->GetExpectedGroupIdMember());
}
void OnActiveScanResult(const tBTA_DM_INQ_RES* result) {
auto csis_device = FindDeviceByAddress(result->bd_addr);
if (csis_device) {
log::debug("Drop same device .. {}",
ADDRESS_TO_LOGGABLE_CSTR(result->bd_addr));
return;
}
/* Make sure device is not already bonded which could
* be a case for dual mode devices where
*/
if (BTM_BleIsLinkKeyKnown(result->bd_addr)) {
log::verbose("Device {} already bonded. Identity address: {}",
ADDRESS_TO_LOGGABLE_CSTR(result->bd_addr),
ADDRESS_TO_LOGGABLE_CSTR(
*BTM_BleGetIdentityAddress(result->bd_addr)));
return;
}
auto all_rsi = GetAllRsiFromAdvertising(result);
if (all_rsi.empty()) return;
/* Notify only the actively searched group */
auto csis_group = FindCsisGroup(discovering_group_);
if (csis_group == nullptr) {
log::error("No ongoing CSIS discovery - disable scan");
CsisActiveObserverSet(false);
return;
}
if (csis_group->GetDesiredSize() > 0 &&
(csis_group->GetDesiredSize() == csis_group->GetCurrentSize())) {
log::warn("Group is already complete");
return;
}
auto discovered_group_rsi = std::find_if(
all_rsi.cbegin(), all_rsi.cend(), [&csis_group](const auto& rsi) {
return csis_group->IsRsiMatching(rsi);
});
if (discovered_group_rsi != all_rsi.cend()) {
log::debug("Found set member {}",
ADDRESS_TO_LOGGABLE_CSTR(result->bd_addr));
CacheAndAdvertiseExpectedMember(result->bd_addr,
csis_group->GetGroupId());
/* Switch back to the opportunistic observer mode.
* When second device will pair, csis will restart active scan
* to search more members if needed */
CsisActiveObserverSet(false);
csis_group->SetDiscoveryState(CsisDiscoveryState::CSIS_DISCOVERY_IDLE);
}
}
static void csis_ad_type_filter_set(bool enable) {
bool is_ad_type_filter_supported =
bluetooth::shim::is_ad_type_filter_supported();
log::info("enable: {}, is_ad_type_filter_supported: {}", enable,
is_ad_type_filter_supported);
if (is_ad_type_filter_supported) {
bluetooth::shim::set_ad_type_rsi_filter(enable);
} else {
bluetooth::shim::set_empty_filter(enable);
}
}
void CsisActiveObserverSet(bool enable) {
log::info("Group_id {}: enable: {}", discovering_group_, enable);
csis_ad_type_filter_set(enable);
BTA_DmBleCsisObserve(enable,
[](tBTA_DM_SEARCH_EVT event, tBTA_DM_SEARCH* p_data) {
/* If there's no instance we are most likely shutting
* down the whole stack and we can ignore this event.
*/
if (instance == nullptr) return;
if (event == BTA_DM_INQ_CMPL_EVT) {
power_telemetry::GetInstance().LogBleScan(
static_cast<int>(p_data->inq_cmpl.num_resps));
log::info("BLE observe complete. Num Resp: {}",
p_data->inq_cmpl.num_resps);
csis_ad_type_filter_set(false);
instance->OnCsisObserveCompleted();
instance->CsisObserverSetBackground(true);
return;
}
if (event != BTA_DM_INQ_RES_EVT) {
log::warn("Unknown event: 0x{:02x}", event);
return;
}
instance->OnActiveScanResult(&p_data->inq_res);
});
BTA_DmBleScan(enable, bluetooth::csis::kDefaultScanDurationS, true);
/* Need to call it by ourselfs */
if (!enable) {
OnCsisObserveCompleted();
CsisObserverSetBackground(true);
}
}
void CheckForGroupInInqDb(const std::shared_ptr<CsisGroup>& csis_group) {
// Check if last inquiry already found devices with RSI matching this group
for (tBTM_INQ_INFO* inq_ent = BTM_InqDbFirst(); inq_ent != nullptr;
inq_ent = BTM_InqDbNext(inq_ent)) {
RawAddress rsi = inq_ent->results.ble_ad_rsi;
if (!csis_group->IsRsiMatching(rsi)) continue;
RawAddress address = inq_ent->results.remote_bd_addr;
auto device = FindDeviceByAddress(address);
if (device && csis_group->IsDeviceInTheGroup(device)) {
// InqDb will also contain existing devices, already in group - skip
// them
continue;
}
log::info("Device {} from inquiry cache match to group id {}",
ADDRESS_TO_LOGGABLE_CSTR(address), csis_group->GetGroupId());
callbacks_->OnSetMemberAvailable(address, csis_group->GetGroupId());
break;
}
}
void CsisActiveDiscovery(std::shared_ptr<CsisGroup> csis_group) {
if (bluetooth::common::InitFlags::UseRsiFromCachedInquiryResults()) {
CheckForGroupInInqDb(csis_group);
}
if ((csis_group->GetDiscoveryState() !=
CsisDiscoveryState::CSIS_DISCOVERY_IDLE)) {
log::error("Incorrect ase group: {}, state 0x{:02x}",
csis_group->GetGroupId(),
static_cast<int>(csis_group->GetDiscoveryState()));
return;
}
csis_group->SetDiscoveryState(CsisDiscoveryState::CSIS_DISCOVERY_ONGOING);
/* TODO Maybe we don't need it */
discovering_group_ = csis_group->GetGroupId();
CsisActiveObserverSet(true);
}
void OnScanBackgroundResult(const tBTA_DM_INQ_RES* result) {
if (csis_groups_.empty()) return;
auto csis_device = FindDeviceByAddress(result->bd_addr);
if (csis_device) {
log::debug("Drop known device {}",
ADDRESS_TO_LOGGABLE_CSTR(result->bd_addr));
return;
}
/* Make sure device is not already bonded which could
* be a case for dual mode devices where
*/
if (BTM_BleIsLinkKeyKnown(result->bd_addr)) {
log::verbose("Device {} already bonded. Identity address: {}",
ADDRESS_TO_LOGGABLE_CSTR(result->bd_addr),
ADDRESS_TO_LOGGABLE_CSTR(
*BTM_BleGetIdentityAddress(result->bd_addr)));
return;
}
auto all_rsi = GetAllRsiFromAdvertising(result);
if (all_rsi.empty()) return;
/* Notify all the groups this device belongs to. */
for (auto& group : csis_groups_) {
for (auto& rsi : all_rsi) {
if (group->IsRsiMatching(rsi)) {
log::info("Device {} match to group id {}",
ADDRESS_TO_LOGGABLE_CSTR(result->bd_addr),
group->GetGroupId());
if (group->GetDesiredSize() > 0 &&
(group->GetCurrentSize() == group->GetDesiredSize())) {
log::warn(
"Group is already completed. Some other device use same SIRK");
break;
}
CacheAndAdvertiseExpectedMember(result->bd_addr, group->GetGroupId());
break;
}
}
}
}
void CsisObserverSetBackground(bool enable) {
log::debug("CSIS Discovery background: {}", enable);
BTA_DmBleCsisObserve(enable,
[](tBTA_DM_SEARCH_EVT event, tBTA_DM_SEARCH* p_data) {
/* If there's no instance we are most likely shutting
* down the whole stack and we can ignore this event.
*/
if (instance == nullptr) return;
if (event == BTA_DM_INQ_CMPL_EVT) {
power_telemetry::GetInstance().LogBleScan(
static_cast<int>(p_data->inq_cmpl.num_resps));
log::verbose("BLE observe complete. Num Resp: {}",
p_data->inq_cmpl.num_resps);
return;
}
if (event != BTA_DM_INQ_RES_EVT) {
log::warn("Unknown event: 0x{:02x}", event);
return;
}
instance->OnScanBackgroundResult(&p_data->inq_res);
});
}
void OnCsisSirkValueUpdate(uint16_t conn_id, tGATT_STATUS status,
uint16_t handle, uint16_t len,
const uint8_t* value,
bool notify_valid_services = true) {
auto device = FindDeviceByConnId(conn_id);
if (device == nullptr) {
log::warn("Skipping unknown device, conn_id=0x{:04x}", conn_id);
return;
}
log::debug("{}, status: 0x{:02x}", ADDRESS_TO_LOGGABLE_CSTR(device->addr),
status);
if (status != GATT_SUCCESS) {
/* TODO handle error codes:
* kCsisErrorCodeLockAccessSirkRejected
* kCsisErrorCodeLockOobSirkOnly
*/
if (status == GATT_DATABASE_OUT_OF_SYNC) {
log::info("Database out of sync for {}",
ADDRESS_TO_LOGGABLE_CSTR(device->addr));
ClearDeviceInformationAndStartSearch(device);
} else {
log::error("Could not read characteristic at handle=0x{:04x}", handle);
BTA_GATTC_Close(device->conn_id);
}
return;
}
if (len != bluetooth::csis::kCsisSirkCharLen) {
log::error("Invalid sirk value length= {} at handle= 0x{:04x}", len,
handle);
BTA_GATTC_Close(device->conn_id);
return;
}
auto csis_instance = device->GetCsisInstanceByOwningHandle(handle);
if (csis_instance == nullptr) {
log::error("Unknown csis instance: handle 0x{:04x}", handle);
BTA_GATTC_Close(device->conn_id);
return;
}
uint8_t sirk_type = value[0];
log::info("SIRK Type: 0x{:02x}", sirk_type);
/* Verify if sirk is not all zeros */
Octet16 zero{};
if (memcmp(zero.data(), value + 1, 16) == 0) {
log::error("Received invalid zero SIRK conn_id: 0x{:02x}. Disconnecting",
device->conn_id);
BTA_GATTC_Close(device->conn_id);
return;
}
Octet16 received_sirk;
memcpy(received_sirk.data(), value + 1, 16);
if (sirk_type == bluetooth::csis::kCsisSirkTypeEncrypted) {
/* Decrypt encrypted SIRK */
Octet16 sirk;
sdf(device->addr, received_sirk, sirk);
received_sirk = sirk;
}
/* SIRK is ready. Add device to the group */
std::shared_ptr<CsisGroup> csis_group;
int group_id = csis_instance->GetGroupId();
if (group_id != bluetooth::groups::kGroupUnknown) {
/* Group already exist. */
csis_group = FindCsisGroup(group_id);
LOG_ASSERT(csis_group) << " group does not exist? " << group_id;
} else {
/* Now having SIRK we can decide if the device belongs to some group we
* know or this is a new group
*/
for (auto& g : csis_groups_) {
if (g->IsSirkBelongsToGroup(received_sirk)) {
group_id = g->GetGroupId();
break;
}
}
if (group_id == bluetooth::groups::kGroupUnknown) {
/* Here it means, we have new group. Let's us create it */
group_id =
dev_groups_->AddDevice(device->addr, csis_instance->GetUuid());
LOG_ASSERT(group_id != bluetooth::groups::kGroupUnknown);
} else {
dev_groups_->AddDevice(device->addr, csis_instance->GetUuid(),
group_id);
}
csis_group = FindCsisGroup(group_id);
csis_group->AddDevice(device);
/* Let's update csis instance group id */
csis_instance->SetGroupId(group_id);
}
csis_group->SetSirk(received_sirk);
device->is_gatt_service_valid = true;
btif_storage_update_csis_info(device->addr);
if (notify_valid_services) NotifyCsisDeviceValidAndStoreIfNeeded(device);
#ifdef CSIS_DEBUG
log::info("SIRK {}, address: {}", base::HexEncode(received_sirk.data(), 16),
ADDRESS_TO_LOGGABLE_CSTR(device->addr));
#endif
log::verbose("Expected group size {}, actual group Size: {}",
csis_group->GetDesiredSize(), csis_group->GetCurrentSize());
if (csis_group->GetDesiredSize() == csis_group->GetCurrentSize()) {
auto iter = devices_.cbegin();
/*
* Remove devices which are expected members but are not connected and
* group is already completed. Those devices are cached ivalid devices
* kept on list to not trigger "new device" found every time advertising
* event is received.
*/
while (iter != devices_.cend()) {
if (((*iter)->GetExpectedGroupIdMember() == csis_group->GetGroupId()) &&
!(*iter)->IsConnected()) {
iter = devices_.erase(iter);
} else {
++iter;
}
}
}
}
void DeregisterNotifications(std::shared_ptr<CsisDevice> device) {
device->ForEachCsisInstance(
[&](const std::shared_ptr<CsisInstance>& csis_inst) {
DisableGattNotification(device->conn_id, device->addr,
csis_inst->svc_data.lock_handle.val_hdl);
DisableGattNotification(device->conn_id, device->addr,
csis_inst->svc_data.sirk_handle.val_hdl);
DisableGattNotification(device->conn_id, device->addr,
csis_inst->svc_data.size_handle.val_hdl);
});
}
void DoDisconnectCleanUp(std::shared_ptr<CsisDevice> device) {
log::info("{}", ADDRESS_TO_LOGGABLE_CSTR(device->addr));
DeregisterNotifications(device);
if (device->IsConnected()) {
BtaGattQueue::Clean(device->conn_id);
device->conn_id = GATT_INVALID_CONN_ID;
}
}
bool OnCsisServiceFound(std::shared_ptr<CsisDevice> device,
const gatt::Service* service,
const bluetooth::Uuid& context_uuid,
bool is_last_instance) {
log::debug("service handle: 0x{:04x}, end handle: 0x{:04x}, uuid: {}",
service->handle, service->end_handle, context_uuid.ToString());
auto csis_inst = std::make_shared<CsisInstance>(
(uint16_t)service->handle, (uint16_t)service->end_handle, context_uuid);
/* Let's check if we know group of this device */
int group_id = dev_groups_->GetGroupId(device->addr, context_uuid);
if (group_id != bluetooth::groups::kGroupUnknown)
csis_inst->SetGroupId(group_id);
device->SetCsisInstance(csis_inst->svc_data.start_handle, csis_inst);
/* Initially validate and store GATT service discovery data */
for (const gatt::Characteristic& charac : service->characteristics) {
if (charac.uuid == kCsisLockUuid) {
/* Find the mandatory CCC descriptor */
uint16_t ccc_handle =
FindCccHandle(device->conn_id, charac.value_handle);
if (ccc_handle == GAP_INVALID_HANDLE) {
log::error("no HAS Active Preset CCC descriptor found!");
device->RemoveCsisInstance(group_id);
return false;
}
csis_inst->svc_data.lock_handle.val_hdl = charac.value_handle;
csis_inst->svc_data.lock_handle.ccc_hdl = ccc_handle;
SubscribeForNotifications(device->conn_id, device->addr,
charac.value_handle, ccc_handle);
log::debug(
"Lock UUID found handle: 0x{:04x}, ccc handle: 0x{:04x}, device: "
"{}",
csis_inst->svc_data.lock_handle.val_hdl,
csis_inst->svc_data.lock_handle.ccc_hdl,
ADDRESS_TO_LOGGABLE_CSTR(device->addr));
} else if (charac.uuid == kCsisRankUuid) {
csis_inst->svc_data.rank_handle = charac.value_handle;
log::debug("Rank UUID found handle: 0x{:04x}, device: {}",
csis_inst->svc_data.rank_handle,
ADDRESS_TO_LOGGABLE_CSTR(device->addr));
} else if (charac.uuid == kCsisSirkUuid) {
/* Find the optional CCC descriptor */
uint16_t ccc_handle =
FindCccHandle(device->conn_id, charac.value_handle);
csis_inst->svc_data.sirk_handle.ccc_hdl = ccc_handle;
csis_inst->svc_data.sirk_handle.val_hdl = charac.value_handle;
if (ccc_handle != GAP_INVALID_HANDLE)
SubscribeForNotifications(device->conn_id, device->addr,
charac.value_handle, ccc_handle);
log::debug(
"SIRK UUID found handle: 0x{:04x}, ccc handle: 0x{:04x}, device: "
"{}",
csis_inst->svc_data.sirk_handle.val_hdl,
csis_inst->svc_data.sirk_handle.ccc_hdl,
ADDRESS_TO_LOGGABLE_CSTR(device->addr));
} else if (charac.uuid == kCsisSizeUuid) {
/* Find the optional CCC descriptor */
uint16_t ccc_handle =
FindCccHandle(device->conn_id, charac.value_handle);
csis_inst->svc_data.size_handle.ccc_hdl = ccc_handle;
csis_inst->svc_data.size_handle.val_hdl = charac.value_handle;
if (ccc_handle != GAP_INVALID_HANDLE)
SubscribeForNotifications(device->conn_id, device->addr,
charac.value_handle, ccc_handle);
log::debug(
"Size UUID found handle: 0x{:04x}, ccc handle: 0x{:04x}, device: "
"{}",
csis_inst->svc_data.size_handle.val_hdl,
csis_inst->svc_data.size_handle.ccc_hdl,
ADDRESS_TO_LOGGABLE_CSTR(device->addr));
}
}
/* Sirk is the only mandatory characteristic. If it is in
* place, service is OK
*/
if (csis_inst->svc_data.sirk_handle.val_hdl == GAP_INVALID_HANDLE) {
/* We have some characteristics but all dependencies are not satisfied */
log::error("Service has a broken structure.");
device->RemoveCsisInstance(group_id);
return false;
}
bool notify_after_sirk_read = false;
bool notify_after_lock_read = false;
bool notify_after_rank_read = false;
bool notify_after_size_read = false;
/* Find which read will be the last one*/
if (is_last_instance) {
if (csis_inst->svc_data.rank_handle != GAP_INVALID_HANDLE) {
notify_after_rank_read = true;
} else if (csis_inst->svc_data.size_handle.val_hdl !=
GAP_INVALID_HANDLE) {
notify_after_size_read = true;
} else if (csis_inst->svc_data.lock_handle.val_hdl !=
GAP_INVALID_HANDLE) {
notify_after_lock_read = true;
} else {
notify_after_sirk_read = true;
}
}
/* Read SIRK */
BtaGattQueue::ReadCharacteristic(
device->conn_id, csis_inst->svc_data.sirk_handle.val_hdl,
[](uint16_t conn_id, tGATT_STATUS status, uint16_t handle, uint16_t len,
uint8_t* value, void* user_data) {
if (instance)
instance->OnCsisSirkValueUpdate(conn_id, status, handle, len, value,
(bool)user_data);
},
(void*)notify_after_sirk_read);
/* Read Lock */
if (csis_inst->svc_data.lock_handle.val_hdl != GAP_INVALID_HANDLE) {
BtaGattQueue::ReadCharacteristic(
device->conn_id, csis_inst->svc_data.lock_handle.val_hdl,
[](uint16_t conn_id, tGATT_STATUS status, uint16_t handle,
uint16_t len, uint8_t* value, void* user_data) {
if (instance)
instance->OnCsisLockReadRsp(conn_id, status, handle, len, value,
(bool)user_data);
},
(void*)notify_after_lock_read);
}
/* Read Size */
if (csis_inst->svc_data.size_handle.val_hdl != GAP_INVALID_HANDLE) {
BtaGattQueue::ReadCharacteristic(
device->conn_id, csis_inst->svc_data.size_handle.val_hdl,
[](uint16_t conn_id, tGATT_STATUS status, uint16_t handle,
uint16_t len, uint8_t* value, void* user_data) {
if (instance)
instance->OnCsisSizeValueUpdate(conn_id, status, handle, len,
value, (bool)user_data);
},
(void*)notify_after_size_read);
}
/* Read Rank */
if (csis_inst->svc_data.rank_handle != GAP_INVALID_HANDLE) {
BtaGattQueue::ReadCharacteristic(
device->conn_id, csis_inst->svc_data.rank_handle,
[](uint16_t conn_id, tGATT_STATUS status, uint16_t handle,
uint16_t len, uint8_t* value, void* user_data) {
if (instance)
instance->OnCsisRankReadRsp(conn_id, status, handle, len, value,
(bool)user_data);
},
(void*)notify_after_rank_read);
}
return true;
}
/* These are all generic GATT event handlers calling HAS specific code. */
void GattcCallback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data) {
log::info("event = 0x{:02x}", event);
/* This is in case Csis CleanUp is already done
* while GATT is still up and could send events
*/
if (!instance) return;
switch (event) {
case BTA_GATTC_DEREG_EVT:
break;
case BTA_GATTC_OPEN_EVT:
OnGattConnected(p_data->open);
break;
case BTA_GATTC_CLOSE_EVT:
OnGattDisconnected(p_data->close);
break;
case BTA_GATTC_SEARCH_CMPL_EVT:
OnGattServiceSearchComplete(p_data->search_cmpl);
break;
case BTA_GATTC_NOTIF_EVT:
OnGattNotification(p_data->notify);
break;
case BTA_GATTC_ENC_CMPL_CB_EVT: {
uint8_t encryption_status;
if (BTM_IsEncrypted(p_data->enc_cmpl.remote_bda, BT_TRANSPORT_LE)) {
encryption_status = BTM_SUCCESS;
} else {
encryption_status = BTM_FAILED_ON_SECURITY;
}
OnLeEncryptionComplete(p_data->enc_cmpl.remote_bda, encryption_status);
} break;
case BTA_GATTC_SRVC_CHG_EVT:
OnGattServiceChangeEvent(p_data->remote_bda);
break;
case BTA_GATTC_SRVC_DISC_DONE_EVT:
OnGattServiceDiscoveryDoneEvent(p_data->remote_bda);
break;
default:
break;
}
}
void OnGattConnected(const tBTA_GATTC_OPEN& evt) {
log::info("{}, conn_id=0x{:04x}, transport={}, status={}(0x{:02x})",
ADDRESS_TO_LOGGABLE_CSTR(evt.remote_bda), evt.conn_id,
bt_transport_text(evt.transport), gatt_status_text(evt.status),
evt.status);
if (evt.transport != BT_TRANSPORT_LE) {
log::warn("Only LE connection is allowed (transport {})",
bt_transport_text(evt.transport));
BTA_GATTC_Close(evt.conn_id);
return;
}
auto device = FindDeviceByAddress(evt.remote_bda);
if (device == nullptr) {
log::debug("Skipping unknown device, address= {}",
ADDRESS_TO_LOGGABLE_CSTR(evt.remote_bda));
BTA_GATTC_Close(evt.conn_id);
return;
}
if (evt.status != GATT_SUCCESS) {
log::error("Failed to connect to server device {}",
ADDRESS_TO_LOGGABLE_CSTR(evt.remote_bda));
if (device->connecting_actively)
callbacks_->OnConnectionState(evt.remote_bda,
ConnectionState::DISCONNECTED);
DoDisconnectCleanUp(device);
return;
}
device->connecting_actively = false;
device->conn_id = evt.conn_id;
/* Verify bond */
if (BTM_SecIsSecurityPending(device->addr)) {
/* if security collision happened, wait for encryption done
* (BTA_GATTC_ENC_CMPL_CB_EVT) */
return;
}
/* verify bond */
if (BTM_IsEncrypted(device->addr, BT_TRANSPORT_LE)) {
/* if link has been encrypted */
OnEncrypted(device);
return;
}
int result = BTM_SetEncryption(device->addr, BT_TRANSPORT_LE, nullptr,
nullptr, BTM_BLE_SEC_ENCRYPT);
log::info("Encryption required for {}. Request result: 0x{:02x}",
ADDRESS_TO_LOGGABLE_CSTR(device->addr), result);
if (result == BTM_ERR_KEY_MISSING) {
log::error("Link key unknown for {}, disconnect profile",
ADDRESS_TO_LOGGABLE_CSTR(device->addr));
BTA_GATTC_Close(device->conn_id);
}
}
void OnGattDisconnected(const tBTA_GATTC_CLOSE& evt) {
auto device = FindDeviceByAddress(evt.remote_bda);
if (device == nullptr) {
log::warn("Skipping unknown device disconnect, conn_id= 0x{:04x}",
evt.conn_id);
return;
}
log::debug("device={}", ADDRESS_TO_LOGGABLE_CSTR(device->addr));
callbacks_->OnConnectionState(evt.remote_bda,
ConnectionState::DISCONNECTED);
// Unlock others only if device was locked by us but has disconnected
// unexpectedly.
if ((evt.reason == GATT_CONN_TIMEOUT) ||
(evt.reason == GATT_CONN_TERMINATE_PEER_USER)) {
device->ForEachCsisInstance(
[&](const std::shared_ptr<CsisInstance>& csis_inst) {
auto csis_group = FindCsisGroup(csis_inst->GetGroupId());
if (csis_group == nullptr) return;
if ((csis_group->GetCurrentLockState() ==
CsisLockState::CSIS_STATE_LOCKED)) {
HandleCsisLockProcedureError(
csis_group, device,
CsisGroupLockStatus::LOCKED_GROUP_MEMBER_LOST);
}
});
}
DoDisconnectCleanUp(device);
}
void OnGattServiceSearchComplete(const tBTA_GATTC_SEARCH_CMPL& evt) {
auto device = FindDeviceByConnId(evt.conn_id);
if (device == nullptr) {
log::warn("Skipping unknown device, conn_id= 0x{:4x}", evt.conn_id);
return;
}
/* verify encryption enabled */
if (!BTM_IsEncrypted(device->addr, BT_TRANSPORT_LE)) {
log::warn("Device not yet bonded - waiting for encryption");
return;
}
/* Ignore if our service data is valid (discovery initiated by someone
* else?) */
if (!device->is_gatt_service_valid) {
if (evt.status != GATT_SUCCESS) {
log::error("Service discovery failed");
BTA_GATTC_Close(device->conn_id);
DoDisconnectCleanUp(device);
return;
}
LOG_VERBOSE();
const std::list<gatt::Service>* all_services =
BTA_GATTC_GetServices(device->conn_id);
std::vector<uint16_t> all_csis_start_handles;
/* Le's just find all the CSIS primary services and store the start
* handles */
for (auto& svrc : *all_services) {
if (svrc.uuid == kCsisServiceUuid) {
all_csis_start_handles.push_back(svrc.handle);
}
}
if (all_csis_start_handles.size() == 0) {
log::debug("No Csis instances found");
BTA_GATTC_Close(device->conn_id);
RemoveCsisDevice(device, bluetooth::groups::kGroupUnknown);
return;
}
for (auto& svrc : *all_services) {
if (svrc.uuid == kCsisServiceUuid) continue;
/* Try to find context for CSIS instances */
for (auto& included_srvc : svrc.included_services) {
if (included_srvc.uuid == kCsisServiceUuid) {
auto csis_svrc = BTA_GATTC_GetOwningService(
device->conn_id, included_srvc.start_handle);
auto iter = std::find(all_csis_start_handles.begin(),
all_csis_start_handles.end(),
included_srvc.start_handle);
if (iter != all_csis_start_handles.end())
all_csis_start_handles.erase(iter);
instance->OnCsisServiceFound(device, csis_svrc, svrc.uuid,
all_csis_start_handles.empty());
}
}
}
/* Here if CSIS is included, all_csis_start_handles should be empty
* Otherwise it means, we have some primary CSIS without a context,
* which means it is for the complete device.
* As per spec, there can be only one service like this.
*/
if (all_csis_start_handles.size()) {
log::debug("there is {} primary services without a context",
static_cast<int>(all_csis_start_handles.size()));
auto csis_svrc = BTA_GATTC_GetOwningService(device->conn_id,
all_csis_start_handles[0]);
instance->OnCsisServiceFound(
device, csis_svrc, bluetooth::groups::kGenericContextUuid, true);
all_csis_start_handles.clear();
}
} else {
/* This might be set already if there is no optional attributes to read
* or write.
*/
if (evt.status == GATT_SUCCESS) {
NotifyCsisDeviceValidAndStoreIfNeeded(device);
}
}
}
void OnGattNotification(const tBTA_GATTC_NOTIFY& evt) {
/* Reject invalid lengths and indications as they are not supported */
if (!evt.is_notify || evt.len > GATT_MAX_ATTR_LEN) {
log::error(": rejected BTA_GATTC_NOTIF_EVT. is_notify = {}, len= {}",
evt.is_notify, evt.len);
}
OnCsisNotification(evt.conn_id, evt.handle, evt.len, evt.value);
}
void OnLeEncryptionComplete(const RawAddress& address, uint8_t status) {
log::info("{}", ADDRESS_TO_LOGGABLE_CSTR(address));
auto device = FindDeviceByAddress(address);
if (device == nullptr) {
log::warn("Skipping unknown device {}",
ADDRESS_TO_LOGGABLE_CSTR(address));
return;
}
if (status != BTM_SUCCESS) {
log::error("encryption failed. status: 0x{:02x}", status);
BTA_GATTC_Close(device->conn_id);
return;
}
if (device->is_gatt_service_valid) {
instance->OnEncrypted(device);
} else {
BTA_GATTC_ServiceSearchRequest(device->conn_id, &kCsisServiceUuid);
}
}
void ClearDeviceInformationAndStartSearch(
std::shared_ptr<CsisDevice> device) {
log::info("{}", ADDRESS_TO_LOGGABLE_CSTR(device->addr));
if (device->is_gatt_service_valid == false) {
log::debug("Device database already invalidated.");
return;
}
/* Invalidate service discovery results */
BtaGattQueue::Clean(device->conn_id);
DeregisterNotifications(device);
device->ClearSvcData();
BTA_GATTC_ServiceSearchRequest(device->conn_id, &kCsisServiceUuid);
}
void OnGattServiceChangeEvent(const RawAddress& address) {
auto device = FindDeviceByAddress(address);
if (!device) {
log::warn("Skipping unknown device {}",
ADDRESS_TO_LOGGABLE_CSTR(address));
return;
}
log::info("{}", ADDRESS_TO_LOGGABLE_CSTR(address));
ClearDeviceInformationAndStartSearch(device);
}
void OnGattServiceDiscoveryDoneEvent(const RawAddress& address) {
auto device = FindDeviceByAddress(address);
if (!device) {
log::warn("Skipping unknown device {}",
ADDRESS_TO_LOGGABLE_CSTR(address));
return;
}
log::debug("address={}", ADDRESS_TO_LOGGABLE_CSTR(address));
if (!device->is_gatt_service_valid)
BTA_GATTC_ServiceSearchRequest(device->conn_id, &kCsisServiceUuid);
}
static uint16_t FindCccHandle(uint16_t conn_id, uint16_t char_handle) {
const gatt::Characteristic* p_char =
BTA_GATTC_GetCharacteristic(conn_id, char_handle);
if (!p_char) {
log::warn("No such characteristic: 0x{:04x}", char_handle);
return GAP_INVALID_HANDLE;
}
for (const gatt::Descriptor& desc : p_char->descriptors) {
if (desc.uuid == Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG))
return desc.handle;
}
return GAP_INVALID_HANDLE;
}
void SubscribeForNotifications(uint16_t conn_id, const RawAddress& address,
uint16_t value_handle, uint16_t ccc_handle) {
if (value_handle != GAP_INVALID_HANDLE) {
tGATT_STATUS register_status =
BTA_GATTC_RegisterForNotifications(gatt_if_, address, value_handle);
log::debug(
"BTA_GATTC_RegisterForNotifications, status=0x{:02x}, value={}, "
"ccc=0x{:04x}",
register_status, loghex(value_handle), ccc_handle);
if (register_status != GATT_SUCCESS) return;
}
std::vector<uint8_t> value(2);
uint8_t* value_ptr = value.data();
UINT16_TO_STREAM(value_ptr, GATT_CHAR_CLIENT_CONFIG_NOTIFICATION);
BtaGattQueue::WriteDescriptor(
conn_id, ccc_handle, std::move(value), GATT_WRITE,
[](uint16_t conn_id, tGATT_STATUS status, uint16_t handle, uint16_t len,
const uint8_t* value, void* user_data) {
if (instance)
instance->OnGattWriteCcc(conn_id, status, handle, user_data);
},
nullptr);
}
void DisableGattNotification(uint16_t conn_id, const RawAddress& address,
uint16_t value_handle) {
if (value_handle != GAP_INVALID_HANDLE) {
tGATT_STATUS register_status =
BTA_GATTC_DeregisterForNotifications(gatt_if_, address, value_handle);
log::debug(
"DisableGattNotification, status=0x{:02x}, value_handle=0x{:04x}",
register_status, value_handle);
if (register_status != GATT_SUCCESS) return;
}
}
void SirkValueReadCompleteDuringPairing(tGATT_STATUS status,
const RawAddress& address,
uint8_t sirk_type,
Octet16& received_sirk) {
log::info("{}, status: 0x{:02x}", ADDRESS_TO_LOGGABLE_CSTR(address),
status);
auto device = FindDeviceByAddress(address);
if (device == nullptr) {
log::error("Unknown device {}", ADDRESS_TO_LOGGABLE_CSTR(address));
BTA_DmSirkConfirmDeviceReply(address, false);
return;
}
auto group_id_to_join = device->GetExpectedGroupIdMember();
device->SetPairingSirkReadFlag(false);
/* Verify group still exist, if not it means user forget the group and
* paring should be rejected.
*/
auto csis_group = FindCsisGroup(group_id_to_join);
if (!csis_group) {
log::error("Group {} removed during paring a set member",
group_id_to_join);
RemoveDevice(address);
BTA_DmSirkConfirmDeviceReply(address, false);
return;
}
if (status != GATT_SUCCESS) {
log::info("Invalid member, can't read SIRK (status: 0x{:02x})", status);
BTA_DmSirkConfirmDeviceReply(address, false);
return;
}
/* Verify if sirk is not all zeros */
Octet16 zero{};
if (memcmp(zero.data(), received_sirk.data(), 16) == 0) {
log::error("Received invalid zero SIRK address: {}",
ADDRESS_TO_LOGGABLE_CSTR(address));
BTA_DmSirkConfirmDeviceReply(address, false);
return;
}
if (sirk_type == bluetooth::csis::kCsisSirkTypeEncrypted) {
/* Decrypt encrypted SIRK */
Octet16 sirk;
sdf(address, received_sirk, sirk);
received_sirk = sirk;
}
if (!csis_group->IsSirkBelongsToGroup(received_sirk)) {
/*
* Joining member must join already existing group otherwise it means
* that its SIRK is different. Device connection was triggered by RSI
* match for group.
*/
log::error("Joining device {}, does not match any existig group",
ADDRESS_TO_LOGGABLE_CSTR(address));
BTA_DmSirkConfirmDeviceReply(address, false);
return;
}
log::info("Device {}, verified successfully by SIRK",
ADDRESS_TO_LOGGABLE_CSTR(address));
BTA_DmSirkConfirmDeviceReply(address, true);
/* It was temporary device and we can remove it. When upper layer
* decides to connect CSIS it will be added then
*/
RemoveDevice(address);
}
void VerifySetMember(const RawAddress& address) {
auto device = FindDeviceByAddress(address);
log::info("Device: {}", ADDRESS_TO_LOGGABLE_CSTR(address));
/* It's ok for device to not be a CSIS device at all */
if (!device) {
log::info("Valid - new member");
BTA_DmSirkConfirmDeviceReply(address, true);
return;
}
auto group_id_to_join = device->GetExpectedGroupIdMember();
if (group_id_to_join == bluetooth::groups::kGroupUnknown) {
log::warn(
"Device {} (conn_id=0x{:04x}) is already known to CSIS (# of "
"instances={}) but it is not scheduled to join any group.",
ADDRESS_TO_LOGGABLE_CSTR(address), device->conn_id,
device->GetNumberOfCsisInstances());
BTA_DmSirkConfirmDeviceReply(address, true);
return;
}
if (!gatt_cl_read_sirk_req(
address,
base::BindOnce(&CsisClientImpl::SirkValueReadCompleteDuringPairing,
weak_factory_.GetWeakPtr()))) {
log::error("Could not read SIKR of {}",
ADDRESS_TO_LOGGABLE_CSTR(address));
BTA_DmSirkConfirmDeviceReply(address, false);
return;
}
device->SetPairingSirkReadFlag(true);
}
uint8_t gatt_if_;
bluetooth::csis::CsisClientCallbacks* callbacks_;
std::list<std::shared_ptr<CsisDevice>> devices_;
std::list<std::shared_ptr<CsisGroup>> csis_groups_;
DeviceGroups* dev_groups_;
int discovering_group_ = bluetooth::groups::kGroupUnknown;
base::WeakPtrFactory<CsisClientImpl> weak_factory_{this};
};
class DeviceGroupsCallbacksImpl : public DeviceGroupsCallbacks {
public:
void OnGroupAdded(const RawAddress& address, const bluetooth::Uuid& uuid,
int group_id) override {
if (instance) instance->OnGroupAddedCb(address, uuid, group_id);
}
void OnGroupMemberAdded(const RawAddress& address, int group_id) override {
if (instance) instance->OnGroupMemberAddedCb(address, group_id);
}
void OnGroupRemoved(const bluetooth::Uuid& uuid, int group_id) override {
if (instance) instance->OnGroupRemovedCb(uuid, group_id);
}
void OnGroupMemberRemoved(const RawAddress& address, int group_id) override {
if (instance) instance->OnGroupMemberRemovedCb(address, group_id);
}
void OnGroupAddFromStorage(const RawAddress& address,
const bluetooth::Uuid& uuid,
int group_id) override {
if (instance) instance->OnGroupAddFromStorageCb(address, uuid, group_id);
}
};
class DeviceGroupsCallbacksImpl;
DeviceGroupsCallbacksImpl deviceGroupsCallbacksImpl;
} // namespace
void CsisClient::Initialize(bluetooth::csis::CsisClientCallbacks* callbacks,
Closure initCb) {
std::scoped_lock<std::mutex> lock(instance_mutex);
if (instance) {
log::info("Already initialized!");
return;
}
device_group_callbacks = &deviceGroupsCallbacksImpl;
instance = new CsisClientImpl(callbacks, initCb);
}
bool CsisClient::IsCsisClientRunning() { return instance; }
CsisClient* CsisClient::Get(void) {
CHECK(instance);
return instance;
}
void CsisClient::AddFromStorage(const RawAddress& addr,
const std::vector<uint8_t>& in) {
if (!instance) {
log::error("Not initialized yet!");
return;
}
instance->AddFromStorage(addr, in);
}
bool CsisClient::GetForStorage(const RawAddress& addr,
std::vector<uint8_t>& out) {
if (!instance) {
log::error("Not initialized yet!");
return false;
}
return instance->SerializeSets(addr, out);
}
void CsisClient::CleanUp() {
std::scoped_lock<std::mutex> lock(instance_mutex);
BTA_DmSirkSecCbRegister(nullptr);
CsisClientImpl* ptr = instance;
instance = nullptr;
if (ptr) {
ptr->CleanUp();
delete ptr;
}
}
void CsisClient::DebugDump(int fd) {
std::scoped_lock<std::mutex> lock(instance_mutex);
dprintf(fd, "Coordinated Set Service Client:\n");
if (instance) instance->Dump(fd);
dprintf(fd, "\n");
}