fw_update: InventoryManager: Add Downstream Devices Support

In DSP0267_1.1.0, a Firmware Device can supports one or more
Downstream Devices to perform get firmware version or firmware
update. Add discovery of Downstream Devices, query the Downstream
Devices Descriptor if the Firmware Device support it.

The code is developed based on DSP0267_1.1.0 Section 10.3
QueryDownstreamDevices command format, Section 10.4
QueryDownstreamIdentifiers command format.

Tested: Add unit tests for the new command handlers.

Change-Id: Iad28b898b5a0799b2b145d38958bba78e9719f4e
Signed-off-by: Unive Tien <unive.tien.wiwynn@gmail.com>
diff --git a/fw-update/inventory_manager.cpp b/fw-update/inventory_manager.cpp
index 43508a7..4101624 100644
--- a/fw-update/inventory_manager.cpp
+++ b/fw-update/inventory_manager.cpp
@@ -19,31 +19,47 @@
 {
     for (const auto& eid : eids)
     {
-        auto instanceId = instanceIdDb.next(eid);
-        Request 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);
-        if (rc)
+        try
         {
-            instanceIdDb.free(eid, instanceId);
-            error(
-                "Failed to encode query device identifiers req for endpoint ID '{EID}', response code '{RC}'",
-                "EID", eid, "RC", rc);
-            continue;
+            sendQueryDownstreamDevicesRequest(eid);
         }
+        catch (const std::exception& e)
+        {
+            error("Failed to discover FDs, EID={EID}, Error={ERROR}", "EID",
+                  eid, "ERROR", e.what());
+        }
+    }
+}
 
-        rc = handler.registerRequest(
-            eid, instanceId, PLDM_FWUP, PLDM_QUERY_DEVICE_IDENTIFIERS,
-            std::move(requestMsg),
-            std::bind_front(&InventoryManager::queryDeviceIdentifiers, this));
-        if (rc)
-        {
-            error(
-                "Failed to send query device identifiers request for endpoint ID '{EID}', response code '{RC}'",
-                "EID", eid, "RC", rc);
-        }
+void InventoryManager::sendQueryDeviceIdentifiersRequest(mctp_eid_t eid)
+{
+    auto instanceId = instanceIdDb.next(eid);
+    Request requestMsg(
+        sizeof(pldm_msg_hdr) + PLDM_QUERY_DEVICE_IDENTIFIERS_REQ_BYTES);
+    auto request = new (requestMsg.data()) pldm_msg;
+    auto rc = encode_query_device_identifiers_req(
+        instanceId, PLDM_QUERY_DEVICE_IDENTIFIERS_REQ_BYTES, request);
+    if (rc)
+    {
+        instanceIdDb.free(eid, instanceId);
+        error(
+            "Failed to encode query device identifiers request, EID={EID}, RC = {RC}",
+            "EID", eid, "RC", rc);
+        throw std::runtime_error(
+            "Failed to encode QueryDeviceIdentifiers request");
+    }
+
+    rc = handler.registerRequest(
+        eid, instanceId, PLDM_FWUP, PLDM_QUERY_DEVICE_IDENTIFIERS,
+        std::move(requestMsg),
+        std::bind_front(&InventoryManager::queryDeviceIdentifiers, this));
+    if (rc)
+    {
+        error(
+            "Failed to send query device identifiers request for endpoint ID '{EID}', response code '{RC}'",
+            "EID", eid, "RC", rc);
+        throw std::runtime_error(
+            "Failed to send QueryDeviceIdentifiers request");
     }
 }
 
@@ -148,6 +164,278 @@
     sendGetFirmwareParametersRequest(eid);
 }
 
