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