fw_update: Add encode req & decode resp for query_downstream_devices

Add support for Query Downstream Devices to ask if a endpoint supports
downstream devices.

The code is developed based on the definition of
'QueryDownstreamDevices' in DSP0267_1.1.0. Section 10.3

Change-Id: I5925290de5023eb48f675e736429fe9f257170c8
Signed-off-by: Delphine CC Chiu <Delphine_CC_Chiu@wiwynn.com>
diff --git a/CHANGELOG.md b/CHANGELOG.md
index be236be..9a1b2aa 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -41,6 +41,7 @@
     Connection Types
 13. pdr: Add decode_numeric_effecter_pdr_data()
 14. oem: ibm: Support for the Real SAI entity id
+15. fw_update: Add encode req & decode resp for query_downstream_devices
 
 ### Changed
 
diff --git a/include/libpldm/base.h b/include/libpldm/base.h
index 544ae2a..01a0418 100644
--- a/include/libpldm/base.h
+++ b/include/libpldm/base.h
@@ -114,6 +114,21 @@
 
 #define PLDM_TIMESTAMP104_SIZE 13
 
+/** @brief Minimum length of response for a optional PLDM command
+ *
+ *  For a optional PLDM command, the command handler might not be
+ *  implemented in a device's firmware, a response contains only CC
+ *  might come in, such as ERROR_UNSUPPORTED_PLDM_CMD.
+ *
+ *  The description can be found in DSP0240:
+ *  > For an unsupported PLDM command, the ERROR_UNSUPPORTED_PLDM_CMD
+ *  > completion code shall be returned unless the responder is in a
+ *  > transient state (not ready), in which it cannot process the PLDM
+ *  > command. If the responder is in a transient state, it may return
+ *  > the ERROR_NOT_READY completion code.
+ */
+#define PLDM_OPTIONAL_COMMAND_RESP_MIN_LEN 1
+
 /** @struct pldm_msg_hdr
  *
  * Structure representing PLDM message header fields
diff --git a/include/libpldm/firmware_update.h b/include/libpldm/firmware_update.h
index ede4bc7..eda6d7c 100644
--- a/include/libpldm/firmware_update.h
+++ b/include/libpldm/firmware_update.h
@@ -17,6 +17,18 @@
 #define PLDM_FWUP_COMPONENT_BITMAP_MULTIPLE		 8
 #define PLDM_FWUP_INVALID_COMPONENT_COMPARISON_TIMESTAMP 0xffffffff
 #define PLDM_QUERY_DEVICE_IDENTIFIERS_REQ_BYTES		 0
+
+/** @brief Length of QueryDownstreamDevices response defined in DSP0267_1.1.0
+ *  Table 15 - QueryDownstreamDevices command format.
+ *
+ *  1 byte for completion code
+ *  1 byte for downstream device update supported
+ *  2 bytes for number of downstream devices
+ *  2 bytes for max number of downstream devices
+ *  4 bytes for capabilities
+ */
+#define PLDM_QUERY_DOWNSTREAM_DEVICES_RESP_BYTES 10
+
 /** @brief Minimum length of device descriptor, 2 bytes for descriptor type,
  *         2 bytes for descriptor length and atleast 1 byte of descriptor data
  */
@@ -35,6 +47,7 @@
 enum pldm_firmware_update_commands {
 	PLDM_QUERY_DEVICE_IDENTIFIERS = 0x01,
 	PLDM_GET_FIRMWARE_PARAMETERS = 0x02,
+	PLDM_QUERY_DOWNSTREAM_DEVICES = 0x03,
 	PLDM_REQUEST_UPDATE = 0x10,
 	PLDM_PASS_COMPONENT_TABLE = 0x13,
 	PLDM_UPDATE_COMPONENT = 0x14,
@@ -324,6 +337,14 @@
 	PLDM_FWUP_COMPONENTS_NOT_FUNCTIONING = 1
 };
 
