libpldm: Add APIs to enable PLDM Requester flow

Implement requester APIs outlined at
https://github.com/openbmc/docs/blob/master/designs/pldm-stack.md#Requester.

Sync (send request msg and wait for response) and async (send request
msg and have caller poll an fd) APIs have been implemented. Example apps
that use both flavor of APIs have been implemented as well.

These APIs depend on openbmc/libmctp. For that reason and given the
OpenBMC agnostic nature of libpldm, these APIs must be conditionally
built via a meson feature (requester-api).

Signed-off-by: Deepak Kodihalli <dkodihal@in.ibm.com>
Change-Id: I666a534d8c876638a29074d62c2ed700f33229a8
diff --git a/libpldm/meson.build b/libpldm/meson.build
index 4cd9ba3..63bd8ad 100644
--- a/libpldm/meson.build
+++ b/libpldm/meson.build
@@ -30,6 +30,16 @@
   libpldm_headers += ['../oem/ibm']
 endif
 
+if get_option('requester-api').enabled()
+  headers += [
+    'requester/pldm.h'
+  ]
+  sources += [
+    'requester/pldm.c'
+  ]
+  libpldm_headers += ['requester']
+endif
+
 install_headers(
   headers,
   subdir: 'libpldm')
diff --git a/libpldm/requester/pldm.c b/libpldm/requester/pldm.c
new file mode 100644
index 0000000..932f55e
--- /dev/null
+++ b/libpldm/requester/pldm.c
@@ -0,0 +1,188 @@
+#include "pldm.h"
+#include "base.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+const uint8_t MCTP_MSG_TYPE_PLDM = 1;
+
+pldm_requester_rc_t pldm_open()
+{
+	int fd = -1;
+	int rc = -1;
+
+	fd = socket(AF_UNIX, SOCK_SEQPACKET, 0);
+	if (-1 == fd) {
+		return fd;
+	}
+
+	const char path[] = "\0mctp-mux";
+	struct sockaddr_un addr;
+	addr.sun_family = AF_UNIX;
+	memcpy(addr.sun_path, path, sizeof(path) - 1);
+	rc = connect(fd, (struct sockaddr *)&addr,
+		     sizeof(path) + sizeof(addr.sun_family) - 1);
+	if (-1 == rc) {
+		return PLDM_REQUESTER_OPEN_FAIL;
+	}
+	rc = write(fd, &MCTP_MSG_TYPE_PLDM, sizeof(MCTP_MSG_TYPE_PLDM));
+	if (-1 == rc) {
+		return PLDM_REQUESTER_OPEN_FAIL;
+	}
+
+	return fd;
+}
+
+/**
+ * @brief Read MCTP socket. If there's data available, return success only if
+ *        data is a PLDM message.
+ *
+ * @param[in] eid - destination MCTP eid
+ * @param[in] mctp_fd - MCTP socket fd
+ * @param[out] pldm_resp_msg - *pldm_resp_msg will point to PLDM msg,
+ *             this function allocates memory, caller to free(*pldm_resp_msg) on
+ *             success.
+ * @param[out] resp_msg_len - caller owned pointer that will be made point to
+ *             the size of the PLDM msg.
+ *
+ * @return pldm_requester_rc_t (errno may be set). failure is returned even
+ *         when data was read, but wasn't a PLDM response message
+ */
+static pldm_requester_rc_t mctp_recv(mctp_eid_t eid, int mctp_fd,
+				     uint8_t **pldm_resp_msg,
+				     size_t *resp_msg_len)
+{
+	ssize_t min_len = sizeof(eid) + sizeof(MCTP_MSG_TYPE_PLDM) +
+			  sizeof(struct pldm_msg_hdr);
+	ssize_t length = recv(mctp_fd, NULL, 0, MSG_PEEK | MSG_TRUNC);
+	if (length <= 0) {
+		return PLDM_REQUESTER_RECV_FAIL;
+	} else if (length < min_len) {
+		/* read and discard */
+		uint8_t buf[length];
+		recv(mctp_fd, buf, length, 0);
+		return PLDM_REQUESTER_INVALID_RECV_LEN;
+	} else {
+		struct iovec iov[2];
+		size_t mctp_prefix_len =
+		    sizeof(eid) + sizeof(MCTP_MSG_TYPE_PLDM);
+		uint8_t mctp_prefix[mctp_prefix_len];
+		size_t pldm_len = length - mctp_prefix_len;
+		iov[0].iov_len = mctp_prefix_len;
+		iov[0].iov_base = mctp_prefix;
+		*pldm_resp_msg = malloc(pldm_len);
+		iov[1].iov_len = pldm_len;
+		iov[1].iov_base = *pldm_resp_msg;
+		struct msghdr msg = {0};
+		msg.msg_iov = iov;
+		msg.msg_iovlen = sizeof(iov) / sizeof(iov[0]);
+		ssize_t bytes = recvmsg(mctp_fd, &msg, 0);
+		if (length != bytes) {
+			free(*pldm_resp_msg);
+			return PLDM_REQUESTER_INVALID_RECV_LEN;
+		}
+		if ((mctp_prefix[0] != eid) ||
+		    (mctp_prefix[1] != MCTP_MSG_TYPE_PLDM)) {
+			free(*pldm_resp_msg);
+			return PLDM_REQUESTER_NOT_PLDM_MSG;
+		}
+		*resp_msg_len = pldm_len;
+		return PLDM_REQUESTER_SUCCESS;
+	}
+}
+
+pldm_requester_rc_t pldm_recv_any(mctp_eid_t eid, int mctp_fd,
+				  uint8_t **pldm_resp_msg, size_t *resp_msg_len)
+{
+	pldm_requester_rc_t rc =
+	    mctp_recv(eid, mctp_fd, pldm_resp_msg, resp_msg_len);
+	if (rc != PLDM_REQUESTER_SUCCESS) {
+		return rc;
+	}
+
+	struct pldm_msg_hdr *hdr = (struct pldm_msg_hdr *)(*pldm_resp_msg);
+	if (hdr->request != PLDM_RESPONSE) {
+		free(*pldm_resp_msg);
+		return PLDM_REQUESTER_NOT_RESP_MSG;
+	}
+
+	uint8_t pldm_rc = 0;
+	if (*resp_msg_len < (sizeof(struct pldm_msg_hdr) + sizeof(pldm_rc))) {
+		free(*pldm_resp_msg);
+		return PLDM_REQUESTER_RESP_MSG_TOO_SMALL;
+	}
+
+	return PLDM_REQUESTER_SUCCESS;
+}
+
+pldm_requester_rc_t pldm_recv(mctp_eid_t eid, int mctp_fd, uint8_t instance_id,
+			      uint8_t **pldm_resp_msg, size_t *resp_msg_len)
+{
+	pldm_requester_rc_t rc =
+	    pldm_recv_any(eid, mctp_fd, pldm_resp_msg, resp_msg_len);
+	if (rc != PLDM_REQUESTER_SUCCESS) {
+		return rc;
+	}
+
+	struct pldm_msg_hdr *hdr = (struct pldm_msg_hdr *)(*pldm_resp_msg);
+	if (hdr->instance_id != instance_id) {
+		free(*pldm_resp_msg);
+		return PLDM_REQUESTER_INSTANCE_ID_MISMATCH;
+	}
+
+	return PLDM_REQUESTER_SUCCESS;
+}
+
+pldm_requester_rc_t pldm_send_recv(mctp_eid_t eid, int mctp_fd,
+				   const uint8_t *pldm_req_msg,
+				   size_t req_msg_len, uint8_t **pldm_resp_msg,
+				   size_t *resp_msg_len)
+{
+	struct pldm_msg_hdr *hdr = (struct pldm_msg_hdr *)pldm_req_msg;
+	if ((hdr->request != PLDM_REQUEST) &&
+	    (hdr->request != PLDM_ASYNC_REQUEST_NOTIFY)) {
+		return PLDM_REQUESTER_NOT_REQ_MSG;
+	}
+
+	pldm_requester_rc_t rc =
+	    pldm_send(eid, mctp_fd, pldm_req_msg, req_msg_len);
+	if (rc != PLDM_REQUESTER_SUCCESS) {
+		return rc;
+	}
+
+	while (1) {
+		rc = pldm_recv(eid, mctp_fd, hdr->instance_id, pldm_resp_msg,
+			       resp_msg_len);
+		if (rc == PLDM_REQUESTER_SUCCESS) {
+			break;
+		}
+	}
+
+	return rc;
+}
+
+pldm_requester_rc_t pldm_send(mctp_eid_t eid, int mctp_fd,
+			      const uint8_t *pldm_req_msg, size_t req_msg_len)
+{
+	uint8_t hdr[2] = {eid, MCTP_MSG_TYPE_PLDM};
+
+	struct iovec iov[2];
+	iov[0].iov_base = hdr;
+	iov[0].iov_len = sizeof(hdr);
+	iov[1].iov_base = (uint8_t *)pldm_req_msg;
+	iov[1].iov_len = req_msg_len;
+
+	struct msghdr msg = {0};
+	msg.msg_iov = iov;
+	msg.msg_iovlen = sizeof(iov) / sizeof(iov[0]);
+
+	ssize_t rc = sendmsg(mctp_fd, &msg, 0);
+	if (rc == -1) {
+		return PLDM_REQUESTER_SEND_FAIL;
+	}
+	return PLDM_REQUESTER_SUCCESS;
+}
diff --git a/libpldm/requester/pldm.h b/libpldm/requester/pldm.h
new file mode 100644
index 0000000..9c5c71e
--- /dev/null
+++ b/libpldm/requester/pldm.h
@@ -0,0 +1,113 @@
+#ifndef MCTP_H
+#define MCTP_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stddef.h>
+#include <stdint.h>
+
+typedef uint8_t mctp_eid_t;
+
+typedef enum pldm_requester_error_codes {
+	PLDM_REQUESTER_SUCCESS = 0,
+	PLDM_REQUESTER_OPEN_FAIL = -1,
+	PLDM_REQUESTER_NOT_PLDM_MSG = -2,
+	PLDM_REQUESTER_NOT_RESP_MSG = -3,
+	PLDM_REQUESTER_NOT_REQ_MSG = -4,
+	PLDM_REQUESTER_RESP_MSG_TOO_SMALL = -5,
+	PLDM_REQUESTER_INSTANCE_ID_MISMATCH = -6,
+	PLDM_REQUESTER_SEND_FAIL = -7,
+	PLDM_REQUESTER_RECV_FAIL = -8,
+	PLDM_REQUESTER_INVALID_RECV_LEN = -9,
+} pldm_requester_rc_t;
+
+/**
+ * @brief Connect to the MCTP socket and provide an fd to it. The fd can be
+ *        used to pass as input to other APIs below, or can be polled.
+ *
+ * @return fd on success, pldm_requester_rc_t on error (errno may be set)
+ */
+pldm_requester_rc_t pldm_open();
+
+/**
+ * @brief Send a PLDM request message. Wait for corresponding response message,
+ *        which once received, is returned to the caller.
+ *
+ * @param[in] eid - destination MCTP eid
+ * @param[in] mctp_fd - MCTP socket fd
+ * @param[in] pldm_req_msg - caller owned pointer to PLDM request msg
+ * @param[in] req_msg_len - size of PLDM request msg
+ * @param[out] pldm_resp_msg - *pldm_resp_msg will point to PLDM response msg,
+ *             this function allocates memory, caller to free(*pldm_resp_msg) on
+ *             success.
+ * @param[out] resp_msg_len - caller owned pointer that will be made point to
+ *             the size of the PLDM response msg.
+ *
+ * @return pldm_requester_rc_t (errno may be set)
+ */
+pldm_requester_rc_t pldm_send_recv(mctp_eid_t eid, int mctp_fd,
+				   const uint8_t *pldm_req_msg,
+				   size_t req_msg_len, uint8_t **pldm_resp_msg,
+				   size_t *resp_msg_len);
+
+/**
+ * @brief Send a PLDM request message, don't wait for response. Essentially an
+ *        async API. A user of this would typically have added the MCTP fd to an
+ *        event loop for polling. Once there's data available, the user would
+ *        invoke pldm_recv().
+ *
+ * @param[in] eid - destination MCTP eid
+ * @param[in] mctp_fd - MCTP socket fd
+ * @param[in] pldm_req_msg - caller owned pointer to PLDM request msg
+ * @param[in] req_msg_len - size of PLDM request msg
+ *
+ * @return pldm_requester_rc_t (errno may be set)
+ */
+pldm_requester_rc_t pldm_send(mctp_eid_t eid, int mctp_fd,
+			      const uint8_t *pldm_req_msg, size_t req_msg_len);
+
+/**
+ * @brief Read MCTP socket. If there's data available, return success only if
+ *        data is a PLDM response message that matches eid and instance_id.
+ *
+ * @param[in] eid - destination MCTP eid
+ * @param[in] mctp_fd - MCTP socket fd
+ * @param[in] instance_id - PLDM instance id of previously sent PLDM request msg
+ * @param[out] pldm_resp_msg - *pldm_resp_msg will point to PLDM response msg,
+ *             this function allocates memory, caller to free(*pldm_resp_msg) on
+ *             success.
+ * @param[out] resp_msg_len - caller owned pointer that will be made point to
+ *             the size of the PLDM response msg.
+ *
+ * @return pldm_requester_rc_t (errno may be set). failure is returned even
+ *         when data was read, but didn't match eid or instance_id.
+ */
+pldm_requester_rc_t pldm_recv(mctp_eid_t eid, int mctp_fd, uint8_t instance_id,
+			      uint8_t **pldm_resp_msg, size_t *resp_msg_len);
+
+/**
+ * @brief Read MCTP socket. If there's data available, return success only if
+ *        data is a PLDM response message.
+ *
+ * @param[in] eid - destination MCTP eid
+ * @param[in] mctp_fd - MCTP socket fd
+ * @param[out] pldm_resp_msg - *pldm_resp_msg will point to PLDM response msg,
+ *             this function allocates memory, caller to free(*pldm_resp_msg) on
+ *             success.
+ * @param[out] resp_msg_len - caller owned pointer that will be made point to
+ *             the size of the PLDM response msg.
+ *
+ * @return pldm_requester_rc_t (errno may be set). failure is returned even
+ *         when data was read, but wasn't a PLDM response message
+ */
+pldm_requester_rc_t pldm_recv_any(mctp_eid_t eid, int mctp_fd,
+				  uint8_t **pldm_resp_msg,
+				  size_t *resp_msg_len);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* MCTP_H */
diff --git a/meson.build b/meson.build
index db8e74e..547466a 100644
--- a/meson.build
+++ b/meson.build
@@ -48,3 +48,7 @@
 if get_option('tests').enabled()
   subdir('test')
 endif
