1 #include "bej_decoder_json.hpp"
2
3 #include <string.h>
4
5 #define MAX_BEJ_STRING_LEN 65536
6
7 namespace libbej
8 {
9
10 /**
11 * @brief This structure is used to pass additional data to callback functions.
12 */
13 struct BejJsonParam
14 {
15 bool* isPrevAnnotated;
16 std::string* output;
17 };
18
19 /**
20 * @brief Add a property name to output buffer.
21 *
22 * @param[in] params - a valid BejJsonParam struct.
23 * @param[in] propertyName - a NULL terminated string.
24 */
addPropertyNameToOutput(struct BejJsonParam * params,const char * propertyName)25 static void addPropertyNameToOutput(struct BejJsonParam* params,
26 const char* propertyName)
27 {
28 if (propertyName[0] == '\0')
29 {
30 return;
31 }
32 if (!(*params->isPrevAnnotated))
33 {
34 params->output->push_back('\"');
35 }
36 params->output->append(propertyName);
37 params->output->append("\":");
38 }
39
40 /**
41 * @brief Callback for bejSet start.
42 *
43 * @param[in] propertyName - a NULL terminated string.
44 * @param[in] dataPtr - pointing to a valid BejJsonParam struct.
45 * @return 0 if successful.
46 */
callbackSetStart(const char * propertyName,void * dataPtr)47 static int callbackSetStart(const char* propertyName, void* dataPtr)
48 {
49 struct BejJsonParam* params =
50 reinterpret_cast<struct BejJsonParam*>(dataPtr);
51 addPropertyNameToOutput(params, propertyName);
52 params->output->push_back('{');
53 *params->isPrevAnnotated = false;
54 return 0;
55 }
56
57 /**
58 * @brief Callback for bejSet end.
59 *
60 * @param[in] dataPtr - pointing to a valid BejJsonParam struct.
61 * @return 0 if successful.
62 */
callbackSetEnd(void * dataPtr)63 static int callbackSetEnd(void* dataPtr)
64 {
65 struct BejJsonParam* params =
66 reinterpret_cast<struct BejJsonParam*>(dataPtr);
67 params->output->push_back('}');
68 return 0;
69 }
70
71 /**
72 * @brief Callback for bejArray start.
73 *
74 * @param[in] propertyName - a NULL terminated string.
75 * @param[in] dataPtr - pointing to a valid BejJsonParam struct.
76 * @return 0 if successful.
77 */
callbackArrayStart(const char * propertyName,void * dataPtr)78 static int callbackArrayStart(const char* propertyName, void* dataPtr)
79 {
80 struct BejJsonParam* params =
81 reinterpret_cast<struct BejJsonParam*>(dataPtr);
82 addPropertyNameToOutput(params, propertyName);
83 params->output->push_back('[');
84 *params->isPrevAnnotated = false;
85 return 0;
86 }
87
88 /**
89 * @brief Callback for bejArray end.
90 *
91 * @param[in] dataPtr - pointing to a valid BejJsonParam struct.
92 * @return 0 if successful.
93 */
callbackArrayEnd(void * dataPtr)94 static int callbackArrayEnd(void* dataPtr)
95 {
96 struct BejJsonParam* params =
97 reinterpret_cast<struct BejJsonParam*>(dataPtr);
98 params->output->push_back(']');
99 return 0;
100 }
101
102 /**
103 * @brief Callback when an end of a property is detected.
104 *
105 * @param[in] dataPtr - pointing to a valid BejJsonParam struct.
106 * @return 0 if successful.
107 */
callbackPropertyEnd(void * dataPtr)108 static int callbackPropertyEnd(void* dataPtr)
109 {
110 struct BejJsonParam* params =
111 reinterpret_cast<struct BejJsonParam*>(dataPtr);
112 // Not a section ending. So add a comma.
113 params->output->push_back(',');
114 return 0;
115 }
116
117 /**
118 * @brief Callback for bejNull type.
119 *
120 * @param[in] propertyName - a NULL terminated string.
121 * @param[in] dataPtr - pointing to a valid BejJsonParam struct.
122 * @return 0 if successful.
123 */
callbackNull(const char * propertyName,void * dataPtr)124 static int callbackNull(const char* propertyName, void* dataPtr)
125 {
126 struct BejJsonParam* params =
127 reinterpret_cast<struct BejJsonParam*>(dataPtr);
128 addPropertyNameToOutput(params, propertyName);
129 params->output->append("null");
130 *params->isPrevAnnotated = false;
131 return 0;
132 }
133
134 /**
135 * @brief Callback for bejInteger type.
136 *
137 * @param[in] propertyName - a NULL terminated string.
138 * @param[in] value - integer value.
139 * @param[in] dataPtr - pointing to a valid BejJsonParam struct.
140 * @return 0 if successful.
141 */
callbackInteger(const char * propertyName,int64_t value,void * dataPtr)142 static int callbackInteger(const char* propertyName, int64_t value,
143 void* dataPtr)
144 {
145 struct BejJsonParam* params =
146 reinterpret_cast<struct BejJsonParam*>(dataPtr);
147 addPropertyNameToOutput(params, propertyName);
148 params->output->append(std::to_string(value));
149 *params->isPrevAnnotated = false;
150 return 0;
151 }
152
153 /**
154 * @brief Callback for bejEnum type.
155 *
156 * @param[in] propertyName - a NULL terminated string.
157 * @param[in] value - a NULL terminated string.
158 * @param[in] dataPtr - pointing to a valid BejJsonParam struct.
159 * @return 0 if successful.
160 */
callbackEnum(const char * propertyName,const char * value,void * dataPtr)161 static int callbackEnum(const char* propertyName, const char* value,
162 void* dataPtr)
163 {
164 struct BejJsonParam* params =
165 reinterpret_cast<struct BejJsonParam*>(dataPtr);
166 addPropertyNameToOutput(params, propertyName);
167 params->output->push_back('\"');
168 params->output->append(value);
169 params->output->push_back('\"');
170 *params->isPrevAnnotated = false;
171 return 0;
172 }
173
174 /**
175 * @brief Callback for bejString type.
176 *
177 * @param[in] propertyName - a NULL terminated string.
178 * @param[in] value - a NULL terminated string.
179 * @param[in] length - length of the string.
180 * @param[in] dataPtr - pointing to a valid BejJsonParam struct.
181 * @return 0 if successful.
182 */
callbackString(const char * propertyName,const char * value,size_t length,void * dataPtr)183 static int callbackString(const char* propertyName, const char* value,
184 size_t length, void* dataPtr)
185 {
186 if ((length > MAX_BEJ_STRING_LEN) ||
187 (strnlen(value, length) != (length - 1)))
188 {
189 fprintf(stderr,
190 "Incorrect BEJ string length %zu or it exceeds maximum %u.\n",
191 (length - 1), MAX_BEJ_STRING_LEN);
192 return bejErrorInvalidSize;
193 }
194 struct BejJsonParam* params =
195 reinterpret_cast<struct BejJsonParam*>(dataPtr);
196 addPropertyNameToOutput(params, propertyName);
197 params->output->push_back('\"');
198 if (length > 0)
199 {
200 params->output->append(value, length - 1);
201 }
202 params->output->push_back('\"');
203 *params->isPrevAnnotated = false;
204 return 0;
205 }
206
207 /**
208 * @brief Callback for bejReal type.
209 *
210 * @param[in] propertyName - a NULL terminated string.
211 * @param[in] value - pointing to a valid BejReal.
212 * @param[in] dataPtr - pointing to a valid BejJsonParam struct.
213 * @return 0 if successful.
214 */
callbackReal(const char * propertyName,const struct BejReal * value,void * dataPtr)215 static int callbackReal(const char* propertyName, const struct BejReal* value,
216 void* dataPtr)
217 {
218 struct BejJsonParam* params =
219 reinterpret_cast<struct BejJsonParam*>(dataPtr);
220
221 // Sanity check for zeroCount
222 if (value->zeroCount > 100)
223 {
224 return bejErrorInvalidSize;
225 }
226
227 addPropertyNameToOutput(params, propertyName);
228 params->output->append(std::to_string(value->whole));
229 params->output->push_back('.');
230 params->output->insert(params->output->cend(), value->zeroCount, '0');
231 params->output->append(std::to_string(value->fract));
232 if (value->expLen != 0)
233 {
234 params->output->push_back('e');
235 params->output->append(std::to_string(value->exp));
236 }
237 *params->isPrevAnnotated = false;
238 return 0;
239 }
240
241 /**
242 * @brief Callback for bejBoolean type.
243 *
244 * @param[in] propertyName - a NULL terminated string.
245 * @param[in] value - boolean value.
246 * @param[in] dataPtr - pointing to a valid BejJsonParam struct.
247 * @return 0 if successful.
248 */
callbackBool(const char * propertyName,bool value,void * dataPtr)249 static int callbackBool(const char* propertyName, bool value, void* dataPtr)
250 {
251 struct BejJsonParam* params =
252 reinterpret_cast<struct BejJsonParam*>(dataPtr);
253 addPropertyNameToOutput(params, propertyName);
254 params->output->append(value ? "true" : "false");
255 *params->isPrevAnnotated = false;
256 return 0;
257 }
258
259 /**
260 * @brief Callback for bejPropertyAnnotation type.
261 *
262 * @param[in] propertyName - a NULL terminated string.
263 * @param[in] dataPtr - pointing to a valid BejJsonParam struct.
264 * @return 0 if successful.
265 */
callbackAnnotation(const char * propertyName,void * dataPtr)266 static int callbackAnnotation(const char* propertyName, void* dataPtr)
267 {
268 struct BejJsonParam* params =
269 reinterpret_cast<struct BejJsonParam*>(dataPtr);
270 params->output->push_back('\"');
271 params->output->append(propertyName);
272
273 // bejPropertyAnnotation type has the form "Status@Message.ExtendedInfo".
274 // First the decoder will see "Status" part of the annotated property. This
275 // will be in its own SFLV tuple. The remainder of the property name,
276 // @Message.ExtendedInfo will be contained in the next bej SFLV tuple.
277 // Therefore to add the inverted commas to the complete property name,
278 // Status@Message.ExtendedInfo, we need to know that the previous property
279 // we processed is a start to an annotation property. We can use
280 // isPrevAnnotated to pass this information.
281 // Here we are adding: "propertyName
282 // If isPrevAnnotated is true, next property should add: propertyNameNext"
283 *params->isPrevAnnotated = true;
284 return 0;
285 }
286
287 /**
288 * @brief Callback for stackEmpty.
289 *
290 * @param[in] dataPtr - pointer to a valid std::vector<BejStackProperty>
291 * @return true if the stack is empty.
292 */
stackEmpty(void * dataPtr)293 static bool stackEmpty(void* dataPtr)
294 {
295 std::vector<BejStackProperty>* stack =
296 reinterpret_cast<std::vector<BejStackProperty>*>(dataPtr);
297 return stack->empty();
298 }
299
300 /**
301 * @brief Callback for stackPeek.
302 *
303 * @param[in] dataPtr - pointer to a valid std::vector<BejStackProperty>
304 * @return a const reference to the stack top.
305 */
stackPeek(void * dataPtr)306 static const struct BejStackProperty* stackPeek(void* dataPtr)
307 {
308 std::vector<BejStackProperty>* stack =
309 reinterpret_cast<std::vector<BejStackProperty>*>(dataPtr);
310 if (stack->empty())
311 {
312 return nullptr;
313 }
314 return &(stack->back());
315 }
316
317 /**
318 * @brief Callback for stackPop. Remove the top element from the stack.
319 *
320 * @param[in] dataPtr - pointer to a valid std::vector<BejStackProperty>
321 */
stackPop(void * dataPtr)322 static void stackPop(void* dataPtr)
323 {
324 std::vector<BejStackProperty>* stack =
325 reinterpret_cast<std::vector<BejStackProperty>*>(dataPtr);
326 if (stack->empty())
327 {
328 return;
329 }
330 stack->pop_back();
331 }
332
333 /**
334 * @brief Callback for stackPush. Push a new element to the top of the stack.
335 *
336 * @param[in] property - property to push.
337 * @param[in] dataPtr - pointer to a valid std::vector<BejStackProperty>
338 * @return 0 if successful.
339 */
stackPush(const struct BejStackProperty * const property,void * dataPtr)340 static int stackPush(const struct BejStackProperty* const property,
341 void* dataPtr)
342 {
343 std::vector<BejStackProperty>* stack =
344 reinterpret_cast<std::vector<BejStackProperty>*>(dataPtr);
345 stack->push_back(*property);
346 return 0;
347 }
348
decode(const BejDictionaries & dictionaries,const std::span<const uint8_t> encodedPldmBlock)349 int BejDecoderJson::decode(const BejDictionaries& dictionaries,
350 const std::span<const uint8_t> encodedPldmBlock)
351 {
352 // Clear the previous output if any.
353 output.clear();
354
355 // The dictionaries have to be traversed in a depth first manner. This is
356 // using a stack to implement it non-recursively. Going into a set or an
357 // array or a property annotation section means that we have to jump to the
358 // child dictionary offset start point but needs to retrieve the parent
359 // dictionary offset start once all the children are processed. This stack
360 // will hold the parent dictionary offsets and endings for each section.
361 stack.clear();
362
363 struct BejStackCallback stackCallback = {
364 .stackEmpty = stackEmpty,
365 .stackPeek = stackPeek,
366 .stackPop = stackPop,
367 .stackPush = stackPush,
368 };
369
370 struct BejDecodedCallback decodedCallback = {
371 .callbackSetStart = callbackSetStart,
372 .callbackSetEnd = callbackSetEnd,
373 .callbackArrayStart = callbackArrayStart,
374 .callbackArrayEnd = callbackArrayEnd,
375 .callbackPropertyEnd = callbackPropertyEnd,
376 .callbackNull = callbackNull,
377 .callbackInteger = callbackInteger,
378 .callbackEnum = callbackEnum,
379 .callbackString = callbackString,
380 .callbackReal = callbackReal,
381 .callbackBool = callbackBool,
382 .callbackAnnotation = callbackAnnotation,
383 .callbackReadonlyProperty = nullptr,
384 };
385
386 isPrevAnnotated = false;
387 struct BejJsonParam callbackData = {
388 .isPrevAnnotated = &isPrevAnnotated,
389 .output = &output,
390 };
391
392 return bejDecodePldmBlock(
393 &dictionaries, encodedPldmBlock.data(), encodedPldmBlock.size_bytes(),
394 &stackCallback, &decodedCallback, (void*)(&callbackData),
395 (void*)(&stack));
396 }
397
getOutput()398 std::string BejDecoderJson::getOutput()
399 {
400 return output;
401 }
402
403 } // namespace libbej
404