PLDMTOOL : Implement a PLDM requester tool.

Implemented a way for sending PLDM requests for specific PLDM commands.
This tool will interact with MCTP daemon using UNIX domain socket, The
response got from socket will be displayed on the console in raw format.

Tested following PLDM commands:
    1) GetPLDMTypes
    2) GetPLDMVersion

Tested : Verified the RAW response data.

./pldmtool -c GetPLDMTypes
Encoded request succesfully : RC = 0
Request Message:
08 01 9b 00 04 00 00
Success in creating the socket : RC = 3
Success in connecting to socket : RC = 0
Success in sending message type as pldm to mctp : RC = 0
Write to socket successful : RC = 7
Total length:7
Loopback response message:
08 01 9b 00 04 00 00
On first recv(),response == request : RC = 0
Total length: 14
Shutdown Socket successful :  RC = 0
Socket recv() successful : RC = 0
Response Message :
08 01 00 00 04 00 01 00 00 00 00 00 00 00

./pldmtool -c GetPLDMVersion base
PLDM Type requested : base
Encoded request succesfully : RC = 0
Request Message:
08 01 9b 00 03 00 00 00 00 01 00
Success in creating the socket : RC = 3
Success in connecting to socket : RC = 0
Success in sending message type as pldm to mctp : RC = 0
Write to socket successful : RC = 11
Total length:11
Loopback response message:
08 01 9b 00 03 00 00 00 00 01 00
On first recv(),response == request : RC = 0
Total length: 15
Shutdown Socket successful :  RC = 0
Socket recv() successful : RC = 0
Response Message:
08 01 00 00 03 00 00 00 00 00 05 f1 f0 f0 00

./pldmtool -h
PLDM requester tool for OpenBMC
Usage: ./pldmtool [OPTIONS] —command... [GetPLDMTypes] [GetPLDMVersion] SUBCOMMA                                                                             ND

Positionals:
  —command TEXT ... REQUIRED
                              PLDM request command
  GetPLDMTypes TEXT           Get PLDM Type
  GetPLDMVersion TEXT         Get PLDM Version

Options:
  -h,--help                   Print this help message and exit
  -c TEXT ... REQUIRED        PLDM request command

Subcommands:
  BASE                        PLDM Command Type = BASE
  BIOS                        PLDM Command Type = BIOS
  OEM                         PLDM Command Type = OEM

Signed-off-by: Lakshminarayana R. Kammath <lkammath@in.ibm.com>
Change-Id: I758b5f5cf03ad9b91ce47144fe46cd79b41381f3
diff --git a/Makefile.am b/Makefile.am
old mode 100644
new mode 100755
index ff964e2..de39e66
--- a/Makefile.am
+++ b/Makefile.am
@@ -27,7 +27,7 @@
 @CODE_COVERAGE_RULES@
 endif
 
-SUBDIRS = libpldm libpldmresponder test
+SUBDIRS = libpldm libpldmresponder test tool
 
 sbin_PROGRAMS = pldmd
 pldmd_SOURCES = \
diff --git a/configure.ac b/configure.ac
old mode 100644
new mode 100755
index 0809f0b..ea36dd3
--- a/configure.ac
+++ b/configure.ac
@@ -84,6 +84,6 @@
 AC_DEFINE(BIOS_TABLES_DIR, "/var/lib/pldm/bios", [Directory housing actual BIOS tables])
 
 # Create configured output
-AC_CONFIG_FILES([Makefile libpldm/Makefile libpldmresponder/Makefile test/Makefile])
+AC_CONFIG_FILES([Makefile libpldm/Makefile libpldmresponder/Makefile test/Makefile tool/Makefile])
 AC_CONFIG_FILES([libpldm/libpldm.pc])
 AC_OUTPUT
