Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 06a5be26 authored by Łukasz Rymanowski's avatar Łukasz Rymanowski Committed by Jakub Pawlowski
Browse files

csip: Coordinated Set Identification Profile implementation

Bug: 150670922
Tag: #feature
Test: atest --host bluetoot_csis_test
Sponsor: jpawlowski@
Merged-In: I5088ef034b10ddf413c5acb4b199295a437e6f7e
Change-Id: I5088ef034b10ddf413c5acb4b199295a437e6f7e
parent a6b83334
Loading
Loading
Loading
Loading
+52 −0
Original line number Diff line number Diff line
@@ -75,6 +75,7 @@ cc_library_static {
        "av/bta_av_ci.cc",
        "av/bta_av_main.cc",
        "av/bta_av_ssm.cc",
        "csis/csis_client.cc",
        "gatt/bta_gattc_act.cc",
        "gatt/bta_gattc_api.cc",
        "gatt/bta_gattc_cache.cc",
@@ -258,6 +259,57 @@ cc_test {
    cflags: ["-DBUILDCFG"],
}

// csis unit tests for host
cc_test {
    name: "bluetooth_csis_test",
    test_suites: ["device-tests"],
    defaults: [
        "fluoride_bta_defaults",
        "clang_coverage_bin",
    ],
    host_supported: true,
    include_dirs: [
        "packages/modules/Bluetooth/system",
        "packages/modules/Bluetooth/system/bta/groups",
        "packages/modules/Bluetooth/system/bta/include",
        "packages/modules/Bluetooth/system/bta/test/common",
        "packages/modules/Bluetooth/system/btif/include",
        "packages/modules/Bluetooth/system/osi/include",
    ],
    srcs : [
        ":TestMockBtif",
        "csis/csis_client.cc",
        "csis/csis_client_test.cc",
        "groups/groups.cc",
        "gatt/database.cc",
        "gatt/database_builder.cc",
        "test/common/bta_dm_api_mock.cc",
        "test/common/bta_gatt_api_mock.cc",
        "test/common/bta_gatt_queue_mock.cc",
        "test/common/btm_api_mock.cc",
    ],
    shared_libs: [
        "libprotobuf-cpp-lite",
        "libcrypto",
    ],
    static_libs : [
        "crypto_toolbox_for_tests",
        "libgmock",
        "libbt-common",
        "libbt-protos-lite",
        "libosi",
    ],
    sanitize: {
        cfi: true,
        scs: true,
        address: true,
        all_undefined: true,
        integer_overflow: true,
        diag: {
            undefined : true
        },
    },
}

// groups unit tests for host
cc_test {
+1 −0
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ static_library("bta") {
    "av/bta_av_ci.cc",
    "av/bta_av_main.cc",
    "av/bta_av_ssm.cc",
    "csis/csis_client.cc",
    "dm/bta_dm_act.cc",
    "dm/bta_dm_api.cc",
    "dm/bta_dm_cfg.cc",
+1813 −0

File added.

Preview size limit exceeded, changes collapsed.

+1119 −0

File added.

Preview size limit exceeded, changes collapsed.

+470 −0
Original line number Diff line number Diff line
/*
 * 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;

  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)
      : group_id_(group_id),
        size_(kDefaultCsisSetSize),
        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(); }
  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_;

  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
Loading