core,API: Add bridge support

This change introduces a facility to bridge messages between two
bindings.

This is implemented through a new mctp_bridge_busses() API, which
applies a new routing policy, sending packets from one binding to the
other. This is in contrast to the current policy of dropping all
non-local packets.

To do this, the message context code needs to know both source and
destination EIDs, so add them to the mctp_msg_ctx_lookup() criteria.

Also, add a small test for bridge mode.

Signed-off-by: Jeremy Kerr <jk@ozlabs.org>
Change-Id: If532613525ddbf81df249e26d0f23825996f7bda
diff --git a/Makefile.am b/Makefile.am
index c224064..c712cfe 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -22,7 +22,7 @@
 
 TESTS = $(check_PROGRAMS)
 
-check_PROGRAMS = tests/test_eid tests/test_seq
+check_PROGRAMS = tests/test_eid tests/test_seq tests/test_bridge
 # We set a global LDADD here, as there's no way to specify it for all
 # tests. This means other targets' LDADDs need to be overridden.
 LDADD = tests/libtest-utils.a libmctp.la
diff --git a/README.md b/README.md
index ab89bbf..50519e3 100644
--- a/README.md
+++ b/README.md
@@ -31,6 +31,22 @@
 for the serial binding, the `mctp_serial_read()` function should be invoked
 when the file-descriptor for the serial device has data available.
 
+### Bridging
+
+libmctp implements basic support for bridging between two hardware bindings.
+In this mode, bindings may have different MTUs, so packets are reassembled into
+their messages, then the messages are re-packetised for the outgoing binding.
+
+For bridging between two endpoints, use the `mctp_bridge_busses()` function:
+
+ * `mctp = mctp_init()`: Initialise the MCTP core
+ * `b1 = mctp_<binding>_init(); b2 = mctp_<binding>_init()`: Initialise two hardware bindings
+ * `mctp_bridge_busses(mctp, b1, b2)`: Setup bridge
+
+Note that no EIDs are defined here; the bridge does not deliver any messages
+to a local rx callback, and messages are bridged as-is.
+
+
 Integration
 -----------
 
diff --git a/core.c b/core.c
index ff4bd63..e0cb95d 100644
--- a/core.c
+++ b/core.c
@@ -30,6 +30,7 @@
 
 struct mctp_msg_ctx {
 	uint8_t		src;
+	uint8_t		dest;
 	uint8_t		tag;
 	uint8_t		last_seq;
 	void		*buf;
@@ -38,8 +39,8 @@
 };
 
 struct mctp {
-	/* todo: multiple busses */
-	struct mctp_bus	busses[1];
+	int			n_busses;
+	struct mctp_bus		*busses;
 
 	/* Message RX callback */
 	mctp_rx_fn		message_rx;
@@ -49,6 +50,11 @@
 	 * @todo: flexible context count
 	 */
 	struct mctp_msg_ctx	msg_ctxs[16];
+
+	enum {
+		ROUTE_ENDPOINT,
+		ROUTE_BRIDGE,
+	}			route_policy;
 };
 
 #ifndef BUILD_ASSERT
@@ -60,6 +66,9 @@
 #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
 #endif
 
