blob: 098c5e7d7a4274235dbf1a9a060e4d7cd2d08acd [file] [log] [blame]
#include "bej_encoder_metadata.h"
#include "bej_common.h"
#include "bej_dictionary.h"
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
/**
* @brief Maximum digits supported in the fractional part of a real number.
*/
#define BEJ_REAL_PRECISION 16
/**
* @brief bejTupleL size of an integer.
*
* Maximum bytes possible for an integer is 8. Therefore to encode the length of
* an integer using a nnint, we only need two bytes. [byte1: nnint length,
* byte2: integer length [0-8]]
*/
#define BEJ_TUPLE_L_SIZE_FOR_BEJ_INTEGER 2
/**
* @brief bejTupleL size of a bool.
*
* 1byte for the nnint length and 1 byte for the value.
*/
#define BEJ_TUPLE_L_SIZE_FOR_BEJ_BOOL 2
/**
* @brief bejTupleF size.
*/
#define BEJ_TUPLE_F_SIZE 1
/**
* @brief Check the name is an annotation type name.
*
* @param[in] name - property name.
* @return true for annotation name, false otherwise.
*/
static bool bejIsAnnotation(const char* name)
{
if (name == NULL)
{
return false;
}
return name[0] == '@';
}
/**
* @brief Get the dictionary for the provided node.
*
* @param[in] dictionaries - available dictionaries for encoding.
* @param[in] parentDictionary - dictionary used for the parent of this node.
* @param[in] nodeName - name of the interested node. Can be NULL if the node
* doesn't have a name.
* @return a pointer to the dictionary to be used.
*/
static const uint8_t*
bejGetRelatedDictionary(const struct BejDictionaries* dictionaries,
const uint8_t* parentDictionary,
const char* nodeName)
{
// If the node name is NULL, we have to use parent dictionary.
if (nodeName == NULL)
{
return parentDictionary;
}
// If the parent is using annotation dictionary, that means the parent is an
// annotation. Therefore the child (this node) should be an annotation too
// (Could this be false?). Therefore we should use the annotation dictionary
// for this node as well.
if (parentDictionary == dictionaries->annotationDictionary)
{
return dictionaries->annotationDictionary;
}
return bejIsAnnotation(nodeName) ? dictionaries->annotationDictionary
: dictionaries->schemaDictionary;
}
/**
* @brief Get dictionary data for the given node.
*
* @param[in] dictionaries - available dictionaries.
* @param[in] parentDictionary - the dictionary used by the provided node's
* parent.
* @param[in] node - node that caller is interested in.
* @param[in] nodeIndex - index of this node within its parent.
* @param[in] dictStartingOffset - starting dictionary child offset value of
* this node's parent.
* @param[out] sequenceNumber - sequence number of the node. bit0 specifies the
* dictionary schema type: [major|annotation].
* @param[out] nodeDictionary - if not NULL, return a pointer to the dictionary
* used for the node.
* @param[out] childEntryOffset - if not NULL, return the dictionary starting
* offset used for this nodes children. If this node is not supposed to have
* children, caller should ignore this value.
* @return 0 if successful.
*/
static int bejFindSeqNumAndChildDictOffset(
const struct BejDictionaries* dictionaries, const uint8_t* parentDictionary,
struct RedfishPropertyNode* node, uint16_t nodeIndex,
uint16_t dictStartingOffset, uint32_t* sequenceNumber,
const uint8_t** nodeDictionary, uint16_t* childEntryOffset)
{
// If the node doesn't have a name, we can't use a dictionary. So we can use
// its parent's info.
if (node->name == NULL || node->name[0] == '\0')
{
if (nodeDictionary != NULL)
{
*nodeDictionary = parentDictionary;
}
if (childEntryOffset != NULL)
{
*childEntryOffset = dictStartingOffset;
}
// If the property doesn't have a name, it has to be an element of an
// array. In that case, sequence number is the array index.
*sequenceNumber = (uint32_t)nodeIndex << 1;
if (dictionaries->annotationDictionary == parentDictionary)
{
*sequenceNumber |= 1;
}
return 0;
}
// If we are here, the property has a name.
const uint8_t* dictionary =
bejGetRelatedDictionary(dictionaries, parentDictionary, node->name);
bool isAnnotation = dictionary == dictionaries->annotationDictionary;
// If this node's dictionary and its parent's dictionary is different,
// this node should start searching from the beginning of its
// dictionary. This should only happen for property annotations of form
// property@annotation_class.annotation_name.
if (dictionary != parentDictionary)
{
// Redundancy check.
if (!isAnnotation)
{
fprintf(stderr,
"Dictionary for property %s should be the annotation "
"dictionary. Might be a encoding failure. Maybe the "
"JSON tree is not created correctly.",
node->name);
return -1;
}
dictStartingOffset = bejDictGetFirstAnnotatedPropertyOffset();
}
const struct BejDictionaryProperty* property;
int ret = bejDictGetPropertyByName(dictionary, dictStartingOffset,
node->name, &property, NULL);
if (ret != 0)
{
fprintf(stderr,
"Failed to find dictionary entry for name %s. Search started "
"at offset: %u. ret: %d\n",
node->name, dictStartingOffset, ret);
return ret;
}
if (nodeDictionary != NULL)
{
*nodeDictionary = dictionary;
}
if (childEntryOffset != NULL)
{
*childEntryOffset = property->childPointerOffset;
}
*sequenceNumber = (uint32_t)(property->sequenceNumber) << 1;
if (isAnnotation)
{
*sequenceNumber |= 1;
}
return 0;
}
static int bejUpdateIntMetaData(const struct BejDictionaries* dictionaries,
const uint8_t* parentDictionary,
struct RedfishPropertyLeafInt* node,
uint16_t nodeIndex, uint16_t dictStartingOffset)
{
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;
// L: Length needed for the value.
node->leaf.metaData.sflSize += BEJ_TUPLE_L_SIZE_FOR_BEJ_INTEGER;
// V: Bytes used for the value.
node->leaf.metaData.vSize = bejIntLengthOfValue(node->value);
return 0;
}
static int bejUpdateStringMetaData(const struct BejDictionaries* dictionaries,
const uint8_t* parentDictionary,
struct RedfishPropertyLeafString* node,
uint16_t nodeIndex,
uint16_t dictStartingOffset)
{
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;
// L: Length needed for the string including the NULL character. Length is
// in nnint format.
size_t strLenWithNull = strlen(node->value) + 1;
node->leaf.metaData.sflSize += bejNnintEncodingSizeOfUInt(strLenWithNull);
// V: Bytes used for the value.
node->leaf.metaData.vSize = strLenWithNull;
return 0;
}
static int bejUpdateRealMetaData(const struct BejDictionaries* dictionaries,
const uint8_t* parentDictionary,
struct RedfishPropertyLeafReal* node,
uint16_t nodeIndex,
uint16_t dictStartingOffset)
{
uint32_t sequenceNumber;
RETURN_IF_IERROR(bejFindSeqNumAndChildDictOffset(
dictionaries, parentDictionary, &(node->leaf.nodeAttr), nodeIndex,
dictStartingOffset, &sequenceNumber, NULL, NULL));
node->leaf.metaData.sequenceNumber = sequenceNumber;
if (node->value > (double)INT64_MAX)
{
// TODO: We should use the exponent.
fprintf(
stderr,
"Need to add support to encode double value larger than INT64_MAX\n");
return -1;
}
// 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;
// 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
double originalWhole;
double originalFract = modf(node->value, &originalWhole);
// Convert the fraction to a whole value for encoding.
// Create a new value by multiplying the original fraction by 10. Do this
// until the fraction of the new value is 0 or we reach the precision. Eg
// 0.00105: This fraction value has two leading zeros. We will keep
// multiplying this by 10 until the fraction of the result of that
// multiplication is 0.
double originalFactConvertedToWhole = fabs(originalFract);
double fract = originalFract;
double intPart;
uint32_t leadingZeros = 0;
uint32_t precision = 0;
while (fract != 0 && precision < BEJ_REAL_PRECISION)
{
originalFactConvertedToWhole = originalFactConvertedToWhole * 10;
fract = modf(originalFactConvertedToWhole, &intPart);
// If the integer portion is 0, that means we still have leading zeros.
if (intPart == 0)
{
++leadingZeros;
}
++precision;
}
node->bejReal.whole = (int64_t)originalWhole;
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;
// Calculate the sizes needed for storing bejReal fields.
// nnint for the length of the "whole" value.
node->leaf.metaData.vSize = BEJ_TUPLE_L_SIZE_FOR_BEJ_INTEGER;
// Length needed for the "whole" value.
node->leaf.metaData.vSize += bejIntLengthOfValue((int64_t)originalWhole);
// nnint for leading zero count.
node->leaf.metaData.vSize += bejNnintEncodingSizeOfUInt(leadingZeros);
// 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);
// L: nnint for the size needed for encoding the bejReal value.
node->leaf.metaData.sflSize +=
bejNnintEncodingSizeOfUInt(node->leaf.metaData.vSize);
return 0;
}
static int bejUpdateEnumMetaData(const struct BejDictionaries* dictionaries,
const uint8_t* parentDictionary,
struct RedfishPropertyLeafEnum* node,
uint16_t nodeIndex,
uint16_t dictStartingOffset)
{
const uint8_t* nodeDictionary;
uint16_t childEntryOffset;
uint32_t sequenceNumber;
// If the enum property doesn't have a name, this will simply return the
// nodeIndex encoded as the sequence number. If not, this will return the
// sequence number in the dictionary and the starting dictionary index for
// the enum values.
RETURN_IF_IERROR(bejFindSeqNumAndChildDictOffset(
dictionaries, parentDictionary, &(node->leaf.nodeAttr), nodeIndex,
dictStartingOffset, &sequenceNumber, &nodeDictionary,
&childEntryOffset));
// Update the sequence number of the property.
node->leaf.metaData.sequenceNumber = sequenceNumber;
// Get the sequence number for the Enum value.
if (node->leaf.nodeAttr.name != NULL && node->leaf.nodeAttr.name[0] != '\0')
{
dictStartingOffset = childEntryOffset;
}
const struct BejDictionaryProperty* enumValueProperty;
int ret = bejDictGetPropertyByName(nodeDictionary, dictStartingOffset,
node->value, &enumValueProperty, NULL);
if (ret != 0)
{
fprintf(
stderr,
"Failed to find dictionary entry for enum value %s. Search started "
"at offset: %u. ret: %d\n",
node->value, dictStartingOffset, ret);
return ret;
}
node->enumValueSeq = enumValueProperty->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;
// V: Bytes used for the value.
node->leaf.metaData.vSize =
bejNnintEncodingSizeOfUInt(enumValueProperty->sequenceNumber);
// L: Length needed for the value nnint.
node->leaf.metaData.sflSize +=
bejNnintEncodingSizeOfUInt(node->leaf.metaData.vSize);
return 0;
}
static int bejUpdateBoolMetaData(const struct BejDictionaries* dictionaries,
const uint8_t* parentDictionary,
struct RedfishPropertyLeafBool* node,
uint16_t nodeIndex,
uint16_t dictStartingOffset)
{
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;
// L: Length needed for the value.
node->leaf.metaData.sflSize += BEJ_TUPLE_L_SIZE_FOR_BEJ_BOOL;
// V: Bytes used for the value; 0x00 or 0xFF.
node->leaf.metaData.vSize = 1;
return 0;
}
/**
* @brief Update metadata of leaf nodes.
*
* @param dictionaries - dictionaries needed for encoding.
* @param parentDictionary - dictionary used by this node's parent.
* @param childPtr - a pointer to the leaf node.
* @param childIndex - if this node is an array element, this is the array
* index.
* @param dictStartingOffset - starting dictionary child offset value of this
* node's parent.
* @return 0 if successful.
*/
static int bejUpdateLeafNodeMetaData(const struct BejDictionaries* dictionaries,
const uint8_t* parentDictionary,
void* childPtr, uint16_t childIndex,
uint16_t dictStartingOffset)
{
struct RedfishPropertyLeaf* chNode = childPtr;
switch (chNode->nodeAttr.format.principalDataType)
{
case bejInteger:
RETURN_IF_IERROR(
bejUpdateIntMetaData(dictionaries, parentDictionary, childPtr,
childIndex, dictStartingOffset));
break;
case bejString:
RETURN_IF_IERROR(bejUpdateStringMetaData(
dictionaries, parentDictionary, childPtr, childIndex,
dictStartingOffset));
break;
case bejReal:
RETURN_IF_IERROR(
bejUpdateRealMetaData(dictionaries, parentDictionary, childPtr,
childIndex, dictStartingOffset));
break;
case bejEnum:
RETURN_IF_IERROR(
bejUpdateEnumMetaData(dictionaries, parentDictionary, childPtr,
childIndex, dictStartingOffset));
break;
case bejBoolean:
RETURN_IF_IERROR(
bejUpdateBoolMetaData(dictionaries, parentDictionary, childPtr,
childIndex, dictStartingOffset));
break;
default:
fprintf(stderr, "Child type %u not supported\n",
chNode->nodeAttr.format.principalDataType);
return -1;
}
return 0;
}
/**
* @brief Update metadata of a parent node.
*
* @param dictionaries - dictionaries needed for encoding.
* @param parentDictionary - dictionary used by this node's parent.
* @param dictStartingOffset - starting dictionary child offset value of this
* node's parent.
* @param node - a pointer to the parent node.
* @param nodeIndex - If this node is an array element, this is the array index.
* @return 0 if successful.
*/
static int bejUpdateParentMetaData(const struct BejDictionaries* dictionaries,
const uint8_t* parentDictionary,
uint16_t dictStartingOffset,
struct RedfishPropertyParent* node,
uint16_t nodeIndex)
{
const uint8_t* nodeDictionary;
uint16_t childEntryOffset;
uint32_t sequenceNumber;
// Get the dictionary related data from the node.
RETURN_IF_IERROR(bejFindSeqNumAndChildDictOffset(
dictionaries, parentDictionary, &node->nodeAttr, nodeIndex,
dictStartingOffset, &sequenceNumber, &nodeDictionary,
&childEntryOffset));
node->metaData.sequenceNumber = sequenceNumber;
node->metaData.childrenDictPropOffset = childEntryOffset;
node->metaData.nextChild = node->firstChild;
node->metaData.nextChildIndex = 0;
node->metaData.dictionary = nodeDictionary;
node->metaData.vSize = 0;
// S: Size needed for encoding sequence number.
node->metaData.sflSize =
bejNnintEncodingSizeOfUInt(node->metaData.sequenceNumber);
// F: Size of the format byte is 1.
node->metaData.sflSize += 1;
// V: Only for bejArray and bejSet types, value size should include the
// children count. We need to add the size needs to encode all the children
// later.
if (node->nodeAttr.format.principalDataType != bejPropertyAnnotation)
{
node->metaData.vSize = bejNnintEncodingSizeOfUInt(node->nChildren);
}
return 0;
}
/**
* @brief Update metadata of child nodes.
*
* If a child node contains its own child nodes, it will be added to the stack
* and function will return.
*
* @param dictionaries - dictionaries needed for encoding.
* @param parent - parent node.
* @param stack - stack holding parent nodes.
* @return 0 if successful.
*/
static int bejProcessChildNodes(const struct BejDictionaries* dictionaries,
struct RedfishPropertyParent* parent,
struct BejPointerStackCallback* stack)
{
// Get the next child of the parent.
void* childPtr = parent->metaData.nextChild;
// Process all the children belongs to the parent.
while (childPtr != NULL)
{
// If we find a child with its own child nodes, add it to the stack and
// return.
if (bejTreeIsParentType(childPtr))
{
RETURN_IF_IERROR(bejUpdateParentMetaData(
dictionaries, parent->metaData.dictionary,
parent->metaData.childrenDictPropOffset, childPtr,
parent->metaData.nextChildIndex));
RETURN_IF_IERROR(stack->stackPush(childPtr, stack->stackContext));
bejParentGoToNextChild(parent, childPtr);
return 0;
}
RETURN_IF_IERROR(
bejUpdateLeafNodeMetaData(dictionaries, parent->metaData.dictionary,
childPtr, parent->metaData.nextChildIndex,
parent->metaData.childrenDictPropOffset));
// 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.
parent->metaData.vSize +=
(leafChild->metaData.sflSize + leafChild->metaData.vSize);
// Get the next child belongs to the parent.
childPtr = bejParentGoToNextChild(parent, childPtr);
}
return 0;
}
int bejUpdateNodeMetadata(const struct BejDictionaries* dictionaries,
uint16_t majorSchemaStartingOffset,
struct RedfishPropertyParent* root,
struct BejPointerStackCallback* stack)
{
// Decide the starting property offset of the dictionary.
uint16_t dictOffset = bejDictGetPropertyHeadOffset();
if (majorSchemaStartingOffset != BEJ_DICTIONARY_START_AT_HEAD)
{
dictOffset = majorSchemaStartingOffset;
}
// Initialize root node metadata.
RETURN_IF_IERROR(
bejUpdateParentMetaData(dictionaries, dictionaries->schemaDictionary,
dictOffset, root, /*childIndex=*/0));
// Push the root to the stack. Because we are not done with the parent node
// yet. Need to figure out all bytes need to encode children of this parent,
// and save it in the parent metadata.
RETURN_IF_IERROR(stack->stackPush(root, stack->stackContext));
while (!stack->stackEmpty(stack->stackContext))
{
// Get the parent at the top of the stack. Stack is only popped if the
// parent stack entry has no pending children; That is
// parent->metaData.nextChild == NULL.
struct RedfishPropertyParent* parent =
stack->stackPeek(stack->stackContext);
// 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));
// 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
// stack.
if (parent != stack->stackPeek(stack->stackContext))
{
continue;
}
// If we are here;
// Then "parent" is the top element of the stack.
// All the children of "parent" has been processed.
// Remove the "parent" from the stack.
parent = stack->stackPop(stack->stackContext);
// L: Add the length needed to store the number of bytes used for the
// parent's value.
parent->metaData.sflSize +=
bejNnintEncodingSizeOfUInt(parent->metaData.vSize);
// Since we now know the total size needs to encode the node pointed by
// "parent" variable, we should add that to the value size of this
// node's parent. Since we already popped this node from the stack, top
// of the stack element is this nodes's parent. "parentsParent" can be
// NULL if the node pointed by "parent" variable is the root.
struct RedfishPropertyParent* parentsParent =
stack->stackPeek(stack->stackContext);
if (parentsParent != NULL)
{
// V: Include the total size to encode the current parent in its
// parent's value size.
parentsParent->metaData.vSize +=
(parent->metaData.sflSize + parent->metaData.vSize);
}
}
return 0;
}