fw_update: Add encode req & decode resp for query_downtream_identifiers

Add support for Query Downstream Identifiers to ask all downstream
devices' Descriptors managed by a endpoint.

The code is developed based on the definition of
'QueryDownstreamIdentifiers' in DSP0267_1.1.0 Section 10.4

Change-Id: I6282a894c73b78470c147e77d81e5a4ddd6a39a8
Signed-off-by: Delphine CC Chiu <Delphine_CC_Chiu@wiwynn.com>
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9a1b2aa..5a919d0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -42,6 +42,7 @@
 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
+16. fw_update: Add encode req & decode resp for query_downstream_identifiers
 
 ### Changed
 
diff --git a/include/libpldm/firmware_update.h b/include/libpldm/firmware_update.h
index eda6d7c..ac757a1 100644
--- a/include/libpldm/firmware_update.h
+++ b/include/libpldm/firmware_update.h
@@ -29,6 +29,25 @@
  */
 #define PLDM_QUERY_DOWNSTREAM_DEVICES_RESP_BYTES 10
 
+/** @brief Length of QueryDownstreamIdentifiers request defined in DSP0267_1.1.0
+ * 	Table 16 - QueryDownstreamIdentifiers command format.
+ *
+ *  4 bytes for data transfer handle
+ *  1 byte for transfer operation flag
+*/
+#define PLDM_QUERY_DOWNSTREAM_IDENTIFIERS_REQ_BYTES 5
+
+/** @brief Minimum length of QueryDownstreamIdentifiers response from DSP0267_1.1.0
+ *  if the complement code is success.
+ *
+ *  1 byte for completion code
+ *  4 bytes for next data transfer handle
+ *  1 byte for transfer flag
+ *  4 bytes for downstream devices length
+ *  2 bytes for number of downstream devices
+ */
+#define PLDM_QUERY_DOWNSTREAM_IDENTIFIERS_RESP_MIN_LEN 12
+
 /** @brief Minimum length of device descriptor, 2 bytes for descriptor type,
  *         2 bytes for descriptor length and atleast 1 byte of descriptor data
  */
@@ -48,6 +67,7 @@
 	PLDM_QUERY_DEVICE_IDENTIFIERS = 0x01,
 	PLDM_GET_FIRMWARE_PARAMETERS = 0x02,
 	PLDM_QUERY_DOWNSTREAM_DEVICES = 0x03,
+	PLDM_QUERY_DOWNSTREAM_IDENTIFIERS = 0x04,
 	PLDM_REQUEST_UPDATE = 0x10,
 	PLDM_PASS_COMPONENT_TABLE = 0x13,
 	PLDM_UPDATE_COMPONENT = 0x14,
@@ -467,6 +487,43 @@
 	bitfield32_t capabilities_during_update;
 } __attribute__((packed));
 
