/**
 * Copyright © 2019 IBM Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "data_interface.hpp"

#include "util.hpp"

#include <phosphor-logging/lg2.hpp>
#include <xyz/openbmc_project/State/BMC/server.hpp>
#include <xyz/openbmc_project/State/Boot/Progress/server.hpp>

// Use a timeout of 10s for D-Bus calls so if there are
// timeouts the callers of the PEL creation method won't
// also timeout.
constexpr auto dbusTimeout = 10000000;

namespace openpower
{
namespace pels
{

namespace service_name
{
constexpr auto objectMapper = "xyz.openbmc_project.ObjectMapper";
constexpr auto vpdManager = "com.ibm.VPD.Manager";
constexpr auto ledGroupManager = "xyz.openbmc_project.LED.GroupManager";
constexpr auto hwIsolation = "org.open_power.HardwareIsolation";
constexpr auto biosConfigMgr = "xyz.openbmc_project.BIOSConfigManager";
constexpr auto bootRawProgress = "xyz.openbmc_project.State.Boot.Raw";
constexpr auto pldm = "xyz.openbmc_project.PLDM";
constexpr auto inventoryManager = "xyz.openbmc_project.Inventory.Manager";
constexpr auto entityManager = "xyz.openbmc_project.EntityManager";
} // namespace service_name

namespace object_path
{
constexpr auto objectMapper = "/xyz/openbmc_project/object_mapper";
constexpr auto systemInv = "/xyz/openbmc_project/inventory/system";
constexpr auto motherBoardInv =
    "/xyz/openbmc_project/inventory/system/chassis/motherboard";
constexpr auto baseInv = "/xyz/openbmc_project/inventory";
constexpr auto bmcState = "/xyz/openbmc_project/state/bmc0";
constexpr auto chassisState = "/xyz/openbmc_project/state/chassis0";
constexpr auto hostState = "/xyz/openbmc_project/state/host0";
constexpr auto enableHostPELs =
    "/xyz/openbmc_project/logging/send_event_logs_to_host";
constexpr auto vpdManager = "/com/ibm/VPD/Manager";
constexpr auto logSetting = "/xyz/openbmc_project/logging/settings";
constexpr auto hwIsolation = "/xyz/openbmc_project/hardware_isolation";
constexpr auto biosConfigMgr = "/xyz/openbmc_project/bios_config/manager";
constexpr auto bootRawProgress = "/xyz/openbmc_project/state/boot/raw0";
} // namespace object_path

namespace interface
{
constexpr auto dbusProperty = "org.freedesktop.DBus.Properties";
constexpr auto objectMapper = "xyz.openbmc_project.ObjectMapper";
constexpr auto invAsset = "xyz.openbmc_project.Inventory.Decorator.Asset";
constexpr auto bootProgress = "xyz.openbmc_project.State.Boot.Progress";
constexpr auto enable = "xyz.openbmc_project.Object.Enable";
constexpr auto bmcState = "xyz.openbmc_project.State.BMC";
constexpr auto chassisState = "xyz.openbmc_project.State.Chassis";
constexpr auto hostState = "xyz.openbmc_project.State.Host";
constexpr auto viniRecordVPD = "com.ibm.ipzvpd.VINI";
constexpr auto vsbpRecordVPD = "com.ibm.ipzvpd.VSBP";
constexpr auto locCode = "xyz.openbmc_project.Inventory.Decorator.LocationCode";
constexpr auto compatible =
    "xyz.openbmc_project.Inventory.Decorator.Compatible";
constexpr auto vpdManager = "com.ibm.VPD.Manager";
constexpr auto ledGroup = "xyz.openbmc_project.Led.Group";
constexpr auto operationalStatus =
    "xyz.openbmc_project.State.Decorator.OperationalStatus";
constexpr auto logSetting = "xyz.openbmc_project.Logging.Settings";
constexpr auto associationDef = "xyz.openbmc_project.Association.Definitions";
constexpr auto dumpEntry = "xyz.openbmc_project.Dump.Entry";
constexpr auto dumpProgress = "xyz.openbmc_project.Common.Progress";
constexpr auto hwIsolationCreate = "org.open_power.HardwareIsolation.Create";
constexpr auto hwIsolationEntry = "xyz.openbmc_project.HardwareIsolation.Entry";
constexpr auto association = "xyz.openbmc_project.Association";
constexpr auto biosConfigMgr = "xyz.openbmc_project.BIOSConfig.Manager";
constexpr auto bootRawProgress = "xyz.openbmc_project.State.Boot.Raw";
constexpr auto invItem = "xyz.openbmc_project.Inventory.Item";
constexpr auto invFan = "xyz.openbmc_project.Inventory.Item.Fan";
constexpr auto invPowerSupply =
    "xyz.openbmc_project.Inventory.Item.PowerSupply";
constexpr auto inventoryManager = "xyz.openbmc_project.Inventory.Manager";
} // namespace interface

using namespace sdbusplus::server::xyz::openbmc_project::state::boot;
using namespace sdbusplus::server::xyz::openbmc_project::state;
namespace match_rules = sdbusplus::bus::match::rules;

const DBusInterfaceList hotplugInterfaces{interface::invFan,
                                          interface::invPowerSupply};

std::pair<std::string, std::string>
    DataInterfaceBase::extractConnectorFromLocCode(
        const std::string& locationCode)
{
    auto base = locationCode;
    std::string connector{};

    auto pos = base.find("-T");
    if (pos != std::string::npos)
    {
        connector = base.substr(pos);
        base = base.substr(0, pos);
    }

    return {base, connector};
}

DataInterface::DataInterface(sdbusplus::bus_t& bus) : _bus(bus)
{
    readBMCFWVersion();
    readServerFWVersion();
    readBMCFWVersionID();

    // Watch the BootProgress property
    _properties.emplace_back(std::make_unique<PropertyWatcher<DataInterface>>(
        bus, object_path::hostState, interface::bootProgress, "BootProgress",
        *this, [this](const auto& value) {
        this->_bootState = std::get<std::string>(value);
        auto status = Progress::convertProgressStagesFromString(
            std::get<std::string>(value));

        if ((status == Progress::ProgressStages::SystemInitComplete) ||
            (status == Progress::ProgressStages::OSRunning))
        {
            setHostUp(true);
        }
        else
        {
            setHostUp(false);
        }
    }));

    // Watch the host PEL enable property
    _properties.emplace_back(std::make_unique<PropertyWatcher<DataInterface>>(
        bus, object_path::enableHostPELs, interface::enable, "Enabled", *this,
        [this](const auto& value) {
        if (std::get<bool>(value) != this->_sendPELsToHost)
        {
            lg2::info("The send PELs to host setting changed to {VAL}", "VAL",
                      std::get<bool>(value));
        }
        this->_sendPELsToHost = std::get<bool>(value);
    }));

    // Watch the BMCState property
    _properties.emplace_back(std::make_unique<PropertyWatcher<DataInterface>>(
        bus, object_path::bmcState, interface::bmcState, "CurrentBMCState",
        *this, [this](const auto& value) {
        const auto& state = std::get<std::string>(value);
        this->_bmcState = state;

        // Wait for BMC ready to start watching for
        // plugs so things calm down first.
        if (BMC::convertBMCStateFromString(state) == BMC::BMCState::Ready)
        {
            startFruPlugWatch();
        }
    }));

    // Watch the chassis current and requested power state properties
    _properties.emplace_back(std::make_unique<InterfaceWatcher<DataInterface>>(
        bus, object_path::chassisState, interface::chassisState, *this,
        [this](const auto& properties) {
        auto state = properties.find("CurrentPowerState");
        if (state != properties.end())
        {
            this->_chassisState = std::get<std::string>(state->second);
        }

        auto trans = properties.find("RequestedPowerTransition");
        if (trans != properties.end())
        {
            this->_chassisTransition = std::get<std::string>(trans->second);
        }
    }));

    // Watch the CurrentHostState property
    _properties.emplace_back(std::make_unique<PropertyWatcher<DataInterface>>(
        bus, object_path::hostState, interface::hostState, "CurrentHostState",
        *this, [this](const auto& value) {
        this->_hostState = std::get<std::string>(value);
    }));

    // Watch the BaseBIOSTable property for the hmc managed attribute
    _properties.emplace_back(std::make_unique<PropertyWatcher<DataInterface>>(
        bus, object_path::biosConfigMgr, interface::biosConfigMgr,
        "BaseBIOSTable", service_name::biosConfigMgr, *this,
        [this](const auto& value) {
        const auto& attributes = std::get<BiosAttributes>(value);

        auto it = attributes.find("pvm_hmc_managed");
        if (it != attributes.end())
        {
            const auto& currentValVariant = std::get<5>(it->second);
            auto currentVal = std::get_if<std::string>(&currentValVariant);
            if (currentVal)
            {
                this->_hmcManaged = (*currentVal == "Enabled") ? true : false;
            }
        }
    }));
}

DBusPropertyMap
    DataInterface::getAllProperties(const std::string& service,
                                    const std::string& objectPath,
                                    const std::string& interface) const
{
    DBusPropertyMap properties;

    auto method = _bus.new_method_call(service.c_str(), objectPath.c_str(),
                                       interface::dbusProperty, "GetAll");
    method.append(interface);
    auto reply = _bus.call(method, dbusTimeout);

    reply.read(properties);

    return properties;
}

void DataInterface::getProperty(const std::string& service,
                                const std::string& objectPath,
                                const std::string& interface,
                                const std::string& property,
                                DBusValue& value) const
{
    auto method = _bus.new_method_call(service.c_str(), objectPath.c_str(),
                                       interface::dbusProperty, "Get");
    method.append(interface, property);
    auto reply = _bus.call(method, dbusTimeout);

    reply.read(value);
}

DBusPathList DataInterface::getPaths(const DBusInterfaceList& interfaces) const
{
    auto method = _bus.new_method_call(
        service_name::objectMapper, object_path::objectMapper,
        interface::objectMapper, "GetSubTreePaths");

    method.append(std::string{"/"}, 0, interfaces);

    auto reply = _bus.call(method, dbusTimeout);

    DBusPathList paths;
    reply.read(paths);

    return paths;
}

DBusService DataInterface::getService(const std::string& objectPath,
                                      const std::string& interface) const
{
    auto method = _bus.new_method_call(service_name::objectMapper,
                                       object_path::objectMapper,
                                       interface::objectMapper, "GetObject");

    method.append(objectPath, std::vector<std::string>({interface}));

    auto reply = _bus.call(method, dbusTimeout);

    std::map<DBusService, DBusInterfaceList> response;
    reply.read(response);

    if (!response.empty())
    {
        return response.begin()->first;
    }

    return std::string{};
}

void DataInterface::readBMCFWVersion()
{
    _bmcFWVersion =
        phosphor::logging::util::getOSReleaseValue("VERSION").value_or("");
}

void DataInterface::readServerFWVersion()
{
    auto value =
        phosphor::logging::util::getOSReleaseValue("VERSION_ID").value_or("");
    if ((value != "") && (value.find_last_of(')') != std::string::npos))
    {
        std::size_t pos = value.find_first_of('(') + 1;
        _serverFWVersion = value.substr(pos, value.find_last_of(')') - pos);
    }
}

void DataInterface::readBMCFWVersionID()
{
    _bmcFWVersionID =
        phosphor::logging::util::getOSReleaseValue("VERSION_ID").value_or("");
}

std::string DataInterface::getMachineTypeModel() const
{
    std::string model;
    try
    {
        auto service = getService(object_path::systemInv, interface::invAsset);
        if (!service.empty())
        {
            DBusValue value;
            getProperty(service, object_path::systemInv, interface::invAsset,
                        "Model", value);

            model = std::get<std::string>(value);
        }
    }
    catch (const std::exception& e)
    {
        lg2::warning("Failed reading Model property from "
                     "interface: {IFACE} exception: {ERROR}",
                     "IFACE", interface::invAsset, "ERROR", e);
    }

    return model;
}

std::string DataInterface::getMachineSerialNumber() const
{
    std::string sn;
    try
    {
        auto service = getService(object_path::systemInv, interface::invAsset);
        if (!service.empty())
        {
            DBusValue value;
            getProperty(service, object_path::systemInv, interface::invAsset,
                        "SerialNumber", value);

            sn = std::get<std::string>(value);
        }
    }
    catch (const std::exception& e)
    {
        lg2::warning("Failed reading SerialNumber property from "
                     "interface: {IFACE} exception: {ERROR}",
                     "IFACE", interface::invAsset, "ERROR", e);
    }

    return sn;
}

std::string DataInterface::getMotherboardCCIN() const
{
    std::string ccin;

    try
    {
        auto service = getService(object_path::motherBoardInv,
                                  interface::viniRecordVPD);
        if (!service.empty())
        {
            DBusValue value;
            getProperty(service, object_path::motherBoardInv,
                        interface::viniRecordVPD, "CC", value);

            auto cc = std::get<std::vector<uint8_t>>(value);
            ccin = std::string{cc.begin(), cc.end()};
        }
    }
    catch (const std::exception& e)
    {
        lg2::warning("Failed reading Motherboard CCIN property from "
                     "interface: {IFACE} exception: {ERROR}",
                     "IFACE", interface::viniRecordVPD, "ERROR", e);
    }

    return ccin;
}

std::vector<uint8_t> DataInterface::getSystemIMKeyword() const
{
    std::vector<uint8_t> systemIM;

    try
    {
        auto service = getService(object_path::motherBoardInv,
                                  interface::vsbpRecordVPD);
        if (!service.empty())
        {
            DBusValue value;
            getProperty(service, object_path::motherBoardInv,
                        interface::vsbpRecordVPD, "IM", value);

            systemIM = std::get<std::vector<uint8_t>>(value);
        }
    }
    catch (const std::exception& e)
    {
        lg2::warning("Failed reading System IM property from "
                     "interface: {IFACE} exception: {ERROR}",
                     "IFACE", interface::vsbpRecordVPD, "ERROR", e);
    }

    return systemIM;
}

void DataInterface::getHWCalloutFields(const std::string& inventoryPath,
                                       std::string& fruPartNumber,
                                       std::string& ccin,
                                       std::string& serialNumber) const
{
    // For now, attempt to get all of the properties directly on the path
    // passed in.  In the future, may need to make use of an algorithm
    // to figure out which inventory objects actually hold these
    // interfaces in the case of non FRUs, or possibly another service
    // will provide this info.  Any missing interfaces will result
    // in exceptions being thrown.

    auto service = getService(inventoryPath, interface::viniRecordVPD);

    auto properties = getAllProperties(service, inventoryPath,
                                       interface::viniRecordVPD);

    auto value = std::get<std::vector<uint8_t>>(properties["FN"]);
    fruPartNumber = std::string{value.begin(), value.end()};

    value = std::get<std::vector<uint8_t>>(properties["CC"]);
    ccin = std::string{value.begin(), value.end()};

    value = std::get<std::vector<uint8_t>>(properties["SN"]);
    serialNumber = std::string{value.begin(), value.end()};
}

std::string
    DataInterface::getLocationCode(const std::string& inventoryPath) const
{
    auto service = getService(inventoryPath, interface::locCode);

    DBusValue locCode;
    getProperty(service, inventoryPath, interface::locCode, "LocationCode",
                locCode);

    return std::get<std::string>(locCode);
}

std::string
    DataInterface::addLocationCodePrefix(const std::string& locationCode)
{
    static const std::string locationCodePrefix{"Ufcs-"};

    // Technically there are 2 location code prefixes, Ufcs and Umts, so
    // if it already starts with a U then don't need to do anything.
    if (locationCode.front() != 'U')
    {
        return locationCodePrefix + locationCode;
    }

    return locationCode;
}

std::string DataInterface::expandLocationCode(const std::string& locationCode,
                                              uint16_t /*node*/) const
{
    // Location codes for connectors are the location code of the FRU they are
    // on, plus a '-Tx' segment.  Remove this last segment before expanding it
    // and then add it back in afterwards.  This way, the connector doesn't have
    // to be in the model just so that it can be expanded.
    auto [baseLoc, connectorLoc] = extractConnectorFromLocCode(locationCode);

    auto method =
        _bus.new_method_call(service_name::vpdManager, object_path::vpdManager,
                             interface::vpdManager, "GetExpandedLocationCode");

    method.append(addLocationCodePrefix(baseLoc), static_cast<uint16_t>(0));

    auto reply = _bus.call(method, dbusTimeout);

    std::string expandedLocationCode;
    reply.read(expandedLocationCode);

    if (!connectorLoc.empty())
    {
        expandedLocationCode += connectorLoc;
    }

    return expandedLocationCode;
}

