Add RDE BEJ to JSON decoder
Tested:
Tested this locally with unit tests. I will submit unit tests for
review in the next CL.
Signed-off-by: Kasun Athukorala <kasunath@google.com>
Change-Id: I6f3a6882c2dc022938a22a200b213216c6b89c64
diff --git a/src/bej_decoder_json.cpp b/src/bej_decoder_json.cpp
new file mode 100644
index 0000000..f6624c7
--- /dev/null
+++ b/src/bej_decoder_json.cpp
@@ -0,0 +1,381 @@
+#include "bej_decoder_json.hpp"
+
+namespace libbej
+{
+
+/**
+ * @brief This structure is used to pass additional data to callback functions.
+ */
+struct BejJsonParam
+{
+ bool* isPrevAnnotated;
+ std::string* output;
+};
+
+/**
+ * @brief Add a property name to output buffer.
+ *
+ * @param[in] params - a valid BejJsonParam struct.
+ * @param[in] propertyName - a NULL terminated string.
+ */
+static void addPropertyNameToOutput(struct BejJsonParam* params,
+ const char* propertyName)
+{
+ if (propertyName[0] == '\0')
+ {
+ return;
+ }
+ if (!(*params->isPrevAnnotated))
+ {
+ params->output->push_back('\"');
+ }
+ params->output->append(propertyName);
+ params->output->append("\":");
+}
+
+/**
+ * @brief Callback for bejSet start.
+ *
+ * @param[in] propertyName - a NULL terminated string.
+ * @param[in] dataPtr - pointing to a valid BejJsonParam struct.
+ * @return 0 if successful.
+ */
+static int callbackSetStart(const char* propertyName, void* dataPtr)
+{
+ struct BejJsonParam* params =
+ reinterpret_cast<struct BejJsonParam*>(dataPtr);
+ addPropertyNameToOutput(params, propertyName);
+ params->output->push_back('{');
+ *params->isPrevAnnotated = false;
+ return 0;
+}
+
+/**
+ * @brief Callback for bejSet end.
+ *
+ * @param[in] dataPtr - pointing to a valid BejJsonParam struct.
+ * @return 0 if successful.
+ */
+static int callbackSetEnd(void* dataPtr)
+{
+ struct BejJsonParam* params =
+ reinterpret_cast<struct BejJsonParam*>(dataPtr);
+ params->output->push_back('}');
+ return 0;
+}
+
+/**
+ * @brief Callback for bejArray start.
+ *
+ * @param[in] propertyName - a NULL terminated string.
+ * @param[in] dataPtr - pointing to a valid BejJsonParam struct.
+ * @return 0 if successful.
+ */
+static int callbackArrayStart(const char* propertyName, void* dataPtr)
+{
+ struct BejJsonParam* params =
+ reinterpret_cast<struct BejJsonParam*>(dataPtr);
+ addPropertyNameToOutput(params, propertyName);
+ params->output->push_back('[');
+ *params->isPrevAnnotated = false;
+ return 0;
+}
+
+/**
+ * @brief Callback for bejArray end.
+ *
+ * @param[in] dataPtr - pointing to a valid BejJsonParam struct.
+ * @return 0 if successful.
+ */
+static int callbackArrayEnd(void* dataPtr)
+{
+ struct BejJsonParam* params =
+ reinterpret_cast<struct BejJsonParam*>(dataPtr);
+ params->output->push_back(']');
+ return 0;
+}
+
+/**
+ * @brief Callback when an end of a property is detected.
+ *
+ * @param[in] dataPtr - pointing to a valid BejJsonParam struct.
+ * @return 0 if successful.
+ */
+static int callbackPropertyEnd(void* dataPtr)
+{
+ struct BejJsonParam* params =
+ reinterpret_cast<struct BejJsonParam*>(dataPtr);
+ // Not a section ending. So add a comma.
+ params->output->push_back(',');
+ return 0;
+}
+
+/**
+ * @brief Callback for bejNull type.
+ *
+ * @param[in] propertyName - a NULL terminated string.
+ * @param[in] dataPtr - pointing to a valid BejJsonParam struct.
+ * @return 0 if successful.
+ */
+static int callbackNull(const char* propertyName, void* dataPtr)
+{
+ struct BejJsonParam* params =
+ reinterpret_cast<struct BejJsonParam*>(dataPtr);
+ addPropertyNameToOutput(params, propertyName);
+ params->output->append("null");
+ *params->isPrevAnnotated = false;
+ return 0;
+}
+
+/**
+ * @brief Callback for bejInteger type.
+ *
+ * @param[in] propertyName - a NULL terminated string.
+ * @param[in] value - integer value.
+ * @param[in] dataPtr - pointing to a valid BejJsonParam struct.
+ * @return 0 if successful.
+ */
+static int callbackInteger(const char* propertyName, int64_t value,
+ void* dataPtr)
+{
+ struct BejJsonParam* params =
+ reinterpret_cast<struct BejJsonParam*>(dataPtr);
+ addPropertyNameToOutput(params, propertyName);
+ params->output->append(std::to_string(value));
+ *params->isPrevAnnotated = false;
+ return 0;
+}
+
+/**
+ * @brief Callback for bejEnum type.
+ *
+ * @param[in] propertyName - a NULL terminated string.
+ * @param[in] value - a NULL terminated string.
+ * @param[in] dataPtr - pointing to a valid BejJsonParam struct.
+ * @return 0 if successful.
+ */
+static int callbackEnum(const char* propertyName, const char* value,
+ void* dataPtr)
+{
+ struct BejJsonParam* params =
+ reinterpret_cast<struct BejJsonParam*>(dataPtr);
+ addPropertyNameToOutput(params, propertyName);
+ params->output->push_back('\"');
+ params->output->append(value);
+ params->output->push_back('\"');
+ *params->isPrevAnnotated = false;
+ return 0;
+}
+
+/**
+ * @brief Callback for bejString type.
+ *
+ * @param[in] propertyName - a NULL terminated string.
+ * @param[in] value - a NULL terminated string.
+ * @param[in] dataPtr - pointing to a valid BejJsonParam struct.
+ * @return 0 if successful.
+ */
+static int callbackString(const char* propertyName, const char* value,
+ void* dataPtr)
+{
+ struct BejJsonParam* params =
+ reinterpret_cast<struct BejJsonParam*>(dataPtr);
+ addPropertyNameToOutput(params, propertyName);
+ params->output->push_back('\"');
+ params->output->append(value);
+ params->output->push_back('\"');
+ *params->isPrevAnnotated = false;
+ return 0;
+}
+
+/**
+ * @brief Callback for bejReal type.
+ *
+ * @param[in] propertyName - a NULL terminated string.
+ * @param[in] value - pointing to a valid BejReal.
+ * @param[in] dataPtr - pointing to a valid BejJsonParam struct.
+ * @return 0 if successful.
+ */
+static int callbackReal(const char* propertyName, const struct BejReal* value,
+ void* dataPtr)
+{
+ struct BejJsonParam* params =
+ reinterpret_cast<struct BejJsonParam*>(dataPtr);
+
+ addPropertyNameToOutput(params, propertyName);
+ params->output->append(std::to_string(value->whole));
+ params->output->push_back('.');
+ params->output->insert(params->output->cend(), value->zeroCount, '0');
+ params->output->append(std::to_string(value->fract));
+ if (value->expLen != 0)
+ {
+ params->output->push_back('e');
+ params->output->append(std::to_string(value->exp));
+ }
+ *params->isPrevAnnotated = false;
+ return 0;
+}
+
+/**
+ * @brief Callback for bejBoolean type.
+ *
+ * @param[in] propertyName - a NULL terminated string.
+ * @param[in] value - boolean value.
+ * @param[in] dataPtr - pointing to a valid BejJsonParam struct.
+ * @return 0 if successful.
+ */
+static int callbackBool(const char* propertyName, bool value, void* dataPtr)
+{
+ struct BejJsonParam* params =
+ reinterpret_cast<struct BejJsonParam*>(dataPtr);
+ addPropertyNameToOutput(params, propertyName);
+ params->output->append(value ? "true" : "false");
+ *params->isPrevAnnotated = false;
+ return 0;
+}
+
+/**
+ * @brief Callback for bejPropertyAnnotation type.
+ *
+ * @param[in] propertyName - a NULL terminated string.
+ * @param[in] dataPtr - pointing to a valid BejJsonParam struct.
+ * @return 0 if successful.
+ */
+static int callbackAnnotation(const char* propertyName, void* dataPtr)
+{
+ struct BejJsonParam* params =
+ reinterpret_cast<struct BejJsonParam*>(dataPtr);
+ params->output->push_back('\"');
+ params->output->append(propertyName);
+
+ // bejPropertyAnnotation type has the form "Status@Message.ExtendedInfo".
+ // First the decoder will see "Status" part of the annotated property. This
+ // will be in its own SFLV tuple. The remainder of the property name,
+ // @Message.ExtendedInfo will be contained in the next bej SFLV tuple.
+ // Therefore to add the inverted commas to the complete property name,
+ // Status@Message.ExtendedInfo, we need to know that the previous property
+ // we processed is a start to an annotation property. We can use
+ // isPrevAnnotated to pass this information.
+ // Here we are adding: "propertyName
+ // If isPrevAnnotated is true, next property should add: propertyNameNext"
+ *params->isPrevAnnotated = true;
+ return 0;
+}
+
+/**
+ * @brief Callback for stackEmpty.
+ *
+ * @param[in] dataPtr - pointer to a valid std::vector<BejStackProperty>
+ * @return true if the stack is empty.
+ */
+static bool stackEmpty(void* dataPtr)
+{
+ std::vector<BejStackProperty>* stack =
+ reinterpret_cast<std::vector<BejStackProperty>*>(dataPtr);
+ return stack->empty();
+}
+
+/**
+ * @brief Callback for stackPeek.
+ *
+ * @param[in] dataPtr - pointer to a valid std::vector<BejStackProperty>
+ * @return a const reference to the stack top.
+ */
+static const struct BejStackProperty* stackPeek(void* dataPtr)
+{
+ std::vector<BejStackProperty>* stack =
+ reinterpret_cast<std::vector<BejStackProperty>*>(dataPtr);
+ if (stack->empty())
+ {
+ return nullptr;
+ }
+ return &(stack->back());
+}
+
+/**
+ * @brief Callback for stackPop. Remove the top element from the stack.
+ *
+ * @param[in] dataPtr - pointer to a valid std::vector<BejStackProperty>
+ */
+static void stackPop(void* dataPtr)
+{
+ std::vector<BejStackProperty>* stack =
+ reinterpret_cast<std::vector<BejStackProperty>*>(dataPtr);
+ if (stack->empty())
+ {
+ return;
+ }
+ stack->pop_back();
+}
+
+/**
+ * @brief Callback for stackPush. Push a new element to the top of the stack.
+ *
+ * @param[in] property - property to push.
+ * @param[in] dataPtr - pointer to a valid std::vector<BejStackProperty>
+ * @return 0 if successful.
+ */
+static int stackPush(const struct BejStackProperty* const property,
+ void* dataPtr)
+{
+ std::vector<BejStackProperty>* stack =
+ reinterpret_cast<std::vector<BejStackProperty>*>(dataPtr);
+ stack->push_back(*property);
+ return 0;
+}
+
+int BejDecoderJson::decode(const BejDictionaries& dictionaries,
+ const std::span<const uint8_t> encodedPldmBlock)
+{
+ // Clear the previous output if any.
+ output.clear();
+
+ // The dictionaries have to be traversed in a depth first manner. This is
+ // using a stack to implement it non-recursively. Going into a set or an
+ // array or a property annotation section means that we have to jump to the
+ // child dictionary offset start point but needs to retrieve the parent
+ // dictionary offset start once all the children are processed. This stack
+ // will hold the parent dictionary offsets and endings for each section.
+ stack.clear();
+
+ struct BejStackCallback stackCallback = {
+ .stackEmpty = stackEmpty,
+ .stackPeek = stackPeek,
+ .stackPop = stackPop,
+ .stackPush = stackPush,
+ };
+
+ struct BejDecodedCallback decodedCallback = {
+ .callbackSetStart = callbackSetStart,
+ .callbackSetEnd = callbackSetEnd,
+ .callbackArrayStart = callbackArrayStart,
+ .callbackArrayEnd = callbackArrayEnd,
+ .callbackPropertyEnd = callbackPropertyEnd,
+ .callbackNull = callbackNull,
+ .callbackInteger = callbackInteger,
+ .callbackEnum = callbackEnum,
+ .callbackString = callbackString,
+ .callbackReal = callbackReal,
+ .callbackBool = callbackBool,
+ .callbackAnnotation = callbackAnnotation,
+ .callbackReadonlyProperty = nullptr,
+ };
+
+ isPrevAnnotated = false;
+ struct BejJsonParam callbackData = {
+ .isPrevAnnotated = &isPrevAnnotated,
+ .output = &output,
+ };
+
+ return bejDecodePldmBlock(&dictionaries, encodedPldmBlock.data(),
+ encodedPldmBlock.size_bytes(), &stackCallback,
+ &decodedCallback, (void*)(&callbackData),
+ (void*)(&stack));
+}
+
+std::string BejDecoderJson::getOutput()
+{
+ return output;
+}
+
+} // namespace libbej