transport: af-mctp: Add pldm_transport_af_mctp_bind()

The af-mctp transport needs specific setup before it can receive
requests[1]. Futher, we must track the MCTP metadata on each request
message and apply it to the response for correlation at the requester.
This behaviour is addressed in the Message Tag and Tag Owner fields
outlined by Table 1 of DSP0236 v1.3.1.

[1]: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/networking/mctp.rst?h=v6.5#n73

Change-Id: I3fbd84a4174b56d618a42ca58c9881ea5a80f060
Signed-off-by: Andrew Jeffery <andrew@codeconstruct.com.au>
Signed-off-by: Konstantin Aladyshev <aladyshev22@gmail.com>
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 408645a..7249d94 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -23,6 +23,7 @@
 
 1. state-set: Add new enum for Operational Fault Status enum
 2. base: Provide pldm_msg_hdr_correlate_response()
+3. transport: af-mctp: Add pldm_transport_af_mctp_bind()
 
 ### Changed
 
diff --git a/include/libpldm/transport/af-mctp.h b/include/libpldm/transport/af-mctp.h
index f0fa2b7..e47b1aa 100644
--- a/include/libpldm/transport/af-mctp.h
+++ b/include/libpldm/transport/af-mctp.h
@@ -10,6 +10,7 @@
 #endif
 
 struct pldm_transport_af_mctp;
+struct sockaddr_mctp;
 
 /* Init the transport backend */
 int pldm_transport_af_mctp_init(struct pldm_transport_af_mctp **ctx);
@@ -36,6 +37,23 @@
 int pldm_transport_af_mctp_unmap_tid(struct pldm_transport_af_mctp *ctx,
 				     pldm_tid_t tid, mctp_eid_t eid);
 
+/**
+ * @brief Allow the transport to receive requests from remote endpoints
+ *
+ * @param[in] transport - The transport instance on which to listen for requests
+ * @param[in] smctp - The configuration provided to bind(2). NULL may be passed,
+ *		      in which case the transport is bound to all available
+ *		      interfaces on the system's default network. If NULL is
+ *		      passed then len must be zero.
+ * @param[in] len - The size of the object pointed to by the smctp argument. If
+ *		    smctp is NULL then len must be zero.
+ *
+ * @return PLDM_REQUESTER_SUCCESS on success, or a negative error code on
+ * failure.
+ */
+int pldm_transport_af_mctp_bind(struct pldm_transport_af_mctp *transport,
+				const struct sockaddr_mctp *smctp, size_t len);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/src/meson.build b/src/meson.build
index 3506113..5b9dd2f 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -6,6 +6,7 @@
   'firmware_update.c',
   'fru.c',
   'pdr.c',
+  'responder.c',
   'utils.c'
   )
 