std::vector<std::string>
    DataInterface::getInventoryFromLocCode(const std::string& locationCode,
                                           uint16_t node, bool expanded) const
{
    std::string methodName = expanded ? "GetFRUsByExpandedLocationCode"
                                      : "GetFRUsByUnexpandedLocationCode";

    // Remove the connector segment, if present, so that this method call
    // returns an inventory path that getHWCalloutFields() can be used with.
    // (The serial number, etc, aren't stored on the connector in the
    // inventory, and may not even be modeled.)
    auto [baseLoc, connectorLoc] = extractConnectorFromLocCode(locationCode);

    auto method =
        _bus.new_method_call(service_name::vpdManager, object_path::vpdManager,
                             interface::vpdManager, methodName.c_str());

    if (expanded)
    {
        method.append(baseLoc);
    }
    else
    {
        method.append(addLocationCodePrefix(baseLoc), node);
    }

    auto reply = _bus.call(method, dbusTimeout);

    std::vector<sdbusplus::message::object_path> entries;
    reply.read(entries);

    std::vector<std::string> paths;

    // Note: The D-Bus method will fail if nothing found.
    std::for_each(entries.begin(), entries.end(),
                  [&paths](const auto& path) { paths.push_back(path); });

    return paths;
}

void DataInterface::assertLEDGroup(const std::string& ledGroup,
                                   bool value) const
{
    DBusValue variant = value;
    auto method = _bus.new_method_call(service_name::ledGroupManager,
                                       ledGroup.c_str(),
                                       interface::dbusProperty, "Set");
    method.append(interface::ledGroup, "Asserted", variant);
    _bus.call(method, dbusTimeout);
}

