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