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