void DataInterface::setFunctional(const std::string& objectPath,
                                  bool value) const
{
    DBusPropertyMap prop{{"Functional", value}};
    DBusInterfaceMap iface{{interface::operationalStatus, prop}};

    // PIM takes a relative path like /system/chassis so remove
    // /xyz/openbmc_project/inventory if present.
    std::string path{objectPath};
    if (path.starts_with(object_path::baseInv))
    {
        path = objectPath.substr(strlen(object_path::baseInv));
    }
    DBusObjectMap object{{path, iface}};

    auto method = _bus.new_method_call(service_name::inventoryManager,
                                       object_path::baseInv,
                                       interface::inventoryManager, "Notify");
    method.append(std::move(object));
    _bus.call(method, dbusTimeout);
}

using AssociationTuple = std::tuple<std::string, std::string, std::string>;
using AssociationsProperty = std::vector<AssociationTuple>;

void DataInterface::setCriticalAssociation(const std::string& objectPath) const
{
    DBusValue getAssociationValue;

    auto service = getService(objectPath, interface::associationDef);

    getProperty(service, objectPath, interface::associationDef, "Associations",
                getAssociationValue);

    auto association = std::get<AssociationsProperty>(getAssociationValue);

    AssociationTuple critAssociation{
        "health_rollup", "critical",
        "/xyz/openbmc_project/inventory/system/chassis"};

    if (std::find(association.begin(), association.end(), critAssociation) ==
        association.end())
    {
        association.push_back(critAssociation);
        DBusValue setAssociationValue = association;

        auto method = _bus.new_method_call(service.c_str(), objectPath.c_str(),
                                           interface::dbusProperty, "Set");

        method.append(interface::associationDef, "Associations",
                      setAssociationValue);
        _bus.call(method, dbusTimeout);
    }
}

