Add helper functions for BEJ dictionaries

Signed-off-by: Kasun Athukorala <kasunath@google.com>
Change-Id: I50f2ccec155434639dfecd96193608cf6c5a1787
diff --git a/include/bej_dictionary.h b/include/bej_dictionary.h
new file mode 100644
index 0000000..13d979e
--- /dev/null
+++ b/include/bej_dictionary.h
@@ -0,0 +1,101 @@
+#pragma once
+
+#include "rde_common.h"
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+/**
+ * @brief Mask for the type of the dictionary within a bejTupleS.
+ */
+#define DICTIONARY_TYPE_MASK 0x01
+
+/**
+ * @brief Number of bits needed to shift to get the sequence number from a
+ * bejTupleS nnint value.
+ */
+#define DICTIONARY_SEQ_NUM_SHIFT 1
+
+    /**
+     * @brief BEJ dictionary type.
+     */
+    enum BejDictionaryType
+    {
+        bejPrimary = 0,
+        bejAnnotation = 1,
+    };
+
+    /**
+     * @brief Dictionary property header.
+     */
+    struct BejDictionaryProperty
+    {
+        struct BejTupleF format;
+        uint16_t sequenceNumber;
+        uint16_t childPointerOffset;
+        uint16_t childCount;
+        uint8_t nameLength;
+        uint16_t nameOffset;
+    } __attribute__((__packed__));
+
+    struct BejDictionaryHeader
+    {
+        uint8_t versionTag;
+        uint8_t truncationFlag : 1;
+        uint8_t reservedFlags : 7;
+        uint16_t entryCount;
+        uint32_t schemaVersion;
+        uint32_t dictionarySize;
+    } __attribute__((__packed__));
+
+    /**
+     * @brief Get the offset of the first property in a dictionary.
+     *
+     * @return the offset to the first property.
+     */
+    uint16_t bejDictGetPropertyHeadOffset();
+
+    /**
+     * @brief Get the offset of the first annotated property in an annoation
+     * dictionary.
+     *
+     * @return the offset to the first annotated property in an annoation
+     * dictionary.
+     */
+    uint16_t bejDictGetFirstAnnotatedPropertyOffset();
+
+    /**
+     * @brief Get the property related to the given sequence number.
+     *
+     * @param[in] dictionary - dictionary containing the sequence number.
+     * @param[in] startingPropertyOffset - offset of the starting property for
+     * the search.
+     * @param[in] sequenceNumber - sequence number of the property.
+     * @param[out] property - if the search is successful, this will point to a
+     * valid property.
+     * @return 0 if successful.
+     */
+    int bejDictGetProperty(const uint8_t* dictionary,
+                           uint16_t startingPropertyOffset,
+                           uint16_t sequenceNumber,
+                           const struct BejDictionaryProperty** property);
+
+    /**
+     * @brief Get the name of a property.
+     *
+     * @param[in] dictionary - dictionary containing the property.
+     * @param[in] nameOffset - dictionary offset of the name.
+     * @param[in] nameLength - length of the name.
+     * @return a NULL terminated string. If the nameLength is 0, this will
+     * return an empty string.
+     */
+    const char* bejDictGetPropertyName(const uint8_t* dictionary,
+                                       uint16_t nameOffset, uint8_t nameLength);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/include/rde_common.h b/include/rde_common.h
index bb55a37..f377507 100644
--- a/include/rde_common.h
+++ b/include/rde_common.h
@@ -27,13 +27,13 @@
      */
     enum BejError
     {
-        BejErrorNoError = 0,
-        BejErrorUnknown,
-        BejErrorInvalidSize,
-        BejErrorNotSuppoted,
-        BejErrorUnknownProperty,
-        BejErrorInvalidSchemaType,
-        BejErrorInvalidPropertyOffset,
+        bejErrorNoError = 0,
+        bejErrorUnknown,
+        bejErrorInvalidSize,
+        bejErrorNotSuppoted,
+        bejErrorUnknownProperty,
+        bejErrorInvalidSchemaType,
+        bejErrorInvalidPropertyOffset,
     };
 
     /**
@@ -41,11 +41,11 @@
      */
     enum BejSchemaClass
     {
-        BejMajorSchemaClass = 0,
-        BejEventSchemaClass = 1,
-        BejAnnotationSchemaClass = 2,
-        BejCollectionMemberTypeSchemaClass = 3,
-        BejErrorSchemaClass = 4,
+        bejMajorSchemaClass = 0,
+        bejEventSchemaClass = 1,
+        bejAnnotationSchemaClass = 2,
+        bejCollectionMemberTypeSchemaClass = 3,
+        bejErrorSchemaClass = 4,
     };
 
     /**
@@ -53,22 +53,22 @@
      */
     enum BejPrincipalDataType
     {
-        BejSet = 0,
-        BejArray = 1,
-        BejNull = 2,
-        BejInteger = 3,
-        BejEnum = 4,
-        BejString = 5,
-        BejReal = 6,
-        BejBoolean = 7,
-        BejBytestring = 8,
-        BejChoice = 9,
-        BejPropertyAnnotation = 10,
-        Reserved1 = 11,
-        Reserved2 = 12,
-        Reserved3 = 13,
-        BejResourceLink = 14,
-        BejResourceLinkExpansion = 15,
+        bejSet = 0,
+        bejArray = 1,
+        bejNull = 2,
+        bejInteger = 3,
+        bejEnum = 4,
+        bejString = 5,
+        bejReal = 6,
+        bejBoolean = 7,
+        bejBytestring = 8,
+        bejChoice = 9,
+        bejPropertyAnnotation = 10,
+        bejPrincipalDataReserved1 = 11,
+        bejPrincipalDataReserved2 = 12,
+        bejPrincipalDataReserved3 = 13,
+        bejResourceLink = 14,
+        bejResourceLinkExpansion = 15,
     };
 
     /**
@@ -145,21 +145,21 @@
 
     enum RdeOperationInitType
     {
-        RdeOpInitOperationHead = 0,
-        RdeOpInitOperationRead = 1,
-        RdeOpInitOperationCreate = 2,
-        RdeOpInitOperationDelete = 3,
-        RdeOpInitOperationUpdate = 4,
-        RdeOpInitOperationReplace = 5,
-        RdeOpInitOperationAction = 6,
+        rdeOpInitOperationHead = 0,
+        rdeOpInitOperationRead = 1,
+        rdeOpInitOperationCreate = 2,
+        rdeOpInitOperationDelete = 3,
+        rdeOpInitOperationUpdate = 4,
+        rdeOpInitOperationReplace = 5,
+        rdeOpInitOperationAction = 6,
     };
 
     enum RdeMultiReceiveTransferFlag
     {
-        RdeMRecFlagStart = 0,
-        RdeMRecFlagMiddle = 1,
-        RdeMRecFlagEnd = 2,
-        RdeMRecFlagStartAndEnd = 3,
+        rdeMRecFlagStart = 0,
+        rdeMRecFlagMiddle = 1,
+        rdeMRecFlagEnd = 2,
+        rdeMRecFlagStartAndEnd = 3,
     };
 
     struct RdeOperationInitReqHeader
@@ -190,9 +190,11 @@
     /**
      * @brief Get the unsigned integer value from provided bytes.
      *
-     * @param bytes - valid pointer to a byte stream in little-endian format.
-     * @param numOfBytes - number of bytes belongs to the value. Maximum number
-     * of bytes supported is 8. If numOfBytes > 8, the result is undefined.
+     * @param[in] bytes - valid pointer to a byte stream in little-endian
+     * format.
+     * @param[in] numOfBytes - number of bytes belongs to the value. Maximum
+     * number of bytes supported is 8. If numOfBytes > 8, the result is
+     * undefined.
      * @return unsigend 64bit representation of the value.
      */
     uint64_t rdeGetUnsignedInteger(const uint8_t* bytes, uint8_t numOfBytes);
