codec/ecu: Introduce new generic Error Concealment Unit abstraction

We don't want to expose the details of a given ECU implementation to
the user (e.g. osmo-bts), but have a generic abstraction layer where
an ECU implementation can simply register a few call-back functions
with the generic core.

As the developer and copyright holder of the related code, I hereby
state that any ECU implementation using 'struct osmo_ecu_ops' and
registering with the 'osmo_ecu_register()' function shall not be
considered as a derivative work under any applicable copyright law;
the copyleft terms of GPLv2 shall hence not apply to any such ECU
implementation.

The intent of the above exception is to allow anyone to combine
third party Error Concealment Unit implementations with libosmocore,
including but not limited to such published by ETSI.

Change-Id: I4d33c9c7c2d4c7462ff38a49c178b65accae1915
diff --git a/src/codec/Makefile.am b/src/codec/Makefile.am
index b522d43..c9d7a22 100644
--- a/src/codec/Makefile.am
+++ b/src/codec/Makefile.am
@@ -13,6 +13,6 @@
 
 lib_LTLIBRARIES = libosmocodec.la
 
-libosmocodec_la_SOURCES = gsm610.c gsm620.c gsm660.c gsm690.c ecu_fr.c
+libosmocodec_la_SOURCES = gsm610.c gsm620.c gsm660.c gsm690.c ecu.c ecu_fr.c
 libosmocodec_la_LDFLAGS = -version-info $(LIBVERSION) -no-undefined
 libosmocodec_la_LIBADD = $(top_builddir)/src/libosmocore.la
diff --git a/src/codec/ecu.c b/src/codec/ecu.c
new file mode 100644
index 0000000..db7148c
--- /dev/null
+++ b/src/codec/ecu.c
@@ -0,0 +1,118 @@
+/* Core infrastructure for ECU implementations */
+
+/* (C) 2019 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * 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.
+ *
+ */
+
+/* As the developer and copyright holder of the related code, I hereby
+ * state that any ECU implementation using 'struct osmo_ecu_ops' and
+ * registering with the 'osmo_ecu_register()' function shall not be
+ * considered as a derivative work under any applicable copyright law;
+ * the copyleft terms of GPLv2 shall hence not apply to any such ECU
+ * implementation.
+ *
+ * The intent of the above exception is to allow anyone to combine third
+ * party Error Concealment Unit implementations with libosmocodec.
+ * including but not limited to such published by ETSI.
+ *
+ *   -- Harald Welte <laforge@gnumonks.org> on August 1, 2019.
+ */
+
+#include <string.h>
+#include <errno.h>
+
+#include <osmocom/codec/ecu.h>
+#include <osmocom/core/talloc.h>
+
+static const struct osmo_ecu_ops *g_ecu_ops[_NUM_OSMO_ECU_CODECS];
+
+/***********************************************************************
+ * high-level API for users
+ ***********************************************************************/
+
+/*! initialize an ECU instance for given codec.
+ *  \param[in] ctx talloc context from which to allocate
+ *  \parma[in] codec codec for which to initialize/create ECU */
+struct osmo_ecu_state *osmo_ecu_init(void *ctx, enum osmo_ecu_codec codec)
+{
+	if (codec >= ARRAY_SIZE(g_ecu_ops))
+		return NULL;
+	if (!g_ecu_ops[codec] || !g_ecu_ops[codec]->init)
+		return NULL;
+	return g_ecu_ops[codec]->init(ctx, codec);
+}
+
+/*! destroy an ECU instance */
+void osmo_ecu_destroy(struct osmo_ecu_state *st)
+{
+	if (st->codec >= ARRAY_SIZE(g_ecu_ops))
+		return;
+	if (!g_ecu_ops[st->codec])
+		return;
+
+	if (!g_ecu_ops[st->codec]->destroy)
+		talloc_free(st);
+	else
+		g_ecu_ops[st->codec]->destroy(st);
+}
+
+/*! process a received frame a substitute/erroneous frame.
+ *  \param[in] st  ECU state/instance on which to operate
+ *  \param[in] bfi Bad Frame Indication
+ *  \param[in] frame received codec frame to be processed
+ *  \param[in] frame_bytes number of bytes available in frame */
+int osmo_ecu_frame_in(struct osmo_ecu_state *st, bool bfi,
+                      const uint8_t *frame, unsigned int frame_bytes)
+{
+	if (st->codec >= ARRAY_SIZE(g_ecu_ops))
+		return -EINVAL;
+	if (!g_ecu_ops[st->codec])
+		return -EBUSY;
+	return g_ecu_ops[st->codec]->frame_in(st, bfi, frame, frame_bytes);
+}
+
+/*! generate output data for a substitute/erroneous frame.
+ *  \param[in] st ECU state/instance on which to operate
+ *  \param[out] frame_out buffer for generated output frame
+ *  \return number of bytes written to frame_out; negative on error */
+int osmo_ecu_frame_out(struct osmo_ecu_state *st, uint8_t *frame_out)
+{
+	if (st->codec >= ARRAY_SIZE(g_ecu_ops))
+		return -EINVAL;
+	if (!g_ecu_ops[st->codec])
+		return -EBUSY;
+	return g_ecu_ops[st->codec]->frame_out(st, frame_out);
+}
+
+/***********************************************************************
+ * low-level API for ECU implementations
+ ***********************************************************************/
+
+/*! register an ECU implementation for a given codec */
+int osmo_ecu_register(const struct osmo_ecu_ops *ops, enum osmo_ecu_codec codec)
+{
+	if (codec >= ARRAY_SIZE(g_ecu_ops))
+		return -EINVAL;
+	if (g_ecu_ops[codec])
+		return -EBUSY;
+
+	g_ecu_ops[codec] = ops;
+
+	return 0;
+}
diff --git a/src/codec/ecu_fr.c b/src/codec/ecu_fr.c
index ef42ea9..4545172 100644
--- a/src/codec/ecu_fr.c
+++ b/src/codec/ecu_fr.c
@@ -164,3 +164,54 @@
 
 	return 0;
 }
