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) { 63 context_flags = flags | kIsNotNull; 64 } HasGattOpContext(void * pp)65 HasGattOpContext(void* pp) { 66 ptr = pp; 67 /* Differ from nullptr in at least 1 bit when everything else is 0 */ 68 context_flags |= kIsNotNull; 69 } 70 operator void*() { return ptr; } 71 }; 72 73 /* Context must be constrained to void* size to pass through the GATT API */ 74 static_assert(sizeof(HasGattOpContext) <= sizeof(void*)); 75 76 /* Service UUIDs */ 77 static const bluetooth::Uuid kUuidHearingAccessService = 78 bluetooth::Uuid::From16Bit(0x1854); 79 static const bluetooth::Uuid kUuidHearingAidFeatures = 80 bluetooth::Uuid::From16Bit(0x2BDA); 81 static const bluetooth::Uuid kUuidHearingAidPresetControlPoint = 82 bluetooth::Uuid::From16Bit(0x2BDB); 83 static const bluetooth::Uuid kUuidActivePresetIndex = 84 bluetooth::Uuid::From16Bit(0x2BDC); 85 86 static const uint8_t kStartPresetIndex = 1; 87 static const uint8_t kMaxNumOfPresets = 255; 88 89 /* Base device class for the GATT-based service clients */ 90 class GattServiceDevice { 91 public: 92 RawAddress addr; 93 uint16_t conn_id = GATT_INVALID_CONN_ID; 94 uint16_t service_handle = GAP_INVALID_HANDLE; 95 bool is_connecting_actively = false; 96 97 uint8_t gatt_svc_validation_steps = 0xFE; isGattServiceValid()98 bool isGattServiceValid() { return gatt_svc_validation_steps == 0; } 99 100 GattServiceDevice(const RawAddress& addr, bool connecting_actively = false) addr(addr)101 : addr(addr), is_connecting_actively(connecting_actively) {} 102 GattServiceDevice()103 GattServiceDevice() : GattServiceDevice(RawAddress::kEmpty) {} 104 IsConnected()105 bool IsConnected() const { return conn_id != GATT_INVALID_CONN_ID; } 106 107 class MatchAddress { 108 private: 109 RawAddress addr; 110 111 public: MatchAddress(RawAddress addr)112 MatchAddress(RawAddress addr) : addr(addr) {} operator()113 bool operator()(const GattServiceDevice& other) const { 114 return (addr == other.addr); 115 } 116 }; 117 118 class MatchConnId { 119 private: 120 uint16_t conn_id; 121 122 public: MatchConnId(uint16_t conn_id)123 MatchConnId(uint16_t conn_id) : conn_id(conn_id) {} operator()124 bool operator()(const GattServiceDevice& other) const { 125 return (conn_id == other.conn_id); 126 } 127 }; 128 Dump(std::ostream & os)129 void Dump(std::ostream& os) const { 130 os << "\"addr\": \"" << addr << "\""; 131 os << ", \"conn_id\": " << conn_id; 132 os << ", \"is_gatt_service_valid\": " 133 << (gatt_svc_validation_steps == 0 ? "\"True\"" : "\"False\"") << "(" 134 << +gatt_svc_validation_steps << ")"; 135 os << ", \"is_connecting_actively\": " 136 << (is_connecting_actively ? "\"True\"" : "\"False\""); 137 } 138 }; 139 140 /* Build on top of the base GattServiceDevice extends the base device context 141 * with service specific informations such as the currently active preset, 142 * all available presets, and supported optional operations. It also stores 143 * HAS service specific GATT informations such as characteristic handles. 144 */ 145 class HasDevice : public GattServiceDevice { 146 uint8_t features = 0x00; 147 uint16_t supported_opcodes_bitmask = 0x0000; 148 RefreshSupportedOpcodesBitmask(void)149 void RefreshSupportedOpcodesBitmask(void) { 150 supported_opcodes_bitmask = 0; 151 152 /* Some opcodes are mandatory but the characteristics aren't - these are 153 * conditional then. 154 */ 155 if ((cp_handle != GAP_INVALID_HANDLE) && 156 (active_preset_handle != GAP_INVALID_HANDLE)) { 157 supported_opcodes_bitmask |= kControlPointMandatoryOpcodesBitmask; 158 } 159 160 if (features & bluetooth::has::kFeatureBitPresetSynchronizationSupported) { 161 supported_opcodes_bitmask |= kControlPointMandatoryOpcodesBitmask; 162 supported_opcodes_bitmask |= kControlPointSynchronizedOpcodesBitmask; 163 } 164 165 if (features & bluetooth::has::kFeatureBitWritablePresets) { 166 supported_opcodes_bitmask |= 167 PresetCtpOpcode2Bitmask(PresetCtpOpcode::WRITE_PRESET_NAME); 168 } 169 } 170 171 public: 172 /* Char handle and current ccc value */ 173 uint16_t active_preset_handle = GAP_INVALID_HANDLE; 174 uint16_t active_preset_ccc_handle = GAP_INVALID_HANDLE; 175 uint16_t cp_handle = GAP_INVALID_HANDLE; 176 uint16_t cp_ccc_handle = GAP_INVALID_HANDLE; 177 uint8_t cp_ccc_val = 0; 178 uint16_t features_handle = GAP_INVALID_HANDLE; 179 uint16_t features_ccc_handle = GAP_INVALID_HANDLE; 180 181 bool features_notifications_enabled = false; 182 183 /* Presets in the ascending order of their indices */ 184 std::set<HasPreset, HasPreset::ComparatorDesc> has_presets; 185 uint8_t currently_active_preset = bluetooth::has::kHasPresetIndexInvalid; 186 187 std::list<HasCtpNtf> ctp_notifications_; 188 HasJournal has_journal_; 189 HasDevice(const RawAddress & addr,uint8_t features)190 HasDevice(const RawAddress& addr, uint8_t features) 191 : GattServiceDevice(addr) { 192 UpdateFeatures(features); 193 } 194 ConnectionCleanUp()195 void ConnectionCleanUp() { 196 conn_id = GATT_INVALID_CONN_ID; 197 is_connecting_actively = false; 198 ctp_notifications_.clear(); 199 } 200 201 using GattServiceDevice::GattServiceDevice; 202 GetFeatures()203 uint8_t GetFeatures() const { return features; } 204 UpdateFeatures(uint8_t new_features)205 void UpdateFeatures(uint8_t new_features) { 206 features = new_features; 207 /* Update the dependent supported feature set */ 208 RefreshSupportedOpcodesBitmask(); 209 } 210 ClearSvcData()211 void ClearSvcData() { 212 GattServiceDevice::service_handle = GAP_INVALID_HANDLE; 213 GattServiceDevice::gatt_svc_validation_steps = 0xFE; 214 215 active_preset_handle = GAP_INVALID_HANDLE; 216 active_preset_ccc_handle = GAP_INVALID_HANDLE; 217 cp_handle = GAP_INVALID_HANDLE; 218 cp_ccc_handle = GAP_INVALID_HANDLE; 219 features_handle = GAP_INVALID_HANDLE; 220 features_ccc_handle = GAP_INVALID_HANDLE; 221 222 features = 0; 223 features_notifications_enabled = false; 224 225 supported_opcodes_bitmask = 0x00; 226 currently_active_preset = bluetooth::has::kHasPresetIndexInvalid; 227 228 has_presets.clear(); 229 } 230 SupportsPresets()231 inline bool SupportsPresets() const { 232 return (active_preset_handle != GAP_INVALID_HANDLE) && 233 (cp_handle != GAP_INVALID_HANDLE); 234 } 235 SupportsActivePresetNotification()236 inline bool SupportsActivePresetNotification() const { 237 return active_preset_ccc_handle != GAP_INVALID_HANDLE; 238 } 239 SupportsFeaturesNotification()240 inline bool SupportsFeaturesNotification() const { 241 return features_ccc_handle != GAP_INVALID_HANDLE; 242 } 243 HasFeaturesNotificationEnabled()244 inline bool HasFeaturesNotificationEnabled() const { 245 return features_notifications_enabled; 246 } 247 SupportsOperation(PresetCtpOpcode op)248 inline bool SupportsOperation(PresetCtpOpcode op) { 249 auto mask = PresetCtpOpcode2Bitmask(op); 250 return (supported_opcodes_bitmask & mask) == mask; 251 } 252 253 bool IsValidPreset(uint8_t preset_index, bool writable_only = false) const { 254 if (has_presets.count(preset_index)) { 255 return writable_only ? has_presets.find(preset_index)->IsWritable() 256 : true; 257 } 258 return false; 259 } 260 261 const HasPreset* GetPreset(uint8_t preset_index, 262 bool writable_only = false) const { 263 if (has_presets.count(preset_index)) { 264 decltype(has_presets)::iterator preset = has_presets.find(preset_index); 265 if (writable_only) return preset->IsWritable() ? &*preset : nullptr; 266 return &*preset; 267 } 268 return nullptr; 269 } 270 GetPresetInfo(uint8_t index)271 std::optional<bluetooth::has::PresetInfo> GetPresetInfo(uint8_t index) const { 272 if (has_presets.count(index)) { 273 auto preset = *has_presets.find(index); 274 return bluetooth::has::PresetInfo({.preset_index = preset.GetIndex(), 275 .writable = preset.IsWritable(), 276 .available = preset.IsAvailable(), 277 .preset_name = preset.GetName()}); 278 } 279 return std::nullopt; 280 } 281 GetAllPresetInfo()282 std::vector<bluetooth::has::PresetInfo> GetAllPresetInfo() const { 283 std::vector<bluetooth::has::PresetInfo> all_info; 284 all_info.reserve(has_presets.size()); 285 286 for (auto const& preset : has_presets) { 287 log::verbose("preset: {}", preset); 288 all_info.push_back({.preset_index = preset.GetIndex(), 289 .writable = preset.IsWritable(), 290 .available = preset.IsAvailable(), 291 .preset_name = preset.GetName()}); 292 } 293 return all_info; 294 } 295 296 /* Calculates the buffer space that all the preset will use when serialized */ SerializedPresetsSize()297 uint8_t SerializedPresetsSize() const { 298 /* Two additional bytes are for the header and the number of presets */ 299 return std::accumulate(has_presets.begin(), has_presets.end(), 0, 300 [](uint8_t current, auto const& preset) { 301 return current + preset.SerializedSize(); 302 }) + 303 2; 304 } 305 306 /* Serializes all the presets into a binary blob for persistent storage */ SerializePresets(std::vector<uint8_t> & out)307 bool SerializePresets(std::vector<uint8_t>& out) const { 308 auto buffer_size = SerializedPresetsSize(); 309 auto buffer_offset = out.size(); 310 311 out.resize(out.size() + buffer_size); 312 auto p_out = out.data() + buffer_offset; 313 314 UINT8_TO_STREAM(p_out, kHasDeviceBinaryBlobHdr); 315 UINT8_TO_STREAM(p_out, has_presets.size()); 316 317 auto* const p_end = p_out + buffer_size; 318 for (auto& preset : has_presets) { 319 if (p_out + preset.SerializedSize() >= p_end) { 320 bluetooth::log::error("Serialization error."); 321 return false; 322 } 323 p_out = preset.Serialize(p_out, p_end - p_out); 324 } 325 326 return true; 327 } 328 329 /* Deserializes all the presets from a binary blob read from the persistent 330 * storage. 331 */ DeserializePresets(const uint8_t * p_in,size_t len,HasDevice & device)332 static bool DeserializePresets(const uint8_t* p_in, size_t len, 333 HasDevice& device) { 334 HasPreset preset; 335 if (len < 2 + preset.SerializedSize()) { 336 bluetooth::log::error( 337 "Deserialization error. Invalid input buffer size length."); 338 return false; 339 } 340 auto* p_end = p_in + len; 341 342 uint8_t hdr; 343 STREAM_TO_UINT8(hdr, p_in); 344 if (hdr != kHasDeviceBinaryBlobHdr) { 345 bluetooth::log::error("Deserialization error. Bad header."); 346 return false; 347 } 348 349 uint8_t num_presets; 350 STREAM_TO_UINT8(num_presets, p_in); 351 352 device.has_presets.clear(); 353 while (p_in < p_end) { 354 auto* p_new = HasPreset::Deserialize(p_in, p_end - p_in, preset); 355 if (p_new <= p_in) { 356 bluetooth::log::error("Deserialization error. Invalid preset found."); 357 device.has_presets.clear(); 358 return false; 359 } 360 361 device.has_presets.insert(preset); 362 p_in = p_new; 363 } 364 365 return device.has_presets.size() == num_presets; 366 } 367 368 friend std::ostream& operator<<(std::ostream& os, const HasDevice& b); 369 Dump(std::ostream & os)370 void Dump(std::ostream& os) const { 371 GattServiceDevice::Dump(os); 372 os << ", \"features\": \"" << loghex(features) << "\""; 373 os << ", \"features_notifications_enabled\": " 374 << (features_notifications_enabled ? "\"Enabled\"" : "\"Disabled\""); 375 os << ", \"ctp_notifications size\": " << ctp_notifications_.size(); 376 os << ",\n"; 377 378 os << " " 379 << "\"presets\": ["; 380 for (auto const& preset : has_presets) { 381 os << "\n " << preset << ","; 382 } 383 os << "\n ],\n"; 384 385 os << " " 386 << "\"Ctp. notifications process queue\": {"; 387 if (ctp_notifications_.size() != 0) { 388 size_t ntf_pos = 0; 389 for (auto const& ntf : ctp_notifications_) { 390 os << "\n "; 391 if (ntf_pos == 0) { 392 os << "\"latest\": "; 393 } else { 394 os << "\"-" << ntf_pos << "\": "; 395 } 396 397 os << ntf << ","; 398 ++ntf_pos; 399 } 400 } 401 os << "\n },\n"; 402 403 os << " " 404 << "\"event history\": {"; 405 size_t pos = 0; 406 for (auto const& record : has_journal_) { 407 os << "\n "; 408 if (pos == 0) { 409 os << "\"latest\": "; 410 } else { 411 os << "\"-" << pos << "\": "; 412 } 413 414 os << record << ","; 415 ++pos; 416 } 417 os << "\n }"; 418 } 419 420 private: 421 static constexpr int kHasDeviceBinaryBlobHdr = 0x55; 422 }; 423 424 } // namespace has 425 } // namespace bluetooth::le_audio 426 427 namespace fmt { 428 template <> 429 struct formatter<bluetooth::le_audio::has::HasDevice> : ostream_formatter {}; 430 } // namespace fmt 431