Add RMCP Ping support

Added support of RMCP Ping/Pong request and response
(ASF messages).

Tested: Tested using rmcpping tool to send RMCP ping.

Resolves openbmc/phosphor-net-ipmid#15

Signed-off-by: Kirill Pakhomov <k.pakhomov@yadro.com>
Change-Id: Ie5199e6af69860d9406bdd516952b62c3d05793f
diff --git a/configure.ac b/configure.ac
index 36b0df4..8e8ad57 100644
--- a/configure.ac
+++ b/configure.ac
@@ -66,6 +66,17 @@
       [AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir])])
 AM_CONDITIONAL([HAVE_SYSTEMD], [test "x$with_systemdsystemunitdir" != "xno"])
 
+AC_ARG_ENABLE([rmcp-ping],
+        AS_HELP_STRING([--enable-rmcp-ping], [Enable RMCP Ping support])
+    )
+    AS_IF([test "x$enable_rmcp_ping" == "xyes"],
+        AC_MSG_NOTICE([Enabling RMCP Ping])
+        [
+            cpp_flags="-DRMCP_PING"
+        ]
+        AC_SUBST([CPPFLAGS], [$cpp_flags])
+)
+
 # Checks for header files.
 AC_CHECK_HEADER(systemd/sd-bus.h, ,[AC_MSG_ERROR([Could not find systemd/sd-bus.h...systemd development package required])])
 
diff --git a/message.hpp b/message.hpp
index 738652c..f01fcc9 100644
--- a/message.hpp
+++ b/message.hpp
@@ -21,6 +21,24 @@
     INVALID = 0xFF,
 };
 
