Add BejSet and BejInteger decoding

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: I44b1b4598d96441f507df86147f5c27046236edc
diff --git a/src/bej_decoder_core.c b/src/bej_decoder_core.c
index ab1f6d6..c226a52 100644
--- a/src/bej_decoder_core.c
+++ b/src/bej_decoder_core.c
@@ -30,6 +30,42 @@
     } while (0)
 
 /**
+ * @brief Check a given varable is NULL. If it is NULL, this will return with
+ * bejErrorNullParameter. If the variable is not NULL, this will will not
+ * return.
+ */
+#define NULL_CHECK(param, structStr)                                           \
+    do                                                                         \
+    {                                                                          \
+        if ((param) == NULL)                                                   \
+        {                                                                      \
+            fprintf(stderr, "nullCheck: %s cannot be null\n", structStr);      \
+            return bejErrorNullParameter;                                      \
+        }                                                                      \
+    } while (0)
+
+/**
+ * @brief Get the integer value from BEJ byte stream.
+ *
+ * @param[in] bytes - valid pointer to a byte stream in little-endian format.
+ * @param[in] numOfBytes - number of bytes belongs to the value. Maximum value
+ * supported is 8 bytes.
+ * @return signed 64bit representation of the value.
+ */
+static int64_t bejGetIntegerValue(const uint8_t* bytes, uint8_t numOfBytes)
+{
+    if (numOfBytes == 0)
+    {
+        return 0;
+    }
+    uint64_t value = rdeGetUnsignedInteger(bytes, numOfBytes);
+    uint8_t bitsInVal = numOfBytes * 8;
+    // Since numOfBytes > 0, bitsInVal is non negative.
+    uint64_t mask = (uint64_t)1 << (uint8_t)(bitsInVal - 1);
+    return (value ^ mask) - mask;
+}
+
+/**
  * @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.
@@ -94,6 +130,284 @@
 }
 
 /**
+ * @brief Get the offset to the first tuple of a bejArray or bejSet.
+ *
+ * The first part of the value of a bejArray or a bejSet contains an nnint
+ * providing the number of elements/tuples. Offset is with respect to the start
+ * of the encoded stream.
+ *
+ * @param[in] params - a valid BejHandleTypeFuncParam struct.
+ * @return offset with respect to the start of the encoded stream.
+ */
+static uint32_t
+    bejGetFirstTupleOffset(const struct BejHandleTypeFuncParam* params)
+{
+    struct BejSFLVOffset localOffset;
+    // Get the offset of the value with respect to the current encoded segment
+    // being decoded.
+    bejGetLocalBejSFLVOffsets(params->state.encodedSubStream, &localOffset);
+    return params->state.encodedStreamOffset + localOffset.valueOffset +
+           rdeGetNnintSize(params->sflv.value);
+}
+
+/**
+ * @brief Get the correct property and the dictionary it belongs to.
+ *
+ * @param[in] params - a BejHandleTypeFuncParam struct pointing to valid
+ * dictionaries.
+ * @param[in] schemaType - indicate whether to use the annotation dictionary or
+ * the main schema dictionary.
+ * @param[in] sequenceNumber - sequence number to use for property search. Not
+ * using the params->sflv.tupleS.sequenceNumber from the provided params struct.
+ * @param[out] dictionary - if the function is successful, this will point to a
+ * valid dictionary to be used.
+ * @param[out] prop - if the function is successful, this will point to a valid
+ * property in a dictionary.
+ * @return 0 if successful.
+ */
+static int
+    bejGetDictionaryAndProperty(const struct BejHandleTypeFuncParam* params,
+                                uint8_t schemaType, uint32_t sequenceNumber,
+                                const uint8_t** dictionary,
+                                const struct BejDictionaryProperty** prop)
+{
+    uint16_t dictPropOffset;
+    // We need to pick the correct dictionary.
+    if (schemaType == bejPrimary)
+    {
+        *dictionary = params->mainDictionary;
+        dictPropOffset = params->state.mainDictPropOffset;
+    }
+    else if (schemaType == bejAnnotation)
+    {
+        *dictionary = params->annotDictionary;
+        dictPropOffset = params->state.annoDictPropOffset;
+    }
+    else
+    {
+        fprintf(stderr, "Failed to select a dictionary. schema type: %u\n",
+                schemaType);
+        return bejErrorInvalidSchemaType;
+    }
+
+    int ret =
+        bejDictGetProperty(*dictionary, dictPropOffset, sequenceNumber, prop);
+    if (ret != 0)
+    {
+        fprintf(stderr, "Failed to get dictionary property for offset: %u\n",
+                dictPropOffset);
+        return ret;
+    }
+    return 0;
+}
+
+/**
+ * @brief Find and return the property name of the current encoded segment.
+ *
+ * @param[in] params - a valid populated BejHandleTypeFuncParam.
+ * @return 0 if successful.
+ */
+static const char* bejFindPropName(struct BejHandleTypeFuncParam* params)
+{
+    const uint8_t* dictionary;
+    const struct BejDictionaryProperty* prop;
+    if (bejGetDictionaryAndProperty(params, params->sflv.tupleS.schema,
+                                    params->sflv.tupleS.sequenceNumber,
+                                    &dictionary, &prop) != 0)
+    {
+        return "";
+    }
+    return bejDictGetPropertyName(dictionary, prop->nameOffset,
+                                  prop->nameLength);
+}
+
+/**
+ * @brief Look for section endings.
+ *
+ * This figures out whether the current encoded segment marks a section
+ * ending. If so, this function will update the decoder state and pop the stack
+ * used to memorize endings. This function should be called after updating the
+ * encodedStreamOffset to the end of decoded SFLV tuple.
+ *
+ * @param[in] params - a valid BejHandleTypeFuncParam which contains the decoder
+ * state.
+ * @param[in] canBeEmpty - if true, the stack being empty is not an error. If
+ * false, stack cannot be empty.
+ * @return 0 if successful.
+ */
+static int bejProcessEnding(struct BejHandleTypeFuncParam* params,
+                            bool canBeEmpty)
+{
+    if (params->stackCallback->stackEmpty(params->stackDataPtr) && !canBeEmpty)
+    {
+        // If bejProcessEnding has been called after adding an appropriate JSON
+        // property, then stack cannot be empty.
+        fprintf(stderr, "Ending stack cannot be empty.\n");
+        return bejErrorUnknown;
+    }
+
+    while (!params->stackCallback->stackEmpty(params->stackDataPtr))
+    {
+        const struct BejStackProperty* const ending =
+            params->stackCallback->stackPeek(params->stackDataPtr);
+        // Check whether the current offset location matches the expected ending
+        // offset. If so, we are done with that section.
+        if (params->state.encodedStreamOffset == ending->streamEndOffset)
+        {
+            // Since we are going out of a section, we need to reset the
+            // dictionary property offsets to this section's parent property
+            // start.
+            params->state.mainDictPropOffset = ending->mainDictPropOffset;
+            params->state.annoDictPropOffset = ending->annoDictPropOffset;
+            params->state.addPropertyName = ending->addPropertyName;
+
+            if (ending->sectionType == bejSectionSet)
+            {
+                RETURN_IF_CALLBACK_IERROR(
+                    params->decodedCallback->callbackSetEnd,
+                    params->callbacksDataPtr);
+            }
+            else if (ending->sectionType == bejSectionArray)
+            {
+                RETURN_IF_CALLBACK_IERROR(
+                    params->decodedCallback->callbackArrayEnd,
+                    params->callbacksDataPtr);
+            }
+            params->stackCallback->stackPop(params->stackDataPtr);
+        }
+        else
+        {
+            RETURN_IF_CALLBACK_IERROR(
+                params->decodedCallback->callbackPropertyEnd,
+                params->callbacksDataPtr);
+            // Do not change the parent dictionary property offset since we are
+            // still inside the same section.
+            return 0;
+        }
+    }
+    return 0;
+}
+
+/**
+ * @brief Check whether the current encoded segment being decoded is an array
+ * element.
+ *
+ * @param[in] params - a valid BejHandleTypeFuncParam struct.
+ * @return true if the encoded segment is an array element. Else false.
+ */
+static bool bejIsArrayElement(const struct BejHandleTypeFuncParam* params)
+{
+    // If the encoded segment enters an array section, we are adding a
+    // BejSectionArray to the stack. Therefore if the stack is empty, encoded
+    // segment cannot be an array element.
+    if (params->stackCallback->stackEmpty(params->stackDataPtr))
+    {
+        return false;
+    }
+    const struct BejStackProperty* const ending =
+        params->stackCallback->stackPeek(params->stackDataPtr);
+    // If the stack top element holds a BejSectionArray, encoded segment is
+    // an array element.
+    return ending->sectionType == bejSectionArray;
+}
+
+/**
+ * @brief Decodes a BejSet type SFLV BEJ tuple.
+ *
+ * @param params - a valid BejHandleTypeFuncParam struct.
+ * @return 0 if successful.
+ */
+static int bejHandleBejSet(struct BejHandleTypeFuncParam* params)
+{
+    uint16_t sequenceNumber = params->sflv.tupleS.sequenceNumber;
+    // Check whether this BejSet is an array element or not.
+    if (bejIsArrayElement(params))
+    {
+        // Dictionary only contains an entry for element 0.
+        sequenceNumber = 0;
+    }
+    const uint8_t* dictionary;
+    const struct BejDictionaryProperty* prop;
+    RETURN_IF_IERROR(
+        bejGetDictionaryAndProperty(params, params->sflv.tupleS.schema,
+                                    sequenceNumber, &dictionary, &prop));
+
+    const char* propName = "";
+    if (params->state.addPropertyName)
+    {
+        propName = bejDictGetPropertyName(dictionary, prop->nameOffset,
+                                          prop->nameLength);
+    }
+
+    RETURN_IF_CALLBACK_IERROR(params->decodedCallback->callbackSetStart,
+                              propName, params->callbacksDataPtr);
+
+    uint64_t elements = rdeGetNnint(params->sflv.value);
+    // If its an empty set, we are done here.
+    if (elements == 0)
+    {
+        RETURN_IF_CALLBACK_IERROR(params->decodedCallback->callbackSetEnd,
+                                  params->callbacksDataPtr);
+    }
+    else
+    {
+        // Update the states for the next encoding segment.
+        struct BejStackProperty newEnding = {
+            .sectionType = bejSectionSet,
+            .addPropertyName = params->state.addPropertyName,
+            .mainDictPropOffset = params->state.mainDictPropOffset,
+            .annoDictPropOffset = params->state.annoDictPropOffset,
+            .streamEndOffset = params->sflv.valueEndOffset,
+        };
+        RETURN_IF_IERROR(
+            params->stackCallback->stackPush(&newEnding, params->stackDataPtr));
+        params->state.addPropertyName = true;
+        if (params->sflv.tupleS.schema == bejAnnotation)
+        {
+            // Since this set is an annotated type, we need to advance the
+            // annotation dictionary for decoding the next segment.
+            params->state.annoDictPropOffset = prop->childPointerOffset;
+        }
+        else
+        {
+            params->state.mainDictPropOffset = prop->childPointerOffset;
+        }
+    }
+    params->state.encodedStreamOffset = bejGetFirstTupleOffset(params);
+    return 0;
+}
+
+/**
+ * @brief Decodes a BejInteger type SFLV BEJ tuple.
+ *
+ * @param params - a valid BejHandleTypeFuncParam struct.
+ * @return 0 if successful.
+ */
+static int bejHandleBejInteger(struct BejHandleTypeFuncParam* params)
+{
+    const char* propName = "";
+    if (params->state.addPropertyName)
+    {
+        propName = bejFindPropName(params);
+    }
+
+    if (params->sflv.valueLength == 0)
+    {
+        RETURN_IF_CALLBACK_IERROR(params->decodedCallback->callbackNull,
+                                  propName, params->callbacksDataPtr);
+    }
+    else
+    {
+        RETURN_IF_CALLBACK_IERROR(
+            params->decodedCallback->callbackInteger, propName,
+            bejGetIntegerValue(params->sflv.value, params->sflv.valueLength),
+            params->callbacksDataPtr);
+    }
+    params->state.encodedStreamOffset = params->sflv.valueEndOffset;
+    return bejProcessEnding(params, /*canBeEmpty=*/false);
+}
+
+/**
  * @brief Decodes an encoded bej stream.
  *
  * @param[in] schemaDictionary - main schema dictionary to use.
@@ -158,9 +472,7 @@
         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;
+                RETURN_IF_IERROR(bejHandleBejSet(&params));
                 break;
             case bejArray:
                 // TODO: Add support for BejArray decoding.
@@ -173,9 +485,7 @@
                 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;
+                RETURN_IF_IERROR(bejHandleBejInteger(&params));
                 break;
             case bejEnum:
                 // TODO: Add support for BejEnum decoding.
@@ -226,8 +536,7 @@
                 break;
         }
     }
-    // TODO: Enable this once we are handling different data types.
-    // RETURN_IF_IERROR(bejProcessEnding(&params, /*canBeEmpty=*/true));
+    RETURN_IF_IERROR(bejProcessEnding(&params, /*canBeEmpty=*/true));
     if (!params.stackCallback->stackEmpty(params.stackDataPtr))
     {
         fprintf(stderr, "Ending stack should be empty but its not. Something "
@@ -262,6 +571,20 @@
                        const struct BejDecodedCallback* decodedCallback,
                        void* callbacksDataPtr, void* stackDataPtr)
 {
+    NULL_CHECK(dictionaries, "dictionaries");
+    NULL_CHECK(dictionaries->schemaDictionary, "schemaDictionary");
+    NULL_CHECK(dictionaries->annotationDictionary, "annotationDictionary");
+
+    NULL_CHECK(encodedPldmBlock, "encodedPldmBlock");
+
+    NULL_CHECK(stackCallback, "stackCallback");
+    NULL_CHECK(stackCallback->stackEmpty, "stackEmpty");
+    NULL_CHECK(stackCallback->stackPeek, "stackPeek");
+    NULL_CHECK(stackCallback->stackPop, "stackPop");
+    NULL_CHECK(stackCallback->stackPush, "stackPush");
+
+    NULL_CHECK(decodedCallback, "decodedCallback");
+
     uint32_t pldmHeaderSize = sizeof(struct BejPldmBlockHeader);
     if (blockLength < pldmHeaderSize)
     {
@@ -289,7 +612,7 @@
     if (pldmHeader->schemaClass == bejCollectionMemberTypeSchemaClass)
     {
         fprintf(stderr, "Decoder doesn't support "
-                        "BejCollectionMemberTypeSchemaClass yet.\n");
+                        "bejCollectionMemberTypeSchemaClass yet.\n");
         return bejErrorNotSuppoted;
     }
     // TODO: Add support for Error schema class.