std::vector<std::string> DataInterface::getSystemNames() const
{
    DBusSubTree subtree;
    DBusValue names;

    auto method = _bus.new_method_call(service_name::objectMapper,
                                       object_path::objectMapper,
                                       interface::objectMapper, "GetSubTree");
    method.append(std::string{"/"}, 0,
                  std::vector<std::string>{interface::compatible});
    auto reply = _bus.call(method, dbusTimeout);

    reply.read(subtree);
    if (subtree.empty())
    {
        throw std::runtime_error("Compatible interface not on D-Bus");
    }

    for (const auto& [path, interfaceMap] : subtree)
    {
        auto iface = interfaceMap.find(service_name::entityManager);
        if (iface == interfaceMap.end())
        {
            continue;
        }

        getProperty(iface->first, path, interface::compatible, "Names", names);

        return std::get<std::vector<std::string>>(names);
    }

    throw std::runtime_error("EM Compatible interface not on D-Bus");
}

bool DataInterface::getQuiesceOnError() const
{
    bool ret = false;

    try
    {
        auto service = getService(object_path::logSetting,
                                  interface::logSetting);
        if (!service.empty())
        {
            DBusValue value;
            getProperty(service, object_path::logSetting, interface::logSetting,
                        "QuiesceOnHwError", value);

            ret = std::get<bool>(value);
        }
    }
    catch (const std::exception& e)
    {
        lg2::warning("Failed reading QuiesceOnHwError property from "
                     "interface: {IFACE} exception: {ERROR}",
                     "IFACE", interface::logSetting, "ERROR", e);
    }

    return ret;
}