+// RMCP Classes of Message as per section 13.1.3.
+enum class ClassOfMsg : uint8_t
+{
+    RESERVED = 0x05,
+    ASF = 0x06,
+    IPMI = 0x07,
+    OEM = 0x08,
+};
+
+#ifdef RMCP_PING
+// RMCP Message Type as per section 13.1.3.
+enum class RmcpMsgType : uint8_t
+{
+    PING = 0x80,
+    PONG = 0x40,
+};
+#endif // RMCP_PING
+
 namespace LAN
 {
 
@@ -103,7 +121,8 @@
     Message() :
         payloadType(PayloadType::INVALID),
         rcSessionID(Message::MESSAGE_INVALID_SESSION_ID),
-        bmcSessionID(Message::MESSAGE_INVALID_SESSION_ID)
+        bmcSessionID(Message::MESSAGE_INVALID_SESSION_ID),
+        rmcpMsgClass(ClassOfMsg::RESERVED)
     {
     }
 
@@ -120,7 +139,7 @@
         isPacketEncrypted(other.isPacketEncrypted),
         isPacketAuthenticated(other.isPacketAuthenticated),
         payloadType(other.payloadType), rcSessionID(other.rcSessionID),
-        bmcSessionID(other.bmcSessionID)
+        bmcSessionID(other.bmcSessionID), rmcpMsgClass(other.rmcpMsgClass)
     {
         // special behavior for rmcp+ session creation
         if (PayloadType::OPEN_SESSION_REQUEST == other.payloadType)
@@ -227,6 +246,10 @@
     uint32_t rcSessionID;       // Remote Client's Session ID
     uint32_t bmcSessionID;      // BMC's session ID
     uint32_t sessionSeqNum;     // Session Sequence Number
+    ClassOfMsg rmcpMsgClass;    // Class of Message
+#ifdef RMCP_PING
+    uint8_t asfMsgTag; // ASF Message Tag
+#endif                 // RMCP_PING
 
     /** @brief Message payload
      *
diff --git a/message_handler.cpp b/message_handler.cpp
index b2b0607..6dadbfc 100644
--- a/message_handler.cpp
+++ b/message_handler.cpp
@@ -37,6 +37,11 @@
     // Unflatten the packet
     std::tie(inMessage, sessionHeader) = parser::unflatten(packet);
 
+    return true;
+}
+
+void Handler::updSessionData(std::shared_ptr<Message>& inMessage)
+{
     auto session = std::get<session::Manager&>(singletonPool)
                        .getSession(inMessage->bmcSessionID);
 
@@ -48,30 +53,37 @@
     uint32_t ipAddr = 0;
     channel->getRemoteAddress(ipAddr);
     session->remoteIPAddr(ipAddr);
-
-    return true;
 }
 
 Handler::~Handler()
 {
-    if (outPayload)
+    try
     {
-        std::shared_ptr<Message> outMessage =
-            inMessage->createResponse(*outPayload);
-        if (!outMessage)
+#ifdef RMCP_PING
+        if (ClassOfMsg::ASF == inMessage->rmcpMsgClass)
         {
-            return;
+            sendASF();
         }
-        try
+        else
+#endif // RMCP_PING
         {
-            send(outMessage);
+            if (outPayload)
+            {
+                std::shared_ptr<Message> outMessage =
+                    inMessage->createResponse(*outPayload);
+                if (!outMessage)
+                {
+                    return;
+                }
+                send(outMessage);
+            }
         }
-        catch (const std::exception& e)
-        {
-            // send failed, most likely due to a session closure
-            log<level::INFO>("Async RMCP+ reply failed",
-                             entry("EXCEPTION=%s", e.what()));
-        }
+    }
+    catch (const std::exception& e)
+    {
+        // send failed, most likely due to a session closure
+        log<level::INFO>("Async RMCP+ reply failed",
+                         entry("EXCEPTION=%s", e.what()));
     }
 }
 
@@ -83,14 +95,21 @@
         return;
     }
 
+#ifdef RMCP_PING
     // Execute the Command, possibly asynchronously
-    executeCommand();
+    if (ClassOfMsg::ASF != inMessage->rmcpMsgClass)
+#endif // RMCP_PING
+    {
+        updSessionData(inMessage);
+        executeCommand();
+    }
 
     // send happens during the destructor if a payload was set
 }
 
 void Handler::executeCommand()
 {
+
     // Get the CommandID to map into the command table
     auto command = inMessage->getCommand();
     if (inMessage->payloadType == PayloadType::IPMI)
@@ -128,6 +147,26 @@
     }
 }
 
+void Handler::writeData(const std::vector<uint8_t>& packet)
+{
+    auto writeStatus = channel->write(packet);
+    if (writeStatus < 0)
+    {
+        throw std::runtime_error("Error in writing to socket");
+    }
+}
+
+#ifdef RMCP_PING
+void Handler::sendASF()
+{
+    // Flatten the packet
+    auto packet = asfparser::flatten(inMessage->asfMsgTag);
+
+    // Write the packet
+    writeData(packet);
+}
+#endif // RMCP_PING
+
 void Handler::send(std::shared_ptr<Message> outMessage)
 {
     auto session =
@@ -137,11 +176,7 @@
     auto packet = parser::flatten(outMessage, sessionHeader, session);
 
     // Write the packet
-    auto writeStatus = channel->write(packet);
-    if (writeStatus < 0)
-    {
-        throw std::runtime_error("Error in writing to socket");
-    }
+    writeData(packet);
 }
 
 void Handler::setChannelInSession() const
diff --git a/message_handler.hpp b/message_handler.hpp
index 533ed6a..2eb4737 100644
--- a/message_handler.hpp
+++ b/message_handler.hpp
@@ -93,6 +93,12 @@
     bool receive();
 
     /**
+     * @brief Get Session data from the IPMI packet
+     *
+     */
+    void updSessionData(std::shared_ptr<Message>& inMessage);
+
+    /**
      * @brief Process the incoming IPMI message
      *
      * The incoming message payload is handled and the command handler for
@@ -110,6 +116,21 @@
      */
     void send(std::shared_ptr<Message> outMessage);
 
+#ifdef RMCP_PING
+    /** @brief Send the outgoing ASF message
+     *
+     *  The outgoing ASF message contains only ASF message header
+     *  which is flattened and sent out on the socket
+     */
+    void sendASF();
+#endif // RMCP_PING
+
+    /** @brief Write the packet to the socket
+     *
+     *  @param[in] packet - Outgoing packet
+     */
+    void writeData(const std::vector<uint8_t>& packet);
+
     /** @brief Socket channel for communicating with the remote client.*/
     std::shared_ptr<udpsocket::Channel> channel;
 
diff --git a/message_parsers.cpp b/message_parsers.cpp
index 6703fc3..18f8389 100644
--- a/message_parsers.cpp
+++ b/message_parsers.cpp
@@ -17,24 +17,37 @@
     unflatten(std::vector<uint8_t>& inPacket)
 {
     // Check if the packet has atleast the size of the RMCP Header
-    if (inPacket.size() < sizeof(BasicHeader_t))
+    if (inPacket.size() < sizeof(RmcpHeader_t))
     {
         throw std::runtime_error("RMCP Header missing");
     }
 
-    auto rmcpHeaderPtr = reinterpret_cast<BasicHeader_t*>(inPacket.data());
+    auto rmcpHeaderPtr = reinterpret_cast<RmcpHeader_t*>(inPacket.data());
 
     // Verify if the fields in the RMCP header conforms to the specification
     if ((rmcpHeaderPtr->version != RMCP_VERSION) ||
         (rmcpHeaderPtr->rmcpSeqNum != RMCP_SEQ) ||
-        (rmcpHeaderPtr->classOfMsg != RMCP_MESSAGE_CLASS_IPMI))
+        (rmcpHeaderPtr->classOfMsg < static_cast<uint8_t>(ClassOfMsg::ASF) &&
+         rmcpHeaderPtr->classOfMsg > static_cast<uint8_t>(ClassOfMsg::OEM)))
     {
         throw std::runtime_error("RMCP Header is invalid");
     }
 
+    if (rmcpHeaderPtr->classOfMsg == static_cast<uint8_t>(ClassOfMsg::ASF))
+    {
+#ifndef RMCP_PING
+        throw std::runtime_error("RMCP Ping is not supported");
+#else
+        return std::make_tuple(asfparser::unflatten(inPacket),
+                               SessionHeader::IPMI15);
+#endif // RMCP_PING
+    }
+
+    auto sessionHeaderPtr = reinterpret_cast<BasicHeader_t*>(inPacket.data());
+
     // Read the Session Header and invoke the parser corresponding to the
     // header type
-    switch (static_cast<SessionHeader>(rmcpHeaderPtr->format.formatType))
+    switch (static_cast<SessionHeader>(sessionHeaderPtr->format.formatType))
     {
         case SessionHeader::IPMI15:
         {
@@ -96,6 +109,8 @@
     message->sessionSeqNum = endian::from_ipmi(header->sessSeqNum);
     message->isPacketEncrypted = false;
     message->isPacketAuthenticated = false;
+    message->rmcpMsgClass =
+        static_cast<ClassOfMsg>(header->base.rmcp.classOfMsg);
 
     auto payloadLen = header->payloadLength;
 
@@ -120,10 +135,10 @@
 
     // Insert Session Header into the Packet
     auto header = reinterpret_cast<SessionHeader_t*>(packet.data());
-    header->base.version = parser::RMCP_VERSION;
-    header->base.reserved = 0x00;
-    header->base.rmcpSeqNum = parser::RMCP_SEQ;
-    header->base.classOfMsg = parser::RMCP_MESSAGE_CLASS_IPMI;
+    header->base.rmcp.version = parser::RMCP_VERSION;
+    header->base.rmcp.reserved = 0x00;
+    header->base.rmcp.rmcpSeqNum = parser::RMCP_SEQ;
+    header->base.rmcp.classOfMsg = static_cast<uint8_t>(ClassOfMsg::IPMI);
     header->base.format.formatType =
         static_cast<uint8_t>(parser::SessionHeader::IPMI15);
     header->sessSeqNum = 0;
@@ -168,6 +183,8 @@
         ((header->payloadType & PAYLOAD_ENCRYPT_MASK) ? true : false);
     message->isPacketAuthenticated =
         ((header->payloadType & PAYLOAD_AUTH_MASK) ? true : false);
+    message->rmcpMsgClass =
+        static_cast<ClassOfMsg>(header->base.rmcp.classOfMsg);
 
     auto payloadLen = endian::from_ipmi(header->payloadLength);
 
@@ -202,10 +219,10 @@
     std::vector<uint8_t> packet(sizeof(SessionHeader_t));
 
     SessionHeader_t* header = reinterpret_cast<SessionHeader_t*>(packet.data());
-    header->base.version = parser::RMCP_VERSION;
-    header->base.reserved = 0x00;
-    header->base.rmcpSeqNum = parser::RMCP_SEQ;
-    header->base.classOfMsg = parser::RMCP_MESSAGE_CLASS_IPMI;
+    header->base.rmcp.version = parser::RMCP_VERSION;
+    header->base.rmcp.reserved = 0x00;
+    header->base.rmcp.rmcpSeqNum = parser::RMCP_SEQ;
+    header->base.rmcp.classOfMsg = static_cast<uint8_t>(ClassOfMsg::IPMI);
     header->base.format.formatType =
         static_cast<uint8_t>(parser::SessionHeader::IPMI20);
     header->payloadType = static_cast<uint8_t>(outMessage->payloadType);
@@ -370,4 +387,53 @@
 
 } // namespace ipmi20parser
 
+#ifdef RMCP_PING
+namespace asfparser
+{
+std::shared_ptr<Message> unflatten(std::vector<uint8_t>& inPacket)
+{
+    auto message = std::make_shared<Message>();
+
+    auto header = reinterpret_cast<AsfMessagePing_t*>(inPacket.data());
+
+    message->payloadType = PayloadType::IPMI;
+    message->rmcpMsgClass = ClassOfMsg::ASF;
+    message->asfMsgTag = header->msgTag;
+
+    return message;
+}
+
+std::vector<uint8_t> flatten(uint8_t asfMsgTag)
+{
+    std::vector<uint8_t> packet(sizeof(AsfMessagePong_t));
+
+    // Insert RMCP header into the Packet
+    auto header = reinterpret_cast<AsfMessagePong_t*>(packet.data());
+    header->ping.rmcp.version = parser::RMCP_VERSION;
+    header->ping.rmcp.reserved = 0x00;
+    header->ping.rmcp.rmcpSeqNum = parser::RMCP_SEQ;
+    header->ping.rmcp.classOfMsg = static_cast<uint8_t>(ClassOfMsg::ASF);
+
+    // No OEM-specific capabilities exist, therefore the second
+    // IANA Enterprise Number contains the same IANA(4542)
+    header->ping.iana = header->iana = endian::to_ipmi(parser::ASF_IANA);
+    header->ping.msgType = static_cast<uint8_t>(RmcpMsgType::PONG);
+    header->ping.msgTag = asfMsgTag;
+    header->ping.reserved = 0x00;
+    header->ping.dataLen =
+        parser::RMCP_ASF_PONG_DATA_LEN; // as per spec 13.2.4,
+
+    header->iana = parser::ASF_IANA;
+    header->oemDefined = 0x00;
+    header->suppEntities = parser::ASF_SUPP_ENT;
+    header->suppInteract = parser::ASF_SUPP_INT;
+    header->reserved1 = 0x00;
+    header->reserved2 = 0x00;
+
+    return packet;
+}
+
+} // namespace asfparser
+#endif // RMCP_PING
+
 } // namespace message
diff --git a/message_parsers.hpp b/message_parsers.hpp
index 5738c43..b26f9a3 100644
--- a/message_parsers.hpp
+++ b/message_parsers.hpp
@@ -24,6 +24,19 @@
 // RMCP Session Header Size
 constexpr size_t RMCP_SESSION_HEADER_SIZE = 4;
 
+// RMCP/ASF Pong Message ASF Header Data Length
+// as per IPMI spec 13.2.4
+constexpr size_t RMCP_ASF_PONG_DATA_LEN = 16;
+
+// ASF IANA
+constexpr uint32_t ASF_IANA = 4542;
+
+// ASF Supported Entities
+constexpr uint32_t ASF_SUPP_ENT = 0x81;
+
+// ASF Supported Entities
+constexpr uint32_t ASF_SUPP_INT = 0x00;
+
 // Maximum payload size
 constexpr size_t MAX_PAYLOAD_SIZE = 255;
 
@@ -34,13 +47,20 @@
     INVALID = 0xFF,
 };
 
-struct BasicHeader_t
+// RMCP Header
+struct RmcpHeader_t
 {
     // RMCP Header
     uint8_t version;
     uint8_t reserved;
     uint8_t rmcpSeqNum;
     uint8_t classOfMsg;
+} __attribute__((packed));
+
+struct BasicHeader_t
+{
+    // RMCP Header
+    struct RmcpHeader_t rmcp;
 
     // IPMI partial session header
     union
@@ -226,4 +246,54 @@
 
 } // namespace ipmi20parser
 
+#ifdef RMCP_PING
+namespace asfparser
+{
+
+// ASF message fields for RMCP Ping message
+struct AsfMessagePing_t
+{
+    struct parser::RmcpHeader_t rmcp;
+
+    uint32_t iana;
+    uint8_t msgType;
+    uint8_t msgTag;
+    uint8_t reserved;
+    uint8_t dataLen;
+} __attribute__((packed));
+
+// ASF message fields for RMCP Pong message
+struct AsfMessagePong_t
+{
+    struct AsfMessagePing_t ping;
+
+    uint32_t iana;
+    uint32_t oemDefined;
+    uint8_t suppEntities;
+    uint8_t suppInteract;
+    uint32_t reserved1;
+    uint16_t reserved2;
+} __attribute__((packed));
+
+/**
+ * @brief Unflatten an incoming packet and prepare the ASF message
+ *
+ * @param[in] inPacket - Incoming ASF packet
+ *
+ * @return ASF message in the packet on success
+ */
+std::shared_ptr<Message> unflatten(std::vector<uint8_t>& inPacket);
+
+/**
+ * @brief Generate the ASF packet with the RMCP header
+ *
+ * @param[in] asfMsgTag - ASF Message Tag from Ping request
+ *
+ * @return ASF packet on success
+ */
+std::vector<uint8_t> flatten(uint8_t asfMsgTag);
+
+} // namespace asfparser
+#endif // RMCP_PING
+
 } // namespace message