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