xref: /openbmc/libbej/src/bej_encoder_core.c (revision 0a29193f)
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 an integer to bejInteger type.
51  */
52 static uint8_t bejEncodeInteger(int64_t val,
53                                 struct BejEncoderOutputHandler* output)
54 {
55     uint8_t copyLength = bejIntLengthOfValue(val);
56     return output->recvOutput(&val, copyLength, output->handlerContext);
57 }
58 
59 /**
60  * @brief Encode a BejInteger type.
61  */
62 int bejEncodeBejInteger(struct RedfishPropertyLeafInt* node,
63                         struct BejEncoderOutputHandler* output)
64 {
65     // Encode Sequence number.
66     RETURN_IF_IERROR(
67         bejEncodeNnint(node->leaf.metaData.sequenceNumber, output));
68     // Add the format.
69     RETURN_IF_IERROR(bejEncodeFormat(&node->leaf.nodeAttr.format, output));
70     // Encode the value length.
71     RETURN_IF_IERROR(bejEncodeNnint(node->leaf.metaData.vSize, output));
72     // Encode the value.
73     return bejEncodeInteger(node->value, output);
74 }
75 
76 /**
77  * @brief Encode a BejEnum type.
78  */
79 int bejEncodeBejEnum(struct RedfishPropertyLeafEnum* node,
80                      struct BejEncoderOutputHandler* output)
81 {
82     // S: Encode Sequence number.
83     RETURN_IF_IERROR(
84         bejEncodeNnint(node->leaf.metaData.sequenceNumber, output));
85     // F: Add the format.
86     RETURN_IF_IERROR(bejEncodeFormat(&node->leaf.nodeAttr.format, output));
87     // L: Encode the value length.
88     RETURN_IF_IERROR(bejEncodeNnint(node->leaf.metaData.vSize, output));
89     // V: Encode the value.
90     return bejEncodeNnint(node->enumValueSeq, output);
91 }
92 
93 /**
94  * @brief Encode a BejNull type.
95  */
96 int bejEncodeBejNull(struct RedfishPropertyLeafNull* node,
97                      struct BejEncoderOutputHandler* output)
98 {
99     // S: Encode Sequence number.
100     RETURN_IF_IERROR(
101         bejEncodeNnint(node->leaf.metaData.sequenceNumber, output));
102     // F: Add the format.
103     RETURN_IF_IERROR(bejEncodeFormat(&node->leaf.nodeAttr.format, output));
104     // L: Encode the value length.
105     return bejEncodeNnint(node->leaf.metaData.vSize, output);
106 }
107 
108 /**
109  * @brief Encode the provided node.
110  */
111 static int bejEncodeNode(void* node, struct BejEncoderOutputHandler* output)
112 {
113     struct RedfishPropertyNode* nodeInfo = node;
114     switch (nodeInfo->format.principalDataType)
115     {
116         case bejSet:
117             RETURN_IF_IERROR(bejEncodeBejSetOrArray(node, output));
118             break;
119         case bejArray:
120             RETURN_IF_IERROR(bejEncodeBejSetOrArray(node, output));
121             break;
122         case bejNull:
123             RETURN_IF_IERROR(bejEncodeBejNull(node, output));
124             break;
125         case bejInteger:
126             RETURN_IF_IERROR(bejEncodeBejInteger(node, output));
127             break;
128         case bejEnum:
129             RETURN_IF_IERROR(bejEncodeBejEnum(node, output));
130             break;
131         default:
132             fprintf(stderr, "Unsupported node type: %d\n",
133                     nodeInfo->format.principalDataType);
134             return -1;
135     }
136     return 0;
137 }
138 
139 /**
140  * @brief A helper function to add a parent to the stack.
141  */
142 static int bejPushParentToStack(struct RedfishPropertyParent* parent,
143                                 struct BejPointerStackCallback* stack)
144 {
145     // Before pushing the parent node, initialize its nextChild as the first
146     // child.
147     parent->metaData.nextChild = parent->firstChild;
148     return stack->stackPush(parent, stack->stackContext);
149 }
150 
151 /**
152  * @brief Process all the child nodes of a parent.
153  */
154 static int bejProcessChildNodes(struct RedfishPropertyParent* parent,
155                                 struct BejPointerStackCallback* stack,
156                                 struct BejEncoderOutputHandler* output)
157 {
158     // Get the next child of the parent.
159     void* childPtr = parent->metaData.nextChild;
160 
161     while (childPtr != NULL)
162     {
163         // First encode the current child node.
164         RETURN_IF_IERROR(bejEncodeNode(childPtr, output));
165         // If this child node has its own children, add it to the stack and
166         // return. Because we need to encode the children of the newly added
167         // node before continuing to encode the child nodes of the current
168         // parent.
169         if (bejTreeIsParentType(childPtr))
170         {
171             RETURN_IF_IERROR(bejPushParentToStack(childPtr, stack));
172             // Update the next child of the current parent we need to
173             // process.
174             bejParentGoToNextChild(parent, childPtr);
175             return 0;
176         }
177         childPtr = bejParentGoToNextChild(parent, childPtr);
178     }
179     return 0;
180 }
181 
182 /**
183  * @brief Encode the provided JSON tree.
184  *
185  * The node metadata should be initialized before using this function.
186  */
187 static int bejEncodeTree(struct RedfishPropertyParent* root,
188                          struct BejPointerStackCallback* stack,
189                          struct BejEncoderOutputHandler* output)
190 {
191     // We need to encode a parent node before its child nodes. So encoding the
192     // root first.
193     RETURN_IF_IERROR(bejEncodeNode(root, output));
194     // Once the root is encoded, push it to the stack used to traverse the child
195     // nodes. We need to keep a parent in this stack until all the child nodes
196     // of this parent has been encoded. Only then we remove the parent node from
197     // the stack.
198     RETURN_IF_IERROR(bejPushParentToStack(root, stack));
199 
200     while (!stack->stackEmpty(stack->stackContext))
201     {
202         struct RedfishPropertyParent* parent =
203             stack->stackPeek(stack->stackContext);
204 
205         // Encode all the child nodes of the current parent node. If one of
206         // these child nodes has its own child nodes, that child node will be
207         // encoded and added to the stack and this function will return. The
208         // rest of the children of the current parent will be encoded later
209         // (after processing all the nodes under the child node added to the
210         // stack).
211         RETURN_IF_IERROR(bejProcessChildNodes(parent, stack, output));
212 
213         // If a new node hasn't been added to the stack by
214         // bejProcessChildNodes(), we know that this parent's child nodes have
215         // been processed. If a new node has been added, then next we need to
216         // process the children of the newly added node.
217         if (parent != stack->stackPeek(stack->stackContext))
218         {
219             continue;
220         }
221         stack->stackPop(stack->stackContext);
222     }
223     return 0;
224 }
225 
226 int bejEncode(const struct BejDictionaries* dictionaries,
227               uint16_t majorSchemaStartingOffset,
228               enum BejSchemaClass schemaClass,
229               struct RedfishPropertyParent* root,
230               struct BejEncoderOutputHandler* output,
231               struct BejPointerStackCallback* stack)
232 {
233     NULL_CHECK(dictionaries, "dictionaries");
234     NULL_CHECK(dictionaries->schemaDictionary, "schemaDictionary");
235     NULL_CHECK(dictionaries->annotationDictionary, "annotationDictionary");
236 
237     NULL_CHECK(root, "root");
238 
239     NULL_CHECK(output, "output");
240     NULL_CHECK(stack, "stack");
241 
242     // Assert root node.
243     if (root->nodeAttr.format.principalDataType != bejSet)
244     {
245         fprintf(stderr, "Invalid root node\n");
246         return -1;
247     }
248 
249     // First we need to encode a parent node before its child nodes. But before
250     // encoding the parent node, the encoder has to figure out the total size
251     // need to encode the parent's child nodes. Therefore first the encoder need
252     // to visit the child nodes and calculate the size need to encode them
253     // before producing the encoded bytes for the parent node.
254     //
255     // So first the encoder will visit child nodes and calculate the size need
256     // to encode each child node. Then store this information in metadata
257     // properties in each node struct.
258     // Next the encoder will again visit each node starting from the parent
259     // node, and produce the encoded bytes.
260 
261     // First calculate metadata for encoding each node.
262     RETURN_IF_IERROR(bejUpdateNodeMetadata(
263         dictionaries, majorSchemaStartingOffset, root, stack));
264 
265     // Derive the header of the encoded output.
266     // BEJ version
267     uint32_t version = BEJ_VERSION;
268     RETURN_IF_IERROR(
269         output->recvOutput(&version, sizeof(uint32_t), output->handlerContext));
270     uint16_t reserved = 0;
271     RETURN_IF_IERROR(output->recvOutput(&reserved, sizeof(uint16_t),
272                                         output->handlerContext));
273     RETURN_IF_IERROR(output->recvOutput(&schemaClass, sizeof(uint8_t),
274                                         output->handlerContext));
275 
276     // Produce the encoded bytes for the nodes using the previously calculated
277     // metadata.
278     return bejEncodeTree(root, stack, output);
279 }
280