Add basic template code for BEJ decoder core

This describes the interfaces we are using for the BEJ decoding.

I did not include data type decoding code here to keep the code size
smaller for this review. I will be adding different data type decoding
in the comming CLs.

I will submit unit tests for review once all the necessary data type
decoding changes are submitted.

Signed-off-by: Kasun Athukorala <kasunath@google.com>
Change-Id: Icbc5c7780da21ba76ae0eaa56ad84dd10360cfc4
diff --git a/include/bej_decoder_core.h b/include/bej_decoder_core.h
new file mode 100644
index 0000000..2c7f066
--- /dev/null
+++ b/include/bej_decoder_core.h
@@ -0,0 +1,223 @@
+#pragma once
+
+#include "rde_common.h"
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+    /**
+     * @brief Indicates whether a new BEJ section falls inside a BEJ array or a
+     * BEJ set or none of those.
+     */
+    enum BejSectionType
+    {
+        bejSectionNoType,
+        bejSectionSet,
+        bejSectionArray,
+    };
+
+    /**
+     * @brief These stack entries are needed to implement the decoding
+     * non-recursively.
+     */
+    struct BejStackProperty
+    {
+        // Indicates whether we are inside an array or a set or an annotation.
+        enum BejSectionType sectionType;
+        // Indicate whether we have property names for properties.
+        bool addPropertyName;
+        // Offset to the parent property in schema dictionary.
+        uint16_t mainDictPropOffset;
+        // Offset to the parent property in annotation dictionary.
+        uint16_t annoDictPropOffset;
+        // Offset to the end of the array or set or annotation.
+        uint32_t streamEndOffset;
+    };
+
+    struct BejDictionaries
+    {
+        const uint8_t* schemaDictionary;
+        const uint8_t* annotationDictionary;
+        const uint8_t* errorDictionary;
+    };
+
+    /**
+     * @brief Holds the information related to the current bejTuple being
+     * decoded.
+     */
+    struct BejDecoderStates
+    {
+        bool addPropertyName;
+        uint16_t mainDictPropOffset;
+        uint16_t annoDictPropOffset;
+        uint32_t encodedStreamOffset;
+        const uint8_t* encodedSubStream;
+    };
+
+    /**
+     * @brief Callbacks for decoded data.
+     *
+     * dataPtr in the callback functions can be used for extra arguments.
+     */
+    struct BejDecodedCallback
+    {
+        /**
+         * @brief Calls when a Set is detected.
+         */
+        int (*callbackSetStart)(const char* propertyName, void* dataPtr);
+
+        /**
+         * @brief Calls when an end of a Set is found.
+         */
+        int (*callbackSetEnd)(void* dataPtr);
+
+        /**
+         * @brief Calls when an array is detected.
+         */
+        int (*callbackArrayStart)(const char* propertyName, void* dataPtr);
+
+        /**
+         * @brief Calls when an end of an array is found.
+         */
+        int (*callbackArrayEnd)(void* dataPtr);
+
+        /**
+         * @brief Calls after a property is finished unless this is the last
+         * property in a Set or an array. In that case appropriate
+         * callbackSetEnd or callbackArrayEnd will be called.
+         */
+        int (*callbackPropertyEnd)(void* dataPtr);
+
+        /**
+         * @brief Calls when a Null property is found or the property length is
+         * 0.
+         */
+        int (*callbackNull)(const char* propertyName, void* dataPtr);
+
+        /**
+         * @brief Calls when an Integer property is found.
+         */
+        int (*callbackInteger)(const char* propertyName, uint64_t value,
+                               void* dataPtr);
+
+        /**
+         * @brief Calls when an Enum property is found.
+         */
+        int (*callbackEnum)(const char* propertyName, const char* value,
+                            void* dataPtr);
+
+        /**
+         * @brief Calls when a String property is found.
+         */
+        int (*callbackString)(const char* propertyName, const char* value,
+                              void* dataPtr);
+
+        /**
+         * @brief Calls when a Real value property is found.
+         */
+        int (*callbackReal)(const char* propertyName,
+                            const struct BejReal* value, void* dataPtr);
+
+        /**
+         * @brief Calls when a Bool property is found.
+         */
+        int (*callbackBool)(const char* propertyName, bool value,
+                            void* dataPtr);
+
+        /**
+         * @brief Calls when an Annotated property is found.
+         */
+        int (*callbackAnnotation)(const char* propertyName, void* dataPtr);
+
+        /**
+         * @brief Calls when a read only property is found.
+         */
+        int (*callbackReadonlyProperty)(uint32_t sequenceNumber, void* dataPtr);
+    };
+
+    /**
+     * @brief Stack for holding BejStackProperty types. Decoder core is not
+     * responsible for creating or deleting stack memory. User of the decoder
+     * core is responsible for creating and deleting stack memory.
+     *
+     * dataPtr in the callback functions can be used for extra arguments.
+     */
+    struct BejStackCallback
+    {
+        /**
+         * @brief Return true if the stack is empty.
+         */
+        bool (*stackEmpty)(void* dataPtr);
+
+        /**
+         * @brief View the object at the top of the stack. If the stack is
+         * empty, this will return NULL.
+         */
+        const struct BejStackProperty* (*stackPeek)(void* dataPtr);
+
+        /**
+         * @brief Removes the top most object from the stack. Client of the
+         * decoder core is responsible for destroying the memory for the removed
+         * object.
+         */
+        void (*stackPop)(void* dataPtr);
+
+        /**
+         * @brief Push an object into the stack. Returns 0 if the operation is
+         * successfull. Client of the decoder core is responsible for allocating
+         * memory for the new object.
+         */
+        int (*stackPush)(const struct BejStackProperty* const property,
+                         void* dataPtr);
+    };
+
+    /**
+     * @brief Used to pass parameters to BEJ decoding local functions.
+     */
+    struct BejHandleTypeFuncParam
+    {
+        struct BejDecoderStates state;
+        struct BejSFLV sflv;
+        const uint8_t* mainDictionary;
+        const uint8_t* annotDictionary;
+        const struct BejDecodedCallback* decodedCallback;
+        const struct BejStackCallback* stackCallback;
+        void* callbacksDataPtr;
+        void* stackDataPtr;
+    };
+
+    /**
+     * @brief Decodes a PLDM block. Maximum encoded stream size the decoder
+     * supports is 32bits.
+     *
+     * @param[in] dictionaries - dictionaries needed for decoding.
+     * @param[in] encodedPldmBlock - encoded PLDM block.
+     * @param[in] blockLength - length of the PLDM block.
+     * @param[in] stackCallback - callbacks for stack handlers. callbacks in
+     * stackCallback struct should be set to valid functions.
+     * @param[in] decodedCallback - callbacks for extracting decoded
+     * properties. callbacks in decodedCallback struct should be set to
+     * NULL or valid functions.
+     * @param[in] callbacksDataPtr - data pointer to pass to decoded callbacks.
+     * This can be used pass additional data.
+     * @param[in] stackDataPtr - data pointer to pass to stack callbacks. This
+     * can be used pass additional data.
+     *
+     * @return 0 if successful.
+     */
+    int bejDecodePldmBlock(const struct BejDictionaries* dictionaries,
+                           const uint8_t* encodedPldmBlock,
+                           uint32_t blockLength,
+                           const struct BejStackCallback* stackCallback,
+                           const struct BejDecodedCallback* decodedCallback,
+                           void* callbacksDataPtr, void* stackDataPtr);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/src/bej_decoder_core.c b/src/bej_decoder_core.c
new file mode 100644
index 0000000..ab1f6d6
--- /dev/null
+++ b/src/bej_decoder_core.c
@@ -0,0 +1,309 @@
+#include "bej_decoder_core.h"
+
+#include "bej_dictionary.h"
+#include "stdio.h"
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+// TODO: Support nested annotations for version 0xF1F1F000
+const uint32_t supportedBejVersions[] = {0xF1F0F000};
+
+/**
+ * @brief Call a callback function. If the callback function is NULL, this will
+ * not do anything. If the callback function returns a non-zero value, this will
+ * cause the caller to return with the non-zero status.
+ */
+#define RETURN_IF_CALLBACK_IERROR(function, ...)                               \
+    do                                                                         \
+    {                                                                          \
+        if ((function) != NULL)                                                \
+        {                                                                      \
+            int __status = ((function)(__VA_ARGS__));                          \
+            if (__status != 0)                                                 \
+            {                                                                  \
+                return __status;                                               \
+            }                                                                  \
+        }                                                                      \
+    } while (0)
+
+/**
+ * @brief Get offsets of SFLV fields with respect to the enSegment start.
+ *
+ * @param[in] enSegment - a valid pointer to a start of a SFLV bejTuple.
+ * @param[out] offsets - this will hold the local offsets.
+ */
+static void bejGetLocalBejSFLVOffsets(const uint8_t* enSegment,
+                                      struct BejSFLVOffset* offsets)
+{
+    // Structure of the SFLV.
+    //   [Number of bytes need to represent the sequence number] - uint8_t
+    //   [SequenceNumber] - multi byte
+    //   [Format] - uint8_t
+    //   [Number of bytes need to represent the value length] - uint8_t
+    //   [Value length] - multi byte
+
+    // Number of bytes need to represent the sequence number.
+    const uint8_t seqSize = *enSegment;
+    // Start of format.
+    const uint32_t formatOffset = sizeof(uint8_t) + seqSize;
+    // Start of length of the value-length bytes.
+    const uint32_t valueLenNnintOffset = formatOffset + sizeof(uint8_t);
+    // Number of bytes need to represent the value length.
+    const uint8_t valueLengthSize = *(enSegment + valueLenNnintOffset);
+    // Start of the Value.
+    const uint32_t valueOffset =
+        valueLenNnintOffset + sizeof(uint8_t) + valueLengthSize;
+
+    offsets->formatOffset = formatOffset;
+    offsets->valueLenNnintOffset = valueLenNnintOffset;
+    offsets->valueOffset = valueOffset;
+}
+
+/**
+ * @brief Initialize sflv struct in params struct.
+ *
+ * @param[inout] params - a valid BejHandleTypeFuncParam struct with
+ * params->state.encodedSubStream pointing to the start of the encoded stream
+ * and params->state.encodedStreamOffset pointing to the current bejTuple.
+ */
+static void bejInitSFLVStruct(struct BejHandleTypeFuncParam* params)
+{
+    struct BejSFLVOffset localOffset;
+    // Get offsets of different SFLV fields with respect to start of the encoded
+    // segment.
+    bejGetLocalBejSFLVOffsets(params->state.encodedSubStream, &localOffset);
+    struct BejSFLV* sflv = &params->sflv;
+    const uint32_t valueLength = (uint32_t)(rdeGetNnint(
+        params->state.encodedSubStream + localOffset.valueLenNnintOffset));
+    // Sequence number itself should be 16bits. Using 32bits for
+    // [sequence_number + schema_type].
+    uint32_t tupleS = (uint32_t)(rdeGetNnint(params->state.encodedSubStream));
+    sflv->tupleS.schema = (uint8_t)(tupleS & DICTIONARY_TYPE_MASK);
+    sflv->tupleS.sequenceNumber =
+        (uint16_t)((tupleS & (~DICTIONARY_TYPE_MASK)) >>
+                   DICTIONARY_SEQ_NUM_SHIFT);
+    sflv->format = *(struct BejTupleF*)(params->state.encodedSubStream +
+                                        localOffset.formatOffset);
+    sflv->valueLength = valueLength;
+    sflv->valueEndOffset = params->state.encodedStreamOffset +
+                           localOffset.valueOffset + valueLength;
+    sflv->value = params->state.encodedSubStream + localOffset.valueOffset;
+}
+
+/**
+ * @brief Decodes an encoded bej stream.
+ *
+ * @param[in] schemaDictionary - main schema dictionary to use.
+ * @param[in] annotationDictionary - annotation dictionary
+ * @param[in] enStream - encoded stream without the PLDM header.
+ * @param[in] streamLen - length of the enStream.
+ * @param[in] stackCallback - callbacks for stack handlers.
+ * @param[in] decodedCallback - callbacks for extracting decoded properties.
+ * @param[in] callbacksDataPtr - data pointer to pass to decoded callbacks. This
+ * can be used pass additional data.
+ * @param[in] stackDataPtr - data pointer to pass to stack callbacks. This can
+ * be used pass additional data.
+ *
+ * @return 0 if successful.
+ */
+static int bejDecode(const uint8_t* schemaDictionary,
+                     const uint8_t* annotationDictionary,
+                     const uint8_t* enStream, uint32_t streamLen,
+                     const struct BejStackCallback* stackCallback,
+                     const struct BejDecodedCallback* decodedCallback,
+                     void* callbacksDataPtr, void* stackDataPtr)
+{
+    struct BejHandleTypeFuncParam params = {
+        .state =
+            {
+                // We only add names of set properties. We don't use names for
+                // array
+                // properties. Here we are omitting the name of the root set.
+                .addPropertyName = false,
+                // At start, parent property from the main dictionary is the
+                // first property.
+                .mainDictPropOffset = bejDictGetPropertyHeadOffset(),
+                .annoDictPropOffset = bejDictGetFirstAnnotatedPropertyOffset(),
+                // Current location of the encoded segment we are processing.
+                .encodedStreamOffset = 0,
+                .encodedSubStream = enStream,
+            },
+        .mainDictionary = schemaDictionary,
+        .annotDictionary = annotationDictionary,
+        .decodedCallback = decodedCallback,
+        .stackCallback = stackCallback,
+        .callbacksDataPtr = callbacksDataPtr,
+        .stackDataPtr = stackDataPtr,
+    };
+
+    while (params.state.encodedStreamOffset < streamLen)
+    {
+        // Go to the next encoded segment in the encoded stream.
+        params.state.encodedSubStream =
+            enStream + params.state.encodedStreamOffset;
+        bejInitSFLVStruct(&params);
+
+        if (params.sflv.format.readOnlyProperty)
+        {
+            RETURN_IF_CALLBACK_IERROR(
+                params.decodedCallback->callbackReadonlyProperty,
+                params.sflv.tupleS.sequenceNumber, params.callbacksDataPtr);
+        }
+
+        // TODO: Handle nullable property types. These are indicated by
+        // params.sflv.format.nullableProperty
+        switch (params.sflv.format.principalDataType)
+        {
+            case bejSet:
+                // TODO: Add support for BejSet decoding.
+                fprintf(stderr, "No BejSet support\n");
+                params.state.encodedStreamOffset = params.sflv.valueEndOffset;
+                break;
+            case bejArray:
+                // TODO: Add support for BejArray decoding.
+                fprintf(stderr, "No BejArray support\n");
+                params.state.encodedStreamOffset = params.sflv.valueEndOffset;
+                break;
+            case bejNull:
+                // TODO: Add support for BejNull decoding.
+                fprintf(stderr, "No BejNull support\n");
+                params.state.encodedStreamOffset = params.sflv.valueEndOffset;
+                break;
+            case bejInteger:
+                // TODO: Add support for BejInteger decoding.
+                fprintf(stderr, "No BejInteger support\n");
+                params.state.encodedStreamOffset = params.sflv.valueEndOffset;
+                break;
+            case bejEnum:
+                // TODO: Add support for BejEnum decoding.
+                fprintf(stderr, "No BejEnum support\n");
+                params.state.encodedStreamOffset = params.sflv.valueEndOffset;
+                break;
+            case bejString:
+                // TODO: Add support for BejString decoding.
+                fprintf(stderr, "No BejString support\n");
+                params.state.encodedStreamOffset = params.sflv.valueEndOffset;
+                break;
+            case bejReal:
+                // TODO: Add support for BejReal decoding.
+                fprintf(stderr, "No BejReal support\n");
+                params.state.encodedStreamOffset = params.sflv.valueEndOffset;
+                break;
+            case bejBoolean:
+                // TODO: Add support for BejBoolean decoding.
+                fprintf(stderr, "No BejBoolean support\n");
+                params.state.encodedStreamOffset = params.sflv.valueEndOffset;
+                break;
+            case bejBytestring:
+                // TODO: Add support for BejBytestring decoding.
+                fprintf(stderr, "No BejBytestring support\n");
+                params.state.encodedStreamOffset = params.sflv.valueEndOffset;
+                break;
+            case bejChoice:
+                // TODO: Add support for BejChoice decoding.
+                fprintf(stderr, "No BejChoice support\n");
+                params.state.encodedStreamOffset = params.sflv.valueEndOffset;
+                break;
+            case bejPropertyAnnotation:
+                // TODO: Add support for BejPropertyAnnotation decoding.
+                fprintf(stderr, "No BejPropertyAnnotation support\n");
+                params.state.encodedStreamOffset = params.sflv.valueEndOffset;
+                break;
+            case bejResourceLink:
+                // TODO: Add support for BejResourceLink decoding.
+                fprintf(stderr, "No BejResourceLink support\n");
+                params.state.encodedStreamOffset = params.sflv.valueEndOffset;
+                break;
+            case bejResourceLinkExpansion:
+                // TODO: Add support for BejResourceLinkExpansion decoding.
+                fprintf(stderr, "No BejResourceLinkExpansion support\n");
+                params.state.encodedStreamOffset = params.sflv.valueEndOffset;
+                break;
+            default:
+                break;
+        }
+    }
+    // TODO: Enable this once we are handling different data types.
+    // RETURN_IF_IERROR(bejProcessEnding(&params, /*canBeEmpty=*/true));
+    if (!params.stackCallback->stackEmpty(params.stackDataPtr))
+    {
+        fprintf(stderr, "Ending stack should be empty but its not. Something "
+                        "must have gone wrong with the encoding\n");
+        return bejErrorUnknown;
+    }
+    return 0;
+}
+
+/**
+ * @brief Check if a bej version is supported by this decoder
+ *
+ * @param bejVersion[in] - the bej version in the received encoded stream
+ * @return true if supported.
+ */
+static bool bejIsSupported(uint32_t bejVersion)
+{
+    for (uint32_t i = 0; i < sizeof(supportedBejVersions) / sizeof(uint32_t);
+         ++i)
+    {
+        if (bejVersion == supportedBejVersions[i])
+        {
+            return true;
+        }
+    }
+    return false;
+}
+
+int bejDecodePldmBlock(const struct BejDictionaries* dictionaries,
+                       const uint8_t* encodedPldmBlock, uint32_t blockLength,
+                       const struct BejStackCallback* stackCallback,
+                       const struct BejDecodedCallback* decodedCallback,
+                       void* callbacksDataPtr, void* stackDataPtr)
+{
+    uint32_t pldmHeaderSize = sizeof(struct BejPldmBlockHeader);
+    if (blockLength < pldmHeaderSize)
+    {
+        fprintf(stderr, "Invalid pldm block size: %u\n", blockLength);
+        return bejErrorInvalidSize;
+    }
+
+    const struct BejPldmBlockHeader* pldmHeader =
+        (const struct BejPldmBlockHeader*)encodedPldmBlock;
+
+    if (!bejIsSupported(pldmHeader->bejVersion))
+    {
+        fprintf(stderr, "Bej decoder doesn't support the bej version: %u\n",
+                pldmHeader->bejVersion);
+        return bejErrorNotSuppoted;
+    }
+
+    if (pldmHeader->schemaClass == bejAnnotationSchemaClass)
+    {
+        fprintf(stderr,
+                "Encoder schema class cannot be BejAnnotationSchemaClass\n");
+        return bejErrorNotSuppoted;
+    }
+    // TODO: Add support for CollectionMemberType schema class.
+    if (pldmHeader->schemaClass == bejCollectionMemberTypeSchemaClass)
+    {
+        fprintf(stderr, "Decoder doesn't support "
+                        "BejCollectionMemberTypeSchemaClass yet.\n");
+        return bejErrorNotSuppoted;
+    }
+    // TODO: Add support for Error schema class.
+    if (pldmHeader->schemaClass == bejErrorSchemaClass)
+    {
+        fprintf(stderr, "Decoder doesn't support BejErrorSchemaClass yet.\n");
+        return bejErrorNotSuppoted;
+    }
+
+    // Skip the PLDM header.
+    const uint8_t* enStream = encodedPldmBlock + pldmHeaderSize;
+    uint32_t streamLen = blockLength - pldmHeaderSize;
+    return bejDecode(dictionaries->schemaDictionary,
+                     dictionaries->annotationDictionary, enStream, streamLen,
+                     stackCallback, decodedCallback, callbacksDataPtr,
+                     stackDataPtr);
+}
diff --git a/src/meson.build b/src/meson.build
index e8d9958..44ce812 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -1,5 +1,6 @@
 libbej_lib = static_library(
   'libbej',
+  'bej_decoder_core.c',
   'rde_common.c',
   'bej_dictionary.c',
   include_directories : libbej_incs,