+static int mctp_message_tx_on_bus(struct mctp *mctp, struct mctp_bus *bus,
+		mctp_eid_t src, mctp_eid_t dest, void *msg, size_t msg_len);
+
 struct mctp_pktbuf *mctp_pktbuf_alloc(struct mctp_binding *binding, size_t len)
 {
 	struct mctp_pktbuf *buf;
@@ -133,7 +142,7 @@
 
 /* Message reassembly */
 static struct mctp_msg_ctx *mctp_msg_ctx_lookup(struct mctp *mctp,
-		uint8_t src, uint8_t tag)
+		uint8_t src, uint8_t dest, uint8_t tag)
 {
 	unsigned int i;
 
@@ -141,7 +150,7 @@
 	 * message contexts */
 	for (i = 0; i < ARRAY_SIZE(mctp->msg_ctxs); i++) {
 		struct mctp_msg_ctx *ctx = &mctp->msg_ctxs[i];
-		if (ctx->src == src && ctx->tag == tag)
+		if (ctx->src == src && ctx->dest == dest && ctx->tag == tag)
 			return ctx;
 	}
 
@@ -149,7 +158,7 @@
 }
 
 static struct mctp_msg_ctx *mctp_msg_ctx_create(struct mctp *mctp,
-		uint8_t src, uint8_t tag)
+		uint8_t src, uint8_t dest, uint8_t tag)
 {
 	struct mctp_msg_ctx *ctx = NULL;
 	unsigned int i;
@@ -166,6 +175,7 @@
 		return NULL;
 
 	ctx->src = src;
+	ctx->dest = dest;
 	ctx->tag = tag;
 	ctx->buf_size = 0;
 
@@ -229,6 +239,8 @@
 static struct mctp_bus *find_bus_for_eid(struct mctp *mctp,
 		mctp_eid_t dest __attribute__((unused)))
 {
+	/* for now, just use the first bus. For full routing support,
+	 * we will need a table of neighbours */
 	return &mctp->busses[0];
 }
 
@@ -237,14 +249,56 @@
 		mctp_eid_t eid)
 {
 	/* todo: multiple busses */
-	assert(!mctp->busses[0].binding);
+	assert(mctp->n_busses == 0);
+	mctp->n_busses = 1;
+	mctp->busses = __mctp_alloc(sizeof(struct mctp_bus));
 	mctp->busses[0].binding = binding;
 	mctp->busses[0].eid = eid;
 	binding->bus = &mctp->busses[0];
 	binding->mctp = mctp;
+	mctp->route_policy = ROUTE_ENDPOINT;
 	return 0;
 }
 
+int mctp_bridge_busses(struct mctp *mctp,
+		struct mctp_binding *b1, struct mctp_binding *b2)
+{
+	assert(mctp->n_busses == 0);
+	mctp->busses = __mctp_alloc(2 * sizeof(struct mctp_bus));
+	mctp->n_busses = 2;
+	mctp->busses[0].binding = b1;
+	b1->bus = &mctp->busses[0];
+	b1->mctp = mctp;
+	mctp->busses[1].binding = b2;
+	b2->bus = &mctp->busses[1];
+	b2->mctp = mctp;
+
+	mctp->route_policy = ROUTE_BRIDGE;
+	return 0;
+}
+
+static void mctp_rx(struct mctp *mctp, struct mctp_bus *bus,
+		mctp_eid_t src, mctp_eid_t dest, void *buf, size_t len)
+{
+	if (mctp->route_policy == ROUTE_ENDPOINT &&
+			dest == bus->eid && mctp->message_rx)
+		mctp->message_rx(src, mctp->message_rx_data, buf, len);
+
+	if (mctp->route_policy == ROUTE_BRIDGE) {
+		int i;
+
+		for (i = 0; i < mctp->n_busses; i++) {
+			struct mctp_bus *dest_bus = &mctp->busses[i];
+			if (dest_bus == bus)
+				continue;
+
+			mctp_message_tx_on_bus(mctp, dest_bus,
+					src, dest, buf, len);
+		}
+
+	}
+}
+
 void mctp_bus_rx(struct mctp_binding *binding, struct mctp_pktbuf *pkt)
 {
 	struct mctp_bus *bus = binding->bus;
@@ -260,8 +314,9 @@
 
 	hdr = mctp_pktbuf_hdr(pkt);
 
-	if (hdr->dest != bus->eid)
-		/* @todo: non-local packet routing */
+	/* small optimisation: don't bother reassembly if we're going to
+	 * drop the packet in mctp_rx anyway */
+	if (mctp->route_policy == ROUTE_ENDPOINT && hdr->dest != bus->eid)
 		goto out;
 
 	flags = hdr->flags_seq_tag & (MCTP_HDR_FLAG_SOM | MCTP_HDR_FLAG_EOM);
@@ -274,18 +329,19 @@
 		 * 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->message_rx(hdr->src, mctp->message_rx_data, p, len);
+		mctp_rx(mctp, bus, hdr->src, hdr->dest, p, len);
 		break;
 
 	case MCTP_HDR_FLAG_SOM:
 		/* start of a new message - start the new context for
 		 * future message reception. If an existing context is
 		 * already present, drop it. */
-		ctx = mctp_msg_ctx_lookup(mctp, hdr->src, tag);
+		ctx = mctp_msg_ctx_lookup(mctp, hdr->src, hdr->dest, tag);
 		if (ctx) {
 			mctp_msg_ctx_reset(ctx);
 		} else {
-			ctx = mctp_msg_ctx_create(mctp, hdr->src, tag);
+			ctx = mctp_msg_ctx_create(mctp,
+					hdr->src, hdr->dest, tag);
 		}
 
 		rc = mctp_msg_ctx_add_pkt(ctx, pkt);
@@ -298,7 +354,7 @@
 		break;
 
 	case MCTP_HDR_FLAG_EOM:
-		ctx = mctp_msg_ctx_lookup(mctp, hdr->src, tag);
+		ctx = mctp_msg_ctx_lookup(mctp, hdr->src, hdr->dest, tag);
 		if (!ctx)
 			goto out;
 
@@ -313,17 +369,16 @@
 		}
 
 		rc = mctp_msg_ctx_add_pkt(ctx, pkt);