diff --git a/src/responder.c b/src/responder.c
new file mode 100644
index 0000000..5112f7b
--- /dev/null
+++ b/src/responder.c
@@ -0,0 +1,69 @@
+#include "responder.h"
+
+#include <libpldm/base.h>
+#include <libpldm/requester/pldm.h>
+
+#include <stdbool.h>
+
+static bool pldm_responder_cookie_eq(const struct pldm_responder_cookie *left,
+				     const struct pldm_responder_cookie *right)
+{
+	return left->tid == right->tid &&
+	       left->instance_id == right->instance_id &&
+	       left->type == right->type && left->command == right->command;
+}
+
+int pldm_responder_cookie_track(struct pldm_responder_cookie *jar,
+				struct pldm_responder_cookie *cookie)
+{
+	struct pldm_responder_cookie *current;
+	struct pldm_responder_cookie *next;
+
+	if (!jar || !cookie) {
+		return PLDM_REQUESTER_INVALID_SETUP;
+	}
+
+	current = jar;
+	next = current->next;
+	while (next) {
+		/* Cookie must not already be known */
+		if (pldm_responder_cookie_eq(next, cookie)) {
+			return PLDM_REQUESTER_INVALID_SETUP;
+		}
+		current = next;
+		next = next->next;
+	}
+
+	cookie->next = NULL;
+	current->next = cookie;
+
+	return PLDM_REQUESTER_SUCCESS;
+}
+
+struct pldm_responder_cookie *
+pldm_responder_cookie_untrack(struct pldm_responder_cookie *jar, pldm_tid_t tid,
+			      pldm_instance_id_t instance_id, uint8_t type,
+			      uint8_t command)
+{
+	const struct pldm_responder_cookie cookie = { tid, instance_id, type,
+						      command, NULL };
+	struct pldm_responder_cookie *current;
+	struct pldm_responder_cookie *next;
+
+	if (!jar) {
+		return NULL;
+	}
+
+	current = jar;
+	next = current->next;
+	while (next && !pldm_responder_cookie_eq(next, &cookie)) {
+		current = next;
+		next = next->next;
+	}
+
+	if (next) {
+		current->next = next->next;
+	}
+
+	return next;
+}
diff --git a/src/responder.h b/src/responder.h
new file mode 100644
index 0000000..37df828
--- /dev/null
+++ b/src/responder.h
@@ -0,0 +1,25 @@
+#ifndef LIBPLDM_SRC_RESPONDER_H
+#define LIBPLDM_SRC_RESPONDER_H
+
+#include <libpldm/base.h>
+#include <libpldm/instance-id.h>
+
+#include <stdint.h>
+
+struct pldm_responder_cookie {
+	pldm_tid_t tid;
+	pldm_instance_id_t instance_id;
+	uint8_t type;
+	uint8_t command;
+	struct pldm_responder_cookie *next;
+};
+
+int pldm_responder_cookie_track(struct pldm_responder_cookie *jar,
+				struct pldm_responder_cookie *cookie);
+
+struct pldm_responder_cookie *
+pldm_responder_cookie_untrack(struct pldm_responder_cookie *jar, pldm_tid_t tid,
+			      pldm_instance_id_t instance_id, uint8_t type,
+			      uint8_t command);
+
+#endif
diff --git a/src/transport/af-mctp.c b/src/transport/af-mctp.c
index ace2969..252c56d 100644
--- a/src/transport/af-mctp.c
+++ b/src/transport/af-mctp.c
@@ -4,6 +4,7 @@
 #include "libpldm/pldm.h"
 #include "libpldm/transport.h"
 #include "libpldm/transport/af-mctp.h"
+#include "responder.h"
 #include "socket.h"
 #include "transport.h"
 
@@ -11,6 +12,7 @@
 #include <limits.h>
 #include <linux/mctp.h>
 #include <poll.h>
+#include <stdbool.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/socket.h>
@@ -18,12 +20,22 @@
 #include <sys/un.h>
 #include <unistd.h>
 
+struct pldm_responder_cookie_af_mctp {
+	struct pldm_responder_cookie req;
+	struct sockaddr_mctp smctp;
+};
+
+#define cookie_to_af_mctp(c)                                                   \
+	container_of((c), struct pldm_responder_cookie_af_mctp, req)
+
 #define AF_MCTP_NAME "AF_MCTP"
 struct pldm_transport_af_mctp {
 	struct pldm_transport transport;
 	int socket;
 	pldm_tid_t tid_eid_map[MCTP_MAX_NUM_EID];
 	struct pldm_socket_sndbuf socket_send_buf;
+	bool bound;
+	struct pldm_responder_cookie cookie_jar;
 };
 
 #define transport_to_af_mctp(ptr)                                              \
@@ -97,6 +109,7 @@
 	struct pldm_transport_af_mctp *af_mctp = transport_to_af_mctp(t);
 	struct sockaddr_mctp addr = { 0 };
 	socklen_t addrlen = sizeof(addr);
+	struct pldm_msg_hdr *hdr;
 	pldm_requester_rc_t res;
 	mctp_eid_t eid = 0;
 	ssize_t length;
@@ -127,6 +140,31 @@
 		goto cleanup_msg;
 	}
 
+	hdr = msg;
+
+	if (af_mctp->bound && hdr->request) {
+		struct pldm_responder_cookie_af_mctp *cookie;
+
+		cookie = malloc(sizeof(*cookie));
+		if (!cookie) {
+			res = PLDM_REQUESTER_RECV_FAIL;
+			goto cleanup_msg;
+		}
+
+		cookie->req.tid = *tid,
+		cookie->req.instance_id = hdr->instance_id,
+		cookie->req.type = hdr->type,
+		cookie->req.command = hdr->command;
+		cookie->smctp = addr;
+
+		rc = pldm_responder_cookie_track(&af_mctp->cookie_jar,
+						 &cookie->req);
+		if (rc) {
+			res = PLDM_REQUESTER_RECV_FAIL;
+			goto cleanup_msg;
+		}
+	}
+
 	*pldm_msg = msg;
 	*msg_len = length;
 
