xref: /openbmc/libbej/src/bej_encoder_core.c (revision 99bd6c90)
1 #include "bej_encoder_core.h"
2 
3 #include "bej_common.h"
4 #include "bej_encoder_metadata.h"
5 
6 #include <stdio.h>
7 #include <string.h>
8 
9 /**
10  * @brief Encode a unsigned value with nnint format.
11  */
12 static int bejEncodeNnint(uint64_t value,
13                           struct BejEncoderOutputHandler* output)
14 {
15     // The length of the value bytes in nnint.
16     uint8_t nnintLengthByte = bejNnintLengthFieldOfUInt(value);
17     RETURN_IF_IERROR(output->recvOutput(&nnintLengthByte, sizeof(uint8_t),
18                                         output->handlerContext));
19     // Write the nnint value bytes.
20     return output->recvOutput(&value, nnintLengthByte, output->handlerContext);
21 }
22 
23 /**
24  * @brief Encode a BejTupleF type.
25  */
26 static int bejEncodeFormat(const struct BejTupleF* format,
27                            struct BejEncoderOutputHandler* output)
28 {
29     return output->recvOutput(format, sizeof(struct BejTupleF),
30                               output->handlerContext);
31 }
32 
33 /**
34  * @brief Encode a BejSet or BejArray type.
35  */
36 static int bejEncodeBejSetOrArray(struct RedfishPropertyParent* node,
37                                   struct BejEncoderOutputHandler* output)
38 {
39     // Encode Sequence number.
40     RETURN_IF_IERROR(bejEncodeNnint(node->metaData.sequenceNumber, output));
41     // Add the format.
42     RETURN_IF_IERROR(bejEncodeFormat(&node->nodeAttr.format, output));
43     // Encode the value length.
44     RETURN_IF_IERROR(bejEncodeNnint(node->metaData.vSize, output));
45     // Encode the child count
46     return bejEncodeNnint(node->nChildren, output);
47 }
48 
49 /**
50  * @brief Encode the provided node.
51  */
52 static int bejEncodeNode(void* node, struct BejEncoderOutputHandler* output)
53 {
54     struct RedfishPropertyNode* nodeInfo = node;
55     switch (nodeInfo->format.principalDataType)
56     {
57         case bejSet:
58             RETURN_IF_IERROR(bejEncodeBejSetOrArray(node, output));
59             break;
60         default:
61             fprintf(stderr, "Unsupported node type: %d\n",
62                     nodeInfo->format.principalDataType);
63             return -1;
64     }
65     return 0;
66 }
67 
68 /**
69  * @brief A helper function to add a parent to the stack.
70  */
71 static int bejPushParentToStack(struct RedfishPropertyParent* parent,
72                                 struct BejPointerStackCallback* stack)
73 {
74     // Before pushing the parent node, initialize its nextChild as the first
75     // child.
76     parent->metaData.nextChild = parent->firstChild;
77     return stack->stackPush(parent, stack->stackContext);
78 }
79 
80 /**
81  * @brief Process all the child nodes of a parent.
82  */
83 static int bejProcessChildNodes(struct RedfishPropertyParent* parent,
84                                 struct BejPointerStackCallback* stack,
85                                 struct BejEncoderOutputHandler* output)
86 {
87     // Get the next child of the parent.
88     void* childPtr = parent->metaData.nextChild;
89 
90     while (childPtr != NULL)
91     {
92         // First encode the current child node.
93         RETURN_IF_IERROR(bejEncodeNode(childPtr, output));
94         // If this child node has its own children, add it to the stack and
95         // return. Because we need to encode the children of the newly added
96         // node before continuing to encode the child nodes of the current
97         // parent.
98         if (bejTreeIsParentType(childPtr))
99         {
100             RETURN_IF_IERROR(bejPushParentToStack(childPtr, stack));
101             // Update the next child of the current parent we need to
102             // process.
103             bejParentGoToNextChild(parent, childPtr);
104             return 0;
105         }
106         childPtr = bejParentGoToNextChild(parent, childPtr);
107     }
108     return 0;
109 }
110 
111 /**
112  * @brief Encode the provided JSON tree.
113  *
114  * The node metadata should be initialized before using this function.
115  */
116 static int bejEncodeTree(struct RedfishPropertyParent* root,
117                          struct BejPointerStackCallback* stack,
118                          struct BejEncoderOutputHandler* output)
119 {
120     // We need to encode a parent node before its child nodes. So encoding the
121     // root first.
122     RETURN_IF_IERROR(bejEncodeNode(root, output));
123     // Once the root is encoded, push it to the stack used to traverse the child
124     // nodes. We need to keep a parent in this stack until all the child nodes
125     // of this parent has been encoded. Only then we remove the parent node from
126     // the stack.
127     RETURN_IF_IERROR(bejPushParentToStack(root, stack));
128 
129     while (!stack->stackEmpty(stack->stackContext))
130     {
131         struct RedfishPropertyParent* parent =
132             stack->stackPeek(stack->stackContext);
133 
134         // Encode all the child nodes of the current parent node. If one of
135         // these child nodes has its own child nodes, that child node will be
136         // encoded and added to the stack and this function will return. The
137         // rest of the children of the current parent will be encoded later
138         // (after processing all the nodes under the child node added to the
139         // stack).
140         RETURN_IF_IERROR(bejProcessChildNodes(parent, stack, output));
141 
142         // If a new node hasn't been added to the stack by
143         // bejProcessChildNodes(), we know that this parent's child nodes have
144         // been processed. If a new node has been added, then next we need to
145         // process the children of the newly added node.
146         if (parent != stack->stackPeek(stack->stackContext))
147         {
148             continue;
149         }
150         stack->stackPop(stack->stackContext);
151     }
152     return 0;
153 }
154 
155 int bejEncode(const struct BejDictionaries* dictionaries,
156               uint16_t majorSchemaStartingOffset,
157               enum BejSchemaClass schemaClass,
158               struct RedfishPropertyParent* root,
159               struct BejEncoderOutputHandler* output,
160               struct BejPointerStackCallback* stack)
161 {
162     NULL_CHECK(dictionaries, "dictionaries");
163     NULL_CHECK(dictionaries->schemaDictionary, "schemaDictionary");
164     NULL_CHECK(dictionaries->annotationDictionary, "annotationDictionary");
165 
166     NULL_CHECK(root, "root");
167 
168     NULL_CHECK(output, "output");
169     NULL_CHECK(stack, "stack");
170 
171     // Assert root node.
172     if (root->nodeAttr.format.principalDataType != bejSet)
173     {
174         fprintf(stderr, "Invalid root node\n");
175         return -1;
176     }
177 
178     // First we need to encode a parent node before its child nodes. But before
179     // encoding the parent node, the encoder has to figure out the total size
180     // need to encode the parent's child nodes. Therefore first the encoder need
181     // to visit the child nodes and calculate the size need to encode them
182     // before producing the encoded bytes for the parent node.
183     //
184     // So first the encoder will visit child nodes and calculate the size need
185     // to encode each child node. Then store this information in metadata
186     // properties in each node struct.
187     // Next the encoder will again visit each node starting from the parent
188     // node, and produce the encoded bytes.
189 
190     // First calculate metadata for encoding each node.
191     RETURN_IF_IERROR(bejUpdateNodeMetadata(
192         dictionaries, majorSchemaStartingOffset, root, stack));
193 
194     // Derive the header of the encoded output.
195     // BEJ version
196     uint32_t version = BEJ_VERSION;
197     RETURN_IF_IERROR(
198         output->recvOutput(&version, sizeof(uint32_t), output->handlerContext));
199     uint16_t reserved = 0;
200     RETURN_IF_IERROR(output->recvOutput(&reserved, sizeof(uint16_t),
201                                         output->handlerContext));
202     RETURN_IF_IERROR(output->recvOutput(&schemaClass, sizeof(uint8_t),
203                                         output->handlerContext));
204 
205     // Produce the encoded bytes for the nodes using the previously calculated
206     // metadata.
207     return bejEncodeTree(root, stack, output);
208 }
209