+/** @brief Downstream device update supported in QueryDownstreamDevices response
+ *         defined in DSP0267_1.1.0
+*/
+enum pldm_firmware_update_downstream_device_update_supported {
+	PLDM_FWUP_DOWNSTREAM_DEVICE_UPDATE_NOT_SUPPORTED = 0,
+	PLDM_FWUP_DOWNSTREAM_DEVICE_UPDATE_SUPPORTED = 1
+};
+
 /** @struct pldm_package_header_information
  *
  *  Structure representing fixed part of package header information
@@ -412,6 +433,20 @@
 	uint8_t pending_comp_image_set_ver_str_len;
 } __attribute__((packed));
 
+/** @struct pldm_query_downstream_devices_resp
+ *
+ *  Structure representing response of QueryDownstreamDevices.
+ *  The definition can be found Table 15 - QueryDownstreamDevices command format
+ *  in DSP0267_1.1.0
+ */
+struct pldm_query_downstream_devices_resp {
+	uint8_t completion_code;
+	uint8_t downstream_device_update_supported;
+	uint16_t number_of_downstream_devices;
+	uint16_t max_number_of_downstream_devices;
+	bitfield32_t capabilities;
+};
+
 /** @struct pldm_component_parameter_entry
  *
  *  Structure representing component parameter table entry.
@@ -738,6 +773,34 @@
 	struct variable_field *active_comp_ver_str,
 	struct variable_field *pending_comp_ver_str);
 
+/** @brief Create a PLDM request message for QueryDownstreamDevices
+ *
+ *  @param[in] instance_id - Message's instance id
+ *  @param[out] msg - Message will be written to this
+ *
+ *  @return pldm_completion_codes
+ *
+ *  @note  Caller is responsible for memory alloc and dealloc of param
+ *         'msg.payload'
+ */
+int encode_query_downstream_devices_req(uint8_t instance_id,
+					struct pldm_msg *msg);
+
+/**
+ * @brief Decodes the response message for Querying Downstream Devices.
+ *
+ * @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 structure to store the decoded response data.
+ * @return pldm_completion_codes
+ *
+ * @note  Caller is responsible for memory alloc and dealloc of param
+ *         'msg.payload'
+ */
+int decode_query_downstream_devices_resp(
+	const struct pldm_msg *msg, size_t payload_length,
+	struct pldm_query_downstream_devices_resp *resp_data);
+
 /** @brief Create PLDM request message for RequestUpdate
  *
  *  @param[in] instance_id - Message's instance id
diff --git a/src/firmware_update.c b/src/firmware_update.c
index 0788d4b..ed1816d 100644
--- a/src/firmware_update.c
+++ b/src/firmware_update.c
@@ -1,4 +1,5 @@
 /* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */
+#include "msgbuf.h"
 #include <libpldm/firmware_update.h>
 #include <libpldm/utils.h>
 
@@ -74,6 +75,17 @@
 	}
 }
 
