#include "bej_encoder_metadata.h" #include "bej_common.h" #include "bej_dictionary.h" #include #include #include #include /** * @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; } static int bejUpdateNullMetaData(const struct BejDictionaries* dictionaries, const uint8_t* parentDictionary, struct RedfishPropertyLeafNull* 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. Value length for NULL type is 0. So we // need to encode [0x01 0x00] node->leaf.metaData.sflSize += BEJ_TUPLE_L_SIZE_FOR_BEJ_INTEGER; node->leaf.metaData.vSize = 0; 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; case bejNull: RETURN_IF_IERROR( bejUpdateNullMetaData(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; }