@@ -144,16 +182,41 @@
 						       size_t msg_len)
 {
 	struct pldm_transport_af_mctp *af_mctp = transport_to_af_mctp(t);
-	mctp_eid_t eid = 0;
-	if (pldm_transport_af_mctp_get_eid(af_mctp, tid, &eid)) {
+	const struct pldm_msg_hdr *hdr;
+	struct sockaddr_mctp addr = { 0 };
+
+	if (msg_len < (ssize_t)sizeof(struct pldm_msg_hdr)) {
 		return PLDM_REQUESTER_SEND_FAIL;
 	}
 
-	struct sockaddr_mctp addr = { 0 };
-	addr.smctp_family = AF_MCTP;
-	addr.smctp_addr.s_addr = eid;
-	addr.smctp_type = MCTP_MSG_TYPE_PLDM;
-	addr.smctp_tag = MCTP_TAG_OWNER;
+	hdr = pldm_msg;
+	if (af_mctp->bound && !hdr->request) {
+		struct pldm_responder_cookie_af_mctp *cookie;
+		struct pldm_responder_cookie *req;
+
+		req = pldm_responder_cookie_untrack(&af_mctp->cookie_jar, tid,
+						    hdr->instance_id, hdr->type,
+						    hdr->command);
+		if (!req) {
+			return PLDM_REQUESTER_SEND_FAIL;
+		}
+
+		cookie = cookie_to_af_mctp(req);
+		addr = cookie->smctp;
+		/* Clear the TO to indicate a response */
+		addr.smctp_tag &= ~MCTP_TAG_OWNER;
+		free(cookie);
+	} else {
+		mctp_eid_t eid = 0;
+		if (pldm_transport_af_mctp_get_eid(af_mctp, tid, &eid)) {
+			return PLDM_REQUESTER_SEND_FAIL;
+		}
+
+		addr.smctp_family = AF_MCTP;
+		addr.smctp_addr.s_addr = eid;
+		addr.smctp_type = MCTP_MSG_TYPE_PLDM;
+		addr.smctp_tag = MCTP_TAG_OWNER;
+	}
 
 	if (msg_len > INT_MAX ||
 	    pldm_socket_sndbuf_accomodate(&(af_mctp->socket_send_buf),
@@ -166,6 +229,7 @@
 	if (rc == -1) {
 		return PLDM_REQUESTER_SEND_FAIL;
 	}
+
 	return PLDM_REQUESTER_SUCCESS;
 }
 
@@ -187,6 +251,8 @@
 	af_mctp->transport.recv = pldm_transport_af_mctp_recv;
 	af_mctp->transport.send = pldm_transport_af_mctp_send;
 	af_mctp->transport.init_pollfd = pldm_transport_af_mctp_init_pollfd;
+	af_mctp->bound = false;
+	af_mctp->cookie_jar.next = NULL;
 	af_mctp->socket = socket(AF_MCTP, SOCK_DGRAM, 0);
 	if (af_mctp->socket == -1) {
 		free(af_mctp);
@@ -213,3 +279,49 @@
 	close(ctx->socket);
 	free(ctx);
 }
+
+LIBPLDM_ABI_TESTING
+int pldm_transport_af_mctp_bind(struct pldm_transport_af_mctp *transport,
+				const struct sockaddr_mctp *smctp, size_t len)
+{
+	struct sockaddr_mctp lsmctp = { 0 };
+	int rc;
+
+	if (!transport) {
+		return PLDM_REQUESTER_INVALID_SETUP;
+	}
+
+	if (!smctp && len) {
+		return PLDM_REQUESTER_INVALID_SETUP;
+	}
+
+	if (!smctp) {
+		lsmctp.smctp_family = AF_MCTP;
+		lsmctp.smctp_network = MCTP_NET_ANY;
+		lsmctp.smctp_addr.s_addr = MCTP_ADDR_ANY;
+		lsmctp.smctp_type = MCTP_MSG_TYPE_PLDM;
+		lsmctp.smctp_tag = MCTP_TAG_OWNER;
+		smctp = &lsmctp;
+		len = sizeof(lsmctp);
+	}
+
+	if (smctp->smctp_family != AF_MCTP ||
+	    smctp->smctp_type != MCTP_MSG_TYPE_PLDM ||
+	    smctp->smctp_tag != MCTP_TAG_OWNER) {
+		return PLDM_REQUESTER_INVALID_SETUP;
+	}
+
+	if (len != sizeof(*smctp)) {
+		return PLDM_REQUESTER_INVALID_SETUP;
+	}
+
+	rc = bind(transport->socket, (const struct sockaddr *)smctp,
+		  sizeof(*smctp));
+	if (rc) {
+		return PLDM_REQUESTER_SETUP_FAIL;
+	}
+
+	transport->bound = true;
+
+	return PLDM_REQUESTER_SUCCESS;
+}
diff --git a/tests/meson.build b/tests/meson.build
index ba538f9..3b6b7e2 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -24,6 +24,7 @@
   'libpldm_pdr_test',
   'libpldm_firmware_update_test',
   'msgbuf',
+  'responder',
 ]
 
 if get_option('abi').contains('testing')
diff --git a/tests/responder.cpp b/tests/responder.cpp
new file mode 100644
index 0000000..6a0a3fd
--- /dev/null
+++ b/tests/responder.cpp
@@ -0,0 +1,88 @@
+// NOLINTNEXTLINE(bugprone-suspicious-include)
+#include "responder.c"
+
+#include <gtest/gtest.h>
+
+TEST(Responder, track_untrack_one)
+{
+    struct pldm_responder_cookie jar
+    {
+    };
+    struct pldm_responder_cookie cookie = {
+        .tid = 1,
+        .instance_id = 1,
+        .type = 0,
+        .command = 0x01, /* SetTID */
+        .next = nullptr,
+    };
+
+    ASSERT_EQ(pldm_responder_cookie_track(&jar, &cookie), 0);
+    ASSERT_NE(jar.next, nullptr);
+    ASSERT_EQ(pldm_responder_cookie_untrack(&jar, 1, 1, 0, 0x01), &cookie);
+    ASSERT_EQ(jar.next, nullptr);
+}
+
+TEST(Responder, untrack_none)
+{
+    struct pldm_responder_cookie jar
+    {
+    };
+
+    ASSERT_EQ(jar.next, nullptr);
+    ASSERT_EQ(pldm_responder_cookie_untrack(&jar, 1, 1, 0, 0x01), nullptr);
+    ASSERT_EQ(jar.next, nullptr);
+}
+
+TEST(Responder, track_one_untrack_bad)
+{
+    struct pldm_responder_cookie jar
+    {
+    };
+    struct pldm_responder_cookie cookie = {
+        .tid = 1,
+        .instance_id = 1,
+        .type = 0,
+        .command = 0x01, /* SetTID */
+        .next = nullptr,
+    };
+
+    ASSERT_EQ(pldm_responder_cookie_track(&jar, &cookie), 0);
+    ASSERT_NE(jar.next, nullptr);
+    ASSERT_EQ(pldm_responder_cookie_untrack(&jar, 2, 1, 0, 0x01), nullptr);
+    ASSERT_EQ(pldm_responder_cookie_untrack(&jar, 1, 2, 0, 0x01), nullptr);
+    ASSERT_EQ(pldm_responder_cookie_untrack(&jar, 1, 1, 1, 0x01), nullptr);
+    ASSERT_EQ(pldm_responder_cookie_untrack(&jar, 1, 1, 0, 0x02), nullptr);
+    ASSERT_NE(jar.next, nullptr);
+    ASSERT_EQ(pldm_responder_cookie_untrack(&jar, 1, 1, 0, 0x01), &cookie);
+    ASSERT_EQ(jar.next, nullptr);
+}
+
+TEST(Responder, track_untrack_two)
+{
+    struct pldm_responder_cookie jar
+    {
+    };
+    struct pldm_responder_cookie cookies[] = {
+        {
+            .tid = 1,
+            .instance_id = 1,
+            .type = 0,
+            .command = 0x01, /* SetTID */
+            .next = nullptr,
+        },
+        {
+            .tid = 2,
+            .instance_id = 1,
+            .type = 0,
+            .command = 0x01, /* SetTID */
+            .next = nullptr,
+        },
+    };
+
+    ASSERT_EQ(pldm_responder_cookie_track(&jar, &cookies[0]), 0);
+    ASSERT_EQ(pldm_responder_cookie_track(&jar, &cookies[1]), 0);
+    ASSERT_NE(jar.next, nullptr);
+    ASSERT_EQ(pldm_responder_cookie_untrack(&jar, 2, 1, 0, 0x01), &cookies[1]);
+    ASSERT_EQ(pldm_responder_cookie_untrack(&jar, 1, 1, 0, 0x01), &cookies[0]);
+    ASSERT_EQ(jar.next, nullptr);
+}