std::vector<bool>
    DataInterface::checkDumpStatus(const std::vector<std::string>& type) const
{
    DBusSubTree subtree;
    std::vector<bool> result(type.size(), false);

    // Query GetSubTree for the availability of dump interface
    auto method = _bus.new_method_call(service_name::objectMapper,
                                       object_path::objectMapper,
                                       interface::objectMapper, "GetSubTree");
    method.append(std::string{"/"}, 0,
                  std::vector<std::string>{interface::dumpEntry});
    auto reply = _bus.call(method, dbusTimeout);

    reply.read(subtree);

    if (subtree.empty())
    {
        return result;
    }

    std::vector<bool>::iterator itDumpStatus = result.begin();
    uint8_t count = 0;
    for (const auto& [path, serviceInfo] : subtree)
    {
        const auto& service = serviceInfo.begin()->first;
        // Check for dump type on the object path
        for (const auto& it : type)
        {
            if (path.find(it) != std::string::npos)
            {
                DBusValue value, progress;

                // If dump type status is already available go for next path
                if (*itDumpStatus)
                {
                    break;
                }

                // Check for valid dump to be available if following
                // conditions are met for the dump entry path -
                // Offloaded == false and Status == Completed
                getProperty(service, path, interface::dumpEntry, "Offloaded",
                            value);
                getProperty(service, path, interface::dumpProgress, "Status",
                            progress);
                auto offload = std::get<bool>(value);
                auto status = std::get<std::string>(progress);
                if (!offload && (status.find("Completed") != std::string::npos))
                {
                    *itDumpStatus = true;
                    count++;
                    if (count >= type.size())
                    {
                        return result;
                    }
                    break;
                }
            }
            ++itDumpStatus;
        }
        itDumpStatus = result.begin();
    }

    return result;
}

