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..63e03af 100644
--- a/fw-update/inventory_manager.cpp
+++ b/fw-update/inventory_manager.cpp
@@ -19,31 +19,48 @@
 {
     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;
+            sendQueryDeviceIdentifiersRequest(eid);
         }
+        catch (const std::exception& e)
+        {
+            error(
+                "Failed to discover file descriptors for endpoint ID {EID} with {Error}",
+                "EID", eid, "ERROR", e);
+        }
+    }
+}
 
-        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 for endpoint ID {EID} with response code {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} with response code {RC}",
+            "EID", eid, "RC", rc);
+        throw std::runtime_error(
+            "Failed to send QueryDeviceIdentifiers request");
     }
 }
 
@@ -53,7 +70,7 @@
     if (response == nullptr || !respMsgLen)
     {
         error(
-            "No response received for query device identifiers for endpoint ID '{EID}'",
+            "No response received for query device identifiers for endpoint ID {EID}",
             "EID", eid);
         return;
     }
@@ -69,7 +86,7 @@
     if (rc)
     {
         error(
-            "Failed to decode query device identifiers response for endpoint ID '{EID}' and descriptor count '{DESCRIPTOR_COUNT}', response code '{RC}'",
+            "Failed to decode query device identifiers response for endpoint ID {EID} and descriptor count {DESCRIPTOR_COUNT}, response code {RC}",
             "EID", eid, "DESCRIPTOR_COUNT", descriptorCount, "RC", rc);
         return;
     }
@@ -77,7 +94,7 @@
     if (completionCode)
     {
         error(
-            "Failed to query device identifiers response for endpoint ID '{EID}', completion code '{CC}'",
+            "Failed to query device identifiers response for endpoint ID {EID}, completion code {CC}",
             "EID", eid, "CC", completionCode);
         return;
     }
