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