-		if (!rc) {
-			mctp->message_rx(ctx->src, mctp->message_rx_data,
+		if (!rc)
+			mctp_rx(mctp, bus, ctx->src, ctx->dest,
 					ctx->buf, ctx->buf_size);
-		}
 
 		mctp_msg_ctx_drop(ctx);
 		break;
 
 	case 0:
 		/* Neither SOM nor EOM */
-		ctx = mctp_msg_ctx_lookup(mctp, hdr->src, tag);
+		ctx = mctp_msg_ctx_lookup(mctp, hdr->src,hdr->dest, tag);
 		if (!ctx)
 			goto out;
 
@@ -386,16 +441,14 @@
 		mctp_send_tx_queue(bus);
 }
 
-int mctp_message_tx(struct mctp *mctp, mctp_eid_t eid,
-		void *msg, size_t msg_len)
+static int mctp_message_tx_on_bus(struct mctp *mctp, struct mctp_bus *bus,
+		mctp_eid_t src, mctp_eid_t dest, void *msg, size_t msg_len)
 {
 	size_t max_payload_len, payload_len, p;
 	struct mctp_pktbuf *pkt;
 	struct mctp_hdr *hdr;
-	struct mctp_bus *bus;
 	int i;
 
-	bus = find_bus_for_eid(mctp, eid);
 	max_payload_len = bus->binding->pkt_size - sizeof(*hdr);
 
 	/* queue up packets, each of max MCTP_MTU size */
@@ -404,13 +457,14 @@
 		if (payload_len > max_payload_len)
 			payload_len = max_payload_len;
 
-		pkt = mctp_pktbuf_alloc(bus->binding, payload_len + sizeof(*hdr));
+		pkt = mctp_pktbuf_alloc(bus->binding,
+				payload_len + sizeof(*hdr));
 		hdr = mctp_pktbuf_hdr(pkt);
 
 		/* todo: tags */
 		hdr->ver = bus->binding->version & 0xf;
-		hdr->dest = eid;
-		hdr->src = bus->eid;
+		hdr->dest = dest;
+		hdr->src = src;
 		hdr->flags_seq_tag = MCTP_HDR_FLAG_TO |
 			(0 << MCTP_HDR_TAG_SHIFT);
 
@@ -437,3 +491,12 @@
 
 	return 0;
 }
+
+int mctp_message_tx(struct mctp *mctp, mctp_eid_t eid,
+		void *msg, size_t msg_len)
+{
+	struct mctp_bus *bus;
+
+	bus = find_bus_for_eid(mctp, eid);
+	return mctp_message_tx_on_bus(mctp, bus, bus->eid, eid, msg, msg_len);
+}
diff --git a/libmctp.h b/libmctp.h
index 5219a35..523891f 100644
--- a/libmctp.h
+++ b/libmctp.h
@@ -63,11 +63,24 @@
 
 /* Register a binding to the MCTP core, and creates a bus (populating
  * binding->bus).
+ *
+ * If this function is called, the MCTP stack is initialised as an 'endpoint',
+ * and will deliver local packets to a RX callback - see `mctp_set_rx_all()`
+ * below.
  */
 int mctp_register_bus(struct mctp *mctp,
 		struct mctp_binding *binding,
 		mctp_eid_t eid);
 
+/* Create a simple bidirectional bridge between busses.
+ *
+ * In this mode, the MCTP stack is initialised as a bridge. There is no EID
+ * defined, so no packets are considered local. Instead, all messages from one
+ * binding are forwarded to the other.
+ */
+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);
 