+
+if get_option('utilities').enabled()
+  subdir('utilities')
+endif
diff --git a/meson_options.txt b/meson_options.txt
index 97e6580..1c059db 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -1,3 +1,5 @@
 option('tests', type: 'feature', description: 'Build tests', value: 'enabled')
 option('oe-sdk', type: 'feature', description: 'Enable OE SDK')
 option('oem-ibm', type: 'feature', description: 'Enable IBM OEM PLDM', value: 'enabled')
+option('requester-api', type: 'feature', description: 'Enable libpldm requester API', value: 'enabled')
+option('utilities', type: 'feature', description: 'Enable debug utilities', value: 'enabled')
diff --git a/utilities/meson.build b/utilities/meson.build
new file mode 100644
index 0000000..98d2807
--- /dev/null
+++ b/utilities/meson.build
@@ -0,0 +1,14 @@
+deps = [ libpldm, dependency('sdeventplus') ]
+
+executable('set-state-effecter', 'requester/set_state_effecter.cpp',
+           implicit_include_directories: false,
+           dependencies: deps,
+           install: true,
+           install_dir: get_option('bindir'))
+
+executable('set-state-effecter-async',
+           'requester/set_state_effecter_async.cpp',
+           implicit_include_directories: false,
+           dependencies: deps,
+           install: true,
+           install_dir: get_option('bindir'))
diff --git a/utilities/requester/set_state_effecter.cpp b/utilities/requester/set_state_effecter.cpp
new file mode 100644
index 0000000..09f5295
--- /dev/null
+++ b/utilities/requester/set_state_effecter.cpp
@@ -0,0 +1,61 @@
+#include <CLI/CLI.hpp>
+#include <array>
+#include <iostream>
+
+#include "libpldm/platform.h"
+#include "libpldm/requester/pldm.h"
+
+int main(int argc, char** argv)
+{
+    CLI::App app{"Send PLDM command SetStateEffecterStates"};
+    uint8_t mctpEid{};
+    app.add_option("-m,--mctp_eid", mctpEid, "MCTP EID")->required();
+    uint16_t effecterId{};
+    app.add_option("-e,--effecter", effecterId, "Effecter Id")->required();
+    uint8_t state{};
+    app.add_option("-s,--state", state, "New state value")->required();
+    CLI11_PARSE(app, argc, argv);
+
+    // Encode PLDM Request message
+    uint8_t effecterCount = 1;
+    std::array<uint8_t, sizeof(pldm_msg_hdr) + sizeof(effecterId) +
+                            sizeof(effecterCount) +
+                            sizeof(set_effecter_state_field)>
+        requestMsg{};
+    auto request = reinterpret_cast<pldm_msg*>(requestMsg.data());
+    set_effecter_state_field stateField{PLDM_REQUEST_SET, state};
+    auto rc = encode_set_state_effecter_states_req(0, effecterId, effecterCount,
+                                                   &stateField, request);
+    if (rc != PLDM_SUCCESS)
+    {
+        std::cerr << "Message encode failure. PLDM error code = " << std::hex
+                  << std::showbase << rc << std::endl;
+        return -1;
+    }
+
+    // Open connection to MCTP socket
+    int fd = pldm_open();
+    if (-1 == fd)
+    {
+        std::cerr << "Failed to init mctp" << std::endl;
+        return -1;
+    }
+
+    uint8_t* responseMsg = nullptr;
+    size_t responseMsgSize{};
+    // Send PLDM request msg and wait for response
+    rc = pldm_send_recv(mctpEid, fd, requestMsg.data(), requestMsg.size(),
+                        &responseMsg, &responseMsgSize);
+    if (0 > rc)
+    {
+        std::cerr << "Failed to send message/receive response. RC = " << rc
+                  << ", errno = " << errno << std::endl;
+        return -1;
+    }
+    pldm_msg* response = reinterpret_cast<pldm_msg*>(responseMsg);
+    std::cout << "Done. PLDM RC = " << std::hex << std::showbase
+              << static_cast<uint16_t>(response->payload[0]) << std::endl;
+    free(responseMsg);
+
+    return 0;
+}
diff --git a/utilities/requester/set_state_effecter_async.cpp b/utilities/requester/set_state_effecter_async.cpp
new file mode 100644
index 0000000..47d8dd1
--- /dev/null
+++ b/utilities/requester/set_state_effecter_async.cpp
@@ -0,0 +1,89 @@
+#include <CLI/CLI.hpp>
+#include <array>
+#include <iostream>
+#include <sdeventplus/event.hpp>
+#include <sdeventplus/source/io.hpp>
+
+#include "libpldm/base.h"
+#include "libpldm/platform.h"
+#include "libpldm/requester/pldm.h"
+
+using namespace sdeventplus;
+using namespace sdeventplus::source;
+
+int main(int argc, char** argv)
+{
+    CLI::App app{"Send PLDM command SetStateEffecterStates"};
+    uint8_t mctpEid{};
+    app.add_option("-m,--mctp_eid", mctpEid, "MCTP EID")->required();
+    uint16_t effecterId{};
+    app.add_option("-e,--effecter", effecterId, "Effecter Id")->required();
+    uint8_t state{};
+    app.add_option("-s,--state", state, "New state value")->required();
+    CLI11_PARSE(app, argc, argv);
+
+    // Encode PLDM Request message
+    uint8_t effecterCount = 1;
+    std::array<uint8_t, sizeof(pldm_msg_hdr) + sizeof(effecterId) +
+                            sizeof(effecterCount) +
+                            sizeof(set_effecter_state_field)>
+        requestMsg{};
+    auto request = reinterpret_cast<pldm_msg*>(requestMsg.data());
+    set_effecter_state_field stateField{PLDM_REQUEST_SET, state};
+    auto rc = encode_set_state_effecter_states_req(0, effecterId, effecterCount,
+                                                   &stateField, request);
+    if (rc != PLDM_SUCCESS)
+    {
+        std::cerr << "Message encode failure. PLDM error code = " << std::hex
+                  << std::showbase << rc << std::endl;
+        return -1;
+    }
+
+    // Get fd of MCTP socket
+    int fd = pldm_open();
+    if (-1 == fd)
+    {
+        std::cerr << "Failed to init mctp" << std::endl;
+        return -1;
+    }
+
+    // Create event loop and add a callback to handle EPOLLIN on fd
+    auto event = Event::get_default();
+    auto callback = [=](IO& io, int fd, uint32_t revents) {
+        if (!(revents & EPOLLIN))
+        {
+            return;
+        }
+
+        uint8_t* responseMsg = nullptr;
+        size_t responseMsgSize{};
+        auto rc = pldm_recv(mctpEid, fd, request->hdr.instance_id, &responseMsg,
+                            &responseMsgSize);
+        if (!rc)
+        {
+            // We've got the response meant for the PLDM request msg that was
+            // sent out
+            io.set_enabled(Enabled::Off);
+            pldm_msg* response = reinterpret_cast<pldm_msg*>(responseMsg);
+            std::cout << "Done. PLDM RC = " << std::hex << std::showbase
+                      << static_cast<uint16_t>(response->payload[0])
+                      << std::endl;
+            free(responseMsg);
+            exit(EXIT_SUCCESS);
+        }
+    };
+    IO io(event, fd, EPOLLIN, std::move(callback));
+
+    // Send PLDM Request message - pldm_send doesn't wait for response
+    rc = pldm_send(mctpEid, fd, requestMsg.data(), requestMsg.size());
+    if (0 > rc)
+    {
+        std::cerr << "Failed to send message/receive response. RC = " << rc
+                  << ", errno = " << errno << std::endl;
+        return -1;
+    }
+
+    event.loop();
+
+    return 0;
+}