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