Implement bejResourceLinkExpansion support and add tests Change-Id: Ica7c1407872a1e72b1fa83d5b9096648730072bf Signed-off-by: Muhammad Usama <muhammadusama@google.com>
diff --git a/include/libbej/bej_common.h b/include/libbej/bej_common.h index da9ba5c..413296d 100644 --- a/include/libbej/bej_common.h +++ b/include/libbej/bej_common.h
@@ -56,6 +56,7 @@ bejErrorInvalidSchemaType, bejErrorInvalidPropertyOffset, bejErrorNullParameter, + bejErrorInvalidNodeInput, }; /** @@ -132,13 +133,14 @@ */ struct BejReal { - // Number bytes in exp. - uint8_t expLen; + // Mathematically an exponent of 0 and omitting the exponent are the + // same. But these 2 situations are encoded differently. This flag is + // used to distinguish these 2 situations. + bool valid_exp; int64_t whole; uint64_t zeroCount; uint64_t fract; int64_t exp; - bool valid_exp; }; /**
diff --git a/include/libbej/bej_decoder_core.h b/include/libbej/bej_decoder_core.h index 84257c7..e0b2fc0 100644 --- a/include/libbej/bej_decoder_core.h +++ b/include/libbej/bej_decoder_core.h
@@ -38,6 +38,8 @@ uint16_t annoDictPropOffset; // Offset to the end of the array or set or annotation. uint32_t streamEndOffset; + // Schema dictionary used for this section. + const uint8_t* schemaDictionary; }; /** @@ -206,6 +208,20 @@ * * @return 0 if successful. */ +struct BejNodeDecodeInfo +{ + /** + * @brief User provided context. + */ + void* context; + + /** + * @brief Get the schema dictionary for the resource ID. + */ + int (*getSchemaDictionary)(void* context, uint32_t resourceId, + const uint8_t** dictionary); +}; + int bejDecodePldmBlock(const struct BejDictionaries* dictionaries, uint16_t majorSchemaStartingOffset, const uint8_t* encodedPldmBlock, uint32_t blockLength, @@ -213,6 +229,36 @@ const struct BejDecodedCallback* decodedCallback, 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] majorSchemaStartingOffset - dictionary starting offset of the + * major schema. Usually this will be set to BEJ_DICTIONARY_START_AT_HEAD + * unless we are decoding a payload associated with a bejLocator type. + * @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. + * @param[in] nodeDecodeInfo - A BejNodeDecodeInfo struct. Can be NULL. + * @return 0 if successful. + */ +int bejDecode(const struct BejDictionaries* dictionaries, + uint16_t majorSchemaStartingOffset, + const uint8_t* encodedPldmBlock, uint32_t blockLength, + const struct BejStackCallback* stackCallback, + const struct BejDecodedCallback* decodedCallback, + void* callbacksDataPtr, void* stackDataPtr, + struct BejNodeDecodeInfo* nodeDecodeInfo); + #ifdef __cplusplus } #endif
diff --git a/include/libbej/bej_decoder_json.hpp b/include/libbej/bej_decoder_json.hpp index cac9fef..e01016f 100644 --- a/include/libbej/bej_decoder_json.hpp +++ b/include/libbej/bej_decoder_json.hpp
@@ -26,7 +26,8 @@ int decode( const BejDictionaries& dictionaries, const std::span<const uint8_t> encodedPldmBlock, - uint16_t majorSchemaStartingOffset = BEJ_DICTIONARY_START_AT_HEAD); + uint16_t majorSchemaStartingOffset = BEJ_DICTIONARY_START_AT_HEAD, + struct BejNodeDecodeInfo* nodeDecodeInfo = NULL); /** * @brief Get the JSON output related to the latest call to decode.
diff --git a/include/libbej/bej_encoder_core.h b/include/libbej/bej_encoder_core.h index b00a537..7c60a65 100644 --- a/include/libbej/bej_encoder_core.h +++ b/include/libbej/bej_encoder_core.h
@@ -45,6 +45,52 @@ struct BejEncoderOutputHandler* output, struct BejPointerStackCallback* stack); +/** + * @brief Perform BEJ encoding. + * + * @param[in] dictionaries - dictionaries for resource being encoded. + * @param[in] majorSchemaStartingOffset - major dictionary starting offset. + * @param[in] schemaClass - schema class + * @param[in] root - A valid RedfishPropertyParent node that points to the + * root of the resource being encoded. + * @param[inout] output - A valid BejEncoderOutputHandler struct to save the + * output of the encoding. + * @param[in] stack - A valid BejPointerStackCallback struct. + * @param[in] nodeInfoCb - A BejNodeInfoCallbacks struct. Can be NULL. But + * this should be valid if the resource contains RedfishPropertyLeafUri type + * nodes. + * @return 0 if successful. + */ +int bejEncodeWithExpand(const struct BejDictionaries* dictionaries, + uint16_t majorSchemaStartingOffset, + enum BejSchemaClass schemaClass, + struct RedfishPropertyParent* root, + struct BejEncoderOutputHandler* output, + struct BejPointerStackCallback* stack, + struct BejNodeInfoCallbacks* nodeInfoCb); + +/** + * @brief Add resource IDs of the URIs that needs to be expanded to the + * given list. + * + * This function goes through the provided BEJ tree JSON representation and + * look for the bejResourceLinkExpansion node types with expand flag set to + * true and add them to the given list. + * + * @param[in] root - A valid RedfishPropertyParent node that points to the root + * of a resource represented using BEJ tree API. + * @param[in] stack - A valid BejPointerStackCallback struct. + * @param[inout] listContext - A context pointer for passing to the listPush + * callback. + * @param[inout] listPush - A callback for pushing resource IDs to a list. + * @return 0 if successful. + */ +int bejGetUriNodesToBeExpanded(struct RedfishPropertyParent* root, + struct BejPointerStackCallback* stack, + void* listContext, + int (*listPush)(uint32_t resourceID, + void* listContext)); + #ifdef __cplusplus } #endif
diff --git a/include/libbej/bej_encoder_metadata.h b/include/libbej/bej_encoder_metadata.h index 8a7fdc5..8b58479 100644 --- a/include/libbej/bej_encoder_metadata.h +++ b/include/libbej/bej_encoder_metadata.h
@@ -27,7 +27,8 @@ int bejUpdateNodeMetadata(const struct BejDictionaries* dictionaries, uint16_t majorSchemaStartingOffset, struct RedfishPropertyParent* root, - struct BejPointerStackCallback* stack); + struct BejPointerStackCallback* stack, + struct BejNodeInfoCallbacks* nodeInfoCb); #ifdef __cplusplus }
diff --git a/include/libbej/bej_tree.h b/include/libbej/bej_tree.h index 5a9ff89..790a460 100644 --- a/include/libbej/bej_tree.h +++ b/include/libbej/bej_tree.h
@@ -136,6 +136,79 @@ }; /** + * @brief A node to store URI link related information + * + * If a link needs the support for expand, use this property node instead of + * using RedfishPropertyLeafString node type for a link. This represents the + * bejResourceLinkExpansion BEJ property type. + */ +struct RedfishPropertyLeafUri +{ + struct RedfishPropertyLeaf leaf; + // Resource ID used for the URI + uint32_t resourceId; + // Indicate whether to expand the URI + bool expand; +}; + +/** + * @brief Callbacks for getting node info. + */ +struct BejNodeInfoCallbacks +{ + // User provided context + void* context; + + /** + * @brief Get the URI for a resource ID + * + * @param[in] resourceId - Resource ID. + * @param[in] context - User provided context to be passed to the + * callback. + * @param[out] uriStr - URI string of the resource. Must be NULL + * terminated. If this returned as NULL, then no URI is present or + * unable to retrieve it. + */ + void (*bejGetUri)(uint32_t resourceId, void* context, + const char** uriStr); + + /** + * @brief Get the bejEncoding size for a resource ID + * + * Callback should return the size of the bejEncoding that needs to be + * embedded for the given resource ID. + * + * @param[in] resourceId - Resource ID. + * @param[in] context - User provided context to be passed to + * the callback. + * @param[out] encodingSize - Size of the bejEncoding. + * @return 0 if successful. + */ + int (*bejGetBejEncodingSize)(uint32_t resourceId, void* context, + uint32_t* encodingSize); + + /** + * @brief Get a bejEncoding data blob for a resource ID + * + * @param[in] resourceId - Resource ID. + * @param[in] context - User provided context to be passed to + * the callback. + * @param[in] readOffset - 0 based offset for reading the payload. + * Offset should be less than the size of the payload. + * @param[in] readSize - Number bytes that can be accepted. + * @param[out] encodingData - A pointer to a continuous data segment of + * the bejEncoding. + * @param[out] encodingDataLength - Size of the bejEncoding segment + * pointed by encodingData. This can be equal to or less than readSize. + * @return 0 if successful. + */ + int (*bejGetBejEncoding)(uint32_t resourceId, void* context, + uint32_t readOffset, uint32_t readSize, + const uint8_t** encodingData, + uint32_t* encodingDataLength); +}; + +/** * @brief bejReal type property node. */ struct RedfishPropertyLeafReal @@ -243,6 +316,20 @@ const char* value); /** + * @brief Add a URI node to a parent node. + * + * @param[inout] parent - a pointer to an initialized parent struct. + * @param[in] child - a pointer to an uninitialized RedfishPropertyLeafUri + * type node. + * @param[in] name - name of the RedfishPropertyLeafUri type property. + * @param[in] resourceId - resourceId used for the URI. + * @param[in] expand - True if the URI needs to be expanded. + */ +void bejTreeAddUri(struct RedfishPropertyParent* parent, + struct RedfishPropertyLeafUri* child, const char* name, + uint32_t resourceId, bool expand); + +/** * @brief Add a bejReal type node to a parent node. * * @param[in] parent - a pointer to an initialized parent struct.
diff --git a/src/bej_common.c b/src/bej_common.c index 419f413..e101494 100644 --- a/src/bej_common.c +++ b/src/bej_common.c
@@ -101,7 +101,7 @@ int bejGetDoubleFromBejReal(const struct BejReal* value, double* output) { -// Maximum suported zero count in BejReal type. The value selected will prevent +// Maximum supported zero count in BejReal type. The value selected will prevent // overflow of divisor(uin64_t). We could support larger zero count but that // will require more computational logic and we probably do not need that level // of precision. @@ -187,7 +187,7 @@ } *output *= exp_factor; } - // Using else if to avoid unecessary division if exp == 0. + // Using else if to avoid unnecessary division if exp == 0. else if (value->exp < 0) { *output /= exp_factor;
diff --git a/src/bej_decoder_core.c b/src/bej_decoder_core.c index 577739c..9ddb3a3 100644 --- a/src/bej_decoder_core.c +++ b/src/bej_decoder_core.c
@@ -156,6 +156,16 @@ bejGetNnintSize(params->sflv.value); } +static uint32_t bejSkipNnintInValue(const struct BejHandleTypeFuncParam* params) +{ + struct BejSFLVOffset localOffset; + // Get the offset of the value with respect to the current SFLV 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. * @@ -267,6 +277,7 @@ params->state.mainDictPropOffset = ending->mainDictPropOffset; params->state.annoDictPropOffset = ending->annoDictPropOffset; params->state.addPropertyName = ending->addPropertyName; + params->mainDictionary = ending->schemaDictionary; if (ending->sectionType == bejSectionSet) { @@ -373,6 +384,7 @@ .mainDictPropOffset = params->state.mainDictPropOffset, .annoDictPropOffset = params->state.annoDictPropOffset, .streamEndOffset = params->sflv.valueEndOffset, + .schemaDictionary = params->mainDictionary, }; RETURN_IF_IERROR( params->stackCallback->stackPush(&newEnding, params->stackDataPtr)); @@ -438,6 +450,7 @@ .mainDictPropOffset = params->state.mainDictPropOffset, .annoDictPropOffset = params->state.annoDictPropOffset, .streamEndOffset = params->sflv.valueEndOffset, + .schemaDictionary = params->mainDictionary, }; RETURN_IF_IERROR( params->stackCallback->stackPush(&newEnding, params->stackDataPtr)); @@ -682,6 +695,7 @@ .mainDictPropOffset = params->state.mainDictPropOffset, .annoDictPropOffset = params->state.annoDictPropOffset, .streamEndOffset = params->sflv.valueEndOffset, + .schemaDictionary = params->mainDictionary, }; // Update the states for the next encoding segment. RETURN_IF_IERROR( @@ -711,12 +725,113 @@ * * @return 0 if successful. */ -static int bejDecode( - const uint8_t* schemaDictionary, const uint8_t* annotationDictionary, - uint16_t majorSchemaStartingOffset, const uint8_t* enStream, - uint32_t streamLen, const struct BejStackCallback* stackCallback, - const struct BejDecodedCallback* decodedCallback, void* callbacksDataPtr, - void* stackDataPtr) +static int bejHandleLinkExpansion(struct BejHandleTypeFuncParam* params, + struct BejNodeDecodeInfo* nodeDecodeInfo) +{ + // Expanded properties doesn't have a property name. + if (params->sflv.valueLength == 0) + { + RETURN_IF_CALLBACK_IERROR(params->decodedCallback->callbackNull, NULL, + params->callbacksDataPtr); + } + + NULL_CHECK(nodeDecodeInfo, "nodeDecodeInfo"); + NULL_CHECK(nodeDecodeInfo->getSchemaDictionary, + "nodeDecodeInfo get dictionary"); + + // Get the bejEncoding.sflv.value = [nnint | bejEncoding]. + uint8_t resourceIdBytes = bejGetNnintSize(params->sflv.value); + uint32_t expBejEncodingLength = params->sflv.valueLength - resourceIdBytes; + uint32_t pldmHeaderSize = sizeof(struct BejPldmBlockHeader); + + if (expBejEncodingLength < pldmHeaderSize) + { + fprintf(stderr, "Invalid expanded pldm block size: %u\n", + expBejEncodingLength); + return bejErrorInvalidSize; + } + + // Current params->sflv.value points to BejResourceLinkExpansion value. + // This has the following format. + // + // [nnint | bejEncoding header | First SFLV tuple | second SFLV tuple | ...] + // + // The first SFLV tuple segment contains the resource type. + // Eg: "Chassis" : { ... }. + // We do not want the resource type name property. So we should + // again skip to the start of the second SFLV tuple. Also note that the + // first SFLV tuple is always a bejSet type. + // + // We need to calculate the offset of the second SFLV with respect to the + // complete encoded payload. + // + // Skip the first nnint. + uint32_t expandedBejEncodingHeaderOffset = bejSkipNnintInValue(params); + // Skip the bejEncoding header. + uint32_t firstSflvOffset = expandedBejEncodingHeaderOffset + pldmHeaderSize; + + // Get the offsets of S F L V fields of the first SFLV segment. + struct BejSFLVOffset firstSflvLocaloffsets = {0}; + const uint8_t* firstSflv = + params->sflv.value + resourceIdBytes + pldmHeaderSize; + bejGetLocalBejSFLVOffsets(firstSflv, &firstSflvLocaloffsets); + + // Get a pointer to the first SFLV tuple value field. + const uint8_t* firstSflvValue = + firstSflv + firstSflvLocaloffsets.valueOffset; + // Get the offset to the second SFLV. First SFLV has the format [nnint | + // second SFLV Tuple] + uint32_t secondSflvOffset = firstSflvOffset + + firstSflvLocaloffsets.valueOffset + + bejGetNnintSize(firstSflvValue); + + // Point the next SFLV offset to be decoded. + params->state.encodedStreamOffset = secondSflvOffset; + + // Get the schema dictionary for the expanded section. + const uint8_t* newSchemaDictionary; + uint32_t resourceId = (uint32_t)(bejGetNnint(params->sflv.value)); + int ret = nodeDecodeInfo->getSchemaDictionary( + nodeDecodeInfo->context, resourceId, &newSchemaDictionary); + if (ret != 0) + { + fprintf(stderr, "Failed to get dictionary for rid: 0x%.8x\n", + resourceId); + return bejErrorInvalidNodeInput; + } + + // We need to know the current state of the decoding once the expanded + // section is done. So update the expected ending and save the current + // state. + struct BejStackProperty newEnding = { + .sectionType = bejSectionNoType, + .addPropertyName = params->state.addPropertyName, + .mainDictPropOffset = params->state.mainDictPropOffset, + .annoDictPropOffset = params->state.annoDictPropOffset, + .streamEndOffset = params->sflv.valueEndOffset, + .schemaDictionary = params->mainDictionary, + }; + RETURN_IF_IERROR( + params->stackCallback->stackPush(&newEnding, params->stackDataPtr)); + + // Resets the dictionary offsets for the expanded encoding. + params->state.annoDictPropOffset = bejDictGetFirstAnnotatedPropertyOffset(); + params->state.mainDictPropOffset = bejDictGetPropertyHeadOffset(); + params->mainDictionary = newSchemaDictionary; + + // We did not decode an actual property/key pair yet. So we shouldn't call + // bejProcessEnding(); + return 0; +} + +static int bejDecodeHelper(const uint8_t* schemaDictionary, + const uint8_t* annotationDictionary, + uint16_t majorSchemaStartingOffset, + const uint8_t* enStream, uint32_t streamLen, + const struct BejStackCallback* stackCallback, + const struct BejDecodedCallback* decodedCallback, + void* callbacksDataPtr, void* stackDataPtr, + struct BejNodeDecodeInfo* nodeDecodeInfo) { struct BejHandleTypeFuncParam params = { .state = @@ -830,9 +945,7 @@ 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; + RETURN_IF_IERROR(bejHandleLinkExpansion(¶ms, nodeDecodeInfo)); break; default: break; @@ -867,12 +980,13 @@ return false; } -int bejDecodePldmBlock(const struct BejDictionaries* dictionaries, - uint16_t majorSchemaStartingOffset, - const uint8_t* encodedPldmBlock, uint32_t blockLength, - const struct BejStackCallback* stackCallback, - const struct BejDecodedCallback* decodedCallback, - void* callbacksDataPtr, void* stackDataPtr) +int bejDecode(const struct BejDictionaries* dictionaries, + uint16_t majorSchemaStartingOffset, + const uint8_t* encodedPldmBlock, uint32_t blockLength, + const struct BejStackCallback* stackCallback, + const struct BejDecodedCallback* decodedCallback, + void* callbacksDataPtr, void* stackDataPtr, + struct BejNodeDecodeInfo* nodeDecodeInfo) { NULL_CHECK(dictionaries, "dictionaries"); NULL_CHECK(dictionaries->schemaDictionary, "schemaDictionary"); @@ -966,8 +1080,20 @@ // Skip the PLDM header. const uint8_t* enStream = encodedPldmBlock + pldmHeaderSize; uint32_t streamLen = blockLength - pldmHeaderSize; - return bejDecode( + return bejDecodeHelper( dictionaries->schemaDictionary, dictionaries->annotationDictionary, majorSchemaStartingOffset, enStream, streamLen, stackCallback, - decodedCallback, callbacksDataPtr, stackDataPtr); + decodedCallback, callbacksDataPtr, stackDataPtr, nodeDecodeInfo); +} + +int bejDecodePldmBlock(const struct BejDictionaries* dictionaries, + uint16_t majorSchemaStartingOffset, + const uint8_t* encodedPldmBlock, uint32_t blockLength, + const struct BejStackCallback* stackCallback, + const struct BejDecodedCallback* decodedCallback, + void* callbacksDataPtr, void* stackDataPtr) +{ + return bejDecode(dictionaries, majorSchemaStartingOffset, encodedPldmBlock, + blockLength, stackCallback, decodedCallback, + callbacksDataPtr, stackDataPtr, NULL); }
diff --git a/src/bej_decoder_json.cpp b/src/bej_decoder_json.cpp index 937da36..8fab2bc 100644 --- a/src/bej_decoder_json.cpp +++ b/src/bej_decoder_json.cpp
@@ -348,7 +348,8 @@ int BejDecoderJson::decode(const BejDictionaries& dictionaries, const std::span<const uint8_t> encodedPldmBlock, - uint16_t majorSchemaStartingOffset) + uint16_t majorSchemaStartingOffset, + struct BejNodeDecodeInfo* nodeDecodeInfo) { // Clear the previous output if any. output.clear(); @@ -390,10 +391,10 @@ .output = &output, }; - return bejDecodePldmBlock( - &dictionaries, majorSchemaStartingOffset, encodedPldmBlock.data(), - encodedPldmBlock.size_bytes(), &stackCallback, &decodedCallback, - (void*)(&callbackData), (void*)(&stack)); + return bejDecode(&dictionaries, majorSchemaStartingOffset, + encodedPldmBlock.data(), encodedPldmBlock.size_bytes(), + &stackCallback, &decodedCallback, (void*)(&callbackData), + (void*)(&stack), nodeDecodeInfo); } std::string BejDecoderJson::getOutput()
diff --git a/src/bej_dictionary.c b/src/bej_dictionary.c index 23479ad..e4b4d40 100644 --- a/src/bej_dictionary.c +++ b/src/bej_dictionary.c
@@ -278,6 +278,6 @@ } fprintf(stderr, "Couldn't reach the expected property. Input is invalid or " - "somethig went wrong during calculation"); + "something went wrong during calculation"); return -1; }
diff --git a/src/bej_encoder_core.c b/src/bej_encoder_core.c index 0e0ad61..2679880 100644 --- a/src/bej_encoder_core.c +++ b/src/bej_encoder_core.c
@@ -181,8 +181,89 @@ // L: Encode the value length. return bejEncodeNnint(node->leaf.metaData.vSize, output); } +static int bejEncodeLinkExpansion(struct RedfishPropertyLeafUri* node, + struct BejEncoderOutputHandler* output, + struct BejNodeInfoCallbacks* nodeInfoCb) +{ + NULL_CHECK(nodeInfoCb, "nodeInfoCb is NULL"); -static int bejEncodeNode(void* node, struct BejEncoderOutputHandler* output) + // S: Encode Sequence number. + RETURN_IF_IERROR( + bejEncodeNnint(node->leaf.metaData.sequenceNumber, output)); + + // Encode the URI + if (!node->expand) + { + const char* uriStr = NULL; + nodeInfoCb->bejGetUri(node->resourceId, nodeInfoCb->context, &uriStr); + if (uriStr == NULL) + { + fprintf(stderr, "Failed to get uri info for rid: 0x%.8x\n", + node->resourceId); + return bejErrorInvalidNodeInput; + } + + // F: Add the format. We will encode this as a string rather than + // bejResourceLinkExpansion format. + struct BejTupleF format = {0}; + format.principalDataType = bejString; + RETURN_IF_IERROR(bejEncodeFormat(&format, output)); + // L: Encode the value length. + RETURN_IF_IERROR(bejEncodeNnint(node->leaf.metaData.vSize, output)); + // V: Encode the value. + return output->recvOutput((void*)uriStr, node->leaf.metaData.vSize, + output->handlerContext); + } + + // Encode with the bejEncoding payload. + uint32_t encodingSize; + int ret = nodeInfoCb->bejGetBejEncodingSize( + node->resourceId, nodeInfoCb->context, &encodingSize); + if (ret != 0) + { + fprintf(stderr, + "Failed to get bejEncoding size for rid: 0x%.8x. Err: %d\n", + node->resourceId, ret); + return bejErrorInvalidNodeInput; + } + + // F: Add the format. + RETURN_IF_IERROR(bejEncodeFormat(&node->leaf.nodeAttr.format, output)); + // L: Encode the value length. + RETURN_IF_IERROR(bejEncodeNnint(node->leaf.metaData.vSize, output)); + // V: Encode the value. value = (resource_ID | bejEncoding) + // First encode resource_ID + bejEncodeNnint(node->resourceId, output); + // Then write the bejEncoding. The bejEncoding data blob might come from a + // wrapped around buffer. So we need to try and read all the data. + + uint32_t readOffset = 0; + uint32_t remainingPayload = encodingSize; + while (remainingPayload > 0) + { + uint32_t payloadLength = 0; + const uint8_t* data = NULL; + + int payloadRet = nodeInfoCb->bejGetBejEncoding( + node->resourceId, nodeInfoCb->context, readOffset, remainingPayload, + &data, &payloadLength); + if (payloadRet != 0) + { + fprintf(stderr, + "Failed to get the payload for rid: 0x%.8x. Err: %d\n", + node->resourceId, payloadRet); + return bejErrorInvalidNodeInput; + } + RETURN_IF_IERROR(output->recvOutput((void*)data, payloadLength, + output->handlerContext)); + readOffset += payloadLength; + remainingPayload -= payloadLength; + } + return 0; +} + +static int bejEncodeNode(void* node, struct BejEncoderOutputHandler* output, + struct BejNodeInfoCallbacks* nodeInfoCb) { struct RedfishPropertyNode* nodeInfo = node; switch (nodeInfo->format.principalDataType) @@ -214,6 +295,9 @@ case bejPropertyAnnotation: RETURN_IF_IERROR(bejEncodeBejProAnno(node, output)); break; + case bejResourceLinkExpansion: + RETURN_IF_IERROR(bejEncodeLinkExpansion(node, output, nodeInfoCb)); + break; default: fprintf(stderr, "Unsupported node type: %d\n", nodeInfo->format.principalDataType); @@ -239,7 +323,8 @@ */ static int bejProcessChildNodes(struct RedfishPropertyParent* parent, struct BejPointerStackCallback* stack, - struct BejEncoderOutputHandler* output) + struct BejEncoderOutputHandler* output, + struct BejNodeInfoCallbacks* nodeInfoCb) { // Get the next child of the parent. void* childPtr = parent->metaData.nextChild; @@ -247,7 +332,7 @@ while (childPtr != NULL) { // First encode the current child node. - RETURN_IF_IERROR(bejEncodeNode(childPtr, output)); + RETURN_IF_IERROR(bejEncodeNode(childPtr, output, nodeInfoCb)); // If this child node has its own children, add it to the stack and // return. Because we need to encode the children of the newly added // node before continuing to encode the child nodes of the current @@ -267,11 +352,12 @@ static int bejEncodeTree(struct RedfishPropertyParent* root, struct BejPointerStackCallback* stack, - struct BejEncoderOutputHandler* output) + struct BejEncoderOutputHandler* output, + struct BejNodeInfoCallbacks* nodeInfoCb) { // We need to encode a parent node before its child nodes. So encoding the // root first. - RETURN_IF_IERROR(bejEncodeNode(root, output)); + RETURN_IF_IERROR(bejEncodeNode(root, output, nodeInfoCb)); // Once the root is encoded, push it to the stack used to traverse the child // nodes. We need to keep a parent in this stack until all the child nodes // of this parent has been encoded. Only then we remove the parent node from @@ -288,7 +374,7 @@ // rest of the children of the current parent will be encoded later // (after processing all the nodes under the child node added to the // stack). - RETURN_IF_IERROR(bejProcessChildNodes(parent, stack, output)); + RETURN_IF_IERROR(bejProcessChildNodes(parent, stack, output, nodeInfoCb)); // If a new node hasn't been added to the stack by // bejProcessChildNodes(), we know that this parent's child nodes have @@ -303,12 +389,13 @@ return 0; } -int bejEncode(const struct BejDictionaries* dictionaries, - uint16_t majorSchemaStartingOffset, - enum BejSchemaClass schemaClass, - struct RedfishPropertyParent* root, - struct BejEncoderOutputHandler* output, - struct BejPointerStackCallback* stack) +int bejEncodeWithExpand(const struct BejDictionaries* dictionaries, + uint16_t majorSchemaStartingOffset, + enum BejSchemaClass schemaClass, + struct RedfishPropertyParent* root, + struct BejEncoderOutputHandler* output, + struct BejPointerStackCallback* stack, + struct BejNodeInfoCallbacks* nodeInfoCb) { NULL_CHECK(dictionaries, "dictionaries"); NULL_CHECK(dictionaries->schemaDictionary, "schemaDictionary"); @@ -340,7 +427,7 @@ // First calculate metadata for encoding each node. RETURN_IF_IERROR(bejUpdateNodeMetadata( - dictionaries, majorSchemaStartingOffset, root, stack)); + dictionaries, majorSchemaStartingOffset, root, stack, nodeInfoCb)); // Derive the header of the encoded output. // BEJ version @@ -354,5 +441,80 @@ output->handlerContext)); // Produce the encoded bytes for the nodes using the previously calculated // metadata. - return bejEncodeTree(root, stack, output); + return bejEncodeTree(root, stack, output, nodeInfoCb); +} + +int bejEncode(const struct BejDictionaries* dictionaries, + uint16_t majorSchemaStartingOffset, + enum BejSchemaClass schemaClass, + struct RedfishPropertyParent* root, + struct BejEncoderOutputHandler* output, + struct BejPointerStackCallback* stack) +{ + return bejEncodeWithExpand(dictionaries, majorSchemaStartingOffset, + schemaClass, root, output, stack, NULL); +} + +int bejGetUriNodesToBeExpanded( + struct RedfishPropertyParent* root, struct BejPointerStackCallback* stack, + void* listContext, int (*listPush)(uint32_t resourceID, void* listContext)) +{ + NULL_CHECK(root, "root"); + NULL_CHECK(stack, "stack"); + + // Push the root to the stack and traverse the child nodes in depth first + // manner. + RETURN_IF_IERROR(bejPushParentToStack(root, stack)); + + while (!stack->stackEmpty(stack->stackContext)) + { + // Get the newest parent node from the stack. + struct RedfishPropertyParent* parent = + stack->stackPeek(stack->stackContext); + + // Get the next child of the parent. + void* childP = parent->metaData.nextChild; + while (childP != NULL) + { + // If the current child node is also a parent node, then add it to + // the stack and exit from the loop to process the children of that + // node first. + if (bejTreeIsParentType(childP)) + { + RETURN_IF_IERROR(bejPushParentToStack(childP, stack)); + // Update the next child of the current parent we need to + // process. + bejParentGoToNextChild(parent, childP); + break; + } + + // Check whether the current child node is bejResourceLinkExpansion + // node type. If yes, add it to the list. + struct RedfishPropertyNode* nodeInfo = + (struct RedfishPropertyNode*)childP; + if (nodeInfo->format.principalDataType == bejResourceLinkExpansion) + { + struct RedfishPropertyLeafUri* nodeUri = + (struct RedfishPropertyLeafUri*)childP; + if (nodeUri->expand) + { + RETURN_IF_IERROR( + listPush(nodeUri->resourceId, listContext)); + } + } + + childP = bejParentGoToNextChild(parent, childP); + } + + // Check if a new node has been added. If yes, then continue without + // popping the stack. If no, then we know that the parent's child nodes + // have been processed. So we can pop it. + if (parent != stack->stackPeek(stack->stackContext)) + { + continue; + } + stack->stackPop(stack->stackContext); + } + + return 0; }
diff --git a/src/bej_encoder_json.cpp b/src/bej_encoder_json.cpp index 06f75f5..8d4d8e2 100644 --- a/src/bej_encoder_json.cpp +++ b/src/bej_encoder_json.cpp
@@ -60,6 +60,7 @@ struct RedfishPropertyParent* root, uint16_t majorSchemaStartingOffset) { + (void)majorSchemaStartingOffset; struct BejEncoderOutputHandler output = { .handlerContext = &encodedPayload, .recvOutput = &getBejEncodedBuffer,
diff --git a/src/bej_encoder_metadata.c b/src/bej_encoder_metadata.c index de759be..3369210 100644 --- a/src/bej_encoder_metadata.c +++ b/src/bej_encoder_metadata.c
@@ -254,6 +254,35 @@ node->leaf.metaData.sflSize = bejNnintEncodingSizeOfUInt(sequenceNumber); // F: Size of the format byte is 1. node->leaf.metaData.sflSize += BEJ_TUPLE_F_SIZE; + + // Negative numbers greater than -1, have 0 as the bejReal whole portion. + // Therefore to include the negative sign, we need to change the provided + // value to a whole.fract × 10^(exp) format where |whole| > 0. + // Eg: + // value = -0.0000012 + // value = -1.2 x 10^(-6) + int exp = 0; + if ((-1 < node->value) && (node->value < 0)) + { + // Multiply the node->value until |node->value| > 0. + while ((-1 < node->value) && exp < BEJ_REAL_PRECISION) + { + node->value = node->value * 10.0; + ++exp; + } + + // Still if the |whole| < 0, then number is smaller than the precision + // supported + if (-1 < node->value) + { + exp = 0; + node->value = 0; + } + + // Exp should be negative + exp = exp * (-1); + } + // We need to breakdown the real number to bejReal type to determine the // length. We are not gonna add an exponent. It will only be the whole part // and the fraction part. Get the whole part @@ -286,9 +315,9 @@ node->bejReal.zeroCount = leadingZeros; node->bejReal.fract = (int64_t)originalFactConvertedToWhole; - // We are omitting exp. So the exp length should be 0. - node->bejReal.expLen = 0; - node->bejReal.exp = 0; + // If exponent is 0, then omit the exponent. + node->bejReal.exp = exp; + node->bejReal.valid_exp = (exp != 0) ? true : false; // Calculate the sizes needed for storing bejReal fields. // nnint for the length of the "whole" value. @@ -300,9 +329,17 @@ // nnint for the factional part. node->leaf.metaData.vSize += bejNnintEncodingSizeOfUInt((int64_t)originalFactConvertedToWhole); - // nnint for the exp length. We are omitting exp. So the exp length should - // be 0. - node->leaf.metaData.vSize += bejNnintEncodingSizeOfUInt(0); + // nnint for the exp length. + if (node->bejReal.valid_exp) + { + // Length of the nnint field that represent the length of the exp. + node->leaf.metaData.vSize += BEJ_TUPLE_L_SIZE_FOR_BEJ_INTEGER; + node->leaf.metaData.vSize += bejIntLengthOfValue(node->bejReal.exp); + } + else + { + node->leaf.metaData.vSize += bejNnintEncodingSizeOfUInt(0); + } // L: nnint for the size needed for encoding the bejReal value. node->leaf.metaData.sflSize += @@ -420,9 +457,72 @@ * node's parent. * @return 0 if successful. */ -static int bejUpdateLeafNodeMetaData( +static int bejUpdateLinkExpansionMetaData( const struct BejDictionaries* dictionaries, const uint8_t* parentDictionary, - void* childPtr, uint16_t childIndex, uint16_t dictStartingOffset) + struct RedfishPropertyLeafUri* node, uint16_t nodeIndex, + uint16_t dictStartingOffset, struct BejNodeInfoCallbacks* nodeInfoCb) +{ + NULL_CHECK(nodeInfoCb, "nodeInfoCb is NULL"); + + uint32_t sequenceNumber; + RETURN_IF_IERROR(bejFindSeqNumAndChildDictOffset( + dictionaries, parentDictionary, &(node->leaf.nodeAttr), nodeIndex, + dictStartingOffset, &sequenceNumber, NULL, NULL)); + node->leaf.metaData.sequenceNumber = sequenceNumber; + + // Calculate the size for encoding this in a SFLV tuple. + // S: Size needed for encoding sequence number. + node->leaf.metaData.sflSize = bejNnintEncodingSizeOfUInt(sequenceNumber); + // F: Size of the format byte is 1. + node->leaf.metaData.sflSize += BEJ_TUPLE_F_SIZE; + + // Get the payload or the URI + if (!node->expand) + { + const char* uriStr = NULL; + NULL_CHECK(nodeInfoCb->bejGetUri, "bejGetUri is NULL"); + nodeInfoCb->bejGetUri(node->resourceId, nodeInfoCb->context, &uriStr); + if (uriStr == NULL) + { + fprintf(stderr, "Failed to get uri info for rid: 0x%.8x\n", + node->resourceId); + return bejErrorInvalidNodeInput; + } + + size_t strLenWithNull = strlen(uriStr) + 1; + node->leaf.metaData.sflSize += bejNnintEncodingSizeOfUInt(strLenWithNull); + // V: Bytes used for the value. + node->leaf.metaData.vSize = strLenWithNull; + return 0; + } + + // Expand flag is present. Get the corresponding bejEncoding size. + uint32_t encodingSize; + NULL_CHECK(nodeInfoCb->bejGetBejEncodingSize, + "bejGetBejEncodingSize is NULL"); + int ret = nodeInfoCb->bejGetBejEncodingSize( + node->resourceId, nodeInfoCb->context, &encodingSize); + if (ret != 0) + { + fprintf(stderr, "Failed to get bejEncoding size for rid: 0x%.8x\n", + node->resourceId); + return bejErrorInvalidNodeInput; + } + + // Size of the value is payload + resource ID in nnint format. + size_t valueLength = encodingSize + bejNnintEncodingSizeOfUInt(node->resourceId); + // L: Length needed for the bejResourceLinkExpansion. Length is in nnint + // format. + node->leaf.metaData.sflSize += bejNnintEncodingSizeOfUInt(valueLength); + // V: Bytes used for the value. + node->leaf.metaData.vSize = valueLength; + return 0; +} + +static int bejUpdateChildMetaData( + const struct BejDictionaries* dictionaries, const uint8_t* parentDictionary, + void* childPtr, uint16_t childIndex, uint16_t dictStartingOffset, + struct BejNodeInfoCallbacks* nodeInfoCb) { struct RedfishPropertyLeaf* chNode = childPtr; switch (chNode->nodeAttr.format.principalDataType) @@ -457,6 +557,11 @@ bejUpdateNullMetaData(dictionaries, parentDictionary, childPtr, childIndex, dictStartingOffset)); break; + case bejResourceLinkExpansion: + RETURN_IF_IERROR(bejUpdateLinkExpansionMetaData( + dictionaries, parentDictionary, childPtr, childIndex, + dictStartingOffset, nodeInfoCb)); + break; default: fprintf(stderr, "Child type %u not supported\n", chNode->nodeAttr.format.principalDataType); @@ -525,7 +630,8 @@ */ static int bejProcessChildNodes(const struct BejDictionaries* dictionaries, struct RedfishPropertyParent* parent, - struct BejPointerStackCallback* stack) + struct BejPointerStackCallback* stack, + struct BejNodeInfoCallbacks* nodeInfoCb) { // Get the next child of the parent. void* childPtr = parent->metaData.nextChild; @@ -547,10 +653,10 @@ return 0; } - RETURN_IF_IERROR(bejUpdateLeafNodeMetaData( + RETURN_IF_IERROR(bejUpdateChildMetaData( dictionaries, parent->metaData.dictionary, childPtr, parent->metaData.nextChildIndex, - parent->metaData.childrenDictPropOffset)); + parent->metaData.childrenDictPropOffset, nodeInfoCb)); // Use the child value size to update the parent value size. struct RedfishPropertyLeaf* leafChild = childPtr; // V: Include the child size in parent's value size. @@ -566,7 +672,8 @@ int bejUpdateNodeMetadata(const struct BejDictionaries* dictionaries, uint16_t majorSchemaStartingOffset, struct RedfishPropertyParent* root, - struct BejPointerStackCallback* stack) + struct BejPointerStackCallback* stack, + struct BejNodeInfoCallbacks* nodeInfoCb) { // Decide the starting property offset of the dictionary. uint16_t dictOffset = bejDictGetPropertyHeadOffset(); @@ -596,7 +703,7 @@ // Calculate metadata of all the child nodes of the current parent node. // If one of these child nodes has its own child nodes, that child node // will be added to the stack and this function will return. - RETURN_IF_IERROR(bejProcessChildNodes(dictionaries, parent, stack)); + RETURN_IF_IERROR(bejProcessChildNodes(dictionaries, parent, stack, nodeInfoCb)); // If a new node hasn't been added to the stack, we know that this // parent's child nodes have been processed. If not, do not pop the
diff --git a/src/bej_tree.c b/src/bej_tree.c index fa2eb22..63d345f 100644 --- a/src/bej_tree.c +++ b/src/bej_tree.c
@@ -89,6 +89,19 @@ bejTreeLinkChildToParent(parent, child); } +void bejTreeAddUri(struct RedfishPropertyParent* parent, + struct RedfishPropertyLeafUri* child, const char* name, + uint32_t resourceId, bool expand) +{ + bejTreeInitChildNode((struct RedfishPropertyLeaf*)child, name, + bejResourceLinkExpansion); + child->resourceId = resourceId; + child->expand = expand; + bejTreeLinkChildToParent(parent, child); +} + + + void bejTreeAddReal(struct RedfishPropertyParent* parent, struct RedfishPropertyLeafReal* child, const char* name, double value)
diff --git a/test/bej_common_test.cpp b/test/bej_common_test.cpp index 7f796bd..34a8c0e 100644 --- a/test/bej_common_test.cpp +++ b/test/bej_common_test.cpp
@@ -113,7 +113,6 @@ BejReal real; double output; real.valid_exp = true; - real.exp = -1; real.whole = -2; real.zeroCount = 0;
diff --git a/test/bej_decoder_test.cpp b/test/bej_decoder_test.cpp index abdd123..01676fd 100644 --- a/test/bej_decoder_test.cpp +++ b/test/bej_decoder_test.cpp
@@ -1,7 +1,6 @@ #include "bej_common_test.hpp" #include "bej_decoder_json.hpp" #include "bej_encoder_json.hpp" -#include "bej_load_files.hpp" #include <memory> #include <string_view> @@ -62,7 +61,7 @@ TEST_P(BejDecoderTest, Decode) { const BejDecoderTestParams& test_case = GetParam(); - auto inputsOrErr = BejFileReader::loadInputs(test_case.inputFiles); + auto inputsOrErr = loadInputs(test_case.inputFiles); EXPECT_TRUE(inputsOrErr); BejDictionaries dictionaries = {
diff --git a/test/bej_encoder_test.cpp b/test/bej_encoder_test.cpp index d98de72..8f39f22 100644 --- a/test/bej_encoder_test.cpp +++ b/test/bej_encoder_test.cpp
@@ -5,7 +5,6 @@ #include "bej_common_test.hpp" #include "bej_decoder_json.hpp" #include "bej_encoder_json.hpp" -#include "bej_load_files.hpp" #include <vector> @@ -54,10 +53,18 @@ .encodedStreamFile = "../test/encoded/chassis_enc.bin", }; -int recvOutput(void* data, size_t data_size, void* handlerContext) +const BejTestInputFiles chassisCollectionTestFiles = { + .jsonFile = "../test/json/chassis_collection.json", + .schemaDictionaryFile = "../test/dictionaries/chassis_collection_dict.bin", + .annotationDictionaryFile = "../test/dictionaries/annotation_dict.bin", + .errorDictionaryFile = "", + .encodedStreamFile = nullptr, +}; + +int recvOutput(const void* data, size_t data_size, void* handlerContext) { auto stack = reinterpret_cast<std::vector<uint8_t>*>(handlerContext); - uint8_t* dataBuf = reinterpret_cast<uint8_t*>(data); + const uint8_t* dataBuf = reinterpret_cast<const uint8_t*>(data); stack->insert(stack->end(), dataBuf, dataBuf + data_size); return 0; } @@ -315,6 +322,14 @@ struct RedfishPropertyLeafString odataId; }; +/** + * @brief Storage for a single odata.id link inside a JSON "Set" object. + * + * Eg: FieldName: { + * "@odata.id": "/redfish/v1/Chassis/Something" + * } + */ + void addLinkToTree(struct RedfishPropertyParent* parent, struct RedfishPropertyParent* linkSet, const char* linkSetLabel, @@ -536,13 +551,16 @@ TEST(BejEncoderDecoderTest, EncodeDecodeDriveAction) { - auto inputsOrErr = BejFileReader::loadInputs(driveOemTestFiles); + auto inputsOrErr = loadInputs(driveOemTestFiles); EXPECT_TRUE(inputsOrErr); BejDictionaries dictionaries = { .schemaDictionary = inputsOrErr->schemaDictionary, + .schemaDictionarySize = inputsOrErr->schemaDictionarySize, .annotationDictionary = inputsOrErr->annotationDictionary, + .annotationDictionarySize = inputsOrErr->annotationDictionarySize, .errorDictionary = inputsOrErr->errorDictionary, + .errorDictionarySize = inputsOrErr->errorDictionarySize, }; std::vector<uint8_t> outputBuffer; @@ -581,4 +599,213 @@ EXPECT_TRUE(jsonDecoded.dump() == expectedJson.dump()); } +uint32_t gChassisResourceId = 0xABCDEF; +uint32_t gResourceUriContext = 1234; +const char* gResourceUri = "/redfish/v1/Chassis/SomeChassis"; + +struct redfishUriJson +{ + struct RedfishPropertyParent set; + struct RedfishPropertyLeafUri odataId; +}; + +struct RedfishPropertyParent* createChassisCollection(uint32_t resourceId, + bool expand) +{ + static struct RedfishPropertyParent root; + static struct RedfishPropertyLeafString odataId; + static struct RedfishPropertyLeafString odataType; + static struct RedfishPropertyLeafString name; + static struct RedfishArrayOfLinksJson membersArray; + static struct redfishUriJson chassisLink; + + bejTreeInitSet(&root, NULL); + bejTreeAddString(&root, &odataId, "@odata.id", "/redfish/v1/Chassis"); + bejTreeAddString(&root, &odataType, "@odata.type", + "#ChassisCollection.ChassisCollection"); + bejTreeAddString(&root, &name, "Name", "Chassis Collection"); + + redfishCreateArrayOfLinksJson(&root, "Members", /*linkCount=*/0, NULL, + &membersArray, NULL); + + bejTreeInitSet(&chassisLink.set, NULL); + bejTreeAddUri(&chassisLink.set, &chassisLink.odataId, "@odata.id", + resourceId, expand); + bejTreeLinkChildToParent(&membersArray.array, &chassisLink.set); + membersArray.count.value = 1; + + return &root; +} + +void bejGetUri(uint32_t resourceId, void* context, const char** uriStr) +{ + if ((gChassisResourceId != resourceId) || (context != &gResourceUriContext)) + { + *uriStr = NULL; + return; + } + *uriStr = gResourceUri; +} + +class BejEncodeDecodeWithExpandTest : public ::testing::Test +{ + protected: + BejEncodeDecodeWithExpandTest() + { + auto chassisColInputsOrErr = + loadInputs(chassisCollectionTestFiles); + EXPECT_TRUE(chassisColInputsOrErr); + + expectedChassisColJson_ = chassisColInputsOrErr->expectedJson; + + chassisColDictionaries_.schemaDictionary = + chassisColInputsOrErr->schemaDictionary; + chassisColDictionaries_.schemaDictionarySize = + chassisColInputsOrErr->schemaDictionarySize; + chassisColDictionaries_.annotationDictionary = + chassisColInputsOrErr->annotationDictionary; + chassisColDictionaries_.annotationDictionarySize = + chassisColInputsOrErr->annotationDictionarySize; + chassisColDictionaries_.errorDictionary = + chassisColInputsOrErr->errorDictionary; + chassisColDictionaries_.errorDictionarySize = + chassisColInputsOrErr->errorDictionarySize; + + chassisColOutputHandler_.handlerContext = &chassisColOutputBuffer_; + chassisColOutputHandler_.recvOutput = &recvOutput; + + stackCallbacks_.stackContext = &pointerStack_; + stackCallbacks_.stackEmpty = &stackEmpty; + stackCallbacks_.stackPeek = &stackPeek; + stackCallbacks_.stackPop = &stackPop; + stackCallbacks_.stackPush = &stackPush; + stackCallbacks_.deleteStack = NULL; + } + + nlohmann::json expectedChassisColJson_; + BejDictionaries chassisColDictionaries_; + std::vector<uint8_t> chassisColOutputBuffer_; + struct BejEncoderOutputHandler chassisColOutputHandler_; + std::vector<void*> pointerStack_; + struct BejPointerStackCallback stackCallbacks_; +}; + +TEST_F(BejEncodeDecodeWithExpandTest, UriNoCallbackFail) +{ + struct BejNodeInfoCallbacks nodeInfoCb = {}; + EXPECT_THAT( + bejEncodeWithExpand( + &chassisColDictionaries_, BEJ_DICTIONARY_START_AT_HEAD, + bejMajorSchemaClass, + createChassisCollection(gChassisResourceId, /*expand=*/false), + &chassisColOutputHandler_, &stackCallbacks_, &nodeInfoCb), + bejErrorNullParameter); +} + +TEST_F(BejEncodeDecodeWithExpandTest, UriInvalidResourceIdFail) +{ + struct BejNodeInfoCallbacks nodeInfoCb = {}; + nodeInfoCb.context = &gResourceUriContext; + nodeInfoCb.bejGetUri = &bejGetUri; + EXPECT_THAT( + bejEncodeWithExpand( + &chassisColDictionaries_, BEJ_DICTIONARY_START_AT_HEAD, + bejMajorSchemaClass, + createChassisCollection(gChassisResourceId + 1, /*expand=*/false), + &chassisColOutputHandler_, &stackCallbacks_, &nodeInfoCb), + bejErrorInvalidNodeInput); +} + +TEST_F(BejEncodeDecodeWithExpandTest, UriEncodeDecodeSuccess) +{ + struct BejNodeInfoCallbacks nodeInfoCb = {}; + nodeInfoCb.context = &gResourceUriContext; + nodeInfoCb.bejGetUri = &bejGetUri; + + EXPECT_THAT( + bejEncodeWithExpand( + &chassisColDictionaries_, BEJ_DICTIONARY_START_AT_HEAD, + bejMajorSchemaClass, + createChassisCollection(gChassisResourceId, /*expand=*/false), + &chassisColOutputHandler_, &stackCallbacks_, &nodeInfoCb), + 0); + + BejDecoderJson decoder; + EXPECT_THAT(decoder.decode(chassisColDictionaries_, + std::span(chassisColOutputBuffer_)), + 0); + std::string decoded = decoder.getOutput(); + nlohmann::json jsonDecoded = nlohmann::json::parse(decoded); + EXPECT_STREQ(jsonDecoded.dump().c_str(), + expectedChassisColJson_.dump().c_str()); +} + +constexpr int collectionMembers = 10; + +struct RedfishPropertyParent* createChassisCollectionWithMultipleMembers() +{ + static struct RedfishPropertyParent root; + static struct RedfishPropertyLeafString odataId; + static struct RedfishPropertyLeafString odataType; + static struct RedfishPropertyLeafString name; + static struct RedfishArrayOfLinksJson membersArray; + static struct redfishUriJson chassisLink[collectionMembers]; + + bejTreeInitSet(&root, NULL); + bejTreeAddString(&root, &odataId, "@odata.id", "/redfish/v1/Chassis"); + bejTreeAddString(&root, &odataType, "@odata.type", + "#ChassisCollection.ChassisCollection"); + bejTreeAddString(&root, &name, "Name", "Chassis Collection"); + + redfishCreateArrayOfLinksJson(&root, "Members", /*linkCount=*/0, NULL, + &membersArray, NULL); + + int resourceId = 0; + for (int i = 0; i < collectionMembers; ++i) + { + bejTreeInitSet(&chassisLink[i].set, NULL); + bejTreeAddUri(&chassisLink[i].set, &chassisLink[i].odataId, "@odata.id", + resourceId, /*expand*/ true); + bejTreeLinkChildToParent(&membersArray.array, &chassisLink[i].set); + + resourceId += 1; + } + membersArray.count.value = collectionMembers; + + return &root; +} + +int listPush(uint32_t resourceID, void* listContext) +{ + auto stack = reinterpret_cast<std::vector<uint32_t>*>(listContext); + stack->push_back(resourceID); + return 0; +} + +TEST(BejEncoderHelperTest, BejGetUriNodesToBeExpandedSuccess) +{ + std::vector<uint32_t> list; + std::vector<void*> pointerStack; + struct BejPointerStackCallback stackCallbacks = { + .stackContext = &pointerStack, + .stackEmpty = stackEmpty, + .stackPeek = stackPeek, + .stackPop = stackPop, + .stackPush = stackPush, + .deleteStack = NULL, + }; + + EXPECT_THAT( + bejGetUriNodesToBeExpanded(createChassisCollectionWithMultipleMembers(), + &stackCallbacks, &list, listPush), + 0); + + int expResourceId = 0; + for (int i = 0; i < collectionMembers; ++i) + { + EXPECT_THAT(list[i], expResourceId); + expResourceId += 1; + } +} + } // namespace libbej
diff --git a/test/dictionaries/chassis_collection_dict.bin b/test/dictionaries/chassis_collection_dict.bin new file mode 100644 index 0000000..2746389 --- /dev/null +++ b/test/dictionaries/chassis_collection_dict.bin Binary files differ
diff --git a/test/include/bej_common_test.hpp b/test/include/bej_common_test.hpp index 9d6165d..9b0fc45 100644 --- a/test/include/bej_common_test.hpp +++ b/test/include/bej_common_test.hpp
@@ -86,11 +86,15 @@ } static uint8_t encBuffer[maxBufferSize]; - auto encLen = readBinaryFile(files.encodedStreamFile, - std::span(encBuffer, maxBufferSize)); - if (encLen == 0) + std::streamsize encLen = 0; + if (files.encodedStreamFile != nullptr) { - return std::nullopt; + encLen = readBinaryFile(files.encodedStreamFile, + std::span(encBuffer, maxBufferSize)); + if (encLen == 0) + { + return std::nullopt; + } } static uint8_t errorDict[maxBufferSize];
diff --git a/test/json/chassis_collection.json b/test/json/chassis_collection.json new file mode 100644 index 0000000..2b0ab19 --- /dev/null +++ b/test/json/chassis_collection.json
@@ -0,0 +1,11 @@ +{ + "@odata.id": "/redfish/v1/Chassis", + "@odata.type": "#ChassisCollection.ChassisCollection", + "Members": [ + { + "@odata.id": "/redfish/v1/Chassis/SomeChassis" + } + ], + "Members@odata.count": 1, + "Name": "Chassis Collection" +}
diff --git a/test/meson.build b/test/meson.build index 1d1272a..0dc6f2d 100644 --- a/test/meson.build +++ b/test/meson.build
@@ -7,18 +7,14 @@ gmock = dependency('gmock', disabler: true, required: get_option('tests')) if not gtest.found() or not gmock.found() - gtest_proj = import('cmake').subproject('googletest', required: false) - if gtest_proj.found() - gtest = declare_dependency( - dependencies: [ - gtest_proj.dependency('gtest'), - gtest_proj.dependency('gtest_main'), - ], - ) - gmock = gtest_proj.dependency('gmock') - else - assert(not get_option('tests').allowed(), 'Googletest is required') - endif + gtest_proj = subproject('gtest') + gtest = declare_dependency( + dependencies: [ + gtest_proj.get_variable('gtest_dep'), + gtest_proj.get_variable('gtest_main_dep'), + ], + ) + gmock = gtest_proj.get_variable('gmock_dep') endif test_dep = declare_dependency(