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