blob: 95e241f854364b9fe2774eb4ee2333d2c17a48d7 [file] [log] [blame]
kasunathb81dcec2022-05-23 12:23:44 -07001#include "bej_decoder_json.hpp"
2
Kasun Athukorala485044b2025-07-15 00:59:36 +00003#include <string.h>
4
5#define MAX_BEJ_STRING_LEN 65536
6
kasunathb81dcec2022-05-23 12:23:44 -07007namespace libbej
8{
9
10/**
11 * @brief This structure is used to pass additional data to callback functions.
12 */
13struct 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 */
25static 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 */
47static 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 */
63static 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 */
78static 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 */
94static 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 */
108static 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 */
124static 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 */
142static 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 */
161static 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.
Kasun Athukorala485044b2025-07-15 00:59:36 +0000179 * @param[in] length - length of the string.
kasunathb81dcec2022-05-23 12:23:44 -0700180 * @param[in] dataPtr - pointing to a valid BejJsonParam struct.
181 * @return 0 if successful.
182 */
183static int callbackString(const char* propertyName, const char* value,
Kasun Athukorala485044b2025-07-15 00:59:36 +0000184 size_t length, void* dataPtr)
kasunathb81dcec2022-05-23 12:23:44 -0700185{
Kasun Athukorala485044b2025-07-15 00:59:36 +0000186 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 }
kasunathb81dcec2022-05-23 12:23:44 -0700194 struct BejJsonParam* params =
195 reinterpret_cast<struct BejJsonParam*>(dataPtr);
196 addPropertyNameToOutput(params, propertyName);
197 params->output->push_back('\"');
Kasun Athukorala485044b2025-07-15 00:59:36 +0000198 if (length > 0)
199 {
200 params->output->append(value, length - 1);
201 }
kasunathb81dcec2022-05-23 12:23:44 -0700202 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 */
215static int callbackReal(const char* propertyName, const struct BejReal* value,
216 void* dataPtr)
217{
218 struct BejJsonParam* params =
219 reinterpret_cast<struct BejJsonParam*>(dataPtr);
220
Brandon Kim9eb01172025-06-23 21:26:59 +0000221 // Sanity check for zeroCount
222 if (value->zeroCount > 100)
223 {
224 return bejErrorInvalidSize;
225 }
226
kasunathb81dcec2022-05-23 12:23:44 -0700227 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 */
249static 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 */
266static 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 */
293static 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 */
306static 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 */
322static 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 */
340static 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
349int 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
Patrick Williamsbe27f2e2024-08-16 15:22:35 -0400392 return bejDecodePldmBlock(
393 &dictionaries, encodedPldmBlock.data(), encodedPldmBlock.size_bytes(),
394 &stackCallback, &decodedCallback, (void*)(&callbackData),
395 (void*)(&stack));
kasunathb81dcec2022-05-23 12:23:44 -0700396}
397
398std::string BejDecoderJson::getOutput()
399{
400 return output;
401}
402
403} // namespace libbej