| #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 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 = bejGetUnsignedInteger(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 (int64_t)((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. |
| * @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)(bejGetNnint( |
| params->state.encodedSubStream + localOffset.valueLenNnintOffset)); |
| // Sequence number itself should be 16bits. Using 32bits for |
| // [sequence_number + schema_type]. |
| uint32_t tupleS = (uint32_t)(bejGetNnint(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 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 + |
| bejGetNnintSize(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. If |
| * the params->state.addPropertyName is false, this will return an empty string. |
| * |
| * @param[in] params - a valid populated BejHandleTypeFuncParam. |
| * @return 0 if successful. |
| */ |
| static const char* bejGetPropName(struct BejHandleTypeFuncParam* params) |
| { |
| const uint8_t* dictionary; |
| const struct BejDictionaryProperty* prop; |
| if (!params->state.addPropertyName || |
| (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[in] 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); |
| |
| // Move the offset to the next SFLV tuple (or end). Make sure that this is |
| // called before calling bejProcessEnding. |
| params->state.encodedStreamOffset = bejGetFirstTupleOffset(params); |
| |
| uint64_t elements = bejGetNnint(params->sflv.value); |
| // If its an empty set, we are done here. |
| if (elements == 0) |
| { |
| RETURN_IF_CALLBACK_IERROR(params->decodedCallback->callbackSetEnd, |
| params->callbacksDataPtr); |
| // Since this is an ending of a property (empty array), we should call |
| // bejProcessEnding. Unless the whole JSON object is an empty set (which |
| // shouldn't be the case), stack cannot be empty. |
| bejProcessEnding(params, /*canBeEmpty=*/false); |
| return 0; |
| } |
| |
| // 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; |
| } |
| return 0; |
| } |
| |
| /** |
| * @brief Decodes a BejArray type SFLV BEJ tuple. |
| * |
| * @param[in] params - a valid BejHandleTypeFuncParam struct. |
| * @return 0 if successful. |
| */ |
| static int bejHandleBejArray(struct BejHandleTypeFuncParam* params) |
| { |
| const uint8_t* dictionary; |
| const struct BejDictionaryProperty* prop; |
| RETURN_IF_IERROR(bejGetDictionaryAndProperty( |
| params, params->sflv.tupleS.schema, params->sflv.tupleS.sequenceNumber, |
| &dictionary, &prop)); |
| |
| const char* propName = ""; |
| if (params->state.addPropertyName) |
| { |
| propName = bejDictGetPropertyName(dictionary, prop->nameOffset, |
| prop->nameLength); |
| } |
| |
| RETURN_IF_CALLBACK_IERROR(params->decodedCallback->callbackArrayStart, |
| propName, params->callbacksDataPtr); |
| |
| // Move the offset to the next SFLV tuple (or end). Make sure that this is |
| // called before calling bejProcessEnding. |
| params->state.encodedStreamOffset = bejGetFirstTupleOffset(params); |
| |
| uint64_t elements = bejGetNnint(params->sflv.value); |
| // If its an empty array, we are done here. |
| if (elements == 0) |
| { |
| RETURN_IF_CALLBACK_IERROR(params->decodedCallback->callbackArrayEnd, |
| params->callbacksDataPtr); |
| // Since this is an ending of a property (empty array), we should call |
| // bejProcessEnding. Stack cannot be empty since there should be at |
| // least 1 parent in the stack. |
| bejProcessEnding(params, /*canBeEmpty=*/false); |
| return 0; |
| } |
| |
| // Update the state for next segment decoding. |
| struct BejStackProperty newEnding = { |
| .sectionType = bejSectionArray, |
| .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)); |
| // We do not add property names for array elements. |
| params->state.addPropertyName = false; |
| if (params->sflv.tupleS.schema == bejAnnotation) |
| { |
| // Since this array 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; |
| } |
| return 0; |
| } |
| |
| /** |
| * @brief Decodes a BejNull type SFLV BEJ tuple. |
| * |
| * @param[in] params - a valid BejHandleTypeFuncParam struct. |
| * @return 0 if successful. |
| */ |
| static int bejHandleBejNull(struct BejHandleTypeFuncParam* params) |
| { |
| const char* propName = bejGetPropName(params); |
| RETURN_IF_CALLBACK_IERROR(params->decodedCallback->callbackNull, propName, |
| params->callbacksDataPtr); |
| params->state.encodedStreamOffset = params->sflv.valueEndOffset; |
| return bejProcessEnding(params, /*canBeEmpty=*/false); |
| } |
| |
| /** |
| * @brief Decodes a BejInteger type SFLV BEJ tuple. |
| * |
| * @param[in] params - a valid BejHandleTypeFuncParam struct. |
| * @return 0 if successful. |
| */ |
| static int bejHandleBejInteger(struct BejHandleTypeFuncParam* params) |
| { |
| const char* propName = bejGetPropName(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 a BejEnum type SFLV BEJ tuple. |
| * |
| * @param[in] params - a valid BejHandleTypeFuncParam struct. |
| * @return 0 if successful. |
| */ |
| static int bejHandleBejEnum(struct BejHandleTypeFuncParam* params) |
| { |
| uint16_t sequenceNumber = params->sflv.tupleS.sequenceNumber; |
| if (bejIsArrayElement(params)) |
| { |
| 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); |
| } |
| |
| if (params->sflv.valueLength == 0) |
| { |
| RETURN_IF_CALLBACK_IERROR(params->decodedCallback->callbackNull, |
| propName, params->callbacksDataPtr); |
| } |
| else |
| { |
| // Get the string for enum value. |
| uint16_t enumValueSequenceN = |
| (uint16_t)(bejGetNnint(params->sflv.value)); |
| const struct BejDictionaryProperty* enumValueProp; |
| RETURN_IF_IERROR( |
| bejDictGetProperty(dictionary, prop->childPointerOffset, |
| enumValueSequenceN, &enumValueProp)); |
| const char* enumValueName = bejDictGetPropertyName( |
| dictionary, enumValueProp->nameOffset, enumValueProp->nameLength); |
| |
| RETURN_IF_CALLBACK_IERROR(params->decodedCallback->callbackEnum, |
| propName, enumValueName, |
| params->callbacksDataPtr); |
| } |
| // Update the offset to point to the next possible SFLV tuple. |
| params->state.encodedStreamOffset = params->sflv.valueEndOffset; |
| return bejProcessEnding(params, /*canBeEmpty=*/false); |
| } |
| |
| /** |
| * @brief Decodes a BejString type SFLV BEJ tuple. |
| * |
| * @param[in] params - a valid BejHandleTypeFuncParam struct. |
| * @return 0 if successful. |
| */ |
| static int bejHandleBejString(struct BejHandleTypeFuncParam* params) |
| { |
| // TODO: Handle deferred bindings. |
| const char* propName = bejGetPropName(params); |
| |
| if (params->sflv.valueLength == 0) |
| { |
| RETURN_IF_CALLBACK_IERROR(params->decodedCallback->callbackNull, |
| propName, params->callbacksDataPtr); |
| } |
| else |
| { |
| RETURN_IF_CALLBACK_IERROR(params->decodedCallback->callbackString, |
| propName, (const char*)(params->sflv.value), |
| params->callbacksDataPtr); |
| } |
| params->state.encodedStreamOffset = params->sflv.valueEndOffset; |
| return bejProcessEnding(params, /*canBeEmpty=*/false); |
| } |
| |
| /** |
| * @brief Decodes a BejReal type SFLV BEJ tuple. |
| * |
| * @param[in] params - a valid BejHandleTypeFuncParam struct. |
| * @return 0 if successful. |
| */ |
| static int bejHandleBejReal(struct BejHandleTypeFuncParam* params) |
| { |
| const char* propName = bejGetPropName(params); |
| |
| if (params->sflv.valueLength == 0) |
| { |
| RETURN_IF_CALLBACK_IERROR(params->decodedCallback->callbackNull, |
| propName, params->callbacksDataPtr); |
| } |
| else |
| { |
| // Real value has the following format. |
| // nnint - Length of whole |
| // bejInteger - whole (includes sign for the overall real number) |
| // nnint - Leading zero count for fract |
| // nnint - fract |
| // nnint - Length of exp |
| // bejInteger - exp (includes sign for the exponent) |
| uint8_t wholeByteLen = (uint8_t)bejGetNnint(params->sflv.value); |
| const uint8_t* wholeBejInt = |
| params->sflv.value + bejGetNnintSize(params->sflv.value); |
| const uint8_t* fractZeroCountNnint = wholeBejInt + wholeByteLen; |
| const uint8_t* fractNnint = |
| fractZeroCountNnint + bejGetNnintSize(fractZeroCountNnint); |
| const uint8_t* lenExpNnint = fractNnint + bejGetNnintSize(fractNnint); |
| const uint8_t* expBejInt = lenExpNnint + bejGetNnintSize(lenExpNnint); |
| |
| struct BejReal realValue; |
| realValue.whole = bejGetIntegerValue(wholeBejInt, wholeByteLen); |
| realValue.zeroCount = bejGetNnint(fractZeroCountNnint); |
| realValue.fract = bejGetNnint(fractNnint); |
| realValue.expLen = (uint8_t)bejGetNnint(lenExpNnint); |
| if (realValue.expLen != 0) |
| { |
| realValue.exp = bejGetIntegerValue( |
| expBejInt, (uint8_t)bejGetNnint(lenExpNnint)); |
| } |
| RETURN_IF_CALLBACK_IERROR(params->decodedCallback->callbackReal, |
| propName, &realValue, |
| params->callbacksDataPtr); |
| } |
| params->state.encodedStreamOffset = params->sflv.valueEndOffset; |
| return bejProcessEnding(params, /*canBeEmpty=*/false); |
| } |
| |
| /** |
| * @brief Decodes a BejBoolean type SFLV BEJ tuple. |
| * |
| * @param[in] params - a valid BejHandleTypeFuncParam struct. |
| * @return 0 if successful. |
| */ |
| static int bejHandleBejBoolean(struct BejHandleTypeFuncParam* params) |
| { |
| const char* propName = bejGetPropName(params); |
| |
| if (params->sflv.valueLength == 0) |
| { |
| RETURN_IF_CALLBACK_IERROR(params->decodedCallback->callbackNull, |
| propName, params->callbacksDataPtr); |
| } |
| else |
| { |
| RETURN_IF_CALLBACK_IERROR(params->decodedCallback->callbackBool, |
| propName, *(params->sflv.value) > 0, |
| params->callbacksDataPtr); |
| } |
| params->state.encodedStreamOffset = params->sflv.valueEndOffset; |
| return bejProcessEnding(params, /*canBeEmpty=*/false); |
| } |
| |
| /** |
| * @brief Decodes a BejPropertyAnnotation type SFLV BEJ tuple. |
| * |
| * @param[in] params - a valid BejHandleTypeFuncParam struct. |
| * @return 0 if successful. |
| */ |
| static int bejHandleBejPropertyAnnotation(struct BejHandleTypeFuncParam* params) |
| { |
| // TODO: Handle colon-delimited string values. |
| |
| // Property annotation has the form OuterProperty@Annotation. First |
| // processing the outer property name. |
| const uint8_t* outerDictionary; |
| const struct BejDictionaryProperty* outerProp; |
| RETURN_IF_IERROR(bejGetDictionaryAndProperty( |
| params, params->sflv.tupleS.schema, params->sflv.tupleS.sequenceNumber, |
| &outerDictionary, &outerProp)); |
| |
| const char* propName = bejDictGetPropertyName( |
| outerDictionary, outerProp->nameOffset, outerProp->nameLength); |
| RETURN_IF_CALLBACK_IERROR(params->decodedCallback->callbackAnnotation, |
| propName, params->callbacksDataPtr); |
| |
| // Mark the ending of the property annotation. |
| struct BejStackProperty newEnding = { |
| .sectionType = bejSectionNoType, |
| .addPropertyName = params->state.addPropertyName, |
| .mainDictPropOffset = params->state.mainDictPropOffset, |
| .annoDictPropOffset = params->state.annoDictPropOffset, |
| .streamEndOffset = params->sflv.valueEndOffset, |
| }; |
| // Update the states for the next encoding segment. |
| RETURN_IF_IERROR( |
| params->stackCallback->stackPush(&newEnding, params->stackDataPtr)); |
| params->state.addPropertyName = true; |
| // We might have to change this for nested annotations. |
| params->state.mainDictPropOffset = outerProp->childPointerOffset; |
| // Point to the start of the value for next decoding. |
| params->state.encodedStreamOffset = |
| params->sflv.valueEndOffset - params->sflv.valueLength; |
| return 0; |
| } |
| |
| /** |
| * @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: |
| RETURN_IF_IERROR(bejHandleBejSet(¶ms)); |
| break; |
| case bejArray: |
| RETURN_IF_IERROR(bejHandleBejArray(¶ms)); |
| break; |
| case bejNull: |
| RETURN_IF_IERROR(bejHandleBejNull(¶ms)); |
| break; |
| case bejInteger: |
| RETURN_IF_IERROR(bejHandleBejInteger(¶ms)); |
| break; |
| case bejEnum: |
| RETURN_IF_IERROR(bejHandleBejEnum(¶ms)); |
| break; |
| case bejString: |
| RETURN_IF_IERROR(bejHandleBejString(¶ms)); |
| break; |
| case bejReal: |
| RETURN_IF_IERROR(bejHandleBejReal(¶ms)); |
| break; |
| case bejBoolean: |
| RETURN_IF_IERROR(bejHandleBejBoolean(¶ms)); |
| 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: |
| RETURN_IF_IERROR(bejHandleBejPropertyAnnotation(¶ms)); |
| 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; |
| } |
| } |
| 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[in] bejVersion - 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) |
| { |
| 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) |
| { |
| 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); |
| } |