Add libbej encoder wrapper for C++
Add unit tests and the test data files needed for unit tests.
Move APIs from bej_decoder_test.cpp to a common file, to share those
with bej_encoder_test.cpp
Tested:
Verified that encoding using the wrapper works well at runtime.
Verified that the unit tests pass.
Change-Id: I61b7c17690eb7e7fefd1973a22d4316c6214267e
Signed-off-by: Nikhil Namjoshi <nikhilnamjoshi@google.com>
diff --git a/include/libbej/bej_encoder_json.hpp b/include/libbej/bej_encoder_json.hpp
new file mode 100644
index 0000000..79c0e18
--- /dev/null
+++ b/include/libbej/bej_encoder_json.hpp
@@ -0,0 +1,92 @@
+#pragma once
+
+#include "bej_common.h"
+#include "bej_encoder_core.h"
+
+#include <vector>
+
+namespace libbej
+{
+
+/**
+ * @brief Callback for stackEmpty. Check if stack is empty
+ *
+ * @param[in] dataPtr - pointer to a valid stack of type std::vector<void*>
+ * @return true if the stack is empty.
+ */
+
+bool stackEmpty(void* dataPtr);
+
+/**
+ * @brief Callback for stackPeek. Read the first element from the stack
+ *
+ * @param[in] dataPtr - pointer to a valid stack of type std::vector<void*>
+ * @return the value of first element in the stack
+ */
+
+void* stackPeek(void* dataPtr);
+
+/**
+ * @brief Callback for stackPop. Remove the top element from the stack.
+ *
+ * @param[in] dataPtr - pointer to a valid stack of type std::vector<void*>
+ * @return the value of first element in the stack
+ */
+
+void* stackPop(void* dataPtr);
+
+/**
+ * @brief Callback for stackPush. Push a new element to the top of the stack.
+ *
+ * @param[in] property - property to push.
+ * @param[in] dataPtr - pointer to a valid stack of type std::vector<void*>
+ * @return 0 if successful.
+ */
+
+int stackPush(void* property, void* dataPtr);
+
+/**
+ * @brief Callback to get the encoded json payload.
+ *
+ * @param[in] data - pointer to a valid stack of type std::vector<void*>
+ * @param[in] dataSize - size of the stack
+ * @param[in] handlerContext - Buffer to store the payload
+ * @return 0 if successful.
+ */
+
+int getBejEncodedBuffer(const void* data, size_t dataSize,
+ void* handlerContext);
+
+/**
+ * @brief Class for encoding JSON input.
+ */
+class BejEncoderJson
+{
+ public:
+ /**
+ * @brief Encode the resource data.
+ *
+ * @param[in] dictionaries - dictionaries needed for encoding.
+ * @param[in] schemaClass - BEJ schema class.
+ * @param[in] root - pointer to a RedfishPropertyParent struct.
+ * @return 0 if successful.
+ */
+ int encode(const struct BejDictionaries* dictionaries,
+ enum BejSchemaClass schemaClass,
+ struct RedfishPropertyParent* root);
+
+ /**
+ * @brief Get the JSON encoded payload.
+ *
+ * @return std::vector<uint8_t> containing encoded JSON bytes. If the
+ * encoding was unsuccessful, the vector will be empty. Note that the
+ * vector resource will be moved to the requester API
+ */
+ std::vector<uint8_t> getOutput();
+
+ private:
+ std::vector<uint8_t> encodedPayload;
+ std::vector<void*> stack;
+};
+
+} // namespace libbej
diff --git a/include/libbej/meson.build b/include/libbej/meson.build
index ba8afc5..6866643 100644
--- a/include/libbej/meson.build
+++ b/include/libbej/meson.build
@@ -4,7 +4,8 @@
'bej_dictionary.h',
'bej_common.h',
'bej_encoder_core.h',
- 'bej_encoder_metadata.h'
+ 'bej_encoder_metadata.h',
+ 'bej_encoder_json.hpp'
)
install_headers(
diff --git a/src/bej_encoder_json.cpp b/src/bej_encoder_json.cpp
new file mode 100644
index 0000000..8117f76
--- /dev/null
+++ b/src/bej_encoder_json.cpp
@@ -0,0 +1,80 @@
+#include "bej_encoder_json.hpp"
+
+namespace libbej
+{
+
+bool stackEmpty(void* dataPtr)
+{
+ return (reinterpret_cast<std::vector<void*>*>(dataPtr))->empty();
+}
+
+void* stackPeek(void* dataPtr)
+{
+ auto stack = reinterpret_cast<std::vector<void*>*>(dataPtr);
+ if (stack->empty())
+ {
+ return nullptr;
+ }
+ return stack->back();
+}
+
+void* stackPop(void* dataPtr)
+{
+ auto stack = reinterpret_cast<std::vector<void*>*>(dataPtr);
+ if (stack->empty())
+ {
+ return nullptr;
+ }
+ void* value = stack->back();
+ stack->pop_back();
+ return value;
+}
+
+int stackPush(void* property, void* dataPtr)
+{
+ auto stack = reinterpret_cast<std::vector<void*>*>(dataPtr);
+ stack->emplace_back(property);
+ return 0;
+}
+
+int getBejEncodedBuffer(const void* data, size_t dataSize, void* handlerContext)
+{
+ auto stack = reinterpret_cast<std::vector<uint8_t>*>(handlerContext);
+ const uint8_t* dataBuf = reinterpret_cast<const uint8_t*>(data);
+ stack->insert(stack->end(), dataBuf, dataBuf + dataSize);
+ return 0;
+}
+
+std::vector<uint8_t> BejEncoderJson::getOutput()
+{
+ std::vector<uint8_t> currentEncodedPayload = std::move(encodedPayload);
+ // Re-Initialize encodedPayload with empty vector to be used again for
+ // next encoding
+ encodedPayload = {};
+
+ return currentEncodedPayload;
+}
+
+int BejEncoderJson::encode(const struct BejDictionaries* dictionaries,
+ enum BejSchemaClass schemaClass,
+ struct RedfishPropertyParent* root)
+{
+ struct BejEncoderOutputHandler output = {
+ .handlerContext = &encodedPayload,
+ .recvOutput = &getBejEncodedBuffer,
+ };
+
+ struct BejPointerStackCallback stackCallbacks = {
+ .stackContext = &stack,
+ .stackEmpty = stackEmpty,
+ .stackPeek = stackPeek,
+ .stackPop = stackPop,
+ .stackPush = stackPush,
+ .deleteStack = nullptr,
+ };
+
+ return bejEncode(dictionaries, BEJ_DICTIONARY_START_AT_HEAD, schemaClass,
+ root, &output, &stackCallbacks);
+}
+
+} // namespace libbej
diff --git a/src/meson.build b/src/meson.build
index 4876d80..b7f0706 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -7,6 +7,7 @@
'bej_encoder_core.c',
'bej_encoder_metadata.c',
'bej_decoder_json.cpp',
+ 'bej_encoder_json.cpp',
include_directories : libbej_incs,
implicit_include_directories: false,
version: meson.project_version(),
diff --git a/test/bej_decoder_test.cpp b/test/bej_decoder_test.cpp
index b390017..16cd19a 100644
--- a/test/bej_decoder_test.cpp
+++ b/test/bej_decoder_test.cpp
@@ -1,11 +1,7 @@
+#include "bej_common_test.hpp"
#include "bej_decoder_json.hpp"
-#include "nlohmann/json.hpp"
-#include <fstream>
-#include <iostream>
#include <memory>
-#include <optional>
-#include <span>
#include <string_view>
#include <gmock/gmock-matchers.h>
@@ -15,24 +11,6 @@
namespace libbej
{
-struct BejTestInputFiles
-{
- const char* jsonFile;
- const char* schemaDictionaryFile;
- const char* annotationDictionaryFile;
- const char* errorDictionaryFile;
- const char* encodedStreamFile;
-};
-
-struct BejTestInputs
-{
- const nlohmann::json expectedJson;
- const uint8_t* schemaDictionary;
- const uint8_t* annotationDictionary;
- const uint8_t* errorDictionary;
- std::span<const uint8_t> encodedStream;
-};
-
struct BejDecoderTestParams
{
const std::string testName;
@@ -73,82 +51,6 @@
.encodedStreamFile = "../test/encoded/dummy_simple_enc.bin",
};
-// Buffer size for storing a single binary file data.
-constexpr uint32_t maxBufferSize = 16 * 1024;
-
-std::streamsize readBinaryFile(const char* fileName, std::span<uint8_t> buffer)
-{
- std::ifstream inputStream(fileName, std::ios::binary);
- if (!inputStream.is_open())
- {
- std::cerr << "Cannot open file: " << fileName << "\n";
- return 0;
- }
- auto readLength = inputStream.readsome(
- reinterpret_cast<char*>(buffer.data()), buffer.size_bytes());
- if (inputStream.peek() != EOF)
- {
- std::cerr << "Failed to read the complete file: " << fileName
- << " read length: " << readLength << "\n";
- return 0;
- }
- return readLength;
-}
-
-std::optional<BejTestInputs> loadInputs(const BejTestInputFiles& files,
- bool readErrorDictionary = false)
-{
- std::ifstream jsonInput(files.jsonFile);
- if (!jsonInput.is_open())
- {
- std::cerr << "Cannot open file: " << files.jsonFile << "\n";
- return std::nullopt;
- }
- nlohmann::json expJson;
- jsonInput >> expJson;
-
- static uint8_t schemaDictBuffer[maxBufferSize];
- if (readBinaryFile(files.schemaDictionaryFile,
- std::span(schemaDictBuffer, maxBufferSize)) == 0)
- {
- return std::nullopt;
- }
-
- static uint8_t annoDictBuffer[maxBufferSize];
- if (readBinaryFile(files.annotationDictionaryFile,
- std::span(annoDictBuffer, maxBufferSize)) == 0)
- {
- return std::nullopt;
- }
-
- static uint8_t encBuffer[maxBufferSize];
- auto encLen = readBinaryFile(files.encodedStreamFile,
- std::span(encBuffer, maxBufferSize));
- if (encLen == 0)
- {
- return std::nullopt;
- }
-
- static uint8_t errorDict[maxBufferSize];
- if (readErrorDictionary)
- {
- if (readBinaryFile(files.errorDictionaryFile,
- std::span(errorDict, maxBufferSize)) == 0)
- {
- return std::nullopt;
- }
- }
-
- BejTestInputs inputs = {
- .expectedJson = expJson,
- .schemaDictionary = schemaDictBuffer,
- .annotationDictionary = annoDictBuffer,
- .errorDictionary = errorDict,
- .encodedStream = std::span(encBuffer, encLen),
- };
- return inputs;
-}
-
TEST_P(BejDecoderTest, Decode)
{
const BejDecoderTestParams& test_case = GetParam();
diff --git a/test/bej_encoder_test.cpp b/test/bej_encoder_test.cpp
new file mode 100644
index 0000000..d258b4b
--- /dev/null
+++ b/test/bej_encoder_test.cpp
@@ -0,0 +1,442 @@
+#include "bej_dictionary.h"
+#include "bej_encoder_core.h"
+#include "bej_tree.h"
+
+#include "bej_common_test.hpp"
+#include "bej_decoder_json.hpp"
+#include "bej_encoder_json.hpp"
+
+#include <vector>
+
+#include <gmock/gmock-matchers.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace libbej
+{
+
+struct BejEncoderTestParams
+{
+ const std::string testName;
+ const BejTestInputFiles inputFiles;
+ std::string expectedJson;
+ struct RedfishPropertyParent* (*createResource)();
+};
+
+using BejEncoderTest = testing::TestWithParam<BejEncoderTestParams>;
+
+const BejTestInputFiles dummySimpleTestFiles = {
+ .jsonFile = "../test/json/dummysimple.json",
+ .schemaDictionaryFile = "../test/dictionaries/dummy_simple_dict.bin",
+ .annotationDictionaryFile = "../test/dictionaries/annotation_dict.bin",
+ .errorDictionaryFile = "",
+ .encodedStreamFile = "../test/encoded/dummy_simple_enc.bin",
+};
+
+const BejTestInputFiles driveOemTestFiles = {
+ .jsonFile = "../test/json/drive_oem.json",
+ .schemaDictionaryFile = "../test/dictionaries/drive_oem_dict.bin",
+ .annotationDictionaryFile = "../test/dictionaries/annotation_dict.bin",
+ .errorDictionaryFile = "",
+ .encodedStreamFile = "../test/encoded/drive_oem_enc.bin",
+};
+
+const BejTestInputFiles chassisTestFiles = {
+ .jsonFile = "../test/json/chassis.json",
+ .schemaDictionaryFile = "../test/dictionaries/chassis_dict.bin",
+ .annotationDictionaryFile = "../test/dictionaries/annotation_dict.bin",
+ .errorDictionaryFile = "",
+ .encodedStreamFile = "../test/encoded/chassis_enc.bin",
+};
+
+struct RedfishPropertyParent* createDummyResource()
+{
+ static struct RedfishPropertyParent root;
+ bejTreeInitSet(&root, "DummySimple");
+
+ static struct RedfishPropertyLeafString id;
+ bejTreeAddString(&root, &id, "Id", "Dummy ID");
+
+ static struct RedfishPropertyLeafInt intProp;
+ bejTreeAddInteger(&root, &intProp, "SampleIntegerProperty", -5);
+
+ static struct RedfishPropertyLeafReal real;
+ bejTreeAddReal(&root, &real, "SampleRealProperty", -5576.90001);
+
+ static struct RedfishPropertyLeafNull enProp;
+ bejTreeAddNull(&root, &enProp, "SampleEnabledProperty");
+
+ static struct RedfishPropertyParent chArraySet1;
+ bejTreeInitSet(&chArraySet1, nullptr);
+
+ static struct RedfishPropertyLeafBool chArraySet1bool;
+ bejTreeAddBool(&chArraySet1, &chArraySet1bool, "AnotherBoolean", true);
+
+ static struct RedfishPropertyLeafEnum chArraySet1Ls;
+ bejTreeAddEnum(&chArraySet1, &chArraySet1Ls, "LinkStatus", "NoLink");
+
+ static struct RedfishPropertyParent chArraySet2;
+ bejTreeInitSet(&chArraySet2, nullptr);
+
+ static struct RedfishPropertyLeafEnum chArraySet2Ls;
+ bejTreeAddEnum(&chArraySet2, &chArraySet2Ls, "LinkStatus", "LinkDown");
+
+ static struct RedfishPropertyParent chArray;
+ bejTreeInitArray(&chArray, "ChildArrayProperty");
+
+ bejTreeLinkChildToParent(&chArray, &chArraySet1);
+ bejTreeLinkChildToParent(&chArray, &chArraySet2);
+
+ bejTreeLinkChildToParent(&root, &chArray);
+ return &root;
+}
+
+const std::string driveOemJson = R"(
+ {
+ "@odata.id": "/redfish/v1/drives/1",
+ "@odata.type": "#Drive.v1_5_0.Drive",
+ "Id": "Drive1",
+ "Actions": {
+ "#Drive.Reset": {
+ "target": "/redfish/v1/drives/1/Actions/Drive.Reset",
+ "title": "Reset a Drive",
+ "ResetType@Redfish.AllowableValues": [
+ "On",
+ "ForceOff",
+ "ForceRestart",
+ "Nmi",
+ "ForceOn",
+ "PushPowerButton"
+ ]
+ }
+ },
+ "Status@Message.ExtendedInfo": [
+ {
+ "MessageId": "PredictiveFailure",
+ "RelatedProperties": ["FailurePredicted", "MediaType"]
+ }
+ ],
+ "Identifiers": [],
+ "Links": {}
+ }
+)";
+
+struct RedfishPropertyParent* createDriveOem()
+{
+ static struct RedfishPropertyParent root;
+ bejTreeInitSet(&root, "Drive");
+
+ static struct RedfishPropertyLeafString odataId;
+ bejTreeAddString(&root, &odataId, "@odata.id", "/redfish/v1/drives/1");
+
+ static struct RedfishPropertyLeafString odataType;
+ bejTreeAddString(&root, &odataType, "@odata.type", "#Drive.v1_5_0.Drive");
+
+ static struct RedfishPropertyLeafString id;
+ bejTreeAddString(&root, &id, "Id", "Drive1");
+
+ static struct RedfishPropertyParent actions;
+ bejTreeInitSet(&actions, "Actions");
+
+ static struct RedfishPropertyParent drRst;
+ bejTreeInitSet(&drRst, "#Drive.Reset");
+
+ static struct RedfishPropertyLeafString drRstTarget;
+ bejTreeAddString(&drRst, &drRstTarget, "target",
+ "/redfish/v1/drives/1/Actions/Drive.Reset");
+
+ static struct RedfishPropertyLeafString drRstTitle;
+ bejTreeAddString(&drRst, &drRstTitle, "title", "Reset a Drive");
+
+ static struct RedfishPropertyParent drRstType;
+ bejTreeInitPropertyAnnotated(&drRstType, "ResetType");
+
+ static struct RedfishPropertyParent drRstTypeAllowable;
+ bejTreeInitArray(&drRstTypeAllowable, "@Redfish.AllowableValues");
+
+ static struct RedfishPropertyLeafString drRstTypeAllowableS1;
+ bejTreeAddString(&drRstTypeAllowable, &drRstTypeAllowableS1, "", "On");
+
+ static struct RedfishPropertyLeafString drRstTypeAllowableS2;
+ bejTreeAddString(&drRstTypeAllowable, &drRstTypeAllowableS2, "",
+ "ForceOff");
+
+ static struct RedfishPropertyLeafString drRstTypeAllowableS3;
+ bejTreeAddString(&drRstTypeAllowable, &drRstTypeAllowableS3, "",
+ "ForceRestart");
+
+ static struct RedfishPropertyLeafString drRstTypeAllowableS4;
+ bejTreeAddString(&drRstTypeAllowable, &drRstTypeAllowableS4, "", "Nmi");
+
+ static struct RedfishPropertyLeafString drRstTypeAllowableS5;
+ bejTreeAddString(&drRstTypeAllowable, &drRstTypeAllowableS5, "", "ForceOn");
+
+ static struct RedfishPropertyLeafString drRstTypeAllowableS6;
+ bejTreeAddString(&drRstTypeAllowable, &drRstTypeAllowableS6, "",
+ "PushPowerButton");
+
+ bejTreeLinkChildToParent(&drRstType, &drRstTypeAllowable);
+ bejTreeLinkChildToParent(&drRst, &drRstType);
+ bejTreeLinkChildToParent(&actions, &drRst);
+ bejTreeLinkChildToParent(&root, &actions);
+
+ static struct RedfishPropertyParent statusAnt;
+ bejTreeInitPropertyAnnotated(&statusAnt, "Status");
+
+ static struct RedfishPropertyParent statusAntMsgExtInfo;
+ bejTreeInitArray(&statusAntMsgExtInfo, "@Message.ExtendedInfo");
+
+ static struct RedfishPropertyParent statusAntMsgExtInfoSet1;
+ bejTreeInitSet(&statusAntMsgExtInfoSet1, nullptr);
+
+ static struct RedfishPropertyLeafString statusAntMsgExtInfoSet1P1;
+ bejTreeAddString(&statusAntMsgExtInfoSet1, &statusAntMsgExtInfoSet1P1,
+ "MessageId", "PredictiveFailure");
+
+ static struct RedfishPropertyParent statusAntMsgExtInfoSet1P2;
+ bejTreeInitArray(&statusAntMsgExtInfoSet1P2, "RelatedProperties");
+ bejTreeLinkChildToParent(&statusAntMsgExtInfoSet1,
+ &statusAntMsgExtInfoSet1P2);
+
+ static struct RedfishPropertyLeafString statusAntMsgExtInfoSet1P2Ele1;
+ bejTreeAddString(&statusAntMsgExtInfoSet1P2, &statusAntMsgExtInfoSet1P2Ele1,
+ "", "FailurePredicted");
+
+ static struct RedfishPropertyLeafString statusAntMsgExtInfoSet1P2Ele2;
+ bejTreeAddString(&statusAntMsgExtInfoSet1P2, &statusAntMsgExtInfoSet1P2Ele2,
+ "", "MediaType");
+
+ bejTreeLinkChildToParent(&statusAntMsgExtInfo, &statusAntMsgExtInfoSet1);
+ bejTreeLinkChildToParent(&statusAnt, &statusAntMsgExtInfo);
+ bejTreeLinkChildToParent(&root, &statusAnt);
+
+ static struct RedfishPropertyParent identifiers;
+ bejTreeInitArray(&identifiers, "Identifiers");
+ bejTreeLinkChildToParent(&root, &identifiers);
+
+ static struct RedfishPropertyParent links;
+ bejTreeInitSet(&links, "Links");
+ bejTreeLinkChildToParent(&root, &links);
+
+ return &root;
+}
+
+/**
+ * @brief Storage for an array of links with an annotated odata.count.
+ *
+ * This doesn't contain storage for the link itself.
+ *
+ * Eg:
+ * "Contains": [],
+ * "Contains@odata.count": 0,
+ */
+struct RedfishArrayOfLinksJson
+{
+ struct RedfishPropertyParent array;
+ struct RedfishPropertyParent annotatedProperty;
+ struct RedfishPropertyLeafInt count;
+};
+
+/**
+ * @brief Storage for a single odata.id link inside a JSON "Set" object.
+ *
+ * Eg: FieldName: {
+ * "@odata.id": "/redfish/v1/Chassis/Something"
+ * }
+ */
+struct RedfishLinkJson
+{
+ struct RedfishPropertyParent set;
+ struct RedfishPropertyLeafString odataId;
+};
+
+void addLinkToTree(struct RedfishPropertyParent* parent,
+ struct RedfishPropertyParent* linkSet,
+ const char* linkSetLabel,
+ struct RedfishPropertyLeafString* odataId,
+ const char* linkValue)
+{
+ bejTreeInitSet(linkSet, linkSetLabel);
+ bejTreeAddString(linkSet, odataId, "@odata.id", linkValue);
+ bejTreeLinkChildToParent(parent, linkSet);
+}
+
+void redfishCreateArrayOfLinksJson(struct RedfishPropertyParent* parent,
+ const char* arrayName, int linkCount,
+ const char* const links[],
+ struct RedfishArrayOfLinksJson* linksInfo,
+ struct RedfishLinkJson* linkJsonArray)
+{
+ bejTreeInitArray(&linksInfo->array, arrayName);
+ bejTreeLinkChildToParent(parent, &linksInfo->array);
+
+ bejTreeInitPropertyAnnotated(&linksInfo->annotatedProperty, arrayName);
+ bejTreeAddInteger(&linksInfo->annotatedProperty, &linksInfo->count,
+ "@odata.count", linkCount);
+ bejTreeLinkChildToParent(parent, &linksInfo->annotatedProperty);
+
+ for (int i = 0; i < linkCount; ++i)
+ {
+ addLinkToTree(&linksInfo->array, &linkJsonArray[i].set, NULL,
+ &linkJsonArray[i].odataId, links[i]);
+ }
+}
+
+struct RedfishPropertyParent* createChassisResource()
+{
+ constexpr int containsLinkCount = 2;
+ const char* contains[containsLinkCount] = {"/redfish/v1/Chassis/Disk_0",
+ "/redfish/v1/Chassis/Disk_1"};
+ const char* storage[1] = {"/redfish/v1/Systems/system/Storage/SATA"};
+ const char* drives[1] = {"/redfish/v1/Chassis/SomeChassis/Drives/SATA_0"};
+ static struct RedfishPropertyParent root;
+ static struct RedfishPropertyLeafString odataId;
+ static struct RedfishPropertyParent links;
+ static struct RedfishPropertyParent computerSystemsArray;
+ static struct RedfishPropertyParent computerSystemsLinkSet;
+ static struct RedfishPropertyLeafString computerSystemsLinkOdataId;
+ static struct RedfishPropertyParent containedBySet;
+ static struct RedfishPropertyLeafString containedByOdataId;
+ static struct RedfishArrayOfLinksJson containsArray;
+ static struct RedfishLinkJson containsLinks[containsLinkCount];
+ static struct RedfishArrayOfLinksJson storageArray;
+ static struct RedfishLinkJson storageLink;
+ static struct RedfishArrayOfLinksJson drives_array;
+ static struct RedfishLinkJson drive_link;
+
+ bejTreeInitSet(&root, "Chassis");
+ bejTreeAddString(&root, &odataId, "@odata.id",
+ "/redfish/v1/Chassis/SomeChassis");
+
+ bejTreeInitSet(&links, "Links");
+ bejTreeLinkChildToParent(&root, &links);
+
+ bejTreeInitArray(&computerSystemsArray, "ComputerSystems");
+ bejTreeLinkChildToParent(&links, &computerSystemsArray);
+
+ addLinkToTree(&computerSystemsArray, &computerSystemsLinkSet, "",
+ &computerSystemsLinkOdataId, "/redfish/v1/Systems/system");
+
+ addLinkToTree(&links, &containedBySet, "ContainedBy", &containedByOdataId,
+ "/redfish/v1/Chassis/SomeOtherChassis");
+
+ redfishCreateArrayOfLinksJson(&links, "Contains", containsLinkCount,
+ contains, &containsArray, containsLinks);
+
+ redfishCreateArrayOfLinksJson(&links, "Storage", /*linkCount=*/1, storage,
+ &storageArray, &storageLink);
+
+ redfishCreateArrayOfLinksJson(&links, "Drives", /*linkCount=*/1, drives,
+ &drives_array, &drive_link);
+
+ return &root;
+}
+
+TEST_P(BejEncoderTest, Encode)
+{
+ const BejEncoderTestParams& test_case = GetParam();
+ auto inputsOrErr = loadInputs(test_case.inputFiles);
+ EXPECT_TRUE(inputsOrErr);
+
+ BejDictionaries dictionaries = {
+ .schemaDictionary = inputsOrErr->schemaDictionary,
+ .annotationDictionary = inputsOrErr->annotationDictionary,
+ .errorDictionary = inputsOrErr->errorDictionary,
+ };
+
+ std::vector<uint8_t> outputBuffer;
+ struct BejEncoderOutputHandler output = {
+ .handlerContext = &outputBuffer,
+ .recvOutput = &getBejEncodedBuffer,
+ };
+
+ std::vector<void*> pointerStack;
+ struct BejPointerStackCallback stackCallbacks = {
+ .stackContext = &pointerStack,
+ .stackEmpty = stackEmpty,
+ .stackPeek = stackPeek,
+ .stackPop = stackPop,
+ .stackPush = stackPush,
+ .deleteStack = NULL,
+ };
+
+ bejEncode(&dictionaries, BEJ_DICTIONARY_START_AT_HEAD, bejMajorSchemaClass,
+ test_case.createResource(), &output, &stackCallbacks);
+
+ BejDecoderJson decoder;
+ EXPECT_THAT(decoder.decode(dictionaries, std::span(outputBuffer)), 0);
+ std::string decoded = decoder.getOutput();
+ nlohmann::json jsonDecoded = nlohmann::json::parse(decoded);
+
+ if (!test_case.expectedJson.empty())
+ {
+ inputsOrErr->expectedJson =
+ nlohmann::json::parse(test_case.expectedJson);
+ }
+ EXPECT_TRUE(jsonDecoded.dump() == inputsOrErr->expectedJson.dump());
+}
+
+TEST_P(BejEncoderTest, EncodeWrapper)
+{
+ const BejEncoderTestParams& test_case = GetParam();
+ auto inputsOrErr = loadInputs(test_case.inputFiles);
+ EXPECT_TRUE(inputsOrErr);
+
+ BejDictionaries dictionaries = {
+ .schemaDictionary = inputsOrErr->schemaDictionary,
+ .annotationDictionary = inputsOrErr->annotationDictionary,
+ .errorDictionary = inputsOrErr->errorDictionary,
+ };
+
+ libbej::BejEncoderJson encoder;
+ encoder.encode(&dictionaries, bejMajorSchemaClass,
+ test_case.createResource());
+
+ std::vector<uint8_t> outputBuffer = encoder.getOutput();
+ BejDecoderJson decoder;
+ EXPECT_THAT(decoder.decode(dictionaries, std::span(outputBuffer)), 0);
+ std::string decoded = decoder.getOutput();
+ nlohmann::json jsonDecoded = nlohmann::json::parse(decoded);
+
+ if (!test_case.expectedJson.empty())
+ {
+ inputsOrErr->expectedJson =
+ nlohmann::json::parse(test_case.expectedJson);
+ }
+ EXPECT_TRUE(jsonDecoded.dump() == inputsOrErr->expectedJson.dump());
+
+ // Try using the same encoder object again to ensure that the same object
+ // does the encoding correctly
+ encoder.encode(&dictionaries, bejMajorSchemaClass,
+ test_case.createResource());
+
+ outputBuffer = encoder.getOutput();
+
+ EXPECT_THAT(decoder.decode(dictionaries, std::span(outputBuffer)), 0);
+ decoded = decoder.getOutput();
+ jsonDecoded = nlohmann::json::parse(decoded);
+
+ if (!test_case.expectedJson.empty())
+ {
+ inputsOrErr->expectedJson =
+ nlohmann::json::parse(test_case.expectedJson);
+ }
+ EXPECT_TRUE(jsonDecoded.dump() == inputsOrErr->expectedJson.dump());
+}
+
+/**
+ * TODO: Add more test cases.
+ */
+
+INSTANTIATE_TEST_SUITE_P(
+ , BejEncoderTest,
+ testing::ValuesIn<BejEncoderTestParams>({
+ {"DriveOEM", driveOemTestFiles, driveOemJson, &createDriveOem},
+ {"DummySimple", dummySimpleTestFiles, "", &createDummyResource},
+ {"Chassis", chassisTestFiles, "", &createChassisResource},
+ }),
+ [](const testing::TestParamInfo<BejEncoderTest::ParamType>& info) {
+ return info.param.testName;
+});
+
+} // namespace libbej
diff --git a/test/dictionaries/chassis_dict.bin b/test/dictionaries/chassis_dict.bin
new file mode 100644
index 0000000..88b3b4d
--- /dev/null
+++ b/test/dictionaries/chassis_dict.bin
Binary files differ
diff --git a/test/encoded/chassis_enc.bin b/test/encoded/chassis_enc.bin
new file mode 100644
index 0000000..a4d4172
--- /dev/null
+++ b/test/encoded/chassis_enc.bin
Binary files differ
diff --git a/test/include/bej_common_test.hpp b/test/include/bej_common_test.hpp
new file mode 100644
index 0000000..74d8c83
--- /dev/null
+++ b/test/include/bej_common_test.hpp
@@ -0,0 +1,109 @@
+#include "bej_dictionary.h"
+#include "bej_encoder_core.h"
+#include "bej_tree.h"
+
+#include "nlohmann/json.hpp"
+
+#include <fstream>
+#include <iostream>
+#include <optional>
+#include <span>
+
+namespace libbej
+{
+
+// Buffer size for storing a single binary file data.
+constexpr uint32_t maxBufferSize = 16 * 1024;
+
+struct BejTestInputFiles
+{
+ const char* jsonFile;
+ const char* schemaDictionaryFile;
+ const char* annotationDictionaryFile;
+ const char* errorDictionaryFile;
+ const char* encodedStreamFile;
+};
+
+struct BejTestInputs
+{
+ nlohmann::json expectedJson;
+ const uint8_t* schemaDictionary;
+ const uint8_t* annotationDictionary;
+ const uint8_t* errorDictionary;
+ std::span<const uint8_t> encodedStream;
+};
+
+std::streamsize readBinaryFile(const char* fileName, std::span<uint8_t> buffer)
+{
+ std::ifstream inputStream(fileName, std::ios::binary);
+ if (!inputStream.is_open())
+ {
+ std::cerr << "Cannot open file: " << fileName << "\n";
+ return 0;
+ }
+ auto readLength = inputStream.readsome(
+ reinterpret_cast<char*>(buffer.data()), buffer.size_bytes());
+ if (inputStream.peek() != EOF)
+ {
+ std::cerr << "Failed to read the complete file: " << fileName
+ << " read length: " << readLength << "\n";
+ return 0;
+ }
+ return readLength;
+}
+
+std::optional<BejTestInputs> loadInputs(const BejTestInputFiles& files,
+ bool readErrorDictionary = false)
+{
+ std::ifstream jsonInput(files.jsonFile);
+ if (!jsonInput.is_open())
+ {
+ std::cerr << "Cannot open file: " << files.jsonFile << "\n";
+ return std::nullopt;
+ }
+ nlohmann::json expJson;
+ jsonInput >> expJson;
+
+ static uint8_t schemaDictBuffer[maxBufferSize];
+ if (readBinaryFile(files.schemaDictionaryFile,
+ std::span(schemaDictBuffer, maxBufferSize)) == 0)
+ {
+ return std::nullopt;
+ }
+
+ static uint8_t annoDictBuffer[maxBufferSize];
+ if (readBinaryFile(files.annotationDictionaryFile,
+ std::span(annoDictBuffer, maxBufferSize)) == 0)
+ {
+ return std::nullopt;
+ }
+
+ static uint8_t encBuffer[maxBufferSize];
+ auto encLen = readBinaryFile(files.encodedStreamFile,
+ std::span(encBuffer, maxBufferSize));
+ if (encLen == 0)
+ {
+ return std::nullopt;
+ }
+
+ static uint8_t errorDict[maxBufferSize];
+ if (readErrorDictionary)
+ {
+ if (readBinaryFile(files.errorDictionaryFile,
+ std::span(errorDict, maxBufferSize)) == 0)
+ {
+ return std::nullopt;
+ }
+ }
+
+ BejTestInputs inputs = {
+ .expectedJson = expJson,
+ .schemaDictionary = schemaDictBuffer,
+ .annotationDictionary = annoDictBuffer,
+ .errorDictionary = errorDict,
+ .encodedStream = std::span(encBuffer, encLen),
+ };
+ return inputs;
+}
+
+} // namespace libbej
diff --git a/test/json/chassis.json b/test/json/chassis.json
new file mode 100644
index 0000000..40cc88d
--- /dev/null
+++ b/test/json/chassis.json
@@ -0,0 +1,34 @@
+{
+ "@odata.id": "/redfish/v1/Chassis/SomeChassis",
+ "Links": {
+ "ComputerSystems": [
+ {
+ "@odata.id": "/redfish/v1/Systems/system"
+ }
+ ],
+ "ContainedBy": {
+ "@odata.id": "/redfish/v1/Chassis/SomeOtherChassis"
+ },
+ "Contains": [
+ {
+ "@odata.id": "/redfish/v1/Chassis/Disk_0"
+ },
+ {
+ "@odata.id": "/redfish/v1/Chassis/Disk_1"
+ }
+ ],
+ "Contains@odata.count": 2,
+ "Storage": [
+ {
+ "@odata.id": "/redfish/v1/Systems/system/Storage/SATA"
+ }
+ ],
+ "Storage@odata.count": 1,
+ "Drives": [
+ {
+ "@odata.id": "/redfish/v1/Chassis/SomeChassis/Drives/SATA_0"
+ }
+ ],
+ "Drives@odata.count": 1
+ }
+}
diff --git a/test/meson.build b/test/meson.build
index a4ddfe1..c61ab8c 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -20,10 +20,14 @@
'bej_common',
'bej_dictionary',
'bej_tree',
+ 'bej_encoder'
]
+
+libbej_test_incs = include_directories('include')
foreach t : gtests
test(t, executable(t.underscorify(), t + '_test.cpp',
build_by_default: false,
implicit_include_directories: false,
+ include_directories: libbej_test_incs,
dependencies: [libbej, gtest, gmock]))
endforeach