xref: /openbmc/libbej/src/bej_decoder_json.cpp (revision 485044b19c85e4c50df34fd508d49838c0f5ee67)
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