core: Add TX/RX API that exposes message tag and tag owner

MCTP received packets can carry a message tag and tag owner bit
which is set by a remote MCTP endpoint. This can be used by the
remote MCTP endpoint to track the responses. Thus, libmctp should
provide a mechanism for the upper layer MCTP applications to
respond with the same message tag.

This patchset extends TX and RX API with message tag and
tag owner bits.

Signed-off-by: Sumanth Bhat <sumanth.bhat@linux.intel.com>
Change-Id: I6d07eafa86c653abdd4313ab7cc77e5a93124477
diff --git a/core.c b/core.c
index 11d4777..c3ee659 100644
--- a/core.c
+++ b/core.c
@@ -88,7 +88,8 @@
 #endif
 
 static int mctp_message_tx_on_bus(struct mctp_bus *bus, mctp_eid_t src,
-				  mctp_eid_t dest, void *msg, size_t msg_len);
+				  mctp_eid_t dest, bool tag_owner,
+				  uint8_t msg_tag, void *msg, size_t msg_len);
 
 struct mctp_pktbuf *mctp_pktbuf_alloc(struct mctp_binding *binding, size_t len)
 {
@@ -436,7 +437,8 @@
 }
 
 static bool mctp_ctrl_handle_msg(struct mctp_bus *bus, mctp_eid_t src,
-				 void *buffer, size_t length)
+				 uint8_t msg_tag, bool tag_owner, void *buffer,
+				 size_t length)
 {
 	struct mctp_ctrl_msg_hdr *msg_hdr = buffer;
 
@@ -450,7 +452,7 @@
 	if (mctp_ctrl_cmd_is_transport(msg_hdr)) {
 		if (bus->binding->control_rx != NULL) {
 			/* MCTP bus binding handler */
-			bus->binding->control_rx(src,
+			bus->binding->control_rx(src, msg_tag, tag_owner,
 						 bus->binding->control_rx_data,
 						 buffer, length);
 			return true;
@@ -482,7 +484,8 @@
  *     'buf' is not NULL.
  */
 static void mctp_rx(struct mctp *mctp, struct mctp_bus *bus, mctp_eid_t src,
-		    mctp_eid_t dest, void *buf, size_t len)
+		    mctp_eid_t dest, bool tag_owner, uint8_t msg_tag, void *buf,
+		    size_t len)
 {
 	assert(buf != NULL);
 
@@ -498,14 +501,16 @@
 			 */
 			if (mctp_ctrl_cmd_is_request(msg_hdr)) {
 				bool handled;
-				handled = mctp_ctrl_handle_msg(bus, src, buf,
-							       len);
+				handled = mctp_ctrl_handle_msg(
+					bus, src, msg_tag, tag_owner, buf, len);
 				if (handled)
 					return;
 			}
 		}
+
 		if (mctp->message_rx)
-			mctp->message_rx(src, mctp->message_rx_data, buf, len);
+			mctp->message_rx(src, tag_owner, msg_tag,
+					 mctp->message_rx_data, buf, len);
 	}
 
 	if (mctp->route_policy == ROUTE_BRIDGE) {
@@ -516,7 +521,8 @@
 			if (dest_bus == bus)
 				continue;
 
-			mctp_message_tx_on_bus(dest_bus, src, dest, buf, len);
+			mctp_message_tx_on_bus(dest_bus, src, dest, tag_owner,
+					       msg_tag, buf, len);
 		}
 
 	}
@@ -529,6 +535,7 @@
 	uint8_t flags, exp_seq, seq, tag;
 	struct mctp_msg_ctx *ctx;
 	struct mctp_hdr *hdr;
+	bool tag_owner;
 	size_t len;
 	void *p;
 	int rc;
@@ -552,6 +559,8 @@
 	flags = hdr->flags_seq_tag & (MCTP_HDR_FLAG_SOM | MCTP_HDR_FLAG_EOM);
 	tag = (hdr->flags_seq_tag >> MCTP_HDR_TAG_SHIFT) & MCTP_HDR_TAG_MASK;
 	seq = (hdr->flags_seq_tag >> MCTP_HDR_SEQ_SHIFT) & MCTP_HDR_SEQ_MASK;
+	tag_owner =
+		(hdr->flags_seq_tag >> MCTP_HDR_TO_SHIFT) & MCTP_HDR_TO_MASK;
 
 	switch (flags) {
 	case MCTP_HDR_FLAG_SOM | MCTP_HDR_FLAG_EOM:
@@ -559,7 +568,7 @@
 		 * no need to create a message context */
 		len = pkt->end - pkt->mctp_hdr_off - sizeof(struct mctp_hdr);
 		p = pkt->data + pkt->mctp_hdr_off + sizeof(struct mctp_hdr);
-		mctp_rx(mctp, bus, hdr->src, hdr->dest, p, len);
+		mctp_rx(mctp, bus, hdr->src, hdr->dest, tag_owner, tag, p, len);
 		break;
 
 	case MCTP_HDR_FLAG_SOM:
@@ -620,8 +629,8 @@
 
 		rc = mctp_msg_ctx_add_pkt(ctx, pkt, mctp->max_message_size);
 		if (!rc)
-			mctp_rx(mctp, bus, ctx->src, ctx->dest,
-					ctx->buf, ctx->buf_size);
+			mctp_rx(mctp, bus, ctx->src, ctx->dest, tag_owner, tag,
+				ctx->buf, ctx->buf_size);
 
 		mctp_msg_ctx_drop(ctx);
 		break;
@@ -735,7 +744,8 @@
 }
 
 static int mctp_message_tx_on_bus(struct mctp_bus *bus, mctp_eid_t src,
-				  mctp_eid_t dest, void *msg, size_t msg_len)
+				  mctp_eid_t dest, bool tag_owner,
+				  uint8_t msg_tag, void *msg, size_t msg_len)
 {
 	size_t max_payload_len, payload_len, p;
 	struct mctp_pktbuf *pkt;
@@ -745,6 +755,9 @@
 	if (bus->state == mctp_bus_state_constructed)
 		return -ENXIO;
 
+	if ((msg_tag & MCTP_HDR_TAG_MASK) != msg_tag)
+		return -EINVAL;
+
 	max_payload_len = MCTP_BODY_SIZE(bus->binding->pkt_size);
 
 	{
@@ -767,12 +780,11 @@
 				payload_len + sizeof(*hdr));
 		hdr = mctp_pktbuf_hdr(pkt);
 
-		/* todo: tags */
 		hdr->ver = bus->binding->version & 0xf;
 		hdr->dest = dest;
 		hdr->src = src;
-		hdr->flags_seq_tag = MCTP_HDR_FLAG_TO |
-			(0 << MCTP_HDR_TAG_SHIFT);
+		hdr->flags_seq_tag = (tag_owner << MCTP_HDR_TO_SHIFT) |
+				     (msg_tag << MCTP_HDR_TAG_SHIFT);
 
 		if (i == 0)
 			hdr->flags_seq_tag |= MCTP_HDR_FLAG_SOM;
@@ -800,14 +812,22 @@
 	return 0;
 }
 
