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 <algorithm>
23 #include <list>
24 #include <optional>
25 #include <vector>
26 
27 #include "hardware/bt_has.h"
28 #include "has_preset.h"
29 #include "osi/include/alarm.h"
30 
31 namespace bluetooth::le_audio {
32 namespace has {
33 /* HAS control point Change Id */
34 enum class PresetCtpChangeId : uint8_t {
35   PRESET_GENERIC_UPDATE = 0,
36   PRESET_DELETED,
37   PRESET_AVAILABLE,
38   PRESET_UNAVAILABLE,
39   /* NOTICE: Values below are for internal use only of this particular
40    * implementation, and do not correspond to any bluetooth specification.
41    */
42   CHANGE_ID_MAX_ = PRESET_UNAVAILABLE,
43 };
44 std::ostream& operator<<(std::ostream& out, const PresetCtpChangeId value);
45 
46 /* HAS control point Opcodes */
47 enum class PresetCtpOpcode : uint8_t {
48   READ_PRESETS = 1,
49   READ_PRESET_RESPONSE,
50   PRESET_CHANGED,
51   WRITE_PRESET_NAME,
52   SET_ACTIVE_PRESET,
53   SET_NEXT_PRESET,
54   SET_PREV_PRESET,
55   SET_ACTIVE_PRESET_SYNC,
56   SET_NEXT_PRESET_SYNC,
57   SET_PREV_PRESET_SYNC,
58   /* NOTICE: Values below are for internal use only of this particular
59    * implementation, and do not correspond to any bluetooth specification.
60    */
61   OP_MAX_ = SET_PREV_PRESET_SYNC,
62   OP_NONE_ = OP_MAX_ + 1,
63 };
64 std::ostream& operator<<(std::ostream& out, const PresetCtpOpcode value);
65 
PresetCtpOpcode2Bitmask(PresetCtpOpcode op)66 static constexpr uint16_t PresetCtpOpcode2Bitmask(PresetCtpOpcode op) {
67   return (uint16_t)0b1 << static_cast<std::underlying_type_t<PresetCtpOpcode>>(op);
68 }
69 
70 /* Mandatory opcodes if control point characteristic exists */
71 static constexpr uint16_t kControlPointMandatoryOpcodesBitmask =
72         PresetCtpOpcode2Bitmask(PresetCtpOpcode::READ_PRESETS) |
73         PresetCtpOpcode2Bitmask(PresetCtpOpcode::SET_ACTIVE_PRESET) |
74         PresetCtpOpcode2Bitmask(PresetCtpOpcode::SET_NEXT_PRESET) |
75         PresetCtpOpcode2Bitmask(PresetCtpOpcode::SET_PREV_PRESET);
76 
77 /* Optional coordinated operation opcodes */
78 static constexpr uint16_t kControlPointSynchronizedOpcodesBitmask =
79         PresetCtpOpcode2Bitmask(PresetCtpOpcode::SET_ACTIVE_PRESET_SYNC) |
80         PresetCtpOpcode2Bitmask(PresetCtpOpcode::SET_NEXT_PRESET_SYNC) |
81         PresetCtpOpcode2Bitmask(PresetCtpOpcode::SET_PREV_PRESET_SYNC);
82 
83 /* Represents HAS Control Point value notification */
84 struct HasCtpNtf {
85   PresetCtpOpcode opcode;
86   PresetCtpChangeId change_id;
87   bool is_last;
88   union {
89     uint8_t index;
90     uint8_t prev_index;
91   };
92   std::optional<HasPreset> preset;
93 
94   static std::optional<HasCtpNtf> FromCharacteristicValue(uint16_t len, const uint8_t* value);
95 };
96 std::ostream& operator<<(std::ostream& out, const HasCtpNtf& value);
97 
98 /* Represents HAS Control Point operation request */
99 struct HasCtpOp {
100   std::variant<RawAddress, int> addr_or_group;
101   PresetCtpOpcode opcode;
102   uint8_t index;
103   uint8_t num_of_indices;
104   std::optional<std::string> name;
105   uint16_t op_id;
106 
107   HasCtpOp(std::variant<RawAddress, int> addr_or_group_id, PresetCtpOpcode op,
108            uint8_t index = bluetooth::has::kHasPresetIndexInvalid, uint8_t num_of_indices = 1,
109            std::optional<std::string> name = std::nullopt)
addr_or_groupHasCtpOp110       : addr_or_group(addr_or_group_id),
111         opcode(op),
112         index(index),
113         num_of_indices(num_of_indices),
114         name(name) {
115     /* Skip 0 on roll-over */
116     last_op_id_ += 1;
117     if (last_op_id_ == 0) {
118       last_op_id_ = 1;
119     }
120     op_id = last_op_id_;
121   }
122 
123   std::vector<uint8_t> ToCharacteristicValue(void) const;
124 
IsGroupRequestHasCtpOp125   bool IsGroupRequest() const { return std::holds_alternative<int>(addr_or_group); }
126 
GetGroupIdHasCtpOp127   int GetGroupId() const {
128     return std::holds_alternative<int>(addr_or_group) ? std::get<int>(addr_or_group) : -1;
129   }
130 
GetDeviceAddrHasCtpOp131   RawAddress GetDeviceAddr() const {
132     return std::holds_alternative<RawAddress>(addr_or_group) ? std::get<RawAddress>(addr_or_group)
133                                                              : RawAddress::kEmpty;
134   }
135 
IsSyncedOperationHasCtpOp136   bool IsSyncedOperation() const {
137     return (opcode == PresetCtpOpcode::SET_ACTIVE_PRESET_SYNC) ||
138            (opcode == PresetCtpOpcode::SET_NEXT_PRESET_SYNC) ||
139            (opcode == PresetCtpOpcode::SET_PREV_PRESET_SYNC);
140   }
141 
142 private:
143   /* It's fine for this to roll-over eventually */
144   static uint16_t last_op_id_;
145 };
146 std::ostream& operator<<(std::ostream& out, const HasCtpOp& value);
147 
148 /* Used to track group operations. SetCompleted() allows to mark
149  * a single device as operation-completed when notification is received.
150  * When all the devices are SetComplete'd, timeout timer is being canceled and
151  * a group operation can be considered completed (IsFullyCompleted() == true).
152  *
153  * NOTICE: A single callback and reference counter is being used for all the
154  *         coordinator instances, therefore creating more instances result
155  *         in timeout timer being rescheduled. User should remove all the
156  *         pending op. coordinators in the timer timeout callback.
157  */
158 struct HasCtpGroupOpCoordinator {
159   std::list<RawAddress> devices;
160   HasCtpOp operation;
161   std::list<bluetooth::has::PresetInfo> preset_info_verification_list;
162 
163   static size_t ref_cnt;
164   static alarm_t* operation_timeout_timer;
165   static constexpr uint16_t kOperationTimeoutMs = 10000u;
166   static alarm_callback_t cb;
167 
168   static void Initialize(alarm_callback_t c = nullptr) {
169     operation_timeout_timer = nullptr;
170     ref_cnt = 0;
171     cb = c;
172   }
173 
CleanupHasCtpGroupOpCoordinator174   static void Cleanup() {
175     if (operation_timeout_timer != nullptr) {
176       if (alarm_is_scheduled(operation_timeout_timer)) {
177         log::verbose("{}", ref_cnt);
178         alarm_cancel(operation_timeout_timer);
179       }
180       alarm_free(operation_timeout_timer);
181       operation_timeout_timer = nullptr;
182     }
183 
184     ref_cnt = 0;
185   }
186 
IsFullyCompletedHasCtpGroupOpCoordinator187   static bool IsFullyCompleted() { return ref_cnt == 0; }
IsPendingHasCtpGroupOpCoordinator188   static bool IsPending() { return ref_cnt != 0; }
189 
190   HasCtpGroupOpCoordinator() = delete;
191   HasCtpGroupOpCoordinator& operator=(const HasCtpGroupOpCoordinator&) = delete;
192   /* NOTICE: It cannot be non-copyable if we want to put it into the std::map.
193    * The default copy constructor and copy assignment operator would break the
194    * reference counting, so we must increment ref_cnt for all the temporary
195    * copies.
196    */
HasCtpGroupOpCoordinatorHasCtpGroupOpCoordinator197   HasCtpGroupOpCoordinator(const HasCtpGroupOpCoordinator& other)
198       : devices(other.devices),
199         operation(other.operation),
200         preset_info_verification_list(other.preset_info_verification_list) {
201     ref_cnt += other.devices.size();
202   }
203 
HasCtpGroupOpCoordinatorHasCtpGroupOpCoordinator204   HasCtpGroupOpCoordinator(const std::vector<RawAddress>& targets, HasCtpOp operation)
205       : operation(operation) {
206     log::assert_that(targets.size() != 0, "Empty device list error.");
207     if (targets.size() != 1) {
208       log::assert_that(operation.IsGroupRequest(), "Must be a group operation!");
209       log::assert_that(operation.GetGroupId() != -1, "Must set valid group_id!");
210     }
211 
212     devices = std::list<RawAddress>(targets.cbegin(), targets.cend());
213 
214     ref_cnt += devices.size();
215     if (operation_timeout_timer == nullptr) {
216       operation_timeout_timer = alarm_new("GroupOpTimer");
217     }
218 
219     if (alarm_is_scheduled(operation_timeout_timer)) {
220       alarm_cancel(operation_timeout_timer);
221     }
222 
223     log::assert_that(cb != nullptr, "Timeout timer callback not set!");
224     alarm_set_on_mloop(operation_timeout_timer, kOperationTimeoutMs, cb, nullptr);
225   }
226 
~HasCtpGroupOpCoordinatorHasCtpGroupOpCoordinator227   ~HasCtpGroupOpCoordinator() {
228     /* Check if cleanup wasn't already called */
229     if (ref_cnt != 0) {
230       ref_cnt -= devices.size();
231       if (ref_cnt == 0) {
232         Cleanup();
233       }
234     }
235   }
236 
SetCompletedHasCtpGroupOpCoordinator237   bool SetCompleted(RawAddress addr) {
238     auto result = false;
239 
240     auto it = std::find(devices.begin(), devices.end(), addr);
241     if (it != devices.end()) {
242       devices.erase(it);
243       --ref_cnt;
244       result = true;
245     }
246 
247     if (ref_cnt == 0) {
248       alarm_cancel(operation_timeout_timer);
249       alarm_free(operation_timeout_timer);
250       operation_timeout_timer = nullptr;
251     }
252 
253     return result;
254   }
255 };
256 
257 }  // namespace has
258 }  // namespace bluetooth::le_audio
259 
260 namespace std {
261 template <>
262 struct formatter<bluetooth::le_audio::has::HasCtpNtf> : ostream_formatter {};
263 template <>
264 struct formatter<bluetooth::le_audio::has::HasCtpOp> : ostream_formatter {};
265 }  // namespace std
266