void DataInterface::createGuardRecord(const std::vector<uint8_t>& binPath,
                                      const std::string& type,
                                      const std::string& logPath) const
{
    try
    {
        auto method = _bus.new_method_call(
            service_name::hwIsolation, object_path::hwIsolation,
            interface::hwIsolationCreate, "CreateWithEntityPath");
        method.append(binPath, type, sdbusplus::message::object_path(logPath));
        // Note: hw isolation "CreateWithEntityPath" got dependency on logging
        // api's. Making d-bus call no reply type to avoid cyclic dependency.
        // Added minimal timeout to catch initial failures.
        // Need to revisit this design later to avoid cyclic dependency.
        constexpr auto hwIsolationTimeout = 100000; // in micro seconds
        _bus.call_noreply(method, hwIsolationTimeout);
    }

    catch (const sdbusplus::exception_t& e)
    {
        std::string errName = e.name();
        // SD_BUS_ERROR_TIMEOUT error is expected, due to PEL api dependency
        // mentioned above. Ignoring the error.
        if (errName != SD_BUS_ERROR_TIMEOUT)
        {
            lg2::error("GUARD D-Bus call exception. Path={PATH}, "
                       "interface = {IFACE}, exception = {ERROR}",
                       "PATH", object_path::hwIsolation, "IFACE",
                       interface::hwIsolationCreate, "ERROR", e);
        }
    }
}

