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