blob: 2679880852724762fae059d6bc69f599edf88571 [file] [edit]
#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;
}