msgbuf: Define a separate msgbuf structure for encode/decode function

Define separate msgbuf structures to avoid casting away const-qualifiers
in the msgbuf constructor function:

* pldm_msgbuf_rw: for encode functions with non const-qualified buffer
* pldm_msgbuf_ro: for decode functions with const-qualified buffer

Further, use _Generic() to keep the API ergonomic while still yielding a
compile error when wrong msgbuf type is passed.

Change-Id: I71dbcb7996e9fb402b49870fce539a939c1497e5
Signed-off-by: John Chung <john.chung@arm.com>
Signed-off-by: Andrew Jeffery <andrew@codeconstruct.com.au>
diff --git a/src/msgbuf/core.h b/src/msgbuf/core.h
new file mode 100644
index 0000000..bce7b59
--- /dev/null
+++ b/src/msgbuf/core.h
@@ -0,0 +1,1579 @@
+/* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */
+#ifndef PLDM_MSGBUF_CORE_H
+#define PLDM_MSGBUF_CORE_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 "compiler.h"
+
+#include <libpldm/pldm_types.h>
+
+#include <endian.h>
+#include <errno.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <uchar.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");
+	int compliance;
+} build_assertions LIBPLDM_CC_UNUSED;
+
+struct pldm_msgbuf_rw {
+	uint8_t *cursor;
+	intmax_t remaining;
+};
+
+struct pldm_msgbuf_ro {
+	const uint8_t *cursor;
+	intmax_t remaining;
+};
+
+LIBPLDM_CC_ALWAYS_INLINE
+// NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
+void pldm__msgbuf_cleanup(const void *cursor LIBPLDM_CC_UNUSED,
+			  intmax_t remaining LIBPLDM_CC_UNUSED)
+{
+	assert(cursor == NULL && remaining == INTMAX_MIN);
+}
+
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE
+// NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
+void pldm__msgbuf_rw_cleanup(struct pldm_msgbuf_rw *ctx LIBPLDM_CC_UNUSED)
+{
+	pldm__msgbuf_cleanup((const void *)ctx->cursor, ctx->remaining);
+}
+
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE
+// NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
+void pldm__msgbuf_ro_cleanup(struct pldm_msgbuf_ro *ctx LIBPLDM_CC_UNUSED)
+{
+	pldm__msgbuf_cleanup((const void *)ctx->cursor, ctx->remaining);
+}
+
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE
+// NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
+int pldm__msgbuf_set_invalid(intmax_t *remaining)
+{
+	*remaining = INTMAX_MIN;
+	return -EOVERFLOW;
+}
+
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE
+// NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
+int pldm__msgbuf_rw_invalidate(struct pldm_msgbuf_rw *ctx)
+{
+	return pldm__msgbuf_set_invalid(&ctx->remaining);
+}
+
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE
+// NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
+int pldm__msgbuf_ro_invalidate(struct pldm_msgbuf_ro *ctx)
+{
+	return pldm__msgbuf_set_invalid(&ctx->remaining);
+}
+
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE
+LIBPLDM_CC_WARN_UNUSED_RESULT
+int
+// NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
+pldm__msgbuf_init_errno(const uint8_t **cursor, intmax_t *remaining,
+			size_t minsize, const void *buf, size_t len)
+{
+	*cursor = NULL;
+
+	if ((minsize > len)) {
+		return pldm__msgbuf_set_invalid(remaining);
+	}
+
+#if INTMAX_MAX < SIZE_MAX
+	if (len > INTMAX_MAX) {
+		return pldm__msgbuf_set_invalid(remaining);
+	}
+#endif
+
+	if (UINTPTR_MAX - (uintptr_t)buf < len) {
+		return pldm__msgbuf_set_invalid(remaining);
+	}
+
+	*cursor = (const uint8_t *)buf;
+	*remaining = (intmax_t)len;
+
+	return 0;
+}
+
+/**
+ * @brief Initialize pldm buf struct for buf extractor
+ *
+ * @param[out] ctx - pldm_msgbuf_rw 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.
+ */
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE
+LIBPLDM_CC_WARN_UNUSED_RESULT
+int
+// NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
+pldm_msgbuf_rw_init_errno(struct pldm_msgbuf_rw *ctx, size_t minsize,
+			  const void *buf, size_t len)
+{
+	return pldm__msgbuf_init_errno((const uint8_t **)&ctx->cursor,
+				       &ctx->remaining, minsize, buf, len);
+}
+
+/**
+ * @brief Initialize pldm buf struct for buf extractor
+ *
+ * @param[out] ctx - pldm_msgbuf_ro 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.
+ */
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE
+LIBPLDM_CC_WARN_UNUSED_RESULT
+int
+// NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
+pldm_msgbuf_ro_init_errno(struct pldm_msgbuf_ro *ctx, size_t minsize,
+			  const void *buf, size_t len)
+{
+	return pldm__msgbuf_init_errno(&ctx->cursor, &ctx->remaining, minsize,
+				       buf, len);
+}
+
+/**
+ * @brief Validate buffer overflow state
+ *
+ * @param[in] ctx - 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.
+ */
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE
+LIBPLDM_CC_WARN_UNUSED_RESULT
+// NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
+int pldm__msgbuf_validate(intmax_t remaining)
+{
+	if (remaining < 0) {
+		return -EOVERFLOW;
+	}
+
+	return 0;
+}
+
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE
+LIBPLDM_CC_WARN_UNUSED_RESULT
+int pldm_msgbuf_ro_validate(struct pldm_msgbuf_ro *ctx)
+{
+	return pldm__msgbuf_validate(ctx->remaining);
+}
+
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE
+LIBPLDM_CC_WARN_UNUSED_RESULT
+int pldm_msgbuf_rw_validate(struct pldm_msgbuf_rw *ctx)
+{
+	return pldm__msgbuf_validate(ctx->remaining);
+}
+
+/**
+ * @brief Test whether a message buffer has been exactly consumed
+ *
+ * @param[in] ctx - pldm_msgbuf context for extractor
+ *
+ * @return 0 iff there are zero bytes of data that remain unread from the buffer
+ * and no overflow has occurred. Otherwise, -EBADMSG if the buffer has not been
+ * completely consumed, or -EOVERFLOW if accesses were attempted beyond the
+ * bounds of the buffer.
+ */
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE
+LIBPLDM_CC_WARN_UNUSED_RESULT
+// NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
+int pldm__msgbuf_consumed(intmax_t remaining)
+{
+	if (remaining > 0) {
+		return -EBADMSG;
+	}
+
+	if (remaining < 0) {
+		return -EOVERFLOW;
+	}
+
+	return 0;
+}
+
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE
+LIBPLDM_CC_WARN_UNUSED_RESULT
+int pldm_msgbuf_ro_consumed(struct pldm_msgbuf_ro *ctx)
+{
+	return pldm__msgbuf_consumed(ctx->remaining);
+}
+
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE
+LIBPLDM_CC_WARN_UNUSED_RESULT
+int pldm_msgbuf_rw_consumed(struct pldm_msgbuf_rw *ctx)
+{
+	return pldm__msgbuf_consumed(ctx->remaining);
+}
+
+/**
+ * @brief End use of a msgbuf under error conditions
+ *
+ * @param[in] ctx - The msgbuf instance to discard
+ * @param[in] error - The error value to propagate
+ *
+ * Under normal conditions use of a msgbuf instance must be ended using @ref
+ * pldm_msgbuf_complete or one of its related APIs. Under error conditions, @ref
+ * pldm_msgbuf_discard should be used instead, as it makes it straight-forward
+ * to finalise the msgbuf while propagating the existing error code.
+ *
+ * @return The value provided in @param error
+ */
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE
+LIBPLDM_CC_WARN_UNUSED_RESULT
+// NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
+int pldm__msgbuf_discard(const uint8_t **cursor, intmax_t *remaining, int error)
+{
+	*cursor = NULL;
+	pldm__msgbuf_set_invalid(remaining);
+	return error;
+}
+
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE
+LIBPLDM_CC_WARN_UNUSED_RESULT
+int pldm_msgbuf_rw_discard(struct pldm_msgbuf_rw *ctx, int error)
+{
+	return pldm__msgbuf_discard((const uint8_t **)&ctx->cursor,
+				    &ctx->remaining, error);
+}
+
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE
+LIBPLDM_CC_WARN_UNUSED_RESULT
+int pldm_msgbuf_ro_discard(struct pldm_msgbuf_ro *ctx, int error)
+{
+	return pldm__msgbuf_discard(&ctx->cursor, &ctx->remaining, error);
+}
+
+/**
+ * @brief Complete the pldm_msgbuf_rw instance
+ *
+ * @param[in] ctx - pldm_msgbuf_rw context for extractor
+ *
+ * @return 0 if all buffer accesses were in-bounds, -EOVERFLOW otherwise.
+ */
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE
+LIBPLDM_CC_WARN_UNUSED_RESULT
+int pldm_msgbuf_rw_complete(struct pldm_msgbuf_rw *ctx)
+{
+	return pldm_msgbuf_rw_discard(ctx, pldm_msgbuf_rw_validate(ctx));
+}
+
+/**
+ * @brief Complete the pldm_msgbuf_ro instance
+ *
+ * @param[in] ctx - pldm_msgbuf_ro context for extractor
+ *
+ * @return 0 if all buffer accesses were in-bounds, -EOVERFLOW otherwise.
+ */
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE
+LIBPLDM_CC_WARN_UNUSED_RESULT
+int pldm_msgbuf_ro_complete(struct pldm_msgbuf_ro *ctx)
+{
+	return pldm_msgbuf_ro_discard(ctx, pldm_msgbuf_ro_validate(ctx));
+}
+
+/**
+ * @brief Complete the pldm_msgbuf_rw instance, and check that the underlying buffer
+ * has been entirely consumed without overflow
+ *
+ * @param[in] ctx - pldm_msgbuf_rw context
+ *
+ * @return 0 if all buffer access were in-bounds and completely consume the
+ * underlying buffer. Otherwise, -EBADMSG if the buffer has not been completely
+ * consumed, or -EOVERFLOW if accesses were attempted beyond the bounds of the
+ * buffer.
+ */
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE
+LIBPLDM_CC_WARN_UNUSED_RESULT
+int pldm_msgbuf_rw_complete_consumed(struct pldm_msgbuf_rw *ctx)
+{
+	return pldm_msgbuf_rw_discard(ctx, pldm_msgbuf_rw_consumed(ctx));
+}
+
+/**
+ * @brief Complete the pldm_msgbuf_ro instance, and check that the underlying buffer
+ * has been entirely consumed without overflow
+ *
+ * @param[in] ctx - pldm_msgbuf_ro context
+ *
+ * @return 0 if all buffer access were in-bounds and completely consume the
+ * underlying buffer. Otherwise, -EBADMSG if the buffer has not been completely
+ * consumed, or -EOVERFLOW if accesses were attempted beyond the bounds of the
+ * buffer.
+ */
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE
+LIBPLDM_CC_WARN_UNUSED_RESULT
+int pldm_msgbuf_ro_complete_consumed(struct pldm_msgbuf_ro *ctx)
+{
+	return pldm_msgbuf_ro_discard(ctx, pldm_msgbuf_ro_consumed(ctx));
+}
+
+/*
+ * 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.
+ */
+
+/**
+ * @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
+ */
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE int
+// NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
+pldm__msgbuf_extract_uint8(struct pldm_msgbuf_ro *ctx, void *dst)
+{
+	if (ctx->remaining >= (intmax_t)sizeof(uint8_t)) {
+		assert(ctx->cursor);
+		memcpy(dst, ctx->cursor, sizeof(uint8_t));
+		ctx->cursor++;
+		ctx->remaining -= sizeof(uint8_t);
+		return 0;
+	}
+
+	if (ctx->remaining > INTMAX_MIN + (intmax_t)sizeof(uint8_t)) {
+		ctx->remaining -= sizeof(uint8_t);
+		return -EOVERFLOW;
+	}
+
+	return pldm__msgbuf_ro_invalidate(ctx);
+}
+
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE int
+// NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
+pldm__msgbuf_extract_int8(struct pldm_msgbuf_ro *ctx, void *dst)
+{
+	if (ctx->remaining >= (intmax_t)sizeof(int8_t)) {
+		assert(ctx->cursor);
+		memcpy(dst, ctx->cursor, sizeof(int8_t));
+		ctx->cursor++;
+		ctx->remaining -= sizeof(int8_t);
+		return 0;
+	}
+
+	if (ctx->remaining > INTMAX_MIN + (intmax_t)sizeof(int8_t)) {
+		ctx->remaining -= sizeof(int8_t);
+		return -EOVERFLOW;
+	}
+
+	return pldm__msgbuf_ro_invalidate(ctx);
+}
+
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE int
+// NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
+pldm__msgbuf_extract_uint16(struct pldm_msgbuf_ro *ctx, void *dst)
+{
+	uint16_t ldst;
+
+	// 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_t)sizeof(ldst)) {
+		assert(ctx->cursor);
+
+		// 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);
+		ctx->remaining -= sizeof(ldst);
+		return 0;
+	}
+
+	if (ctx->remaining > INTMAX_MIN + (intmax_t)sizeof(ldst)) {
+		ctx->remaining -= sizeof(ldst);
+		return -EOVERFLOW;
+	}
+
+	return pldm__msgbuf_ro_invalidate(ctx);
+}
+
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE int
+// NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
+pldm__msgbuf_extract_int16(struct pldm_msgbuf_ro *ctx, void *dst)
+{
+	int16_t ldst;
+
+	static_assert(
+		// NOLINTNEXTLINE(bugprone-sizeof-expression)
+		sizeof(ldst) < INTMAX_MAX,
+		"The following addition may not uphold the runtime assertion");
+
+	if (ctx->remaining >= (intmax_t)sizeof(ldst)) {
+		assert(ctx->cursor);
+		memcpy(&ldst, ctx->cursor, sizeof(ldst));
+		ldst = le16toh(ldst);
+		memcpy(dst, &ldst, sizeof(ldst));
+		ctx->cursor += sizeof(ldst);
+		ctx->remaining -= sizeof(ldst);
+		return 0;
+	}
+
+	if (ctx->remaining > INTMAX_MIN + (intmax_t)sizeof(ldst)) {
+		ctx->remaining -= sizeof(ldst);
+		return -EOVERFLOW;
+	}
+
+	return pldm__msgbuf_ro_invalidate(ctx);
+}
+
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE int
+// NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
+pldm__msgbuf_extract_uint32(struct pldm_msgbuf_ro *ctx, void *dst)
+{
+	uint32_t ldst;
+
+	static_assert(
+		// NOLINTNEXTLINE(bugprone-sizeof-expression)
+		sizeof(ldst) < INTMAX_MAX,
+		"The following addition may not uphold the runtime assertion");
+
+	if (ctx->remaining >= (intmax_t)sizeof(ldst)) {
+		assert(ctx->cursor);
+		memcpy(&ldst, ctx->cursor, sizeof(ldst));
+		ldst = le32toh(ldst);
+		memcpy(dst, &ldst, sizeof(ldst));
+		ctx->cursor += sizeof(ldst);
+		ctx->remaining -= sizeof(ldst);
+		return 0;
+	}
+
+	if (ctx->remaining > INTMAX_MIN + (intmax_t)sizeof(ldst)) {
+		ctx->remaining -= sizeof(ldst);
+		return -EOVERFLOW;
+	}
+
+	return pldm__msgbuf_ro_invalidate(ctx);
+}
+
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE int
+// NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
+pldm__msgbuf_extract_int32(struct pldm_msgbuf_ro *ctx, void *dst)
+{
+	int32_t ldst;
+
+	static_assert(
+		// NOLINTNEXTLINE(bugprone-sizeof-expression)
+		sizeof(ldst) < INTMAX_MAX,
+		"The following addition may not uphold the runtime assertion");
+
+	if (ctx->remaining >= (intmax_t)sizeof(ldst)) {
+		assert(ctx->cursor);
+		memcpy(&ldst, ctx->cursor, sizeof(ldst));
+		ldst = le32toh(ldst);
+		memcpy(dst, &ldst, sizeof(ldst));
+		ctx->cursor += sizeof(ldst);
+		ctx->remaining -= sizeof(ldst);
+		return 0;
+	}
+
+	if (ctx->remaining > INTMAX_MIN + (intmax_t)sizeof(ldst)) {
+		ctx->remaining -= sizeof(ldst);
+		return -EOVERFLOW;
+	}
+
+	return pldm__msgbuf_ro_invalidate(ctx);
+}
+
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE int
+// NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
+pldm__msgbuf_extract_real32(struct pldm_msgbuf_ro *ctx, void *dst)
+{
+	uint32_t ldst;
+
+	static_assert(sizeof(real32_t) == sizeof(ldst),
+		      "Mismatched type sizes for dst and ldst");
+
+	static_assert(
+		// NOLINTNEXTLINE(bugprone-sizeof-expression)
+		sizeof(ldst) < INTMAX_MAX,
+		"The following addition may not uphold the runtime assertion");
+
+	if (ctx->remaining >= (intmax_t)sizeof(ldst)) {
+		assert(ctx->cursor);
+		memcpy(&ldst, ctx->cursor, sizeof(ldst));
+		ldst = le32toh(ldst);
+		memcpy(dst, &ldst, sizeof(ldst));
+		ctx->cursor += sizeof(ldst);
+		ctx->remaining -= sizeof(ldst);
+		return 0;
+	}
+
+	if (ctx->remaining > INTMAX_MIN + (intmax_t)sizeof(ldst)) {
+		ctx->remaining -= sizeof(ldst);
+		return -EOVERFLOW;
+	}
+
+	return pldm__msgbuf_ro_invalidate(ctx);
+}
+
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_WARN_UNUSED_RESULT
+LIBPLDM_CC_ALWAYS_INLINE int
+// NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
+pldm__msgbuf_extract_array_void(struct pldm_msgbuf_ro *ctx, size_t count,
+				void *dst, size_t dst_count)
+{
+	if (count > dst_count) {
+		return -EINVAL;
+	}
+
+	if (!count) {
+		return 0;
+	}
+
+#if INTMAX_MAX < SIZE_MAX
+	if (count > INTMAX_MAX) {
+		return pldm__msgbuf_ro_invalidate(ctx);
+	}
+#endif
+
+	if (ctx->remaining >= (intmax_t)count) {
+		assert(ctx->cursor);
+		memcpy(dst, ctx->cursor, count);
+		ctx->cursor += count;
+		ctx->remaining -= (intmax_t)count;
+		return 0;
+	}
+
+	if (ctx->remaining > INTMAX_MIN + (intmax_t)count) {
+		ctx->remaining -= (intmax_t)count;
+		return -EOVERFLOW;
+	}
+
+	return pldm__msgbuf_ro_invalidate(ctx);
+}
+
+/**
+ * @ref pldm_msgbuf_extract_array
+ */
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_WARN_UNUSED_RESULT
+LIBPLDM_CC_ALWAYS_INLINE int
+pldm_msgbuf_extract_array_char(struct pldm_msgbuf_ro *ctx, size_t count,
+			       char *dst, size_t dst_count)
+{
+	return pldm__msgbuf_extract_array_void(ctx, count, dst, dst_count);
+}
+
+/**
+ * @ref pldm_msgbuf_extract_array
+ */
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_WARN_UNUSED_RESULT
+LIBPLDM_CC_ALWAYS_INLINE int
+pldm_msgbuf_extract_array_uint8(struct pldm_msgbuf_ro *ctx, size_t count,
+				uint8_t *dst, size_t dst_count)
+{
+	return pldm__msgbuf_extract_array_void(ctx, count, dst, dst_count);
+}
+
+/**
+ * Extract an array of data from the msgbuf instance
+ *
+ * @param ctx - The msgbuf instance from which to extract an array of data
+ * @param count - The number of array elements to extract
+ * @param dst - The array object into which elements from @p ctx should be
+                extracted
+ * @param dst_count - The maximum number of elements to place into @p dst
+ *
+ * Note that both @p count and @p dst_count can only be counted by `sizeof` for
+ * arrays where `sizeof(*dst) == 1` holds. Specifically, they count the number
+ * of array elements and _not_ the object size of the array.
+ */
+#define pldm_msgbuf_extract_array(ctx, count, dst, dst_count)                  \
+	_Generic((*(dst)),                                                     \
+		uint8_t: pldm_msgbuf_extract_array_uint8,                      \
+		char: pldm_msgbuf_extract_array_char)(ctx, count, dst,         \
+						      dst_count)
+
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE int
+pldm_msgbuf_insert_uint64(struct pldm_msgbuf_rw *ctx, const uint64_t src)
+{
+	uint64_t val = htole64(src);
+
+	static_assert(
+		// NOLINTNEXTLINE(bugprone-sizeof-expression)
+		sizeof(src) < INTMAX_MAX,
+		"The following addition may not uphold the runtime assertion");
+
+	if (ctx->remaining >= (intmax_t)sizeof(src)) {
+		assert(ctx->cursor);
+		memcpy(ctx->cursor, &val, sizeof(val));
+		ctx->cursor += sizeof(src);
+		ctx->remaining -= sizeof(src);
+		return 0;
+	}
+
+	if (ctx->remaining > INTMAX_MIN + (intmax_t)sizeof(src)) {
+		ctx->remaining -= sizeof(src);
+		return -EOVERFLOW;
+	}
+
+	return pldm__msgbuf_rw_invalidate(ctx);
+}
+
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE int
+pldm_msgbuf_insert_uint32(struct pldm_msgbuf_rw *ctx, const uint32_t src)
+{
+	uint32_t val = htole32(src);
+
+	static_assert(
+		// NOLINTNEXTLINE(bugprone-sizeof-expression)
+		sizeof(src) < INTMAX_MAX,
+		"The following addition may not uphold the runtime assertion");
+
+	if (ctx->remaining >= (intmax_t)sizeof(src)) {
+		assert(ctx->cursor);
+		memcpy(ctx->cursor, &val, sizeof(val));
+		ctx->cursor += sizeof(src);
+		ctx->remaining -= sizeof(src);
+		return 0;
+	}
+
+	if (ctx->remaining > INTMAX_MIN + (intmax_t)sizeof(src)) {
+		ctx->remaining -= sizeof(src);
+		return -EOVERFLOW;
+	}
+
+	return pldm__msgbuf_rw_invalidate(ctx);
+}
+
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE int
+pldm_msgbuf_insert_uint16(struct pldm_msgbuf_rw *ctx, const uint16_t src)
+{
+	uint16_t val = htole16(src);
+
+	static_assert(
+		// NOLINTNEXTLINE(bugprone-sizeof-expression)
+		sizeof(src) < INTMAX_MAX,
+		"The following addition may not uphold the runtime assertion");
+
+	if (ctx->remaining >= (intmax_t)sizeof(src)) {
+		assert(ctx->cursor);
+		memcpy(ctx->cursor, &val, sizeof(val));
+		ctx->cursor += sizeof(src);
+		ctx->remaining -= sizeof(src);
+		return 0;
+	}
+
+	if (ctx->remaining > INTMAX_MIN + (intmax_t)sizeof(src)) {
+		ctx->remaining -= sizeof(src);
+		return -EOVERFLOW;
+	}
+
+	return pldm__msgbuf_rw_invalidate(ctx);
+}
+
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE int
+pldm_msgbuf_insert_uint8(struct pldm_msgbuf_rw *ctx, const uint8_t src)
+{
+	static_assert(
+		// NOLINTNEXTLINE(bugprone-sizeof-expression)
+		sizeof(src) < INTMAX_MAX,
+		"The following addition may not uphold the runtime assertion");
+
+	if (ctx->remaining >= (intmax_t)sizeof(src)) {
+		assert(ctx->cursor);
+		memcpy(ctx->cursor, &src, sizeof(src));
+		ctx->cursor += sizeof(src);
+		ctx->remaining -= sizeof(src);
+		return 0;
+	}
+
+	if (ctx->remaining > INTMAX_MIN + (intmax_t)sizeof(src)) {
+		ctx->remaining -= sizeof(src);
+		return -EOVERFLOW;
+	}
+
+	return pldm__msgbuf_rw_invalidate(ctx);
+}
+
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE int
+pldm_msgbuf_insert_int32(struct pldm_msgbuf_rw *ctx, const int32_t src)
+{
+	int32_t val = htole32(src);
+
+	static_assert(
+		// NOLINTNEXTLINE(bugprone-sizeof-expression)
+		sizeof(src) < INTMAX_MAX,
+		"The following addition may not uphold the runtime assertion");
+
+	if (ctx->remaining >= (intmax_t)sizeof(src)) {
+		assert(ctx->cursor);
+		memcpy(ctx->cursor, &val, sizeof(val));
+		ctx->cursor += sizeof(src);
+		ctx->remaining -= sizeof(src);
+		return 0;
+	}
+
+	if (ctx->remaining > INTMAX_MIN + (intmax_t)sizeof(src)) {
+		ctx->remaining -= sizeof(src);
+		return -EOVERFLOW;
+	}
+
+	return pldm__msgbuf_rw_invalidate(ctx);
+}
+
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE int
+pldm_msgbuf_insert_int16(struct pldm_msgbuf_rw *ctx, const int16_t src)
+{
+	int16_t val = htole16(src);
+
+	static_assert(
+		// NOLINTNEXTLINE(bugprone-sizeof-expression)
+		sizeof(src) < INTMAX_MAX,
+		"The following addition may not uphold the runtime assertion");
+
+	if (ctx->remaining >= (intmax_t)sizeof(src)) {
+		assert(ctx->cursor);
+		memcpy(ctx->cursor, &val, sizeof(val));
+		ctx->cursor += sizeof(src);
+		ctx->remaining -= sizeof(src);
+		return 0;
+	}
+
+	if (ctx->remaining > INTMAX_MIN + (intmax_t)sizeof(src)) {
+		ctx->remaining -= sizeof(src);
+		return -EOVERFLOW;
+	}
+
+	return pldm__msgbuf_rw_invalidate(ctx);
+}
+
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE int pldm_msgbuf_insert_int8(struct pldm_msgbuf_rw *ctx,
+						     const int8_t src)
+{
+	static_assert(
+		// NOLINTNEXTLINE(bugprone-sizeof-expression)
+		sizeof(src) < INTMAX_MAX,
+		"The following addition may not uphold the runtime assertion");
+
+	if (ctx->remaining >= (intmax_t)sizeof(src)) {
+		assert(ctx->cursor);
+		memcpy(ctx->cursor, &src, sizeof(src));
+		ctx->cursor += sizeof(src);
+		ctx->remaining -= sizeof(src);
+		return 0;
+	}
+
+	if (ctx->remaining > INTMAX_MIN + (intmax_t)sizeof(src)) {
+		ctx->remaining -= sizeof(src);
+		return -EOVERFLOW;
+	}
+
+	return pldm__msgbuf_rw_invalidate(ctx);
+}
+
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_WARN_UNUSED_RESULT
+LIBPLDM_CC_ALWAYS_INLINE int
+// NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
+pldm__msgbuf_insert_array_void(struct pldm_msgbuf_rw *ctx, size_t count,
+			       const void *src, size_t src_count)
+{
+	if (count > src_count) {
+		return -EINVAL;
+	}
+
+	if (!count) {
+		return 0;
+	}
+
+#if INTMAX_MAX < SIZE_MAX
+	if (count > INTMAX_MAX) {
+		return pldm__msgbuf_rw_invalidate(ctx);
+	}
+#endif
+
+	if (ctx->remaining >= (intmax_t)count) {
+		assert(ctx->cursor);
+		memcpy(ctx->cursor, src, count);
+		ctx->cursor += count;
+		ctx->remaining -= (intmax_t)count;
+		return 0;
+	}
+
+	if (ctx->remaining > INTMAX_MIN + (intmax_t)count) {
+		ctx->remaining -= (intmax_t)count;
+		return -EOVERFLOW;
+	}
+
+	return pldm__msgbuf_rw_invalidate(ctx);
+}
+
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_WARN_UNUSED_RESULT
+LIBPLDM_CC_ALWAYS_INLINE int
+pldm_msgbuf_insert_array_char(struct pldm_msgbuf_rw *ctx, size_t count,
+			      const char *src, size_t src_count)
+{
+	return pldm__msgbuf_insert_array_void(ctx, count, src, src_count);
+}
+
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_WARN_UNUSED_RESULT
+LIBPLDM_CC_ALWAYS_INLINE int
+pldm_msgbuf_insert_array_uint8(struct pldm_msgbuf_rw *ctx, size_t count,
+			       const uint8_t *src, size_t src_count)
+{
+	return pldm__msgbuf_insert_array_void(ctx, count, src, src_count);
+}
+
+LIBPLDM_CC_NONNULL_ARGS(1)
+LIBPLDM_CC_ALWAYS_INLINE int
+// NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
+pldm__msgbuf_span_required(const uint8_t **buf, intmax_t *remaining,
+			   size_t required, const void **cursor)
+{
+#if INTMAX_MAX < SIZE_MAX
+	if (required > INTMAX_MAX) {
+		return pldm__msgbuf_set_invalid(remaining);
+	}
+#endif
+
+	if (*remaining >= (intmax_t)required) {
+		assert(*buf);
+		if (cursor) {
+			*cursor = *buf;
+		}
+		*buf += required;
+		*remaining -= (intmax_t)required;
+		return 0;
+	}
+
+	if (*remaining > INTMAX_MIN + (intmax_t)required) {
+		*remaining -= (intmax_t)required;
+		return -EOVERFLOW;
+	}
+
+	return pldm__msgbuf_set_invalid(remaining);
+}
+
+LIBPLDM_CC_NONNULL_ARGS(1)
+LIBPLDM_CC_ALWAYS_INLINE int
+pldm_msgbuf_ro_span_required(struct pldm_msgbuf_ro *ctx, size_t required,
+			     const void **cursor)
+{
+	return pldm__msgbuf_span_required(&ctx->cursor, &ctx->remaining,
+					  required, cursor);
+}
+
+LIBPLDM_CC_NONNULL_ARGS(1)
+LIBPLDM_CC_ALWAYS_INLINE int
+pldm_msgbuf_rw_span_required(struct pldm_msgbuf_rw *ctx, size_t required,
+			     void **cursor)
+{
+	return pldm__msgbuf_span_required((const uint8_t **)&ctx->cursor,
+					  &ctx->remaining, required,
+					  (const void **)cursor);
+}
+
+LIBPLDM_CC_NONNULL_ARGS(1)
+LIBPLDM_CC_ALWAYS_INLINE int
+// NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
+pldm__msgbuf_span_string_ascii(const uint8_t **buf, intmax_t *remaining,
+			       const void **cursor, size_t *length)
+{
+	intmax_t measured;
+
+	if (*remaining < 0) {
+		return pldm__msgbuf_set_invalid(remaining);
+	}
+	assert(*buf);
+
+	measured = (intmax_t)strnlen((const char *)*buf, *remaining);
+	if (measured == *remaining) {
+		return pldm__msgbuf_set_invalid(remaining);
+	}
+
+	/* Include the NUL terminator in the span length, as spans are opaque */
+	measured++;
+
+	if (*remaining >= measured) {
+		assert(*buf);
+		if (cursor) {
+			*cursor = *buf;
+		}
+
+		*buf += measured;
+
+		if (length) {
+			*length = measured;
+		}
+
+		*remaining -= measured;
+		return 0;
+	}
+
+	if (*remaining > INTMAX_MIN + measured) {
+		*remaining -= measured;
+		return -EOVERFLOW;
+	}
+
+	return pldm__msgbuf_set_invalid(remaining);
+}
+
+LIBPLDM_CC_NONNULL_ARGS(1)
+LIBPLDM_CC_ALWAYS_INLINE int
+pldm_msgbuf_rw_span_string_ascii(struct pldm_msgbuf_rw *ctx, void **cursor,
+				 size_t *length)
+{
+	return pldm__msgbuf_span_string_ascii((const uint8_t **)&ctx->cursor,
+					      &ctx->remaining,
+					      (const void **)cursor, length);
+}
+
+LIBPLDM_CC_NONNULL_ARGS(1)
+LIBPLDM_CC_ALWAYS_INLINE int
+pldm_msgbuf_ro_span_string_ascii(struct pldm_msgbuf_ro *ctx,
+				 const void **cursor, size_t *length)
+{
+	return pldm__msgbuf_span_string_ascii(&ctx->cursor, &ctx->remaining,
+					      cursor, length);
+}
+
+LIBPLDM_CC_NONNULL_ARGS(1)
+LIBPLDM_CC_ALWAYS_INLINE
+// NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
+int pldm__msgbuf_span_string_utf16(const uint8_t **buf, intmax_t *remaining,
+				   const void **cursor, size_t *length)
+{
+	static const char16_t term = 0;
+	ptrdiff_t measured;
+	const void *end;
+
+	if (*remaining < 0) {
+		return pldm__msgbuf_set_invalid(remaining);
+	}
+	assert(*buf);
+
+	/*
+	 * Avoid tripping up on UTF16-LE: We may have consecutive NUL _bytes_ that do
+	 * not form a UTF16 NUL _code-point_ due to alignment with respect to the
+	 * start of the string
+	 */
+	end = *buf;
+	do {
+		if (end != *buf) {
+			/*
+			 * If we've looped we've found a relatively-unaligned NUL code-point.
+			 * Scan again from a relatively-aligned start point.
+			 */
+			end = (char *)end + 1;
+		}
+		measured = (char *)end - (char *)*buf;
+		end = memmem(end, *remaining - measured, &term, sizeof(term));
+	} while (end && ((uintptr_t)end & 1) != ((uintptr_t)*buf & 1));
+
+	if (!end) {
+		/*
+		 * Optimistically, the last required pattern byte was one beyond the end of
+		 * the buffer. Setting ctx->remaining negative ensures the
+		 * `pldm_msgbuf_complete*()` APIs also return an error.
+		 */
+		return pldm__msgbuf_set_invalid(remaining);
+	}
+
+	end = (char *)end + sizeof(char16_t);
+	measured = (char *)end - (char *)*buf;
+
+#if INTMAX_MAX < PTRDIFF_MAX
+	if (measured >= INTMAX_MAX) {
+		return pldm__msgbuf_set_invalid(remaining);
+	}
+#endif
+
+	if (*remaining >= (intmax_t)measured) {
+		assert(*buf);
+		if (cursor) {
+			*cursor = *buf;
+		}
+
+		*buf += measured;
+
+		if (length) {
+			*length = (size_t)measured;
+		}
+
+		*remaining -= (intmax_t)measured;
+		return 0;
+	}
+
+	if (*remaining > INTMAX_MIN + (intmax_t)measured) {
+		*remaining -= (intmax_t)measured;
+		return -EOVERFLOW;
+	}
+
+	return pldm__msgbuf_set_invalid(remaining);
+}
+
+LIBPLDM_CC_NONNULL_ARGS(1)
+LIBPLDM_CC_ALWAYS_INLINE int
+pldm_msgbuf_ro_span_string_utf16(struct pldm_msgbuf_ro *ctx,
+				 const void **cursor, size_t *length)
+{
+	return pldm__msgbuf_span_string_utf16(&ctx->cursor, &ctx->remaining,
+					      cursor, length);
+}
+
+LIBPLDM_CC_NONNULL_ARGS(1)
+LIBPLDM_CC_ALWAYS_INLINE int
+pldm_msgbuf_rw_span_string_utf16(struct pldm_msgbuf_rw *ctx, void **cursor,
+				 size_t *length)
+{
+	return pldm__msgbuf_span_string_utf16((const uint8_t **)&ctx->cursor,
+					      &ctx->remaining,
+					      (const void **)cursor, length);
+}
+
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE int
+// NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
+pldm__msgbuf_span_remaining(const uint8_t **buf, intmax_t *remaining,
+			    const void **cursor, size_t *len)
+{
+	if (*remaining < 0) {
+		return -EOVERFLOW;
+	}
+
+	assert(*buf);
+	*cursor = *buf;
+	*buf += *remaining;
+	*len = *remaining;
+	*remaining = 0;
+
+	return 0;
+}
+
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE int
+pldm_msgbuf_rw_span_remaining(struct pldm_msgbuf_rw *ctx, void **cursor,
+			      size_t *len)
+{
+	return pldm__msgbuf_span_remaining((const uint8_t **)&ctx->cursor,
+					   &ctx->remaining,
+					   (const void **)cursor, len);
+}
+
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE int
+pldm_msgbuf_ro_span_remaining(struct pldm_msgbuf_ro *ctx, const void **cursor,
+			      size_t *len)
+{
+	return pldm__msgbuf_span_remaining(&ctx->cursor, &ctx->remaining,
+					   cursor, len);
+}
+
+LIBPLDM_CC_NONNULL_ARGS(1)
+LIBPLDM_CC_ALWAYS_INLINE
+// NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
+int pldm__msgbuf_span_until(const uint8_t **buf, intmax_t *remaining,
+			    size_t trailer, const void **cursor, size_t *length)
+{
+#if INTMAX_MAX < SIZE_MAX
+	if (trailer > INTMAX_MAX) {
+		return pldm__msgbuf_set_invalid(remaining);
+	}
+#endif
+
+	if (*remaining >= (intmax_t)trailer) {
+		ptrdiff_t delta;
+
+		assert(*buf);
+
+		delta = *remaining - (intmax_t)trailer;
+		if (cursor) {
+			*cursor = *buf;
+		}
+		*buf += delta;
+		if (length) {
+			*length = delta;
+		}
+		*remaining = (intmax_t)trailer;
+		return 0;
+	}
+
+	if (*remaining > INTMAX_MIN + (intmax_t)trailer) {
+		*remaining = INTMAX_MIN + (intmax_t)trailer;
+		return -EOVERFLOW;
+	}
+
+	return pldm__msgbuf_set_invalid(remaining);
+}
+
+LIBPLDM_CC_NONNULL_ARGS(1)
+LIBPLDM_CC_ALWAYS_INLINE
+int pldm_msgbuf_ro_span_until(struct pldm_msgbuf_ro *ctx, size_t trailer,
+			      const void **cursor, size_t *length)
+{
+	return pldm__msgbuf_span_until(&ctx->cursor, &ctx->remaining, trailer,
+				       cursor, length);
+}
+
+LIBPLDM_CC_NONNULL_ARGS(1)
+LIBPLDM_CC_ALWAYS_INLINE
+int pldm_msgbuf_rw_span_until(struct pldm_msgbuf_rw *ctx, size_t trailer,
+			      void **cursor, size_t *length)
+{
+	return pldm__msgbuf_span_until((const uint8_t **)&ctx->cursor,
+				       &ctx->remaining, trailer,
+				       (const void **)cursor, length);
+}
+
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE int
+pldm_msgbuf_peek_remaining(struct pldm_msgbuf_rw *ctx, void **cursor,
+			   size_t *len)
+{
+	if (ctx->remaining < 0) {
+		return -EOVERFLOW;
+	}
+
+	assert(ctx->cursor);
+	*cursor = ctx->cursor;
+	*len = ctx->remaining;
+
+	return 0;
+}
+
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE int pldm_msgbuf_skip(struct pldm_msgbuf_rw *ctx,
+					      size_t count)
+{
+#if INTMAX_MAX < SIZE_MAX
+	if (count > INTMAX_MAX) {
+		return pldm__msgbuf_rw_invalidate(ctx);
+	}
+#endif
+
+	if (ctx->remaining >= (intmax_t)count) {
+		assert(ctx->cursor);
+		ctx->cursor += count;
+		ctx->remaining -= (intmax_t)count;
+		return 0;
+	}
+
+	if (ctx->remaining > INTMAX_MIN + (intmax_t)count) {
+		ctx->remaining -= (intmax_t)count;
+		return -EOVERFLOW;
+	}
+
+	return pldm__msgbuf_rw_invalidate(ctx);
+}
+
+/**
+ * @brief Complete the pldm_msgbuf instance and return the number of bytes
+ * consumed.
+ *
+ * @param ctx - The msgbuf.
+ * @param orig_len - The original size of the msgbuf, the `len` argument passed to
+ * 		pldm_msgbuf_init_errno().
+ * @param ret_used_len - The number of bytes that have been used from the msgbuf instance.
+ *
+ * This can be called after a number of pldm_msgbuf_insert...() calls to
+ * determine the total size that was written.
+ *
+ * @return 0 on success, -EOVERFLOW if an implausible orig_len was provided or
+ * an out-of-bounds access occurred.
+ */
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE
+LIBPLDM_CC_WARN_UNUSED_RESULT
+int pldm_msgbuf_complete_used(struct pldm_msgbuf_rw *ctx, size_t orig_len,
+			      size_t *ret_used_len)
+{
+	int rc;
+
+	ctx->cursor = NULL;
+	rc = pldm_msgbuf_rw_validate(ctx);
+	if (rc) {
+		pldm__msgbuf_rw_invalidate(ctx);
+		return rc;
+	}
+
+	if ((size_t)ctx->remaining > orig_len) {
+		/* Caller passed incorrect orig_len */
+		return pldm__msgbuf_rw_invalidate(ctx);
+	}
+
+	*ret_used_len = orig_len - ctx->remaining;
+	pldm__msgbuf_rw_invalidate(ctx);
+	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)
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE int
+// NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
+pldm__msgbuf_copy(struct pldm_msgbuf_rw *dst, struct pldm_msgbuf_ro *src,
+		  size_t size, const char *description LIBPLDM_CC_UNUSED)
+{
+#if INTMAX_MAX < SIZE_MAX
+	if (size > INTMAX_MAX) {
+		pldm__msgbuf_ro_invalidate(src);
+		pldm__msgbuf_rw_invalidate(dst);
+		return -EOVERFLOW;
+	}
+#endif
+
+	if (src->remaining >= (intmax_t)size &&
+	    dst->remaining >= (intmax_t)size) {
+		assert(src->cursor && dst->cursor);
+		memcpy(dst->cursor, src->cursor, size);
+		src->cursor += size;
+		src->remaining -= (intmax_t)size;
+		dst->cursor += size;
+		dst->remaining -= (intmax_t)size;
+		return 0;
+	}
+
+	if (src->remaining > INTMAX_MIN + (intmax_t)size) {
+		src->remaining -= (intmax_t)size;
+	} else {
+		pldm__msgbuf_ro_invalidate(src);
+	}
+
+	if (dst->remaining > INTMAX_MIN + (intmax_t)size) {
+		dst->remaining -= (intmax_t)size;
+	} else {
+		pldm__msgbuf_rw_invalidate(dst);
+	}
+
+	return -EOVERFLOW;
+}
+
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_WARN_UNUSED_RESULT
+LIBPLDM_CC_ALWAYS_INLINE int
+pldm_msgbuf_copy_string_ascii(struct pldm_msgbuf_rw *dst,
+			      struct pldm_msgbuf_ro *src)
+{
+	const void *ascii = NULL;
+	size_t len = 0;
+	int rc;
+
+	rc = pldm_msgbuf_ro_span_string_ascii(src, &ascii, &len);
+	if (rc < 0) {
+		return rc;
+	}
+
+	return pldm__msgbuf_insert_array_void(dst, len, ascii, len);
+}
+
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_WARN_UNUSED_RESULT
+LIBPLDM_CC_ALWAYS_INLINE int
+pldm_msgbuf_copy_string_utf16(struct pldm_msgbuf_rw *dst,
+			      struct pldm_msgbuf_ro *src)
+{
+	const void *utf16 = NULL;
+	size_t len = 0;
+	int rc;
+
+	rc = pldm_msgbuf_ro_span_string_utf16(src, &utf16, &len);
+	if (rc < 0) {
+		return rc;
+	}
+
+	return pldm__msgbuf_insert_array_void(dst, len, utf16, len);
+}
+
+/**
+ * @brief pldm_msgbuf uint8_t extractor for a size_t
+ *
+ * @param[in,out] ctx - pldm_msgbuf context for extractor
+ * @param[out] dst - destination of extracted value
+ *
+ * @return 0 if buffer accesses were in-bounds,
+ * -EINVAL if dst pointer is invalid,
+ * -EOVERFLOW is the buffer was out of bound.
+ */
+#define pldm_msgbuf_extract_uint8_to_size(ctx, dst)                            \
+	pldm__msgbuf_extract_uint8_to_size(ctx, &(dst))
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE int
+// NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
+pldm__msgbuf_extract_uint8_to_size(struct pldm_msgbuf_ro *ctx, size_t *dst)
+{
+	uint8_t value = 0;
+	int rc;
+
+	rc = pldm__msgbuf_extract_uint8(ctx, &value);
+	if (rc) {
+		return rc;
+	}
+
+	static_assert(SIZE_MAX >= UINT8_MAX, "Invalid promotion");
+
+	*dst = value;
+	return 0;
+}
+
+/**
+ * @brief pldm_msgbuf uint16_t extractor for a size_t
+ *
+ * @param[in,out] ctx - pldm_msgbuf context for extractor
+ * @param[out] dst - destination of extracted value
+ *
+ * @return 0 if buffer accesses were in-bounds,
+ * -EINVAL if dst pointer is invalid,
+ * -EOVERFLOW is the buffer was out of bound.
+ */
+#define pldm_msgbuf_extract_uint16_to_size(ctx, dst)                           \
+	pldm__msgbuf_extract_uint16_to_size(ctx, &(dst))
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE int
+// NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
+pldm__msgbuf_extract_uint16_to_size(struct pldm_msgbuf_ro *ctx, size_t *dst)
+{
+	uint16_t value = 0;
+	int rc;
+
+	rc = pldm__msgbuf_extract_uint16(ctx, &value);
+	if (rc) {
+		return rc;
+	}
+
+	static_assert(SIZE_MAX >= UINT16_MAX, "Invalid promotion");
+
+	*dst = value;
+	return 0;
+}
+
+/**
+ * @brief pldm_msgbuf uint32_t extractor for a size_t
+ *
+ * @param[in,out] ctx - pldm_msgbuf context for extractor
+ * @param[out] dst - destination of extracted value
+ *
+ * @return 0 if buffer accesses were in-bounds,
+ * -EINVAL if dst pointer is invalid,
+ * -EOVERFLOW is the buffer was out of bound.
+ */
+#define pldm_msgbuf_extract_uint32_to_size(ctx, dst)                           \
+	pldm__msgbuf_extract_uint32_to_size(ctx, &(dst))
+LIBPLDM_CC_NONNULL
+LIBPLDM_CC_ALWAYS_INLINE int
+// NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
+pldm__msgbuf_extract_uint32_to_size(struct pldm_msgbuf_ro *ctx, size_t *dst)
+{
+	uint32_t value = 0;
+	int rc;
+
+	rc = pldm__msgbuf_extract_uint32(ctx, &value);
+	if (rc) {
+		return rc;
+	}
+
+	static_assert(SIZE_MAX >= UINT32_MAX, "Invalid promotion");
+
+	*dst = value;
+	return 0;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/msgbuf/platform.h b/src/msgbuf/platform.h
index 897c199..695ab1e 100644
--- a/src/msgbuf/platform.h
+++ b/src/msgbuf/platform.h
@@ -9,7 +9,7 @@
 
 LIBPLDM_CC_NONNULL
 LIBPLDM_CC_ALWAYS_INLINE int
-pldm_msgbuf_extract_value_pdr_hdr(struct pldm_msgbuf *ctx,
+pldm_msgbuf_extract_value_pdr_hdr(struct pldm_msgbuf_ro *ctx,
 				  struct pldm_value_pdr_hdr *hdr, size_t lower,
 				  size_t upper)
 {
@@ -36,7 +36,7 @@
 }
 
 LIBPLDM_CC_ALWAYS_INLINE int
-pldm_msgbuf_extract_sensor_data(struct pldm_msgbuf *ctx,
+pldm_msgbuf_extract_sensor_data(struct pldm_msgbuf_ro *ctx,
 				enum pldm_sensor_readings_data_type tag,
 				union_sensor_data_size *dst)
 {
@@ -64,7 +64,7 @@
  * above
  */
 LIBPLDM_CC_ALWAYS_INLINE int
-pldm_msgbuf_extract_sensor_value(struct pldm_msgbuf *ctx,
+pldm_msgbuf_extract_sensor_value(struct pldm_msgbuf_ro *ctx,
 				 enum pldm_sensor_readings_data_type tag,
 				 void *val)
 {
@@ -91,7 +91,7 @@
 				      pldm__msgbuf_extract_range_field_format, \
 				      dst, ctx, tag, (void *)&(dst))
 LIBPLDM_CC_ALWAYS_INLINE int pldm__msgbuf_extract_range_field_format(
-	struct pldm_msgbuf *ctx, enum pldm_range_field_format tag, void *rff)
+	struct pldm_msgbuf_ro *ctx, enum pldm_range_field_format tag, void *rff)
 {
 	switch (tag) {
 	case PLDM_RANGE_FIELD_FORMAT_UINT8:
@@ -129,7 +129,7 @@
 
 /* This API is bad, but it's because the caller's APIs are also bad */
 LIBPLDM_CC_ALWAYS_INLINE int
-pldm_msgbuf_extract_effecter_value(struct pldm_msgbuf *ctx,
+pldm_msgbuf_extract_effecter_value(struct pldm_msgbuf_ro *ctx,
 				   enum pldm_effecter_data_size tag, void *dst)
 {
 	switch (tag) {
@@ -155,7 +155,7 @@
 				      pldm__msgbuf_extract_range_field_format, \
 				      dst, ctx, tag, (void *)&(dst))
 LIBPLDM_CC_ALWAYS_INLINE int
-pldm__msgbuf_extract_effecter_data(struct pldm_msgbuf *ctx,
+pldm__msgbuf_extract_effecter_data(struct pldm_msgbuf_ro *ctx,
 				   enum pldm_effecter_data_size tag, void *ed)
 {
 	switch (tag) {
@@ -192,8 +192,10 @@
 #include <type_traits>
 
 template <typename T>
-static inline int pldm_msgbuf_typecheck_range_field_format(
-	struct pldm_msgbuf *ctx, enum pldm_range_field_format tag, void *_rff)
+static inline int
+pldm_msgbuf_typecheck_range_field_format(struct pldm_msgbuf_ro *ctx,
+					 enum pldm_range_field_format tag,
+					 void *_rff)
 {
 	static_assert(std::is_same<union_range_field_format, T>::value);
 	return pldm__msgbuf_extract_range_field_format(ctx, tag, _rff);