Move source files into application-specific sub-directories

Currently, dbus-sensors implement multiple applications:
 - psusensor
 - adcsensor
 - intelcpusensor
 - hwmontempsensor
 - ipmbsensor
 - nvmesensor
 - externalsensor
 - mcutempsensor
 - intrusionsensor
 - fansensor
 - exitairtempsensor

This commit is to create separate directories for each application so
that things can be separated more easily and the files are smaller,
instead of creating one huge file for the sensor implementation.

There was some discussion in discord on this. [1][2]

[1]: https://discord.com/channels/775381525260664832/1187158775438778408/1284106093756289067
[2]: https://discord.com/channels/775381525260664832/867820390406422538/1303217796821553214

Signed-off-by: George Liu <liuxiwei@ieisystem.com>
Change-Id: I258fc2ee7d8f939c7b83a07350395e78775b2b8d
diff --git a/src/intrusion/ChassisIntrusionSensor.cpp b/src/intrusion/ChassisIntrusionSensor.cpp
new file mode 100644
index 0000000..2c1e391
--- /dev/null
+++ b/src/intrusion/ChassisIntrusionSensor.cpp
@@ -0,0 +1,500 @@
+/*
+// Copyright (c) 2018 Intel 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 "ChassisIntrusionSensor.hpp"
+
+#include <fcntl.h>
+#include <linux/i2c.h>
+#include <sys/ioctl.h>
+#include <sys/syslog.h>
+#include <systemd/sd-journal.h>
+#include <unistd.h>
+
+#include <Utils.hpp>
+#include <boost/asio/error.hpp>
+#include <boost/asio/io_context.hpp>
+#include <boost/asio/posix/stream_descriptor.hpp>
+#include <gpiod.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+
+#include <chrono>
+#include <cstddef>
+#include <cstdint>
+#include <filesystem>
+#include <fstream>
+#include <iostream>
+#include <memory>
+#include <stdexcept>
+#include <string>
+#include <utility>
+#include <vector>
+
+extern "C"
+{
+#include <i2c/smbus.h>
+#include <linux/i2c-dev.h>
+}
+
+static constexpr bool debug = false;
+
+static constexpr unsigned int defaultPollSec = 1;
+static constexpr unsigned int sensorFailedPollSec = 5;
+static unsigned int intrusionSensorPollSec = defaultPollSec;
+static constexpr const char* hwIntrusionValStr =
+    "xyz.openbmc_project.Chassis.Intrusion.Status.HardwareIntrusion";
+static constexpr const char* normalValStr =
+    "xyz.openbmc_project.Chassis.Intrusion.Status.Normal";
+static constexpr const char* manualRearmStr =
+    "xyz.openbmc_project.Chassis.Intrusion.RearmMode.Manual";
+static constexpr const char* autoRearmStr =
+    "xyz.openbmc_project.Chassis.Intrusion.RearmMode.Automatic";
+
+// SMLink Status Register
+const static constexpr size_t pchStatusRegIntrusion = 0x04;
+
+// Status bit field masks
+const static constexpr size_t pchRegMaskIntrusion = 0x01;
+
+// Value to clear intrusion status hwmon file
+const static constexpr size_t intrusionStatusHwmonClearValue = 0;
+
+void ChassisIntrusionSensor::updateValue(const size_t& value)
+{
+    std::string newValue = value != 0 ? hwIntrusionValStr : normalValStr;
+
+    // Take no action if the hardware status does not change
+    // Same semantics as Sensor::updateValue(const double&)
+    if (newValue == mValue)
+    {
+        return;
+    }
+
+    if constexpr (debug)
+    {
+        std::cout << "Update value from " << mValue << " to " << newValue
+                  << "\n";
+    }
+
+    // Automatic Rearm mode allows direct update
+    // Manual Rearm mode requires a rearm action to clear the intrusion
+    // status
+    if (!mAutoRearm)
+    {
+        if (newValue == normalValStr)
+        {
+            // Chassis is first closed from being open. If it has been
+            // rearmed externally, reset the flag, update mValue and
+            // return, without having to write "Normal" to DBus property
+            // (because the rearm action already did).
+            // Otherwise, return with no more action.
+            if (mRearmFlag)
+            {
+                mRearmFlag = false;
+                mValue = newValue;
+            }
+            return;
+        }
+    }
+
+    // Flush the rearm flag everytime it allows an update to Dbus
+    mRearmFlag = false;
+
+    // indicate that it is internal set call
+    mOverridenState = false;
+    mInternalSet = true;
+    mIface->set_property("Status", newValue);
+    mInternalSet = false;
+
+    mValue = newValue;
+}
+
+int ChassisIntrusionPchSensor::readSensor()
+{
+    int32_t statusMask = pchRegMaskIntrusion;
+    int32_t statusReg = pchStatusRegIntrusion;
+
+    int32_t value = i2c_smbus_read_byte_data(mBusFd, statusReg);
+    if constexpr (debug)
+    {
+        std::cout << "Pch type: raw value is " << value << "\n";
+    }
+
+    if (value < 0)
+    {
+        std::cerr << "i2c_smbus_read_byte_data failed \n";
+        return -1;
+    }
+
+    // Get status value with mask
+    value &= statusMask;
+
+    if constexpr (debug)
+    {
+        std::cout << "Pch type: masked raw value is " << value << "\n";
+    }
+    return value;
+}
+
+void ChassisIntrusionPchSensor::pollSensorStatus()
+{
+    std::weak_ptr<ChassisIntrusionPchSensor> weakRef = weak_from_this();
+
+    // setting a new experation implicitly cancels any pending async wait
+    mPollTimer.expires_after(std::chrono::seconds(intrusionSensorPollSec));
+
+    mPollTimer.async_wait([weakRef](const boost::system::error_code& ec) {
+        // case of being canceled
+        if (ec == boost::asio::error::operation_aborted)
+        {
+            std::cerr << "Timer of intrusion sensor is cancelled\n";
+            return;
+        }
+
+        std::shared_ptr<ChassisIntrusionPchSensor> self = weakRef.lock();
+        if (!self)
+        {
+            std::cerr << "ChassisIntrusionSensor no self\n";
+            return;
+        }
+
+        int value = self->readSensor();
+        if (value < 0)
+        {
+            intrusionSensorPollSec = sensorFailedPollSec;
+        }
+        else
+        {
+            intrusionSensorPollSec = defaultPollSec;
+            self->updateValue(value);
+        }
+
+        // trigger next polling
+        self->pollSensorStatus();
+    });
+}
+
+int ChassisIntrusionGpioSensor::readSensor()
+{
+    mGpioLine.event_read();
+    auto value = mGpioLine.get_value();
+    if constexpr (debug)
+    {
+        std::cout << "Gpio type: raw value is " << value << "\n";
+    }
+    return value;
+}
+
+void ChassisIntrusionGpioSensor::pollSensorStatus()
+{
+    mGpioFd.async_wait(
+        boost::asio::posix::stream_descriptor::wait_read,
+        [this](const boost::system::error_code& ec) {
+            if (ec == boost::system::errc::bad_file_descriptor)
+            {
+                return; // we're being destroyed
+            }
+
+            if (ec)
+            {
+                std::cerr
+                    << "Error on GPIO based intrusion sensor wait event\n";
+            }
+            else
+            {
+                int value = readSensor();
+                if (value >= 0)
+                {
+                    updateValue(value);
+                }
+                // trigger next polling
+                pollSensorStatus();
+            }
+        });
+}
+
+int ChassisIntrusionHwmonSensor::readSensor()
+{
+    int value = 0;
+
+    std::fstream stream(mHwmonPath, std::ios::in | std::ios::out);
+    if (!stream.good())
+    {
+        std::cerr << "Error reading status at " << mHwmonPath << "\n";
+        return -1;
+    }
+
+    std::string line;
+    if (!std::getline(stream, line))
+    {
+        std::cerr << "Error reading status at " << mHwmonPath << "\n";
+        return -1;
+    }
+
+    try
+    {
+        value = std::stoi(line);
+        if constexpr (debug)
+        {
+            std::cout << "Hwmon type: raw value is " << value << "\n";
+        }
+    }
+    catch (const std::invalid_argument& e)
+    {
+        std::cerr << "Error reading status at " << mHwmonPath << " : "
+                  << e.what() << "\n";
+        return -1;
+    }
+
+    // Reset chassis intrusion status after every reading
+    stream << intrusionStatusHwmonClearValue;
+
+    return value;
+}
+
+void ChassisIntrusionHwmonSensor::pollSensorStatus()
+{
+    std::weak_ptr<ChassisIntrusionHwmonSensor> weakRef = weak_from_this();
+
+    // setting a new experation implicitly cancels any pending async wait
+    mPollTimer.expires_after(std::chrono::seconds(intrusionSensorPollSec));
+
+    mPollTimer.async_wait([weakRef](const boost::system::error_code& ec) {
+        // case of being canceled
+        if (ec == boost::asio::error::operation_aborted)
+        {
+            std::cerr << "Timer of intrusion sensor is cancelled\n";
+            return;
+        }
+
+        std::shared_ptr<ChassisIntrusionHwmonSensor> self = weakRef.lock();
+        if (!self)
+        {
+            std::cerr << "ChassisIntrusionSensor no self\n";
+            return;
+        }
+
+        int value = self->readSensor();
+        if (value < 0)
+        {
+            intrusionSensorPollSec = sensorFailedPollSec;
+        }
+        else
+        {
+            intrusionSensorPollSec = defaultPollSec;
+            self->updateValue(value);
+        }
+
+        // trigger next polling
+        self->pollSensorStatus();
+    });
+}
+
+int ChassisIntrusionSensor::setSensorValue(const std::string& req,
+                                           std::string& propertyValue)
+{
+    if (!mInternalSet)
+    {
+        /*
+           1. Assuming that setting property in Automatic mode causes
+           no effect but only event logs and propertiesChanged signal
+           (because the property will be updated continuously to the
+           current hardware status anyway), only update Status property
+           and raise rearm flag in Manual rearm mode.
+           2. Only accept Normal value from an external call.
+        */
+        if (!mAutoRearm && req == normalValStr)
+        {
+            mRearmFlag = true;
+            propertyValue = req;
+            mOverridenState = true;
+        }
+    }
+    else if (!mOverridenState)
+    {
+        propertyValue = req;
+    }
+    else
+    {
+        return 1;
+    }
+    // Send intrusion event to Redfish
+    if (mValue == normalValStr && propertyValue != normalValStr)
+    {
+        sd_journal_send("MESSAGE=%s", "Chassis intrusion assert event",
+                        "PRIORITY=%i", LOG_INFO, "REDFISH_MESSAGE_ID=%s",
+                        "OpenBMC.0.1.ChassisIntrusionDetected", NULL);
+    }
+    else if (mValue == hwIntrusionValStr && propertyValue == normalValStr)
+    {
+        sd_journal_send("MESSAGE=%s", "Chassis intrusion de-assert event",
+                        "PRIORITY=%i", LOG_INFO, "REDFISH_MESSAGE_ID=%s",
+                        "OpenBMC.0.1.ChassisIntrusionReset", NULL);
+    }
+    return 1;
+}
+
+void ChassisIntrusionSensor::start()
+{
+    mIface->register_property(
+        "Status", mValue,
+        [&](const std::string& req, std::string& propertyValue) {
+            return setSensorValue(req, propertyValue);
+        });
+    std::string rearmStr = mAutoRearm ? autoRearmStr : manualRearmStr;
+    mIface->register_property("Rearm", rearmStr);
+    mIface->initialize();
+    pollSensorStatus();
+}
+
+ChassisIntrusionSensor::ChassisIntrusionSensor(
+    bool autoRearm, sdbusplus::asio::object_server& objServer) :
+    mValue(normalValStr), mAutoRearm(autoRearm), mObjServer(objServer)
+{
+    mIface = mObjServer.add_interface("/xyz/openbmc_project/Chassis/Intrusion",
+                                      "xyz.openbmc_project.Chassis.Intrusion");
+}
+
+ChassisIntrusionPchSensor::ChassisIntrusionPchSensor(
+    bool autoRearm, boost::asio::io_context& io,
+    sdbusplus::asio::object_server& objServer, int busId, int slaveAddr) :
+    ChassisIntrusionSensor(autoRearm, objServer), mPollTimer(io)
+{
+    if (busId < 0 || slaveAddr <= 0)
+    {
+        throw std::invalid_argument(
+            "Invalid i2c bus " + std::to_string(busId) + " address " +
+            std::to_string(slaveAddr) + "\n");
+    }
+
+    mSlaveAddr = slaveAddr;
+
+    std::string devPath = "/dev/i2c-" + std::to_string(busId);
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
+    mBusFd = open(devPath.c_str(), O_RDWR | O_CLOEXEC);
+    if (mBusFd < 0)
+    {
+        throw std::invalid_argument("Unable to open " + devPath + "\n");
+    }
+
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
+    if (ioctl(mBusFd, I2C_SLAVE_FORCE, mSlaveAddr) < 0)
+    {
+        throw std::runtime_error("Unable to set device address\n");
+    }
+
+    unsigned long funcs = 0;
+
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
+    if (ioctl(mBusFd, I2C_FUNCS, &funcs) < 0)
+    {
+        throw std::runtime_error("Don't support I2C_FUNCS\n");
+    }
+
+    if ((funcs & I2C_FUNC_SMBUS_READ_BYTE_DATA) == 0U)
+    {
+        throw std::runtime_error(
+            "Do not have I2C_FUNC_SMBUS_READ_BYTE_DATA \n");
+    }
+}
+
+ChassisIntrusionGpioSensor::ChassisIntrusionGpioSensor(
+    bool autoRearm, boost::asio::io_context& io,
+    sdbusplus::asio::object_server& objServer, bool gpioInverted) :
+    ChassisIntrusionSensor(autoRearm, objServer), mGpioInverted(gpioInverted),
+    mGpioFd(io)
+{
+    mGpioLine = gpiod::find_line(mPinName);
+    if (!mGpioLine)
+    {
+        throw std::invalid_argument(
+            "Error finding gpio pin name: " + mPinName + "\n");
+    }
+    mGpioLine.request(
+        {"ChassisIntrusionSensor", gpiod::line_request::EVENT_BOTH_EDGES,
+         mGpioInverted ? gpiod::line_request::FLAG_ACTIVE_LOW : 0});
+
+    auto gpioLineFd = mGpioLine.event_get_fd();
+    if (gpioLineFd < 0)
+    {
+        throw std::invalid_argument("Failed to get " + mPinName + " fd\n");
+    }
+
+    mGpioFd.assign(gpioLineFd);
+}
+
+ChassisIntrusionHwmonSensor::ChassisIntrusionHwmonSensor(
+    bool autoRearm, boost::asio::io_context& io,
+    sdbusplus::asio::object_server& objServer, std::string hwmonName) :
+    ChassisIntrusionSensor(autoRearm, objServer),
+    mHwmonName(std::move(hwmonName)), mPollTimer(io)
+{
+    std::vector<fs::path> paths;
+
+    if (!findFiles(fs::path("/sys/class/hwmon"), mHwmonName, paths))
+    {
+        throw std::invalid_argument("Failed to find hwmon path in sysfs\n");
+    }
+
+    if (paths.empty())
+    {
+        throw std::invalid_argument(
+            "Hwmon file " + mHwmonName + " can't be found in sysfs\n");
+    }
+
+    if (paths.size() > 1)
+    {
+        std::cerr << "Found more than 1 hwmon file to read chassis intrusion"
+                  << " status. Taking the first one. \n";
+    }
+
+    // Expecting only one hwmon file for one given chassis
+    mHwmonPath = paths[0].string();
+
+    if constexpr (debug)
+    {
+        std::cout << "Found " << paths.size()
+                  << " paths for intrusion status \n"
+                  << " The first path is: " << mHwmonPath << "\n";
+    }
+}
+
+ChassisIntrusionSensor::~ChassisIntrusionSensor()
+{
+    mObjServer.remove_interface(mIface);
+}
+
+ChassisIntrusionPchSensor::~ChassisIntrusionPchSensor()
+{
+    mPollTimer.cancel();
+    if (close(mBusFd) < 0)
+    {
+        std::cerr << "Failed to close fd " << std::to_string(mBusFd);
+    }
+}
+
+ChassisIntrusionGpioSensor::~ChassisIntrusionGpioSensor()
+{
+    mGpioFd.close();
+    if (mGpioLine)
+    {
+        mGpioLine.release();
+    }
+}
+
+ChassisIntrusionHwmonSensor::~ChassisIntrusionHwmonSensor()
+{
+    mPollTimer.cancel();
+}
diff --git a/src/intrusion/ChassisIntrusionSensor.hpp b/src/intrusion/ChassisIntrusionSensor.hpp
new file mode 100644
index 0000000..d22eaf8
--- /dev/null
+++ b/src/intrusion/ChassisIntrusionSensor.hpp
@@ -0,0 +1,97 @@
+#pragma once
+
+#include <boost/asio/io_context.hpp>
+#include <boost/asio/steady_timer.hpp>
+#include <gpiod.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+
+#include <memory>
+#include <string>
+
+namespace fs = std::filesystem;
+
+class ChassisIntrusionSensor
+{
+  public:
+    explicit ChassisIntrusionSensor(bool autoRearm,
+                                    sdbusplus::asio::object_server& objServer);
+
+    virtual ~ChassisIntrusionSensor();
+
+    void start();
+
+  protected:
+    virtual int readSensor() = 0;
+    virtual void pollSensorStatus() = 0;
+    void updateValue(const size_t& value);
+
+  private:
+    std::string mValue;
+    // If this sensor uses automatic rearm method. Otherwise, manually rearm it
+    bool mAutoRearm;
+    std::shared_ptr<sdbusplus::asio::dbus_interface> mIface;
+    sdbusplus::asio::object_server& mObjServer;
+    bool mOverridenState = false;
+    bool mInternalSet = false;
+    bool mRearmFlag = false;
+
+    int setSensorValue(const std::string& req, std::string& propertyValue);
+};
+
+class ChassisIntrusionPchSensor :
+    public ChassisIntrusionSensor,
+    public std::enable_shared_from_this<ChassisIntrusionPchSensor>
+{
+  public:
+    ChassisIntrusionPchSensor(bool autoRearm, boost::asio::io_context& io,
+                              sdbusplus::asio::object_server& objServer,
+                              int busId, int slaveAddr);
+
+    ~ChassisIntrusionPchSensor() override;
+
+  private:
+    int mBusFd{-1};
+    int mSlaveAddr{-1};
+    boost::asio::steady_timer mPollTimer;
+    int readSensor() override;
+    void pollSensorStatus() override;
+};
+
+class ChassisIntrusionGpioSensor :
+    public ChassisIntrusionSensor,
+    public std::enable_shared_from_this<ChassisIntrusionGpioSensor>
+{
+  public:
+    ChassisIntrusionGpioSensor(bool autoRearm, boost::asio::io_context& io,
+                               sdbusplus::asio::object_server& objServer,
+                               bool gpioInverted);
+
+    ~ChassisIntrusionGpioSensor() override;
+
+  private:
+    bool mGpioInverted{false};
+    std::string mPinName = "CHASSIS_INTRUSION";
+    gpiod::line mGpioLine;
+    boost::asio::posix::stream_descriptor mGpioFd;
+    int readSensor() override;
+    void pollSensorStatus() override;
+};
+
+class ChassisIntrusionHwmonSensor :
+    public ChassisIntrusionSensor,
+    public std::enable_shared_from_this<ChassisIntrusionHwmonSensor>
+{
+  public:
+    ChassisIntrusionHwmonSensor(bool autoRearm, boost::asio::io_context& io,
+                                sdbusplus::asio::object_server& objServer,
+                                std::string hwmonName);
+
+    ~ChassisIntrusionHwmonSensor() override;
+
+  private:
+    std::string mHwmonName;
+    std::string mHwmonPath;
+    boost::asio::steady_timer mPollTimer;
+    int readSensor() override;
+    void pollSensorStatus() override;
+};
diff --git a/src/intrusion/IntrusionSensorMain.cpp b/src/intrusion/IntrusionSensorMain.cpp
new file mode 100644
index 0000000..c94727a
--- /dev/null
+++ b/src/intrusion/IntrusionSensorMain.cpp
@@ -0,0 +1,548 @@
+/*
+// Copyright (c) 2018 Intel 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 "ChassisIntrusionSensor.hpp"
+#include "Utils.hpp"
+
+#include <boost/asio/error.hpp>
+#include <boost/asio/io_context.hpp>
+#include <boost/asio/steady_timer.hpp>
+#include <boost/container/flat_map.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/bus/match.hpp>
+#include <sdbusplus/message.hpp>
+
+#include <array>
+#include <charconv>
+#include <chrono>
+#include <cstdint>
+#include <ctime>
+#include <exception>
+#include <filesystem>
+#include <fstream>
+#include <functional>
+#include <iostream>
+#include <map>
+#include <memory>
+#include <string>
+#include <system_error>
+#include <utility>
+#include <variant>
+#include <vector>
+
+static constexpr bool debug = false;
+
+static constexpr const char* sensorType = "ChassisIntrusionSensor";
+static constexpr const char* nicType = "NIC";
+static constexpr auto nicTypes{std::to_array<const char*>({nicType})};
+
+static const std::map<std::string, std::string> compatibleHwmonNames = {
+    {"Aspeed2600_Hwmon", "intrusion0_alarm"}
+    // Add compatible strings here for new hwmon intrusion detection
+    // drivers that have different hwmon names but would also like to
+    // use the available Hwmon class.
+};
+
+static void createSensorsFromConfig(
+    boost::asio::io_context& io, sdbusplus::asio::object_server& objServer,
+    const std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
+    std::shared_ptr<ChassisIntrusionSensor>& pSensor)
+{
+    // find matched configuration according to sensor type
+    ManagedObjectType sensorConfigurations;
+    bool useCache = false;
+
+    if (!getSensorConfiguration(sensorType, dbusConnection,
+                                sensorConfigurations, useCache))
+    {
+        std::cerr << "error communicating to entity manager\n";
+        return;
+    }
+
+    const SensorData* sensorData = nullptr;
+    const std::pair<std::string, SensorBaseConfigMap>* baseConfiguration =
+        nullptr;
+
+    for (const auto& [path, cfgData] : sensorConfigurations)
+    {
+        baseConfiguration = nullptr;
+        sensorData = &cfgData;
+
+        // match sensor type
+        auto sensorBase = sensorData->find(configInterfaceName(sensorType));
+        if (sensorBase == sensorData->end())
+        {
+            std::cerr << "error finding base configuration \n";
+            continue;
+        }
+
+        baseConfiguration = &(*sensorBase);
+
+        // Rearm defaults to "Automatic" mode
+        bool autoRearm = true;
+        auto findRearm = baseConfiguration->second.find("Rearm");
+        if (findRearm != baseConfiguration->second.end())
+        {
+            std::string rearmStr = std::get<std::string>(findRearm->second);
+            if (rearmStr != "Automatic" && rearmStr != "Manual")
+            {
+                std::cerr << "Wrong input for Rearm parameter\n";
+                continue;
+            }
+            autoRearm = (rearmStr == "Automatic");
+        }
+
+        // judge class, "Gpio", "Hwmon" or "I2C"
+        auto findClass = baseConfiguration->second.find("Class");
+        if (findClass != baseConfiguration->second.end())
+        {
+            auto classString = std::get<std::string>(findClass->second);
+            if (classString == "Gpio")
+            {
+                auto findGpioPolarity =
+                    baseConfiguration->second.find("GpioPolarity");
+
+                if (findGpioPolarity == baseConfiguration->second.end())
+                {
+                    std::cerr
+                        << "error finding gpio polarity in configuration \n";
+                    continue;
+                }
+
+                try
+                {
+                    bool gpioInverted =
+                        (std::get<std::string>(findGpioPolarity->second) ==
+                         "Low");
+                    pSensor = std::make_shared<ChassisIntrusionGpioSensor>(
+                        autoRearm, io, objServer, gpioInverted);
+                    pSensor->start();
+                    if (debug)
+                    {
+                        std::cout
+                            << "find chassis intrusion sensor polarity inverted "
+                               "flag is "
+                            << gpioInverted << "\n";
+                    }
+                    return;
+                }
+                catch (const std::bad_variant_access& e)
+                {
+                    std::cerr << "invalid value for gpio info in config. \n";
+                    continue;
+                }
+                catch (const std::exception& e)
+                {
+                    std::cerr << e.what() << std::endl;
+                    continue;
+                }
+            }
+            // If class string contains Hwmon string
+            else if (classString.find("Hwmon") != std::string::npos)
+            {
+                std::string hwmonName;
+                std::map<std::string, std::string>::const_iterator
+                    compatIterator = compatibleHwmonNames.find(classString);
+
+                if (compatIterator == compatibleHwmonNames.end())
+                {
+                    std::cerr << "Hwmon Class string is not supported\n";
+                    continue;
+                }
+
+                hwmonName = compatIterator->second;
+
+                try
+                {
+                    pSensor = std::make_shared<ChassisIntrusionHwmonSensor>(
+                        autoRearm, io, objServer, hwmonName);
+                    pSensor->start();
+                    return;
+                }
+                catch (const std::exception& e)
+                {
+                    std::cerr << e.what() << std::endl;
+                    continue;
+                }
+            }
+            else
+            {
+                auto findBus = baseConfiguration->second.find("Bus");
+                auto findAddress = baseConfiguration->second.find("Address");
+                if (findBus == baseConfiguration->second.end() ||
+                    findAddress == baseConfiguration->second.end())
+                {
+                    std::cerr
+                        << "error finding bus or address in configuration \n";
+                    continue;
+                }
+                try
+                {
+                    int busId = std::get<uint64_t>(findBus->second);
+                    int slaveAddr = std::get<uint64_t>(findAddress->second);
+                    pSensor = std::make_shared<ChassisIntrusionPchSensor>(
+                        autoRearm, io, objServer, busId, slaveAddr);
+                    pSensor->start();
+                    if (debug)
+                    {
+                        std::cout
+                            << "find matched bus " << busId
+                            << ", matched slave addr " << slaveAddr << "\n";
+                    }
+                    return;
+                }
+                catch (const std::bad_variant_access& e)
+                {
+                    std::cerr
+                        << "invalid value for bus or address in config. \n";
+                    continue;
+                }
+                catch (const std::exception& e)
+                {
+                    std::cerr << e.what() << std::endl;
+                    continue;
+                }
+            }
+        }
+    }
+
+    std::cerr << " Can't find matched I2C, GPIO or Hwmon configuration\n";
+
+    // Make sure nothing runs when there's failure in configuration for the
+    // sensor after rescan
+    if (pSensor)
+    {
+        std::cerr << " Reset the occupied sensor pointer\n";
+        pSensor = nullptr;
+    }
+}
+
+static constexpr bool debugLanLeash = false;
+boost::container::flat_map<int, bool> lanStatusMap;
+boost::container::flat_map<int, std::string> lanInfoMap;
+boost::container::flat_map<std::string, int> pathSuffixMap;
+
+static void getNicNameInfo(
+    const std::shared_ptr<sdbusplus::asio::connection>& dbusConnection)
+{
+    auto getter = std::make_shared<GetSensorConfiguration>(
+        dbusConnection, [](const ManagedObjectType& sensorConfigurations) {
+            // Get NIC name and save to map
+            lanInfoMap.clear();
+            for (const auto& [path, cfgData] : sensorConfigurations)
+            {
+                const std::pair<std::string, SensorBaseConfigMap>*
+                    baseConfiguration = nullptr;
+
+                // find base configuration
+                auto sensorBase = cfgData.find(configInterfaceName(nicType));
+                if (sensorBase == cfgData.end())
+                {
+                    continue;
+                }
+                baseConfiguration = &(*sensorBase);
+
+                auto findEthIndex = baseConfiguration->second.find("EthIndex");
+                auto findName = baseConfiguration->second.find("Name");
+
+                if (findEthIndex != baseConfiguration->second.end() &&
+                    findName != baseConfiguration->second.end())
+                {
+                    const auto* pEthIndex =
+                        std::get_if<uint64_t>(&findEthIndex->second);
+                    const auto* pName =
+                        std::get_if<std::string>(&findName->second);
+                    if (pEthIndex != nullptr && pName != nullptr)
+                    {
+                        lanInfoMap[*pEthIndex] = *pName;
+                        if (debugLanLeash)
+                        {
+                            std::cout << "find name of eth" << *pEthIndex
+                                      << " is " << *pName << "\n";
+                        }
+                    }
+                }
+            }
+
+            if (lanInfoMap.empty())
+            {
+                std::cerr << "can't find matched NIC name. \n";
+            }
+        });
+
+    getter->getConfiguration(
+        std::vector<std::string>{nicTypes.begin(), nicTypes.end()});
+}
+
+static void processLanStatusChange(sdbusplus::message_t& message)
+{
+    const std::string& pathName = message.get_path();
+    std::string interfaceName;
+    SensorBaseConfigMap properties;
+    message.read(interfaceName, properties);
+
+    auto findStateProperty = properties.find("OperationalState");
+    if (findStateProperty == properties.end())
+    {
+        return;
+    }
+    std::string* pState =
+        std::get_if<std::string>(&(findStateProperty->second));
+    if (pState == nullptr)
+    {
+        std::cerr << "invalid OperationalState \n";
+        return;
+    }
+
+    bool newLanConnected = (*pState == "routable" || *pState == "carrier" ||
+                            *pState == "degraded");
+
+    // get ethNum from path. /org/freedesktop/network1/link/_32 for eth0
+    size_t pos = pathName.find("/_");
+    if (pos == std::string::npos || pathName.length() <= pos + 2)
+    {
+        std::cerr << "unexpected path name " << pathName << "\n";
+        return;
+    }
+    std::string suffixStr = pathName.substr(pos + 2);
+
+    auto findEthNum = pathSuffixMap.find(suffixStr);
+    if (findEthNum == pathSuffixMap.end())
+    {
+        std::cerr << "unexpected eth for suffixStr " << suffixStr << "\n";
+        return;
+    }
+    int ethNum = findEthNum->second;
+
+    // get lan status from map
+    auto findLanStatus = lanStatusMap.find(ethNum);
+    if (findLanStatus == lanStatusMap.end())
+    {
+        std::cerr << "unexpected eth " << ethNum << " in lanStatusMap \n";
+        return;
+    }
+    bool oldLanConnected = findLanStatus->second;
+
+    // get lan info from map
+    std::string lanInfo;
+    if (!lanInfoMap.empty())
+    {
+        auto findLanInfo = lanInfoMap.find(ethNum);
+        if (findLanInfo == lanInfoMap.end())
+        {
+            std::cerr << "unexpected eth " << ethNum << " in lanInfoMap \n";
+        }
+        else
+        {
+            lanInfo = "(" + findLanInfo->second + ")";
+        }
+    }
+
+    if (debugLanLeash)
+    {
+        std::cout << "ethNum = " << ethNum << ", state = " << *pState
+                  << ", oldLanConnected = "
+                  << (oldLanConnected ? "true" : "false")
+                  << ", newLanConnected = "
+                  << (newLanConnected ? "true" : "false") << "\n";
+    }
+
+    if (oldLanConnected != newLanConnected)
+    {
+        std::string strEthNum = "eth" + std::to_string(ethNum) + lanInfo;
+        const auto* strState = newLanConnected ? "connected" : "lost";
+        const auto* strMsgId =
+            newLanConnected ? "OpenBMC.0.1.LanRegained" : "OpenBMC.0.1.LanLost";
+
+        lg2::info("{ETHDEV} LAN leash {STATE}", "ETHDEV", strEthNum, "STATE",
+                  strState, "REDFISH_MESSAGE_ID", strMsgId,
+                  "REDFISH_MESSAGE_ARGS", strEthNum);
+
+        lanStatusMap[ethNum] = newLanConnected;
+    }
+}
+
+/** @brief Initialize the lan status.
+ *
+ * @return true on success and false on failure
+ */
+static bool initializeLanStatus(
+    const std::shared_ptr<sdbusplus::asio::connection>& conn)
+{
+    // init lan port name from configuration
+    getNicNameInfo(conn);
+
+    // get eth info from sysfs
+    std::vector<fs::path> files;
+    if (!findFiles(fs::path("/sys/class/net/"), R"(eth\d+/ifindex)", files))
+    {
+        std::cerr << "No eth in system\n";
+        return false;
+    }
+
+    // iterate through all found eth files, and save ifindex
+    for (const fs::path& fileName : files)
+    {
+        if (debugLanLeash)
+        {
+            std::cout << "Reading " << fileName << "\n";
+        }
+        std::ifstream sysFile(fileName);
+        if (!sysFile.good())
+        {
+            std::cerr << "Failure reading " << fileName << "\n";
+            continue;
+        }
+        std::string line;
+        getline(sysFile, line);
+        const uint8_t ifindex = std::stoi(line);
+        // pathSuffix is ASCII of ifindex
+        const std::string& pathSuffix = std::to_string(ifindex + 30);
+
+        // extract ethNum
+        const std::string& fileStr = fileName.string();
+        const int pos = fileStr.find("eth");
+        const std::string& ethNumStr = fileStr.substr(pos + 3);
+        int ethNum = 0;
+        std::from_chars_result r = std::from_chars(
+            ethNumStr.data(), ethNumStr.data() + ethNumStr.size(), ethNum);
+        if (r.ec != std::errc())
+        {
+            std::cerr << "invalid ethNum string: " << ethNumStr << "\n";
+            continue;
+        }
+
+        // save pathSuffix
+        pathSuffixMap[pathSuffix] = ethNum;
+        if (debugLanLeash)
+        {
+            std::cout << "ethNum = " << std::to_string(ethNum) << ", ifindex = "
+                      << line << ", pathSuffix = " << pathSuffix << "\n";
+        }
+
+        // init lan connected status from networkd
+        conn->async_method_call(
+            [ethNum](boost::system::error_code ec,
+                     const std::variant<std::string>& property) {
+                lanStatusMap[ethNum] = false;
+                if (ec)
+                {
+                    std::cerr
+                        << "Error reading init status of eth" << ethNum << "\n";
+                    return;
+                }
+                const std::string* pState = std::get_if<std::string>(&property);
+                if (pState == nullptr)
+                {
+                    std::cerr << "Unable to read lan status value\n";
+                    return;
+                }
+                bool isLanConnected =
+                    (*pState == "routable" || *pState == "carrier" ||
+                     *pState == "degraded");
+                if (debugLanLeash)
+                {
+                    std::cout << "ethNum = " << std::to_string(ethNum)
+                              << ", init LAN status = "
+                              << (isLanConnected ? "true" : "false") << "\n";
+                }
+                lanStatusMap[ethNum] = isLanConnected;
+            },
+            "org.freedesktop.network1",
+            "/org/freedesktop/network1/link/_" + pathSuffix,
+            "org.freedesktop.DBus.Properties", "Get",
+            "org.freedesktop.network1.Link", "OperationalState");
+    }
+    return true;
+}
+
+int main()
+{
+    std::shared_ptr<ChassisIntrusionSensor> intrusionSensor;
+
+    // setup connection to dbus
+    boost::asio::io_context io;
+    auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
+
+    // setup object server, define interface
+    systemBus->request_name("xyz.openbmc_project.IntrusionSensor");
+
+    sdbusplus::asio::object_server objServer(systemBus, true);
+
+    objServer.add_manager("/xyz/openbmc_project/Chassis");
+
+    createSensorsFromConfig(io, objServer, systemBus, intrusionSensor);
+
+    // callback to handle configuration change
+    boost::asio::steady_timer filterTimer(io);
+    std::function<void(sdbusplus::message_t&)> eventHandler =
+        [&](sdbusplus::message_t& message) {
+            if (message.is_method_error())
+            {
+                std::cerr << "callback method error\n";
+                return;
+            }
+            // this implicitly cancels the timer
+            filterTimer.expires_after(std::chrono::seconds(1));
+            filterTimer.async_wait([&](const boost::system::error_code& ec) {
+                if (ec == boost::asio::error::operation_aborted)
+                {
+                    // timer was cancelled
+                    return;
+                }
+                std::cout << "rescan due to configuration change \n";
+                createSensorsFromConfig(io, objServer, systemBus,
+                                        intrusionSensor);
+            });
+        };
+
+    std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches =
+        setupPropertiesChangedMatches(
+            *systemBus, std::to_array<const char*>({sensorType}), eventHandler);
+
+    if (initializeLanStatus(systemBus))
+    {
+        // add match to monitor lan status change
+        sdbusplus::bus::match_t lanStatusMatch(
+            static_cast<sdbusplus::bus_t&>(*systemBus),
+            "type='signal', member='PropertiesChanged',"
+            "arg0namespace='org.freedesktop.network1.Link'",
+            [](sdbusplus::message_t& msg) { processLanStatusChange(msg); });
+
+        // add match to monitor entity manager signal about nic name config
+        // change
+        sdbusplus::bus::match_t lanConfigMatch(
+            static_cast<sdbusplus::bus_t&>(*systemBus),
+            "type='signal', member='PropertiesChanged',path_namespace='" +
+                std::string(inventoryPath) + "',arg0namespace='" +
+                configInterfaceName(nicType) + "'",
+            [&systemBus](sdbusplus::message_t& msg) {
+                if (msg.is_method_error())
+                {
+                    std::cerr << "callback method error\n";
+                    return;
+                }
+                getNicNameInfo(systemBus);
+            });
+    }
+
+    io.run();
+
+    return 0;
+}
diff --git a/src/intrusion/meson.build b/src/intrusion/meson.build
new file mode 100644
index 0000000..f88d2c9
--- /dev/null
+++ b/src/intrusion/meson.build
@@ -0,0 +1,15 @@
+src_inc = include_directories('..')
+
+executable(
+    'intrusionsensor',
+    'ChassisIntrusionSensor.cpp',
+    'IntrusionSensorMain.cpp',
+    dependencies: [
+        default_deps,
+        gpiodcxx,
+        i2c,
+        utils_dep,
+    ],
+    include_directories: src_inc,
+    install: true,
+)
\ No newline at end of file