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/include/bej_decoder_core.h b/include/bej_decoder_core.h
index 2c7f066..a0c4686 100644
--- a/include/bej_decoder_core.h
+++ b/include/bej_decoder_core.h
@@ -103,7 +103,7 @@
/**
* @brief Calls when an Integer property is found.
*/
- int (*callbackInteger)(const char* propertyName, uint64_t value,
+ int (*callbackInteger)(const char* propertyName, int64_t value,
void* dataPtr);
/**
diff --git a/include/rde_common.h b/include/rde_common.h
index f377507..bc5498c 100644
--- a/include/rde_common.h
+++ b/include/rde_common.h
@@ -34,6 +34,7 @@
bejErrorUnknownProperty,
bejErrorInvalidSchemaType,
bejErrorInvalidPropertyOffset,
+ bejErrorNullParameter,
};
/**
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(¶ms));
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(¶ms));
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(¶ms, /*canBeEmpty=*/true));
+ RETURN_IF_IERROR(bejProcessEnding(¶ms, /*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.