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