| #include "bej_encoder_core.h" |
| |
| #include "bej_common.h" |
| #include "bej_encoder_metadata.h" |
| |
| #include <stdio.h> |
| #include <string.h> |
| |
| /** |
| * @brief Encode a unsigned value with nnint format. |
| */ |
| static int bejEncodeNnint(uint64_t value, |
| struct BejEncoderOutputHandler* output) |
| { |
| // The length of the value bytes in nnint. |
| uint8_t nnintLengthByte = bejNnintLengthFieldOfUInt(value); |
| RETURN_IF_IERROR(output->recvOutput(&nnintLengthByte, sizeof(uint8_t), |
| output->handlerContext)); |
| // Write the nnint value bytes. |
| return output->recvOutput(&value, nnintLengthByte, output->handlerContext); |
| } |
| |
| /** |
| * @brief Encode a BejTupleF type. |
| */ |
| static int bejEncodeFormat(const struct BejTupleF* format, |
| struct BejEncoderOutputHandler* output) |
| { |
| return output->recvOutput(format, sizeof(struct BejTupleF), |
| output->handlerContext); |
| } |
| |
| /** |
| * @brief Encode a BejSet or BejArray type. |
| */ |
| static int bejEncodeBejSetOrArray(struct RedfishPropertyParent* node, |
| struct BejEncoderOutputHandler* output) |
| { |
| // Encode Sequence number. |
| RETURN_IF_IERROR(bejEncodeNnint(node->metaData.sequenceNumber, output)); |
| // Add the format. |
| RETURN_IF_IERROR(bejEncodeFormat(&node->nodeAttr.format, output)); |
| // Encode the value length. |
| RETURN_IF_IERROR(bejEncodeNnint(node->metaData.vSize, output)); |
| // Encode the child count |
| return bejEncodeNnint(node->nChildren, output); |
| } |
| |
| /** |
| * @brief Encode an integer to bejInteger type. |
| */ |
| static uint8_t bejEncodeInteger(int64_t val, |
| struct BejEncoderOutputHandler* output) |
| { |
| uint8_t copyLength = bejIntLengthOfValue(val); |
| return output->recvOutput(&val, copyLength, output->handlerContext); |
| } |
| |
| /** |
| * @brief Encode a BejInteger type. |
| */ |
| int bejEncodeBejInteger(struct RedfishPropertyLeafInt* node, |
| struct BejEncoderOutputHandler* output) |
| { |
| // Encode Sequence number. |
| RETURN_IF_IERROR( |
| bejEncodeNnint(node->leaf.metaData.sequenceNumber, output)); |
| // Add the format. |
| RETURN_IF_IERROR(bejEncodeFormat(&node->leaf.nodeAttr.format, output)); |
| // Encode the value length. |
| RETURN_IF_IERROR(bejEncodeNnint(node->leaf.metaData.vSize, output)); |
| // Encode the value. |
| return bejEncodeInteger(node->value, output); |
| } |
| |
| /** |
| * @brief Encode a BejEnum type. |
| */ |
| int bejEncodeBejEnum(struct RedfishPropertyLeafEnum* node, |
| struct BejEncoderOutputHandler* output) |
| { |
| // S: Encode Sequence number. |
| RETURN_IF_IERROR( |
| bejEncodeNnint(node->leaf.metaData.sequenceNumber, output)); |
| // 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. |
| return bejEncodeNnint(node->enumValueSeq, output); |
| } |
| int bejEncodeBejString(struct RedfishPropertyLeafString* node, |
| struct BejEncoderOutputHandler* output) |
| { |
| // S: Encode Sequence number. |
| RETURN_IF_IERROR( |
| bejEncodeNnint(node->leaf.metaData.sequenceNumber, output)); |
| // 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. |
| return output->recvOutput((void*)node->value, node->leaf.metaData.vSize, |
| output->handlerContext); |
| } |
| |
| int bejEncodeBejReal(struct RedfishPropertyLeafReal* node, |
| struct BejEncoderOutputHandler* output) |
| { |
| // S: Encode Sequence number. |
| RETURN_IF_IERROR( |
| bejEncodeNnint(node->leaf.metaData.sequenceNumber, output)); |
| // 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. |
| // Length of the "whole" value as nnint. |
| RETURN_IF_IERROR( |
| bejEncodeNnint(bejIntLengthOfValue(node->bejReal.whole), output)); |
| // Add the "whole" value. |
| RETURN_IF_IERROR(bejEncodeInteger(node->bejReal.whole, output)); |
| // Leading zero count as a nnint. |
| RETURN_IF_IERROR(bejEncodeNnint(node->bejReal.zeroCount, output)); |
| // Fraction as a nnint. |
| RETURN_IF_IERROR(bejEncodeNnint(node->bejReal.fract, output)); |
| |
| if (node->bejReal.valid_exp) |
| { |
| RETURN_IF_IERROR( |
| bejEncodeNnint(bejIntLengthOfValue(node->bejReal.exp), output)); |
| RETURN_IF_IERROR(bejEncodeInteger(node->bejReal.exp, output)); |
| } |
| else |
| { |
| // If the exp length is 0, then exp value is omitted. |
| RETURN_IF_IERROR(bejEncodeNnint(/*value=*/0, output)); |
| } |
| |
| return 0; |
| } |
| |
| int bejEncodeBejBool(struct RedfishPropertyLeafBool* node, |
| struct BejEncoderOutputHandler* output) |
| { |
| // S: Encode Sequence number. |
| RETURN_IF_IERROR( |
| bejEncodeNnint(node->leaf.metaData.sequenceNumber, output)); |
| // 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. |
| uint8_t value = node->value ? 0xFF : 0x00; |
| return output->recvOutput(&value, /*data_size=*/sizeof(uint8_t), |
| output->handlerContext); |
| } |
| |
| int bejEncodeBejProAnno(struct RedfishPropertyParent* node, |
| struct BejEncoderOutputHandler* output) |
| { |
| // Encode Sequence number. |
| RETURN_IF_IERROR(bejEncodeNnint(node->metaData.sequenceNumber, output)); |
| // Add the format. |
| RETURN_IF_IERROR(bejEncodeFormat(&node->nodeAttr.format, output)); |
| // Encode the value length. |
| return bejEncodeNnint(node->metaData.vSize, output); |
| } |
| |
| /** |
| * @brief Encode a BejNull type. |
| */ |
| int bejEncodeBejNull(struct RedfishPropertyLeafNull* node, |
| struct BejEncoderOutputHandler* output) |
| { |
| // S: Encode Sequence number. |
| RETURN_IF_IERROR( |
| bejEncodeNnint(node->leaf.metaData.sequenceNumber, output)); |
| // F: Add the format. |
| RETURN_IF_IERROR(bejEncodeFormat(&node->leaf.nodeAttr.format, output)); |
| // 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"); |
| |
| // 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) |
| { |
| case bejSet: |
| RETURN_IF_IERROR(bejEncodeBejSetOrArray(node, output)); |
| break; |
| case bejArray: |
| RETURN_IF_IERROR(bejEncodeBejSetOrArray(node, output)); |
| break; |
| case bejNull: |
| RETURN_IF_IERROR(bejEncodeBejNull(node, output)); |
| break; |
| case bejInteger: |
| RETURN_IF_IERROR(bejEncodeBejInteger(node, output)); |
| break; |
| case bejEnum: |
| RETURN_IF_IERROR(bejEncodeBejEnum(node, output)); |
| break; |
| case bejString: |
| RETURN_IF_IERROR(bejEncodeBejString(node, output)); |
| break; |
| case bejReal: |
| RETURN_IF_IERROR(bejEncodeBejReal(node, output)); |
| break; |
| case bejBoolean: |
| RETURN_IF_IERROR(bejEncodeBejBool(node, output)); |
| break; |
| 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); |
| return -1; |
| } |
| return 0; |
| } |
| |
| /** |
| * @brief A helper function to add a parent to the stack. |
| */ |
| static int bejPushParentToStack(struct RedfishPropertyParent* parent, |
| struct BejPointerStackCallback* stack) |
| { |
| // Before pushing the parent node, initialize its nextChild as the first |
| // child. |
| parent->metaData.nextChild = parent->firstChild; |
| return stack->stackPush(parent, stack->stackContext); |
| } |
| |
| /** |
| * @brief Process all the child nodes of a parent. |
| */ |
| static int bejProcessChildNodes(struct RedfishPropertyParent* parent, |
| struct BejPointerStackCallback* stack, |
| struct BejEncoderOutputHandler* output, |
| struct BejNodeInfoCallbacks* nodeInfoCb) |
| { |
| // Get the next child of the parent. |
| void* childPtr = parent->metaData.nextChild; |
| |
| while (childPtr != NULL) |
| { |
| // First encode the current child node. |
| 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 |
| // parent. |
| if (bejTreeIsParentType(childPtr)) |
| { |
| RETURN_IF_IERROR(bejPushParentToStack(childPtr, stack)); |
| // Update the next child of the current parent we need to |
| // process. |
| bejParentGoToNextChild(parent, childPtr); |
| return 0; |
| } |
| childPtr = bejParentGoToNextChild(parent, childPtr); |
| } |
| return 0; |
| } |
| |
| static int bejEncodeTree(struct RedfishPropertyParent* root, |
| struct BejPointerStackCallback* stack, |
| 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, 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 |
| // the stack. |
| RETURN_IF_IERROR(bejPushParentToStack(root, stack)); |
| |
| while (!stack->stackEmpty(stack->stackContext)) |
| { |
| struct RedfishPropertyParent* parent = |
| stack->stackPeek(stack->stackContext); |
| // Encode 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 |
| // encoded and added to the stack and this function will return. The |
| // 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, nodeInfoCb)); |
| |
| // If a new node hasn't been added to the stack by |
| // bejProcessChildNodes(), we know that this parent's child nodes have |
| // been processed. If a new node has been added, then next we need to |
| // process the children of the newly added node. |
| if (parent != stack->stackPeek(stack->stackContext)) |
| { |
| continue; |
| } |
| stack->stackPop(stack->stackContext); |
| } |
| return 0; |
| } |
| |
| 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"); |
| NULL_CHECK(dictionaries->annotationDictionary, "annotationDictionary"); |
| |
| NULL_CHECK(root, "root"); |
| |
| NULL_CHECK(output, "output"); |
| NULL_CHECK(stack, "stack"); |
| |
| // Assert root node. |
| if (root->nodeAttr.format.principalDataType != bejSet) |
| { |
| fprintf(stderr, "Invalid root node\n"); |
| return -1; |
| } |
| |
| // First we need to encode a parent node before its child nodes. But before |
| // encoding the parent node, the encoder has to figure out the total size |
| // need to encode the parent's child nodes. Therefore first the encoder need |
| // to visit the child nodes and calculate the size need to encode them |
| // before producing the encoded bytes for the parent node. |
| // |
| // So first the encoder will visit child nodes and calculate the size need |
| // to encode each child node. Then store this information in metadata |
| // properties in each node struct. |
| // Next the encoder will again visit each node starting from the parent |
| // node, and produce the encoded bytes. |
| |
| // First calculate metadata for encoding each node. |
| RETURN_IF_IERROR(bejUpdateNodeMetadata( |
| dictionaries, majorSchemaStartingOffset, root, stack, nodeInfoCb)); |
| |
| // Derive the header of the encoded output. |
| // BEJ version |
| uint32_t version = BEJ_VERSION; |
| RETURN_IF_IERROR( |
| output->recvOutput(&version, sizeof(uint32_t), output->handlerContext)); |
| uint16_t reserved = 0; |
| RETURN_IF_IERROR(output->recvOutput(&reserved, sizeof(uint16_t), |
| output->handlerContext)); |
| RETURN_IF_IERROR(output->recvOutput(&schemaClass, sizeof(uint8_t), |
| output->handlerContext)); |
| // Produce the encoded bytes for the nodes using the previously calculated |
| // metadata. |
| 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; |
| } |