/* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ #ifndef PLDM_MSGBUF_H #define PLDM_MSGBUF_H /* * Historically, many of the structs exposed in libpldm's public headers are * defined with __attribute__((packed)). This is unfortunate: it gives the * impression that a wire-format buffer can be cast to the message type to make * the message's fields easily accessible. As it turns out, that's not * that's valid for several reasons: * * 1. Casting the wire-format buffer to a struct of the message type doesn't * abstract the endianness of message field values * * 2. Some messages contain packed tagged union fields which cannot be properly * described in a C struct. * * The msgbuf APIs exist to assist with (un)packing the wire-format in a way * that is type-safe, spatially memory-safe, endian-safe, performant, and * free of undefined-behaviour. Message structs that are added to the public * library API should no-longer be marked __attribute__((packed)), and the * implementation of their encode and decode functions must exploit the msgbuf * API. * * However, we would like to allow implementation of codec functions in terms of * msgbuf APIs even if they're decoding a message into a (historically) packed * struct. Some of the complexity that follows is a consequence of the packed/ * unpacked conflict. */ #ifdef __cplusplus /* * Fix up C11's _Static_assert() vs C++'s static_assert(). * * Can we please have nice things for once. */ // NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp) #define _Static_assert(...) static_assert(__VA_ARGS__) extern "C" { #endif #include #include #include "compiler.h" #include #include #include #include #include #include #include #include /* * We can't use static_assert() outside of some other C construct. Deal * with high-level global assertions by burying them in an unused struct * declaration, that has a sole member for compliance with the requirement that * types must have a size. */ static struct { static_assert( INTMAX_MAX != SIZE_MAX, "Extraction and insertion value comparisons may be broken"); static_assert(INTMAX_MIN + INTMAX_MAX <= 0, "Extraction and insertion arithmetic may be broken"); static_assert(PLDM_SUCCESS == 0, "Error handling is broken"); int compliance; } build_assertions __attribute__((unused)); enum pldm_msgbuf_error_mode { PLDM_MSGBUF_PLDM_CC = 0x5a, PLDM_MSGBUF_C_ERRNO = 0xa5, }; struct pldm_msgbuf { uint8_t *cursor; intmax_t remaining; enum pldm_msgbuf_error_mode mode; }; /** * @brief Either negate an errno value or return a value mapped to a PLDM * completion code. * * Note that `pldm_msgbuf_status()` is purely internal to the msgbuf API * for ergonomics. It's preferred that we don't try to unify this with * `pldm_xlate_errno()` from src/api.h despite the similarities. * * @param[in] ctx - The msgbuf context providing the personality info * @param[in] err - The positive errno value to translate * * @return Either the negated value of @p err if the context's error mode is * `PLDM_MSGBUF_C_ERRNO`, or the equivalent PLDM completion code if the * error mode is `PLDM_MSGBUF_PLDM_CC`. */ __attribute__((always_inline)) static inline int pldm_msgbuf_status(struct pldm_msgbuf *ctx, unsigned int err) { int rc; assert(err != 0); assert(err <= INT_MAX); if (ctx->mode == PLDM_MSGBUF_C_ERRNO) { if (err > INT_MAX) { return -EINVAL; } static_assert(INT_MIN + INT_MAX < 0, "Arithmetic assumption failure"); return -((int)err); } if (err > INT_MAX) { return PLDM_ERROR; } assert(ctx->mode == PLDM_MSGBUF_PLDM_CC); switch (err) { case EINVAL: rc = PLDM_ERROR_INVALID_DATA; break; case EBADMSG: case EOVERFLOW: rc = PLDM_ERROR_INVALID_LENGTH; break; default: assert(false); rc = PLDM_ERROR; break; } assert(rc > 0); return rc; } /** * @brief Initialize pldm buf struct for buf extractor * * @param[out] ctx - pldm_msgbuf context for extractor * @param[in] minsize - The minimum required length of buffer `buf` * @param[in] buf - buffer to be extracted * @param[in] len - size of buffer * * @return 0 on success, otherwise an error code appropriate for the current * personality. */ __attribute__((always_inline)) static inline int // 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) { assert(ctx); assert(ctx->mode == PLDM_MSGBUF_PLDM_CC || ctx->mode == PLDM_MSGBUF_C_ERRNO); if (!buf) { return pldm_msgbuf_status(ctx, EINVAL); } if ((minsize > len)) { return pldm_msgbuf_status(ctx, EOVERFLOW); } #if INTMAX_MAX < SIZE_MAX if (len > INTMAX_MAX) { return pldm_msgbuf_status(ctx, EOVERFLOW); } #endif if ((uintptr_t)buf + len < len) { return pldm_msgbuf_status(ctx, EOVERFLOW); } ctx->cursor = (uint8_t *)buf; ctx->remaining = (intmax_t)len; return 0; } /** * @brief Initialise a msgbuf instance to return errors as PLDM completion codes * * @see pldm__msgbuf_init * * @param[out] ctx - pldm_msgbuf context for extractor * @param[in] minsize - The minimum required length of buffer `buf` * @param[in] buf - buffer to be extracted * @param[in] len - size of buffer * * @return PLDM_SUCCESS if the provided buffer region is sensible, * otherwise PLDM_ERROR_INVALID_DATA if pointer parameters are invalid, * or PLDM_ERROR_INVALID_LENGTH if length constraints are violated. */ __attribute__((always_inline)) static inline int pldm_msgbuf_init_cc(struct pldm_msgbuf *ctx, size_t minsize, const void *buf, size_t len) { if (!ctx) { return PLDM_ERROR_INVALID_DATA; } ctx->mode = PLDM_MSGBUF_PLDM_CC; return pldm__msgbuf_init(ctx, minsize, buf, len); } /** * @brief Initialise a msgbuf instance to return errors as negative errno values * * @see pldm__msgbuf_init * * @param[out] ctx - pldm_msgbuf context for extractor * @param[in] minsize - The minimum required length of buffer `buf` * @param[in] buf - buffer to be extracted * @param[in] len - size of buffer * * @return 0 if the provided buffer region is sensible, otherwise -EINVAL if * pointer parameters are invalid, or -EOVERFLOW if length constraints * are violated. */ __attribute__((always_inline)) static inline int pldm_msgbuf_init_errno(struct pldm_msgbuf *ctx, size_t minsize, const void *buf, size_t len) { if (!ctx) { return -EINVAL; } ctx->mode = PLDM_MSGBUF_C_ERRNO; return pldm__msgbuf_init(ctx, minsize, buf, len); } /** * @brief Validate buffer overflow state * * @param[in] ctx - pldm_msgbuf context for extractor * * @return PLDM_SUCCESS if there are zero or more bytes of data that remain * unread from the buffer. Otherwise, PLDM_ERROR_INVALID_LENGTH indicates that a * prior accesses would have occurred beyond the bounds of the buffer, and * PLDM_ERROR_INVALID_DATA indicates that the provided context was not a valid * pointer. */ __attribute__((always_inline)) static inline int pldm_msgbuf_validate(struct pldm_msgbuf *ctx) { assert(ctx); if (ctx->remaining < 0) { return pldm_msgbuf_status(ctx, EOVERFLOW); } return 0; } /** * @brief Test whether a message buffer has been exactly consumed * * @param[in] ctx - pldm_msgbuf context for extractor * * @return PLDM_SUCCESS iff there are zero bytes of data that remain unread from * the buffer and no overflow has occurred. Otherwise, PLDM_ERROR_INVALID_LENGTH * indicates that an incorrect sequence of accesses have occurred, and * PLDM_ERROR_INVALID_DATA indicates that the provided context was not a valid * pointer. */ __attribute__((always_inline)) static inline int pldm_msgbuf_consumed(struct pldm_msgbuf *ctx) { assert(ctx); if (ctx->remaining != 0) { return pldm_msgbuf_status(ctx, EBADMSG); } return 0; } /** * @brief Destroy the pldm buf * * @param[in] ctx - pldm_msgbuf context for extractor * * @return PLDM_SUCCESS if all buffer accesses were in-bounds, * PLDM_ERROR_INVALID_DATA if the ctx parameter is invalid, or * PLDM_ERROR_INVALID_LENGTH if prior accesses would have occurred beyond the * bounds of the buffer. */ __attribute__((always_inline)) static inline int pldm_msgbuf_destroy(struct pldm_msgbuf *ctx) { int valid; assert(ctx); valid = pldm_msgbuf_validate(ctx); ctx->cursor = NULL; ctx->remaining = 0; return valid; } /** * @brief Destroy the pldm_msgbuf instance, and check that the underlying buffer * has been completely consumed without overflow * * @param[in] ctx - pldm_msgbuf context * * @return PLDM_SUCCESS if all buffer access were in-bounds and completely * consume the underlying buffer. Otherwise, PLDM_ERROR_INVALID_DATA if the ctx * parameter is invalid, or PLDM_ERROR_INVALID_LENGTH if prior accesses would * have occurred byond the bounds of the buffer */ __attribute__((always_inline)) static inline int pldm_msgbuf_destroy_consumed(struct pldm_msgbuf *ctx) { int consumed; assert(ctx); consumed = pldm_msgbuf_consumed(ctx); ctx->cursor = NULL; ctx->remaining = 0; return consumed; } /* * Exploit the pre-processor to perform type checking by macro substitution. * * A C type is defined by its alignment as well as its object * size, and compilers have a hammer to enforce it in the form of * `-Waddress-of-packed-member`. Due to the unpacked/packed struct conflict in * the libpldm public API this presents a problem: Naively attempting to use the * msgbuf APIs on a member of a packed struct would yield an error. * * The msgbuf APIs are implemented such that data is moved through unaligned * pointers in a safe way, but to mitigate `-Waddress-of-packed-member` we must * make the object pointers take a trip through `void *` at its API boundary. * That presents a bit too much of an opportunity to non-surgically remove your * own foot, so here we set about doing something to mitigate that as well. * * pldm_msgbuf_extract_typecheck() exists to enforce pointer type correctness * only for the purpose of object sizes, disregarding alignment. We have a few * constraints that cause some headaches: * * 1. We have to perform the type-check before a call through a C function, * as the function must take the object pointer argument as `void *`. * Essentially, this constrains us to doing something with macros. * * 2. While libpldm is a C library, its test suite is written in C++ to take * advantage of gtest. * * 3. Ideally we'd do something with C's `static_assert()`, however * `static_assert()` is defined as void, and as we're constrained to macros, * using `static_assert()` would require a statement-expression * * 4. Currently the project is built with `-std=c17`. CPP statement-expressions * are a GNU extension. We prefer to avoid switching to `-std=gnu17` just for * the purpose of enabling statement-expressions in this one instance. * * 5. We can achieve a conditional build error using `pldm_require_obj_type()`, * however it's implemented in terms of `_Generic()`, which is not available * in C++. * * Combined this means we need separate solutions for C and C++. * * For C, as we don't have statement-expressions, we need to exploit some other * language feature to inject a `pldm_require_obj_type()` prior to the msgbuf * API function call. We also have to take care of the fact that the call-sites * may be in the context of a variable assignment for error-handling purposes. * The key observation is that we can use the comma operator as a sequence point * to order the type check before the API call, discarding the "result" value of * the type check and yielding the return value of the API call. * * C++ could be less of a headache than the C as we can leverage template * functions. An advantage of template functions is that while their definition * is driven by instantion, the definition does not appear at the source * location of the instantiation, which gives it a great leg-up over the problems * we have in the C path. However, the use of the msgbuf APIs in the test suite * still makes things somewhat tricky, as the call-sites in the test suite are * wrapped up in EXPECT_*() gtest macros. Ideally we'd implement functions that * takes both the object type and the required type as template arguments, and * then define the object pointer parameter as `void *` for a call through to * the appropriate msgbuf API. However, because the msgbuf API call-sites are * encapsulated in gtest macros, use of commas in the template specification * causes pre-processor confusion. In this way we're constrained to only one * template argument per function. * * Implement the C++ path using template functions that take the destination * object type as a template argument, while the name of the function symbols * are derived from the required type. The manual implementations of these * appear at the end of the header. The type safety is actually enforced * by `static_assert()` this time, as we can use statements as we're not * constrained to an expression in the templated function body. * * The invocations of pldm_msgbuf_extract_typecheck() typically result in * double-evaluation of some arguments. We're not yet bothered by this for two * reasons: * * 1. The nature of the current call-sites are such that there are no * argument expressions that result in undesirable side-effects * * 2. It's an API internal to the libpldm implementation, and we can fix things * whenever something crops up the violates the observation in 1. */ #ifdef __cplusplus #define pldm_msgbuf_extract_typecheck(ty, fn, dst, ...) \ pldm_msgbuf_typecheck_##ty(__VA_ARGS__) #else #define pldm_msgbuf_extract_typecheck(ty, fn, dst, ...) \ (pldm_require_obj_type(dst, ty), fn(__VA_ARGS__)) #endif /** * @brief pldm_msgbuf extractor for a uint8_t * * @param[in,out] ctx - pldm_msgbuf context for extractor * @param[out] dst - destination of extracted value * * @return PLDM_SUCCESS if buffer accesses were in-bounds, * PLDM_ERROR_INVALID_LENGTH otherwise. * PLDM_ERROR_INVALID_DATA if input a invalid ctx */ #define pldm_msgbuf_extract_uint8(ctx, dst) \ pldm_msgbuf_extract_typecheck(uint8_t, pldm__msgbuf_extract_uint8, \ dst, ctx, dst) __attribute__((always_inline)) static inline int // NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp) pldm__msgbuf_extract_uint8(struct pldm_msgbuf *ctx, void *dst) { assert(ctx); if (!ctx->cursor || !dst) { return pldm_msgbuf_status(ctx, EINVAL); } if (ctx->remaining == INTMAX_MIN) { assert(ctx->remaining < 0); return pldm_msgbuf_status(ctx, EOVERFLOW); } ctx->remaining -= sizeof(uint8_t); assert(ctx->remaining >= 0); if (ctx->remaining < 0) { return pldm_msgbuf_status(ctx, EOVERFLOW); } memcpy(dst, ctx->cursor, sizeof(uint8_t)); ctx->cursor++; return 0; } #define pldm_msgbuf_extract_int8(ctx, dst) \ pldm_msgbuf_extract_typecheck(int8_t, pldm__msgbuf_extract_int8, dst, \ ctx, dst) __attribute__((always_inline)) static inline int // NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp) pldm__msgbuf_extract_int8(struct pldm_msgbuf *ctx, void *dst) { assert(ctx); if (!ctx->cursor || !dst) { return pldm_msgbuf_status(ctx, EINVAL); } if (ctx->remaining == INTMAX_MIN) { assert(ctx->remaining < 0); return pldm_msgbuf_status(ctx, EOVERFLOW); } ctx->remaining -= sizeof(int8_t); assert(ctx->remaining >= 0); if (ctx->remaining < 0) { return pldm_msgbuf_status(ctx, EOVERFLOW); } memcpy(dst, ctx->cursor, sizeof(int8_t)); ctx->cursor++; return 0; } #define pldm_msgbuf_extract_uint16(ctx, dst) \ pldm_msgbuf_extract_typecheck(uint16_t, pldm__msgbuf_extract_uint16, \ dst, ctx, dst) __attribute__((always_inline)) static inline int // NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp) pldm__msgbuf_extract_uint16(struct pldm_msgbuf *ctx, void *dst) { uint16_t ldst; assert(ctx); if (!ctx->cursor || !dst) { return pldm_msgbuf_status(ctx, EINVAL); } // Check for underflow while tracking the magnitude of the buffer overflow static_assert( // NOLINTNEXTLINE(bugprone-sizeof-expression) sizeof(ldst) < INTMAX_MAX, "The following addition may not uphold the runtime assertion"); if (ctx->remaining < INTMAX_MIN + (intmax_t)sizeof(ldst)) { assert(ctx->remaining < 0); return pldm_msgbuf_status(ctx, EOVERFLOW); } // Check for buffer overflow. If we overflow, account for the request as // negative values in ctx->remaining. This way we can debug how far // we've overflowed. ctx->remaining -= sizeof(ldst); // Prevent the access if it would overflow. First, assert so we blow up // the test suite right at the point of failure. However, cater to // -DNDEBUG by explicitly testing that the access is valid. assert(ctx->remaining >= 0); if (ctx->remaining < 0) { return pldm_msgbuf_status(ctx, EOVERFLOW); } // Use memcpy() to have the compiler deal with any alignment // issues on the target architecture memcpy(&ldst, ctx->cursor, sizeof(ldst)); // Only assign the target value once it's correctly decoded ldst = le16toh(ldst); // Allow storing to unaligned memcpy(dst, &ldst, sizeof(ldst)); ctx->cursor += sizeof(ldst); return 0; } #define pldm_msgbuf_extract_int16(ctx, dst) \ pldm_msgbuf_extract_typecheck(int16_t, pldm__msgbuf_extract_int16, \ dst, ctx, dst) __attribute__((always_inline)) static inline int // NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp) pldm__msgbuf_extract_int16(struct pldm_msgbuf *ctx, void *dst) { int16_t ldst; assert(ctx); if (!ctx->cursor || !dst) { return pldm_msgbuf_status(ctx, EINVAL); } static_assert( // NOLINTNEXTLINE(bugprone-sizeof-expression) sizeof(ldst) < INTMAX_MAX, "The following addition may not uphold the runtime assertion"); if (ctx->remaining < INTMAX_MIN + (intmax_t)sizeof(ldst)) { assert(ctx->remaining < 0); return pldm_msgbuf_status(ctx, EOVERFLOW); } ctx->remaining -= sizeof(ldst); assert(ctx->remaining >= 0); if (ctx->remaining < 0) { return pldm_msgbuf_status(ctx, EOVERFLOW); } memcpy(&ldst, ctx->cursor, sizeof(ldst)); ldst = le16toh(ldst); memcpy(dst, &ldst, sizeof(ldst)); ctx->cursor += sizeof(ldst); return 0; } #define pldm_msgbuf_extract_uint32(ctx, dst) \ pldm_msgbuf_extract_typecheck(uint32_t, pldm__msgbuf_extract_uint32, \ dst, ctx, dst) __attribute__((always_inline)) static inline int // NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp) pldm__msgbuf_extract_uint32(struct pldm_msgbuf *ctx, void *dst) { uint32_t ldst; assert(ctx); if (!ctx->cursor || !dst) { return pldm_msgbuf_status(ctx, EINVAL); } static_assert( // NOLINTNEXTLINE(bugprone-sizeof-expression) sizeof(ldst) < INTMAX_MAX, "The following addition may not uphold the runtime assertion"); if (ctx->remaining < INTMAX_MIN + (intmax_t)sizeof(ldst)) { assert(ctx->remaining < 0); return pldm_msgbuf_status(ctx, EOVERFLOW); } ctx->remaining -= sizeof(ldst); assert(ctx->remaining >= 0); if (ctx->remaining < 0) { return pldm_msgbuf_status(ctx, EOVERFLOW); } memcpy(&ldst, ctx->cursor, sizeof(ldst)); ldst = le32toh(ldst); memcpy(dst, &ldst, sizeof(ldst)); ctx->cursor += sizeof(ldst); return 0; } #define pldm_msgbuf_extract_int32(ctx, dst) \ pldm_msgbuf_extract_typecheck(int32_t, pldm__msgbuf_extract_int32, \ dst, ctx, dst) __attribute__((always_inline)) static inline int // NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp) pldm__msgbuf_extract_int32(struct pldm_msgbuf *ctx, void *dst) { int32_t ldst; assert(ctx); if (!ctx->cursor || !dst) { return pldm_msgbuf_status(ctx, EINVAL); } static_assert( // NOLINTNEXTLINE(bugprone-sizeof-expression) sizeof(ldst) < INTMAX_MAX, "The following addition may not uphold the runtime assertion"); if (ctx->remaining < INTMAX_MIN + (intmax_t)sizeof(ldst)) { assert(ctx->remaining < 0); return pldm_msgbuf_status(ctx, EOVERFLOW); } ctx->remaining -= sizeof(ldst); assert(ctx->remaining >= 0); if (ctx->remaining < 0) { return pldm_msgbuf_status(ctx, EOVERFLOW); } memcpy(&ldst, ctx->cursor, sizeof(ldst)); ldst = le32toh(ldst); memcpy(dst, &ldst, sizeof(ldst)); ctx->cursor += sizeof(ldst); return PLDM_SUCCESS; } #define pldm_msgbuf_extract_real32(ctx, dst) \ pldm_msgbuf_extract_typecheck(real32_t, pldm__msgbuf_extract_real32, \ dst, ctx, dst) __attribute__((always_inline)) static inline int // NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp) pldm__msgbuf_extract_real32(struct pldm_msgbuf *ctx, void *dst) { uint32_t ldst; static_assert(sizeof(real32_t) == sizeof(ldst), "Mismatched type sizes for dst and ldst"); assert(ctx); if (!ctx->cursor || !dst) { return pldm_msgbuf_status(ctx, EINVAL); } static_assert( // NOLINTNEXTLINE(bugprone-sizeof-expression) sizeof(ldst) < INTMAX_MAX, "The following addition may not uphold the runtime assertion"); if (ctx->remaining < INTMAX_MIN + (intmax_t)sizeof(ldst)) { assert(ctx->remaining < 0); return pldm_msgbuf_status(ctx, EOVERFLOW); } ctx->remaining -= sizeof(ldst); assert(ctx->remaining >= 0); if (ctx->remaining < 0) { return pldm_msgbuf_status(ctx, EOVERFLOW); } memcpy(&ldst, ctx->cursor, sizeof(ldst)); ldst = le32toh(ldst); memcpy(dst, &ldst, sizeof(ldst)); ctx->cursor += sizeof(ldst); return 0; } /** * Extract the field at the msgbuf cursor into the lvalue named by dst. * * @param ctx The msgbuf context object * @param dst The lvalue into which the field at the msgbuf cursor should be * extracted * * @return PLDM_SUCCESS on success, otherwise another value on error */ #define pldm_msgbuf_extract(ctx, dst) \ _Generic((dst), \ uint8_t: pldm__msgbuf_extract_uint8, \ int8_t: pldm__msgbuf_extract_int8, \ uint16_t: pldm__msgbuf_extract_uint16, \ int16_t: pldm__msgbuf_extract_int16, \ uint32_t: pldm__msgbuf_extract_uint32, \ int32_t: pldm__msgbuf_extract_int32, \ real32_t: pldm__msgbuf_extract_real32)(ctx, (void *)&(dst)) /** * Extract the field at the msgbuf cursor into the object pointed-to by dst. * * @param ctx The msgbuf context object * @param dst The pointer to the object into which the field at the msgbuf * cursor should be extracted * * @return PLDM_SUCCESS on success, otherwise another value on error */ #define pldm_msgbuf_extract_p(ctx, dst) \ _Generic((dst), \ uint8_t *: pldm__msgbuf_extract_uint8, \ int8_t *: pldm__msgbuf_extract_int8, \ uint16_t *: pldm__msgbuf_extract_uint16, \ int16_t *: pldm__msgbuf_extract_int16, \ uint32_t *: pldm__msgbuf_extract_uint32, \ int32_t *: pldm__msgbuf_extract_int32, \ real32_t *: pldm__msgbuf_extract_real32)(ctx, dst) __attribute__((always_inline)) static inline int pldm_msgbuf_extract_array_uint8(struct pldm_msgbuf *ctx, uint8_t *dst, size_t count) { assert(ctx); if (!ctx->cursor || !dst) { return pldm_msgbuf_status(ctx, EINVAL); } if (!count) { return 0; } #if INTMAX_MAX < SIZE_MAX if (count > INTMAX_MAX) { return pldm_msgbuf_status(ctx, EOVERFLOW); } #endif if (ctx->remaining < INTMAX_MIN + (intmax_t)count) { return pldm_msgbuf_status(ctx, EOVERFLOW); } ctx->remaining -= (intmax_t)count; assert(ctx->remaining >= 0); if (ctx->remaining < 0) { return pldm_msgbuf_status(ctx, EOVERFLOW); } memcpy(dst, ctx->cursor, count); ctx->cursor += count; return 0; } #define pldm_msgbuf_extract_array(ctx, dst, count) \ _Generic((*(dst)), uint8_t: pldm_msgbuf_extract_array_uint8)(ctx, dst, \ count) __attribute__((always_inline)) static inline int pldm_msgbuf_insert_uint32(struct pldm_msgbuf *ctx, const uint32_t src) { uint32_t val = htole32(src); assert(ctx); if (!ctx->cursor) { return pldm_msgbuf_status(ctx, EINVAL); } static_assert( // NOLINTNEXTLINE(bugprone-sizeof-expression) sizeof(src) < INTMAX_MAX, "The following addition may not uphold the runtime assertion"); if (ctx->remaining < INTMAX_MIN + (intmax_t)sizeof(src)) { assert(ctx->remaining < 0); return pldm_msgbuf_status(ctx, EOVERFLOW); } ctx->remaining -= sizeof(src); assert(ctx->remaining >= 0); if (ctx->remaining < 0) { return pldm_msgbuf_status(ctx, EOVERFLOW); } memcpy(ctx->cursor, &val, sizeof(val)); ctx->cursor += sizeof(src); return 0; } __attribute__((always_inline)) static inline int pldm_msgbuf_insert_uint16(struct pldm_msgbuf *ctx, const uint16_t src) { uint16_t val = htole16(src); assert(ctx); if (!ctx->cursor) { return pldm_msgbuf_status(ctx, EINVAL); } static_assert( // NOLINTNEXTLINE(bugprone-sizeof-expression) sizeof(src) < INTMAX_MAX, "The following addition may not uphold the runtime assertion"); if (ctx->remaining < INTMAX_MIN + (intmax_t)sizeof(src)) { assert(ctx->remaining < 0); return pldm_msgbuf_status(ctx, EOVERFLOW); } ctx->remaining -= sizeof(src); assert(ctx->remaining >= 0); if (ctx->remaining < 0) { return pldm_msgbuf_status(ctx, EOVERFLOW); } memcpy(ctx->cursor, &val, sizeof(val)); ctx->cursor += sizeof(src); return 0; } __attribute__((always_inline)) static inline int pldm_msgbuf_insert_uint8(struct pldm_msgbuf *ctx, const uint8_t src) { assert(ctx); if (!ctx->cursor) { return pldm_msgbuf_status(ctx, EINVAL); } static_assert( // NOLINTNEXTLINE(bugprone-sizeof-expression) sizeof(src) < INTMAX_MAX, "The following addition may not uphold the runtime assertion"); if (ctx->remaining < INTMAX_MIN + (intmax_t)sizeof(src)) { assert(ctx->remaining < 0); return pldm_msgbuf_status(ctx, EOVERFLOW); } ctx->remaining -= sizeof(src); assert(ctx->remaining >= 0); if (ctx->remaining < 0) { return pldm_msgbuf_status(ctx, EOVERFLOW); } memcpy(ctx->cursor, &src, sizeof(src)); ctx->cursor += sizeof(src); return 0; } __attribute__((always_inline)) static inline int pldm_msgbuf_insert_int32(struct pldm_msgbuf *ctx, const int32_t src) { int32_t val = htole32(src); assert(ctx); if (!ctx->cursor) { return pldm_msgbuf_status(ctx, EINVAL); } static_assert( // NOLINTNEXTLINE(bugprone-sizeof-expression) sizeof(src) < INTMAX_MAX, "The following addition may not uphold the runtime assertion"); if (ctx->remaining < INTMAX_MIN + (intmax_t)sizeof(src)) { assert(ctx->remaining < 0); return pldm_msgbuf_status(ctx, EOVERFLOW); } ctx->remaining -= sizeof(src); assert(ctx->remaining >= 0); if (ctx->remaining < 0) { return pldm_msgbuf_status(ctx, EOVERFLOW); } memcpy(ctx->cursor, &val, sizeof(val)); ctx->cursor += sizeof(src); return 0; } __attribute__((always_inline)) static inline int pldm_msgbuf_insert_int16(struct pldm_msgbuf *ctx, const int16_t src) { int16_t val = htole16(src); assert(ctx); if (!ctx->cursor) { return pldm_msgbuf_status(ctx, EINVAL); } static_assert( // NOLINTNEXTLINE(bugprone-sizeof-expression) sizeof(src) < INTMAX_MAX, "The following addition may not uphold the runtime assertion"); if (ctx->remaining < INTMAX_MIN + (intmax_t)sizeof(src)) { assert(ctx->remaining < 0); return pldm_msgbuf_status(ctx, EOVERFLOW); } ctx->remaining -= sizeof(src); assert(ctx->remaining >= 0); if (ctx->remaining < 0) { return pldm_msgbuf_status(ctx, EOVERFLOW); } memcpy(ctx->cursor, &val, sizeof(val)); ctx->cursor += sizeof(src); return 0; } __attribute__((always_inline)) static inline int pldm_msgbuf_insert_int8(struct pldm_msgbuf *ctx, const int8_t src) { assert(ctx); if (!ctx->cursor) { return pldm_msgbuf_status(ctx, EINVAL); } static_assert( // NOLINTNEXTLINE(bugprone-sizeof-expression) sizeof(src) < INTMAX_MAX, "The following addition may not uphold the runtime assertion"); if (ctx->remaining < INTMAX_MIN + (intmax_t)sizeof(src)) { assert(ctx->remaining < 0); return pldm_msgbuf_status(ctx, EOVERFLOW); } ctx->remaining -= sizeof(src); assert(ctx->remaining >= 0); if (ctx->remaining < 0) { return pldm_msgbuf_status(ctx, EOVERFLOW); } memcpy(ctx->cursor, &src, sizeof(src)); ctx->cursor += sizeof(src); return 0; } #define pldm_msgbuf_insert(dst, src) \ _Generic((src), \ uint8_t: pldm_msgbuf_insert_uint8, \ int8_t: pldm_msgbuf_insert_int8, \ uint16_t: pldm_msgbuf_insert_uint16, \ int16_t: pldm_msgbuf_insert_int16, \ uint32_t: pldm_msgbuf_insert_uint32, \ int32_t: pldm_msgbuf_insert_int32)(dst, src) __attribute__((always_inline)) static inline int pldm_msgbuf_insert_array_uint8(struct pldm_msgbuf *ctx, const uint8_t *src, size_t count) { assert(ctx); if (!ctx->cursor || !src) { return pldm_msgbuf_status(ctx, EINVAL); } if (!count) { return 0; } #if INTMAX_MAX < SIZE_MAX if (count > INTMAX_MAX) { return pldm_msgbuf_status(ctx, EOVERFLOW); } #endif if (ctx->remaining < INTMAX_MIN + (intmax_t)count) { return pldm_msgbuf_status(ctx, EOVERFLOW); } ctx->remaining -= (intmax_t)count; assert(ctx->remaining >= 0); if (ctx->remaining < 0) { return pldm_msgbuf_status(ctx, EOVERFLOW); } memcpy(ctx->cursor, src, count); ctx->cursor += count; return 0; } #define pldm_msgbuf_insert_array(dst, src, count) \ _Generic((*(src)), uint8_t: pldm_msgbuf_insert_array_uint8)(dst, src, \ count) __attribute__((always_inline)) static inline int pldm_msgbuf_span_required(struct pldm_msgbuf *ctx, size_t required, void **cursor) { assert(ctx); if (!ctx->cursor || !cursor || *cursor) { return pldm_msgbuf_status(ctx, EINVAL); } #if INTMAX_MAX < SIZE_MAX if (required > INTMAX_MAX) { return pldm_msgbuf_status(ctx, EOVERFLOW); } #endif if (ctx->remaining < INTMAX_MIN + (intmax_t)required) { return pldm_msgbuf_status(ctx, EOVERFLOW); } ctx->remaining -= (intmax_t)required; assert(ctx->remaining >= 0); if (ctx->remaining < 0) { return pldm_msgbuf_status(ctx, EOVERFLOW); } *cursor = ctx->cursor; ctx->cursor += required; return 0; } __attribute__((always_inline)) static inline int pldm_msgbuf_span_remaining(struct pldm_msgbuf *ctx, void **cursor, size_t *len) { assert(ctx); if (!ctx->cursor || !cursor || *cursor || !len) { return pldm_msgbuf_status(ctx, EINVAL); } assert(ctx->remaining >= 0); if (ctx->remaining < 0) { return pldm_msgbuf_status(ctx, EOVERFLOW); } *cursor = ctx->cursor; ctx->cursor += ctx->remaining; *len = ctx->remaining; ctx->remaining = 0; return 0; } /** * @brief pldm_msgbuf copy data between two msg buffers * * @param[in,out] src - pldm_msgbuf for source from where value should be copied * @param[in,out] dst - destination of copy from source * @param[in] size - size of data to be copied * @param[in] description - description of data copied * * @return PLDM_SUCCESS if buffer accesses were in-bounds, * PLDM_ERROR_INVALID_LENGTH otherwise. * PLDM_ERROR_INVALID_DATA if input is invalid */ #define pldm_msgbuf_copy(dst, src, type, name) \ pldm__msgbuf_copy(dst, src, sizeof(type), #name) __attribute__((always_inline)) static inline int // 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) { assert(src); assert(dst); assert(src->mode == dst->mode); if (!src->cursor || !dst->cursor || !description) { return pldm_msgbuf_status(dst, EINVAL); } #if INTMAX_MAX < SIZE_MAX if (size > INTMAX_MAX) { return pldm_msgbuf_status(dst, EOVERFLOW); } #endif if (src->remaining < INTMAX_MIN + (intmax_t)size) { return pldm_msgbuf_status(dst, EOVERFLOW); } if (dst->remaining < INTMAX_MIN + (intmax_t)size) { return pldm_msgbuf_status(dst, EOVERFLOW); } src->remaining -= (intmax_t)size; assert(src->remaining >= 0); if (src->remaining < 0) { return pldm_msgbuf_status(dst, EOVERFLOW); } dst->remaining -= (intmax_t)size; assert(dst->remaining >= 0); if (dst->remaining < 0) { return pldm_msgbuf_status(dst, EOVERFLOW); } memcpy(dst->cursor, src->cursor, size); src->cursor += size; dst->cursor += size; return 0; } #ifdef __cplusplus } #endif #ifdef __cplusplus #include template static inline int pldm_msgbuf_typecheck_uint8_t(struct pldm_msgbuf *ctx, void *buf) { static_assert(std::is_same::value); return pldm__msgbuf_extract_uint8(ctx, buf); } template static inline int pldm_msgbuf_typecheck_int8_t(struct pldm_msgbuf *ctx, void *buf) { static_assert(std::is_same::value); return pldm__msgbuf_extract_int8(ctx, buf); } template static inline int pldm_msgbuf_typecheck_uint16_t(struct pldm_msgbuf *ctx, void *buf) { static_assert(std::is_same::value); return pldm__msgbuf_extract_uint16(ctx, buf); } template static inline int pldm_msgbuf_typecheck_int16_t(struct pldm_msgbuf *ctx, void *buf) { static_assert(std::is_same::value); return pldm__msgbuf_extract_int16(ctx, buf); } template static inline int pldm_msgbuf_typecheck_uint32_t(struct pldm_msgbuf *ctx, void *buf) { static_assert(std::is_same::value); return pldm__msgbuf_extract_uint32(ctx, buf); } template static inline int pldm_msgbuf_typecheck_int32_t(struct pldm_msgbuf *ctx, void *buf) { static_assert(std::is_same::value); return pldm__msgbuf_extract_int32(ctx, buf); } template static inline int pldm_msgbuf_typecheck_real32_t(struct pldm_msgbuf *ctx, void *buf) { static_assert(std::is_same::value); return pldm__msgbuf_extract_real32(ctx, buf); } #endif #endif /* BUF_H */