-int mctp_message_tx(struct mctp *mctp, mctp_eid_t eid,
-		void *msg, size_t msg_len)
+int mctp_message_tx(struct mctp *mctp, mctp_eid_t eid, bool tag_owner,
+		    uint8_t msg_tag, void *msg, size_t msg_len)
 {
 	struct mctp_bus *bus;
 
+	/* TODO: Protect against same tag being used across
+	 * different callers */
+	if ((msg_tag & MCTP_HDR_TAG_MASK) != msg_tag) {
+		mctp_prerr("Incorrect message tag %u passed.", msg_tag);
+		return -EINVAL;
+	}
+
 	bus = find_bus_for_eid(mctp, eid);
 	if (!bus)
 		return 0;
 
-	return mctp_message_tx_on_bus(bus, bus->eid, eid, msg, msg_len);
+	return mctp_message_tx_on_bus(bus, bus->eid, eid, tag_owner, msg_tag,
+				      msg, msg_len);
 }
diff --git a/libmctp.h b/libmctp.h
index bc0cb5b..497d536 100644
--- a/libmctp.h
+++ b/libmctp.h
@@ -27,13 +27,18 @@
 };
 
 /* Definitions for flags_seq_tag field */
-#define MCTP_HDR_FLAG_SOM	(1<<7)
-#define MCTP_HDR_FLAG_EOM	(1<<6)
-#define MCTP_HDR_FLAG_TO	(1<<3)
-#define MCTP_HDR_SEQ_SHIFT	(4)
-#define MCTP_HDR_SEQ_MASK	(0x3)
-#define MCTP_HDR_TAG_SHIFT	(0)
-#define MCTP_HDR_TAG_MASK	(0x7)
+#define MCTP_HDR_FLAG_SOM  (1 << 7)
+#define MCTP_HDR_FLAG_EOM  (1 << 6)
+#define MCTP_HDR_FLAG_TO   (1 << 3)
+#define MCTP_HDR_TO_SHIFT  (3)
+#define MCTP_HDR_TO_MASK   (1)
+#define MCTP_HDR_SEQ_SHIFT (4)
+#define MCTP_HDR_SEQ_MASK  (0x3)
+#define MCTP_HDR_TAG_SHIFT (0)
+#define MCTP_HDR_TAG_MASK  (0x7)
+
+#define MCTP_MESSAGE_TO_SRC true
+#define MCTP_MESSAGE_TO_DST false
 
 /* Baseline Transmission Unit and packet size */
 #define MCTP_BTU		64