void DataInterface::createProgressSRC(
    const uint64_t& priSRC, const std::vector<uint8_t>& srcStruct) const
{
    DBusValue variant = std::make_tuple(priSRC, srcStruct);

    auto method = _bus.new_method_call(service_name::bootRawProgress,
                                       object_path::bootRawProgress,
                                       interface::dbusProperty, "Set");

    method.append(interface::bootRawProgress, "Value", variant);

    _bus.call(method, dbusTimeout);
}

std::vector<uint32_t> DataInterface::getLogIDWithHwIsolation() const
{
    std::vector<std::string> association = {"xyz.openbmc_project.Association"};
    std::string hwErrorLog = "/isolated_hw_errorlog";
    std::string errorLog = "/error_log";
    DBusPathList paths;
    std::vector<uint32_t> ids;

    // Get all latest mapper associations
    paths = getPaths(association);
    for (auto& path : paths)
    {
        // Look for object path with hardware isolation entry if any
        size_t pos = path.find(hwErrorLog);
        if (pos != std::string::npos)
        {
            // Get the object path
            std::string ph = path;
            ph.erase(pos, hwErrorLog.length());
            auto service = getService(ph, interface::hwIsolationEntry);
            if (!service.empty())
            {
                bool status;
                DBusValue value;

                // Read the Resolved property from object path
                getProperty(service, ph, interface::hwIsolationEntry,
                            "Resolved", value);

                status = std::get<bool>(value);

                // If the entry isn't resolved
                if (!status)
                {
                    auto assocService = getService(path,
                                                   interface::association);
                    if (!assocService.empty())
                    {
                        DBusValue endpoints;

                        // Read Endpoints property
                        getProperty(assocService, path, interface::association,
                                    "endpoints", endpoints);

                        auto logPath =
                            std::get<std::vector<std::string>>(endpoints);
                        if (!logPath.empty())
                        {
                            // Get OpenBMC event log Id
                            uint32_t id = stoi(logPath[0].substr(
                                logPath[0].find_last_of('/') + 1));
                            ids.push_back(id);
                        }
                    }
                }
            }
        }

        // Look for object path with error_log entry if any
        pos = path.find(errorLog);
        if (pos != std::string::npos)
        {
            auto service = getService(path, interface::association);
            if (!service.empty())
            {
                DBusValue value;

                // Read Endpoints property
                getProperty(service, path, interface::association, "endpoints",
                            value);

                auto logPath = std::get<std::vector<std::string>>(value);
                if (!logPath.empty())
                {
                    // Get OpenBMC event log Id
                    uint32_t id = stoi(
                        logPath[0].substr(logPath[0].find_last_of('/') + 1));
                    ids.push_back(id);
                }
            }
        }
    }

    if (ids.size() > 1)
    {
        // remove duplicates to have only unique ids
        std::sort(ids.begin(), ids.end());
        ids.erase(std::unique(ids.begin(), ids.end()), ids.end());
    }
    return ids;
}

