1 /* 2 * Copyright 2021 HIMSA II K/S - www.himsa.com. 3 * Represented by EHIMA - www.ehima.com 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 #pragma once 19 20 #include <bluetooth/log.h> 21 22 #include <numeric> 23 #include <optional> 24 #include <set> 25 #include <vector> 26 27 #include "gap_api.h" 28 #include "hardware/bt_has.h" 29 #include "has_ctp.h" 30 #include "has_journal.h" 31 #include "has_preset.h" 32 #include "internal_include/bt_trace.h" 33 #include "stack/include/bt_types.h" 34 #include "stack/include/gatt_api.h" 35 36 namespace bluetooth::le_audio { 37 namespace has { 38 39 /* Helper class to pass some minimal context through the GATT operation API. */ 40 union HasGattOpContext { 41 public: 42 void* ptr = nullptr; 43 struct { 44 /* Ctp. Operation ID or 0 if not a control point operation context */ 45 uint16_t ctp_op_id; 46 47 /* Additional user flags */ 48 uint8_t context_flags; 49 }; 50 51 /* Flags describing operation context */ 52 static constexpr uint8_t kContextFlagsEnableNotification = 0x01; 53 static constexpr uint8_t kIsNotNull = 0x02; 54 55 static constexpr uint8_t kStatusCodeNotSet = 0xF0; 56 57 HasGattOpContext(const HasCtpOp& ctp_op, uint8_t flags = 0) { 58 ctp_op_id = ctp_op.op_id; 59 /* Differ from nullptr in at least 1 bit when everything else is 0 */ 60 context_flags = flags | kIsNotNull; 61 } HasGattOpContext(uint8_t flags)62 HasGattOpContext(uint8_t flags) : ctp_op_id(0) { context_flags = flags | kIsNotNull; } HasGattOpContext(void * pp)63 HasGattOpContext(void* pp) { 64 ptr = pp; 65 /* Differ from nullptr in at least 1 bit when everything else is 0 */ 66 context_flags |= kIsNotNull; 67 } 68 operator void*() { return ptr; } 69 }; 70 71 /* Context must be constrained to void* size to pass through the GATT API */ 72 static_assert(sizeof(HasGattOpContext) <= sizeof(void*)); 73 74 /* Service UUIDs */ 75 static const bluetooth::Uuid kUuidHearingAccessService = bluetooth::Uuid::From16Bit(0x1854); 76 static const bluetooth::Uuid kUuidHearingAidFeatures = bluetooth::Uuid::From16Bit(0x2BDA); 77 static const bluetooth::Uuid kUuidHearingAidPresetControlPoint = bluetooth::Uuid::From16Bit(0x2BDB); 78 static const bluetooth::Uuid kUuidActivePresetIndex = bluetooth::Uuid::From16Bit(0x2BDC); 79 80 static const uint8_t kStartPresetIndex = 1; 81 static const uint8_t kMaxNumOfPresets = 255; 82 83 /* Base device class for the GATT-based service clients */ 84 class GattServiceDevice { 85 public: 86 RawAddress addr; 87 tCONN_ID conn_id = GATT_INVALID_CONN_ID; 88 uint16_t service_handle = GAP_INVALID_HANDLE; 89 bool is_connecting_actively = false; 90 91 uint8_t gatt_svc_validation_steps = 0xFE; isGattServiceValid()92 bool isGattServiceValid() { return gatt_svc_validation_steps == 0; } 93 94 GattServiceDevice(const RawAddress& addr, bool connecting_actively = false) addr(addr)95 : addr(addr), is_connecting_actively(connecting_actively) {} 96 GattServiceDevice()97 GattServiceDevice() : GattServiceDevice(RawAddress::kEmpty) {} 98 IsConnected()99 bool IsConnected() const { return conn_id != GATT_INVALID_CONN_ID; } 100 101 class MatchAddress { 102 private: 103 RawAddress addr; 104 105 public: MatchAddress(RawAddress addr)106 MatchAddress(RawAddress addr) : addr(addr) {} operator()107 bool operator()(const GattServiceDevice& other) const { return addr == other.addr; } 108 }; 109 110 class MatchConnId { 111 private: 112 tCONN_ID conn_id; 113 114 public: MatchConnId(tCONN_ID conn_id)115 MatchConnId(tCONN_ID conn_id) : conn_id(conn_id) {} operator()116 bool operator()(const GattServiceDevice& other) const { return conn_id == other.conn_id; } 117 }; 118 Dump(std::ostream & os)119 void Dump(std::ostream& os) const { 120 os << "\"addr\": \"" << addr << "\""; 121 os << ", \"conn_id\": " << conn_id; 122 os << ", \"is_gatt_service_valid\": " 123 << (gatt_svc_validation_steps == 0 ? "\"True\"" : "\"False\"") << "(" 124 << +gatt_svc_validation_steps << ")"; 125 os << ", \"is_connecting_actively\": " << (is_connecting_actively ? "\"True\"" : "\"False\""); 126 } 127 }; 128 129 /* Build on top of the base GattServiceDevice extends the base device context 130 * with service specific informations such as the currently active preset, 131 * all available presets, and supported optional operations. It also stores 132 * HAS service specific GATT informations such as characteristic handles. 133 */ 134 class HasDevice : public GattServiceDevice { 135 uint8_t features = 0x00; 136 uint16_t supported_opcodes_bitmask = 0x0000; 137 RefreshSupportedOpcodesBitmask(void)138 void RefreshSupportedOpcodesBitmask(void) { 139 supported_opcodes_bitmask = 0; 140 141 /* Some opcodes are mandatory but the characteristics aren't - these are 142 * conditional then. 143 */ 144 if ((cp_handle != GAP_INVALID_HANDLE) && (active_preset_handle != GAP_INVALID_HANDLE)) { 145 supported_opcodes_bitmask |= kControlPointMandatoryOpcodesBitmask; 146 } 147 148 if (features & bluetooth::has::kFeatureBitPresetSynchronizationSupported) { 149 supported_opcodes_bitmask |= kControlPointMandatoryOpcodesBitmask; 150 supported_opcodes_bitmask |= kControlPointSynchronizedOpcodesBitmask; 151 } 152 153 if (features & bluetooth::has::kFeatureBitWritablePresets) { 154 supported_opcodes_bitmask |= PresetCtpOpcode2Bitmask(PresetCtpOpcode::WRITE_PRESET_NAME); 155 } 156 } 157 158 public: 159 /* Char handle and current ccc value */ 160 uint16_t active_preset_handle = GAP_INVALID_HANDLE; 161 uint16_t active_preset_ccc_handle = GAP_INVALID_HANDLE; 162 uint16_t cp_handle = GAP_INVALID_HANDLE; 163 uint16_t cp_ccc_handle = GAP_INVALID_HANDLE; 164 uint8_t cp_ccc_val = 0; 165 uint16_t features_handle = GAP_INVALID_HANDLE; 166 uint16_t features_ccc_handle = GAP_INVALID_HANDLE; 167 168 bool features_notifications_enabled = false; 169 170 /* Presets in the ascending order of their indices */ 171 std::set<HasPreset, HasPreset::ComparatorDesc> has_presets; 172 uint8_t currently_active_preset = bluetooth::has::kHasPresetIndexInvalid; 173 174 std::list<HasCtpNtf> ctp_notifications_; 175 HasJournal has_journal_; 176 HasDevice(const RawAddress & addr,uint8_t features)177 HasDevice(const RawAddress& addr, uint8_t features) : GattServiceDevice(addr) { 178 UpdateFeatures(features); 179 } 180 ConnectionCleanUp()181 void ConnectionCleanUp() { 182 conn_id = GATT_INVALID_CONN_ID; 183 is_connecting_actively = false; 184 ctp_notifications_.clear(); 185 } 186 187 using GattServiceDevice::GattServiceDevice; 188 GetFeatures()189 uint8_t GetFeatures() const { return features; } 190 UpdateFeatures(uint8_t new_features)191 void UpdateFeatures(uint8_t new_features) { 192 features = new_features; 193 /* Update the dependent supported feature set */ 194 RefreshSupportedOpcodesBitmask(); 195 } 196 ClearSvcData()197 void ClearSvcData() { 198 GattServiceDevice::service_handle = GAP_INVALID_HANDLE; 199 GattServiceDevice::gatt_svc_validation_steps = 0xFE; 200 201 active_preset_handle = GAP_INVALID_HANDLE; 202 active_preset_ccc_handle = GAP_INVALID_HANDLE; 203 cp_handle = GAP_INVALID_HANDLE; 204 cp_ccc_handle = GAP_INVALID_HANDLE; 205 features_handle = GAP_INVALID_HANDLE; 206 features_ccc_handle = GAP_INVALID_HANDLE; 207 208 features = 0; 209 features_notifications_enabled = false; 210 211 supported_opcodes_bitmask = 0x00; 212 currently_active_preset = bluetooth::has::kHasPresetIndexInvalid; 213 214 has_presets.clear(); 215 } 216 SupportsPresets()217 inline bool SupportsPresets() const { 218 return (active_preset_handle != GAP_INVALID_HANDLE) && (cp_handle != GAP_INVALID_HANDLE); 219 } 220 SupportsActivePresetNotification()221 inline bool SupportsActivePresetNotification() const { 222 return active_preset_ccc_handle != GAP_INVALID_HANDLE; 223 } 224 SupportsFeaturesNotification()225 inline bool SupportsFeaturesNotification() const { 226 return features_ccc_handle != GAP_INVALID_HANDLE; 227 } 228 HasFeaturesNotificationEnabled()229 inline bool HasFeaturesNotificationEnabled() const { return features_notifications_enabled; } 230 SupportsOperation(PresetCtpOpcode op)231 inline bool SupportsOperation(PresetCtpOpcode op) { 232 auto mask = PresetCtpOpcode2Bitmask(op); 233 return (supported_opcodes_bitmask & mask) == mask; 234 } 235 236 bool IsValidPreset(uint8_t preset_index, bool writable_only = false) const { 237 if (has_presets.count(preset_index)) { 238 return writable_only ? has_presets.find(preset_index)->IsWritable() : true; 239 } 240 return false; 241 } 242 243 const HasPreset* GetPreset(uint8_t preset_index, bool writable_only = false) const { 244 if (has_presets.count(preset_index)) { 245 decltype(has_presets)::iterator preset = has_presets.find(preset_index); 246 if (writable_only) { 247 return preset->IsWritable() ? &*preset : nullptr; 248 } 249 return &*preset; 250 } 251 return nullptr; 252 } 253 GetPresetInfo(uint8_t index)254 std::optional<bluetooth::has::PresetInfo> GetPresetInfo(uint8_t index) const { 255 if (has_presets.count(index)) { 256 auto preset = *has_presets.find(index); 257 return bluetooth::has::PresetInfo({.preset_index = preset.GetIndex(), 258 .writable = preset.IsWritable(), 259 .available = preset.IsAvailable(), 260 .preset_name = preset.GetName()}); 261 } 262 return std::nullopt; 263 } 264 GetAllPresetInfo()265 std::vector<bluetooth::has::PresetInfo> GetAllPresetInfo() const { 266 std::vector<bluetooth::has::PresetInfo> all_info; 267 all_info.reserve(has_presets.size()); 268 269 for (auto const& preset : has_presets) { 270 log::verbose("preset: {}", preset); 271 all_info.push_back({.preset_index = preset.GetIndex(), 272 .writable = preset.IsWritable(), 273 .available = preset.IsAvailable(), 274 .preset_name = preset.GetName()}); 275 } 276 return all_info; 277 } 278 279 /* Calculates the buffer space that all the preset will use when serialized */ SerializedPresetsSize()280 uint8_t SerializedPresetsSize() const { 281 /* Two additional bytes are for the header and the number of presets */ 282 return std::accumulate(has_presets.begin(), has_presets.end(), 0, 283 [](uint8_t current, auto const& preset) { 284 return current + preset.SerializedSize(); 285 }) + 286 2; 287 } 288 289 /* Serializes all the presets into a binary blob for persistent storage */ SerializePresets(std::vector<uint8_t> & out)290 bool SerializePresets(std::vector<uint8_t>& out) const { 291 auto buffer_size = SerializedPresetsSize(); 292 auto buffer_offset = out.size(); 293 294 out.resize(out.size() + buffer_size); 295 auto p_out = out.data() + buffer_offset; 296 297 UINT8_TO_STREAM(p_out, kHasDeviceBinaryBlobHdr); 298 UINT8_TO_STREAM(p_out, has_presets.size()); 299 300 auto* const p_end = p_out + buffer_size; 301 for (auto& preset : has_presets) { 302 if (p_out + preset.SerializedSize() >= p_end) { 303 bluetooth::log::error("Serialization error."); 304 return false; 305 } 306 p_out = preset.Serialize(p_out, p_end - p_out); 307 } 308 309 return true; 310 } 311 312 /* Deserializes all the presets from a binary blob read from the persistent 313 * storage. 314 */ DeserializePresets(const uint8_t * p_in,size_t len,HasDevice & device)315 static bool DeserializePresets(const uint8_t* p_in, size_t len, HasDevice& device) { 316 HasPreset preset; 317 if (len < 2 + preset.SerializedSize()) { 318 bluetooth::log::error("Deserialization error. Invalid input buffer size length."); 319 return false; 320 } 321 auto* p_end = p_in + len; 322 323 uint8_t hdr; 324 STREAM_TO_UINT8(hdr, p_in); 325 if (hdr != kHasDeviceBinaryBlobHdr) { 326 bluetooth::log::error("Deserialization error. Bad header."); 327 return false; 328 } 329 330 uint8_t num_presets; 331 STREAM_TO_UINT8(num_presets, p_in); 332 333 device.has_presets.clear(); 334 while (p_in < p_end) { 335 auto* p_new = HasPreset::Deserialize(p_in, p_end - p_in, preset); 336 if (p_new <= p_in) { 337 bluetooth::log::error("Deserialization error. Invalid preset found."); 338 device.has_presets.clear(); 339 return false; 340 } 341 342 device.has_presets.insert(preset); 343 p_in = p_new; 344 } 345 346 return device.has_presets.size() == num_presets; 347 } 348 349 friend std::ostream& operator<<(std::ostream& os, const HasDevice& b); 350 Dump(std::ostream & os)351 void Dump(std::ostream& os) const { 352 GattServiceDevice::Dump(os); 353 os << ", \"features\": \"" << loghex(features) << "\""; 354 os << ", \"features_notifications_enabled\": " 355 << (features_notifications_enabled ? "\"Enabled\"" : "\"Disabled\""); 356 os << ", \"ctp_notifications size\": " << ctp_notifications_.size(); 357 os << ",\n"; 358 359 os << " " << "\"presets\": ["; 360 for (auto const& preset : has_presets) { 361 os << "\n " << preset << ","; 362 } 363 os << "\n ],\n"; 364 365 os << " " << "\"Ctp. notifications process queue\": {"; 366 if (ctp_notifications_.size() != 0) { 367 size_t ntf_pos = 0; 368 for (auto const& ntf : ctp_notifications_) { 369 os << "\n "; 370 if (ntf_pos == 0) { 371 os << "\"latest\": "; 372 } else { 373 os << "\"-" << ntf_pos << "\": "; 374 } 375 376 os << ntf << ","; 377 ++ntf_pos; 378 } 379 } 380 os << "\n },\n"; 381 382 os << " " << "\"event history\": {"; 383 size_t pos = 0; 384 for (auto const& record : has_journal_) { 385 os << "\n "; 386 if (pos == 0) { 387 os << "\"latest\": "; 388 } else { 389 os << "\"-" << pos << "\": "; 390 } 391 392 os << record << ","; 393 ++pos; 394 } 395 os << "\n }"; 396 } 397 398 private: 399 static constexpr int kHasDeviceBinaryBlobHdr = 0x55; 400 }; 401 402 } // namespace has 403 } // namespace bluetooth::le_audio 404 405 namespace std { 406 template <> 407 struct formatter<bluetooth::le_audio::has::HasDevice> : ostream_formatter {}; 408 } // namespace std 409