fwupdate: pldmtool - add QueryDeviceIdentifiers

QueryDeviceIdentifiers is used to determine if
the firmware update package is applicable for
updating a specific FD by comparing device
identifier records in the package header to those
obtained from the FD.This patch adds support for
QueryDeviceIdentifiers in pldmtool.

Command Usage:

```
pldmtool fw_update QueryDeviceIdentifiers -m <EID>

pldmtool fw_update QueryDeviceIdentifiers -m 0
{
    "EID": 0,
    "Descriptors": [
        {
            "Type": "IANA Enterprise ID",
            "Value": [
                "58270000"
            ]
        },
        {
            "Type": "UUID",
            "Value": [
                "273034c83ec4511695f548701e48d764"
            ]
        },
        {
            "Type": "Vendor Defined",
            "Value": [
                {
                    "TESTID1": "30"
                },
                {
                    "TESTID2": "40"
                }
            ]
        }
    ]
}

```

Unit Tests:
  1. Verify QueryDeviceIdentifiers response with single descriptors
  2. Verify QueryDeviceIdentifiers response with multiple descriptors of
     same type
  3. Compare query device identifiers with raw response from FD

Signed-off-by: Chinmay Shripad Hegde <hosmanechinmay@gmail.com>
Change-Id: I28b7a368138803dd4407edd06667d8be8b801a2c
diff --git a/pldmtool/pldm_cmd_helper.hpp b/pldmtool/pldm_cmd_helper.hpp
index ef33bcb..6f185dd 100644
--- a/pldmtool/pldm_cmd_helper.hpp
+++ b/pldmtool/pldm_cmd_helper.hpp
@@ -99,6 +99,16 @@
     int pldmSendRecv(std::vector<uint8_t>& requestMsg,
                      std::vector<uint8_t>& responseMsg);
 
+    /**
+     * @brief get MCTP endpoint ID
+     *
+     * @return uint8_t - MCTP endpoint ID
+     */
+    inline uint8_t getMCTPEID()
+    {
+        return mctp_eid;
+    }
+
   private:
     const std::string pldmType;
     const std::string commandName;
diff --git a/pldmtool/pldm_fw_update_cmd.cpp b/pldmtool/pldm_fw_update_cmd.cpp
index 30491ef..bf03302 100644
--- a/pldmtool/pldm_fw_update_cmd.cpp
+++ b/pldmtool/pldm_fw_update_cmd.cpp
@@ -15,6 +15,7 @@
 {
 
 using namespace pldmtool::helper;
+using namespace pldm::fw_update;
 
 std::vector<std::unique_ptr<CommandInterface>> commands;
 
@@ -53,6 +54,24 @@
     {PLDM_FD_TIMEOUT_VERIFY, "Timeout occurred when in VERIFY state"},
     {PLDM_FD_TIMEOUT_APPLY, "Timeout occurred when in APPLY state"}};
 
+/**
+ * @brief descriptor type to name mapping
+ *
+ */
+const std::map<DescriptorType, const char*> descriptorName{
+    {PLDM_FWUP_PCI_VENDOR_ID, "PCI Vendor ID"},
+    {PLDM_FWUP_IANA_ENTERPRISE_ID, "IANA Enterprise ID"},
+    {PLDM_FWUP_UUID, "UUID"},
+    {PLDM_FWUP_PNP_VENDOR_ID, "PnP Vendor ID"},
+    {PLDM_FWUP_ACPI_VENDOR_ID, "ACPI Vendor ID"},
+    {PLDM_FWUP_PCI_DEVICE_ID, "PCI Device ID"},
+    {PLDM_FWUP_PCI_SUBSYSTEM_VENDOR_ID, "PCI Subsystem Vendor ID"},
+    {PLDM_FWUP_PCI_SUBSYSTEM_ID, "PCI Subsystem ID"},
+    {PLDM_FWUP_PCI_REVISION_ID, "PCI Revision ID"},
+    {PLDM_FWUP_PNP_PRODUCT_IDENTIFIER, "PnP Product Identifier"},
+    {PLDM_FWUP_ACPI_PRODUCT_IDENTIFIER, "ACPI Product Identifier"},
+    {PLDM_FWUP_VENDOR_DEFINED, "Vendor Defined"}};
+
 class GetStatus : public CommandInterface
 {
   public:
@@ -389,6 +408,209 @@
     }
 };
 
