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