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 = ¶ms->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(¶ms);
+
+ 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(¶ms, /*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,