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(&params, 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(