+static bool is_downstream_device_update_support_valid(uint8_t resp)
+{
+	switch (resp) {
+	case PLDM_FWUP_DOWNSTREAM_DEVICE_UPDATE_NOT_SUPPORTED:
+	case PLDM_FWUP_DOWNSTREAM_DEVICE_UPDATE_SUPPORTED:
+		return true;
+	default:
+		return false;
+	}
+}
+
 /** @brief Check whether ComponentResponse is valid
  *
  *  @return true if ComponentResponse is valid, false if not
@@ -854,6 +866,68 @@
 	return PLDM_SUCCESS;
 }
 
+LIBPLDM_ABI_TESTING
+int encode_query_downstream_devices_req(uint8_t instance_id,
+					struct pldm_msg *msg)
+{
+	if (msg == NULL) {
+		return PLDM_ERROR_INVALID_DATA;
+	}
+
+	return encode_pldm_header_only(PLDM_REQUEST, instance_id, PLDM_FWUP,
+				       PLDM_QUERY_DOWNSTREAM_DEVICES, msg);
+}
+
+LIBPLDM_ABI_TESTING
+int decode_query_downstream_devices_resp(
+	const struct pldm_msg *msg, size_t payload_length,
+	struct pldm_query_downstream_devices_resp *resp_data)
+{
+	struct pldm_msgbuf _buf;
+	struct pldm_msgbuf *buf = &_buf;
+	int rc;
+
+	if (msg == NULL || resp_data == NULL || !payload_length) {
+		return PLDM_ERROR_INVALID_DATA;
+	}
+
+	rc = pldm_msgbuf_init(buf, PLDM_OPTIONAL_COMMAND_RESP_MIN_LEN,
+			      msg->payload, payload_length);
+	if (rc) {
+		return rc;
+	}
+
+	rc = pldm_msgbuf_extract(buf, resp_data->completion_code);
+	if (rc) {
+		return rc;
+	}
+	if (PLDM_SUCCESS != resp_data->completion_code) {
+		// Return the CC directly without decoding the rest of the payload
+		return PLDM_SUCCESS;
+	}
+
+	if (payload_length < PLDM_QUERY_DOWNSTREAM_DEVICES_RESP_BYTES) {
+		return PLDM_ERROR_INVALID_LENGTH;
+	}
+
+	rc = pldm_msgbuf_extract(buf,
+				 resp_data->downstream_device_update_supported);
+	if (rc) {
+		return rc;
+	}
+
+	if (!is_downstream_device_update_support_valid(
+		    resp_data->downstream_device_update_supported)) {
+		return PLDM_ERROR_INVALID_DATA;
+	}
+
+	pldm_msgbuf_extract(buf, resp_data->number_of_downstream_devices);
+	pldm_msgbuf_extract(buf, resp_data->max_number_of_downstream_devices);
+	pldm_msgbuf_extract(buf, resp_data->capabilities.value);
+
+	return pldm_msgbuf_destroy_consumed(buf);
+}
+
 LIBPLDM_ABI_STABLE
 int encode_request_update_req(uint8_t instance_id, uint32_t max_transfer_size,
 			      uint16_t num_of_comp,
diff --git a/tests/libpldm_firmware_update_test.cpp b/tests/libpldm_firmware_update_test.cpp
index 52e867a..3870fee 100644
--- a/tests/libpldm_firmware_update_test.cpp
+++ b/tests/libpldm_firmware_update_test.cpp
@@ -13,6 +13,8 @@
 #include <string_view>
 #include <vector>
 
+#include "msgbuf.h"
+
 #include <gtest/gtest.h>
 
 constexpr auto hdrSize = sizeof(pldm_msg_hdr);
@@ -1276,6 +1278,153 @@
                         outPendingCompVerStr.length));
 }
 
+TEST(QueryDownstreamDevices, goodPathEncodeRequest)
+{
+    constexpr uint8_t instanceId = 1;
+    std::array<uint8_t, sizeof(pldm_msg_hdr)> requestMsg{};
+    auto requestPtr = reinterpret_cast<pldm_msg*>(requestMsg.data());
+
+    auto rc = encode_query_downstream_devices_req(instanceId, requestPtr);
+
+    EXPECT_EQ(rc, PLDM_SUCCESS);
+    EXPECT_EQ(requestPtr->hdr.request, PLDM_REQUEST);
+    EXPECT_EQ(requestPtr->hdr.instance_id, instanceId);
+    EXPECT_EQ(requestPtr->hdr.type, PLDM_FWUP);
+    EXPECT_EQ(requestPtr->hdr.command, PLDM_QUERY_DOWNSTREAM_DEVICES);
+}
+
+TEST(QueryDownstreamDevices, encodeRequestInvalidData)
+{
+    constexpr uint8_t instanceId = 1;
+
+    auto rc = encode_query_downstream_devices_req(instanceId, nullptr);
+
+    EXPECT_EQ(rc, PLDM_ERROR_INVALID_DATA);
+}
+
+TEST(QueryDownstreamDevices, goodPathDecodeResponse)
+{
+    uint8_t completion_code_resp = PLDM_SUCCESS;
+    uint8_t downstream_device_update_supported_resp =
+        PLDM_FWUP_DOWNSTREAM_DEVICE_UPDATE_SUPPORTED;
+    uint16_t number_of_downstream_devices_resp = 1;
+    uint16_t max_number_of_downstream_devices_resp = 1;
+    /** Capabilities of updating downstream devices
+     * FDP supports downstream devices dynamically attached [Bit position 0] &
+     * FDP supports downstream devices dynamically removed [Bit position 1]
+     */
+    bitfield32_t capabilities_resp = {.value = 0x0002};
+    int rc;
+
+    std::array<uint8_t, hdrSize + PLDM_QUERY_DOWNSTREAM_DEVICES_RESP_BYTES>
+        responseMsg{};
+
+    struct pldm_msgbuf _buf;
+    struct pldm_msgbuf* buf = &_buf;
+    rc = pldm_msgbuf_init(buf, 0, responseMsg.data() + hdrSize,
+                          responseMsg.size() - hdrSize);
+    EXPECT_EQ(rc, PLDM_SUCCESS);
+
+    pldm_msgbuf_insert_uint8(buf, completion_code_resp);
+    pldm_msgbuf_insert_uint8(buf, downstream_device_update_supported_resp);
+    pldm_msgbuf_insert_uint16(buf, number_of_downstream_devices_resp);
+    pldm_msgbuf_insert_uint16(buf, max_number_of_downstream_devices_resp);
+    pldm_msgbuf_insert_uint32(buf, capabilities_resp.value);
+
+    auto response = reinterpret_cast<pldm_msg*>(responseMsg.data());
+    struct pldm_query_downstream_devices_resp resp_data;
+
+    rc = decode_query_downstream_devices_resp(
+        response, responseMsg.size() - hdrSize, &resp_data);
+
+    EXPECT_EQ(rc, PLDM_SUCCESS);
+    EXPECT_EQ(resp_data.completion_code, completion_code_resp);
+    EXPECT_EQ(resp_data.downstream_device_update_supported,
+              downstream_device_update_supported_resp);
+    EXPECT_EQ(resp_data.number_of_downstream_devices,
+              number_of_downstream_devices_resp);
+    EXPECT_EQ(resp_data.max_number_of_downstream_devices,
+              max_number_of_downstream_devices_resp);
+    EXPECT_EQ(resp_data.capabilities.value, capabilities_resp.value);
+}
+
+TEST(QueryDownstreamDevices, decodeRequestUndefinedValue)
+{
+    uint8_t completion_code_resp = PLDM_SUCCESS;
+    uint8_t downstream_device_update_supported_resp = 0xe; /*Undefined value*/
+    uint16_t number_of_downstream_devices_resp = 1;
+    uint16_t max_number_of_downstream_devices_resp = 1;
+    /** Capabilities of updating downstream devices
+     * FDP supports downstream devices dynamically attached [Bit position 0] &
+     * FDP supports downstream devices dynamically removed [Bit position 1]
+     */
+    bitfield32_t capabilities_resp = {.value = 0x0002};
+    int rc;
+
+    std::array<uint8_t, hdrSize + PLDM_QUERY_DOWNSTREAM_DEVICES_RESP_BYTES>
+        responseMsg{};
+
+    struct pldm_msgbuf _buf;
+    struct pldm_msgbuf* buf = &_buf;
+    rc = pldm_msgbuf_init(buf, 0, responseMsg.data() + hdrSize,
+                          responseMsg.size() - hdrSize);
+    EXPECT_EQ(rc, PLDM_SUCCESS);
+
+    pldm_msgbuf_insert_uint8(buf, completion_code_resp);
+    pldm_msgbuf_insert_uint8(buf, downstream_device_update_supported_resp);
+    pldm_msgbuf_insert_uint16(buf, number_of_downstream_devices_resp);
+    pldm_msgbuf_insert_uint16(buf, max_number_of_downstream_devices_resp);
+    pldm_msgbuf_insert_uint32(buf, capabilities_resp.value);
+
+    auto response = reinterpret_cast<pldm_msg*>(responseMsg.data());
+    struct pldm_query_downstream_devices_resp resp_data;
+
+    rc = decode_query_downstream_devices_resp(
+        response, responseMsg.size() - hdrSize, &resp_data);
+
+    ASSERT_EQ(rc, PLDM_ERROR_INVALID_DATA);
+}
+
+TEST(QueryDownstreamDevices, decodeRequestErrorBufSize)
+{
+    uint8_t completion_code_resp = PLDM_SUCCESS;
+    uint8_t downstream_device_update_supported_resp =
+        PLDM_FWUP_DOWNSTREAM_DEVICE_UPDATE_SUPPORTED;
+    uint16_t number_of_downstream_devices_resp = 1;
+    uint16_t max_number_of_downstream_devices_resp = 1;
+    /** Capabilities of updating downstream devices
+     * FDP supports downstream devices dynamically attached [Bit position 0] &
+     * FDP supports downstream devices dynamically removed [Bit position 1]
+     */
+    bitfield32_t capabilities_resp = {.value = 0x0002};
+    int rc;
+
+    std::array<uint8_t, hdrSize + PLDM_QUERY_DOWNSTREAM_DEVICES_RESP_BYTES -
+                            2 /* Inject error length*/>
+        responseMsg{};
+
+    struct pldm_msgbuf _buf;
+    struct pldm_msgbuf* buf = &_buf;
+    rc = pldm_msgbuf_init(buf, 0, responseMsg.data() + hdrSize,
+                          responseMsg.size() - hdrSize);
+    EXPECT_EQ(rc, PLDM_SUCCESS);
+
+    pldm_msgbuf_insert_uint8(buf, completion_code_resp);
+    pldm_msgbuf_insert_uint8(buf, downstream_device_update_supported_resp);
+    pldm_msgbuf_insert_uint16(buf, number_of_downstream_devices_resp);
+    pldm_msgbuf_insert_uint16(buf, max_number_of_downstream_devices_resp);
+    // Inject error value
+    pldm_msgbuf_insert_uint16(buf, (uint16_t)capabilities_resp.value);
+
+    auto response = reinterpret_cast<pldm_msg*>(responseMsg.data());
+    struct pldm_query_downstream_devices_resp resp_data;
+
+    rc = decode_query_downstream_devices_resp(
+        response, responseMsg.size() - hdrSize, &resp_data);
+
+    EXPECT_EQ(rc, PLDM_ERROR_INVALID_LENGTH);
+}
+
 TEST(RequestUpdate, goodPathEncodeRequest)
 {
     constexpr uint8_t instanceId = 1;