• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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