netipmid: Manage and expose session object

Session (RMCP+) is managed by net-ipmid directly, but
session commands has to be supported in both LAN & other
session-less interfaces. In order to make session commands
to work in other interfaces, session objects must be
exposed as D-Bus objects, so that ipmi-providers can
query the same.

Tested:
 1. Verified that RMCP+ session are perfectly working
 2. Verified RMCP+ session establishment fails for wrong
    password
 3. Verified that session privilege level are maintained
    and access are restricted accordingly
 4. Verified session timeout and sessions are destroyed
    accordingly after timeout
 5. verified max session count working behavior
 6. verified ipmi-providers responding with proper response for this
    (or D-Bus objects are exposed correctly during session creation,
    session deletion,session update,
    (like privilege, - say even set session privilege level command)
 7.Session objects are created dynamically.

Change-Id: I78a8449359877ef6cc4cd8161d8c67e6e54eb52b
Signed-off-by: Suryakanth Sekar <suryakanth.sekar@linux.intel.com>
diff --git a/command/open_session.cpp b/command/open_session.cpp
index e3453f8..78fb9d0 100644
--- a/command/open_session.cpp
+++ b/command/open_session.cpp
@@ -108,7 +108,7 @@
     session->updateLastTransactionTime();
 
     // Session state is Setup in progress
-    session->state = session::State::SETUP_IN_PROGRESS;
+    session->state(static_cast<uint8_t>(session::State::setupInProgress));
     return outPayload;
 }
 
diff --git a/command/rakp12.cpp b/command/rakp12.cpp
index 172c83e..01355d1 100644
--- a/command/rakp12.cpp
+++ b/command/rakp12.cpp
@@ -26,7 +26,7 @@
 
     // Session ID zero is reserved for Session Setup
     if (endian::from_ipmi(request->managedSystemSessionID) ==
-        session::SESSION_ZERO)
+        session::sessionZero)
     {
         log<level::INFO>("RAKP12: BMC invalid Session ID");
         response->rmcpStatusCode =
@@ -127,12 +127,14 @@
     // established with CALLBACK privilege if requested for callback. All other
     // sessions are initialy set to USER privilege, regardless of the requested
     // maximum privilege.
-    session->curPrivLevel = session::Privilege::CALLBACK;
+    session->currentPrivilege(
+        static_cast<uint8_t>(session::Privilege::CALLBACK));
     if (static_cast<session::Privilege>(request->req_max_privilege_level &
                                         session::reqMaxPrivMask) >
         session::Privilege::CALLBACK)
     {
-        session->curPrivLevel = session::Privilege::USER;
+        session->currentPrivilege(
+            static_cast<uint8_t>(session::Privilege::USER));
     }
     session->reqMaxPrivLevel =
         static_cast<session::Privilege>(request->req_max_privilege_level);
@@ -201,7 +203,8 @@
             static_cast<uint8_t>(RAKP_ReturnCode::INACTIVE_ROLE);
         return outPayload;
     }
-    session->chNum = chNum;
+    session->channelNum(chNum);
+    session->userID(userId);
     // minimum privilege of Channel / User / session::privilege::USER/CALLBACK /
     // has to be used as session current privilege level
     uint8_t minPriv = 0;
@@ -214,9 +217,9 @@
     {
         minPriv = session->sessionUserPrivAccess.privilege;
     }
-    if (session->curPrivLevel > static_cast<session::Privilege>(minPriv))
+    if (session->currentPrivilege() > minPriv)
     {
-        session->curPrivLevel = static_cast<session::Privilege>(minPriv);
+        session->currentPrivilege(static_cast<uint8_t>(minPriv));
     }
     // For username / privilege lookup, fail with UNAUTH_NAME, if requested
     // max privilege does not match user privilege