@@ -93,13 +98,13 @@
 int mctp_bridge_busses(struct mctp *mctp,
 		struct mctp_binding *b1, struct mctp_binding *b2);
 
-typedef void (*mctp_rx_fn)(uint8_t src_eid, void *data,
-		void *msg, size_t len);
+typedef void (*mctp_rx_fn)(uint8_t src_eid, bool tag_owner, uint8_t msg_tag,
+			   void *data, void *msg, size_t len);
 
 int mctp_set_rx_all(struct mctp *mctp, mctp_rx_fn fn, void *data);
 
-int mctp_message_tx(struct mctp *mctp, mctp_eid_t eid,
-		void *msg, size_t msg_len);
+int mctp_message_tx(struct mctp *mctp, mctp_eid_t eid, bool tag_owner,
+		    uint8_t msg_tag, void *msg, size_t msg_len);
 
 /* hardware bindings */
 struct mctp_binding {
diff --git a/tests/test_astlpc.c b/tests/test_astlpc.c
index 054eda1..f781fa5 100644
--- a/tests/test_astlpc.c
+++ b/tests/test_astlpc.c
@@ -150,7 +150,8 @@
 	.lpc_write = mctp_astlpc_mmio_lpc_write,
 };
 
-static void rx_message(uint8_t eid __unused, void *data __unused, void *msg,
+static void rx_message(uint8_t eid __unused, bool tag_owner __unused,
+		       uint8_t msg_tag __unused, void *data __unused, void *msg,
 		       size_t len)
 {
 	struct astlpc_test *test = data;
@@ -257,7 +258,8 @@
 	mctp_set_rx_all(ctx.host.mctp, rx_message, &ctx);
 
 	/* BMC sends a message */
-	rc = mctp_message_tx(ctx.bmc.mctp, 9, msg, sizeof(msg));
+	rc = mctp_message_tx(ctx.bmc.mctp, 9, MCTP_MESSAGE_TO_SRC, 0, msg,
+			     sizeof(msg));
 	assert(rc == 0);
 
 	/* Host receives the first packet */
@@ -284,6 +286,7 @@
 {
 	struct astlpc_test ctx = { 0 };
 	uint8_t msg[MCTP_BTU];
+	uint8_t tag = 0;
 	int rc;
 
 	/* Test harness initialisation */
@@ -297,7 +300,8 @@
 	mctp_set_rx_all(ctx.bmc.mctp, rx_message, &ctx);
 
 	/* Host sends the single-packet message */
-	rc = mctp_message_tx(ctx.host.mctp, 8, msg, sizeof(msg));
+	rc = mctp_message_tx(ctx.host.mctp, 8, MCTP_MESSAGE_TO_DST, tag, msg,
+			     sizeof(msg));
 	assert(rc == 0);
 	assert(ctx.kcs[MCTP_ASTLPC_KCS_REG_STATUS] & KCS_STATUS_IBF);
 	assert(ctx.kcs[MCTP_ASTLPC_KCS_REG_DATA] == 0x01);
@@ -324,6 +328,7 @@
 {
 	struct astlpc_test ctx = { 0 };
 	uint8_t msg[MCTP_BTU];
+	uint8_t tag = 0;
 	int rc;
 
 	/* Test harness initialisation */
@@ -337,7 +342,8 @@
 	mctp_set_rx_all(ctx.host.mctp, rx_message, &ctx);
 
 	/* BMC sends the single-packet message */
-	rc = mctp_message_tx(ctx.bmc.mctp, 9, msg, sizeof(msg));
+	rc = mctp_message_tx(ctx.bmc.mctp, 9, MCTP_MESSAGE_TO_SRC, tag, msg,
+			     sizeof(msg));
 	assert(rc == 0);
 	assert(ctx.kcs[MCTP_ASTLPC_KCS_REG_STATUS] & KCS_STATUS_OBF);
 	assert(ctx.kcs[MCTP_ASTLPC_KCS_REG_DATA] == 0x01);
@@ -549,6 +555,7 @@
 	struct astlpc_test ctx = { 0 };
 	uint8_t kcs[2] = { 0 };
 	uint8_t msg[MCTP_BTU];
+	uint8_t tag = 0;
 	int rc;
 
 	ctx.lpc_mem = calloc(1, LPC_WIN_SIZE);
@@ -594,7 +601,8 @@
 	mctp_set_rx_all(ctx.host.mctp, rx_message, &ctx);
 
 	/* BMC sends the single-packet message */
-	rc = mctp_message_tx(ctx.bmc.mctp, 9, msg, sizeof(msg));
+	rc = mctp_message_tx(ctx.bmc.mctp, 9, MCTP_MESSAGE_TO_SRC, tag, msg,
+			     sizeof(msg));
 	assert(rc == 0);
 
 	/* Host receives the single-packet message */
@@ -615,6 +623,7 @@
 	struct astlpc_test ctx = { 0 };
 	uint8_t unwritten[MCTP_BTU];
 	uint8_t msg[MCTP_BTU];
+	uint8_t tag = 0;
 	int rc;
 
 	/* Test harness initialisation */
@@ -634,7 +643,8 @@
 	mctp_astlpc_poll(ctx.host.astlpc);
 
 	/* Host attempts to send the single-packet message, but is prevented */
-	rc = mctp_message_tx(ctx.host.mctp, 8, msg, sizeof(msg));
+	rc = mctp_message_tx(ctx.host.mctp, 8, MCTP_MESSAGE_TO_DST, tag, msg,
+			     sizeof(msg));
 	assert(rc == 0);
 	assert(!(ctx.kcs[MCTP_ASTLPC_KCS_REG_STATUS] & KCS_STATUS_OBF));
 	astlpc_assert_tx_packet(&ctx.host, &unwritten[0], MCTP_BTU);
@@ -1083,6 +1093,7 @@
 	struct astlpc_endpoint *bmc, *host;
 	struct astlpc_test ctx;
 	uint8_t kcs[2] = { 0 };
+	uint8_t tag = 0;
 	void *lpc_mem;
 	int rc;
 
@@ -1117,7 +1128,8 @@
 
 	memset(ctx.msg, 0x5a, 2 * MCTP_BODY_SIZE(8192));
 
-	rc = mctp_message_tx(host->mctp, 8, ctx.msg, 2 * MCTP_BODY_SIZE(8192));
+	rc = mctp_message_tx(host->mctp, 8, MCTP_MESSAGE_TO_DST, tag, ctx.msg,
+			     2 * MCTP_BODY_SIZE(8192));
 	assert(rc == 0);
 	rc = mctp_astlpc_poll(bmc->astlpc);
 	assert(rc == 0);
@@ -1142,6 +1154,7 @@
 	struct astlpc_test ctx;
 	uint8_t kcs[2] = { 0 };
 	uint8_t msg[MCTP_BTU];
+	uint8_t tag = 0;
 	void *lpc_mem;
 	int rc;
 
@@ -1165,7 +1178,8 @@
 	 * terminating after a period long enough to packetise the message.
 	 */
 	alarm(1);
-	mctp_message_tx(bmc->mctp, 9, msg, sizeof(msg));
+	mctp_message_tx(bmc->mctp, 9, MCTP_MESSAGE_TO_SRC, tag, msg,
+			sizeof(msg));
 	alarm(0);
 
 	endpoint_destroy(bmc);
@@ -1178,6 +1192,7 @@
 	struct mctp_lpcmap_hdr *hdr;
 	uint8_t msg[MCTP_BTU];
 	uint32_t offset;
+	uint8_t tag = 0;
 	uint32_t code;
 	uint8_t *tlr;
 	int rc;
@@ -1193,7 +1208,8 @@
 	mctp_set_rx_all(ctx.bmc.mctp, rx_message, &ctx);
 
 	/* Host sends the single-packet message */
-	rc = mctp_message_tx(ctx.host.mctp, 8, msg, sizeof(msg));
+	rc = mctp_message_tx(ctx.host.mctp, 8, MCTP_MESSAGE_TO_DST, tag, msg,
+			     sizeof(msg));
 	assert(rc == 0);
 	assert(ctx.kcs[MCTP_ASTLPC_KCS_REG_STATUS] & KCS_STATUS_IBF);
 	assert(ctx.kcs[MCTP_ASTLPC_KCS_REG_DATA] == 0x01);
@@ -1230,6 +1246,7 @@
 	struct mctp_lpcmap_hdr *hdr;
 	uint8_t msg[MCTP_BTU];
 	uint32_t offset;
+	uint8_t tag = 0;
 	uint32_t code;
 	uint8_t *tlr;
 	int rc;
@@ -1245,7 +1262,8 @@
 	mctp_set_rx_all(ctx.host.mctp, rx_message, &ctx);
 
 	/* BMC sends the single-packet message */
-	rc = mctp_message_tx(ctx.bmc.mctp, 9, msg, sizeof(msg));
+	rc = mctp_message_tx(ctx.bmc.mctp, 9, MCTP_MESSAGE_TO_SRC, tag, msg,
+			     sizeof(msg));
 	assert(rc == 0);
 	assert(ctx.kcs[MCTP_ASTLPC_KCS_REG_STATUS] & KCS_STATUS_OBF);
 	assert(ctx.kcs[MCTP_ASTLPC_KCS_REG_DATA] == 0x01);
diff --git a/tests/test_cmds.c b/tests/test_cmds.c
index 1b7536a..ca5e838 100644
--- a/tests/test_cmds.c
+++ b/tests/test_cmds.c
@@ -32,6 +32,8 @@
 };
 
 static void control_message_transport_callback(mctp_eid_t src __unused,
+					       bool tag_owner __unused,
+					       uint8_t msg_tag __unused,
 					       void *data, void *buf,
 					       size_t len __unused)
 {
diff --git a/tests/test_core.c b/tests/test_core.c
index dacf197..744fe96 100644
--- a/tests/test_core.c
+++ b/tests/test_core.c
@@ -42,17 +42,21 @@
 struct test_params {
 	bool seen;
 	size_t message_size;
+	uint8_t msg_tag;
+	bool tag_owner;
 };
 
-static void rx_message(uint8_t eid __unused, void *data, void *msg __unused,
-		       size_t len)
+static void rx_message(uint8_t eid __unused, bool tag_owner, uint8_t msg_tag,
+		       void *data, void *msg __unused, size_t len)
 {
 	struct test_params *param = (struct test_params *)data;
 
-	mctp_prdebug("MCTP message received: len %zd", len);
+	mctp_prdebug("MCTP message received: len %zd, tag %u", len, msg_tag);
 
 	param->seen = true;
 	param->message_size = len;
+	param->msg_tag = msg_tag;
+	param->tag_owner = tag_owner;
 }
 
 static uint8_t get_sequence()
@@ -441,6 +445,92 @@
 	mctp_destroy(mctp);
 }
 
+static void mctp_core_test_rx_with_tag()
+{
+	struct mctp *mctp = NULL;
+	struct mctp_binding_test *binding = NULL;
+	struct test_params test_param;
+	static uint8_t test_payload[MCTP_BTU];
+	uint8_t tag = get_tag();
+	struct pktbuf pktbuf;
+	uint8_t flags_seq_tag;
+
+	memset(test_payload, 0, sizeof(test_payload));
+	test_param.seen = false;
+	test_param.message_size = 0;
+	test_param.msg_tag = 0;
+	test_param.tag_owner = false;
+
+	mctp_test_stack_init(&mctp, &binding, TEST_DEST_EID);
+	mctp_set_rx_all(mctp, rx_message, &test_param);
+	memset(&pktbuf, 0, sizeof(pktbuf));
+	pktbuf.hdr.dest = TEST_DEST_EID;
+	pktbuf.hdr.src = TEST_SRC_EID;
+
+	/* Set tag and tag owner fields for a recieve packet */
+	flags_seq_tag = MCTP_HDR_FLAG_SOM | MCTP_HDR_FLAG_EOM |
+			(1 << MCTP_HDR_TO_SHIFT) | tag;
+	receive_one_fragment(binding, test_payload, MCTP_BTU, flags_seq_tag,
+			     &pktbuf);
+
+	assert(test_param.seen);
+	assert(test_param.message_size == (MCTP_BTU));
+	assert(test_param.msg_tag == tag);
+	assert(test_param.tag_owner);
+
+	mctp_binding_test_destroy(binding);
+	mctp_destroy(mctp);
+}
+
+static void mctp_core_test_rx_with_tag_multifragment()
+{
+	struct mctp *mctp = NULL;
+	struct mctp_binding_test *binding = NULL;
+	struct test_params test_param;
+	static uint8_t test_payload[MCTP_BTU];
+	uint8_t tag = get_tag();
+	struct pktbuf pktbuf;
+	uint8_t flags_seq_tag;
+
+	memset(test_payload, 0, sizeof(test_payload));
+	test_param.seen = false;
+	test_param.message_size = 0;
+	test_param.msg_tag = 0;
+	test_param.tag_owner = false;
+
+	mctp_test_stack_init(&mctp, &binding, TEST_DEST_EID);
+	mctp_set_rx_all(mctp, rx_message, &test_param);
+	memset(&pktbuf, 0, sizeof(pktbuf));
+	pktbuf.hdr.dest = TEST_DEST_EID;
+	pktbuf.hdr.src = TEST_SRC_EID;
+
+	/* Set tag and tag owner fields for a 3 fragment packet */
+	flags_seq_tag = MCTP_HDR_FLAG_SOM |
+			(get_sequence() << MCTP_HDR_SEQ_SHIFT) |
+			(1 << MCTP_HDR_TO_SHIFT) | tag;
+	receive_one_fragment(binding, test_payload, MCTP_BTU, flags_seq_tag,
+			     &pktbuf);
+
+	flags_seq_tag = (get_sequence() << MCTP_HDR_SEQ_SHIFT) |
+			(1 << MCTP_HDR_TO_SHIFT) | tag;
+	receive_one_fragment(binding, test_payload, MCTP_BTU, flags_seq_tag,
+			     &pktbuf);
+
+	flags_seq_tag = MCTP_HDR_FLAG_EOM |
+			(get_sequence() << MCTP_HDR_SEQ_SHIFT) |
+			(1 << MCTP_HDR_TO_SHIFT) | tag;
+	receive_one_fragment(binding, test_payload, MCTP_BTU, flags_seq_tag,
+			     &pktbuf);
+
+	assert(test_param.seen);
+	assert(test_param.message_size == (3 * MCTP_BTU));
+	assert(test_param.msg_tag == tag);
+	assert(test_param.tag_owner);
+
+	mctp_binding_test_destroy(binding);
+	mctp_destroy(mctp);
+}
+
 /* clang-format off */
 #define TEST_CASE(test) { #test, test }
 static const struct {
@@ -455,6 +545,8 @@
 	TEST_CASE(mctp_core_test_receive_bigger_end_fragment),
 	TEST_CASE(mctp_core_test_drop_large_fragments),
 	TEST_CASE(mctp_core_test_exhaust_context_buffers),
+	TEST_CASE(mctp_core_test_rx_with_tag),
+	TEST_CASE(mctp_core_test_rx_with_tag_multifragment),
 };
 /* clang-format on */
 
diff --git a/tests/test_eid.c b/tests/test_eid.c
index 031d044..5c089ad 100644
--- a/tests/test_eid.c
+++ b/tests/test_eid.c
@@ -16,7 +16,9 @@
 	mctp_eid_t			src_eid;
 };
 
-static void test_rx(uint8_t eid, void *data, void *msg, size_t len)
+static void
+test_rx(uint8_t eid, bool tag_owner __unused, uint8_t msg_tag __unused,
+	void *data, void *msg __unused, size_t len __unused)
 {
 	struct test_ctx *ctx = data;
 
diff --git a/tests/test_seq.c b/tests/test_seq.c
index 8838acb..e5a8954 100644
--- a/tests/test_seq.c
+++ b/tests/test_seq.c
@@ -19,12 +19,12 @@
 	size_t				rx_len;
 };
 
-static void test_rx(uint8_t eid, void *data, void *msg, size_t len)
+static void
+test_rx(uint8_t eid __unused, bool tag_owner __unused,
+	uint8_t msg_tag __unused, void *data, void *msg, size_t len)
 {
 	struct test_ctx *ctx = data;
 
-	(void)eid;
-
 	ctx->rx_count++;
 
 	/* append incoming message data to the existing rx_data */
diff --git a/tests/test_serial.c b/tests/test_serial.c
index 3c6d147..aa282c9 100644
--- a/tests/test_serial.c
+++ b/tests/test_serial.c
@@ -43,20 +43,25 @@
 uint8_t mctp_msg_src[2 * MCTP_BTU];
 
 static bool seen;
+static bool received_tag_owner;
+static uint8_t received_msg_tag;
 
-static void rx_message(uint8_t eid __unused, void *data __unused, void *msg,
-		       size_t len)
+static void rx_message(uint8_t eid __unused, bool tag_owner, uint8_t msg_tag,
+		       void *data __unused, void *msg, size_t len)
 {
 	uint8_t type;
 
 	type = *(uint8_t *)msg;
 
-	mctp_prdebug("MCTP message received: len %zd, type %d", len, type);
+	mctp_prdebug("MCTP message received: len %zd, type %d, tag %d", len,
+		     type, msg_tag);
 
 	assert(sizeof(mctp_msg_src) == len);
 	assert(!memcmp(mctp_msg_src, msg, len));
 
 	seen = true;
+	received_msg_tag = msg_tag;
+	received_tag_owner = tag_owner;
 }
 
 struct serial_test {
@@ -70,6 +75,8 @@
 
 	struct mctp_binding_serial_pipe *a;
 	struct mctp_binding_serial_pipe *b;
+	uint8_t msg_tag = 2;
+	bool tag_owner = false;
 	int p[2][2];
 	int rc;
 
@@ -109,14 +116,19 @@
 	mctp_serial_set_tx_fn(b->serial, mctp_binding_serial_pipe_tx, a);
 	mctp_register_bus(scenario[1].mctp, mctp_binding_serial_core(b->serial), 9);
 
-	/* Transmit a message from A to B */
-	rc = mctp_message_tx(scenario[0].mctp, 9, mctp_msg_src, sizeof(mctp_msg_src));
+	/* Transmit a message from A to B, with message tag */
+	rc = mctp_message_tx(scenario[0].mctp, 9, tag_owner, msg_tag,
+			     mctp_msg_src, sizeof(mctp_msg_src));
 	assert(rc == 0);
 
 	/* Read the message at B from A */
 	seen = false;
+	received_tag_owner = true;
+	received_msg_tag = 0;
 	mctp_serial_read(b->serial);
 	assert(seen);
+	assert(received_tag_owner == tag_owner);
+	assert(received_msg_tag == msg_tag);
 
 	mctp_serial_destroy(scenario[1].binding.serial);
 	mctp_destroy(scenario[1].mctp);
diff --git a/utils/mctp-astlpc-daemon.c b/utils/mctp-astlpc-daemon.c
index f782d4c..3852bd4 100644
--- a/utils/mctp-astlpc-daemon.c
+++ b/utils/mctp-astlpc-daemon.c
@@ -28,11 +28,12 @@
 	type = len > 0 ? *(uint8_t *)(msg) : 0x00;
 
 	fprintf(stderr, "TX: dest EID 0x%02x: %zd bytes, first byte [0x%02x]\n",
-			eid, len, type);
-	mctp_message_tx(ctx->mctp, eid, msg, len);
+		eid, len, type);
+	mctp_message_tx(ctx->mctp, eid, 0, MCTP_MESSAGE_TO_SRC, msg, len);
 }
 
-static void rx_message(uint8_t eid, void *data, void *msg, size_t len)
+static void rx_message(uint8_t eid, uint8_t msg_tag, bool tag_owner, void *data,
+		       void *msg, size_t len)
 {
 	struct ctx *ctx = data;
 	uint8_t type;
diff --git a/utils/mctp-demux-daemon.c b/utils/mctp-demux-daemon.c
index d7ab9bc..aeb2de0 100644
--- a/utils/mctp-demux-daemon.c
+++ b/utils/mctp-demux-daemon.c
@@ -84,7 +84,7 @@
 {
 	int rc;
 
-	rc = mctp_message_tx(ctx->mctp, eid, msg, len);
+	rc = mctp_message_tx(ctx->mctp, eid, MCTP_MESSAGE_TO_SRC, 0, msg, len);
 	if (rc)
 		warnx("Failed to send message: %d", rc);
 }
@@ -107,7 +107,9 @@
 	}
 }
 
-static void rx_message(uint8_t eid, void *data, void *msg, size_t len)
+static void
+rx_message(uint8_t eid, bool tag_owner __unused, uint8_t msg_tag __unused,
+	   void *data, void *msg, size_t len)
 {
 	struct ctx *ctx = data;
 	struct iovec iov[2];
@@ -410,7 +412,8 @@
 
 
 	if (eid == ctx->local_eid)
-		rx_message(eid, ctx, ctx->buf + 1, rc - 1);
+		rx_message(eid, MCTP_MESSAGE_TO_DST, 0, ctx, ctx->buf + 1,
+			   rc - 1);
 	else
 		tx_message(ctx, eid, ctx->buf + 1, rc - 1);
 
diff --git a/utils/mctp-in.c b/utils/mctp-in.c
index eff4abf..d4987ce 100644
--- a/utils/mctp-in.c
+++ b/utils/mctp-in.c
@@ -12,11 +12,11 @@
 #include <sys/poll.h>
 #include <sys/socket.h>
 
-static void rx_message(uint8_t eid, void *data, void *msg, size_t len)
+static void
+rx_message(uint8_t eid __unused, bool tag_owner __unused,
+	   uint8_t msg_tag __unused, void *data __unused, void *msg, size_t len)
 {
 	ssize_t rc;
-	(void)eid;
-	(void)data;
 
 	rc = write(STDOUT_FILENO, msg, len);
 	if (rc < 0)
diff --git a/utils/mctp-pipe.c b/utils/mctp-pipe.c
index ad466fb..12da74f 100644
--- a/utils/mctp-pipe.c
+++ b/utils/mctp-pipe.c
@@ -12,11 +12,11 @@
 #include <sys/poll.h>
 #include <sys/socket.h>
 
-static void rx_message(uint8_t eid, void *data, void *msg, size_t len)
+static void
+rx_message(uint8_t eid __unused, bool tag_owner __unused,
+	   uint8_t msg_tag __unused, void *data __unused, void *msg, size_t len)
 {
 	ssize_t rc;
-	(void)eid;
-	(void)data;
 
 	rc = write(STDOUT_FILENO, msg, len);
 	if (rc < 0)
@@ -91,7 +91,9 @@
 			} else if (rc < 0) {
 				err(EXIT_FAILURE, "read");
 			} else {
-				mctp_message_tx(mctp[0], eids[1], buf, rc);
+				mctp_message_tx(mctp[0], eids[1],
+						MCTP_MESSAGE_TO_SRC, 0, buf,
+						rc);
 			}
 		}
 
diff --git a/utils/mctp-test-client.c b/utils/mctp-test-client.c
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/utils/mctp-test-client.c