blob: 0be02fd3eb1db3acc429cc32b60bd21eabe7dd32 [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.
*/
#pragma once
#include <base/logging.h>
#include <base/strings/string_number_conversions.h>
#include <algorithm>
#include <map>
#include <vector>
#include "bta_csis_api.h"
#include "bta_gatt_api.h"
#include "bta_groups.h"
#include "gap_api.h"
#include "stack/crypto_toolbox/crypto_toolbox.h"
namespace bluetooth {
namespace csis {
using bluetooth::csis::CsisLockCb;
// CSIP additions
/* Generic UUID is used when CSIS is not included in any context */
static const bluetooth::Uuid kCsisServiceUuid = bluetooth::Uuid::From16Bit(0x1846);
static const bluetooth::Uuid kCsisSirkUuid = bluetooth::Uuid::From16Bit(0x2B84);
static const bluetooth::Uuid kCsisSizeUuid = bluetooth::Uuid::From16Bit(0x2B85);
static const bluetooth::Uuid kCsisLockUuid = bluetooth::Uuid::From16Bit(0x2B86);
static const bluetooth::Uuid kCsisRankUuid = bluetooth::Uuid::From16Bit(0x2B87);
static constexpr uint8_t kCsisErrorCodeLockDenied = 0x80;
static constexpr uint8_t kCsisErrorCodeReleaseNotAllowed = 0x81;
static constexpr uint8_t kCsisErrorCodeInvalidValue = 0x82;
static constexpr uint8_t kCsisErrorCodeLockAccessSirkRejected = 0x83;
static constexpr uint8_t kCsisErrorCodeLockOobSirkOnly = 0x84;
static constexpr uint8_t kCsisErrorCodeLockAlreadyGranted = 0x85;
static constexpr uint8_t kCsisSirkTypeEncrypted = 0x00;
static constexpr uint8_t kCsisSirkCharLen = 17;
struct hdl_pair {
hdl_pair() {}
hdl_pair(uint16_t val_hdl, uint16_t ccc_hdl) : val_hdl(val_hdl), ccc_hdl(ccc_hdl) {}
uint16_t val_hdl;
uint16_t ccc_hdl;
};
/* CSIS Types */
static constexpr uint8_t kDefaultScanDurationS = 5;
static constexpr uint8_t kDefaultCsisSetSize = 2;
static constexpr uint8_t kUnknownRank = 0xff;
/* Enums */
enum class CsisLockState : uint8_t {
CSIS_STATE_UNSET = 0x00,
CSIS_STATE_UNLOCKED,
CSIS_STATE_LOCKED
};
enum class CsisDiscoveryState : uint8_t {
CSIS_DISCOVERY_IDLE = 0x00,
CSIS_DISCOVERY_ONGOING,
CSIS_DISCOVERY_COMPLETED,
};
class GattServiceDevice {
public:
RawAddress addr;
/*
* This is true only during first connection to profile, until we store the
* device.
*/
bool first_connection;
/*
* We are making active attempt to connect to this device, 'direct connect'.
*/
bool connecting_actively = false;
uint16_t conn_id = GATT_INVALID_CONN_ID;
uint16_t service_handle = GAP_INVALID_HANDLE;
bool is_gatt_service_valid = false;
GattServiceDevice(const RawAddress& addr, bool first_connection)
: addr(addr), first_connection(first_connection) {}
GattServiceDevice() : GattServiceDevice(RawAddress::kEmpty, false) {}
bool IsConnected() const { return (conn_id != GATT_INVALID_CONN_ID); }
class MatchAddress {
private:
RawAddress addr;
public:
MatchAddress(const RawAddress& addr) : addr(addr) {}
bool operator()(const std::shared_ptr<GattServiceDevice>& other) const {
return (addr == other->addr);
}
};
class MatchConnId {
private:
uint16_t conn_id;
public:
MatchConnId(uint16_t conn_id) : conn_id(conn_id) {}
bool operator()(const std::shared_ptr<GattServiceDevice>& other) const {
return (conn_id == other->conn_id);
}
};
};
/*
* CSIS instance represents single CSIS service on the remote device
* along with the handle in database and specific data to control CSIS like:
* rank, lock state.
*
* It also inclues UUID of the primary service which includes that CSIS
* instance. If this is 0x0000 it means CSIS is per device and not for specific
* service.
*/
class CsisInstance {
public:
bluetooth::Uuid coordinated_service = bluetooth::groups::kGenericContextUuid;
struct SvcData {
uint16_t start_handle;
uint16_t end_handle;
struct hdl_pair sirk_handle;
struct hdl_pair lock_handle;
uint16_t rank_handle;
struct hdl_pair size_handle;
} svc_data = {
GAP_INVALID_HANDLE,
GAP_INVALID_HANDLE,
{GAP_INVALID_HANDLE, GAP_INVALID_HANDLE},
{GAP_INVALID_HANDLE, GAP_INVALID_HANDLE},
GAP_INVALID_HANDLE,
{GAP_INVALID_HANDLE, GAP_INVALID_HANDLE},
};
CsisInstance(uint16_t start_handle, uint16_t end_handle, const bluetooth::Uuid& uuid)
: coordinated_service(uuid),
group_id_(bluetooth::groups::kGroupUnknown),
rank_(kUnknownRank),
lock_state_(CsisLockState::CSIS_STATE_UNSET) {
svc_data.start_handle = start_handle;
svc_data.end_handle = end_handle;
}
void SetLockState(CsisLockState state) {
DLOG(INFO) << __func__ << " current lock state: " << (int)(lock_state_)
<< " new lock state: " << (int)(state);
lock_state_ = state;
}
CsisLockState GetLockState(void) const { return lock_state_; }
uint8_t GetRank(void) const { return rank_; }
void SetRank(uint8_t rank) {
DLOG(INFO) << __func__ << " current rank state: " << loghex(rank_)
<< " new lock state: " << loghex(rank);
rank_ = rank;
}
void SetGroupId(int group_id) {
LOG(INFO) << __func__ << " set group id: " << group_id
<< " instance handle: " << loghex(svc_data.start_handle);
group_id_ = group_id;
}
int GetGroupId(void) const { return group_id_; }
bool HasSameUuid(const CsisInstance& csis_instance) const {
return (csis_instance.coordinated_service == coordinated_service);
}
const bluetooth::Uuid& GetUuid(void) const { return coordinated_service; }
bool IsForUuid(const bluetooth::Uuid& uuid) const { return coordinated_service == uuid; }
private:
int group_id_;
uint8_t rank_;
CsisLockState lock_state_;
};
/*
* Csis Device represents remote device and its all CSIS instances.
* It can happen that device can have more than one CSIS service instance
* if those instances are included in other services. In this way, coordinated
* set is within the context of the primary service which includes the instance.
*
* CsisDevice contains vector of the instances.
*/
class CsisDevice : public GattServiceDevice {
public:
using GattServiceDevice::GattServiceDevice;
void ClearSvcData() {
GattServiceDevice::service_handle = GAP_INVALID_HANDLE;
GattServiceDevice::is_gatt_service_valid = false;
csis_instances_.clear();
}
std::shared_ptr<CsisInstance> GetCsisInstanceByOwningHandle(uint16_t handle) {
uint16_t hdl = 0;
for (const auto& [h, inst] : csis_instances_) {
if (handle >= inst->svc_data.start_handle && handle <= inst->svc_data.end_handle) {
hdl = h;
DLOG(INFO) << __func__ << " found " << loghex(hdl);
break;
}
}
return (hdl > 0) ? csis_instances_.at(hdl) : nullptr;
}
std::shared_ptr<CsisInstance> GetCsisInstanceByGroupId(int group_id) {
uint16_t hdl = 0;
for (const auto& [handle, inst] : csis_instances_) {
if (inst->GetGroupId() == group_id) {
hdl = handle;
break;
}
}
return (hdl > 0) ? csis_instances_.at(hdl) : nullptr;
}
void SetCsisInstance(uint16_t handle, std::shared_ptr<CsisInstance> csis_instance) {
if (csis_instances_.count(handle)) {
DLOG(INFO) << __func__ << " instance is already here: " << csis_instance->GetUuid();
return;
}
csis_instances_.insert({handle, csis_instance});
DLOG(INFO) << __func__ << " instance added: " << loghex(handle) << "device: " << addr;
}
void RemoveCsisInstance(int group_id) {
for (auto it = csis_instances_.begin(); it != csis_instances_.end(); it++) {
if (it->second->GetGroupId() == group_id) {
csis_instances_.erase(it);
return;
}
}
}
int GetNumberOfCsisInstances(void) { return csis_instances_.size(); }
void ForEachCsisInstance(std::function<void(const std::shared_ptr<CsisInstance>&)> cb) {
for (auto const& kv_pair : csis_instances_) {
cb(kv_pair.second);
}
}
private:
/* Instances per start handle */
std::map<uint16_t, std::shared_ptr<CsisInstance>> csis_instances_;
};
/*
* CSIS group gathers devices which belongs to specific group.
* It also contains methond to decode encrypted SIRK and also to
* resolve PRSI in order to find out if device belongs to given group
*/
class CsisGroup {
public:
CsisGroup(int group_id, const bluetooth::Uuid& uuid)
: group_id_(group_id),
size_(kDefaultCsisSetSize),
uuid_(uuid),
member_discovery_state_(CsisDiscoveryState::CSIS_DISCOVERY_IDLE),
lock_state_(CsisLockState::CSIS_STATE_UNSET),
target_lock_state_(CsisLockState::CSIS_STATE_UNSET),
lock_transition_cnt_(0) {
devices_.clear();
}
void AddDevice(std::shared_ptr<CsisDevice> csis_device) {
auto it =
find_if(devices_.begin(), devices_.end(), CsisDevice::MatchAddress(csis_device->addr));
if (it != devices_.end()) return;
devices_.push_back(std::move(csis_device));
}
void RemoveDevice(const RawAddress& bd_addr) {
auto it = find_if(devices_.begin(), devices_.end(), CsisDevice::MatchAddress(bd_addr));
if (it != devices_.end()) devices_.erase(it);
}
int GetCurrentSize(void) const { return devices_.size(); }
bluetooth::Uuid GetUuid() const { return uuid_; }
void SetUuid(const bluetooth::Uuid& uuid) { uuid_ = uuid; }
int GetGroupId(void) const { return group_id_; }
int GetDesiredSize(void) const { return size_; }
void SetDesiredSize(int size) { size_ = size; }
bool IsGroupComplete(void) const { return size_ == (int)devices_.size(); }
bool IsEmpty(void) const { return devices_.empty(); }
bool IsDeviceInTheGroup(std::shared_ptr<CsisDevice>& csis_device) {
auto it =
find_if(devices_.begin(), devices_.end(), CsisDevice::MatchAddress(csis_device->addr));
return (it != devices_.end());
}
bool IsRsiMatching(const RawAddress& rsi) const { return is_rsi_match_sirk(rsi, GetSirk()); }
bool IsSirkBelongsToGroup(Octet16 sirk) const { return (sirk_available_ && sirk_ == sirk); }
Octet16 GetSirk(void) const { return sirk_; }
void SetSirk(Octet16& sirk) {
if (sirk_available_) {
DLOG(INFO) << __func__ << " Updating SIRK";
}
sirk_available_ = true;
sirk_ = sirk;
}
int GetNumOfConnectedDevices(void) {
return std::count_if(devices_.begin(), devices_.end(),
[](auto& d) { return d->IsConnected(); });
}
CsisDiscoveryState GetDiscoveryState(void) const { return member_discovery_state_; }
void SetDiscoveryState(CsisDiscoveryState state) {
DLOG(INFO) << __func__ << " current discovery state: " << (int)(member_discovery_state_)
<< " new discovery state: " << (int)(state);
member_discovery_state_ = state;
}
void SetCurrentLockState(CsisLockState state) { lock_state_ = state; }
void SetTargetLockState(CsisLockState state, CsisLockCb cb = base::DoNothing()) {
target_lock_state_ = state;
cb_ = std::move(cb);
switch (state) {
case CsisLockState::CSIS_STATE_LOCKED:
lock_transition_cnt_ = GetNumOfConnectedDevices();
break;
case CsisLockState::CSIS_STATE_UNLOCKED:
case CsisLockState::CSIS_STATE_UNSET:
lock_transition_cnt_ = 0;
break;
}
}
CsisLockCb GetLockCb(void) { return std::move(cb_); }
CsisLockState GetCurrentLockState(void) const { return lock_state_; }
CsisLockState GetTargetLockState(void) const { return target_lock_state_; }
bool IsAvailableForCsisLockOperation(void) {
int id = group_id_;
auto iter = std::find_if(devices_.begin(), devices_.end(), [id](auto& d) {
if (!d->IsConnected()) return false;
auto inst = d->GetCsisInstanceByGroupId(id);
LOG_ASSERT(inst);
return inst->GetLockState() == CsisLockState::CSIS_STATE_LOCKED;
});
/* If there is no locked device, we are good to go */
return iter == devices_.end();
}
void SortByCsisRank(void) {
int id = group_id_;
std::sort(devices_.begin(), devices_.end(), [id](auto& dev1, auto& dev2) {
auto inst1 = dev1->GetCsisInstanceByGroupId(id);
auto inst2 = dev2->GetCsisInstanceByGroupId(id);
if (!inst1 || !inst2) {
/* One of the device is not connected */
DLOG(INFO) << __func__ << " one of the device is not connected: inst1: " << inst1
<< " inst2: " << inst2;
return dev1->IsConnected();
}
return (inst1->GetRank() < inst2->GetRank());
});
}
std::shared_ptr<CsisDevice> GetFirstDevice(void) { return (devices_.front()); }
std::shared_ptr<CsisDevice> GetLastDevice(void) { return (devices_.back()); }
std::shared_ptr<CsisDevice> GetNextDevice(std::shared_ptr<CsisDevice>& device) {
auto iter =
std::find_if(devices_.begin(), devices_.end(), CsisDevice::MatchAddress(device->addr));
/* If reference device not found */
if (iter == devices_.end()) return nullptr;
iter++;
/* If reference device is last in group */
if (iter == devices_.end()) return nullptr;
return (*iter);
}
std::shared_ptr<CsisDevice> GetPrevDevice(std::shared_ptr<CsisDevice>& device) {
auto iter =
std::find_if(devices_.rbegin(), devices_.rend(), CsisDevice::MatchAddress(device->addr));
/* If reference device not found */
if (iter == devices_.rend()) return nullptr;
iter++;
if (iter == devices_.rend()) return nullptr;
return (*iter);
}
int GetLockTransitionCnt(void) const { return lock_transition_cnt_; }
int UpdateLockTransitionCnt(int i) {
lock_transition_cnt_ += i;
return lock_transition_cnt_;
}
/* Return true if given Autoset Private Address |srpa| matches Set Identity
* Resolving Key |sirk| */
static bool is_rsi_match_sirk(const RawAddress& rsi, const Octet16& sirk) {
/* use the 3 MSB of bd address as prand */
uint8_t rand[3];
rand[0] = rsi.address[2];
rand[1] = rsi.address[1];
rand[2] = rsi.address[0];
DLOG(INFO) << "Prand " << base::HexEncode(rand, 3);
DLOG(INFO) << "SIRK " << base::HexEncode(sirk.data(), 16);
/* generate X = E irk(R0, R1, R2) and R is random address 3 LSO */
Octet16 x = crypto_toolbox::aes_128(sirk, &rand[0], 3);
DLOG(INFO) << "X" << base::HexEncode(x.data(), 16);
rand[0] = rsi.address[5];
rand[1] = rsi.address[4];
rand[2] = rsi.address[3];
DLOG(INFO) << "Hash " << base::HexEncode(rand, 3);
if (memcmp(x.data(), &rand[0], 3) == 0) {
// match
return true;
}
// not a match
return false;
}
private:
int group_id_;
Octet16 sirk_ = {0};
bool sirk_available_ = false;
int size_;
bluetooth::Uuid uuid_;
std::vector<std::shared_ptr<CsisDevice>> devices_;
CsisDiscoveryState member_discovery_state_;
CsisLockState lock_state_;
CsisLockState target_lock_state_;
int lock_transition_cnt_;
CsisLockCb cb_;
};
} // namespace csis
} // namespace bluetooth