Implement ITU-T I.460 multiplex / demultiplex

This implements a multiplexer and de-multiplexer for the ITU-T I.460
standard.  The latter covers the transmission of sub-slots of 32/16/8k
inside 64k timeslots.

Change-Id: Id522f06e73b77332b437b7a27e4966872da70eda
diff --git a/src/gsm/Makefile.am b/src/gsm/Makefile.am
index 6935eab..eeb1164 100644
--- a/src/gsm/Makefile.am
+++ b/src/gsm/Makefile.am
@@ -32,7 +32,7 @@
 			milenage/milenage.c gan.c ipa.c gsm0341.c apn.c \
 			gsup.c gsup_sms.c gprs_gea.c gsm0503_conv.c oap.c gsm0808_utils.c \
 			gsm23003.c mncc.c bts_features.c oap_client.c \
-			gsm29118.c gsm48_rest_octets.c cbsp.c gsm48049.c
+			gsm29118.c gsm48_rest_octets.c cbsp.c gsm48049.c i460_mux.c
 libgsmint_la_LDFLAGS = -no-undefined
 libgsmint_la_LIBADD = $(top_builddir)/src/libosmocore.la
 
diff --git a/src/gsm/i460_mux.c b/src/gsm/i460_mux.c
new file mode 100644
index 0000000..3fb63ec
--- /dev/null
+++ b/src/gsm/i460_mux.c
@@ -0,0 +1,363 @@
+/*! \file i460_mux.c
+ * ITU-T I.460 sub-channel multiplexer + demultiplexer */
+/*
+ * (C) 2020 by Harald Welte <laforge@gnumonks.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301, USA.
+ */
+
+#include <errno.h>
+
+#include <osmocom/core/bits.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/i460_mux.h>
+
+/* count the number of sub-channels in this I460 slot */
+static int osmo_i460_subchan_count(struct osmo_i460_timeslot *ts)
+{
+	int i, num_used = 0;
+
+	for (i = 0; i < ARRAY_SIZE(ts->schan); i++) {
+		if (ts->schan[i].rate != OSMO_I460_RATE_NONE)
+			num_used++;
+	}
+
+	return num_used;
+}
+
+/* does this channel have no sub-streams (single 64k subchannel)? */
+static bool osmo_i460_has_single_64k_schan(struct osmo_i460_timeslot *ts)
+{
+	if (osmo_i460_subchan_count(ts) != 1)
+		return false;
+
+	if (ts->schan[0].rate != OSMO_I460_RATE_64k)
+		return false;
+
+	return true;
+}
+
+/***********************************************************************
+ * Demultiplexer
+ ***********************************************************************/
+
+/* append a single bit to a sub-channel */
+static void demux_subchan_append_bit(struct osmo_i460_subchan *schan, uint8_t bit)
+{
+	struct osmo_i460_subchan_demux *demux = &schan->demux;
+
+	OSMO_ASSERT(demux->out_bitbuf);
+	OSMO_ASSERT(demux->out_idx < demux->out_bitbuf_size);
+
+	demux->out_bitbuf[demux->out_idx++] = bit ? 1 : 0;
+
+	if (demux->out_idx >= demux->out_bitbuf_size) {
+		if (demux->out_cb_bits)
+			demux->out_cb_bits(demux->user_data, demux->out_bitbuf, demux->out_idx);
+		else {
+			/* pack bits into bytes */
+			OSMO_ASSERT((demux->out_idx % 8) == 0);
+			unsigned int num_bytes = demux->out_idx / 8;
+			uint8_t bytes[num_bytes];
+			osmo_ubit2pbit(bytes, demux->out_bitbuf, demux->out_idx);
+			demux->out_cb_bytes(demux->user_data, bytes, num_bytes);
+		}
+		demux->out_idx = 0;
+	}
+}
+
+/* extract those bits relevant to this schan of each byte in 'data' */
+static void demux_subchan_extract_bits(struct osmo_i460_subchan *schan, const uint8_t *data, size_t data_len)
+{
+	int i;
+
+	for (i = 0; i < data_len; i++) {
+		uint8_t inbyte = data[i];
+		uint8_t inbits = inbyte >> schan->bit_offset;
+
+		/* extract the bits relevant to the given schan */
+		switch (schan->rate) {
+		case OSMO_I460_RATE_8k:
+			demux_subchan_append_bit(schan, inbits & 0x01);
+			break;
+		case OSMO_I460_RATE_16k:
+			demux_subchan_append_bit(schan, inbits & 0x01);
+			demux_subchan_append_bit(schan, inbits & 0x02);
+			break;
+		case OSMO_I460_RATE_32k:
+			demux_subchan_append_bit(schan, inbits & 0x01);
+			demux_subchan_append_bit(schan, inbits & 0x02);
+			demux_subchan_append_bit(schan, inbits & 0x04);
+			demux_subchan_append_bit(schan, inbits & 0x08);
+			break;
+		case OSMO_I460_RATE_64k:
+			demux_subchan_append_bit(schan, inbits & 0x01);
+			demux_subchan_append_bit(schan, inbits & 0x02);
+			demux_subchan_append_bit(schan, inbits & 0x04);
+			demux_subchan_append_bit(schan, inbits & 0x08);
+			demux_subchan_append_bit(schan, inbits & 0x10);
+			demux_subchan_append_bit(schan, inbits & 0x20);
+			demux_subchan_append_bit(schan, inbits & 0x40);
+			demux_subchan_append_bit(schan, inbits & 0x80);
+			break;
+		default:
+			OSMO_ASSERT(0);
+		}
+	}
+}
+
+/*! Data from E1 timeslot into de-multiplexer
+ *  \param[in] ts timeslot state
+ *  \param[in] data input data bytes as received from E1/T1
+ *  \param[in] data_len length of data in bytes */
+void osmo_i460_demux_in(struct osmo_i460_timeslot *ts, const uint8_t *data, size_t data_len)
+{
+	struct osmo_i460_subchan *schan;
+	struct osmo_i460_subchan_demux *demux;
+	int i;
+
+	/* fast path if entire 64k slot is used */
+	if (osmo_i460_has_single_64k_schan(ts)) {
+		schan = &ts->schan[0];
+		demux = &schan->demux;
+		if (demux->out_cb_bytes)
+			demux->out_cb_bytes(demux->user_data, data, data_len);
+		else {
+			ubit_t bits[data_len*8];
+			osmo_pbit2ubit(bits, data, data_len*8);
+			demux->out_cb_bits(demux->user_data, bits, data_len*8);
+		}
+		return;
+	}
+
+	/* Slow path iterating over all lchans */
+	for (i = 0; i < ARRAY_SIZE(ts->schan); i++) {
+		schan = &ts->schan[i];
+		if (schan->rate == OSMO_I460_RATE_NONE)
+			continue;
+		demux_subchan_extract_bits(schan, data, data_len);
+	}
+}
+
+
+/***********************************************************************
+ * Multiplexer
+ ***********************************************************************/
+
+/*! enqueue a to-be-transmitted message buffer containing unpacked bits */
+void osmo_i460_mux_enqueue(struct osmo_i460_subchan *schan, struct msgb *msg)
+{
+	OSMO_ASSERT(msgb_length(msg) > 0);
+	msgb_enqueue(&schan->mux.tx_queue, msg);
+}
+
+/* mux: pull the next bit out of the given sub-channel */
+static ubit_t mux_schan_provide_bit(struct osmo_i460_subchan *schan)
+{
+	struct osmo_i460_subchan_mux *mux = &schan->mux;
+	struct msgb *msg;
+	ubit_t bit;
+
+	/* if we don't have anything to transmit, return '1' bits */
+	if (llist_empty(&mux->tx_queue))
+		return 0x01;
+
+	msg = llist_entry(mux->tx_queue.next, struct msgb, list);
+	bit = msgb_pull_u8(msg);
+
+	/* free msgb if we have pulled the last bit */
+	if (msgb_length(msg) <= 0) {
+		llist_del(&msg->list);
+		talloc_free(msg);
+	}
+
+	return bit;
+}
+
+/*! provide one byte with the subchan-specific bits of given sub-channel.
+ *  \param[in] schan sub-channel that is to provide bits
+ *  \parma[out] mask bitmask of those bits filled in
+ *  \returns bits of given sub-channel */
+static uint8_t mux_subchan_provide_bits(struct osmo_i460_subchan *schan, uint8_t *mask)
+{
+	uint8_t outbits = 0;
+	uint8_t outmask;
+
+	switch (schan->rate) {
+	case OSMO_I460_RATE_8k:
+		outbits = mux_schan_provide_bit(schan);
+		outmask = 0x01;
+		break;
+	case OSMO_I460_RATE_16k:
+		outbits |= mux_schan_provide_bit(schan) << 1;
+		outbits |= mux_schan_provide_bit(schan) << 0;
+		outmask = 0x03;
+		break;
+	case OSMO_I460_RATE_32k:
+		outbits |= mux_schan_provide_bit(schan) << 3;
+		outbits |= mux_schan_provide_bit(schan) << 2;
+		outbits |= mux_schan_provide_bit(schan) << 1;
+		outbits |= mux_schan_provide_bit(schan) << 0;
+		outmask = 0x0F;
+		break;
+	case OSMO_I460_RATE_64k:
+		outbits |= mux_schan_provide_bit(schan) << 7;
+		outbits |= mux_schan_provide_bit(schan) << 6;
+		outbits |= mux_schan_provide_bit(schan) << 5;
+		outbits |= mux_schan_provide_bit(schan) << 4;
+		outbits |= mux_schan_provide_bit(schan) << 3;
+		outbits |= mux_schan_provide_bit(schan) << 2;
+		outbits |= mux_schan_provide_bit(schan) << 1;
+		outbits |= mux_schan_provide_bit(schan) << 0;
+		outmask = 0xFF;
+		break;
+	default:
+		OSMO_ASSERT(0);
+	}
+	*mask = outmask << schan->bit_offset;
+	return outbits << schan->bit_offset;
+}
+
+/* provide one byte of multiplexed I.460 bits */
+static uint8_t mux_timeslot_provide_bits(struct osmo_i460_timeslot *ts)
+{
+	int i, count = 0;
+	uint8_t ret = 0xff; /* unused bits must be '1' as per I.460 */
+
+	for (i = 0; i < ARRAY_SIZE(ts->schan); i++) {
+		struct osmo_i460_subchan *schan = &ts->schan[i];
+		uint8_t bits, mask;
+
+		if (schan->rate == OSMO_I460_RATE_NONE)
+			continue;
+		count++;
+		bits = mux_subchan_provide_bits(schan, &mask);
+		ret &= ~mask;
+		ret |= bits;
+	}
+
+	return ret;
+}
+
+
+/*! Data from E1 timeslot into de-multiplexer
+ *  \param[in] ts timeslot state
+ *  \param[out] out caller-provided buffer where to store generated output bytes
+ *  \param[in] out_len number of bytes to be stored at out
+ */
+int osmo_i460_mux_out(struct osmo_i460_timeslot *ts, uint8_t *out, size_t out_len)
+{
+	int i;
+
+	/* fast path if entire 64k slot is used */
+	//if (osmo_i460_has_single_64k_schan(ts)) { }
+
+	for (i = 0; i < out_len; i++)
+		out[i] = mux_timeslot_provide_bits(ts);
+
+	return out_len;
+}
+
+
+/***********************************************************************
+ * Initialization / Control
+ ***********************************************************************/
+
+
+static int alloc_bitbuf(void *ctx, struct osmo_i460_subchan *schan, size_t num_bits)
+{
+	struct osmo_i460_subchan_demux *demux = &schan->demux;
+
+	talloc_free(demux->out_bitbuf);
+	demux->out_bitbuf = talloc_zero_size(ctx, num_bits);
+	if (!demux->out_bitbuf)
+		return -ENOMEM;
+	demux->out_bitbuf_size = num_bits;
+
+	return 0;
+}
+
+
+static int find_unused_subchan_idx(const struct osmo_i460_timeslot *ts)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(ts->schan); i++) {
+		const struct osmo_i460_subchan *schan = &ts->schan[i];
+		if (schan->rate == OSMO_I460_RATE_NONE)
+			return i;
+	}
+	return -1;
+}
+
+/*! initialize an I.460 timeslot */
+void osmo_i460_ts_init(struct osmo_i460_timeslot *ts)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(ts->schan); i++) {
+		struct osmo_i460_subchan *schan = &ts->schan[i];
+
+		memset(schan, 0, sizeof(*schan));
+		schan->rate = OSMO_I460_RATE_NONE;
+		INIT_LLIST_HEAD(&schan->mux.tx_queue);
+	}
+}
+
+/*! add a new sub-channel to the given timeslot
+ *  \param[in] ctx talloc context from where to allocate the internal buffer
+ *  \param[in] ts timeslot to which to add a sub-channel
+ *  \param[in] chd description of the sub-channel to be added
+ *  \return pointer to sub-channel on success, NULL on error */
+struct osmo_i460_subchan *
+osmo_i460_subchan_add(void *ctx, struct osmo_i460_timeslot *ts, const struct osmo_i460_schan_desc *chd)
+{
+	struct osmo_i460_subchan *schan;
+	int idx, rc;
+
+	idx = find_unused_subchan_idx(ts);
+	if (idx < 0)
+		return NULL;
+
+	schan = &ts->schan[idx];
+
+	schan->rate = chd->rate;
+	schan->bit_offset = chd->bit_offset;
+
+	schan->demux.out_cb_bits = chd->demux.out_cb_bits;
+	schan->demux.out_cb_bytes = chd->demux.out_cb_bytes;
+	schan->demux.user_data = chd->demux.user_data;
+	rc = alloc_bitbuf(ctx, schan, chd->demux.num_bits);
+	if (rc < 0) {
+		memset(schan, 0, sizeof(*schan));
+		return NULL;
+	}
+
+	/* return number of schan in use */
+	return schan;
+}
+
+/* remove a su-channel from the multiplex */
+void osmo_i460_subchan_del(struct osmo_i460_subchan *schan)
+{
+	talloc_free(schan->demux.out_bitbuf);
+	memset(schan, 0, sizeof(*schan));
+}
+
+/*! @} */
diff --git a/src/gsm/libosmogsm.map b/src/gsm/libosmogsm.map
index 70b3916..2000e6c 100644
--- a/src/gsm/libosmogsm.map
+++ b/src/gsm/libosmogsm.map
@@ -667,5 +667,12 @@
 osmo_cbsp_recv_buffered;
 osmo_cbsp_errstr;
 
+osmo_i460_demux_in;
+osmo_i460_mux_enqueue;
+osmo_i460_mux_out;
+osmo_i460_subchan_add;
+osmo_i460_subchan_del;
+osmo_i460_ts_init;
+
 local: *;
 };