dsp: firmware_update: Iterators for downstream device descriptors

Provide an ergonomic and safe means to iterate through downstream
devices and their descriptors while avoiding the need to allocate. The
strategy leaves the library user to make their own choices about how the
data is handled.

The user-facing portion of the change takes the form of two new macros,
to be nested inside one another:

```
foreach_pldm_downstream_device(...) {
    foreach_pldm_downstream_device_descriptor(...) {
        // Do something with the device-specific descriptor
    }
}
```

Examples uses are provided in the documentation and in changes to the
test suite.

Change-Id: I6e79454b94868da73f318635bcaae57cd51fbf97
Signed-off-by: Andrew Jeffery <andrew@codeconstruct.com.au>
diff --git a/include/libpldm/api.h b/include/libpldm/api.h
new file mode 100644
index 0000000..c5e9931
--- /dev/null
+++ b/include/libpldm/api.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */
+
+#ifndef LIBPLDM_API_H
+#define LIBPLDM_API_H
+
+#include <libpldm/compiler.h>
+
+#define LIBPLDM_ITERATOR LIBPLDM_CC_NONNULL LIBPLDM_CC_ALWAYS_INLINE
+
+#endif
diff --git a/include/libpldm/compiler.h b/include/libpldm/compiler.h
index f8d7dc7..c70bcab 100644
--- a/include/libpldm/compiler.h
+++ b/include/libpldm/compiler.h
@@ -4,13 +4,31 @@
 #define LIBPLDM_COMPILER_H
 
 #if defined __has_attribute
+
+#if __has_attribute(always_inline)
+#define LIBPLDM_CC_ALWAYS_INLINE __attribute__((always_inline)) static inline
+#endif
+
 #if __has_attribute(counted_by)
 #define LIBPLDM_CC_COUNTED_BY(x) __attribute__((counted_by(x)))
 #endif
+
+#if __has_attribute(nonnull)
+#define LIBPLDM_CC_NONNULL __attribute__((nonnull))
+#endif
+
+#endif
+
+#ifndef LIBPLDM_CC_ALWAYS_INLINE
+#define LIBPLDM_CC_ALWAYS_INLINE static inline
 #endif
 
 #ifndef LIBPLDM_CC_COUNTED_BY
 #define LIBPLDM_CC_COUNTED_BY(x)
 #endif
 
+#ifndef LIBPLDM_CC_NONNULL
+#define LIBPLDM_CC_NONNULL
+#endif
+
 #endif
diff --git a/include/libpldm/firmware_update.h b/include/libpldm/firmware_update.h
index 27e626d..3fdd222 100644
--- a/include/libpldm/firmware_update.h
+++ b/include/libpldm/firmware_update.h
@@ -6,13 +6,14 @@
 extern "C" {
 #endif
 
+#include <libpldm/api.h>
 #include <libpldm/base.h>
 #include <libpldm/pldm_types.h>
+#include <libpldm/utils.h>
 
 #include <stdbool.h>
 #include <stddef.h>
 #include <stdint.h>
-struct variable_field;
 
 #define PLDM_FWUP_COMPONENT_BITMAP_MULTIPLE		 8
 #define PLDM_FWUP_INVALID_COMPONENT_COMPARISON_TIMESTAMP 0xffffffff
@@ -570,6 +571,175 @@
 };
 #define PLDM_DOWNSTREAM_DEVICE_BYTES 3
 
