mctp: add I3CMCTPDDevice Assignment

This commit adds the assignment of I3CMCTPDDevice to MCTPEndpoint.

Tested on yosemite4, which is a multihost platform with 8 server blades,
mctpreactor may associate these endpoint with EM configs properly.

Change-Id: I4f8038f539245b43ba3c9ac923ccad9670aed87f
Signed-off-by: Unive Tien <unive.tien.wiwynn@gmail.com>
diff --git a/src/Utils.hpp b/src/Utils.hpp
index 037c127..cb96822 100644
--- a/src/Utils.hpp
+++ b/src/Utils.hpp
@@ -43,9 +43,9 @@
 const std::regex illegalDbusRegex("[^A-Za-z0-9_]");
 
 using BasicVariantType =
-    std::variant<std::vector<std::string>, std::vector<uint8_t>, std::string,
-                 int64_t, uint64_t, double, int32_t, uint32_t, int16_t,
-                 uint16_t, uint8_t, bool>;
+    std::variant<std::vector<std::string>, std::vector<uint8_t>,
+                 std::vector<std::uint64_t>, std::string, int64_t, uint64_t,
+                 double, int32_t, uint32_t, int16_t, uint16_t, uint8_t, bool>;
 using SensorBaseConfigMap =
     boost::container::flat_map<std::string, BasicVariantType>;
 using SensorBaseConfiguration = std::pair<std::string, SensorBaseConfigMap>;
diff --git a/src/VariantVisitors.hpp b/src/VariantVisitors.hpp
index 80d07da..d0ebeff 100644
--- a/src/VariantVisitors.hpp
+++ b/src/VariantVisitors.hpp
@@ -17,8 +17,10 @@
 #pragma once
 #include <boost/type_index.hpp>
 
+#include <concepts>
 #include <stdexcept>
 #include <string>
+#include <vector>
 
 namespace details
 {
@@ -66,3 +68,27 @@
             boost::typeindex::type_id<T>().pretty_name() + " to string");
     }
 };
+
+template <std::integral V, std::integral U>
+struct VariantToNumArrayVisitor
+{
+    template <typename T>
+    std::vector<V> operator()(const T& t) const
+    {
+        if constexpr (std::is_same_v<T, std::vector<U>>)
+        {
+            std::vector<V> output;
+            output.reserve(t.size());
+
+            for (const auto& value : t)
+            {
+                output.push_back(static_cast<V>(value));
+            }
+
+            return output;
+        }
+        throw std::invalid_argument(
+            "Cannot handle type " +
+            boost::typeindex::type_id<T>().pretty_name() + " to vector<U>");
+    }
+};
diff --git a/src/mctp/MCTPEndpoint.cpp b/src/mctp/MCTPEndpoint.cpp
index 5eb114e..391085d 100644
--- a/src/mctp/MCTPEndpoint.cpp
+++ b/src/mctp/MCTPEndpoint.cpp
@@ -329,11 +329,27 @@
     return iface->second;
 }
 
+std::optional<SensorBaseConfigMap> I3CMCTPDDevice::match(
+    const SensorData& config)
+{
+    auto iface = config.find(configInterfaceName(configType));
+    if (iface == config.end())
+    {
+        return std::nullopt;
+    }
+    return iface->second;
+}
+
 bool I2CMCTPDDevice::match(const std::set<std::string>& interfaces)
 {
     return interfaces.contains(configInterfaceName(configType));
 }
 
+bool I3CMCTPDDevice::match(const std::set<std::string>& interfaces)
+{
+    return interfaces.contains(configInterfaceName(configType));
+}
+
 std::shared_ptr<I2CMCTPDDevice> I2CMCTPDDevice::from(
     const std::shared_ptr<sdbusplus::asio::connection>& connection,
     const SensorBaseConfigMap& iface)
@@ -391,6 +407,61 @@
     }
 }
 
+std::shared_ptr<I3CMCTPDDevice> I3CMCTPDDevice::from(
+    const std::shared_ptr<sdbusplus::asio::connection>& connection,
+    const SensorBaseConfigMap& iface)
+{
+    auto mType = iface.find("Type");
+    if (mType == iface.end())
+    {
+        throw std::invalid_argument(
+            "No 'Type' member found for provided configuration object");
+    }
+
+    auto type = std::visit(VariantToStringVisitor(), mType->second);
+    if (type != configType)
+    {
+        throw std::invalid_argument("Not an I3C device");
+    }
+
+    auto mAddress = iface.find("Address");
+    auto mBus = iface.find("Bus");
+    auto mName = iface.find("Name");
+    if (mAddress == iface.end() || mBus == iface.end() || mName == iface.end())
+    {
+        throw std::invalid_argument(
+            "Configuration object violates MCTPI3CTarget schema");
+    }
+
+    auto address = std::visit(VariantToNumArrayVisitor<uint8_t, uint64_t>(),
+                              mAddress->second);
+    if (address.empty())
+    {
+        throw std::invalid_argument("Bad device address");
+    }
+
+    auto sBus = std::visit(VariantToStringVisitor(), mBus->second);
+    int bus{};
+    auto [bptr,
+          bec] = std::from_chars(sBus.data(), sBus.data() + sBus.size(), bus);
+    if (bec != std::errc{})
+    {
+        throw std::invalid_argument("Bad bus index");
+    }
+
+    try
+    {
+        return std::make_shared<I3CMCTPDDevice>(connection, bus, address);
+    }
+    catch (const MCTPException& ex)
+    {
+        warning(
+            "Failed to create I3CMCTPDDevice at [ bus: {I3C_BUS} ]: {EXCEPTION}",
+            "I3C_BUS", bus, "EXCEPTION", ex);
+        return {};
+    }
+}
+
 std::string I2CMCTPDDevice::interfaceFromBus(int bus)
 {
     std::filesystem::path netdir =
@@ -406,3 +477,29 @@
 
     return it->path().filename();
 }