+
+/***********************************************************************
+ * Integration with ECU core
+ ***********************************************************************/
+
+static struct osmo_ecu_state *ecu_fr_init(void *ctx, enum osmo_ecu_codec codec)
+{
+	struct osmo_ecu_state *st;
+	size_t size = sizeof(*st) + sizeof(struct osmo_ecu_fr_state);
+
+	st = talloc_named_const(ctx, size, "ecu_state_FR");
+	if (!st)
+		return NULL;
+
+	memset(st, 0, size);
+	st->codec = codec;
+
+	return st;
+}
+
+static int ecu_fr_frame_in(struct osmo_ecu_state *st, bool bfi, const uint8_t *frame,
+			   unsigned int frame_bytes)
+{
+	struct osmo_ecu_fr_state *fr = (struct osmo_ecu_fr_state *) &st->data;
+	if (bfi)
+		return 0;
+
+	osmo_ecu_fr_reset(fr, frame);
+	return 0;
+}
+
+static int ecu_fr_frame_out(struct osmo_ecu_state *st, uint8_t *frame_out)
+{
+	struct osmo_ecu_fr_state *fr = (struct osmo_ecu_fr_state *) &st->data;
+
+	if (osmo_ecu_fr_conceal(fr, frame_out) == 0)
+		return GSM_FR_BYTES;
+	else
+		return -1;
+}
+
+static const struct osmo_ecu_ops osmo_ecu_ops_fr = {
+	.init = ecu_fr_init,
+	.frame_in = ecu_fr_frame_in,
+	.frame_out = ecu_fr_frame_out,
+};
+
+static __attribute__((constructor)) void on_dso_load_ecu_fr(void)
+{
+	osmo_ecu_register(&osmo_ecu_ops_fr, OSMO_ECU_CODEC_FR);
+}