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/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;
+}