+class QueryDeviceIdentifiers : public CommandInterface
+{
+  public:
+    ~QueryDeviceIdentifiers() = default;
+    QueryDeviceIdentifiers() = delete;
+    QueryDeviceIdentifiers(const QueryDeviceIdentifiers&) = delete;
+    QueryDeviceIdentifiers(QueryDeviceIdentifiers&&) = default;
+    QueryDeviceIdentifiers& operator=(const QueryDeviceIdentifiers&) = delete;
+    QueryDeviceIdentifiers& operator=(QueryDeviceIdentifiers&&) = default;
+
+    /**
+     * @brief Implementation of createRequestMsg for QueryDeviceIdentifiers
+     *
+     * @return std::pair<int, std::vector<uint8_t>>
+     */
+    std::pair<int, std::vector<uint8_t>> createRequestMsg() override;
+
+    /**
+     * @brief Implementation of parseResponseMsg for QueryDeviceIdentifiers
+     *
+     * @param[in] responsePtr
+     * @param[in] payloadLength
+     */
+    void parseResponseMsg(pldm_msg* responsePtr, size_t payloadLength) override;
+    using CommandInterface::CommandInterface;
+
+  private:
+    /**
+     * @brief Method to update QueryDeviceIdentifiers json response in a user
+     * friendly format
+     *
+     * @param[in] descriptors - descriptor json response
+     * @param[in] descriptorType - descriptor type
+     * @param[in] descriptorVal - descriptor value
+     */
+    void updateDescriptor(
+        ordered_json& descriptors, const DescriptorType& descriptorType,
+        const std::variant<DescriptorData, VendorDefinedDescriptorInfo>&
+            descriptorVal);
+};
+
+void QueryDeviceIdentifiers::updateDescriptor(
+    ordered_json& descriptors, const DescriptorType& descriptorType,
+    const std::variant<DescriptorData, VendorDefinedDescriptorInfo>&
+        descriptorVal)
+{
+    std::ostringstream descDataStream;
+    DescriptorData descData;
+    if (descriptorType != PLDM_FWUP_VENDOR_DEFINED)
+    {
+        descData = std::get<DescriptorData>(descriptorVal);
+    }
+    else
+    {
+        descData = std::get<VendorDefinedDescriptorData>(
+            std::get<VendorDefinedDescriptorInfo>(descriptorVal));
+    }
+    for (int byte : descData)
+    {
+        descDataStream << std::setfill('0') << std::setw(2) << std::hex << byte;
+    }
+
+    if (descriptorName.contains(descriptorType))
+    {
+        // Update the existing json response if entry is already present
+        for (auto& descriptor : descriptors)
+        {
+            if (descriptor["Type"] == descriptorName.at(descriptorType))
+            {
+                if (descriptorType != PLDM_FWUP_VENDOR_DEFINED)
+                {
+                    descriptor["Value"].emplace_back(descDataStream.str());
+                }
+                else
+                {
+                    ordered_json vendorDefinedVal;
+                    vendorDefinedVal[std::get<VendorDefinedDescriptorTitle>(
+                        std::get<VendorDefinedDescriptorInfo>(descriptorVal))] =
+                        descDataStream.str();
+                    descriptor["Value"].emplace_back(vendorDefinedVal);
+                }
+                return;
+            }
+        }
+        // Entry is not present, add type and value to json response
+        ordered_json descriptor =
+            ordered_json::object({{"Type", descriptorName.at(descriptorType)},
+                                  {"Value", ordered_json::array()}});
+        if (descriptorType != PLDM_FWUP_VENDOR_DEFINED)
+        {
+            descriptor["Value"].emplace_back(descDataStream.str());
+        }
+        else
+        {
+            ordered_json vendorDefinedVal;
+            vendorDefinedVal[std::get<VendorDefinedDescriptorTitle>(
+                std::get<VendorDefinedDescriptorInfo>(descriptorVal))] =
+                descDataStream.str();
+            descriptor["Value"].emplace_back(vendorDefinedVal);
+        }
+        descriptors.emplace_back(descriptor);
+    }
+    else
+    {
+        std::cerr << "Unknown descriptor type, type=" << descriptorType << "\n";
+    }
+}
+std::pair<int, std::vector<uint8_t>> QueryDeviceIdentifiers::createRequestMsg()
+{
+    std::vector<uint8_t> requestMsg(sizeof(pldm_msg_hdr) +
+                                    PLDM_QUERY_DEVICE_IDENTIFIERS_REQ_BYTES);
+    auto request = reinterpret_cast<pldm_msg*>(requestMsg.data());
+    auto rc = encode_query_device_identifiers_req(
+        instanceId, PLDM_QUERY_DEVICE_IDENTIFIERS_REQ_BYTES, request);
+    return {rc, requestMsg};
+}
+
+void QueryDeviceIdentifiers::parseResponseMsg(pldm_msg* responsePtr,
+                                              size_t payloadLength)
+{
+    uint8_t completionCode = PLDM_SUCCESS;
+    uint32_t deviceIdentifiersLen = 0;
+    uint8_t descriptorCount = 0;
+    uint8_t* descriptorPtr = nullptr;
+    uint8_t eid = getMCTPEID();
+    auto rc = decode_query_device_identifiers_resp(
+        responsePtr, payloadLength, &completionCode, &deviceIdentifiersLen,
+        &descriptorCount, &descriptorPtr);
+    if (rc)
+    {
+        std::cerr << "Decoding QueryDeviceIdentifiers response failed,EID="
+                  << unsigned(eid) << ", RC=" << rc << "\n";
+        return;
+    }
+    if (completionCode)
+    {
+        std::cerr << "QueryDeviceIdentifiers response failed with error "
+                     "completion code, EID="
+                  << unsigned(eid) << ", CC=" << unsigned(completionCode)
+                  << "\n";
+        return;
+    }
+    ordered_json data;
+    data["EID"] = eid;
+    ordered_json descriptors;
+    while (descriptorCount-- && (deviceIdentifiersLen > 0))
+    {
+        DescriptorType descriptorType = 0;
+        variable_field descriptorData{};
+
+        rc = decode_descriptor_type_length_value(
+            descriptorPtr, deviceIdentifiersLen, &descriptorType,
+            &descriptorData);
+        if (rc)
+        {
+            std::cerr << "Decoding descriptor type, length and value failed,"
+                      << "EID=" << unsigned(eid) << ",RC=" << rc << "\n ";
+            return;
+        }
+
+        if (descriptorType != PLDM_FWUP_VENDOR_DEFINED)
+        {
+            std::vector<uint8_t> descData(
+                descriptorData.ptr, descriptorData.ptr + descriptorData.length);
+            updateDescriptor(descriptors, descriptorType, descData);
+        }
+        else
+        {
+            uint8_t descriptorTitleStrType = 0;
+            variable_field descriptorTitleStr{};
+            variable_field vendorDefinedDescriptorData{};
+
+            rc = decode_vendor_defined_descriptor_value(
+                descriptorData.ptr, descriptorData.length,
+                &descriptorTitleStrType, &descriptorTitleStr,
+                &vendorDefinedDescriptorData);
+            if (rc)
+            {
+                std::cerr << "Decoding Vendor-defined descriptor value"
+                          << "failed EID=" << unsigned(eid) << ", RC=" << rc
+                          << "\n ";
+                return;
+            }
+
+            auto vendorDescTitle = pldm::utils::toString(descriptorTitleStr);
+            std::vector<uint8_t> vendorDescData(
+                vendorDefinedDescriptorData.ptr,
+                vendorDefinedDescriptorData.ptr +
+                    vendorDefinedDescriptorData.length);
+            updateDescriptor(descriptors, descriptorType,
+                             std::make_tuple(vendorDescTitle, vendorDescData));
+        }
+        auto nextDescriptorOffset =
+            sizeof(pldm_descriptor_tlv().descriptor_type) +
+            sizeof(pldm_descriptor_tlv().descriptor_length) +
+            descriptorData.length;
+        descriptorPtr += nextDescriptorOffset;
+        deviceIdentifiersLen -= nextDescriptorOffset;
+    }
+    data["Descriptors"] = descriptors;
+    pldmtool::helper::DisplayInJson(data);
+}
+
 void registerCommand(CLI::App& app)
 {
     auto fwUpdate =
@@ -403,6 +625,11 @@
         "GetFwParams", "To get the component details of the FD");
     commands.push_back(
         std::make_unique<GetFwParams>("fw_update", "GetFwParams", getFwParams));
+
+    auto queryDeviceIdentifiers = fwUpdate->add_subcommand(
+        "QueryDeviceIdentifiers", "To query device identifiers of the FD");
+    commands.push_back(std::make_unique<QueryDeviceIdentifiers>(
+        "fw_update", "QueryDeviceIdentifiers", queryDeviceIdentifiers));
 }
 
 } // namespace fw_update