+
+std::string I3CMCTPDDevice::interfaceFromBus(int bus)
+{
+    std::filesystem::path netdir = std::format("/sys/devices/virtual/net");
+    std::error_code ec;
+    std::filesystem::directory_iterator it(netdir, ec);
+    if (ec || it == std::filesystem::end(it))
+    {
+        error("No net device associated with I3C bus {I3C_BUS} at {NET_DEVICE}",
+              "I3C_BUS", bus, "NET_DEVICE", netdir);
+        throw MCTPException("Bus is not configured as an MCTP interface");
+    }
+
+    std::string targetInterface = std::format("mctpi3c{}", bus);
+    for (const auto& entry : std::filesystem::directory_iterator(netdir))
+    {
+        if (entry.is_directory() && entry.path().filename() == targetInterface)
+        {
+            return targetInterface;
+        }
+    }
+
+    error("No matching net device found for I3C bus {I3C_BUS} at {NET_DEVICE}",
+          "I3C_BUS", bus, "NET_DEVICE", netdir);
+    throw MCTPException("No matching net device found for the specified bus");
+}
diff --git a/src/mctp/MCTPEndpoint.hpp b/src/mctp/MCTPEndpoint.hpp
index ffba380..2abfc95 100644
--- a/src/mctp/MCTPEndpoint.hpp
+++ b/src/mctp/MCTPEndpoint.hpp
@@ -326,3 +326,26 @@
 
     static std::string interfaceFromBus(int bus);
 };
+
+class I3CMCTPDDevice : public MCTPDDevice
+{
+  public:
+    static std::optional<SensorBaseConfigMap> match(const SensorData& config);
+    static bool match(const std::set<std::string>& interfaces);
+    static std::shared_ptr<I3CMCTPDDevice> from(
+        const std::shared_ptr<sdbusplus::asio::connection>& connection,
+        const SensorBaseConfigMap& iface);
+
+    I3CMCTPDDevice() = delete;
+    I3CMCTPDDevice(
+        const std::shared_ptr<sdbusplus::asio::connection>& connection, int bus,
+        const std::vector<uint8_t>& physaddr) :
+        MCTPDDevice(connection, interfaceFromBus(bus), physaddr)
+    {}
+    ~I3CMCTPDDevice() override = default;
+
+  private:
+    static constexpr const char* configType = "MCTPI3CTarget";
+
+    static std::string interfaceFromBus(int bus);
+};
diff --git a/src/mctp/MCTPReactorMain.cpp b/src/mctp/MCTPReactorMain.cpp
index 5d453d2..6d33349 100644
--- a/src/mctp/MCTPReactorMain.cpp
+++ b/src/mctp/MCTPReactorMain.cpp
@@ -80,11 +80,19 @@
     try
     {
         std::optional<SensorBaseConfigMap> iface;
-        // NOLINTNEXTLINE(bugprone-assignment-in-if-condition)
-        if ((iface = I2CMCTPDDevice::match(config)))
+        iface = I2CMCTPDDevice::match(config);
+        if (iface)
         {
+            info("Creating I2CMCTPDDevice");
             return I2CMCTPDDevice::from(connection, *iface);
         }
+
+        iface = I3CMCTPDDevice::match(config);
+        if (iface)
+        {
+            info("Creating I3CMCTPDDevice");
+            return I3CMCTPDDevice::from(connection, *iface);
+        }
     }
     catch (const std::invalid_argument& ex)
     {
@@ -125,7 +133,7 @@
         msg.unpack<sdbusplus::message::object_path, std::set<std::string>>();
     try
     {
-        if (I2CMCTPDDevice::match(removed))
+        if (I2CMCTPDDevice::match(removed) || I3CMCTPDDevice::match(removed))
         {
             reactor->unmanageMCTPDevice(path.str);
         }
@@ -240,7 +248,7 @@
     boost::asio::post(io, [reactor, systemBus]() {
         auto gsc = std::make_shared<GetSensorConfiguration>(
             systemBus, std::bind_front(manageMCTPEntity, systemBus, reactor));
-        gsc->getConfiguration({"MCTPI2CTarget"});
+        gsc->getConfiguration({"MCTPI2CTarget", "MCTPI3CTarget"});
     });
 
     io.run();