Line data Source code
1 : /* SPDX-License-Identifier: GPL-2.0-only */
2 : /*
3 : * Copyright (c) 2022 Meta Platforms, Inc. and affiliates.
4 : */
5 :
6 : #pragma once
7 :
8 : #include <stdbool.h>
9 : #include <stddef.h>
10 : #include <stdint.h>
11 :
12 : #include <bpfilter/logger.h>
13 :
14 : /**
15 : * @file pack.h
16 : *
17 : * Serialization is used to send/receive bpfilter objects to and from the
18 : * daemon. While bpfilter originally used a custom logic to convert its objects
19 : * into binary data, it was inefficient and didn't support different versions of
20 : * the same object (when fields are added or removed). Instead, the new packing
21 : * logic provides an API to (de)serialize bpfilter objects using
22 : * [MessagePack](https://msgpack.org).
23 : *
24 : * The packing mechanism doesn't implement the MessagePack protocol directly,
25 : * but relies on the [MPack](https://ludocode.github.io/mpack/) implementation
26 : * instead. MPack provides the following advantages:
27 : * - Fast (de)serialization: 4x faster than the previous custom serialization
28 : * logic, and 10x faster than Jansson.
29 : * - Named fields: each serialized field of an object is named, allowing the
30 : * objects to evolve and introduce or remove new fields while maintaining
31 : * backward compatibility.
32 : * - Can be used in place: existing objects format can be used as-is, without
33 : * extra field dedicated to the serializer or schema file (hello Protobuf
34 : * and Thrift).
35 : *
36 : * Two different packing objects are provided:
37 : * - `bf_wpack_t`: the writer object, to serialize data. To keep the
38 : * the serialization as simple as possible, no error will be returned when
39 : * a field is serialized. Instead, users must use `bf_wpack_is_valid` to
40 : * validate the serialized data before it is exported. This behaviour is
41 : * different for the rest of the core module, but is justified for usability
42 : * reasons.
43 : * - `bf_rpack_t`: node-based API to read serialized data. Each serialized field
44 : * can be represented as a node. The object deserialization functions should
45 : * expect to receive a node which represents them.
46 : *
47 : * ## Serializing
48 : * `bf_wpack_t` always have a root node containing key-value pairs. Keys should
49 : * be strings, and values can be any primal type, array, or another object.
50 : * `bf_wpack_kv_TYPE` functions should be used to write both the key and the
51 : * value in an object, `bf_wpack_TYPE` functions should be used to write single
52 : * objects (without a key) into arrays.
53 : * To nest objects and arrays, use `bf_wpack_open_object` and
54 : * `bf_wpack_open_array`, they both allow the caller to provide a key: if the
55 : * object (or array) is inserted into an object you should provide a key, if the
56 : * object (or array) is inserted into an array you should not provide a key. In
57 : * any case, call `bf_wpack_close_object` or `bf_wpack_close_array` to complete
58 : * the nested object/array.
59 : * Before using the serialized data, ensure it is valid with `bf_wpack_is_valid`,
60 : * as not error will be returned during the pack creation.
61 : *
62 : * ## Deserializing
63 : * `bf_rpack_t` is the deserialization object, although we do not read directly
64 : * from it. Instead, one should get the root object with `bf_rpack_root`, then
65 : * query keys from it.
66 : * All the functions used to deserialize data from the pack are available in two
67 : * formats:
68 : * - `bf_rpack_TYPE`: read a typed value from a node.
69 : * - `bf_rpack_kv_TYPE`: read the value identified by a key in an object node.
70 : * To prevent any tempering or corruption, every function able to read data from
71 : * a node will return a value: 0 on success, or a negative integer on failure.
72 : *
73 : * @note To ensure compatibility of the binary data over time, the following
74 : * constraints must be followed:
75 : * - Every enumeration value is serialized as integer: it should support every
76 : * possible enumeration value used by bpfilter, including negative values.
77 : * - `size_t` values are serialied as `uint64_t`: for similar reasons to he
78 : * enumerations.
79 : *
80 : * @todo `bf_wpack_kv_TYPE` functions should only write into objects.
81 : * @todo `bf_wpack_TYPE` functions should only write into arrays.
82 : * @todo Create dedicated functions to pack and unpack a `size_t` and `ssize_t`.
83 : * @todo Functions that write to the pack object should be more verbose: even
84 : * if they don't return an error code, they should log a warning when a packing
85 : * operation fails.
86 : */
87 :
88 : struct bf_wpack;
89 : typedef struct bf_wpack bf_wpack_t;
90 :
91 : struct bf_list;
92 : typedef struct bf_list bf_list;
93 :
94 : /// @brief Cleanup attribute for dynamically allocated `bf_wpack_t` objects.
95 : #define _free_bf_wpack_ __attribute__((cleanup(bf_wpack_free)))
96 :
97 : /**
98 : * @brief Allocate and initialize a new `bf_wpack_t` object.
99 : *
100 : * @param pack `bf_wpack_t` object to allocate and initialize. Can't be NULL.
101 : * @return 0 on sucess, or a negative error value on failure.
102 : */
103 : int bf_wpack_new(bf_wpack_t **pack);
104 :
105 : /**
106 : * @brief Deinitialize and deallocate a `bf_wpack_t` object.
107 : *
108 : * @param pack `bf_wpack_t` object to deallocate and deinitialize. Can't be
109 : * NULL. If `*pack` is NULL, this function has no effect.
110 : */
111 : void bf_wpack_free(bf_wpack_t **pack);
112 :
113 : /**
114 : * @brief Check if the serialized data is valid.
115 : *
116 : * @param pack `bf_wpack_t` object to check. Can't be NULL.
117 : * @return True if the serialized data is valid, false otherwise.
118 : */
119 : bool bf_wpack_is_valid(bf_wpack_t *pack);
120 :
121 : /**
122 : * @brief Get the serialized data.
123 : *
124 : * Ensure the buffer is flushed and all the remaining data has been serialized.
125 : * On success, `*data` points to a buffer containing `data_len` bytes. The
126 : * caller do not own the data.
127 : *
128 : * @return 0 on success, or a negative error value on failure.
129 : */
130 : int bf_wpack_get_data(bf_wpack_t *pack, const void **data, size_t *data_len);
131 :
132 : void bf_wpack_nil(bf_wpack_t *pack);
133 : void bf_wpack_int(bf_wpack_t *pack, int value);
134 : void bf_wpack_uint(bf_wpack_t *pack, unsigned int value);
135 : void bf_wpack_u8(bf_wpack_t *pack, uint8_t value);
136 : void bf_wpack_u16(bf_wpack_t *pack, uint16_t value);
137 : void bf_wpack_u32(bf_wpack_t *pack, uint32_t value);
138 : void bf_wpack_u64(bf_wpack_t *pack, uint64_t value);
139 : void bf_wpack_bool(bf_wpack_t *pack, bool value);
140 : void bf_wpack_str(bf_wpack_t *pack, const char *value);
141 : void bf_wpack_bin(bf_wpack_t *pack, const void *value, size_t len);
142 : void bf_wpack_list(bf_wpack_t *pack, const bf_list *value);
143 : #define bf_wpack_enum(pack, value) bf_wpack_int((pack), (value))
144 :
145 : static inline void bf_wpack_kv_nil(bf_wpack_t *pack, const char *key)
146 : {
147 2 : bf_wpack_str(pack, key);
148 2 : bf_wpack_nil(pack);
149 2 : }
150 :
151 : static inline void bf_wpack_kv_int(bf_wpack_t *pack, const char *key, int value)
152 : {
153 51 : bf_wpack_str(pack, key);
154 51 : bf_wpack_int(pack, value);
155 0 : }
156 :
157 : static inline void bf_wpack_kv_uint(bf_wpack_t *pack, const char *key,
158 : unsigned int value)
159 : {
160 0 : bf_wpack_str(pack, key);
161 0 : bf_wpack_uint(pack, value);
162 0 : }
163 :
164 : static inline void bf_wpack_kv_u8(bf_wpack_t *pack, const char *key,
165 : uint8_t value)
166 : {
167 5 : bf_wpack_str(pack, key);
168 5 : bf_wpack_u8(pack, value);
169 : }
170 :
171 : static inline void bf_wpack_kv_u16(bf_wpack_t *pack, const char *key,
172 : uint16_t value)
173 : {
174 : bf_wpack_str(pack, key);
175 : bf_wpack_u16(pack, value);
176 : }
177 :
178 : static inline void bf_wpack_kv_u32(bf_wpack_t *pack, const char *key,
179 : uint32_t value)
180 : {
181 5 : bf_wpack_str(pack, key);
182 5 : bf_wpack_u32(pack, value);
183 : }
184 :
185 : static inline void bf_wpack_kv_u64(bf_wpack_t *pack, const char *key,
186 : uint64_t value)
187 : {
188 24 : bf_wpack_str(pack, key);
189 24 : bf_wpack_u64(pack, value);
190 : }
191 :
192 : static inline void bf_wpack_kv_bool(bf_wpack_t *pack, const char *key,
193 : bool value)
194 : {
195 5 : bf_wpack_str(pack, key);
196 5 : bf_wpack_bool(pack, value);
197 : }
198 :
199 : static inline void bf_wpack_kv_str(bf_wpack_t *pack, const char *key,
200 : const char *str)
201 : {
202 11 : bf_wpack_str(pack, key);
203 11 : bf_wpack_str(pack, str);
204 0 : }
205 :
206 : static inline void bf_wpack_kv_bin(bf_wpack_t *pack, const char *key,
207 : const void *data, size_t data_len)
208 : {
209 23 : bf_wpack_str(pack, key);
210 23 : bf_wpack_bin(pack, data, data_len);
211 : }
212 :
213 : static inline void bf_wpack_kv_enum(bf_wpack_t *pack, const char *key,
214 : int value)
215 : {
216 7 : bf_wpack_str(pack, key);
217 7 : bf_wpack_enum(pack, value);
218 : }
219 :
220 : void bf_wpack_kv_list(bf_wpack_t *pack, const char *key, const bf_list *value);
221 :
222 : /**
223 : * @brief Open a new object in the pack.
224 : *
225 : * Once this function returns, any call to `bf_wpack_TYPE` or `bf_wpack_kv_TYPE`
226 : * will insert data into the object.
227 : *
228 : * @param pack `bf_wpack_t` object to open the object in. Can't be NULL.
229 : * @param key Key to use for the object. If NULL, no key is inserted.
230 : */
231 : void bf_wpack_open_object(bf_wpack_t *pack, const char *key);
232 :
233 : /**
234 : * @brief Close the current object.
235 : *
236 : * Once the object is closed, any call to `bf_wpack_TYPE` or `bf_wpack_kv_TYPE`
237 : * will insert data into the parent object or array.
238 : *
239 : * @param pack `bf_wpack_t` object to close the object in. Can't be NULL.
240 : */
241 : void bf_wpack_close_object(bf_wpack_t *pack);
242 :
243 : /**
244 : * @brief Open a new array in the pack.
245 : *
246 : * Once this function returns, any call to `bf_wpack_TYPE` or `bf_wpack_kv_TYPE`
247 : * will insert data into the array.
248 : *
249 : * @param pack `bf_wpack_t` object to open the array in. Can't be NULL.
250 : * @param key Key to use for the array. If NULL, no key is inserted.
251 : */
252 : void bf_wpack_open_array(bf_wpack_t *pack, const char *key);
253 :
254 : /**
255 : * @brief Close the current array.
256 : *
257 : * Once the array is closed, any call to `bf_wpack_TYPE` or `bf_wpack_kv_TYPE`
258 : * will insert data into the parent object or array.
259 : *
260 : * @param pack `bf_wpack_t` object to close the array in. Can't be NULL.
261 : */
262 : void bf_wpack_close_array(bf_wpack_t *pack);
263 :
264 : struct bf_rpack;
265 : typedef struct bf_rpack bf_rpack_t;
266 :
267 : /**
268 : * @brief Opaque structure to pass nodes by value.
269 : */
270 : typedef union
271 : {
272 : char _opaque[16];
273 : void *_align;
274 : } bf_rpack_node_t;
275 :
276 : /// @brief Cleanup attribute for dynamically allocated `bf_rpack_t` objects.
277 : #define _free_bf_rpack_ __attribute__((cleanup(bf_rpack_free)))
278 :
279 : /**
280 : * @brief Loop over all the nodes in an array node.
281 : *
282 : * The counter `i` is accessible within the scope.
283 : *
284 : * @warning This loop must ensure we don't query the node for invalid data:
285 : * we should not request a node from the array if it's empty, of if the index
286 : * is out of the array. Otherwise, it would put the `bf_rpack_t` object in
287 : * failure mode.
288 : *
289 : * @param node Array node. If the node is not an array, the loop will be
290 : * ignored.
291 : * @param value_node A node in the array, updated on each iteration.
292 : */
293 : #define bf_rpack_array_foreach(node, value_node) \
294 : for (size_t i = ((bf_rpack_array_count(node) ? \
295 : ((value_node) = bf_rpack_array_value_at(node, 0)) : \
296 : (bf_rpack_node_t) {}), \
297 : 0); \
298 : i < bf_rpack_array_count(node); \
299 : ++i, (value_node) = (i < bf_rpack_array_count(node) ? \
300 : bf_rpack_array_value_at(node, i) : \
301 : (bf_rpack_node_t) {}))
302 :
303 : /**
304 : * @brief Read an named enumerator value from a node.
305 : *
306 : * This macro will ensure the enumerator value is properly casted from the
307 : * corresponding integer stored in the pack.
308 : *
309 : * @param node Node to read from.
310 : * @param key Key of the node to read. Can't be NULL.
311 : * @param value Pointer to the enumerator value to read into.
312 : * @return 0 on success, or a negative error value on failure.
313 : */
314 : #define bf_rpack_kv_enum(node, key, value) \
315 : ({ \
316 : int __value; \
317 : int __r = bf_rpack_kv_int(node, key, &__value); \
318 : if (!__r) \
319 : *(value) = __value; \
320 : __r; \
321 : })
322 :
323 : /**
324 : * @brief Read an enumerator value from a node.
325 : *
326 : * Similar to `bf_rpack_kv_enum` but reads a node directly.
327 : *
328 : * @param node Node to read from.
329 : * @param value Pointer to the enumerator value to read into.
330 : * @return 0 on success, or a negative error value on failure.
331 : */
332 : #define bf_rpack_enum(node, value) \
333 : ({ \
334 : int __value; \
335 : int __r = bf_rpack_int(node, &__value); \
336 : if (!__r) \
337 : *(value) = __value; \
338 : __r; \
339 : })
340 :
341 : /**
342 : * @brief Log a missing key error and return a negative error value.
343 : *
344 : * @param v The error value to return.
345 : * @param key The missing key, as a string.
346 : */
347 : #define bf_rpack_key_err(v, key) bf_err_r(v, "failed to read %s from pack", key)
348 :
349 : /**
350 : * @brief Allocate and initialize a new `bf_rpack_t` object.
351 : *
352 : * @param pack `bf_rpack_t` object to allocate and initialize. Can't be NULL.
353 : * @param data Serialized data. To prevent large copies, the deserialization
354 : * object won't take ownership of the data. Can't be NULL.
355 : * @param data_len Length of `data`.
356 : * @return 0 on success, or a negative error value on failure.
357 : */
358 : int bf_rpack_new(bf_rpack_t **pack, const void *data, size_t data_len);
359 :
360 : /**
361 : * @brief Deinitialize and deallocate a `bf_rpack_t` object.
362 : *
363 : * @param pack `bf_rpack_t` object to deallocate and deinitialize. Can't be
364 : * NULL. If `*pack` is NULL, this function has no effect.
365 : */
366 : void bf_rpack_free(bf_rpack_t **pack);
367 :
368 : /**
369 : * @brief Get the root node of `pack`.
370 : *
371 : * @param pack `bf_rpack_t` object to get the root node from. Can't be NULL.
372 : * @return Root node for `pack`.
373 : */
374 : bf_rpack_node_t bf_rpack_root(const bf_rpack_t *pack);
375 :
376 : /**
377 : * @brief Returns true if the node is nil, false otherwise.
378 : *
379 : * Nil nodes are used to specific an absent value (e.g. optional `bf_hookopts`).
380 : *
381 : * @param node Node to check for nil.
382 : * @return True if `node` is nil, false otherwise.
383 : */
384 : bool bf_rpack_is_nil(bf_rpack_node_t node);
385 :
386 : /**
387 : * @brief Returns true if the node is an array, false otherwise.
388 : *
389 : * @param node Node to check for array type.
390 : * @return True if `node` is an array, false otherwise.
391 : */
392 : bool bf_rpack_is_array(bf_rpack_node_t node);
393 :
394 : /**
395 : * @brief Returns the number of elements in an array node.
396 : *
397 : * To ensure the returned value is relevant, the caller should ensure `node` is
398 : * an array.
399 : *
400 : * @param node Array node to get the number of elements of.
401 : * @return The number of elements in `node`, or 0 if `node` is not an array.
402 : */
403 : size_t bf_rpack_array_count(bf_rpack_node_t node);
404 :
405 : /**
406 : * @brief Get an array element by index.
407 : *
408 : * The caller is responsible for ensuring `node` is an array and `index` is a
409 : * valid index in `node`, otherwise the returned node will be invalid.
410 : *
411 : * @param node Array node to get elements from.
412 : * @param index Index of the node to get.
413 : * @return The node of the element as `index` in `node`. */
414 : bf_rpack_node_t bf_rpack_array_value_at(bf_rpack_node_t node, size_t index);
415 :
416 : /**
417 : * @brief Check if an object node contains a given key.
418 : *
419 : * The caller is responsible for ensuring `node` is a valid object node. If
420 : * `node` is invalid or the wrong type, false is returned.
421 : *
422 : * @param node Object node to check.
423 : * @param key Key to check in `node`. Can't be NULL.
424 : * @return True if `key` is a valid key in `node`, false otherwise. On error,
425 : * false is returned.
426 : */
427 : bool bf_rpack_kv_contains(bf_rpack_node_t node, const char *key);
428 :
429 : int bf_rpack_int(bf_rpack_node_t node, int *value);
430 : int bf_rpack_uint(bf_rpack_node_t node, unsigned int *value);
431 : int bf_rpack_u8(bf_rpack_node_t node, uint8_t *value);
432 : int bf_rpack_u16(bf_rpack_node_t node, uint16_t *value);
433 : int bf_rpack_u32(bf_rpack_node_t node, uint32_t *value);
434 : int bf_rpack_u64(bf_rpack_node_t node, uint64_t *value);
435 : int bf_rpack_str(bf_rpack_node_t node, char **value);
436 : int bf_rpack_bool(bf_rpack_node_t node, bool *value);
437 : int bf_rpack_bin(bf_rpack_node_t node, const void **data, size_t *data_len);
438 :
439 : int bf_rpack_kv_node(bf_rpack_node_t node, const char *key,
440 : bf_rpack_node_t *child);
441 : int bf_rpack_kv_int(bf_rpack_node_t node, const char *key, int *value);
442 : int bf_rpack_kv_uint(bf_rpack_node_t node, const char *key,
443 : unsigned int *value);
444 : int bf_rpack_kv_u8(bf_rpack_node_t node, const char *key, uint8_t *value);
445 : int bf_rpack_kv_u16(bf_rpack_node_t node, const char *key, uint16_t *value);
446 : int bf_rpack_kv_u32(bf_rpack_node_t node, const char *key, uint32_t *value);
447 : int bf_rpack_kv_u64(bf_rpack_node_t node, const char *key, uint64_t *value);
448 : int bf_rpack_kv_str(bf_rpack_node_t node, const char *key, char **value);
449 : int bf_rpack_kv_bool(bf_rpack_node_t node, const char *key, bool *value);
450 : int bf_rpack_kv_bin(bf_rpack_node_t node, const char *key, const void **data,
451 : size_t *data_len);
452 : int bf_rpack_kv_obj(bf_rpack_node_t node, const char *key,
453 : bf_rpack_node_t *child);
454 : int bf_rpack_kv_array(bf_rpack_node_t node, const char *key,
455 : bf_rpack_node_t *child);
|