diff --git a/tool/Makefile.am b/tool/Makefile.am
new file mode 100755
index 0000000..6818be8
--- /dev/null
+++ b/tool/Makefile.am
@@ -0,0 +1,12 @@
+pldmtool_SOURCES 	= \
+                          pldm_cmd_helper.cpp \
+                          pldm_base_cmd.cpp \
+                          pldmtool.cpp
+
+pldmtool_LDADD = \
+        ../libpldm/libpldm.la
+
+pldmtool_CPPFLAGS = \
+        -I$(top_srcdir)
+
+sbin_PROGRAMS		= pldmtool
diff --git a/tool/handler.hpp b/tool/handler.hpp
new file mode 100644
index 0000000..4ee7fe8
--- /dev/null
+++ b/tool/handler.hpp
@@ -0,0 +1,20 @@
+#pragma once
+
+#include "pldm_base_cmd.hpp"
+
+#include <functional>
+#include <map>
+#include <string>
+#include <typeindex>
+
+using Cmd = std::string;
+using Args = std::vector<std::string>;
+class Handler
+{
+  public:
+    const std::map<Cmd, std::function<void(Args&&)>> dispatcher{
+        {"GetPLDMTypes",
+         [](Args&& args) { return getPLDMTypes(std::move(args)); }},
+        {"GetPLDMVersion",
+         [](Args&& args) { return getPLDMVersion(std::move(args)); }}};
+};
diff --git a/tool/pldm_base_cmd.cpp b/tool/pldm_base_cmd.cpp
new file mode 100644
index 0000000..aa5b668
--- /dev/null
+++ b/tool/pldm_base_cmd.cpp
@@ -0,0 +1,134 @@
+#include "pldm_base_cmd.hpp"
+
+#include "pldm_cmd_helper.hpp"
+
+#include <string>
+
+constexpr uint8_t PLDM_ENTITY_ID = 8;
+constexpr uint8_t MCTP_MSG_TYPE_PLDM = 1;
+constexpr uint8_t PLDM_LOCAL_INSTANCE_ID = 0;
+
+using namespace std;
+
+/*
+ * Main function that handles the GetPLDMTypes response callback via mctp
+ *
+ */
+void getPLDMTypes(vector<std::string>&& args)
+{
+    // Create and encode the request message
+    vector<uint8_t> requestMsg(sizeof(pldm_msg_hdr) +
+                               sizeof(MCTP_MSG_TYPE_PLDM) +
+                               sizeof(PLDM_ENTITY_ID));
+    auto request = reinterpret_cast<pldm_msg*>(requestMsg.data());
+
+    // Encode the get_types request message
+    uint8_t instanceId = PLDM_LOCAL_INSTANCE_ID;
+    auto returnCode = encode_get_types_req(instanceId, request);
+    if (returnCode)
+    {
+        cerr << "Failed to encode request msg for GetPLDMType : RC = "
+             << returnCode << endl;
+        return;
+    }
+    cout << "Encoded request succesfully : RC = " << returnCode << endl;
+
+    // Insert the PLDM message type and EID at the begining of the request msg.
+    requestMsg.insert(requestMsg.begin(), MCTP_MSG_TYPE_PLDM);
+    requestMsg.insert(requestMsg.begin(), PLDM_ENTITY_ID);
+
+    cout << "Request Message:" << endl;
+    printBuffer(requestMsg);
+
+    // Create the response message
+    vector<uint8_t> responseMsg;
+
+    // Compares the response with request packet on first socket recv() call.
+    // If above condition is qualified then, reads the actual response from
+    // the socket to output buffer responseMsg.
+    returnCode = mctpSockSendRecv(requestMsg, responseMsg);
+    if (!returnCode)
+    {
+        cout << "Socket recv() successful : RC = " << returnCode << endl;
+        cout << "Response Message : " << endl;
+        printBuffer(responseMsg);
+    }
+    else
+    {
+        cerr << "Failed to recieve from socket : RC = " << returnCode << endl;
+        return;
+    }
+}
+
+/*
+ * Main function that handles the GetPLDMVersion response callback via mctp
+ *
+ */
+void getPLDMVersion(vector<std::string>&& args)
+{
+    // Create a request packet
+    std::vector<uint8_t> requestMsg(sizeof(pldm_msg_hdr) +
+                                    PLDM_GET_VERSION_REQ_BYTES);
+    auto request = reinterpret_cast<pldm_msg*>(requestMsg.data());
+    uint8_t pldmType = 0x0;
+
+    if (!args[1].c_str())
+    {
+        cout << "Mandatory argument PLDM Command Type not provided!" << endl;
+        cout << "Run pldmtool --help for more information" << endl;
+        return;
+    }
+
+    if (!strcasecmp(args[1].c_str(), "base"))
+    {
+        cout << "PLDM Type requested : " << args[1] << endl;
+        pldmType = PLDM_BASE;
+    }
+    else
+    {
+        cerr << "Unsupported pldm command type OR not supported yet : "
+             << args[1] << endl;
+        return;
+    }
+
+    uint8_t instanceId = PLDM_LOCAL_INSTANCE_ID;
+    uint32_t transferHandle = 0x0;
+    transfer_op_flag opFlag = PLDM_GET_FIRSTPART;
+
+    // encode the get_version request
+    auto returnCode = encode_get_version_req(instanceId, transferHandle, opFlag,
+                                             pldmType, request);
+    if (returnCode)
+    {
+        cerr << "Failed to encode request msg for GetPLDMVersion. RC = "
+             << returnCode << endl;
+        return;
+    }
+    cout << "Encoded request succesfully : RC = " << returnCode << endl;
+
+    // Insert the PLDM message type and EID at the begining of the request msg.
+    requestMsg.insert(requestMsg.begin(), MCTP_MSG_TYPE_PLDM);
+    requestMsg.insert(requestMsg.begin(), PLDM_ENTITY_ID);
+
+    cout << "Request Message:" << endl;
+    printBuffer(requestMsg);
+
+    // Create the response message
+    vector<uint8_t> responseMsg;
+
+    // Compares the response with request packet on first socket recv() call.
+    // If above condition is qualified then, reads the actual response from
+    // the socket to output buffer responseMsg.
+    returnCode = mctpSockSendRecv(requestMsg, responseMsg);
+    if (!returnCode)
+    {
+        cout << "Socket recv() successful : RC = " << returnCode << endl;
+        cout << "Response Message:" << endl;
+        printBuffer(responseMsg);
+    }
+    else
+    {
+        cerr << "Failed to recieve from socket : RC = " << returnCode << endl;
+        return;
+    }
+}
diff --git a/tool/pldm_base_cmd.hpp b/tool/pldm_base_cmd.hpp
new file mode 100644
index 0000000..fefdcd1
--- /dev/null
+++ b/tool/pldm_base_cmd.hpp
@@ -0,0 +1,27 @@
+#pragma once
+
+#ifndef PLDM_BASE_CMD_H
+#define PLDM_BASE_CMD_H
+
+#include "pldm_cmd_helper.hpp"
+
+/** @brief Handler for GetPLDMTypes command
+ *
+ *  @param[in]  args - Argument to be passed to the handler.
+ *                     Optional argument.
+ *
+ *  @return - None
+ */
+void getPLDMTypes(std::vector<std::string>&& args);
+
+/** @brief Handler for GetPLDMVersion command
+ *
+ *
+ *  @param[in]  args - Argument to be passed to the handler
+ *              e.g :  PLDM Command Type : base, bios etc.
+ *
+ *  @return - None
+ */
+void getPLDMVersion(std::vector<std::string>&& args);
+
+#endif /* PLDM_BASE_CMD_H */
diff --git a/tool/pldm_cmd_helper.cpp b/tool/pldm_cmd_helper.cpp
new file mode 100644
index 0000000..eea2759
--- /dev/null
+++ b/tool/pldm_cmd_helper.cpp
@@ -0,0 +1,162 @@
+#include "pldm_cmd_helper.hpp"
+
+constexpr uint8_t MCTP_MSG_TYPE_PLDM = 1;
+constexpr uint8_t PLDM_ENTITY_ID = 8;
+
+using namespace std;
+
+/*
+ * print the input buffer
+ *
+ */
+void printBuffer(const std::vector<uint8_t>& buffer)
+{
+    std::ostringstream tempStream;
+    if (!buffer.empty())
+    {
+        for (int byte : buffer)
+        {
+            tempStream << std::setfill('0') << std::setw(2) << std::hex << byte
+                       << " ";
+        }
+    }
+    cout << tempStream.str().c_str() << endl;
+}
+
+/*
+ * Initialize the socket, send pldm command & recieve response from socket
+ *
+ */
+int mctpSockSendRecv(const std::vector<uint8_t>& requestMsg,
+                     std::vector<uint8_t>& responseMsg)
+{
+    const char devPath[] = "\0mctp-mux";
+    int returnCode = 0;
+
+    int sockFd = socket(AF_UNIX, SOCK_SEQPACKET, 0);
+    if (-1 == sockFd)
+    {
+        returnCode = -errno;
+        cerr << "Failed to create the socket : RC = " << sockFd << endl;
+        return returnCode;
+    }
+    cout << "Success in creating the socket : RC = " << sockFd << endl;
+
+    struct sockaddr_un addr
+    {
+    };
+    addr.sun_family = AF_UNIX;
+
+    memcpy(addr.sun_path, devPath, sizeof(devPath) - 1);
+
+    CustomFD socketFd(sockFd);
+    int result = connect(socketFd(), reinterpret_cast<struct sockaddr*>(&addr),
+                         sizeof(devPath) + sizeof(addr.sun_family) - 1);
+    if (-1 == result)
+    {
+        returnCode = -errno;
+        cerr << "Failed to connect to socket : RC = " << returnCode << endl;
+        return returnCode;
+    }
+    cout << "Success in connecting to socket : RC = " << returnCode << endl;
+
+    auto pldmType = MCTP_MSG_TYPE_PLDM;
+    result = write(socketFd(), &pldmType, sizeof(pldmType));
+    if (-1 == result)
+    {
+        returnCode = -errno;
+        cerr << "Failed to send message type as pldm to mctp : RC = "
+             << returnCode << endl;
+        return returnCode;
+    }
+    cout << "Success in sending message type as pldm to mctp : RC = "
+         << returnCode << endl;
+
+    result = send(socketFd(), requestMsg.data(), requestMsg.size(), 0);
+    if (-1 == result)
+    {
+        returnCode = -errno;
+        cerr << "Write to socket failure : RC = " << returnCode << endl;
+        return returnCode;
+    }
+    cout << "Write to socket successful : RC = " << result << endl;
+
+    // Read the response from socket
+    ssize_t peekedLength = recv(socketFd(), nullptr, 0, MSG_TRUNC | MSG_PEEK);
+    if (0 == peekedLength)
+    {
+        cerr << "Socket is closed : peekedLength = " << peekedLength << endl;
+        return returnCode;
+    }
+    else if (peekedLength <= -1)
+    {
+        returnCode = -errno;
+        cerr << "recv() system call failed : RC = " << returnCode << endl;
+        return returnCode;
+    }
+    else
+    {
+        // loopback response message
+        std::vector<uint8_t> loopBackRespMsg(peekedLength);
+        auto recvDataLength =
+            recv(socketFd(), reinterpret_cast<void*>(loopBackRespMsg.data()),
+                 peekedLength, 0);
+        if (recvDataLength == peekedLength)
+        {
+            cout << "Total length:" << recvDataLength << endl;
+            cout << "Loopback response message:" << endl;
+            printBuffer(loopBackRespMsg);
+        }
+        else
+        {
+            cerr << "Failure to read peeked length packet : RC = " << returnCode
+                 << endl;
+            cerr << "peekedLength: " << peekedLength << endl;
+            cerr << "Total length: " << recvDataLength << endl;
+            return returnCode;
+        }
+
+        // Confirming on the first recv() the Request bit is set in
+        // pldm_msg_hdr struct. If set proceed with recv() or else, quit.
+        auto hdr = reinterpret_cast<const pldm_msg_hdr*>(&loopBackRespMsg[2]);
+        uint8_t request = hdr->request;
+        if (request == PLDM_REQUEST)
+        {
+            cout << "On first recv(),response == request : RC = " << returnCode
+                 << endl;
+            ssize_t peekedLength =
+                recv(socketFd(), nullptr, 0, MSG_PEEK | MSG_TRUNC);
+
+            responseMsg.resize(peekedLength);
+            recvDataLength =
+                recv(socketFd(), reinterpret_cast<void*>(responseMsg.data()),
+                     peekedLength, 0);
+            if (recvDataLength == peekedLength)
+            {
+                cout << "Total length: " << recvDataLength << endl;
+            }
+            else
+            {
+                cerr << "Failure to read response length packet: length = "
+                     << recvDataLength << endl;
+                return returnCode;
+            }
+        }
+        else
+        {
+            cerr << "On first recv(),request != response : RC = " << returnCode
+                 << endl;
+            return returnCode;
+        }
+    }
+    returnCode = shutdown(socketFd(), SHUT_RDWR);
+    if (-1 == returnCode)
+    {
+        returnCode = -errno;
+        cerr << "Failed to shutdown the socket : RC = " << returnCode << endl;
+        return returnCode;
+    }
+
+    cout << "Shutdown Socket successful :  RC = " << returnCode << endl;
+    return PLDM_SUCCESS;
+}
diff --git a/tool/pldm_cmd_helper.hpp b/tool/pldm_cmd_helper.hpp
new file mode 100644
index 0000000..6a4f087
--- /dev/null
+++ b/tool/pldm_cmd_helper.hpp
@@ -0,0 +1,41 @@
+#pragma once
+
+#ifndef PLDM_CMD_HELPER_H
+#define PLDM_CMD_HELPER_H
+
+#include "libpldmresponder/utils.hpp"
+
+#include <err.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include <cstring>
+#include <iomanip>
+#include <iostream>
+
+#include "libpldm/base.h"
+
+using namespace pldm::responder::utils;
+
+/** @brief Print the buffer
+ *
+ *  @param[in]  buffer  - Buffer to print
+ *
+ *  @return - None
+ */
+void printBuffer(const std::vector<uint8_t>& buffer);
+
+/** @brief MCTP socket read/recieve
+ *
+ *  @param[in]  requestMsg - Request message to compare against loopback
+ *              message recieved from mctp socket
+ *  @param[out] responseMsg - Response buffer recieved from mctp socket
+ *
+ *  @return -   0 on success.
+ *             -1 or -errno on failure.
+ */
+int mctpSockSendRecv(const std::vector<uint8_t>& requestMsg,
+                     std::vector<uint8_t>& responseMsg);
+
+#endif
diff --git a/tool/pldmtool.cpp b/tool/pldmtool.cpp
new file mode 100644
index 0000000..6956845
--- /dev/null
+++ b/tool/pldmtool.cpp
@@ -0,0 +1,42 @@
+#include "handler.hpp"
+
+#include <CLI/CLI.hpp>
+
+int main(int argc, char** argv)
+{
+
+    CLI::App app{"PLDM requester tool for OpenBMC"};
+
+    // TODO: To enable it later
+    // bool verbose_flag = false;
+    // app.add_flag("-v, --verbose", verbose_flag, "Output debug logs ");
+    std::vector<std::string> args{};
+    app.add_option("-c, —command", args, "PLDM request command")->required();
+
+    std::string pldmCmdName;
+    app.add_option("GetPLDMTypes", pldmCmdName, "Get PLDM Type");
+    app.add_option("GetPLDMVersion", pldmCmdName, "Get PLDM Version");
+
+    app.add_subcommand("base", "PLDM Command Type = base");
+    app.add_subcommand("bios", "PLDM Command Type = bios");
+    app.add_subcommand("oem", "PLDM Command Type = oem");
+
+    CLI11_PARSE(app, argc, argv);
+
+    Handler handler;
+
+    // Parse args to program
+    std::string cmdName = args[0];
+    int rc = 0;
+
+    try
+    {
+        handler.dispatcher.at(cmdName)(std::move(args));
+    }
+    catch (const std::out_of_range& e)
+    {
+        std::cerr << cmdName << " is not supported!" << std::endl;
+        rc = -1;
+    }
+    return (rc < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
+}