+struct pldm_downstream_device_iter {
+	struct variable_field field;
+	size_t devs;
+};
+
+LIBPLDM_ITERATOR
+bool pldm_downstream_device_iter_end(
+	const struct pldm_downstream_device_iter *iter)
+{
+	return !iter->devs;
+}
+
+LIBPLDM_ITERATOR
+bool pldm_downstream_device_iter_next(struct pldm_downstream_device_iter *iter)
+{
+	if (!iter->devs) {
+		return false;
+	}
+
+	iter->devs--;
+	return true;
+}
+
+int decode_pldm_downstream_device_from_iter(
+	struct pldm_downstream_device_iter *iter,
+	struct pldm_downstream_device *dev);
+
+/** @brief Iterate downstream devices in QueryDownstreamIdentifiers response
+ *
+ * @param devs The @ref "struct pldm_downstream_device_iter" lvalue used as the
+ *                  out-value from the corresponding call to @ref
+ *                  decode_query_downstream_identifiers_resp
+ * @param dev The @ref "struct pldm_downstream_device" lvalue into which the
+ *            next device instance should be decoded.
+ * @param rc An lvalue of type int into which the return code from the decoding
+ *           will be placed.
+ *
+ * Example use of the macro is as follows:
+ *
+ * @code
+ * struct pldm_query_downstream_identifiers_resp resp;
+ * struct pldm_downstream_device_iter devs;
+ * struct pldm_downstream_device dev;
+ * int rc;
+ *
+ * rc = decode_query_downstream_identifiers_resp(..., &resp, &devs);
+ * if (rc) {
+ *     // Handle any error from decoding fixed-portion of response
+ * }
+ *
+ * foreach_pldm_downstream_device(devs, dev, rc) {
+ *     // Do something with each decoded device placed in `dev`
+ * }
+ *
+ * if (rc) {
+ *     // Handle any decoding error while iterating variable-length set of
+ *     // devices
+ * }
+ * @endcode
+ */
+#define foreach_pldm_downstream_device(devs, dev, rc)                          \
+	for ((rc) = 0; (!pldm_downstream_device_iter_end(&(devs)) &&           \
+			!((rc) = decode_pldm_downstream_device_from_iter(      \
+				  &(devs), &(dev))));                          \
+	     pldm_downstream_device_iter_next(&(devs)))
+
+/** @struct pldm_descriptor
+ *
+ * Structure representing a type-length-value descriptor as defined in Table 7 -
+ * Descriptor Definition.
+ *
+ * Member values are always host-endian. When decoding messages, the
+ * descriptor_data member points into the message buffer.
+ */
+struct pldm_descriptor {
+	uint16_t descriptor_type;
+	uint16_t descriptor_length;
+	const void *descriptor_data;
+};
+
+struct pldm_descriptor_iter {
+	struct variable_field *field;
+	size_t count;
+};
+
+LIBPLDM_ITERATOR
+struct pldm_descriptor_iter pldm_downstream_device_descriptor_iter_init(
+	struct pldm_downstream_device_iter *devs,
+	const struct pldm_downstream_device *dev)
+{
+	struct pldm_descriptor_iter iter = { &devs->field,
+					     dev->downstream_descriptor_count };
+	return iter;
+}
+
+LIBPLDM_ITERATOR
+bool pldm_descriptor_iter_end(const struct pldm_descriptor_iter *iter)
+{
+	return !iter->count;
+}
+
+LIBPLDM_ITERATOR
+bool pldm_descriptor_iter_next(struct pldm_descriptor_iter *iter)
+{
+	if (!iter->count) {
+		return false;
+	}
+
+	iter->count--;
+	return true;
+}
+
+int decode_pldm_descriptor_from_iter(struct pldm_descriptor_iter *iter,
+				     struct pldm_descriptor *desc);
+
+/** @brief Iterate a downstream device's descriptors in a
+ *         QueryDownstreamIdentifiers response
+ *
+ * @param devs The @ref "struct pldm_downstream_device_iter" lvalue used as the
+ *                  out-value from the corresponding call to @ref
+ *                  decode_query_downstream_identifiers_resp
+ * @param dev The @ref "struct pldm_downstream_device" lvalue over whose
+ *            descriptors to iterate
+ * @param desc The @ref "struct pldm_descriptor" lvalue into which the next
+ *             descriptor instance should be decoded
+ * @param rc An lvalue of type int into which the return code from the decoding
+ *           will be placed
+ *
+ * Example use of the macro is as follows:
+ *
+ * @code
+ * struct pldm_query_downstream_identifiers_resp resp;
+ * struct pldm_downstream_device_iter devs;
+ * struct pldm_downstream_device dev;
+ * int rc;
+ *
+ * rc = decode_query_downstream_identifiers_resp(..., &resp, &devs);
+ * if (rc) {
+ *     // Handle any error from decoding fixed-portion of response
+ * }
+ *
+ * foreach_pldm_downstream_device(devs, dev, rc) {
+ *     struct pldm_descriptor desc;
+ *
+ *     foreach_pldm_downstream_device_descriptor(devs, dev, desc, rc) {
+ *         // Do something with each decoded descriptor placed in `desc`
+ *     }
+ *
+ *     if (rc) {
+ *         // Handle any decoding error while iterating on the variable-length
+ *         // set of descriptors
+ *     }
+ * }
+ *
+ * if (rc) {
+ *     // Handle any decoding error while iterating variable-length set of
+ *     // devices
+ * }
+ * @endcode
+ */
+#define foreach_pldm_downstream_device_descriptor(devs, dev, desc, rc)         \
+	for (struct pldm_descriptor_iter desc##_iter =                         \
+		     ((rc) = 0, pldm_downstream_device_descriptor_iter_init(   \
+					&(devs), &(dev)));                     \
+	     (!pldm_descriptor_iter_end(&(desc##_iter)) &&                     \
+	      !((rc) = decode_pldm_descriptor_from_iter(&(desc##_iter),        \
+							&(desc))));            \
+	     pldm_descriptor_iter_next(&(desc##_iter)))
+
 /** @struct pldm_query_downstream_firmware_param_req
  *
  *  Structure representing QueryDownstreamFirmwareParameters request
@@ -1006,7 +1176,7 @@
  * @param[in] msg The PLDM message to decode.
  * @param[in] payload_length The length of the message payload.
  * @param[out] resp_data Pointer to the decoded response data.
- * @param[out] downstream_devices Pointer to the downstream devices.
+ * @param[out] iter Pointer to the downstream device iterator structure.
  * @return pldm_completion_codes
  *
  * @note Caller is responsible for memory alloc and dealloc of pointer params
@@ -1014,7 +1184,7 @@
 int decode_query_downstream_identifiers_resp(
 	const struct pldm_msg *msg, size_t payload_length,
 	struct pldm_query_downstream_identifiers_resp *resp_data,
-	struct variable_field *downstream_devices);
+	struct pldm_downstream_device_iter *iter);
 
 /**
  * @brief Encodes request message for Get Downstream Firmware Parameters.
diff --git a/src/compiler.h b/src/compiler.h
index 91b6e24..18e1a32 100644
--- a/src/compiler.h
+++ b/src/compiler.h
@@ -2,6 +2,8 @@
 #ifndef PLDM_COMPILER_H
 #define PLDM_COMPILER_H
 
+#include <libpldm/compiler.h>
+
 #ifndef __has_attribute
 #error The libpldm implementation requires __has_attribute
 #endif
@@ -20,8 +22,14 @@
 	int compliance;
 } pldm_required_attributes __attribute__((unused));
 
-#define LIBPLDM_CC_ALWAYS_INLINE      __attribute__((always_inline)) static inline
-#define LIBPLDM_CC_NONNULL	      __attribute__((nonnull))
+#ifndef LIBPLDM_CC_ALWAYS_INLINE
+#error Missing definition for LIBPLDM_ALWAYS_INLINE
+#endif
+
+#ifndef LIBPLDM_CC_NONNULL
+#error Missing definition for LIBPLDM_CC_NONNULL
+#endif
+
 #define LIBPLDM_CC_NONNULL_ARGS(...)  __attribute__((nonnull(__VA_ARGS__)))
 #define LIBPLDM_CC_UNUSED	      __attribute__((unused))
 #define LIBPLDM_CC_WARN_UNUSED_RESULT __attribute__((warn_unused_result))
diff --git a/src/dsp/firmware_update.c b/src/dsp/firmware_update.c
index 2afcaab..d570d5c 100644
--- a/src/dsp/firmware_update.c
+++ b/src/dsp/firmware_update.c
@@ -473,6 +473,40 @@
 	return PLDM_SUCCESS;
 }
 
+LIBPLDM_ABI_TESTING
+int decode_pldm_descriptor_from_iter(struct pldm_descriptor_iter *iter,
+				     struct pldm_descriptor *desc)
+{
+	struct pldm_msgbuf _buf;
+	struct pldm_msgbuf *buf = &_buf;
+	int rc;
+
+	if (!iter || !iter->field || !desc) {
+		return -EINVAL;
+	}
+
+	rc = pldm_msgbuf_init_errno(buf, PLDM_FWUP_DEVICE_DESCRIPTOR_MIN_LEN,
+				    iter->field->ptr, iter->field->length);
+	if (rc) {
+		return rc;
+	}
+
+	pldm_msgbuf_extract(buf, desc->descriptor_type);
+	rc = pldm_msgbuf_extract(buf, desc->descriptor_length);
+	if (rc) {
+		return rc;
+	}
+
+	desc->descriptor_data = NULL;
+	pldm_msgbuf_span_required(buf, desc->descriptor_length,
+				  (void **)&desc->descriptor_data);
+	iter->field->ptr = NULL;
+	pldm_msgbuf_span_remaining(buf, (void **)&iter->field->ptr,
+				   &iter->field->length);
+
+	return pldm_msgbuf_destroy(buf);
+}
+
 LIBPLDM_ABI_STABLE
 int decode_descriptor_type_length_value(const uint8_t *data, size_t length,
 					uint16_t *descriptor_type,
@@ -998,13 +1032,14 @@
 int decode_query_downstream_identifiers_resp(
 	const struct pldm_msg *msg, size_t payload_length,
 	struct pldm_query_downstream_identifiers_resp *resp_data,
-	struct variable_field *downstream_devices)
+	struct pldm_downstream_device_iter *iter)
 {
 	struct pldm_msgbuf _buf;
 	struct pldm_msgbuf *buf = &_buf;
+	void *remaining = NULL;
 	int rc = PLDM_ERROR;
 
-	if (msg == NULL || resp_data == NULL || downstream_devices == NULL ||
+	if (msg == NULL || resp_data == NULL || iter == NULL ||
 	    !payload_length) {
 		return PLDM_ERROR_INVALID_DATA;
 	}
@@ -1036,23 +1071,53 @@
 	}
 
 	pldm_msgbuf_extract(buf, resp_data->number_of_downstream_devices);
-	rc = pldm_msgbuf_span_required(buf,
-				       resp_data->downstream_devices_length,
-				       (void **)&downstream_devices->ptr);
+	rc = pldm_msgbuf_span_required(
+		buf, resp_data->downstream_devices_length, &remaining);
 	if (rc) {
 		return pldm_xlate_errno(rc);
 	}
-	downstream_devices->length = resp_data->downstream_devices_length;
 
 	rc = pldm_msgbuf_destroy(buf);
 	if (rc) {
 		return pldm_xlate_errno(rc);
 	}
 
+	iter->field.ptr = remaining;
+	iter->field.length = resp_data->downstream_devices_length;
+	iter->devs = resp_data->number_of_downstream_devices;
+
 	return PLDM_SUCCESS;
 }
 
 LIBPLDM_ABI_TESTING
+int decode_pldm_downstream_device_from_iter(
+	struct pldm_downstream_device_iter *iter,
+	struct pldm_downstream_device *dev)
+{
+	struct pldm_msgbuf _buf;
+	struct pldm_msgbuf *buf = &_buf;
+	int rc;
+
+	if (!iter || !dev) {
+		return -EINVAL;
+	}
+
+	rc = pldm_msgbuf_init_errno(buf, 3, iter->field.ptr,
+				    iter->field.length);
+	if (rc) {
+		return rc;
+	}
+
+	pldm_msgbuf_extract(buf, dev->downstream_device_index);
+	pldm_msgbuf_extract(buf, dev->downstream_descriptor_count);
+	iter->field.ptr = NULL;
+	pldm_msgbuf_span_remaining(buf, (void **)&iter->field.ptr,
+				   &iter->field.length);
+
+	return pldm_msgbuf_destroy(buf);
+}
+
+LIBPLDM_ABI_TESTING
 int encode_get_downstream_firmware_params_req(
 	uint8_t instance_id, uint32_t data_transfer_handle,
 	enum transfer_op_flag transfer_operation_flag, struct pldm_msg *msg,
diff --git a/tests/dsp/firmware_update.cpp b/tests/dsp/firmware_update.cpp
index 10db51a..db1a10d 100644
--- a/tests/dsp/firmware_update.cpp
+++ b/tests/dsp/firmware_update.cpp
@@ -1517,11 +1517,94 @@
 #endif
 
 #ifdef LIBPLDM_API_TESTING
-TEST(QueryDownstreamIdentifiers, goodPathDecodeResponse)
+TEST(QueryDownstreamIdentifiers, decodeResponseNoDevices)
 {
-    // Len is not fixed here taking it as 9, contains 1 downstream device with
-    // 1 descriptor
-    constexpr uint32_t downstreamDevicesLen = 9;
+    constexpr uint8_t completion_code_resp = PLDM_SUCCESS;
+    constexpr uint32_t next_data_transfer_handle_resp = 0x0;
+    constexpr uint8_t transfer_flag_resp = PLDM_START_AND_END;
+    constexpr uint32_t downstream_devices_length_resp = 0;
+    constexpr uint16_t number_of_downstream_devices_resp = 0;
+
+    PLDM_MSG_DEFINE_P(response, PLDM_QUERY_DOWNSTREAM_IDENTIFIERS_RESP_MIN_LEN);
+    struct pldm_query_downstream_identifiers_resp resp_data = {};
+    struct pldm_downstream_device_iter devs;
+    struct pldm_msgbuf _buf;
+    struct pldm_msgbuf* buf = &_buf;
+    int rc = 0;
+
+    rc = pldm_msgbuf_init_errno(buf, 0, response->payload,
+                                PLDM_QUERY_DOWNSTREAM_IDENTIFIERS_RESP_MIN_LEN);
+    ASSERT_EQ(rc, 0);
+
+    pldm_msgbuf_insert_uint8(buf, completion_code_resp);
+    pldm_msgbuf_insert_uint32(buf, next_data_transfer_handle_resp);
+    pldm_msgbuf_insert_uint8(buf, transfer_flag_resp);
+    pldm_msgbuf_insert_uint32(buf, downstream_devices_length_resp);
+    pldm_msgbuf_insert_uint16(buf, number_of_downstream_devices_resp);
+
+    ASSERT_EQ(pldm_msgbuf_destroy_consumed(buf), 0);
+
+    rc = decode_query_downstream_identifiers_resp(
+        response, PLDM_QUERY_DOWNSTREAM_IDENTIFIERS_RESP_MIN_LEN, &resp_data,
+        &devs);
+
+    ASSERT_EQ(rc, PLDM_SUCCESS);
+    EXPECT_EQ(resp_data.completion_code, completion_code_resp);
+    EXPECT_EQ(resp_data.next_data_transfer_handle,
+              next_data_transfer_handle_resp);
+    EXPECT_EQ(resp_data.transfer_flag, transfer_flag_resp);
+    EXPECT_EQ(resp_data.downstream_devices_length,
+              downstream_devices_length_resp);
+    EXPECT_EQ(resp_data.number_of_downstream_devices,
+              number_of_downstream_devices_resp);
+}
+#endif
+
+#ifdef LIBPLDM_API_TESTING
+TEST(QueryDownstreamIdentifiers, decodeResponseNoDevicesBadCount)
+{
+    constexpr uint8_t completion_code_resp = PLDM_SUCCESS;
+    constexpr uint32_t next_data_transfer_handle_resp = 0x0;
+    constexpr uint8_t transfer_flag_resp = PLDM_START_AND_END;
+    constexpr uint32_t downstream_devices_length_resp = 0;
+    constexpr uint16_t number_of_downstream_devices_resp = 1;
+
+    PLDM_MSG_DEFINE_P(response, PLDM_QUERY_DOWNSTREAM_IDENTIFIERS_RESP_MIN_LEN);
+    struct pldm_query_downstream_identifiers_resp resp = {};
+    struct pldm_downstream_device_iter devs;
+    struct pldm_downstream_device dev;
+    struct pldm_msgbuf _buf;
+    struct pldm_msgbuf* buf = &_buf;
+    int rc = 0;
+
+    rc = pldm_msgbuf_init_errno(buf, 0, response->payload,
+                                PLDM_QUERY_DOWNSTREAM_IDENTIFIERS_RESP_MIN_LEN);
+    ASSERT_EQ(rc, 0);
+
+    pldm_msgbuf_insert_uint8(buf, completion_code_resp);
+    pldm_msgbuf_insert_uint32(buf, next_data_transfer_handle_resp);
+    pldm_msgbuf_insert_uint8(buf, transfer_flag_resp);
+    pldm_msgbuf_insert_uint32(buf, downstream_devices_length_resp);
+    pldm_msgbuf_insert_uint16(buf, number_of_downstream_devices_resp);
+
+    ASSERT_EQ(pldm_msgbuf_destroy_consumed(buf), 0);
+
+    rc = decode_query_downstream_identifiers_resp(
+        response, PLDM_QUERY_DOWNSTREAM_IDENTIFIERS_RESP_MIN_LEN, &resp, &devs);
+    ASSERT_EQ(rc, PLDM_SUCCESS);
+
+    foreach_pldm_downstream_device(devs, dev, rc)
+    {
+        ASSERT_TRUE(false);
+    }
+    ASSERT_NE(rc, 0);
+}
+#endif
+
+#ifdef LIBPLDM_API_TESTING
+TEST(QueryDownstreamIdentifiers, decodeResponseOneDeviceOneDescriptor)
+{
+    constexpr uint32_t downstreamDevicesLen = 11;
     constexpr uint8_t completion_code_resp = PLDM_SUCCESS;
     constexpr uint32_t next_data_transfer_handle_resp = 0x0;
     constexpr uint8_t transfer_flag_resp = PLDM_START_AND_END;
@@ -1532,12 +1615,11 @@
         PLDM_QUERY_DOWNSTREAM_IDENTIFIERS_RESP_MIN_LEN + downstreamDevicesLen;
 
     struct pldm_query_downstream_identifiers_resp resp_data = {};
-    struct variable_field downstreamDevices = {};
     PLDM_MSG_DEFINE_P(response, payloadLen);
+    struct pldm_downstream_device_iter devs;
+    struct pldm_downstream_device dev;
     struct pldm_msgbuf _buf;
     struct pldm_msgbuf* buf = &_buf;
-    void* devicesStart = NULL;
-    size_t devicesLen = 0;
     int rc = 0;
 
     rc = pldm_msgbuf_init_errno(buf, 0, response->payload, payloadLen);
@@ -1548,18 +1630,22 @@
     pldm_msgbuf_insert_uint8(buf, transfer_flag_resp);
     pldm_msgbuf_insert_uint32(buf, downstream_devices_length_resp);
     pldm_msgbuf_insert_uint16(buf, number_of_downstream_devices_resp);
-    rc = pldm_msgbuf_span_remaining(buf, &devicesStart, &devicesLen);
-    ASSERT_EQ(rc, 0);
 
-    /** Filling descriptor data, the correctness of the downstream devices data
-     *  is not checked in this test case so filling with 0xff
-     */
-    std::fill_n(static_cast<uint8_t*>(devicesStart), devicesLen, 0xff);
+    /* Downstream device */
+    pldm_msgbuf_insert_uint16(buf, 1);
+    pldm_msgbuf_insert_uint8(buf, 1);
 
-    rc = decode_query_downstream_identifiers_resp(
-        response, payloadLen, &resp_data, &downstreamDevices);
+    /* Device descriptor */
+    pldm_msgbuf_insert_uint16(buf, 1);
+    pldm_msgbuf_insert_uint16(buf, 4);
+    pldm_msgbuf_insert_uint32(buf, 412);
 
-    EXPECT_EQ(rc, PLDM_SUCCESS);
+    ASSERT_EQ(pldm_msgbuf_destroy_consumed(buf), 0);
+
+    rc = decode_query_downstream_identifiers_resp(response, payloadLen,
+                                                  &resp_data, &devs);
+
+    ASSERT_EQ(rc, PLDM_SUCCESS);
     EXPECT_EQ(resp_data.completion_code, completion_code_resp);
     EXPECT_EQ(resp_data.next_data_transfer_handle,
               next_data_transfer_handle_resp);
@@ -1568,8 +1654,384 @@
               downstream_devices_length_resp);
     EXPECT_EQ(resp_data.number_of_downstream_devices,
               number_of_downstream_devices_resp);
-    EXPECT_EQ(downstreamDevices.ptr, static_cast<const uint8_t*>(devicesStart));
-    EXPECT_EQ(downstreamDevices.length, devicesLen);
+
+    foreach_pldm_downstream_device(devs, dev, rc)
+    {
+        struct pldm_descriptor desc;
+
+        EXPECT_EQ(dev.downstream_device_index, 1);
+        EXPECT_EQ(dev.downstream_descriptor_count, 1);
+
+        foreach_pldm_downstream_device_descriptor(devs, dev, desc, rc)
+        {
+            static const uint32_t dmtf = htole32(412);
+            EXPECT_EQ(desc.descriptor_type, 1);
+            EXPECT_EQ(desc.descriptor_length, 4);
+            EXPECT_EQ(memcmp(desc.descriptor_data, &dmtf, sizeof(dmtf)), 0);
+        }
+        ASSERT_EQ(rc, 0);
+    }
+    ASSERT_EQ(rc, 0);
+}
+#endif
+
+#ifdef LIBPLDM_API_TESTING
+constexpr const uint16_t descriptor_id_type_iana_pen = 0x1;
+constexpr const uint16_t descriptor_id_len_iana_pen = 0x4;
+const uint32_t iana_pen_openbmc = htole16(49871u);
+const uint32_t iana_pen_dmtf = htole16(412u);
+#endif
+
+#ifdef LIBPLDM_API_TESTING
+TEST(QueryDownstreamIdentifiers, decodeResponseTwoDevicesOneDescriptorEach)
+{
+    constexpr const std::array<pldm_downstream_device, 2> expected_devices = {{
+        {0, 1},
+        {1, 1},
+    }};
+
+    constexpr const std::array<pldm_descriptor, 2> expected_descriptors = {{
+        {descriptor_id_type_iana_pen, descriptor_id_len_iana_pen,
+         &iana_pen_dmtf},
+        {descriptor_id_type_iana_pen, descriptor_id_len_iana_pen,
+         &iana_pen_openbmc},
+    }};
+
+    constexpr uint32_t downstream_devices_len = 22;
+    constexpr uint8_t completion_code_resp = PLDM_SUCCESS;
+    constexpr uint32_t next_data_transfer_handle_resp = 0x0;
+    constexpr uint8_t transfer_flag_resp = PLDM_START_AND_END;
+    const uint32_t downstream_devices_length_resp =
+        htole32(downstream_devices_len);
+    constexpr uint16_t number_of_downstream_devices_resp = 2;
+    constexpr size_t payloadLen =
+        PLDM_QUERY_DOWNSTREAM_IDENTIFIERS_RESP_MIN_LEN + downstream_devices_len;
+
+    struct pldm_query_downstream_identifiers_resp resp_data
+    {
+    };
+    PLDM_MSG_DEFINE_P(response, payloadLen);
+    struct pldm_downstream_device_iter devs;
+    struct pldm_downstream_device dev;
+    struct pldm_msgbuf _buf;
+    struct pldm_msgbuf* buf = &_buf;
+    int rc = 0;
+
+    rc = pldm_msgbuf_init_errno(buf, 0, response->payload, payloadLen);
+    ASSERT_EQ(rc, 0);
+
+    pldm_msgbuf_insert_uint8(buf, completion_code_resp);
+    pldm_msgbuf_insert_uint32(buf, next_data_transfer_handle_resp);
+    pldm_msgbuf_insert_uint8(buf, transfer_flag_resp);
+    pldm_msgbuf_insert_uint32(buf, downstream_devices_length_resp);
+    pldm_msgbuf_insert_uint16(buf, number_of_downstream_devices_resp);
+
+    /* Downstream device */
+    pldm_msgbuf_insert_uint16(buf, 0);
+    pldm_msgbuf_insert_uint8(buf, 1);
+
+    /* Device descriptor */
+    pldm_msgbuf_insert_uint16(buf, descriptor_id_type_iana_pen);
+    pldm_msgbuf_insert_uint16(buf, descriptor_id_len_iana_pen);
+    pldm_msgbuf_insert_uint32(buf, iana_pen_dmtf);
+
+    /* Downstream device */
+    pldm_msgbuf_insert_uint16(buf, 1);
+    pldm_msgbuf_insert_uint8(buf, 1);
+
+    /* Device descriptor */
+    pldm_msgbuf_insert_uint16(buf, descriptor_id_type_iana_pen);
+    pldm_msgbuf_insert_uint16(buf, descriptor_id_len_iana_pen);
+    pldm_msgbuf_insert_uint32(buf, iana_pen_openbmc);
+
+    ASSERT_EQ(pldm_msgbuf_destroy_consumed(buf), 0);
+
+    rc = decode_query_downstream_identifiers_resp(response, payloadLen,
+                                                  &resp_data, &devs);
+
+    ASSERT_EQ(rc, PLDM_SUCCESS);
+    EXPECT_EQ(resp_data.number_of_downstream_devices,
+              number_of_downstream_devices_resp);
+
+    size_t devIndex = 0;
+    size_t descIndex = 0;
+    foreach_pldm_downstream_device(devs, dev, rc)
+    {
+        struct pldm_descriptor desc;
+
+        ASSERT_LT(devIndex, expected_devices.size());
+
+        const struct pldm_downstream_device* expectedDev =
+            &expected_devices[devIndex];
+
+        EXPECT_EQ(dev.downstream_device_index,
+                  expectedDev->downstream_device_index);
+        EXPECT_EQ(dev.downstream_descriptor_count,
+                  expectedDev->downstream_descriptor_count);
+
+        foreach_pldm_downstream_device_descriptor(devs, dev, desc, rc)
+        {
+            ASSERT_LT(descIndex, expected_descriptors.size());
+
+            const struct pldm_descriptor* expectedDesc =
+                &expected_descriptors[descIndex];
+
+            EXPECT_EQ(desc.descriptor_type, expectedDesc->descriptor_type);
+            ASSERT_EQ(desc.descriptor_length, expectedDesc->descriptor_length);
+            EXPECT_EQ(memcmp(desc.descriptor_data,
+                             expectedDesc->descriptor_data,
+                             expectedDesc->descriptor_length),
+                      0);
+
+            descIndex++;
+        }
+        ASSERT_EQ(rc, 0);
+        EXPECT_EQ(descIndex, 1 * devIndex + 1);
+
+        devIndex++;
+    }
+    ASSERT_EQ(rc, 0);
+    EXPECT_EQ(devIndex, 2);
+}
+#endif
+
+#ifdef LIBPLDM_API_TESTING
+TEST(QueryDownstreamIdentifiers, decodeResponseTwoDevicesTwoOneDescriptors)
+{
+    constexpr const std::array<pldm_downstream_device, 2> expected_devices = {{
+        {0, 2},
+        {1, 1},
+    }};
+
+    constexpr const std::array<pldm_descriptor, 3> expected_descriptors = {{
+        {descriptor_id_type_iana_pen, descriptor_id_len_iana_pen,
+         &iana_pen_dmtf},
+        {descriptor_id_type_iana_pen, descriptor_id_len_iana_pen,
+         &iana_pen_openbmc},
+        {descriptor_id_type_iana_pen, descriptor_id_len_iana_pen,
+         &iana_pen_dmtf},
+    }};
+
+    constexpr uint32_t downstream_devices_len = 30;
+    constexpr uint8_t completion_code_resp = PLDM_SUCCESS;
+    constexpr uint32_t next_data_transfer_handle_resp = 0x0;
+    constexpr uint8_t transfer_flag_resp = PLDM_START_AND_END;
+    const uint32_t downstream_devices_length_resp =
+        htole32(downstream_devices_len);
+    constexpr uint16_t number_of_downstream_devices_resp = 2;
+    constexpr size_t payloadLen =
+        PLDM_QUERY_DOWNSTREAM_IDENTIFIERS_RESP_MIN_LEN + downstream_devices_len;
+
+    struct pldm_query_downstream_identifiers_resp resp_data
+    {
+    };
+    PLDM_MSG_DEFINE_P(response, payloadLen);
+    struct pldm_downstream_device_iter devs;
+    struct pldm_downstream_device dev;
+    struct pldm_msgbuf _buf;
+    struct pldm_msgbuf* buf = &_buf;
+    int rc = 0;
+
+    rc = pldm_msgbuf_init_errno(buf, 0, response->payload, payloadLen);
+    ASSERT_EQ(rc, 0);
+
+    pldm_msgbuf_insert_uint8(buf, completion_code_resp);
+    pldm_msgbuf_insert_uint32(buf, next_data_transfer_handle_resp);
+    pldm_msgbuf_insert_uint8(buf, transfer_flag_resp);
+    pldm_msgbuf_insert_uint32(buf, downstream_devices_length_resp);
+    pldm_msgbuf_insert_uint16(buf, number_of_downstream_devices_resp);
+
+    /* Downstream device */
+    pldm_msgbuf_insert_uint16(buf, 0);
+    pldm_msgbuf_insert_uint8(buf, 2);
+
+    /* Device descriptor */
+    pldm_msgbuf_insert_uint16(buf, descriptor_id_type_iana_pen);
+    pldm_msgbuf_insert_uint16(buf, descriptor_id_len_iana_pen);
+    pldm_msgbuf_insert_uint32(buf, iana_pen_dmtf);
+
+    /* Device descriptor */
+    pldm_msgbuf_insert_uint16(buf, descriptor_id_type_iana_pen);
+    pldm_msgbuf_insert_uint16(buf, descriptor_id_len_iana_pen);
+    pldm_msgbuf_insert_uint32(buf, iana_pen_openbmc);
+
+    /* Downstream device */
+    pldm_msgbuf_insert_uint16(buf, 1);
+    pldm_msgbuf_insert_uint8(buf, 1);
+
+    /* Device descriptor */
+    pldm_msgbuf_insert_uint16(buf, descriptor_id_type_iana_pen);
+    pldm_msgbuf_insert_uint16(buf, descriptor_id_len_iana_pen);
+    pldm_msgbuf_insert_uint32(buf, iana_pen_dmtf);
+
+    ASSERT_EQ(pldm_msgbuf_destroy_consumed(buf), 0);
+
+    rc = decode_query_downstream_identifiers_resp(response, payloadLen,
+                                                  &resp_data, &devs);
+
+    ASSERT_EQ(rc, PLDM_SUCCESS);
+    EXPECT_EQ(resp_data.number_of_downstream_devices,
+              number_of_downstream_devices_resp);
+
+    size_t devIndex = 0;
+    size_t descIndex = 0;
+    foreach_pldm_downstream_device(devs, dev, rc)
+    {
+        struct pldm_descriptor desc;
+
+        ASSERT_LT(devIndex, expected_devices.size());
+
+        const struct pldm_downstream_device* expectedDev =
+            &expected_devices[devIndex];
+
+        EXPECT_EQ(dev.downstream_device_index,
+                  expectedDev->downstream_device_index);
+        EXPECT_EQ(dev.downstream_descriptor_count,
+                  expectedDev->downstream_descriptor_count);
+
+        foreach_pldm_downstream_device_descriptor(devs, dev, desc, rc)
+        {
+            ASSERT_LT(descIndex, expected_descriptors.size());
+
+            const struct pldm_descriptor* expectedDesc =
+                &expected_descriptors[descIndex];
+
+            EXPECT_EQ(desc.descriptor_type, expectedDesc->descriptor_type);
+            ASSERT_EQ(desc.descriptor_length, expectedDesc->descriptor_length);
+            EXPECT_EQ(memcmp(desc.descriptor_data,
+                             expectedDesc->descriptor_data,
+                             expectedDesc->descriptor_length),
+                      0);
+
+            descIndex++;
+        }
+        ASSERT_EQ(rc, 0);
+
+        devIndex++;
+    }
+    ASSERT_EQ(rc, 0);
+    EXPECT_EQ(devIndex, 2);
+    EXPECT_EQ(descIndex, 3);
+}
+#endif
+
+#ifdef LIBPLDM_API_TESTING
+TEST(QueryDownstreamIdentifiers, decodeResponseTwoDevicesOneTwoDescriptors)
+{
+    constexpr const std::array<pldm_downstream_device, 2> expected_devices = {{
+        {0, 1},
+        {1, 2},
+    }};
+
+    constexpr const std::array<pldm_descriptor, 3> expected_descriptors = {{
+        {descriptor_id_type_iana_pen, descriptor_id_len_iana_pen,
+         &iana_pen_dmtf},
+        {descriptor_id_type_iana_pen, descriptor_id_len_iana_pen,
+         &iana_pen_openbmc},
+        {descriptor_id_type_iana_pen, descriptor_id_len_iana_pen,
+         &iana_pen_dmtf},
+    }};
+
+    constexpr uint32_t downstream_devices_len = 30;
+    constexpr uint8_t completion_code_resp = PLDM_SUCCESS;
+    constexpr uint32_t next_data_transfer_handle_resp = 0x0;
+    constexpr uint8_t transfer_flag_resp = PLDM_START_AND_END;
+    const uint32_t downstream_devices_length_resp =
+        htole32(downstream_devices_len);
+    constexpr uint16_t number_of_downstream_devices_resp = 2;
+    constexpr size_t payloadLen =
+        PLDM_QUERY_DOWNSTREAM_IDENTIFIERS_RESP_MIN_LEN + downstream_devices_len;
+
+    struct pldm_query_downstream_identifiers_resp resp_data
+    {
+    };
+    PLDM_MSG_DEFINE_P(response, payloadLen);
+    struct pldm_downstream_device_iter devs;
+    struct pldm_downstream_device dev;
+    struct pldm_msgbuf _buf;
+    struct pldm_msgbuf* buf = &_buf;
+    int rc = 0;
+
+    rc = pldm_msgbuf_init_errno(buf, 0, response->payload, payloadLen);
+    ASSERT_EQ(rc, 0);
+
+    pldm_msgbuf_insert_uint8(buf, completion_code_resp);
+    pldm_msgbuf_insert_uint32(buf, next_data_transfer_handle_resp);
+    pldm_msgbuf_insert_uint8(buf, transfer_flag_resp);
+    pldm_msgbuf_insert_uint32(buf, downstream_devices_length_resp);
+    pldm_msgbuf_insert_uint16(buf, number_of_downstream_devices_resp);
+
+    /* Downstream device */
+    pldm_msgbuf_insert_uint16(buf, 0);
+    pldm_msgbuf_insert_uint8(buf, 1);
+
+    /* Device descriptor */
+    pldm_msgbuf_insert_uint16(buf, descriptor_id_type_iana_pen);
+    pldm_msgbuf_insert_uint16(buf, descriptor_id_len_iana_pen);
+    pldm_msgbuf_insert_uint32(buf, iana_pen_dmtf);
+
+    /* Downstream device */
+    pldm_msgbuf_insert_uint16(buf, 1);
+    pldm_msgbuf_insert_uint8(buf, 2);
+
+    /* Device descriptor */
+    pldm_msgbuf_insert_uint16(buf, descriptor_id_type_iana_pen);
+    pldm_msgbuf_insert_uint16(buf, descriptor_id_len_iana_pen);
+    pldm_msgbuf_insert_uint32(buf, iana_pen_openbmc);
+
+    /* Device descriptor */
+    pldm_msgbuf_insert_uint16(buf, descriptor_id_type_iana_pen);
+    pldm_msgbuf_insert_uint16(buf, descriptor_id_len_iana_pen);
+    pldm_msgbuf_insert_uint32(buf, iana_pen_dmtf);
+
+    ASSERT_EQ(pldm_msgbuf_destroy_consumed(buf), 0);
+
+    rc = decode_query_downstream_identifiers_resp(response, payloadLen,
+                                                  &resp_data, &devs);
+
+    ASSERT_EQ(rc, PLDM_SUCCESS);
+    EXPECT_EQ(resp_data.number_of_downstream_devices,
+              number_of_downstream_devices_resp);
+
+    size_t devIndex = 0;
+    size_t descIndex = 0;
+    foreach_pldm_downstream_device(devs, dev, rc)
+    {
+        struct pldm_descriptor desc;
+
+        ASSERT_LT(devIndex, expected_devices.size());
+
+        const struct pldm_downstream_device* expectedDev =
+            &expected_devices[devIndex];
+
+        EXPECT_EQ(dev.downstream_device_index,
+                  expectedDev->downstream_device_index);
+        EXPECT_EQ(dev.downstream_descriptor_count,
+                  expectedDev->downstream_descriptor_count);
+
+        foreach_pldm_downstream_device_descriptor(devs, dev, desc, rc)
+        {
+            ASSERT_LT(descIndex, expected_descriptors.size());
+
+            const struct pldm_descriptor* expectedDesc =
+                &expected_descriptors[descIndex];
+
+            EXPECT_EQ(desc.descriptor_type, expectedDesc->descriptor_type);
+            ASSERT_EQ(desc.descriptor_length, expectedDesc->descriptor_length);
+            EXPECT_EQ(memcmp(desc.descriptor_data,
+                             expectedDesc->descriptor_data,
+                             expectedDesc->descriptor_length),
+                      0);
+
+            descIndex++;
+        }
+        ASSERT_EQ(rc, 0);
+
+        devIndex++;
+    }
+    ASSERT_EQ(rc, 0);
+    EXPECT_EQ(devIndex, 2);
+    EXPECT_EQ(descIndex, 3);
 }
 #endif
 
@@ -1579,25 +2041,25 @@
     constexpr size_t payloadLen = sizeof(uint8_t);
 
     struct pldm_query_downstream_identifiers_resp resp_data = {};
-    struct variable_field downstreamDevices = {};
+    struct pldm_downstream_device_iter devs;
     PLDM_MSG_DEFINE_P(response, payloadLen);
 
     // Test nullptr
-    auto rc = decode_query_downstream_identifiers_resp(
-        nullptr, payloadLen, nullptr, &downstreamDevices);
+    auto rc = decode_query_downstream_identifiers_resp(nullptr, payloadLen,
+                                                       nullptr, &devs);
     EXPECT_EQ(rc, PLDM_ERROR_INVALID_DATA);
 
     // Test not PLDM_SUCCESS completion code
     response->payload[0] = PLDM_ERROR_UNSUPPORTED_PLDM_CMD;
-    rc = decode_query_downstream_identifiers_resp(
-        response, payloadLen, &resp_data, &downstreamDevices);
+    rc = decode_query_downstream_identifiers_resp(response, payloadLen,
+                                                  &resp_data, &devs);
     EXPECT_EQ(rc, PLDM_SUCCESS);
     EXPECT_EQ(resp_data.completion_code, PLDM_ERROR_UNSUPPORTED_PLDM_CMD);
 
     // Test payload length less than minimum length
     response->payload[0] = PLDM_SUCCESS;
-    rc = decode_query_downstream_identifiers_resp(
-        response, payloadLen, &resp_data, &downstreamDevices);
+    rc = decode_query_downstream_identifiers_resp(response, payloadLen,
+                                                  &resp_data, &devs);
 
     EXPECT_EQ(rc, PLDM_ERROR_INVALID_LENGTH);
 }
@@ -1621,7 +2083,7 @@
         htole32(actualDownstreamDevicesLen + 1 /* inject error length*/);
 
     struct pldm_query_downstream_identifiers_resp resp_data = {};
-    struct variable_field downstreamDevices = {};
+    struct pldm_downstream_device_iter devs;
     PLDM_MSG_DEFINE_P(response, payloadLen);
     struct pldm_msgbuf _buf;
     struct pldm_msgbuf* buf = &_buf;
@@ -1645,8 +2107,8 @@
     std::fill_n(static_cast<uint8_t*>(devicesStart), actualDownstreamDevicesLen,
                 0xff);
 
-    EXPECT_NE(decode_query_downstream_identifiers_resp(
-                  response, payloadLen, &resp_data, &downstreamDevices),
+    EXPECT_NE(decode_query_downstream_identifiers_resp(response, payloadLen,
+                                                       &resp_data, &devs),
               PLDM_SUCCESS);
 }
 #endif
@@ -1666,7 +2128,7 @@
         htole32(actualDownstreamDevicesLen);
 
     struct pldm_query_downstream_identifiers_resp resp_data = {};
-    struct variable_field downstreamDevices = {};
+    struct pldm_downstream_device_iter devs;
     PLDM_MSG_DEFINE_P(response, payloadLen);
     struct pldm_msgbuf _buf;
     struct pldm_msgbuf* buf = &_buf;
@@ -1682,8 +2144,8 @@
     // Inject error buffer size
     pldm_msgbuf_insert_uint8(buf, (uint8_t)number_of_downstream_devices_resp);
 
-    rc = decode_query_downstream_identifiers_resp(
-        response, payloadLen, &resp_data, &downstreamDevices);
+    rc = decode_query_downstream_identifiers_resp(response, payloadLen,
+                                                  &resp_data, &devs);
 
     EXPECT_EQ(rc, PLDM_ERROR_INVALID_LENGTH);
 }