+/** @struct pldm_query_downstream_identifiers_req
+ *
+ *  Structure for QueryDownstreamIdentifiers request defined in Table 16 -
+ *  QueryDownstreamIdentifiers command format in DSP0267_1.1.0
+ */
+struct pldm_query_downstream_identifiers_req {
+	uint32_t data_transfer_handle;
+	uint8_t transfer_operation_flag;
+};
+
+/** @struct pldm_query_downstream_identifiers_resp
+ *
+ *  Structure representing the fixed part of QueryDownstreamIdentifiers response
+ *  defined in Table 16 - QueryDownstreamIdentifiers command format, and
+ *  Table 17 - QueryDownstreamIdentifiers response definition in DSP0267_1.1.0.
+ *
+ *  Squash the two tables into one since the definition of
+ *  Table 17 is `Portion of QueryDownstreamIdentifiers response`
+ */
+struct pldm_query_downstream_identifiers_resp {
+	uint8_t completion_code;
+	uint32_t next_data_transfer_handle;
+	uint8_t transfer_flag;
+	uint32_t downstream_devices_length;
+	uint16_t number_of_downstream_devices;
+};
+
+/** @struct pldm_downstream_device
+ *
+ *  Structure representing downstream device information defined in
+ *  Table 18 - DownstreamDevice definition in DSP0267_1.1.0
+ */
+struct pldm_downstream_device {
+	uint16_t downstream_device_index;
+	uint8_t downstream_descriptor_count;
+};
+
 /** @struct pldm_request_update_req
  *
  *  Structure representing fixed part of Request Update request
@@ -801,6 +858,39 @@
 	const struct pldm_msg *msg, size_t payload_length,
 	struct pldm_query_downstream_devices_resp *resp_data);
 
+/**
+ * @brief Encodes a request message for Query Downstream Identifiers.
+ *
+ * @param[in] instance_id The instance ID of the PLDM entity.
+ * @param[in] data_transfer_handle The handle for the data transfer.
+ * @param[in] transfer_operation_flag The flag indicating the transfer operation.
+ * @param[out] msg Pointer to the PLDM message structure to store the encoded message.
+ * @param[in] payload_length The length of the payload.
+ * @return pldm_completion_codes
+ *
+ * @note Caller is responsible for memory alloc and dealloc of param
+ *        'msg.payload'
+ */
+int encode_query_downstream_identifiers_req(
+	uint8_t instance_id, uint32_t data_transfer_handle,
+	enum transfer_op_flag transfer_operation_flag, struct pldm_msg *msg,
+	size_t payload_length);
+
+/**
+ * @brief Decodes the response message for Querying Downstream Identifiers.
+ * @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.
+ * @return pldm_completion_codes
+ *
+ * @note Caller is responsible for memory alloc and dealloc of pointer params
+ */
+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);
+
 /** @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 ed1816d..4aafadc 100644
--- a/src/firmware_update.c
+++ b/src/firmware_update.c
@@ -86,6 +86,18 @@
 	}
 }
 
+static bool
+is_transfer_operation_flag_valid(enum transfer_op_flag transfer_op_flag)
+{
+	switch (transfer_op_flag) {
+	case PLDM_GET_NEXTPART:
+	case PLDM_GET_FIRSTPART:
+		return true;
+	default:
+		return false;
+	}
+}
+
 /** @brief Check whether ComponentResponse is valid
  *
  *  @return true if ComponentResponse is valid, false if not
@@ -928,6 +940,100 @@
 	return pldm_msgbuf_destroy_consumed(buf);
 }
 
+LIBPLDM_ABI_TESTING
+int encode_query_downstream_identifiers_req(
+	uint8_t instance_id, uint32_t data_transfer_handle,
+	enum transfer_op_flag transfer_operation_flag, struct pldm_msg *msg,
+	size_t payload_length)
+{
+	struct pldm_msgbuf _buf;
+	struct pldm_msgbuf *buf = &_buf;
+	int rc;
+
+	if (msg == NULL) {
+		return PLDM_ERROR_INVALID_DATA;
+	}
+
+	if (!is_transfer_operation_flag_valid(transfer_operation_flag)) {
+		return PLDM_INVALID_TRANSFER_OPERATION_FLAG;
+	}
+
+	struct pldm_header_info header = { 0 };
+	header.instance = instance_id;
+	header.msg_type = PLDM_REQUEST;
+	header.pldm_type = PLDM_FWUP;
+	header.command = PLDM_QUERY_DOWNSTREAM_IDENTIFIERS;
+	rc = pack_pldm_header(&header, &(msg->hdr));
+	if (rc) {
+		return rc;
+	}
+
+	rc = pldm_msgbuf_init(buf, PLDM_QUERY_DOWNSTREAM_IDENTIFIERS_REQ_BYTES,
+			      msg->payload, payload_length);
+	if (rc) {
+		return rc;
+	}
+
+	pldm_msgbuf_insert(buf, data_transfer_handle);
+	// Data correctness has been verified, cast it to 1-byte data directly.
+	pldm_msgbuf_insert(buf, (uint8_t)transfer_operation_flag);
+
+	return pldm_msgbuf_destroy(buf);
+}
+
+LIBPLDM_ABI_TESTING
+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_msgbuf _buf;
+	struct pldm_msgbuf *buf = &_buf;
+	int rc = PLDM_ERROR;
+
+	if (msg == NULL || resp_data == NULL || downstream_devices == 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 PLDM_SUCCESS;
+	}
+
+	if (payload_length < PLDM_QUERY_DOWNSTREAM_IDENTIFIERS_RESP_MIN_LEN) {
+		return PLDM_ERROR_INVALID_LENGTH;
+	}
+
+	pldm_msgbuf_extract(buf, resp_data->next_data_transfer_handle);
+	pldm_msgbuf_extract(buf, resp_data->transfer_flag);
+
+	rc = pldm_msgbuf_extract(buf, resp_data->downstream_devices_length);
+	if (rc) {
+		return rc;
+	}
+
+	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);
+	if (rc) {
+		return rc;
+	}
+	downstream_devices->length = resp_data->downstream_devices_length;
+
+	return pldm_msgbuf_destroy(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 3870fee..493da65 100644
--- a/tests/libpldm_firmware_update_test.cpp
+++ b/tests/libpldm_firmware_update_test.cpp
@@ -1425,6 +1425,236 @@
     EXPECT_EQ(rc, PLDM_ERROR_INVALID_LENGTH);
 }
 
+TEST(QueryDownstreamIdentifiers, goodPathEncodeRequest)
+{
+    constexpr uint8_t instanceId = 1;
+    constexpr uint32_t dataTransferHandle = 0xFFFFFFFF;
+    constexpr enum transfer_op_flag transferOperationFlag = PLDM_GET_FIRSTPART;
+    constexpr size_t payload_length =
+        PLDM_QUERY_DOWNSTREAM_IDENTIFIERS_REQ_BYTES;
+    std::array<uint8_t, hdrSize + payload_length> requestMsg{};
+    auto requestPtr = reinterpret_cast<pldm_msg*>(requestMsg.data());
+
+    auto rc = encode_query_downstream_identifiers_req(
+        instanceId, dataTransferHandle, transferOperationFlag, requestPtr,
+        payload_length);
+    EXPECT_EQ(rc, PLDM_SUCCESS);
+
+    std::array<uint8_t, hdrSize + PLDM_QUERY_DOWNSTREAM_IDENTIFIERS_REQ_BYTES>
+        expectedReq{0x81, 0x05, 0x04, 0xFF, 0xFF, 0xFF, 0xFF, 0x01};
+    EXPECT_EQ(requestMsg, expectedReq);
+}
+
+TEST(QueryDownstreamIdentifiers, encodeRequestInvalidErrorPaths)
+{
+    constexpr uint8_t instanceId = 1;
+    constexpr uint32_t dataTransferHandle = 0x0;
+    constexpr enum transfer_op_flag transferOperationFlag = PLDM_GET_FIRSTPART;
+    constexpr enum transfer_op_flag invalidTransferOperationFlag =
+        PLDM_ACKNOWLEDGEMENT_ONLY;
+    constexpr size_t payload_length =
+        PLDM_QUERY_DOWNSTREAM_IDENTIFIERS_REQ_BYTES;
+    std::array<uint8_t, hdrSize + payload_length> requestMsg{};
+    auto requestPtr = reinterpret_cast<pldm_msg*>(requestMsg.data());
+
+    auto rc = encode_query_downstream_identifiers_req(
+        instanceId, dataTransferHandle, transferOperationFlag, nullptr,
+        payload_length);
+    EXPECT_EQ(rc, PLDM_ERROR_INVALID_DATA);
+
+    rc = encode_query_downstream_identifiers_req(
+        instanceId, dataTransferHandle, transferOperationFlag, requestPtr,
+        payload_length - 1);
+    EXPECT_EQ(rc, PLDM_ERROR_INVALID_LENGTH);
+
+    rc = encode_query_downstream_identifiers_req(instanceId, dataTransferHandle,
+                                                 invalidTransferOperationFlag,
+                                                 requestPtr, payload_length);
+    EXPECT_EQ(rc, PLDM_INVALID_TRANSFER_OPERATION_FLAG);
+}
+
+TEST(QueryDownstreamIdentifiers, goodPathDecodeResponse)
+{
+    // Len is not fixed here taking it as 9, constains 1 downstream device with
+    // 1 descriptor
+    constexpr uint32_t downstreamDevicesLen = 9;
+    constexpr uint8_t complition_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(downstreamDevicesLen);
+    constexpr uint16_t number_of_downstream_devices_resp = 1;
+    std::array<uint8_t, hdrSize +
+                            PLDM_QUERY_DOWNSTREAM_IDENTIFIERS_RESP_MIN_LEN +
+                            downstreamDevicesLen>
+        responseMsg{};
+    int rc = 0;
+
+    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, complition_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);
+
+    /** Filling descriptor data, the correctness of the downstream devices data
+     *  is not checked in this test case so filling with 0xff
+     */
+    std::fill_n(responseMsg.data() + hdrSize +
+                    PLDM_QUERY_DOWNSTREAM_IDENTIFIERS_RESP_MIN_LEN,
+                downstreamDevicesLen, 0xff);
+
+    auto response = reinterpret_cast<pldm_msg*>(responseMsg.data());
+    struct pldm_query_downstream_identifiers_resp resp_data = {};
+    struct variable_field downstreamDevices = {};
+
+    rc = decode_query_downstream_identifiers_resp(
+        response, responseMsg.size() - hdrSize, &resp_data, &downstreamDevices);
+
+    EXPECT_EQ(rc, PLDM_SUCCESS);
+    EXPECT_EQ(resp_data.completion_code, complition_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);
+    EXPECT_EQ(downstreamDevices.length, downstreamDevicesLen);
+    EXPECT_EQ(true,
+              std::equal(downstreamDevices.ptr,
+                         downstreamDevices.ptr + downstreamDevices.length,
+                         responseMsg.begin() + hdrSize +
+                             PLDM_QUERY_DOWNSTREAM_IDENTIFIERS_RESP_MIN_LEN,
+                         responseMsg.end()));
+}
+
+TEST(QueryDownstreamIdentifiers, decodeRequestErrorPaths)
+{
+    std::array<uint8_t, hdrSize + sizeof(uint8_t)> responseMsg{};
+    auto response = reinterpret_cast<pldm_msg*>(responseMsg.data());
+    struct pldm_query_downstream_identifiers_resp resp_data = {};
+    struct variable_field downstreamDevices = {};
+
+    // Test nullptr
+    auto rc = decode_query_downstream_identifiers_resp(
+        nullptr, responseMsg.size() - hdrSize, nullptr, &downstreamDevices);
+    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, responseMsg.size() - hdrSize, &resp_data, &downstreamDevices);
+    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, responseMsg.size() - hdrSize, &resp_data, &downstreamDevices);
+
+    EXPECT_EQ(rc, PLDM_ERROR_INVALID_LENGTH);
+}
+
+TEST(QueryDownstreamIdentifiers, decodeRequestErrorDownstreamDevicesSize)
+{
+    // Len is not fixed here taking it as 9, constains 1 downstream device with
+    // 1 descriptor
+    constexpr uint32_t actualDownstreamDevicesLen = 9;
+    constexpr uint8_t complition_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(actualDownstreamDevicesLen + 1 /* inject error length*/);
+    constexpr uint16_t number_of_downstream_devices_resp = 1;
+    std::array<uint8_t, hdrSize +
+                            PLDM_QUERY_DOWNSTREAM_IDENTIFIERS_RESP_MIN_LEN +
+                            actualDownstreamDevicesLen>
+        responseMsg{};
+    int rc = 0;
+
+    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, complition_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);
+
+    /** Filling descriptor data, the correctness of the downstream devices data
+     *  is not checked in this test case so filling with 0xff
+     */
+    std::fill_n(responseMsg.data() + hdrSize +
+                    PLDM_QUERY_DOWNSTREAM_IDENTIFIERS_RESP_MIN_LEN,
+                actualDownstreamDevicesLen, 0xff);
+
+    auto response = reinterpret_cast<pldm_msg*>(responseMsg.data());
+    struct pldm_query_downstream_identifiers_resp resp_data = {};
+    struct variable_field downstreamDevices = {};
+
+    /** In test mode, this will trigger an assert failure and cause the unit
+     * test to fail if only testing by the rc. Use ASSERT_DEATH to test this
+     * scenario.
+     *
+     *  The 1st parameter is the function under test.
+     *  The 2nd parameter compares the output of the program.
+     */
+    ASSERT_DEATH(
+        decode_query_downstream_identifiers_resp(
+            response, responseMsg.size() - hdrSize, &resp_data,
+            &downstreamDevices),
+        // This error doesn't output any error message, leave it be empty
+        "");
+}
+
+TEST(QueryDownstreamIdentifiers, decodeRequestErrorBufSize)
+{
+    constexpr uint32_t actualDownstreamDevicesLen = 0;
+    constexpr uint16_t number_of_downstream_devices_resp = 1;
+    constexpr uint8_t complition_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(actualDownstreamDevicesLen);
+
+    std::array<uint8_t, hdrSize +
+                            PLDM_QUERY_DOWNSTREAM_IDENTIFIERS_RESP_MIN_LEN -
+                            1 /* Inject error length*/>
+        responseMsg{};
+    int rc = 0;
+
+    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, complition_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);
+    // Inject error buffer size
+    pldm_msgbuf_insert_uint8(buf, (uint8_t)number_of_downstream_devices_resp);
+
+    auto response = reinterpret_cast<pldm_msg*>(responseMsg.data());
+    struct pldm_query_downstream_identifiers_resp resp_data = {};
+    struct variable_field downstreamDevices = {};
+
+    rc = decode_query_downstream_identifiers_resp(
+        response, responseMsg.size() - hdrSize, &resp_data, &downstreamDevices);
+
+    EXPECT_EQ(rc, PLDM_ERROR_INVALID_LENGTH);
+}
+
 TEST(RequestUpdate, goodPathEncodeRequest)
 {
     constexpr uint8_t instanceId = 1;