@@ -200,7 +202,7 @@
     /**
      * @brief Get the value from nnint type.
      *
-     * @param  nnint - nnint should be pointing to a valid nnint.
+     * @param[in] nnint - nnint should be pointing to a valid nnint.
      * @return unsigend 64bit representation of the value.
      */
     uint64_t rdeGetNnint(const uint8_t* nnint);
@@ -208,7 +210,7 @@
     /**
      * @brief Get the size of the complete nnint.
      *
-     * @param nnint - pointer to a valid nnint.
+     * @param[in] nnint - pointer to a valid nnint.
      * @return size of the complete nnint.
      */
     uint8_t rdeGetNnintSize(const uint8_t* nnint);
diff --git a/src/bej_dictionary.c b/src/bej_dictionary.c
new file mode 100644
index 0000000..a86720b
--- /dev/null
+++ b/src/bej_dictionary.c
@@ -0,0 +1,100 @@
+#include "bej_dictionary.h"
+
+#include <stdbool.h>
+#include <stdio.h>
+
+/**
+ * @brief Get the index for a property offset. First property will be at index
+ * 0.
+ *
+ * @param[in] propertyOffset - a valid property offset.
+ * @return index of the property.
+ */
+static uint16_t bejGetPropertyEntryIndex(uint16_t propertyOffset)
+{
+    return (propertyOffset - bejDictGetPropertyHeadOffset()) /
+           sizeof(struct BejDictionaryProperty);
+}
+
+/**
+ * @brief  Validate a property offset.
+ *
+ * @param[in] propertyOffset - offset needed to be validated.
+ * @return true if propertyOffset is a valid offset.
+ */
+static bool bejValidatePropertyOffset(uint16_t propertyOffset)
+{
+    // propertyOffset should be greater than or equal to first property offset.
+    if (propertyOffset < bejDictGetPropertyHeadOffset())
+    {
+        fprintf(
+            stderr,
+            "Invalid property offset. Pointing to Dictionary header data\n");
+        return false;
+    }
+
+    // propertyOffset should be a multiple of sizeof(BejDictionaryProperty)
+    // starting from first property within the dictionary.
+    if ((propertyOffset - bejDictGetPropertyHeadOffset()) %
+        sizeof(struct BejDictionaryProperty))
+    {
+        fprintf(stderr, "Invalid property offset. Does not point to beginning "
+                        "of property\n");
+        return false;
+    }
+
+    return true;
+}
+
+uint16_t bejDictGetPropertyHeadOffset()
+{
+    // First property is present soon after the dictionary header.
+    return sizeof(struct BejDictionaryHeader);
+}
+
+uint16_t bejDictGetFirstAnnotatedPropertyOffset()
+{
+    // The first property available is the "Annotations" set which is the parent
+    // for all properties. Next immediate property is the first property we
+    // need.
+    return sizeof(struct BejDictionaryHeader) +
+           sizeof(struct BejDictionaryProperty);
+}
+
+int bejDictGetProperty(const uint8_t* dictionary,
+                       uint16_t startingPropertyOffset, uint16_t sequenceNumber,
+                       const struct BejDictionaryProperty** property)
+{
+    uint16_t propertyOffset = startingPropertyOffset;
+    const struct BejDictionaryHeader* header =
+        (const struct BejDictionaryHeader*)dictionary;
+
+    if (!bejValidatePropertyOffset(propertyOffset))
+    {
+        return bejErrorInvalidPropertyOffset;
+    }
+    uint16_t propertyIndex = bejGetPropertyEntryIndex(propertyOffset);
+
+    for (uint16_t index = propertyIndex; index < header->entryCount; ++index)
+    {
+        const struct BejDictionaryProperty* p =
+            (const struct BejDictionaryProperty*)(dictionary + propertyOffset);
+        if (p->sequenceNumber == sequenceNumber)
+        {
+            *property = p;
+            return 0;
+        }
+        propertyOffset += sizeof(struct BejDictionaryProperty);
+    }
+    return bejErrorUnknownProperty;
+}
+
+const char* bejDictGetPropertyName(const uint8_t* dictionary,
+                                   uint16_t nameOffset, uint8_t nameLength)
+{
+    if (nameLength == 0)
+    {
+        return "";
+    }
+    return (const char*)(dictionary + nameOffset);
+}
diff --git a/src/meson.build b/src/meson.build
index 0d2703d..e8d9958 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -1,6 +1,7 @@
 libbej_lib = static_library(
   'libbej',
   'rde_common.c',
+  'bej_dictionary.c',
   include_directories : libbej_incs,
   implicit_include_directories: false
 )
