xref: /openbmc/libpldm/src/msgbuf.h (revision 76712f69)
1 /* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */
2 #ifndef PLDM_MSGBUF_H
3 #define PLDM_MSGBUF_H
4 
5 /*
6  * Historically, many of the structs exposed in libpldm's public headers are
7  * defined with __attribute__((packed)). This is unfortunate: it gives the
8  * impression that a wire-format buffer can be cast to the message type to make
9  * the message's fields easily accessible. As it turns out, that's not
10  * that's valid for several reasons:
11  *
12  * 1. Casting the wire-format buffer to a struct of the message type doesn't
13  *    abstract the endianness of message field values
14  *
15  * 2. Some messages contain packed tagged union fields which cannot be properly
16  *    described in a C struct.
17  *
18  * The msgbuf APIs exist to assist with (un)packing the wire-format in a way
19  * that is type-safe, spatially memory-safe, endian-safe, performant, and
20  * free of undefined-behaviour. Message structs that are added to the public
21  * library API should no-longer be marked __attribute__((packed)), and the
22  * implementation of their encode and decode functions must exploit the msgbuf
23  * API.
24  *
25  * However, we would like to allow implementation of codec functions in terms of
26  * msgbuf APIs even if they're decoding a message into a (historically) packed
27  * struct. Some of the complexity that follows is a consequence of the packed/
28  * unpacked conflict.
29  */
30 
31 #ifdef __cplusplus
32 /*
33  * Fix up C11's _Static_assert() vs C++'s static_assert().
34  *
35  * Can we please have nice things for once.
36  */
37 // NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
38 #define _Static_assert(...) static_assert(__VA_ARGS__)
39 extern "C" {
40 #endif
41 
42 #include <libpldm/base.h>
43 #include <libpldm/pldm_types.h>
44 
45 #include "compiler.h"
46 
47 #include <assert.h>
48 #include <endian.h>
49 #include <limits.h>
50 #include <stdbool.h>
51 #include <stdint.h>
52 #include <string.h>
53 #include <sys/types.h>
54 
55 /*
56  * We can't use static_assert() outside of some other C construct. Deal
57  * with high-level global assertions by burying them in an unused struct
58  * declaration, that has a sole member for compliance with the requirement that
59  * types must have a size.
60 */
61 static struct {
62 	static_assert(
63 		INTMAX_MAX != SIZE_MAX,
64 		"Extraction and insertion value comparisons may be broken");
65 	static_assert(INTMAX_MIN + INTMAX_MAX <= 0,
66 		      "Extraction and insertion arithmetic may be broken");
67 	int compliance;
68 } build_assertions __attribute__((unused));
69 
70 struct pldm_msgbuf {
71 	uint8_t *cursor;
72 	intmax_t remaining;
73 };
74 
75 /**
76  * @brief Initialize pldm buf struct for buf extractor
77  *
78  * @param[out] ctx - pldm_msgbuf context for extractor
79  * @param[in] minsize - The minimum required length of buffer `buf`
80  * @param[in] buf - buffer to be extracted
81  * @param[in] len - size of buffer
82  *
83  * @return PLDM_SUCCESS if all buffer accesses were in-bounds,
84  * PLDM_ERROR_INVALID_DATA if pointer parameters are invalid, or
85  * PLDM_ERROR_INVALID_LENGTH if length constraints are violated.
86  */
87 __attribute__((always_inline)) static inline int
88 pldm_msgbuf_init(struct pldm_msgbuf *ctx, size_t minsize, const void *buf,
89 		 size_t len)
90 {
91 	if (!ctx || !buf) {
92 		return PLDM_ERROR_INVALID_DATA;
93 	}
94 
95 	if ((minsize > len)) {
96 		return PLDM_ERROR_INVALID_LENGTH;
97 	}
98 
99 #if INTMAX_MAX < SIZE_MAX
100 	if (len > INTMAX_MAX) {
101 		return PLDM_ERROR_INVALID_LENGTH;
102 	}
103 #endif
104 
105 	if ((uintptr_t)buf + len < len) {
106 		return PLDM_ERROR_INVALID_LENGTH;
107 	}
108 
109 	ctx->cursor = (uint8_t *)buf;
110 	ctx->remaining = (intmax_t)len;
111 
112 	return PLDM_SUCCESS;
113 }
114 
115 /**
116  * @brief Validate buffer overflow state
117  *
118  * @param[in] ctx - pldm_msgbuf context for extractor
119  *
120  * @return PLDM_SUCCESS if there are zero or more bytes of data that remain
121  * unread from the buffer. Otherwise, PLDM_ERROR_INVALID_LENGTH indicates that a
122  * prior accesses would have occurred beyond the bounds of the buffer, and
123  * PLDM_ERROR_INVALID_DATA indicates that the provided context was not a valid
124  * pointer.
125  */
126 __attribute__((always_inline)) static inline int
127 pldm_msgbuf_validate(struct pldm_msgbuf *ctx)
128 {
129 	if (!ctx) {
130 		return PLDM_ERROR_INVALID_DATA;
131 	}
132 
133 	return ctx->remaining >= 0 ? PLDM_SUCCESS : PLDM_ERROR_INVALID_LENGTH;
134 }
135 
136 /**
137  * @brief Test whether a message buffer has been exactly consumed
138  *
139  * @param[in] ctx - pldm_msgbuf context for extractor
140  *
141  * @return PLDM_SUCCESS iff there are zero bytes of data that remain unread from
142  * the buffer and no overflow has occurred. Otherwise, PLDM_ERROR_INVALID_LENGTH
143  * indicates that an incorrect sequence of accesses have occurred, and
144  * PLDM_ERROR_INVALID_DATA indicates that the provided context was not a valid
145  * pointer.
146  */
147 __attribute__((always_inline)) static inline int
148 pldm_msgbuf_consumed(struct pldm_msgbuf *ctx)
149 {
150 	if (!ctx) {
151 		return PLDM_ERROR_INVALID_DATA;
152 	}
153 
154 	return ctx->remaining == 0 ? PLDM_SUCCESS : PLDM_ERROR_INVALID_LENGTH;
155 }
156 
157 /**
158  * @brief Destroy the pldm buf
159  *
160  * @param[in] ctx - pldm_msgbuf context for extractor
161  *
162  * @return PLDM_SUCCESS if all buffer accesses were in-bounds,
163  * PLDM_ERROR_INVALID_DATA if the ctx parameter is invalid, or
164  * PLDM_ERROR_INVALID_LENGTH if prior accesses would have occurred beyond the
165  * bounds of the buffer.
166  */
167 __attribute__((always_inline)) static inline int
168 pldm_msgbuf_destroy(struct pldm_msgbuf *ctx)
169 {
170 	int valid;
171 
172 	if (!ctx) {
173 		return PLDM_ERROR_INVALID_DATA;
174 	}
175 
176 	valid = pldm_msgbuf_validate(ctx);
177 
178 	ctx->cursor = NULL;
179 	ctx->remaining = 0;
180 
181 	return valid;
182 }
183 
184 /**
185  * @brief Destroy the pldm_msgbuf instance, and check that the underlying buffer
186  * has been completely consumed without overflow
187  *
188  * @param[in] ctx - pldm_msgbuf context
189  *
190  * @return PLDM_SUCCESS if all buffer access were in-bounds and completely
191  * consume the underlying buffer. Otherwise, PLDM_ERROR_INVALID_DATA if the ctx
192  * parameter is invalid, or PLDM_ERROR_INVALID_LENGTH if prior accesses would
193  * have occurred byond the bounds of the buffer
194  */
195 __attribute__((always_inline)) static inline int
196 pldm_msgbuf_destroy_consumed(struct pldm_msgbuf *ctx)
197 {
198 	int consumed;
199 
200 	if (!ctx) {
201 		return PLDM_ERROR_INVALID_DATA;
202 	}
203 
204 	consumed = pldm_msgbuf_consumed(ctx);
205 
206 	ctx->cursor = NULL;
207 	ctx->remaining = 0;
208 
209 	return consumed;
210 }
211 
212 /*
213  * Exploit the pre-processor to perform type checking by macro substitution.
214  *
215  * A C type is defined by its alignment as well as its object
216  * size, and compilers have a hammer to enforce it in the form of
217  * `-Waddress-of-packed-member`. Due to the unpacked/packed struct conflict in
218  * the libpldm public API this presents a problem: Naively attempting to use the
219  * msgbuf APIs on a member of a packed struct would yield an error.
220  *
221  * The msgbuf APIs are implemented such that data is moved through unaligned
222  * pointers in a safe way, but to mitigate `-Waddress-of-packed-member` we must
223  * make the object pointers take a trip through `void *` at its API boundary.
224  * That presents a bit too much of an opportunity to non-surgically remove your
225  * own foot, so here we set about doing something to mitigate that as well.
226  *
227  * pldm_msgbuf_extract_typecheck() exists to enforce pointer type correctness
228  * only for the purpose of object sizes, disregarding alignment. We have a few
229  * constraints that cause some headaches:
230  *
231  * 1. We have to perform the type-check before a call through a C function,
232  *    as the function must take the object pointer argument as `void *`.
233  *    Essentially, this constrains us to doing something with macros.
234  *
235  * 2. While libpldm is a C library, its test suite is written in C++ to take
236  *    advantage of gtest.
237  *
238  * 3. Ideally we'd do something with C's `static_assert()`, however
239  *    `static_assert()` is defined as void, and as we're constrained to macros,
240  *    using `static_assert()` would require a statement-expression
241  *
242  * 4. Currently the project is built with `-std=c17`. CPP statement-expressions
243  *    are a GNU extension. We prefer to avoid switching to `-std=gnu17` just for
244  *    the purpose of enabling statement-expressions in this one instance.
245  *
246  * 5. We can achieve a conditional build error using `pldm_require_obj_type()`,
247  *    however it's implemented in terms of `_Generic()`, which is not available
248  *    in C++.
249  *
250  * Combined this means we need separate solutions for C and C++.
251  *
252  * For C, as we don't have statement-expressions, we need to exploit some other
253  * language feature to inject a `pldm_require_obj_type()` prior to the msgbuf
254  * API function call. We also have to take care of the fact that the call-sites
255  * may be in the context of a variable assignment for error-handling purposes.
256  * The key observation is that we can use the comma operator as a sequence point
257  * to order the type check before the API call, discarding the "result" value of
258  * the type check and yielding the return value of the API call.
259  *
260  * C++ could be less of a headache than the C as we can leverage template
261  * functions. An advantage of template functions is that while their definition
262  * is driven by instantion, the definition does not appear at the source
263  * location of the instantation, which gives it a great leg-up over the problems
264  * we have in the C path. However, the use of the msgbuf APIs in the test suite
265  * still makes things somewhat tricky, as the call-sites in the test suite are
266  * wrapped up in EXPECT_*() gtest macros. Ideally we'd implement functions that
267  * takes both the object type and the required type as template arguments, and
268  * then define the object pointer parameter as `void *` for a call through to
269  * the appropriate msgbuf API. However, because the msgbuf API call-sites are
270  * encapsulated in gtest macros, use of commas in the template specification
271  * causes pre-processor confusion. In this way we're constrained to only one
272  * template argument per function.
273  *
274  * Implement the C++ path using template functions that take the destination
275  * object type as a template argument, while the name of the function symbols
276  * are derived from the required type. The manual implementations of these
277  * appear at the end of the header. The type safety is actually enforced
278  * by `static_assert()` this time, as we can use statements as we're not
279  * constrained to an expression in the templated function body.
280  *
281  * The invocations of pldm_msgbuf_extract_typecheck() typically result in
282  * double-evaluation of some arguments. We're not yet bothered by this for two
283  * reasons:
284  *
285  * 1. The nature of the current call-sites are such that there are no
286  *    argument expressions that result in undesirable side-effects
287  *
288  * 2. It's an API internal to the libpldm implementation, and we can fix things
289  *    whenever something crops up the violates the observation in 1.
290  */
291 #ifdef __cplusplus
292 #define pldm_msgbuf_extract_typecheck(ty, fn, dst, ...)                        \
293 	pldm_msgbuf_typecheck_##ty<decltype(dst)>(__VA_ARGS__)
294 #else
295 #define pldm_msgbuf_extract_typecheck(ty, fn, dst, ...)                        \
296 	(pldm_require_obj_type(dst, ty), fn(__VA_ARGS__))
297 #endif
298 
299 /**
300  * @brief pldm_msgbuf extractor for a uint8_t
301  *
302  * @param[inout] ctx - pldm_msgbuf context for extractor
303  * @param[out] dst - destination of extracted value
304  *
305  * @return PLDM_SUCCESS if buffer accesses were in-bounds,
306  * PLDM_ERROR_INVALID_LENGTH otherwise.
307  * PLDM_ERROR_INVALID_DATA if input a invalid ctx
308  */
309 #define pldm_msgbuf_extract_uint8(ctx, dst)                                    \
310 	pldm_msgbuf_extract_typecheck(uint8_t, pldm__msgbuf_extract_uint8,     \
311 				      dst, ctx, dst)
312 __attribute__((always_inline)) static inline int
313 // NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
314 pldm__msgbuf_extract_uint8(struct pldm_msgbuf *ctx, void *dst)
315 {
316 	if (!ctx || !ctx->cursor || !dst) {
317 		return PLDM_ERROR_INVALID_DATA;
318 	}
319 
320 	if (ctx->remaining == INTMAX_MIN) {
321 		assert(ctx->remaining < 0);
322 		return PLDM_ERROR_INVALID_LENGTH;
323 	}
324 	ctx->remaining -= sizeof(uint8_t);
325 	assert(ctx->remaining >= 0);
326 	if (ctx->remaining < 0) {
327 		return PLDM_ERROR_INVALID_LENGTH;
328 	}
329 
330 	memcpy(dst, ctx->cursor, sizeof(uint8_t));
331 
332 	ctx->cursor++;
333 	return PLDM_SUCCESS;
334 }
335 
336 #define pldm_msgbuf_extract_int8(ctx, dst)                                     \
337 	pldm_msgbuf_extract_typecheck(int8_t, pldm__msgbuf_extract_int8, dst,  \
338 				      ctx, dst)
339 __attribute__((always_inline)) static inline int
340 // NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
341 pldm__msgbuf_extract_int8(struct pldm_msgbuf *ctx, void *dst)
342 {
343 	if (!ctx || !ctx->cursor || !dst) {
344 		return PLDM_ERROR_INVALID_DATA;
345 	}
346 
347 	if (ctx->remaining == INTMAX_MIN) {
348 		assert(ctx->remaining < 0);
349 		return PLDM_ERROR_INVALID_LENGTH;
350 	}
351 	ctx->remaining -= sizeof(int8_t);
352 	assert(ctx->remaining >= 0);
353 	if (ctx->remaining < 0) {
354 		return PLDM_ERROR_INVALID_LENGTH;
355 	}
356 
357 	memcpy(dst, ctx->cursor, sizeof(int8_t));
358 	ctx->cursor++;
359 	return PLDM_SUCCESS;
360 }
361 
362 #define pldm_msgbuf_extract_uint16(ctx, dst)                                   \
363 	pldm_msgbuf_extract_typecheck(uint16_t, pldm__msgbuf_extract_uint16,   \
364 				      dst, ctx, dst)
365 __attribute__((always_inline)) static inline int
366 // NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
367 pldm__msgbuf_extract_uint16(struct pldm_msgbuf *ctx, void *dst)
368 {
369 	uint16_t ldst;
370 
371 	if (!ctx || !ctx->cursor || !dst) {
372 		return PLDM_ERROR_INVALID_DATA;
373 	}
374 
375 	// Check for underflow while tracking the magnitude of the buffer overflow
376 	static_assert(
377 		// NOLINTNEXTLINE(bugprone-sizeof-expression)
378 		sizeof(ldst) < INTMAX_MAX,
379 		"The following addition may not uphold the runtime assertion");
380 	if (ctx->remaining < INTMAX_MIN + (intmax_t)sizeof(ldst)) {
381 		assert(ctx->remaining < 0);
382 		return PLDM_ERROR_INVALID_LENGTH;
383 	}
384 
385 	// Check for buffer overflow. If we overflow, account for the request as
386 	// negative values in ctx->remaining. This way we can debug how far
387 	// we've overflowed.
388 	ctx->remaining -= sizeof(ldst);
389 
390 	// Prevent the access if it would overflow. First, assert so we blow up
391 	// the test suite right at the point of failure. However, cater to
392 	// -DNDEBUG by explicitly testing that the access is valid.
393 	assert(ctx->remaining >= 0);
394 	if (ctx->remaining < 0) {
395 		return PLDM_ERROR_INVALID_LENGTH;
396 	}
397 
398 	// Use memcpy() to have the compiler deal with any alignment
399 	// issues on the target architecture
400 	memcpy(&ldst, ctx->cursor, sizeof(ldst));
401 
402 	// Only assign the target value once it's correctly decoded
403 	ldst = le16toh(ldst);
404 
405 	// Allow storing to unaligned
406 	memcpy(dst, &ldst, sizeof(ldst));
407 
408 	ctx->cursor += sizeof(ldst);
409 
410 	return PLDM_SUCCESS;
411 }
412 
413 #define pldm_msgbuf_extract_int16(ctx, dst)                                    \
414 	pldm_msgbuf_extract_typecheck(int16_t, pldm__msgbuf_extract_int16,     \
415 				      dst, ctx, dst)
416 __attribute__((always_inline)) static inline int
417 // NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
418 pldm__msgbuf_extract_int16(struct pldm_msgbuf *ctx, void *dst)
419 {
420 	int16_t ldst;
421 
422 	if (!ctx || !ctx->cursor || !dst) {
423 		return PLDM_ERROR_INVALID_DATA;
424 	}
425 
426 	static_assert(
427 		// NOLINTNEXTLINE(bugprone-sizeof-expression)
428 		sizeof(ldst) < INTMAX_MAX,
429 		"The following addition may not uphold the runtime assertion");
430 	if (ctx->remaining < INTMAX_MIN + (intmax_t)sizeof(ldst)) {
431 		assert(ctx->remaining < 0);
432 		return PLDM_ERROR_INVALID_LENGTH;
433 	}
434 	ctx->remaining -= sizeof(ldst);
435 	assert(ctx->remaining >= 0);
436 	if (ctx->remaining < 0) {
437 		return PLDM_ERROR_INVALID_LENGTH;
438 	}
439 
440 	memcpy(&ldst, ctx->cursor, sizeof(ldst));
441 
442 	ldst = le16toh(ldst);
443 	memcpy(dst, &ldst, sizeof(ldst));
444 	ctx->cursor += sizeof(ldst);
445 
446 	return PLDM_SUCCESS;
447 }
448 
449 #define pldm_msgbuf_extract_uint32(ctx, dst)                                   \
450 	pldm_msgbuf_extract_typecheck(uint32_t, pldm__msgbuf_extract_uint32,   \
451 				      dst, ctx, dst)
452 __attribute__((always_inline)) static inline int
453 // NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
454 pldm__msgbuf_extract_uint32(struct pldm_msgbuf *ctx, void *dst)
455 {
456 	uint32_t ldst;
457 
458 	if (!ctx || !ctx->cursor || !dst) {
459 		return PLDM_ERROR_INVALID_DATA;
460 	}
461 
462 	static_assert(
463 		// NOLINTNEXTLINE(bugprone-sizeof-expression)
464 		sizeof(ldst) < INTMAX_MAX,
465 		"The following addition may not uphold the runtime assertion");
466 	if (ctx->remaining < INTMAX_MIN + (intmax_t)sizeof(ldst)) {
467 		assert(ctx->remaining < 0);
468 		return PLDM_ERROR_INVALID_LENGTH;
469 	}
470 	ctx->remaining -= sizeof(ldst);
471 	assert(ctx->remaining >= 0);
472 	if (ctx->remaining < 0) {
473 		return PLDM_ERROR_INVALID_LENGTH;
474 	}
475 
476 	memcpy(&ldst, ctx->cursor, sizeof(ldst));
477 
478 	ldst = le32toh(ldst);
479 	memcpy(dst, &ldst, sizeof(ldst));
480 	ctx->cursor += sizeof(ldst);
481 
482 	return PLDM_SUCCESS;
483 }
484 
485 #define pldm_msgbuf_extract_int32(ctx, dst)                                    \
486 	pldm_msgbuf_extract_typecheck(int32_t, pldm__msgbuf_extract_int32,     \
487 				      dst, ctx, dst)
488 __attribute__((always_inline)) static inline int
489 // NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
490 pldm__msgbuf_extract_int32(struct pldm_msgbuf *ctx, void *dst)
491 {
492 	int32_t ldst;
493 
494 	if (!ctx || !ctx->cursor || !dst) {
495 		return PLDM_ERROR_INVALID_DATA;
496 	}
497 
498 	static_assert(
499 		// NOLINTNEXTLINE(bugprone-sizeof-expression)
500 		sizeof(ldst) < INTMAX_MAX,
501 		"The following addition may not uphold the runtime assertion");
502 	if (ctx->remaining < INTMAX_MIN + (intmax_t)sizeof(ldst)) {
503 		assert(ctx->remaining < 0);
504 		return PLDM_ERROR_INVALID_LENGTH;
505 	}
506 	ctx->remaining -= sizeof(ldst);
507 	assert(ctx->remaining >= 0);
508 	if (ctx->remaining < 0) {
509 		return PLDM_ERROR_INVALID_LENGTH;
510 	}
511 
512 	memcpy(&ldst, ctx->cursor, sizeof(ldst));
513 
514 	ldst = le32toh(ldst);
515 	memcpy(dst, &ldst, sizeof(ldst));
516 	ctx->cursor += sizeof(ldst);
517 
518 	return PLDM_SUCCESS;
519 }
520 
521 #define pldm_msgbuf_extract_real32(ctx, dst)                                   \
522 	pldm_msgbuf_extract_typecheck(real32_t, pldm__msgbuf_extract_real32,   \
523 				      dst, ctx, dst)
524 __attribute__((always_inline)) static inline int
525 // NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
526 pldm__msgbuf_extract_real32(struct pldm_msgbuf *ctx, void *dst)
527 {
528 	uint32_t ldst;
529 
530 	_Static_assert(sizeof(real32_t) == sizeof(ldst),
531 		       "Mismatched type sizes for dst and ldst");
532 
533 	if (!ctx || !ctx->cursor || !dst) {
534 		return PLDM_ERROR_INVALID_DATA;
535 	}
536 
537 	static_assert(
538 		// NOLINTNEXTLINE(bugprone-sizeof-expression)
539 		sizeof(ldst) < INTMAX_MAX,
540 		"The following addition may not uphold the runtime assertion");
541 	if (ctx->remaining < INTMAX_MIN + (intmax_t)sizeof(ldst)) {
542 		assert(ctx->remaining < 0);
543 		return PLDM_ERROR_INVALID_LENGTH;
544 	}
545 	ctx->remaining -= sizeof(ldst);
546 	assert(ctx->remaining >= 0);
547 	if (ctx->remaining < 0) {
548 		return PLDM_ERROR_INVALID_LENGTH;
549 	}
550 
551 	memcpy(&ldst, ctx->cursor, sizeof(ldst));
552 	ldst = le32toh(ldst);
553 	memcpy(dst, &ldst, sizeof(ldst));
554 	ctx->cursor += sizeof(ldst);
555 
556 	return PLDM_SUCCESS;
557 }
558 
559 /**
560  * Extract the field at the msgbuf cursor into the lvalue named by dst.
561  *
562  * @param ctx The msgbuf context object
563  * @param dst The lvalue into which the field at the msgbuf cursor should be
564  *            extracted
565  *
566  * @return PLDM_SUCCESS on success, otherwise another value on error
567  */
568 #define pldm_msgbuf_extract(ctx, dst)                                          \
569 	_Generic((dst),                                                        \
570 		uint8_t: pldm__msgbuf_extract_uint8,                           \
571 		int8_t: pldm__msgbuf_extract_int8,                             \
572 		uint16_t: pldm__msgbuf_extract_uint16,                         \
573 		int16_t: pldm__msgbuf_extract_int16,                           \
574 		uint32_t: pldm__msgbuf_extract_uint32,                         \
575 		int32_t: pldm__msgbuf_extract_int32,                           \
576 		real32_t: pldm__msgbuf_extract_real32)(ctx, (void *)&(dst))
577 
578 /**
579  * Extract the field at the msgbuf cursor into the object pointed-to by dst.
580  *
581  * @param ctx The msgbuf context object
582  * @param dst The pointer to the object into which the field at the msgbuf
583  *            cursor should be extracted
584  *
585  * @return PLDM_SUCCESS on success, otherwise another value on error
586  */
587 #define pldm_msgbuf_extract_p(ctx, dst)                                        \
588 	_Generic((dst),                                                        \
589 		uint8_t *: pldm__msgbuf_extract_uint8,                         \
590 		int8_t *: pldm__msgbuf_extract_int8,                           \
591 		uint16_t *: pldm__msgbuf_extract_uint16,                       \
592 		int16_t *: pldm__msgbuf_extract_int16,                         \
593 		uint32_t *: pldm__msgbuf_extract_uint32,                       \
594 		int32_t *: pldm__msgbuf_extract_int32,                         \
595 		real32_t *: pldm__msgbuf_extract_real32)(ctx, dst)
596 
597 __attribute__((always_inline)) static inline int
598 pldm_msgbuf_extract_array_uint8(struct pldm_msgbuf *ctx, uint8_t *dst,
599 				size_t count)
600 {
601 	if (!ctx || !ctx->cursor || !dst) {
602 		return PLDM_ERROR_INVALID_DATA;
603 	}
604 
605 	if (!count) {
606 		return PLDM_SUCCESS;
607 	}
608 
609 #if INTMAX_MAX < SIZE_MAX
610 	if (count > INTMAX_MAX) {
611 		return PLDM_ERROR_INVALID_LENGTH;
612 	}
613 #endif
614 
615 	if (ctx->remaining < INTMAX_MIN + (intmax_t)count) {
616 		return PLDM_ERROR_INVALID_LENGTH;
617 	}
618 	ctx->remaining -= (intmax_t)count;
619 	assert(ctx->remaining >= 0);
620 	if (ctx->remaining < 0) {
621 		return PLDM_ERROR_INVALID_LENGTH;
622 	}
623 
624 	memcpy(dst, ctx->cursor, count);
625 	ctx->cursor += count;
626 
627 	return PLDM_SUCCESS;
628 }
629 
630 #define pldm_msgbuf_extract_array(ctx, dst, count)                             \
631 	_Generic((*(dst)), uint8_t: pldm_msgbuf_extract_array_uint8)(ctx, dst, \
632 								     count)
633 
634 __attribute__((always_inline)) static inline int
635 pldm_msgbuf_insert_uint32(struct pldm_msgbuf *ctx, const uint32_t src)
636 {
637 	uint32_t val = htole32(src);
638 
639 	if (!ctx || !ctx->cursor) {
640 		return PLDM_ERROR_INVALID_DATA;
641 	}
642 
643 	static_assert(
644 		// NOLINTNEXTLINE(bugprone-sizeof-expression)
645 		sizeof(src) < INTMAX_MAX,
646 		"The following addition may not uphold the runtime assertion");
647 	if (ctx->remaining < INTMAX_MIN + (intmax_t)sizeof(src)) {
648 		assert(ctx->remaining < 0);
649 		return PLDM_ERROR_INVALID_LENGTH;
650 	}
651 	ctx->remaining -= sizeof(src);
652 	assert(ctx->remaining >= 0);
653 	if (ctx->remaining < 0) {
654 		return PLDM_ERROR_INVALID_LENGTH;
655 	}
656 
657 	memcpy(ctx->cursor, &val, sizeof(val));
658 	ctx->cursor += sizeof(src);
659 
660 	return PLDM_SUCCESS;
661 }
662 
663 __attribute__((always_inline)) static inline int
664 pldm_msgbuf_insert_uint16(struct pldm_msgbuf *ctx, const uint16_t src)
665 {
666 	uint16_t val = htole16(src);
667 
668 	if (!ctx || !ctx->cursor) {
669 		return PLDM_ERROR_INVALID_DATA;
670 	}
671 
672 	static_assert(
673 		// NOLINTNEXTLINE(bugprone-sizeof-expression)
674 		sizeof(src) < INTMAX_MAX,
675 		"The following addition may not uphold the runtime assertion");
676 	if (ctx->remaining < INTMAX_MIN + (intmax_t)sizeof(src)) {
677 		assert(ctx->remaining < 0);
678 		return PLDM_ERROR_INVALID_LENGTH;
679 	}
680 	ctx->remaining -= sizeof(src);
681 	assert(ctx->remaining >= 0);
682 	if (ctx->remaining < 0) {
683 		return PLDM_ERROR_INVALID_LENGTH;
684 	}
685 
686 	memcpy(ctx->cursor, &val, sizeof(val));
687 	ctx->cursor += sizeof(src);
688 
689 	return PLDM_SUCCESS;
690 }
691 
692 __attribute__((always_inline)) static inline int
693 pldm_msgbuf_insert_uint8(struct pldm_msgbuf *ctx, const uint8_t src)
694 {
695 	if (!ctx || !ctx->cursor) {
696 		return PLDM_ERROR_INVALID_DATA;
697 	}
698 
699 	static_assert(
700 		// NOLINTNEXTLINE(bugprone-sizeof-expression)
701 		sizeof(src) < INTMAX_MAX,
702 		"The following addition may not uphold the runtime assertion");
703 	if (ctx->remaining < INTMAX_MIN + (intmax_t)sizeof(src)) {
704 		assert(ctx->remaining < 0);
705 		return PLDM_ERROR_INVALID_LENGTH;
706 	}
707 	ctx->remaining -= sizeof(src);
708 	assert(ctx->remaining >= 0);
709 	if (ctx->remaining < 0) {
710 		return PLDM_ERROR_INVALID_LENGTH;
711 	}
712 
713 	memcpy(ctx->cursor, &src, sizeof(src));
714 	ctx->cursor += sizeof(src);
715 
716 	return PLDM_SUCCESS;
717 }
718 
719 __attribute__((always_inline)) static inline int
720 pldm_msgbuf_insert_int32(struct pldm_msgbuf *ctx, const int32_t src)
721 {
722 	int32_t val = htole32(src);
723 
724 	if (!ctx || !ctx->cursor) {
725 		return PLDM_ERROR_INVALID_DATA;
726 	}
727 
728 	static_assert(
729 		// NOLINTNEXTLINE(bugprone-sizeof-expression)
730 		sizeof(src) < INTMAX_MAX,
731 		"The following addition may not uphold the runtime assertion");
732 	if (ctx->remaining < INTMAX_MIN + (intmax_t)sizeof(src)) {
733 		assert(ctx->remaining < 0);
734 		return PLDM_ERROR_INVALID_LENGTH;
735 	}
736 	ctx->remaining -= sizeof(src);
737 	assert(ctx->remaining >= 0);
738 	if (ctx->remaining < 0) {
739 		return PLDM_ERROR_INVALID_LENGTH;
740 	}
741 
742 	memcpy(ctx->cursor, &val, sizeof(val));
743 	ctx->cursor += sizeof(src);
744 
745 	return PLDM_SUCCESS;
746 }
747 
748 __attribute__((always_inline)) static inline int
749 pldm_msgbuf_insert_int16(struct pldm_msgbuf *ctx, const int16_t src)
750 {
751 	int16_t val = htole16(src);
752 
753 	if (!ctx || !ctx->cursor) {
754 		return PLDM_ERROR_INVALID_DATA;
755 	}
756 
757 	static_assert(
758 		// NOLINTNEXTLINE(bugprone-sizeof-expression)
759 		sizeof(src) < INTMAX_MAX,
760 		"The following addition may not uphold the runtime assertion");
761 	if (ctx->remaining < INTMAX_MIN + (intmax_t)sizeof(src)) {
762 		assert(ctx->remaining < 0);
763 		return PLDM_ERROR_INVALID_LENGTH;
764 	}
765 	ctx->remaining -= sizeof(src);
766 	assert(ctx->remaining >= 0);
767 	if (ctx->remaining < 0) {
768 		return PLDM_ERROR_INVALID_LENGTH;
769 	}
770 
771 	memcpy(ctx->cursor, &val, sizeof(val));
772 	ctx->cursor += sizeof(src);
773 
774 	return PLDM_SUCCESS;
775 }
776 
777 __attribute__((always_inline)) static inline int
778 pldm_msgbuf_insert_int8(struct pldm_msgbuf *ctx, const int8_t src)
779 {
780 	if (!ctx || !ctx->cursor) {
781 		return PLDM_ERROR_INVALID_DATA;
782 	}
783 
784 	static_assert(
785 		// NOLINTNEXTLINE(bugprone-sizeof-expression)
786 		sizeof(src) < INTMAX_MAX,
787 		"The following addition may not uphold the runtime assertion");
788 	if (ctx->remaining < INTMAX_MIN + (intmax_t)sizeof(src)) {
789 		assert(ctx->remaining < 0);
790 		return PLDM_ERROR_INVALID_LENGTH;
791 	}
792 	ctx->remaining -= sizeof(src);
793 	assert(ctx->remaining >= 0);
794 	if (ctx->remaining < 0) {
795 		return PLDM_ERROR_INVALID_LENGTH;
796 	}
797 
798 	memcpy(ctx->cursor, &src, sizeof(src));
799 	ctx->cursor += sizeof(src);
800 
801 	return PLDM_SUCCESS;
802 }
803 
804 #define pldm_msgbuf_insert(dst, src)                                           \
805 	_Generic((src),                                                        \
806 		uint8_t: pldm_msgbuf_insert_uint8,                             \
807 		int8_t: pldm_msgbuf_insert_int8,                               \
808 		uint16_t: pldm_msgbuf_insert_uint16,                           \
809 		int16_t: pldm_msgbuf_insert_int16,                             \
810 		uint32_t: pldm_msgbuf_insert_uint32,                           \
811 		int32_t: pldm_msgbuf_insert_int32)(dst, src)
812 
813 __attribute__((always_inline)) static inline int
814 pldm_msgbuf_insert_array_uint8(struct pldm_msgbuf *ctx, const uint8_t *src,
815 			       size_t count)
816 {
817 	if (!ctx || !ctx->cursor || !src) {
818 		return PLDM_ERROR_INVALID_DATA;
819 	}
820 
821 	if (!count) {
822 		return PLDM_SUCCESS;
823 	}
824 
825 #if INTMAX_MAX < SIZE_MAX
826 	if (count > INTMAX_MAX) {
827 		return PLDM_ERROR_INVALID_LENGTH;
828 	}
829 #endif
830 
831 	if (ctx->remaining < INTMAX_MIN + (intmax_t)count) {
832 		return PLDM_ERROR_INVALID_LENGTH;
833 	}
834 	ctx->remaining -= (intmax_t)count;
835 	assert(ctx->remaining >= 0);
836 	if (ctx->remaining < 0) {
837 		return PLDM_ERROR_INVALID_LENGTH;
838 	}
839 
840 	memcpy(ctx->cursor, src, count);
841 	ctx->cursor += count;
842 
843 	return PLDM_SUCCESS;
844 }
845 
846 #define pldm_msgbuf_insert_array(dst, src, count)                              \
847 	_Generic((*(src)), uint8_t: pldm_msgbuf_insert_array_uint8)(dst, src,  \
848 								    count)
849 
850 __attribute__((always_inline)) static inline int
851 pldm_msgbuf_span_required(struct pldm_msgbuf *ctx, size_t required,
852 			  void **cursor)
853 {
854 	if (!ctx || !ctx->cursor || !cursor || *cursor) {
855 		return PLDM_ERROR_INVALID_DATA;
856 	}
857 
858 #if INTMAX_MAX < SIZE_MAX
859 	if (required > INTMAX_MAX) {
860 		return PLDM_ERROR_INVALID_LENGTH;
861 	}
862 #endif
863 
864 	if (ctx->remaining < INTMAX_MIN + (intmax_t)required) {
865 		return PLDM_ERROR_INVALID_LENGTH;
866 	}
867 	ctx->remaining -= (intmax_t)required;
868 	assert(ctx->remaining >= 0);
869 	if (ctx->remaining < 0) {
870 		return PLDM_ERROR_INVALID_LENGTH;
871 	}
872 
873 	*cursor = ctx->cursor;
874 	ctx->cursor += required;
875 
876 	return PLDM_SUCCESS;
877 }
878 
879 __attribute__((always_inline)) static inline int
880 pldm_msgbuf_span_remaining(struct pldm_msgbuf *ctx, void **cursor, size_t *len)
881 {
882 	if (!ctx || !ctx->cursor || !cursor || *cursor || !len) {
883 		return PLDM_ERROR_INVALID_DATA;
884 	}
885 
886 	assert(ctx->remaining >= 0);
887 	if (ctx->remaining < 0) {
888 		return PLDM_ERROR_INVALID_LENGTH;
889 	}
890 
891 	*cursor = ctx->cursor;
892 	ctx->cursor += ctx->remaining;
893 	*len = ctx->remaining;
894 	ctx->remaining = 0;
895 
896 	return PLDM_SUCCESS;
897 }
898 
899 /**
900  * @brief pldm_msgbuf copy data between two msg buffers
901  *
902  * @param[inout] src - pldm_msgbuf for source from where value should be copied
903  * @param[inout] dst - destination of copy from source
904  * @param[in] size - size of data to be copied
905  * @param[in] description - description of data copied
906  *
907  * @return PLDM_SUCCESS if buffer accesses were in-bounds,
908  * PLDM_ERROR_INVALID_LENGTH otherwise.
909  * PLDM_ERROR_INVALID_DATA if input is invalid
910  */
911 #define pldm_msgbuf_copy(dst, src, type, name)                                 \
912 	pldm__msgbuf_copy(dst, src, sizeof(type), #name)
913 __attribute__((always_inline)) static inline int
914 // NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
915 pldm__msgbuf_copy(struct pldm_msgbuf *dst, struct pldm_msgbuf *src, size_t size,
916 		  const char *description)
917 {
918 	if (!src || !src->cursor || !dst || !dst->cursor || !description) {
919 		return PLDM_ERROR_INVALID_DATA;
920 	}
921 
922 #if INTMAX_MAX < SIZE_MAX
923 	if (size > INTMAX_MAX) {
924 		return PLDM_ERROR_INVALID_LENGTH;
925 	}
926 #endif
927 
928 	if (src->remaining < INTMAX_MIN + (intmax_t)size) {
929 		return PLDM_ERROR_INVALID_LENGTH;
930 	}
931 
932 	if (dst->remaining < INTMAX_MIN + (intmax_t)size) {
933 		return PLDM_ERROR_INVALID_LENGTH;
934 	}
935 
936 	src->remaining -= (intmax_t)size;
937 	assert(src->remaining >= 0);
938 	if (src->remaining < 0) {
939 		return PLDM_ERROR_INVALID_LENGTH;
940 	}
941 
942 	dst->remaining -= (intmax_t)size;
943 	assert(dst->remaining >= 0);
944 	if (dst->remaining < 0) {
945 		return PLDM_ERROR_INVALID_LENGTH;
946 	}
947 
948 	memcpy(dst->cursor, src->cursor, size);
949 	src->cursor += size;
950 	dst->cursor += size;
951 
952 	return PLDM_SUCCESS;
953 }
954 #ifdef __cplusplus
955 }
956 #endif
957 
958 #ifdef __cplusplus
959 #include <type_traits>
960 
961 template <typename T>
962 static inline int pldm_msgbuf_typecheck_uint8_t(struct pldm_msgbuf *ctx,
963 						void *buf)
964 {
965 	static_assert(std::is_same<uint8_t *, T>::value);
966 	return pldm__msgbuf_extract_uint8(ctx, buf);
967 }
968 
969 template <typename T>
970 static inline int pldm_msgbuf_typecheck_int8_t(struct pldm_msgbuf *ctx,
971 					       void *buf)
972 {
973 	static_assert(std::is_same<int8_t *, T>::value);
974 	return pldm__msgbuf_extract_int8(ctx, buf);
975 }
976 
977 template <typename T>
978 static inline int pldm_msgbuf_typecheck_uint16_t(struct pldm_msgbuf *ctx,
979 						 void *buf)
980 {
981 	static_assert(std::is_same<uint16_t *, T>::value);
982 	return pldm__msgbuf_extract_uint16(ctx, buf);
983 }
984 
985 template <typename T>
986 static inline int pldm_msgbuf_typecheck_int16_t(struct pldm_msgbuf *ctx,
987 						void *buf)
988 {
989 	static_assert(std::is_same<int16_t *, T>::value);
990 	return pldm__msgbuf_extract_int16(ctx, buf);
991 }
992 
993 template <typename T>
994 static inline int pldm_msgbuf_typecheck_uint32_t(struct pldm_msgbuf *ctx,
995 						 void *buf)
996 {
997 	static_assert(std::is_same<uint32_t *, T>::value);
998 	return pldm__msgbuf_extract_uint32(ctx, buf);
999 }
1000 
1001 template <typename T>
1002 static inline int pldm_msgbuf_typecheck_int32_t(struct pldm_msgbuf *ctx,
1003 						void *buf)
1004 {
1005 	static_assert(std::is_same<int32_t *, T>::value);
1006 	return pldm__msgbuf_extract_int32(ctx, buf);
1007 }
1008 
1009 template <typename T>
1010 static inline int pldm_msgbuf_typecheck_real32_t(struct pldm_msgbuf *ctx,
1011 						 void *buf)
1012 {
1013 	static_assert(std::is_same<real32_t *, T>::value);
1014 	return pldm__msgbuf_extract_real32(ctx, buf);
1015 }
1016 #endif
1017 
1018 #endif /* BUF_H */
1019