+void InventoryManager::sendQueryDownstreamDevicesRequest(mctp_eid_t eid)
+{
+    Request requestMsg(sizeof(pldm_msg_hdr));
+    auto instanceId = instanceIdDb.next(eid);
+    auto request = new (requestMsg.data()) pldm_msg;
+    auto rc = encode_query_downstream_devices_req(instanceId, request);
+    if (rc)
+    {
+        instanceIdDb.free(eid, instanceId);
+        error(
+            "Failed to encode query downstream devices request, EID={EID}, RC = {RC}",
+            "EID", eid, "RC", rc);
+        throw std::runtime_error(
+            "Failed to encode query downstream devices request");
+    }
+
+    rc = handler.registerRequest(
+        eid, instanceId, PLDM_FWUP, PLDM_QUERY_DOWNSTREAM_DEVICES,
+        std::move(requestMsg),
+        std::bind_front(&InventoryManager::queryDownstreamDevices, this));
+    if (rc)
+    {
+        error(
+            "Failed to send QueryDownstreamDevices request, EID={EID}, RC = {RC}",
+            "EID", eid, "RC", rc);
+    }
+}
+
+void InventoryManager::queryDownstreamDevices(
+    mctp_eid_t eid, const pldm_msg* response, size_t respMsgLen)
+{
+    if (!response || !respMsgLen)
+    {
+        error("No response received for QueryDownstreamDevices, EID={EID}",
+              "EID", eid);
+        return;
+    }
+
+    pldm_query_downstream_devices_resp downstreamDevicesResp{};
+    auto rc = decode_query_downstream_devices_resp(response, respMsgLen,
+                                                   &downstreamDevicesResp);
+    if (rc)
+    {
+        error(
+            "Decoding QueryDownstreamDevices response failed, EID={EID}, RC = {RC}",
+            "EID", eid, "RC", rc);
+        return;
+    }
+
+    switch (downstreamDevicesResp.completion_code)
+    {
+        case PLDM_SUCCESS:
+            break;
+        case PLDM_ERROR_UNSUPPORTED_PLDM_CMD:
+            /* QueryDownstreamDevices is optional, consider the device does not
+             * support Downstream Devices.
+             */
+            return;
+        default:
+            error(
+                "QueryDownstreamDevices response failed with error completion code, EID={EID}, CC = {CC}",
+                "EID", eid, "CC", downstreamDevicesResp.completion_code);
+            return;
+    }
+
+    switch (downstreamDevicesResp.downstream_device_update_supported)
+    {
+        case PLDM_FWUP_DOWNSTREAM_DEVICE_UPDATE_SUPPORTED:
+            /** DataTransferHandle will be skipped when TransferOperationFlag is
+             *  `GetFirstPart`. Use 0x0 as default by following example in
+             *  Figure 9 in DSP0267 1.1.0
+             */
+            try
+            {
+                sendQueryDownstreamIdentifiersRequest(eid, 0x0,
+                                                      PLDM_GET_FIRSTPART);
+            }
+            catch (const std::exception& e)
+            {
+                error(
+                    "Failed to send QueryDownstreamIdentifiers request, EID={EID}, Error={ERROR}",
+                    "EID", eid, "ERROR", e.what());
+            }
+            break;
+        case PLDM_FWUP_DOWNSTREAM_DEVICE_UPDATE_NOT_SUPPORTED:
+            /* The FDP does not support firmware updates but may report
+             * inventory information on downstream devices.
+             * In this scenario, sends only GetDownstreamFirmwareParameters
+             * to the FDP.
+             * The definition can be found at Table 15 of DSP0267_1.1.0
+             */
+            break;
+        default:
+            error(
+                "Unknown response of DownstreamDeviceUpdateSupported from EID={EID}, Value = {VALUE}",
+                "EID", eid, "VALUE",
+                downstreamDevicesResp.downstream_device_update_supported);
+            return;
+    }
+}
+
+void InventoryManager::sendQueryDownstreamIdentifiersRequest(
+    mctp_eid_t eid, uint32_t dataTransferHandle,
+    enum transfer_op_flag transferOperationFlag)
+{
+    auto instanceId = instanceIdDb.next(eid);
+    Request requestMsg(
+        sizeof(pldm_msg_hdr) + PLDM_QUERY_DOWNSTREAM_IDENTIFIERS_REQ_BYTES);
+    auto request = new (requestMsg.data()) pldm_msg;
+    pldm_query_downstream_identifiers_req requestParameters{
+        dataTransferHandle, static_cast<uint8_t>(transferOperationFlag)};
+
+    auto rc = encode_query_downstream_identifiers_req(
+        instanceId, &requestParameters, request,
+        PLDM_QUERY_DOWNSTREAM_IDENTIFIERS_REQ_BYTES);
+    if (rc)
+    {
+        instanceIdDb.free(eid, instanceId);
+        error(
+            "Failed to encode query downstream identifiers request, EID={EID}, RC = {RC}",
+            "EID", eid, "RC", rc);
+        throw std::runtime_error(
+            "Failed to encode query downstream identifiers request");
+    }
+
+    rc = handler.registerRequest(
+        eid, instanceId, PLDM_FWUP, PLDM_QUERY_DOWNSTREAM_IDENTIFIERS,
+        std::move(requestMsg),
+        std::bind_front(&InventoryManager::queryDownstreamIdentifiers, this));
+    if (rc)
+    {
+        error(
+            "Failed to send QueryDownstreamIdentifiers request, EID={EID}, RC = {RC}",
+            "EID", eid, "RC", rc);
+    }
+}
+
+void InventoryManager::queryDownstreamIdentifiers(
+    mctp_eid_t eid, const pldm_msg* response, size_t respMsgLen)
+{
+    if (!response || !respMsgLen)
+    {
+        error("No response received for QueryDownstreamIdentifiers, EID={EID}",
+              "EID", eid);
+        descriptorMap.erase(eid);
+        return;
+    }
+
+    pldm_query_downstream_identifiers_resp downstreamIds{};
+    pldm_downstream_device_iter devs{};
+
+    auto rc = decode_query_downstream_identifiers_resp(response, respMsgLen,
+                                                       &downstreamIds, &devs);
+    if (rc)
+    {
+        error(
+            "Decoding QueryDownstreamIdentifiers response failed, EID={EID}, RC = {RC}",
+            "EID", eid, "RC", rc);
+        return;
+    }
+
+    if (downstreamIds.completion_code)
+    {
+        error(
+            "QueryDownstreamIdentifiers response failed with error completion code, EID={EID}, CC = {CC}",
+            "EID", eid, "CC", unsigned(downstreamIds.completion_code));
+        return;
+    }
+
+    DownstreamDevices initialDownstreamDevices{};
+    DownstreamDevices* downstreamDevices;
+    switch (downstreamIds.transfer_flag)
+    {
+        case PLDM_MIDDLE:
+        case PLDM_END:
+            downstreamDevices = &downstreamDescriptorMap.at(eid);
+            break;
+        default:
+            downstreamDevices = &initialDownstreamDevices;
+            break;
+    }
+
+    pldm_downstream_device dev;
+    foreach_pldm_downstream_device(devs, dev, rc)
+    {
+        pldm_descriptor desc;
+        Descriptors descriptors{};
+        foreach_pldm_downstream_device_descriptor(devs, dev, desc, rc)
+        {
+            const auto descriptorData =
+                new (const_cast<void*>(desc.descriptor_data))
+                    uint8_t[desc.descriptor_length];
+            if (desc.descriptor_type != PLDM_FWUP_VENDOR_DEFINED)
+            {
+                std::vector<uint8_t> descData(
+                    descriptorData, descriptorData + desc.descriptor_length);
+                descriptors.emplace(desc.descriptor_type, std::move(descData));
+            }
+            else
+            {
+                uint8_t descriptorTitleStrType = 0;
+                variable_field descriptorTitleStr{};
+                variable_field vendorDefinedDescriptorData{};
+
+                rc = decode_vendor_defined_descriptor_value(
+                    descriptorData, desc.descriptor_length,
+                    &descriptorTitleStrType, &descriptorTitleStr,
+                    &vendorDefinedDescriptorData);
+
+                if (rc)
+                {
+                    error(
+                        "Decoding Vendor-defined descriptor value failed, EID={EID}, RC = {RC}",
+                        "EID", eid, "RC", rc);
+                    return;
+                }
+
+                auto vendorDefinedDescriptorTitleStr =
+                    utils::toString(descriptorTitleStr);
+                std::vector<uint8_t> vendorDescData(
+                    vendorDefinedDescriptorData.ptr,
+                    vendorDefinedDescriptorData.ptr +
+                        vendorDefinedDescriptorData.length);
+                descriptors.emplace(
+                    desc.descriptor_type,
+                    std::make_tuple(vendorDefinedDescriptorTitleStr,
+                                    vendorDescData));
+            }
+        }
+        if (rc)
+        {
+            error(
+                "Failed to decode downstream descriptors from iterator, EID={EID}, RC = {RC}",
+                "EID", eid, "RC", rc);
+            return;
+        }
+        downstreamDevices->emplace_back(
+            DownstreamDeviceInfo{dev.downstream_device_index, descriptors});
+    }
+    if (rc)
+    {
+        error(
+            "Failed to decode downstream devices from iterator, EID={EID}, RC = {RC}",
+            "EID", eid, "RC", rc);
+        return;
+    }
+
+    switch (downstreamIds.transfer_flag)
+    {
+        case PLDM_START:
+            downstreamDescriptorMap.emplace(
+                eid, std::move(initialDownstreamDevices));
+            [[fallthrough]];
+        case PLDM_MIDDLE:
+            sendQueryDownstreamIdentifiersRequest(
+                eid, downstreamIds.next_data_transfer_handle,
+                PLDM_GET_NEXTPART);
+            break;
+        case PLDM_START_AND_END:
+            downstreamDescriptorMap.emplace(
+                eid, std::move(initialDownstreamDevices));
+            /** DataTransferHandle will be skipped when TransferOperationFlag is
+             *  `GetFirstPart`. Use 0x0 as default by following example in
+             *  Figure 9 in DSP0267 1.1.0
+             */
+            [[fallthrough]];
+        case PLDM_END:
+            sendQueryDeviceIdentifiersRequest(eid);
+            break;
+    }
+}
+
 void InventoryManager::sendGetFirmwareParametersRequest(mctp_eid_t eid)
 {
     auto instanceId = instanceIdDb.next(eid);