| /* 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 <libpldm/base.h> |
| #include <libpldm/pldm_types.h> |
| |
| #include "compiler.h" |
| |
| #include <assert.h> |
| #include <endian.h> |
| #include <errno.h> |
| #include <limits.h> |
| #include <stdbool.h> |
| #include <stdint.h> |
| #include <string.h> |
| #include <sys/types.h> |
| |
| /* |
| * 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<decltype(dst)>(__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 |
| // NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp) |
| pldm__msgbuf_extract_array_void(struct pldm_msgbuf *ctx, void *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; |
| } |
| |
| __attribute__((always_inline)) static inline int |
| pldm_msgbuf_extract_array_char(struct pldm_msgbuf *ctx, char *dst, size_t count) |
| { |
| return pldm__msgbuf_extract_array_void(ctx, dst, count); |
| } |
| |
| __attribute__((always_inline)) static inline int |
| pldm_msgbuf_extract_array_uint8(struct pldm_msgbuf *ctx, uint8_t *dst, |
| size_t count) |
| { |
| return pldm__msgbuf_extract_array_void(ctx, dst, count); |
| } |
| |
| #define pldm_msgbuf_extract_array(ctx, dst, count) \ |
| _Generic((*(dst)), \ |
| uint8_t: pldm_msgbuf_extract_array_uint8, \ |
| char: pldm_msgbuf_extract_array_char)(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 |
| // NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp) |
| pldm__msgbuf_insert_array_void(struct pldm_msgbuf *ctx, const void *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; |
| } |
| |
| __attribute__((always_inline)) static inline int |
| pldm_msgbuf_insert_array_char(struct pldm_msgbuf *ctx, const char *src, |
| size_t count) |
| { |
| return pldm__msgbuf_insert_array_void(ctx, src, count); |
| } |
| |
| __attribute__((always_inline)) static inline int |
| pldm_msgbuf_insert_array_uint8(struct pldm_msgbuf *ctx, const uint8_t *src, |
| size_t count) |
| { |
| return pldm__msgbuf_insert_array_void(ctx, src, count); |
| } |
| |
| #define pldm_msgbuf_insert_array(dst, src, count) \ |
| _Generic((*(src)), \ |
| uint8_t: pldm_msgbuf_insert_array_uint8, \ |
| char: pldm_msgbuf_insert_array_char)(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_string_ascii(struct pldm_msgbuf *ctx, void **cursor, |
| size_t *length) |
| { |
| intmax_t measured; |
| |
| assert(ctx); |
| |
| if (!ctx->cursor || (cursor && *cursor)) { |
| return pldm_msgbuf_status(ctx, EINVAL); |
| } |
| |
| if (ctx->remaining < 0) { |
| /* Tracking the amount of overflow gets disturbed here */ |
| return pldm_msgbuf_status(ctx, EOVERFLOW); |
| } |
| |
| measured = (intmax_t)strnlen((const char *)ctx->cursor, ctx->remaining); |
| if (measured == ctx->remaining) { |
| /* |
| * We have hit the end of the buffer prior to the NUL terminator. |
| * Optimistically, the NUL terminator was one-beyond-the-end. Setting |
| * ctx->remaining negative ensures the `pldm_msgbuf_destroy*()` APIs also |
| * return an error. |
| */ |
| ctx->remaining = -1; |
| return pldm_msgbuf_status(ctx, EOVERFLOW); |
| } |
| |
| /* Include the NUL terminator in the span length, as spans are opaque */ |
| measured++; |
| |
| if (ctx->remaining < INTMAX_MIN + measured) { |
| return pldm_msgbuf_status(ctx, EOVERFLOW); |
| } |
| |
| ctx->remaining -= measured; |
| assert(ctx->remaining >= 0); |
| if (ctx->remaining < 0) { |
| return pldm_msgbuf_status(ctx, EOVERFLOW); |
| } |
| |
| if (cursor) { |
| *cursor = ctx->cursor; |
| } |
| |
| ctx->cursor += measured; |
| |
| if (length) { |
| *length = measured; |
| } |
| |
| 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; |
| } |
| |
| __attribute__((always_inline)) static inline int |
| pldm_msgbuf_copy_string_ascii(struct pldm_msgbuf *dst, struct pldm_msgbuf *src) |
| { |
| void *ascii = NULL; |
| size_t len = 0; |
| int rc; |
| |
| rc = pldm_msgbuf_span_string_ascii(src, &ascii, &len); |
| if (rc < 0) { |
| return rc; |
| } |
| |
| return pldm__msgbuf_insert_array_void(dst, ascii, len); |
| } |
| |
| #ifdef __cplusplus |
| } |
| #endif |
| |
| #ifdef __cplusplus |
| #include <type_traits> |
| |
| template <typename T> |
| static inline int pldm_msgbuf_typecheck_uint8_t(struct pldm_msgbuf *ctx, |
| void *buf) |
| { |
| static_assert(std::is_same<uint8_t *, T>::value); |
| return pldm__msgbuf_extract_uint8(ctx, buf); |
| } |
| |
| template <typename T> |
| static inline int pldm_msgbuf_typecheck_int8_t(struct pldm_msgbuf *ctx, |
| void *buf) |
| { |
| static_assert(std::is_same<int8_t *, T>::value); |
| return pldm__msgbuf_extract_int8(ctx, buf); |
| } |
| |
| template <typename T> |
| static inline int pldm_msgbuf_typecheck_uint16_t(struct pldm_msgbuf *ctx, |
| void *buf) |
| { |
| static_assert(std::is_same<uint16_t *, T>::value); |
| return pldm__msgbuf_extract_uint16(ctx, buf); |
| } |
| |
| template <typename T> |
| static inline int pldm_msgbuf_typecheck_int16_t(struct pldm_msgbuf *ctx, |
| void *buf) |
| { |
| static_assert(std::is_same<int16_t *, T>::value); |
| return pldm__msgbuf_extract_int16(ctx, buf); |
| } |
| |
| template <typename T> |
| static inline int pldm_msgbuf_typecheck_uint32_t(struct pldm_msgbuf *ctx, |
| void *buf) |
| { |
| static_assert(std::is_same<uint32_t *, T>::value); |
| return pldm__msgbuf_extract_uint32(ctx, buf); |
| } |
| |
| template <typename T> |
| static inline int pldm_msgbuf_typecheck_int32_t(struct pldm_msgbuf *ctx, |
| void *buf) |
| { |
| static_assert(std::is_same<int32_t *, T>::value); |
| return pldm__msgbuf_extract_int32(ctx, buf); |
| } |
| |
| template <typename T> |
| static inline int pldm_msgbuf_typecheck_real32_t(struct pldm_msgbuf *ctx, |
| void *buf) |
| { |
| static_assert(std::is_same<real32_t *, T>::value); |
| return pldm__msgbuf_extract_real32(ctx, buf); |
| } |
| #endif |
| |
| #endif /* BUF_H */ |