std::vector<uint8_t> DataInterface::getRawProgressSRC(void) const
{
    using RawProgressProperty = std::tuple<uint64_t, std::vector<uint8_t>>;

    DBusValue value;
    getProperty(service_name::bootRawProgress, object_path::bootRawProgress,
                interface::bootRawProgress, "Value", value);

    const auto& rawProgress = std::get<RawProgressProperty>(value);
    return std::get<1>(rawProgress);
}

void DataInterface::startFruPlugWatch()
{
    // Add a watch on inventory InterfacesAdded and then find all
    // existing hotpluggable interfaces and add propertiesChanged
    // watches on them.

    _invIaMatch = std::make_unique<sdbusplus::bus::match_t>(
        _bus, match_rules::interfacesAdded(object_path::baseInv),
        std::bind(&DataInterface::inventoryIfaceAdded, this,
                  std::placeholders::_1));
    try
    {
        auto paths = getPaths(hotplugInterfaces);

        _invPresentMatches.clear();

        std::for_each(paths.begin(), paths.end(),
                      [this](const auto& path) { addHotplugWatch(path); });
    }
    catch (const sdbusplus::exception_t& e)
    {
        lg2::warning("Failed getting FRU paths to watch: {ERROR}", "ERROR", e);
    }
}

void DataInterface::addHotplugWatch(const std::string& path)
{
    if (!_invPresentMatches.contains(path))
    {
        _invPresentMatches.emplace(
            path,
            std::make_unique<sdbusplus::bus::match_t>(
                _bus, match_rules::propertiesChanged(path, interface::invItem),
                std::bind(&DataInterface::presenceChanged, this,
                          std::placeholders::_1)));
    }
}

void DataInterface::inventoryIfaceAdded(sdbusplus::message_t& msg)
{
    sdbusplus::message::object_path path;
    DBusInterfaceMap interfaces;

    msg.read(path, interfaces);

    // Check if any of the new interfaces are for hot pluggable FRUs.
    if (std::find_if(interfaces.begin(), interfaces.end(),
                     [](const auto& interfacePair) {
        return std::find(hotplugInterfaces.begin(), hotplugInterfaces.end(),
                         interfacePair.first) != hotplugInterfaces.end();
    }) == interfaces.end())
    {
        return;
    }

    addHotplugWatch(path.str);

    // If an Inventory.Item interface was also added, check presence now.

    // Notes:
    // * This assumes the Inv.Item and Inv.Fan/PS are added together which
    //   is currently the case.
    // * If the code ever switches to something without a Present
    //   property, then the IA signal itself would probably indicate presence.

    auto itemIt = interfaces.find(interface::invItem);
    if (itemIt != interfaces.end())
    {
        notifyPresenceSubsribers(path.str, itemIt->second);
    }
}

void DataInterface::presenceChanged(sdbusplus::message_t& msg)
{
    DBusInterface interface;
    DBusPropertyMap properties;

    msg.read(interface, properties);
    if (interface != interface::invItem)
    {
        return;
    }

    std::string path = msg.get_path();
    notifyPresenceSubsribers(path, properties);
}

void DataInterface::notifyPresenceSubsribers(const std::string& path,
                                             const DBusPropertyMap& properties)
{
    auto prop = properties.find("Present");
    if ((prop == properties.end()) || (!std::get<bool>(prop->second)))
    {
        return;
    }

    std::string locCode;

    try
    {
        auto service = getService(path, interface::locCode);

        // If the hotplugged FRU is hosted by PLDM, then it is
        // in an IO expansion drawer and we don't care about it.
        if (service == service_name::pldm)
        {
            return;
        }

        locCode = getLocationCode(path);
    }
    catch (const sdbusplus::exception_t& e)
    {
        lg2::debug("Could not get location code for {PATH}: {ERROR}", "PATH",
                   path, "ERROR", e);
        return;
    }

    lg2::debug("Detected FRU {PATH} ({LOC}) present ", "PATH", path, "LOC",
               locCode);

    // Tell the subscribers.
    setFruPresent(locCode);
}
} // namespace pels
} // namespace openpower