diff --git a/test/bej_dictionary_test.cpp b/test/bej_dictionary_test.cpp
new file mode 100644
index 0000000..0ca9361
--- /dev/null
+++ b/test/bej_dictionary_test.cpp
@@ -0,0 +1,107 @@
+#include "bej_dictionary.h"
+
+#include <array>
+#include <string_view>
+#include <tuple>
+
+#include <gmock/gmock-matchers.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace rde
+{
+
+/**
+ * @brief A valid dictionary.
+ */
+constexpr std::array<uint8_t, 279> dummySimpleDict{
+    {0x0,  0x0,  0xc,  0x0,  0x0,  0xf0, 0xf0, 0xf1, 0x17, 0x1,  0x0,  0x0,
+     0x0,  0x0,  0x0,  0x16, 0x0,  0x5,  0x0,  0xc,  0x84, 0x0,  0x14, 0x0,
+     0x0,  0x48, 0x0,  0x1,  0x0,  0x13, 0x90, 0x0,  0x56, 0x1,  0x0,  0x0,
+     0x0,  0x0,  0x0,  0x3,  0xa3, 0x0,  0x74, 0x2,  0x0,  0x0,  0x0,  0x0,
+     0x0,  0x16, 0xa6, 0x0,  0x34, 0x3,  0x0,  0x0,  0x0,  0x0,  0x0,  0x16,
+     0xbc, 0x0,  0x64, 0x4,  0x0,  0x0,  0x0,  0x0,  0x0,  0x13, 0xd2, 0x0,
+     0x0,  0x0,  0x0,  0x52, 0x0,  0x2,  0x0,  0x0,  0x0,  0x0,  0x74, 0x0,
+     0x0,  0x0,  0x0,  0x0,  0x0,  0xf,  0xe5, 0x0,  0x46, 0x1,  0x0,  0x66,
+     0x0,  0x3,  0x0,  0xb,  0xf4, 0x0,  0x50, 0x0,  0x0,  0x0,  0x0,  0x0,
+     0x0,  0x9,  0xff, 0x0,  0x50, 0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x7,
+     0x8,  0x1,  0x50, 0x2,  0x0,  0x0,  0x0,  0x0,  0x0,  0x7,  0xf,  0x1,
+     0x44, 0x75, 0x6d, 0x6d, 0x79, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x0,
+     0x43, 0x68, 0x69, 0x6c, 0x64, 0x41, 0x72, 0x72, 0x61, 0x79, 0x50, 0x72,
+     0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x0,  0x49, 0x64, 0x0,  0x53, 0x61,
+     0x6d, 0x70, 0x6c, 0x65, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x50,
+     0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x0,  0x53, 0x61, 0x6d, 0x70,
+     0x6c, 0x65, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x50, 0x72, 0x6f,
+     0x70, 0x65, 0x72, 0x74, 0x79, 0x0,  0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65,
+     0x52, 0x65, 0x61, 0x6c, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79,
+     0x0,  0x41, 0x6e, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x42, 0x6f, 0x6f, 0x6c,
+     0x65, 0x61, 0x6e, 0x0,  0x4c, 0x69, 0x6e, 0x6b, 0x53, 0x74, 0x61, 0x74,
+     0x75, 0x73, 0x0,  0x4c, 0x69, 0x6e, 0x6b, 0x44, 0x6f, 0x77, 0x6e, 0x0,
+     0x4c, 0x69, 0x6e, 0x6b, 0x55, 0x70, 0x0,  0x4e, 0x6f, 0x4c, 0x69, 0x6e,
+     0x6b, 0x0,  0x0}};
+
+/**
+ * @brief Property names and sequence numbers in dummySimpleDict.
+ * Order here is the same as the order in the dummySimpleDict.
+ */
+constexpr std::array<std::tuple<std::string_view, int>, 12> propertyNameSeq{{
+    {"DummySimple", 0},
+    {"ChildArrayProperty", 0},
+    {"Id", 1},
+    {"SampleEnabledProperty", 2},
+    {"SampleIntegerProperty", 3},
+    {"SampleRealProperty", 4},
+    {"", 0},
+    {"AnotherBoolean", 0},
+    {"LinkStatus", 1},
+    {"LinkDown", 0},
+    {"LinkUp", 1},
+    {"NoLink", 2},
+}};
+
+TEST(BejDictionaryTest, PropertyHeadOffsetTest)
+{
+    EXPECT_THAT(bejDictGetPropertyHeadOffset(), sizeof(BejDictionaryHeader));
+}
+
+TEST(BejDictionaryTest, AnnotationPropertyHeadOffsetTest)
+{
+    EXPECT_THAT(bejDictGetFirstAnnotatedPropertyOffset(),
+                sizeof(BejDictionaryHeader) + sizeof(BejDictionaryProperty));
+}
+
+TEST(BejDictionaryTest, ValidPropertyTest)
+{
+    const struct BejDictionaryHeader* header =
+        (const struct BejDictionaryHeader*)dummySimpleDict.data();
+    uint16_t propHead = bejDictGetPropertyHeadOffset();
+    // Read each property in the dictionary and verify that the property name is
+    // correct.
+    for (uint16_t index = 0; index < header->entryCount; ++index)
+    {
+        uint16_t offset = propHead + sizeof(BejDictionaryProperty) * index;
+        const struct BejDictionaryProperty* property;
+        EXPECT_THAT(bejDictGetProperty(dummySimpleDict.data(), offset,
+                                       std::get<1>(propertyNameSeq[index]),
+                                       &property),
+                    0);
+        EXPECT_THAT(bejDictGetPropertyName(dummySimpleDict.data(),
+                                           property->nameOffset,
+                                           property->nameLength),
+                    std::get<0>(propertyNameSeq[index]));
+    }
+}
+
+TEST(BejDictionaryTest, invalidPropertyOffsetTest)
+{
+    const struct BejDictionaryProperty* property;
+    EXPECT_THAT(bejDictGetProperty(dummySimpleDict.data(), /*offset=*/0,
+                                   /*sequenceNumber=*/0, &property),
+                bejErrorInvalidPropertyOffset);
+    uint16_t propHead = bejDictGetPropertyHeadOffset();
+    EXPECT_THAT(bejDictGetProperty(dummySimpleDict.data(), propHead + 1,
+                                   /*sequenceNumber=*/0, &property),
+                bejErrorInvalidPropertyOffset);
+}
+
+} // namespace rde
diff --git a/test/meson.build b/test/meson.build
index a775a0c..8e406a7 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -17,6 +17,7 @@
 
 gtests = [
   'rde_common',
+  'bej_dictionary',
 ]
 foreach t : gtests
   test(t, executable(t.underscorify(), t + '_test.cpp',