diff --git a/tests/test-utils.h b/tests/test-utils.h
index 94f43b8..966a2df 100644
--- a/tests/test-utils.h
+++ b/tests/test-utils.h
@@ -5,6 +5,11 @@
 
 #include <libmctp.h>
 
+#ifndef container_of
+#define container_of(ptr, type, member) \
+    (type *)((char *)(ptr) - (char *)&((type *)0)->member)
+#endif
+
 /* test binding implementation */
 
 /* standard binding interface */
diff --git a/tests/test_bridge.c b/tests/test_bridge.c
new file mode 100644
index 0000000..496ae19
--- /dev/null
+++ b/tests/test_bridge.c
@@ -0,0 +1,102 @@
+/* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */
+
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <libmctp.h>
+#include <libmctp-alloc.h>
+
+#include "test-utils.h"
+
+struct mctp_binding_bridge {
+	struct mctp_binding	binding;
+	int			rx_count;
+	int			tx_count;
+	uint8_t			last_pkt_data;
+};
+
+struct test_ctx {
+	struct mctp			*mctp;
+	struct mctp_binding_bridge	*bindings[2];
+};
+
+static int mctp_binding_bridge_tx(struct mctp_binding *b,
+		struct mctp_pktbuf *pkt)
+{
+	struct mctp_binding_bridge *binding = container_of(b,
+			struct mctp_binding_bridge, binding);
+
+	binding->tx_count++;
+	assert(mctp_pktbuf_size(pkt) == sizeof(struct mctp_hdr) + 1);
+	binding->last_pkt_data = *(uint8_t *)mctp_pktbuf_data(pkt);
+
+	return 0;
+}
+
+static int mctp_binding_bridge_rx(struct mctp_binding_bridge *binding,
+		uint8_t key)
+{
+	struct mctp_pktbuf *pkt;
+	struct mctp_hdr *hdr;
+	uint8_t *buf;
+
+	pkt = mctp_pktbuf_alloc(&binding->binding,
+			sizeof(struct mctp_hdr) + 1);
+
+	hdr = mctp_pktbuf_hdr(pkt);
+	hdr->flags_seq_tag = MCTP_HDR_FLAG_SOM | MCTP_HDR_FLAG_EOM;
+
+	/* arbitrary src/dest, as we're bridging */
+	hdr->src = 1;
+	hdr->dest = 2;
+
+	buf = mctp_pktbuf_data(pkt);
+	*buf = key;
+
+	binding->rx_count++;
+	mctp_bus_rx(&binding->binding, pkt);
+}
+
+static struct mctp_binding_bridge *mctp_binding_bridge_init(void)
+{
+	struct mctp_binding_bridge *binding;
+
+	binding = __mctp_alloc(sizeof(*binding));
+	memset(binding, 0, sizeof(*binding));
+	binding->binding.name = "test";
+	binding->binding.version = 1;
+	binding->binding.tx = mctp_binding_bridge_tx;
+	binding->binding.pkt_size = MCTP_BMTU;
+	binding->binding.pkt_pad = 0;
+	return binding;
+}
+
+int main(void)
+{
+	struct test_ctx _ctx, *ctx = &_ctx;
+
+	ctx->mctp = mctp_init();
+	ctx->bindings[0] = mctp_binding_bridge_init();
+	ctx->bindings[1] = mctp_binding_bridge_init();
+
+	mctp_bridge_busses(ctx->mctp,
+			&ctx->bindings[0]->binding,
+			&ctx->bindings[1]->binding);
+
+	mctp_binding_set_tx_enabled(&ctx->bindings[0]->binding, true);
+	mctp_binding_set_tx_enabled(&ctx->bindings[1]->binding, true);
+
+	mctp_binding_bridge_rx(ctx->bindings[0], 0xaa);
+	assert(ctx->bindings[0]->tx_count == 0);
+	assert(ctx->bindings[1]->tx_count == 1);
+	assert(ctx->bindings[1]->last_pkt_data == 0xaa);
+
+	mctp_binding_bridge_rx(ctx->bindings[1], 0x55);
+	assert(ctx->bindings[1]->tx_count == 1);
+	assert(ctx->bindings[0]->tx_count == 1);
+	assert(ctx->bindings[0]->last_pkt_data == 0x55);
+
+	return EXIT_SUCCESS;
+}