@@ -94,7 +111,7 @@
         if (rc)
         {
             error(
-                "Failed to decode descriptor type {TYPE}, length {LENGTH} and value for endpoint ID '{EID}', response code '{RC}'",
+                "Failed to decode descriptor type {TYPE}, length {LENGTH} and value for endpoint ID {EID}, response code {RC}",
                 "TYPE", descriptorType, "LENGTH", deviceIdentifiersLen, "EID",
                 eid, "RC", rc);
             return;
@@ -119,7 +136,7 @@
             if (rc)
             {
                 error(
-                    "Failed to decode vendor-defined descriptor value for endpoint ID '{EID}', response code '{RC}'",
+                    "Failed to decode vendor-defined descriptor value for endpoint ID {EID}, response code {RC}",
                     "EID", eid, "RC", rc);
                 return;
             }
@@ -148,6 +165,378 @@
     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 for endpoint ID EID {EID} with response code {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 for endpoint ID {EID} with response code {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 for endpoint ID {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 for endpoint ID {EID} with response code {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.
+             */
+            info("Endpoint ID {EID} does not support QueryDownstreamDevices",
+                 "EID", eid);
+            return;
+        default:
+            error(
+                "QueryDownstreamDevices response failed with error completion code for endpoint ID {EID} with completion code {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 for endpoint ID {EID} with {ERROR}",
+                    "EID", eid, "ERROR", e);
+            }
+            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 endpoint ID {EID} with 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 for endpoint ID {EID} with response code {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 for endpoint ID {EID} with response code {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 for endpoint ID {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 for endpoint ID {EID} with response code {RC}",
+            "EID", eid, "RC", rc);
+        return;
+    }
+
+    if (downstreamIds.completion_code)
+    {
+        error(
+            "QueryDownstreamIdentifiers response failed with error completion code for endpoint ID {EID} with completion code {CC}",
+            "EID", eid, "CC", unsigned(downstreamIds.completion_code));
+        return;
+    }
+
+    DownstreamDeviceInfo initialDownstreamDevices{};
+    DownstreamDeviceInfo* downstreamDevices;
+    if (!downstreamDescriptorMap.contains(eid) ||
+        downstreamIds.transfer_flag == PLDM_START ||
+        downstreamIds.transfer_flag == PLDM_START_AND_END)
+    {
+        downstreamDevices = &initialDownstreamDevices;
+    }
+    else
+    {
+        downstreamDevices = &downstreamDescriptorMap.at(eid);
+    }
+
+    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 for endpoint ID {EID} with response code {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 device descriptor for endpoint ID {EID} with response code {RC}",
+                "EID", eid, "RC", rc);
+            return;
+        }
+        downstreamDevices->emplace(dev.downstream_device_index, descriptors);
+    }
+    if (rc)
+    {
+        error(
+            "Failed to decode downstream devices from iterator for endpoint ID {EID} with response code {RC}",
+            "EID", eid, "RC", rc);
+        return;
+    }
+
+    switch (downstreamIds.transfer_flag)
+    {
+        case PLDM_START:
+            downstreamDescriptorMap.insert_or_assign(
+                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.insert_or_assign(
+                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:
+            sendGetDownstreamFirmwareParametersRequest(eid, 0x0,
+                                                       PLDM_GET_FIRSTPART);
+            break;
+    }
+}
+
+void InventoryManager::sendGetDownstreamFirmwareParametersRequest(
+    mctp_eid_t eid, uint32_t dataTransferHandle,
+    enum transfer_op_flag transferOperationFlag)
+{
+    Request requestMsg(sizeof(pldm_msg_hdr) +
+                       PLDM_GET_DOWNSTREAM_FIRMWARE_PARAMETERS_REQ_BYTES);
+    auto instanceId = instanceIdDb.next(eid);
+    auto request = new (requestMsg.data()) pldm_msg;
+    pldm_get_downstream_firmware_parameters_req requestParameters{
+        dataTransferHandle, static_cast<uint8_t>(transferOperationFlag)};
+    auto rc = encode_get_downstream_firmware_parameters_req(
+        instanceId, &requestParameters, request,
+        PLDM_GET_DOWNSTREAM_FIRMWARE_PARAMETERS_REQ_BYTES);
+    if (rc)
+    {
+        instanceIdDb.free(eid, instanceId);
+        error(
+            "Failed to encode query downstream firmware parameters request for endpoint ID {EID} with response code {RC}",
+            "EID", eid, "RC", rc);
+        throw std::runtime_error(
+            "Failed to encode query downstream firmware parameters request");
+    }
+
+    rc = handler.registerRequest(
+        eid, instanceId, PLDM_FWUP, PLDM_QUERY_DOWNSTREAM_FIRMWARE_PARAMETERS,
+        std::move(requestMsg),
+        std::bind_front(&InventoryManager::getDownstreamFirmwareParameters,
+                        this));
+    if (rc)
+    {
+        error(
+            "Failed to send QueryDownstreamFirmwareParameters request for endpoint ID {EID} with response code {RC}",
+            "EID", eid, "RC", rc);
+    }
+}
+
+void InventoryManager::getDownstreamFirmwareParameters(
+    mctp_eid_t eid, const pldm_msg* response, size_t respMsgLen)
+{
+    if (!response || !respMsgLen)
+    {
+        error(
+            "No response received for QueryDownstreamFirmwareParameters for endpoint ID {EID}",
+            "EID", eid);
+        descriptorMap.erase(eid);
+        return;
+    }
+
+    pldm_get_downstream_firmware_parameters_resp resp{};
+    pldm_downstream_device_parameters_iter params{};
+    pldm_downstream_device_parameters_entry entry{};
+
+    auto rc = decode_get_downstream_firmware_parameters_resp(
+        response, respMsgLen, &resp, &params);
+
+    if (rc)
+    {
+        error(
+            "Decoding QueryDownstreamFirmwareParameters response failed for endpoint ID {EID} with response code {RC}",
+            "EID", eid, "RC", rc);
+        return;
+    }
+
+    if (resp.completion_code)
+    {
+        error(
+            "QueryDownstreamFirmwareParameters response failed with error completion code for endpoint ID {EID} with completion code {CC}",
+            "EID", eid, "CC", resp.completion_code);
+        return;
+    }
+
+    foreach_pldm_downstream_device_parameters_entry(params, entry, rc)
+    {
+        // Reserved for upcoming use
+        [[maybe_unused]] variable_field activeCompVerStr{
+            reinterpret_cast<const uint8_t*>(entry.active_comp_ver_str),
+            entry.active_comp_ver_str_len};
+    }
+    if (rc)
+    {
+        error(
+            "Failed to decode downstream device parameters from iterator for endpoint ID {EID} with response code {RC}",
+            "EID", eid, "RC", rc);
+        return;
+    }
+
+    switch (resp.transfer_flag)
+    {
+        case PLDM_START:
+        case PLDM_MIDDLE:
+            sendGetDownstreamFirmwareParametersRequest(
+                eid, resp.next_data_transfer_handle, PLDM_GET_NEXTPART);
+            break;
+    }
+}
+
 void InventoryManager::sendGetFirmwareParametersRequest(mctp_eid_t eid)
 {
     auto instanceId = instanceIdDb.next(eid);
@@ -160,7 +549,7 @@
     {
         instanceIdDb.free(eid, instanceId);
         error(
-            "Failed to encode get firmware parameters req for endpoint ID '{EID}', response code '{RC}'",
+            "Failed to encode get firmware parameters req for endpoint ID {EID}, response code {RC}",
             "EID", eid, "RC", rc);
         return;
     }
@@ -172,7 +561,7 @@
     if (rc)
     {
         error(
-            "Failed to send get firmware parameters request for endpoint ID '{EID}', response code '{RC}'",
+            "Failed to send get firmware parameters request for endpoint ID {EID}, response code {RC}",
             "EID", eid, "RC", rc);
     }
 }
@@ -183,7 +572,7 @@
     if (response == nullptr || !respMsgLen)
     {
         error(
-            "No response received for get firmware parameters for endpoint ID '{EID}'",
+            "No response received for get firmware parameters for endpoint ID {EID}",
             "EID", eid);
         descriptorMap.erase(eid);
         return;
@@ -200,7 +589,7 @@
     if (rc)
     {
         error(
-            "Failed to decode get firmware parameters response for endpoint ID '{EID}', response code '{RC}'",
+            "Failed to decode get firmware parameters response for endpoint ID {EID}, response code {RC}",
             "EID", eid, "RC", rc);
         return;
     }
@@ -209,7 +598,7 @@
     {
         auto fw_param_cc = fwParams.completion_code;
         error(
-            "Failed to get firmware parameters response for endpoint ID '{EID}', completion code '{CC}'",
+            "Failed to get firmware parameters response for endpoint ID {EID}, completion code {CC}",
             "EID", eid, "CC", fw_param_cc);
         return;
     }
@@ -229,7 +618,7 @@
         if (rc)
         {
             error(
-                "Failed to decode component parameter table entry for endpoint ID '{EID}', response code '{RC}'",
+                "Failed to decode component parameter table entry for endpoint ID {EID}, response code {RC}",
                 "EID", eid, "RC", rc);
             return;
         }