Introduce a small msgbuf abstraction
Tidy up how we extract data from wire-format buffers.
The API achieves the following:
1. Abstracts the buffer tracking to improve clarity in the calling code
2. Prevents buffer overflows during data extraction
3. Handles type conversions while avoiding undefined behaviour
4. Handles alignment concerns with packed data and removes the need for
`__attribute__((packed))` structs in the public ABI
5. Handles the endianness conversions required by the PLDM
specification(s)
6. Batches error checks such that you mostly only have to do `return
pldm_msgbuf_destroy();` at the end of the decode_* function for error
handling, no error handling required on every `pldm_msgbuf_extract()`
call
7. pldm_msgbuf_extract() is generic over the type of the data pointer
(automatically calls the correct extractor for the type of the
pointer)
8. Generates optimal code (where the optimiser can prove the accesses
are in-bounds we generate straight-line load/store pairs and no
function calls)
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
Change-Id: I7e727cbd26c43aae2815ababe0e6ca4c8e629766
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
diff --git a/meson.build b/meson.build
index 05e0b7f..e9b4be2 100644
--- a/meson.build
+++ b/meson.build
@@ -16,7 +16,7 @@
libpldm_sources = files()
subdir('src')
-libpldm_include_dir = ['include']
+libpldm_include_dir = ['include', 'src']
libpldm_headers = files()
subdir('include/libpldm')
diff --git a/src/msgbuf.h b/src/msgbuf.h
new file mode 100644
index 0000000..5489590
--- /dev/null
+++ b/src/msgbuf.h
@@ -0,0 +1,291 @@
+#ifndef PLDM_MSGBUF_H
+#define PLDM_MSGBUF_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "base.h"
+#include "pldm_types.h"
+
+#include <assert.h>
+#include <endian.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <string.h>
+#include <sys/types.h>
+
+struct pldm_msgbuf {
+ const uint8_t *cursor;
+ ssize_t remaining;
+};
+
+/**
+ * @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 PLDM_SUCCESS if all buffer accesses were in-bounds,
+ * PLDM_ERROR_INVALID_DATA if pointer parameters are invalid, or
+ * PLDM_ERROR_INVALID_LENGTH if length constraints are violated.
+ */
+static inline int pldm_msgbuf_init(struct pldm_msgbuf *ctx, size_t minsize,
+ const void *buf, size_t len)
+{
+ uint8_t *end;
+
+ if (!ctx || !buf) {
+ return PLDM_ERROR_INVALID_DATA;
+ }
+
+ if ((minsize > len) || (len > SSIZE_MAX)) {
+ return PLDM_ERROR_INVALID_LENGTH;
+ }
+
+ end = (uint8_t *)buf + len;
+ if (end && end < (uint8_t *)buf) {
+ return PLDM_ERROR_INVALID_LENGTH;
+ }
+
+ ctx->cursor = (uint8_t *)buf;
+ ctx->remaining = (ssize_t)len;
+
+ return PLDM_SUCCESS;
+}
+
+/**
+ * @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.
+ */
+static inline int pldm_msgbuf_validate(struct pldm_msgbuf *ctx)
+{
+ if (!ctx) {
+ return PLDM_ERROR_INVALID_DATA;
+ }
+
+ return ctx->remaining >= 0 ? PLDM_SUCCESS : PLDM_ERROR_INVALID_LENGTH;
+}
+
+/**
+ * @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.
+ */
+static inline int pldm_msgbuf_destroy(struct pldm_msgbuf *ctx)
+{
+ int valid;
+
+ if (!ctx) {
+ return PLDM_ERROR_INVALID_DATA;
+ }
+
+ valid = pldm_msgbuf_validate(ctx);
+
+ ctx->cursor = NULL;
+ ctx->remaining = 0;
+
+ return valid;
+}
+
+/**
+ * @brief pldm_msgbuf extractor for a uint8_t
+ *
+ * @param[inout] 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
+ */
+static inline int pldm_msgbuf_extract_uint8(struct pldm_msgbuf *ctx,
+ uint8_t *dst)
+{
+ if (!ctx || !ctx->cursor || !dst) {
+ return PLDM_ERROR_INVALID_DATA;
+ }
+
+ ctx->remaining -= sizeof(*dst);
+ assert(ctx->remaining >= 0);
+ if (ctx->remaining < 0) {
+ return PLDM_ERROR_INVALID_LENGTH;
+ }
+
+ *dst = *((uint8_t *)(ctx->cursor));
+ ctx->cursor++;
+ return PLDM_SUCCESS;
+}
+
+static inline int pldm_msgbuf_extract_int8(struct pldm_msgbuf *ctx, int8_t *dst)
+{
+ if (!ctx || !ctx->cursor || !dst) {
+ return PLDM_ERROR_INVALID_DATA;
+ }
+
+ ctx->remaining -= sizeof(*dst);
+ assert(ctx->remaining >= 0);
+ if (ctx->remaining < 0) {
+ return PLDM_ERROR_INVALID_LENGTH;
+ }
+
+ *dst = *((int8_t *)(ctx->cursor));
+ ctx->cursor++;
+ return PLDM_SUCCESS;
+}
+
+static inline int pldm_msgbuf_extract_uint16(struct pldm_msgbuf *ctx,
+ uint16_t *dst)
+{
+ uint16_t ldst;
+
+ if (!ctx || !ctx->cursor || !dst) {
+ return PLDM_ERROR_INVALID_DATA;
+ }
+
+ // 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_ERROR_INVALID_LENGTH;
+ }
+
+ // 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
+ *dst = le16toh(ldst);
+ ctx->cursor += sizeof(ldst);
+
+ return PLDM_SUCCESS;
+}
+
+static inline int pldm_msgbuf_extract_int16(struct pldm_msgbuf *ctx,
+ int16_t *dst)
+{
+ int16_t ldst;
+
+ if (!ctx || !ctx->cursor || !dst) {
+ return PLDM_ERROR_INVALID_DATA;
+ }
+
+ ctx->remaining -= sizeof(ldst);
+ assert(ctx->remaining >= 0);
+ if (ctx->remaining < 0) {
+ return PLDM_ERROR_INVALID_LENGTH;
+ }
+
+ memcpy(&ldst, ctx->cursor, sizeof(ldst));
+
+ *dst = le16toh(ldst);
+ ctx->cursor += sizeof(ldst);
+
+ return PLDM_SUCCESS;
+}
+
+static inline int pldm_msgbuf_extract_uint32(struct pldm_msgbuf *ctx,
+ uint32_t *dst)
+{
+ uint32_t ldst;
+
+ if (!ctx || !ctx->cursor || !dst) {
+ return PLDM_ERROR_INVALID_DATA;
+ }
+
+ ctx->remaining -= sizeof(ldst);
+ assert(ctx->remaining >= 0);
+ if (ctx->remaining < 0) {
+ return PLDM_ERROR_INVALID_LENGTH;
+ }
+
+ memcpy(&ldst, ctx->cursor, sizeof(ldst));
+
+ *dst = le32toh(ldst);
+ ctx->cursor += sizeof(ldst);
+
+ return PLDM_SUCCESS;
+}
+
+static inline int pldm_msgbuf_extract_int32(struct pldm_msgbuf *ctx,
+ int32_t *dst)
+{
+ int32_t ldst;
+
+ if (!ctx || !ctx->cursor || !dst) {
+ return PLDM_ERROR_INVALID_DATA;
+ }
+
+ ctx->remaining -= sizeof(ldst);
+ assert(ctx->remaining >= 0);
+ if (ctx->remaining < 0) {
+ return PLDM_ERROR_INVALID_LENGTH;
+ }
+
+ memcpy(&ldst, ctx->cursor, sizeof(ldst));
+
+ *dst = le32toh(ldst);
+ ctx->cursor += sizeof(ldst);
+
+ return PLDM_SUCCESS;
+}
+
+static inline int pldm_msgbuf_extract_real32(struct pldm_msgbuf *ctx,
+ real32_t *dst)
+{
+ uint32_t ldst;
+
+ if (!ctx || !ctx->cursor || !dst) {
+ return PLDM_ERROR_INVALID_DATA;
+ }
+
+ ctx->remaining -= sizeof(ldst);
+ assert(ctx->remaining >= 0);
+ if (ctx->remaining < 0) {
+ return PLDM_ERROR_INVALID_LENGTH;
+ }
+
+ _Static_assert(sizeof(*dst) == sizeof(ldst),
+ "Mismatched type sizes for dst and ldst");
+ memcpy(&ldst, ctx->cursor, sizeof(ldst));
+ ldst = le32toh(ldst);
+ memcpy(dst, &ldst, sizeof(*dst));
+ ctx->cursor += sizeof(*dst);
+
+ return PLDM_SUCCESS;
+}
+
+#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, dst)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* BUF_H */
diff --git a/tests/meson.build b/tests/meson.build
index 1955487..aba89bc 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -21,7 +21,8 @@
'libpldm_fru_test',
'libpldm_utils_test',
'libpldm_pdr_test',
- 'libpldm_firmware_update_test'
+ 'libpldm_firmware_update_test',
+ 'msgbuf',
]
if get_option('oem-ibm').enabled()
@@ -31,9 +32,12 @@
]
endif
+src_includes = include_directories('..' / 'src', '..' / 'include' / 'libpldm')
+
foreach t : tests
test(t, executable(t.underscorify(), t + '.cpp',
implicit_include_directories: false,
+ include_directories: src_includes,
dependencies: [
libpldm_dep,
gtest_dep,
@@ -41,3 +45,7 @@
workdir: meson.current_source_dir())
endforeach
+test('msgbuf_generic', executable('msgbuf_generic',
+ 'msgbuf_generic.c',
+ implicit_include_directories: false,
+ include_directories: src_includes))
diff --git a/tests/msgbuf.cpp b/tests/msgbuf.cpp
new file mode 100644
index 0000000..bf2f95e
--- /dev/null
+++ b/tests/msgbuf.cpp
@@ -0,0 +1,317 @@
+#include <endian.h>
+
+#include <cfloat>
+
+#include <gtest/gtest.h>
+
+/* We're exercising the implementation so disable the asserts for now */
+#ifndef NDEBUG
+#define NDEBUG 1
+#endif
+
+/*
+ * Fix up C11's _Static_assert() vs C++'s static_assert().
+ *
+ * Can we please have nice things for once.
+ */
+#ifdef __cplusplus
+// NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
+#define _Static_assert(...) static_assert(__VA_ARGS__)
+#endif
+
+#include "msgbuf.h"
+
+TEST(msgbuf, init_bad_ctx)
+{
+ EXPECT_NE(pldm_msgbuf_init(NULL, 0, NULL, 0), PLDM_SUCCESS);
+}
+
+TEST(msgbuf, init_bad_minsize)
+{
+ struct pldm_msgbuf _ctx;
+ struct pldm_msgbuf* ctx = &_ctx;
+ uint8_t buf[1] = {};
+
+ EXPECT_NE(pldm_msgbuf_init(ctx, sizeof(buf) + 1U, buf, sizeof(buf)),
+ PLDM_SUCCESS);
+}
+
+TEST(msgbuf, init_bad_buf)
+{
+ struct pldm_msgbuf _ctx;
+ struct pldm_msgbuf* ctx = &_ctx;
+
+ EXPECT_NE(pldm_msgbuf_init(ctx, 0, NULL, 0), PLDM_SUCCESS);
+}
+
+TEST(msgbuf, init_bad_len)
+{
+ struct pldm_msgbuf _ctx;
+ struct pldm_msgbuf* ctx = &_ctx;
+ uint8_t buf[1] = {};
+
+ EXPECT_NE(pldm_msgbuf_init(ctx, sizeof(buf), buf, SIZE_MAX), PLDM_SUCCESS);
+}
+
+TEST(msgbuf, init_overflow)
+{
+ struct pldm_msgbuf _ctx;
+ struct pldm_msgbuf* ctx = &_ctx;
+ // This is an intrinsic part of the test.
+ // NOLINTNEXTLINE(performance-no-int-to-ptr)
+ uint8_t* buf = (uint8_t*)SIZE_MAX;
+
+ EXPECT_NE(pldm_msgbuf_init(ctx, 0, buf, 2), PLDM_SUCCESS);
+}
+
+TEST(msgbuf, init_success)
+{
+ struct pldm_msgbuf _ctx;
+ struct pldm_msgbuf* ctx = &_ctx;
+ uint8_t buf[1] = {};
+
+ EXPECT_EQ(pldm_msgbuf_init(ctx, sizeof(buf), buf, sizeof(buf)),
+ PLDM_SUCCESS);
+}
+
+TEST(msgbuf, destroy_none)
+{
+ struct pldm_msgbuf _ctx;
+ struct pldm_msgbuf* ctx = &_ctx;
+ uint8_t buf[1] = {};
+
+ ASSERT_EQ(pldm_msgbuf_init(ctx, sizeof(buf), buf, sizeof(buf)),
+ PLDM_SUCCESS);
+ EXPECT_EQ(pldm_msgbuf_destroy(ctx), PLDM_SUCCESS);
+}
+
+TEST(msgbuf, destroy_exact)
+{
+ struct pldm_msgbuf _ctx;
+ struct pldm_msgbuf* ctx = &_ctx;
+ uint8_t buf[1] = {0xa5};
+ uint8_t val;
+
+ ASSERT_EQ(pldm_msgbuf_init(ctx, sizeof(buf), buf, sizeof(buf)),
+ PLDM_SUCCESS);
+ EXPECT_EQ(pldm_msgbuf_extract_uint8(ctx, &val), PLDM_SUCCESS);
+ EXPECT_EQ(val, 0xa5);
+ EXPECT_EQ(pldm_msgbuf_destroy(ctx), PLDM_SUCCESS);
+}
+
+TEST(msgbuf, destroy_over)
+{
+ struct pldm_msgbuf _ctx;
+ struct pldm_msgbuf* ctx = &_ctx;
+ uint8_t buf[1] = {0xa5};
+ uint8_t val;
+
+ ASSERT_EQ(pldm_msgbuf_init(ctx, sizeof(buf), buf, sizeof(buf)),
+ PLDM_SUCCESS);
+ ASSERT_EQ(pldm_msgbuf_extract_uint8(ctx, &val), PLDM_SUCCESS);
+ ASSERT_EQ(val, 0xa5);
+ EXPECT_NE(pldm_msgbuf_extract_uint8(ctx, &val), PLDM_SUCCESS);
+ EXPECT_EQ(pldm_msgbuf_destroy(ctx), PLDM_ERROR_INVALID_LENGTH);
+}
+
+TEST(msgbuf, destroy_under)
+{
+ struct pldm_msgbuf _ctx;
+ struct pldm_msgbuf* ctx = &_ctx;
+ uint8_t buf[2] = {0x5a, 0xa5};
+ uint8_t val;
+
+ ASSERT_EQ(pldm_msgbuf_init(ctx, sizeof(buf), buf, sizeof(buf)),
+ PLDM_SUCCESS);
+ EXPECT_EQ(pldm_msgbuf_extract_uint8(ctx, &val), PLDM_SUCCESS);
+ EXPECT_EQ(val, 0x5a);
+ EXPECT_EQ(pldm_msgbuf_destroy(ctx), PLDM_SUCCESS);
+}
+
+TEST(msgbuf, extract_one_uint8)
+{
+ struct pldm_msgbuf _ctx;
+ struct pldm_msgbuf* ctx = &_ctx;
+ uint8_t buf[1] = {0xa5};
+ uint8_t val;
+
+ ASSERT_EQ(pldm_msgbuf_init(ctx, sizeof(buf), buf, sizeof(buf)),
+ PLDM_SUCCESS);
+ EXPECT_EQ(pldm_msgbuf_extract_uint8(ctx, &val), PLDM_SUCCESS);
+ EXPECT_EQ(val, 0xa5);
+ EXPECT_EQ(pldm_msgbuf_destroy(ctx), PLDM_SUCCESS);
+}
+
+TEST(msgbuf, extract_over_uint8)
+{
+ struct pldm_msgbuf _ctx;
+ struct pldm_msgbuf* ctx = &_ctx;
+ uint8_t buf[1] = {};
+ uint8_t val;
+
+ ASSERT_EQ(pldm_msgbuf_init(ctx, 0, buf, 0), PLDM_SUCCESS);
+ EXPECT_NE(pldm_msgbuf_extract_uint8(ctx, &val), PLDM_SUCCESS);
+ EXPECT_EQ(pldm_msgbuf_destroy(ctx), PLDM_ERROR_INVALID_LENGTH);
+}
+
+TEST(msgbuf, extract_one_int8)
+{
+ struct pldm_msgbuf _ctx;
+ struct pldm_msgbuf* ctx = &_ctx;
+ int8_t buf[1] = {-1};
+ int8_t val;
+
+ ASSERT_EQ(pldm_msgbuf_init(ctx, sizeof(buf), buf, sizeof(buf)),
+ PLDM_SUCCESS);
+ EXPECT_EQ(pldm_msgbuf_extract_int8(ctx, &val), PLDM_SUCCESS);
+ EXPECT_EQ(val, -1);
+ EXPECT_EQ(pldm_msgbuf_destroy(ctx), PLDM_SUCCESS);
+}
+
+TEST(msgbuf, extract_over_int8)
+{
+ struct pldm_msgbuf _ctx;
+ struct pldm_msgbuf* ctx = &_ctx;
+ int8_t buf[1] = {};
+ int8_t val;
+
+ ASSERT_EQ(pldm_msgbuf_init(ctx, 0, buf, 0), PLDM_SUCCESS);
+ EXPECT_NE(pldm_msgbuf_extract_int8(ctx, &val), PLDM_SUCCESS);
+ EXPECT_EQ(pldm_msgbuf_destroy(ctx), PLDM_ERROR_INVALID_LENGTH);
+}
+
+TEST(msgbuf, extract_one_uint16)
+{
+ struct pldm_msgbuf _ctx;
+ struct pldm_msgbuf* ctx = &_ctx;
+ uint16_t buf[1] = {htole16(0x5aa5)};
+ uint16_t val = {};
+
+ ASSERT_EQ(pldm_msgbuf_init(ctx, sizeof(buf), buf, sizeof(buf)),
+ PLDM_SUCCESS);
+ EXPECT_EQ(pldm_msgbuf_extract_uint16(ctx, &val), PLDM_SUCCESS);
+ EXPECT_EQ(val, 0x5aa5);
+ EXPECT_EQ(pldm_msgbuf_destroy(ctx), PLDM_SUCCESS);
+}
+
+TEST(msgbuf, extract_over_uint16)
+{
+ struct pldm_msgbuf _ctx;
+ struct pldm_msgbuf* ctx = &_ctx;
+ uint16_t buf[1] = {};
+ uint16_t val;
+
+ ASSERT_EQ(pldm_msgbuf_init(ctx, 0, buf, 0), PLDM_SUCCESS);
+ EXPECT_NE(pldm_msgbuf_extract_uint16(ctx, &val), PLDM_SUCCESS);
+ EXPECT_EQ(pldm_msgbuf_destroy(ctx), PLDM_ERROR_INVALID_LENGTH);
+}
+
+TEST(msgbuf, extract_one_int16)
+{
+ struct pldm_msgbuf _ctx;
+ struct pldm_msgbuf* ctx = &_ctx;
+ int16_t buf[1] = {(int16_t)(htole16((uint16_t)INT16_MIN))};
+ int16_t val;
+
+ ASSERT_EQ(pldm_msgbuf_init(ctx, sizeof(buf), buf, sizeof(buf)),
+ PLDM_SUCCESS);
+ EXPECT_EQ(pldm_msgbuf_extract_int16(ctx, &val), PLDM_SUCCESS);
+ EXPECT_EQ(val, INT16_MIN);
+ EXPECT_EQ(pldm_msgbuf_destroy(ctx), PLDM_SUCCESS);
+}
+
+TEST(msgbuf, extract_over_int16)
+{
+ struct pldm_msgbuf _ctx;
+ struct pldm_msgbuf* ctx = &_ctx;
+ int16_t buf[1] = {};
+ int16_t val;
+
+ ASSERT_EQ(pldm_msgbuf_init(ctx, 0, buf, 0), PLDM_SUCCESS);
+ EXPECT_NE(pldm_msgbuf_extract_int16(ctx, &val), PLDM_SUCCESS);
+ EXPECT_EQ(pldm_msgbuf_destroy(ctx), PLDM_ERROR_INVALID_LENGTH);
+}
+
+TEST(msgbuf, extract_one_uint32)
+{
+ struct pldm_msgbuf _ctx;
+ struct pldm_msgbuf* ctx = &_ctx;
+ uint32_t buf[1] = {htole32(0x5a00ffa5)};
+ uint32_t val;
+
+ ASSERT_EQ(pldm_msgbuf_init(ctx, sizeof(buf), buf, sizeof(buf)),
+ PLDM_SUCCESS);
+ EXPECT_EQ(pldm_msgbuf_extract_uint32(ctx, &val), PLDM_SUCCESS);
+ EXPECT_EQ(val, 0x5a00ffa5);
+ EXPECT_EQ(pldm_msgbuf_destroy(ctx), PLDM_SUCCESS);
+}
+
+TEST(msgbuf, extract_over_uint32)
+{
+ struct pldm_msgbuf _ctx;
+ struct pldm_msgbuf* ctx = &_ctx;
+ uint32_t buf[1] = {};
+ uint32_t val;
+
+ ASSERT_EQ(pldm_msgbuf_init(ctx, 0, buf, 0), PLDM_SUCCESS);
+ EXPECT_NE(pldm_msgbuf_extract_uint32(ctx, &val), PLDM_SUCCESS);
+ EXPECT_EQ(pldm_msgbuf_destroy(ctx), PLDM_ERROR_INVALID_LENGTH);
+}
+
+TEST(msgbuf, extract_one_int32)
+{
+ struct pldm_msgbuf _ctx;
+ struct pldm_msgbuf* ctx = &_ctx;
+ int32_t buf[1] = {(int32_t)(htole32((uint32_t)(INT32_MIN)))};
+ int32_t val;
+
+ ASSERT_EQ(pldm_msgbuf_init(ctx, sizeof(buf), buf, sizeof(buf)),
+ PLDM_SUCCESS);
+ EXPECT_EQ(pldm_msgbuf_extract_int32(ctx, &val), PLDM_SUCCESS);
+ EXPECT_EQ(val, INT32_MIN);
+ EXPECT_EQ(pldm_msgbuf_destroy(ctx), PLDM_SUCCESS);
+}
+
+TEST(msgbuf, extract_over_int32)
+{
+ struct pldm_msgbuf _ctx;
+ struct pldm_msgbuf* ctx = &_ctx;
+ int32_t buf[1] = {};
+ int32_t val;
+
+ ASSERT_EQ(pldm_msgbuf_init(ctx, 0, buf, 0), PLDM_SUCCESS);
+ EXPECT_NE(pldm_msgbuf_extract_int32(ctx, &val), PLDM_SUCCESS);
+ EXPECT_EQ(pldm_msgbuf_destroy(ctx), PLDM_ERROR_INVALID_LENGTH);
+}
+
+TEST(msgbuf, extract_one_real32)
+{
+ struct pldm_msgbuf _ctx;
+ struct pldm_msgbuf* ctx = &_ctx;
+ uint32_t buf[1] = {};
+ uint32_t xform;
+ real32_t val;
+
+ val = FLT_MAX;
+ memcpy(&xform, &val, sizeof(val));
+ buf[0] = htole32(xform);
+ val = 0;
+
+ ASSERT_EQ(pldm_msgbuf_init(ctx, sizeof(buf), buf, sizeof(buf)),
+ PLDM_SUCCESS);
+ EXPECT_EQ(pldm_msgbuf_extract_real32(ctx, &val), PLDM_SUCCESS);
+ EXPECT_EQ(val, FLT_MAX);
+ EXPECT_EQ(pldm_msgbuf_destroy(ctx), PLDM_SUCCESS);
+}
+
+TEST(msgbuf, extract_over_real32)
+{
+ struct pldm_msgbuf _ctx;
+ struct pldm_msgbuf* ctx = &_ctx;
+ real32_t buf[1] = {};
+ real32_t val;
+
+ ASSERT_EQ(pldm_msgbuf_init(ctx, 0, buf, 0), PLDM_SUCCESS);
+ EXPECT_NE(pldm_msgbuf_extract_real32(ctx, &val), PLDM_SUCCESS);
+ EXPECT_EQ(pldm_msgbuf_destroy(ctx), PLDM_ERROR_INVALID_LENGTH);
+}
diff --git a/tests/msgbuf_generic.c b/tests/msgbuf_generic.c
new file mode 100644
index 0000000..9c3e1b9
--- /dev/null
+++ b/tests/msgbuf_generic.c
@@ -0,0 +1,151 @@
+/* Test pldm_msgbuf_extract() separately because we can't do _Generic() in C++
+ * code, i.e. gtest */
+
+#include <endian.h>
+#include <float.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+/* We're exercising the implementation so disable the asserts for now */
+#ifndef NDEBUG
+#define NDEBUG 1
+#endif
+#include "msgbuf.h"
+
+/* Given we disabled asserts above, set up our own expectation framework */
+#define expect(cond) __expect(__func__, __LINE__, (cond))
+#define __expect(fn, line, cond) \
+ do \
+ { \
+ if (!(cond)) \
+ { \
+ fprintf(stderr, "%s:%d: failed expectation: %s\n", fn, line, \
+ #cond); \
+ exit(EXIT_FAILURE); \
+ } \
+ } while (0)
+
+static void test_msgbuf_extract_generic_uint8(void)
+{
+ struct pldm_msgbuf _ctx;
+ struct pldm_msgbuf* ctx = &_ctx;
+ uint8_t buf[1] = {0xa5};
+ uint8_t val;
+
+ expect(pldm_msgbuf_init(ctx, sizeof(buf), buf, sizeof(buf)) ==
+ PLDM_SUCCESS);
+ expect(pldm_msgbuf_extract(ctx, &val) == PLDM_SUCCESS);
+ expect(val == 0xa5);
+ expect(pldm_msgbuf_destroy(ctx) == PLDM_SUCCESS);
+}
+
+static void test_msgbuf_extract_generic_int8(void)
+{
+ struct pldm_msgbuf _ctx;
+ struct pldm_msgbuf* ctx = &_ctx;
+ int8_t buf[1] = {-1};
+ int8_t val;
+
+ expect(pldm_msgbuf_init(ctx, sizeof(buf), buf, sizeof(buf)) ==
+ PLDM_SUCCESS);
+ expect(pldm_msgbuf_extract(ctx, &val) == PLDM_SUCCESS);
+ expect(val == -1);
+ expect(pldm_msgbuf_destroy(ctx) == PLDM_SUCCESS);
+}
+
+static void test_msgbuf_extract_generic_uint16(void)
+{
+ struct pldm_msgbuf _ctx;
+ struct pldm_msgbuf* ctx = &_ctx;
+ uint16_t buf[1] = {0x5aa5};
+ uint16_t val;
+
+ expect(pldm_msgbuf_init(ctx, sizeof(buf), buf, sizeof(buf)) ==
+ PLDM_SUCCESS);
+ expect(pldm_msgbuf_extract(ctx, &val) == PLDM_SUCCESS);
+ expect(val == 0x5aa5);
+ expect(pldm_msgbuf_destroy(ctx) == PLDM_SUCCESS);
+}
+
+static void test_msgbuf_extract_generic_int16(void)
+{
+ struct pldm_msgbuf _ctx;
+ struct pldm_msgbuf* ctx = &_ctx;
+ int16_t buf[1] = {(int16_t)(htole16((uint16_t)INT16_MIN))};
+ int16_t val;
+
+ expect(pldm_msgbuf_init(ctx, sizeof(buf), buf, sizeof(buf)) ==
+ PLDM_SUCCESS);
+ expect(pldm_msgbuf_extract(ctx, &val) == PLDM_SUCCESS);
+ expect(val == INT16_MIN);
+ expect(pldm_msgbuf_destroy(ctx) == PLDM_SUCCESS);
+}
+
+static void test_msgbuf_extract_generic_uint32(void)
+{
+ struct pldm_msgbuf _ctx;
+ struct pldm_msgbuf* ctx = &_ctx;
+ uint32_t buf[1] = {0x5a00ffa5};
+ uint32_t val;
+
+ expect(pldm_msgbuf_init(ctx, sizeof(buf), buf, sizeof(buf)) ==
+ PLDM_SUCCESS);
+ expect(pldm_msgbuf_extract(ctx, &val) == PLDM_SUCCESS);
+ expect(val == 0x5a00ffa5);
+ expect(pldm_msgbuf_destroy(ctx) == PLDM_SUCCESS);
+}
+
+static void test_msgbuf_extract_generic_int32(void)
+{
+ struct pldm_msgbuf _ctx;
+ struct pldm_msgbuf* ctx = &_ctx;
+ int32_t buf[1] = {(int32_t)(htole32((uint32_t)INT32_MIN))};
+ int32_t val;
+
+ expect(pldm_msgbuf_init(ctx, sizeof(buf), buf, sizeof(buf)) ==
+ PLDM_SUCCESS);
+ expect(pldm_msgbuf_extract(ctx, &val) == PLDM_SUCCESS);
+ expect(val == INT32_MIN);
+ expect(pldm_msgbuf_destroy(ctx) == PLDM_SUCCESS);
+}
+
+static void test_msgbuf_extract_generic_real32(void)
+{
+ struct pldm_msgbuf _ctx;
+ struct pldm_msgbuf* ctx = &_ctx;
+ uint32_t buf[1];
+ uint32_t xform;
+ real32_t val;
+
+ val = FLT_MAX;
+ memcpy(&xform, &val, sizeof(val));
+ buf[0] = htole32(xform);
+ val = 0;
+
+ expect(pldm_msgbuf_init(ctx, sizeof(buf), buf, sizeof(buf)) ==
+ PLDM_SUCCESS);
+ expect(pldm_msgbuf_extract(ctx, &val) == PLDM_SUCCESS);
+ expect(val == FLT_MAX);
+ expect(pldm_msgbuf_destroy(ctx) == PLDM_SUCCESS);
+}
+
+typedef void (*testfn)(void);
+
+static const testfn tests[] = {
+ test_msgbuf_extract_generic_uint8, test_msgbuf_extract_generic_int8,
+ test_msgbuf_extract_generic_uint16, test_msgbuf_extract_generic_int16,
+ test_msgbuf_extract_generic_uint32, test_msgbuf_extract_generic_int32,
+ test_msgbuf_extract_generic_real32, NULL};
+
+int main(void)
+{
+ testfn const* testp = &tests[0];
+
+ while (*testp)
+ {
+ (*testp)();
+ testp++;
+ }
+
+ return 0;
+}