diff --git a/command/rakp34.cpp b/command/rakp34.cpp
index 370c132..b106b6f 100644
--- a/command/rakp34.cpp
+++ b/command/rakp34.cpp
@@ -85,7 +85,7 @@
 
     // Session ID zero is reserved for Session Setup
     if (endian::from_ipmi(request->managedSystemSessionID) ==
-        session::SESSION_ZERO)
+        session::sessionZero)
     {
         log<level::INFO>("RAKP34: BMC invalid Session ID");
         response->rmcpStatusCode =
@@ -272,7 +272,7 @@
     // Set the Confidentiality Algorithm
     applyCryptAlgo(session->getBMCSessionID());
 
-    session->state = session::State::ACTIVE;
+    session->state(static_cast<uint8_t>(session::State::active));
     return outPayload;
 }
 
diff --git a/command/session_cmds.cpp b/command/session_cmds.cpp
index 8606ce5..52eb02f 100644
--- a/command/session_cmds.cpp
+++ b/command/session_cmds.cpp
@@ -26,7 +26,7 @@
 
     if (reqPrivilegeLevel == 0) // Just return present privilege level
     {
-        response->newPrivLevel = static_cast<uint8_t>(session->curPrivLevel);
+        response->newPrivLevel = session->currentPrivilege();
         return outPayload;
     }
     if (reqPrivilegeLevel > (static_cast<uint8_t>(session->reqMaxPrivLevel) &
@@ -55,8 +55,7 @@
     else
     {
         // update current privilege of the session.
-        session->curPrivLevel =
-            static_cast<session::Privilege>(reqPrivilegeLevel);
+        session->currentPrivilege(static_cast<uint8_t>(reqPrivilegeLevel));
         response->newPrivLevel = reqPrivilegeLevel;
     }
 
@@ -76,7 +75,7 @@
 
     // Session 0 is needed to handle session setup, so session zero is never
     // closed
-    if (bmcSessionID == session::SESSION_ZERO)
+    if (bmcSessionID == session::sessionZero)
     {
         response->completionCode = IPMI_CC_INVALID_SESSIONID;
     }
diff --git a/command_table.cpp b/command_table.cpp
index f4e3ece..1ad2175 100644
--- a/command_table.cpp
+++ b/command_table.cpp
@@ -66,7 +66,8 @@
         std::map<std::string, ipmi::Value> options = {
             {"userId", ipmi::Value(static_cast<int>(
                            ipmi::ipmiUserGetUserId(session->userName)))},
-            {"privilege", ipmi::Value(static_cast<int>(session->curPrivLevel))},
+            {"privilege",
+             ipmi::Value(static_cast<int>(session->currentPrivilege()))},
         };
         bus->async_method_call(
             [handler, this](const boost::system::error_code& ec,
@@ -123,7 +124,7 @@
     std::vector<uint8_t> errResponse;
 
     // Check if the command qualifies to be run prior to establishing a session
-    if (!sessionless && (handler->sessionID == session::SESSION_ZERO))
+    if (!sessionless && (handler->sessionID == session::sessionZero))
     {
         errResponse.resize(1);
         errResponse[0] = IPMI_CC_INSUFFICIENT_PRIVILEGE;
diff --git a/main.cpp b/main.cpp
index 880244b..1d84c3c 100644
--- a/main.cpp
+++ b/main.cpp
@@ -102,6 +102,7 @@
         setInterfaceIndex(channel);
     }
 
+    std::get<session::Manager&>(singletonPool).managerInit(channel);
     // Register callback to update cache for a GUID change and cache the GUID
     command::registerGUIDChangeCallback();
     cache::guid = command::getSystemGUID();
diff --git a/message_handler.cpp b/message_handler.cpp
index e2aafb3..f0c653d 100644
--- a/message_handler.cpp
+++ b/message_handler.cpp
@@ -43,6 +43,8 @@
     sessionID = inMessage->bmcSessionID;
     inMessage->rcSessionID = session->getRCSessionID();
     session->updateLastTransactionTime();
+    session->channelPtr = channel;
+    session->remotePort(channel->getPort());
 
     return true;
 }
@@ -94,7 +96,7 @@
             std::get<session::Manager&>(singletonPool).getSession(sessionID);
         // Process PayloadType::IPMI only if ipmi is enabled or for sessionless
         // or for session establisbment command
-        if (this->sessionID == session::SESSION_ZERO ||
+        if (this->sessionID == session::sessionZero ||
             session->sessionUserPrivAccess.ipmiEnabled)
         {
             if (inMessage->payload.size() <
diff --git a/message_parsers.cpp b/message_parsers.cpp
index 21fb125..95fcfaf 100644
--- a/message_parsers.cpp
+++ b/message_parsers.cpp
@@ -250,7 +250,7 @@
 {
     SessionHeader_t* header = reinterpret_cast<SessionHeader_t*>(packet.data());
 
-    if (header->sessId == session::SESSION_ZERO)
+    if (header->sessId == session::sessionZero)
     {
         header->sessSeqNum = 0x00;
     }
diff --git a/session.hpp b/session.hpp
index 7b02221..4d4bf19 100644
--- a/session.hpp
+++ b/session.hpp
@@ -9,12 +9,18 @@
 
 #include <chrono>
 #include <exception>
+#include <ipmid/api.hpp>
+#include <ipmid/sessiondef.hpp>
 #include <list>
 #include <memory>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/server/object.hpp>
 #include <string>
+#include <unordered_map>
 #include <user_channel/channel_layer.hpp>
 #include <user_channel/user_layer.hpp>
 #include <vector>
+#include <xyz/openbmc_project/Ipmi/SessionInfo/server.hpp>
 
 namespace session
 {
@@ -32,14 +38,6 @@
     OEM,
 };
 
-enum class State
-{
-    INACTIVE,              // Session is not in use
-    SETUP_IN_PROGRESS,     // Session Setup Sequence is progressing
-    ACTIVE,                // Session is active
-    TEAR_DOWN_IN_PROGRESS, // When Closing Session
-};
-
 // Seconds of inactivity allowed during session setup stage
 constexpr auto SESSION_SETUP_TIMEOUT = 5s;
 // Seconds of inactivity allowed when session is active
@@ -98,7 +96,11 @@
  * active sessions established with the BMC. It is recommended that a BMC
  * implementation support at least four simultaneous sessions
  */
-class Session
+
+using SessionIface = sdbusplus::server::object::object<
+    sdbusplus::xyz::openbmc_project::Ipmi::server::SessionInfo>;
+
+class Session : public SessionIface
 {
   public:
     Session() = default;
@@ -117,10 +119,14 @@
      * @param[in] inRemoteConsoleSessID - Remote Console Session ID
      * @param[in] priv - Privilege Level requested in the Command
      */
-    Session(SessionID inRemoteConsoleSessID, Privilege priv) :
-        reqMaxPrivLevel(priv), bmcSessionID(crypto::prng::rand()),
-        remoteConsoleSessionID(inRemoteConsoleSessID)
+    Session(sdbusplus::bus::bus& bus, const char* path,
+            SessionID inRemoteConsoleSessID, SessionID BMCSessionID,
+            char priv) :
+        SessionIface(bus, path)
     {
+        reqMaxPrivLevel = static_cast<session::Privilege>(priv);
+        bmcSessionID = BMCSessionID;
+        remoteConsoleSessionID = inRemoteConsoleSessID;
     }
 
     auto getBMCSessionID() const
@@ -238,21 +244,23 @@
      * transaction time is compared against the session inactivity timeout.
      *
      */
-    bool isSessionActive()
+    bool isSessionActive(uint8_t sessionState)
     {
         auto currentTime = std::chrono::steady_clock::now();
         auto elapsedSeconds = std::chrono::duration_cast<std::chrono::seconds>(
             currentTime - lastTime);
 
+        State state = static_cast<session::State>(sessionState);
+
         switch (state)
         {
-            case State::SETUP_IN_PROGRESS:
+            case State::setupInProgress:
                 if (elapsedSeconds < SESSION_SETUP_TIMEOUT)
                 {
                     return true;
                 }
                 break;
-            case State::ACTIVE:
+            case State::active:
                 if (elapsedSeconds < SESSION_INACTIVITY_TIMEOUT)
                 {
                     return true;
@@ -265,11 +273,6 @@
     }
 
     /**
-     * @brief Session's Current Privilege Level
-     */
-    Privilege curPrivLevel = Privilege::CALLBACK;
-
-    /**
      * @brief Session's Requested Maximum Privilege Level
      */
     Privilege reqMaxPrivLevel;
@@ -280,13 +283,11 @@
     ipmi::PrivAccess sessionUserPrivAccess{};
     ipmi::ChannelAccess sessionChannelAccess{};
 
-    SequenceNumbers sequenceNums;  // Session Sequence Numbers
-    State state = State::INACTIVE; // Session State
-    std::string userName{};        // User Name
+    SequenceNumbers sequenceNums; // Session Sequence Numbers
+    std::string userName{};       // User Name
 
     /** @brief Socket channel for communicating with the remote client.*/
     std::shared_ptr<udpsocket::Channel> channelPtr;
-    uint8_t chNum;
 
   private:
     SessionID bmcSessionID = 0;           // BMC Session ID
diff --git a/sessions_manager.cpp b/sessions_manager.cpp
index 95a8a15..a8e2361 100644
--- a/sessions_manager.cpp
+++ b/sessions_manager.cpp
@@ -1,5 +1,6 @@
 #include "sessions_manager.hpp"
 
+#include "main.hpp"
 #include "session.hpp"
 
 #include <algorithm>
@@ -7,21 +8,75 @@
 #include <iomanip>
 #include <memory>
 #include <phosphor-logging/log.hpp>
+#include <sdbusplus/asio/connection.hpp>
+#include <user_channel/channel_layer.hpp>
 
 using namespace phosphor::logging;
 
 namespace session
 {
 
+static std::array<uint8_t, session::maxNetworkInstanceSupported>
+    ipmiNetworkChannelNumList = {0};
+
+void Manager::setNetworkInstance(void)
+{
+
+    uint8_t index = 0, ch = 1;
+    // Constructing net-ipmid instances list based on channel info
+    // valid channel start from 1 to 15  and assuming max 4 LAN channel
+    // supported
+
+    while (ch < ipmi::maxIpmiChannels &&
+           index < session::maxNetworkInstanceSupported)
+    {
+        ipmi::ChannelInfo chInfo;
+        ipmi::getChannelInfo(ch, chInfo);
+        if (static_cast<ipmi::EChannelMediumType>(chInfo.mediumType) ==
+            ipmi::EChannelMediumType::lan8032)
+        {
+
+            if (getInterfaceIndex() == ch)
+            {
+                ipmiNetworkInstance = index;
+            }
+
+            ipmiNetworkChannelNumList[index] = ch;
+            index++;
+        }
+        ch++;
+    }
+}
+
+uint8_t Manager::getNetworkInstance(void)
+{
+    return ipmiNetworkInstance;
+}
+
 Manager::Manager()
 {
+}
+
+void Manager::managerInit(const std::string& channel)
+{
+
     /*
      * Session ID is 0000_0000h for messages that are sent outside the session.
      * The session setup commands are sent on this session, so when the session
      * manager comes up, is creates the Session ID  0000_0000h. It is active
      * through the lifetime of the Session Manager.
      */
-    sessionsMap.emplace(0, std::make_shared<Session>());
+
+    objManager = std::make_unique<sdbusplus::server::manager::manager>(
+        *getSdBus(), session::sessionManagerRootPath);
+
+    auto objPath =
+        std::string(session::sessionManagerRootPath) + "/" + channel + "/0";
+
+    chName = channel;
+    setNetworkInstance();
+    sessionsMap.emplace(
+        0, std::make_shared<Session>(*getSdBus(), objPath.c_str(), 0, 0, 0));
 }
 
 std::shared_ptr<Session>
@@ -31,16 +86,20 @@
                           cipher::crypt::Algorithms cryptAlgo)
 {
     std::shared_ptr<Session> session = nullptr;
-    SessionID sessionID = 0;
+    SessionID bmcSessionID = 0;
     cleanStaleEntries();
-    auto activeSessions = sessionsMap.size() - MAX_SESSIONLESS_COUNT;
+    uint8_t sessionHandle = 0;
 
-    if (activeSessions < MAX_SESSION_COUNT)
+    auto activeSessions = sessionsMap.size() - session::maxSessionlessCount;
+
+    if (activeSessions < session::maxSessionCountPerChannel)
     {
         do
         {
-            session = std::make_shared<Session>(remoteConsoleSessID, priv);
-
+            bmcSessionID = (crypto::prng::rand());
+            bmcSessionID &= session::multiIntfaceSessionIDMask;
+            // In sessionID , BIT 31 BIT30 are used for netipmid instance
+            bmcSessionID |= ipmiNetworkInstance << 30;
             /*
              * Every IPMI Session has two ID's attached to it Remote Console
              * Session ID and BMC Session ID. The remote console ID is passed
@@ -51,11 +110,10 @@
              * created session is reset and a new session is created for
              * validating collision.
              */
-            auto iterator = sessionsMap.find(session->getBMCSessionID());
+            auto iterator = sessionsMap.find(bmcSessionID);
             if (iterator != sessionsMap.end())
             {
                 // Detected BMC Session ID collisions
-                session.reset();
                 continue;
             }
             else
@@ -64,6 +122,26 @@
             }
         } while (1);
 
+        sessionHandle = storeSessionHandle(bmcSessionID);
+
+        if (!sessionHandle)
+        {
+            throw std::runtime_error(
+                "Invalid sessionHandle - No sessionID slot ");
+        }
+        sessionHandle &= session::multiIntfaceSessionHandleMask;
+        // In sessionID , BIT 31 BIT30 are used for netipmid instance
+        sessionHandle |= ipmiNetworkInstance << 6;
+        std::stringstream sstream;
+        sstream << std::hex << bmcSessionID;
+        std::stringstream shstream;
+        shstream << std::hex << (int)sessionHandle;
+        auto objPath = std::string(session::sessionManagerRootPath) + "/" +
+                       chName + "/" + sstream.str() + "_" + shstream.str();
+        session = std::make_shared<Session>(*getSdBus(), objPath.c_str(),
+                                            remoteConsoleSessID, bmcSessionID,
+                                            static_cast<uint8_t>(priv));
+
         // Set the Authentication Algorithm
         switch (authAlgo)
         {
@@ -86,8 +164,10 @@
                 throw std::runtime_error("Invalid Authentication Algorithm");
             }
         }
-        sessionID = session->getBMCSessionID();
-        sessionsMap.emplace(sessionID, session);
+
+        sessionsMap.emplace(bmcSessionID, session);
+        session->sessionHandle(sessionHandle);
+
         return session;
     }
 
@@ -101,7 +181,8 @@
     auto iter = sessionsMap.find(bmcSessionID);
     if (iter != sessionsMap.end())
     {
-        iter->second->state = State::TEAR_DOWN_IN_PROGRESS;
+        iter->second->state(
+            static_cast<uint8_t>(session::State::tearDownInProgress));
         return true;
     }
     else
@@ -151,10 +232,12 @@
 {
     for (auto iter = sessionsMap.begin(); iter != sessionsMap.end();)
     {
+
         auto session = iter->second;
-        if ((session->getBMCSessionID() != SESSION_ZERO) &&
-            !(session->isSessionActive()))
+        if ((session->getBMCSessionID() != session::sessionZero) &&
+            !(session->isSessionActive(session->state())))
         {
+            sessionHandleMap[getSessionHandle(session->getBMCSessionID())] = 0;
             iter = sessionsMap.erase(iter);
         }
         else
@@ -164,4 +247,54 @@
     }
 }
 
+uint8_t Manager::storeSessionHandle(SessionID bmcSessionID)
+{
+    // Handler index 0 is  reserved for invalid session.
+    // index starts with 1, for direct usage. Index 0 reserved
+    for (uint8_t i = 1; i <= session::maxSessionCountPerChannel; i++)
+    {
+        if (sessionHandleMap[i] == 0)
+        {
+            sessionHandleMap[i] = bmcSessionID;
+            return i;
+        }
+    }
+    return 0;
+}
+
+uint32_t Manager::getSessionIDbyHandle(uint8_t sessionHandle) const
+{
+    if (sessionHandle <= session::maxSessionCountPerChannel)
+    {
+        return sessionHandleMap[sessionHandle];
+    }
+    return 0;
+}
+
+uint8_t Manager::getSessionHandle(SessionID bmcSessionID) const
+{
+
+    // Handler index 0 is reserved for invalid session.
+    // index starts with 1, for direct usage. Index 0 reserved
+
+    for (uint8_t i = 1; i <= session::maxSessionCountPerChannel; i++)
+    {
+        if (sessionHandleMap[i] == bmcSessionID)
+        {
+            return (i);
+        }
+    }
+    return 0;
+}
+uint8_t Manager::getActiveSessionCount() const
+{
+
+    return (std::count_if(
+        sessionsMap.begin(), sessionsMap.end(),
+        [](const std::pair<const uint32_t, std::shared_ptr<Session>>& in)
+            -> bool {
+            return in.second->state() ==
+                   static_cast<uint8_t>(session::State::active);
+        }));
+}
 } // namespace session
diff --git a/sessions_manager.hpp b/sessions_manager.hpp
index 9fd38b1..f9c00b5 100644
--- a/sessions_manager.hpp
+++ b/sessions_manager.hpp
@@ -2,6 +2,8 @@
 
 #include "session.hpp"
 
+#include <ipmid/api.hpp>
+#include <ipmid/sessiondef.hpp>
 #include <map>
 #include <memory>
 #include <mutex>
@@ -15,10 +17,6 @@
     RC_SESSION_ID,
 };
 
-constexpr size_t SESSION_ZERO = 0;
-constexpr size_t MAX_SESSIONLESS_COUNT = 1;
-constexpr size_t MAX_SESSION_COUNT = 15;
-
 /**
  * @class Manager
  *
@@ -82,20 +80,35 @@
     std::shared_ptr<Session>
         getSession(SessionID sessionID,
                    RetrieveOption option = RetrieveOption::BMC_SESSION_ID);
+    uint8_t getActiveSessionCount() const;
+    uint8_t getSessionHandle(SessionID bmcSessionID) const;
+    uint8_t storeSessionHandle(SessionID bmcSessionID);
+    uint32_t getSessionIDbyHandle(uint8_t sessionHandle) const;
+
+    void managerInit(const std::string& channel);
+
+    uint8_t getNetworkInstance(void);
 
   private:
+    //+1 for session, as 0 is reserved for sessionless command
+    std::array<uint32_t, session::maxSessionCountPerChannel + 1>
+        sessionHandleMap = {0};
+
     /**
      * @brief Session Manager keeps the session objects as a sorted
      *        associative container with Session ID as the unique key
      */
     SessionMap sessionsMap;
-
+    std::unique_ptr<sdbusplus::server::manager::manager> objManager = nullptr;
+    std::string chName{}; // Channel Name
+    uint8_t ipmiNetworkInstance;
     /**
      * @brief Clean Session Stale Entries
      *
      *  Removes the inactive sessions entries from the Session Map
      */
     void cleanStaleEntries();
+    void setNetworkInstance(void);
 };
 
 } // namespace session