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