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 #include "has_ctp.h"
19
20 #include <bluetooth/log.h>
21
22 #include <cstdint>
23 #include <cstring>
24 #include <optional>
25 #include <ostream>
26 #include <type_traits>
27 #include <variant>
28 #include <vector>
29
30 #include "has_preset.h"
31 #include "os/logging/log_adapter.h"
32 #include "stack/include/bt_types.h"
33 #include "types/raw_address.h"
34
35 using namespace bluetooth;
36
37 namespace bluetooth::le_audio {
38 namespace has {
39
ParsePresetGenericUpdate(uint16_t & len,const uint8_t * value,HasCtpNtf & ntf)40 static bool ParsePresetGenericUpdate(uint16_t& len, const uint8_t* value, HasCtpNtf& ntf) {
41 if (len < sizeof(ntf.prev_index) + HasPreset::kCharValueMinSize) {
42 log::error("Invalid preset value length={} for generic update.", len);
43 return false;
44 }
45
46 STREAM_TO_UINT8(ntf.index, value);
47 len -= 1;
48
49 ntf.preset = HasPreset::FromCharacteristicValue(len, value);
50 return true;
51 }
52
ParsePresetIndex(uint16_t & len,const uint8_t * value,HasCtpNtf & ntf)53 static bool ParsePresetIndex(uint16_t& len, const uint8_t* value, HasCtpNtf& ntf) {
54 if (len < sizeof(ntf.index)) {
55 log::error("Invalid preset value length={} for generic update.", len);
56 return false;
57 }
58
59 STREAM_TO_UINT8(ntf.index, value);
60 len -= 1;
61 return true;
62 }
63
ParsePresetReadResponse(uint16_t & len,const uint8_t * value,HasCtpNtf & ntf)64 static bool ParsePresetReadResponse(uint16_t& len, const uint8_t* value, HasCtpNtf& ntf) {
65 if (len < sizeof(ntf.is_last) + HasPreset::kCharValueMinSize) {
66 log::error("Invalid preset value length={}", len);
67 return false;
68 }
69
70 STREAM_TO_UINT8(ntf.is_last, value);
71 len -= 1;
72
73 ntf.preset = HasPreset::FromCharacteristicValue(len, value);
74 return true;
75 }
76
ParsePresetChanged(uint16_t len,const uint8_t * value,HasCtpNtf & ntf)77 static bool ParsePresetChanged(uint16_t len, const uint8_t* value, HasCtpNtf& ntf) {
78 if (len < sizeof(ntf.is_last) + sizeof(ntf.change_id)) {
79 log::error("Invalid preset value length={}", len);
80 return false;
81 }
82
83 uint8_t change_id;
84 STREAM_TO_UINT8(change_id, value);
85 len -= 1;
86 if (change_id >
87 static_cast<std::underlying_type_t<PresetCtpChangeId>>(PresetCtpChangeId::CHANGE_ID_MAX_)) {
88 log::error("Invalid preset chenge_id={}", change_id);
89 return false;
90 }
91 ntf.change_id = PresetCtpChangeId(change_id);
92 STREAM_TO_UINT8(ntf.is_last, value);
93 len -= 1;
94
95 switch (ntf.change_id) {
96 case PresetCtpChangeId::PRESET_GENERIC_UPDATE:
97 return ParsePresetGenericUpdate(len, value, ntf);
98 case PresetCtpChangeId::PRESET_AVAILABLE:
99 return ParsePresetIndex(len, value, ntf);
100 case PresetCtpChangeId::PRESET_UNAVAILABLE:
101 return ParsePresetIndex(len, value, ntf);
102 case PresetCtpChangeId::PRESET_DELETED:
103 return ParsePresetIndex(len, value, ntf);
104 default:
105 return false;
106 }
107
108 return true;
109 }
110
FromCharacteristicValue(uint16_t len,const uint8_t * value)111 std::optional<HasCtpNtf> HasCtpNtf::FromCharacteristicValue(uint16_t len, const uint8_t* value) {
112 if (len < 3) {
113 log::error("Invalid Cp notification.");
114 return std::nullopt;
115 }
116
117 uint8_t op;
118 STREAM_TO_UINT8(op, value);
119 --len;
120
121 if ((op != static_cast<std::underlying_type_t<PresetCtpOpcode>>(
122 PresetCtpOpcode::READ_PRESET_RESPONSE)) &&
123 (op !=
124 static_cast<std::underlying_type_t<PresetCtpOpcode>>(PresetCtpOpcode::PRESET_CHANGED))) {
125 log::error("Received invalid opcode in control point notification: {}", op);
126 return std::nullopt;
127 }
128
129 HasCtpNtf ntf;
130 ntf.opcode = PresetCtpOpcode(op);
131 if (ntf.opcode == bluetooth::le_audio::has::PresetCtpOpcode::PRESET_CHANGED) {
132 if (!ParsePresetChanged(len, value, ntf)) {
133 return std::nullopt;
134 }
135
136 } else if (ntf.opcode == bluetooth::le_audio::has::PresetCtpOpcode::READ_PRESET_RESPONSE) {
137 if (!ParsePresetReadResponse(len, value, ntf)) {
138 return std::nullopt;
139 }
140 }
141
142 return ntf;
143 }
144
145 uint16_t HasCtpOp::last_op_id_ = 0;
146
ToCharacteristicValue() const147 std::vector<uint8_t> HasCtpOp::ToCharacteristicValue() const {
148 std::vector<uint8_t> value;
149 auto* pp = value.data();
150
151 switch (opcode) {
152 case PresetCtpOpcode::READ_PRESETS:
153 value.resize(3);
154 pp = value.data();
155 UINT8_TO_STREAM(pp, static_cast<std::underlying_type_t<PresetCtpOpcode>>(opcode));
156 UINT8_TO_STREAM(pp, index);
157 UINT8_TO_STREAM(pp, num_of_indices);
158 break;
159 case PresetCtpOpcode::SET_ACTIVE_PRESET:
160 case PresetCtpOpcode::SET_ACTIVE_PRESET_SYNC:
161 value.resize(2);
162 pp = value.data();
163 UINT8_TO_STREAM(pp, static_cast<std::underlying_type_t<PresetCtpOpcode>>(opcode));
164 UINT8_TO_STREAM(pp, index);
165 break;
166
167 case PresetCtpOpcode::SET_NEXT_PRESET:
168 case PresetCtpOpcode::SET_NEXT_PRESET_SYNC:
169 case PresetCtpOpcode::SET_PREV_PRESET:
170 case PresetCtpOpcode::SET_PREV_PRESET_SYNC:
171 value.resize(1);
172 pp = value.data();
173 UINT8_TO_STREAM(pp, static_cast<std::underlying_type_t<PresetCtpOpcode>>(opcode));
174 break;
175
176 case PresetCtpOpcode::WRITE_PRESET_NAME: {
177 auto name_str = name.value_or("");
178 value.resize(2 + name_str.length());
179 pp = value.data();
180
181 UINT8_TO_STREAM(pp, static_cast<std::underlying_type_t<PresetCtpOpcode>>(opcode));
182 UINT8_TO_STREAM(pp, index);
183 memcpy(pp, name_str.c_str(), name_str.length());
184 } break;
185
186 default:
187 log::fatal("Bad control point operation!");
188 break;
189 }
190
191 return value;
192 }
193
194 #define CASE_SET_PTR_TO_TOKEN_STR(en) \
195 case (en): \
196 ch = #en; \
197 break;
198
operator <<(std::ostream & out,const PresetCtpChangeId value)199 std::ostream& operator<<(std::ostream& out, const PresetCtpChangeId value) {
200 const char* ch = 0;
201 switch (value) {
202 CASE_SET_PTR_TO_TOKEN_STR(PresetCtpChangeId::PRESET_GENERIC_UPDATE);
203 CASE_SET_PTR_TO_TOKEN_STR(PresetCtpChangeId::PRESET_DELETED);
204 CASE_SET_PTR_TO_TOKEN_STR(PresetCtpChangeId::PRESET_AVAILABLE);
205 CASE_SET_PTR_TO_TOKEN_STR(PresetCtpChangeId::PRESET_UNAVAILABLE);
206 default:
207 ch = "INVALID_CHANGE_ID";
208 break;
209 }
210 return out << ch;
211 }
212
operator <<(std::ostream & out,const PresetCtpOpcode value)213 std::ostream& operator<<(std::ostream& out, const PresetCtpOpcode value) {
214 const char* ch = 0;
215 switch (value) {
216 CASE_SET_PTR_TO_TOKEN_STR(PresetCtpOpcode::READ_PRESETS);
217 CASE_SET_PTR_TO_TOKEN_STR(PresetCtpOpcode::READ_PRESET_RESPONSE);
218 CASE_SET_PTR_TO_TOKEN_STR(PresetCtpOpcode::PRESET_CHANGED);
219 CASE_SET_PTR_TO_TOKEN_STR(PresetCtpOpcode::WRITE_PRESET_NAME);
220 CASE_SET_PTR_TO_TOKEN_STR(PresetCtpOpcode::SET_ACTIVE_PRESET);
221 CASE_SET_PTR_TO_TOKEN_STR(PresetCtpOpcode::SET_NEXT_PRESET);
222 CASE_SET_PTR_TO_TOKEN_STR(PresetCtpOpcode::SET_PREV_PRESET);
223 CASE_SET_PTR_TO_TOKEN_STR(PresetCtpOpcode::SET_ACTIVE_PRESET_SYNC);
224 CASE_SET_PTR_TO_TOKEN_STR(PresetCtpOpcode::SET_NEXT_PRESET_SYNC);
225 CASE_SET_PTR_TO_TOKEN_STR(PresetCtpOpcode::SET_PREV_PRESET_SYNC);
226 default:
227 ch = "NOT_A_VALID_OPCODE";
228 break;
229 }
230 return out << ch;
231 }
232 #undef SET_CH_TO_TOKENIZED
233
operator <<(std::ostream & out,const HasCtpOp & op)234 std::ostream& operator<<(std::ostream& out, const HasCtpOp& op) {
235 out << "\"HasCtpOp\": {";
236 if (std::holds_alternative<int>(op.addr_or_group)) {
237 out << "\"group_id\": " << std::get<int>(op.addr_or_group);
238 } else if (std::holds_alternative<RawAddress>(op.addr_or_group)) {
239 out << "\"address\": \"" << ADDRESS_TO_LOGGABLE_STR(std::get<RawAddress>(op.addr_or_group))
240 << "\"";
241 } else {
242 out << "\"bad value\"";
243 }
244 out << ", \"id\": " << op.op_id << ", \"opcode\": \"" << op.opcode << "\""
245 << ", \"index\": " << +op.index << ", \"name\": \"" << op.name.value_or("<none>") << "\""
246 << "}";
247 return out;
248 }
249
operator <<(std::ostream & out,const HasCtpNtf & ntf)250 std::ostream& operator<<(std::ostream& out, const HasCtpNtf& ntf) {
251 out << "\"HasCtpNtf\": {";
252 out << "\"opcode\": \"" << ntf.opcode << "\"";
253
254 if (ntf.opcode == PresetCtpOpcode::READ_PRESET_RESPONSE) {
255 out << ", \"is_last\": " << (ntf.is_last ? "\"True\"" : "\"False\"");
256 if (ntf.preset.has_value()) {
257 out << ", \"preset\": " << ntf.preset.value();
258 } else {
259 out << ", \"preset\": \"None\"";
260 }
261
262 } else if (ntf.opcode == PresetCtpOpcode::PRESET_CHANGED) {
263 out << ", \"change_id\": " << ntf.change_id;
264 out << ", \"is_last\": " << (ntf.is_last ? "\"True\"" : "\"False\"");
265 switch (ntf.change_id) {
266 case PresetCtpChangeId::PRESET_GENERIC_UPDATE:
267 out << ", \"prev_index\": " << +ntf.prev_index;
268 if (ntf.preset.has_value()) {
269 out << ", \"preset\": {" << ntf.preset.value() << "}";
270 } else {
271 out << ", \"preset\": \"None\"";
272 }
273 break;
274 case PresetCtpChangeId::PRESET_DELETED:
275 case PresetCtpChangeId::PRESET_AVAILABLE:
276 case PresetCtpChangeId::PRESET_UNAVAILABLE:
277 out << ", \"index\": " << +ntf.index;
278 break;
279 default:
280 break;
281 }
282 }
283 out << "}";
284
285 return out;
286 }
287
288 } // namespace has
289 } // namespace bluetooth::le_audio
290