Portability fix: Adding local partial copy of libosmocore (TODO: minimize it)
diff --git a/lib/decoding/osmocom/core/CMakeLists.txt b/lib/decoding/osmocom/core/CMakeLists.txt
new file mode 100644
index 0000000..ce8e60f
--- /dev/null
+++ b/lib/decoding/osmocom/core/CMakeLists.txt
@@ -0,0 +1,11 @@
+add_sources(
+bits.c
+bitvec.c
+conv_acc.c
+conv_acc_generic.c
+conv.c
+crc16gen.c
+crc64gen.c
+crc8gen.c
+panic.c
+)
diff --git a/lib/decoding/osmocom/core/bit16gen.h b/lib/decoding/osmocom/core/bit16gen.h
new file mode 100644
index 0000000..5c6162c
--- /dev/null
+++ b/lib/decoding/osmocom/core/bit16gen.h
@@ -0,0 +1,105 @@
+/*
+ * bit16gen.h
+ *
+ * Copyright (C) 2014  Max <max.suraev@fairwaves.co>
+ *
+ * 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.
+ */
+
+#pragma once
+
+/*! \brief load unaligned n-byte integer (little-endian encoding) into uint16_t
+ *  \param[in] p Buffer where integer is stored
+ *  \param[in] n Number of bytes stored in p
+ *  \returns 16 bit unsigned integer
+ */
+static inline uint16_t osmo_load16le_ext(const void *p, uint8_t n)
+{
+	uint8_t i;
+	uint16_t r = 0;
+	const uint8_t *q = (uint8_t *)p;
+	for(i = 0; i < n; r |= ((uint16_t)q[i] << (8 * i)), i++);
+	return r;
+}
+
+/*! \brief load unaligned n-byte integer (big-endian encoding) into uint16_t
+ *  \param[in] p Buffer where integer is stored
+ *  \param[in] n Number of bytes stored in p
+ *  \returns 16 bit unsigned integer
+ */
+static inline uint16_t osmo_load16be_ext(const void *p, uint8_t n)
+{
+	uint8_t i;
+	uint16_t r = 0;
+	const uint8_t *q = (uint8_t *)p;
+	for(i = 0; i < n; r |= ((uint16_t)q[i] << (16 - 8* (1 + i))), i++);
+	return r;
+}
+
+
+/*! \brief store unaligned n-byte integer (little-endian encoding) from uint16_t
+ *  \param[in] x unsigned 16 bit integer
+ *  \param[out] p Buffer to store integer
+ *  \param[in] n Number of bytes to store
+ */
+static inline void osmo_store16le_ext(uint16_t x, void *p, uint8_t n)
+{
+	uint8_t i;
+	uint8_t *q = (uint8_t *)p;
+	for(i = 0; i < n; q[i] = (x >> i * 8) & 0xFF, i++);
+}
+
+/*! \brief store unaligned n-byte integer (big-endian encoding) from uint16_t
+ *  \param[in] x unsigned 16 bit integer
+ *  \param[out] p Buffer to store integer
+ *  \param[in] n Number of bytes to store
+ */
+static inline void osmo_store16be_ext(uint16_t x, void *p, uint8_t n)
+{
+	uint8_t i;
+	uint8_t *q = (uint8_t *)p;
+	for(i = 0; i < n; q[i] = (x >> ((n - 1 - i) * 8)) & 0xFF, i++);
+}
+
+
+/* Convenience function for most-used cases */
+
+
+/*! \brief load unaligned 16-bit integer (little-endian encoding) */
+static inline uint16_t osmo_load16le(const void *p)
+{
+	return osmo_load16le_ext(p, 16 / 8);
+}
+
+/*! \brief load unaligned 16-bit integer (big-endian encoding) */
+static inline uint16_t osmo_load16be(const void *p)
+{
+	return osmo_load16be_ext(p, 16 / 8);
+}
+
+
+/*! \brief store unaligned 16-bit integer (little-endian encoding) */
+static inline void osmo_store16le(uint16_t x, void *p)
+{
+	osmo_store16le_ext(x, p, 16 / 8);
+}
+
+/*! \brief store unaligned 16-bit integer (big-endian encoding) */
+static inline void osmo_store16be(uint16_t x, void *p)
+{
+	osmo_store16be_ext(x, p, 16 / 8);
+}
diff --git a/lib/decoding/osmocom/core/bit32gen.h b/lib/decoding/osmocom/core/bit32gen.h
new file mode 100644
index 0000000..6640e76
--- /dev/null
+++ b/lib/decoding/osmocom/core/bit32gen.h
@@ -0,0 +1,105 @@
+/*
+ * bit32gen.h
+ *
+ * Copyright (C) 2014  Max <max.suraev@fairwaves.co>
+ *
+ * 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.
+ */
+
+#pragma once
+
+/*! \brief load unaligned n-byte integer (little-endian encoding) into uint32_t
+ *  \param[in] p Buffer where integer is stored
+ *  \param[in] n Number of bytes stored in p
+ *  \returns 32 bit unsigned integer
+ */
+static inline uint32_t osmo_load32le_ext(const void *p, uint8_t n)
+{
+	uint8_t i;
+	uint32_t r = 0;
+	const uint8_t *q = (uint8_t *)p;
+	for(i = 0; i < n; r |= ((uint32_t)q[i] << (8 * i)), i++);
+	return r;
+}
+
+/*! \brief load unaligned n-byte integer (big-endian encoding) into uint32_t
+ *  \param[in] p Buffer where integer is stored
+ *  \param[in] n Number of bytes stored in p
+ *  \returns 32 bit unsigned integer
+ */
+static inline uint32_t osmo_load32be_ext(const void *p, uint8_t n)
+{
+	uint8_t i;
+	uint32_t r = 0;
+	const uint8_t *q = (uint8_t *)p;
+	for(i = 0; i < n; r |= ((uint32_t)q[i] << (32 - 8* (1 + i))), i++);
+	return r;
+}
+
+
+/*! \brief store unaligned n-byte integer (little-endian encoding) from uint32_t
+ *  \param[in] x unsigned 32 bit integer
+ *  \param[out] p Buffer to store integer
+ *  \param[in] n Number of bytes to store
+ */
+static inline void osmo_store32le_ext(uint32_t x, void *p, uint8_t n)
+{
+	uint8_t i;
+	uint8_t *q = (uint8_t *)p;
+	for(i = 0; i < n; q[i] = (x >> i * 8) & 0xFF, i++);
+}
+
+/*! \brief store unaligned n-byte integer (big-endian encoding) from uint32_t
+ *  \param[in] x unsigned 32 bit integer
+ *  \param[out] p Buffer to store integer
+ *  \param[in] n Number of bytes to store
+ */
+static inline void osmo_store32be_ext(uint32_t x, void *p, uint8_t n)
+{
+	uint8_t i;
+	uint8_t *q = (uint8_t *)p;
+	for(i = 0; i < n; q[i] = (x >> ((n - 1 - i) * 8)) & 0xFF, i++);
+}
+
+
+/* Convenience function for most-used cases */
+
+
+/*! \brief load unaligned 32-bit integer (little-endian encoding) */
+static inline uint32_t osmo_load32le(const void *p)
+{
+	return osmo_load32le_ext(p, 32 / 8);
+}
+
+/*! \brief load unaligned 32-bit integer (big-endian encoding) */
+static inline uint32_t osmo_load32be(const void *p)
+{
+	return osmo_load32be_ext(p, 32 / 8);
+}
+
+
+/*! \brief store unaligned 32-bit integer (little-endian encoding) */
+static inline void osmo_store32le(uint32_t x, void *p)
+{
+	osmo_store32le_ext(x, p, 32 / 8);
+}
+
+/*! \brief store unaligned 32-bit integer (big-endian encoding) */
+static inline void osmo_store32be(uint32_t x, void *p)
+{
+	osmo_store32be_ext(x, p, 32 / 8);
+}
diff --git a/lib/decoding/osmocom/core/bit64gen.h b/lib/decoding/osmocom/core/bit64gen.h
new file mode 100644
index 0000000..8c7b709
--- /dev/null
+++ b/lib/decoding/osmocom/core/bit64gen.h
@@ -0,0 +1,105 @@
+/*
+ * bit64gen.h
+ *
+ * Copyright (C) 2014  Max <max.suraev@fairwaves.co>
+ *
+ * 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.
+ */
+
+#pragma once
+
+/*! \brief load unaligned n-byte integer (little-endian encoding) into uint64_t
+ *  \param[in] p Buffer where integer is stored
+ *  \param[in] n Number of bytes stored in p
+ *  \returns 64 bit unsigned integer
+ */
+static inline uint64_t osmo_load64le_ext(const void *p, uint8_t n)
+{
+	uint8_t i;
+	uint64_t r = 0;
+	const uint8_t *q = (uint8_t *)p;
+	for(i = 0; i < n; r |= ((uint64_t)q[i] << (8 * i)), i++);
+	return r;
+}
+
+/*! \brief load unaligned n-byte integer (big-endian encoding) into uint64_t
+ *  \param[in] p Buffer where integer is stored
+ *  \param[in] n Number of bytes stored in p
+ *  \returns 64 bit unsigned integer
+ */
+static inline uint64_t osmo_load64be_ext(const void *p, uint8_t n)
+{
+	uint8_t i;
+	uint64_t r = 0;
+	const uint8_t *q = (uint8_t *)p;
+	for(i = 0; i < n; r |= ((uint64_t)q[i] << (64 - 8* (1 + i))), i++);
+	return r;
+}
+
+
+/*! \brief store unaligned n-byte integer (little-endian encoding) from uint64_t
+ *  \param[in] x unsigned 64 bit integer
+ *  \param[out] p Buffer to store integer
+ *  \param[in] n Number of bytes to store
+ */
+static inline void osmo_store64le_ext(uint64_t x, void *p, uint8_t n)
+{
+	uint8_t i;
+	uint8_t *q = (uint8_t *)p;
+	for(i = 0; i < n; q[i] = (x >> i * 8) & 0xFF, i++);
+}
+
+/*! \brief store unaligned n-byte integer (big-endian encoding) from uint64_t
+ *  \param[in] x unsigned 64 bit integer
+ *  \param[out] p Buffer to store integer
+ *  \param[in] n Number of bytes to store
+ */
+static inline void osmo_store64be_ext(uint64_t x, void *p, uint8_t n)
+{
+	uint8_t i;
+	uint8_t *q = (uint8_t *)p;
+	for(i = 0; i < n; q[i] = (x >> ((n - 1 - i) * 8)) & 0xFF, i++);
+}
+
+
+/* Convenience function for most-used cases */
+
+
+/*! \brief load unaligned 64-bit integer (little-endian encoding) */
+static inline uint64_t osmo_load64le(const void *p)
+{
+	return osmo_load64le_ext(p, 64 / 8);
+}
+
+/*! \brief load unaligned 64-bit integer (big-endian encoding) */
+static inline uint64_t osmo_load64be(const void *p)
+{
+	return osmo_load64be_ext(p, 64 / 8);
+}
+
+
+/*! \brief store unaligned 64-bit integer (little-endian encoding) */
+static inline void osmo_store64le(uint64_t x, void *p)
+{
+	osmo_store64le_ext(x, p, 64 / 8);
+}
+
+/*! \brief store unaligned 64-bit integer (big-endian encoding) */
+static inline void osmo_store64be(uint64_t x, void *p)
+{
+	osmo_store64be_ext(x, p, 64 / 8);
+}
diff --git a/lib/decoding/osmocom/core/bits.c b/lib/decoding/osmocom/core/bits.c
new file mode 100644
index 0000000..8837c1f
--- /dev/null
+++ b/lib/decoding/osmocom/core/bits.c
@@ -0,0 +1,311 @@
+/*
+ * (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2011 by Sylvain Munaut <tnt@246tNt.com>
+ *
+ * All Rights Reserved
+ *
+ * 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 <stdint.h>
+
+#include <osmocom/core/bits.h>
+
+/*! \addtogroup bits
+ *  @{
+ *  Osmocom bit level support code.
+ *
+ *  This module implements the notion of different bit-fields, such as
+ *  - unpacked bits (\ref ubit_t), i.e. 1 bit per byte
+ *  - packed bits (\ref pbit_t), i.e. 8 bits per byte
+ *  - soft bits (\ref sbit_t), 1 bit per byte from -127 to 127
+ *
+ * \file bits.c */
+
+/*! convert unpacked bits to packed bits, return length in bytes
+ *  \param[out] out output buffer of packed bits
+ *  \param[in] in input buffer of unpacked bits
+ *  \param[in] num_bits number of bits
+ */
+int osmo_ubit2pbit(pbit_t *out, const ubit_t *in, unsigned int num_bits)
+{
+	unsigned int i;
+	uint8_t curbyte = 0;
+	pbit_t *outptr = out;
+
+	for (i = 0; i < num_bits; i++) {
+		uint8_t bitnum = 7 - (i % 8);
+
+		curbyte |= (in[i] << bitnum);
+
+		if(i % 8 == 7){
+			*outptr++ = curbyte;
+			curbyte = 0;
+		}
+	}
+	/* we have a non-modulo-8 bitcount */
+	if (i % 8)
+		*outptr++ = curbyte;
+
+	return outptr - out;
+}
+
+/*! Shift unaligned input to octet-aligned output
+ *  \param[out] out output buffer, unaligned
+ *  \param[in] in input buffer, octet-aligned
+ *  \param[in] num_nibbles number of nibbles
+ */
+void osmo_nibble_shift_right(uint8_t *out, const uint8_t *in,
+			     unsigned int num_nibbles)
+{
+	unsigned int i, num_whole_bytes = num_nibbles / 2;
+	if (!num_whole_bytes)
+		return;
+
+	/* first byte: upper nibble empty, lower nibble from src */
+	out[0] = (in[0] >> 4);
+
+	/* bytes 1.. */
+	for (i = 1; i < num_whole_bytes; i++)
+		out[i] = ((in[i - 1] & 0xF) << 4) | (in[i] >> 4);
+
+	/* shift the last nibble, in case there's an odd count */
+	i = num_whole_bytes;
+	if (num_nibbles & 1)
+		out[i] = ((in[i - 1] & 0xF) << 4) | (in[i] >> 4);
+	else
+		out[i] = (in[i - 1] & 0xF) << 4;
+}
+
+/*! Shift unaligned input to octet-aligned output
+ *  \param[out] out output buffer, octet-aligned
+ *  \param[in] in input buffer, unaligned
+ *  \param[in] num_nibbles number of nibbles
+ */
+void osmo_nibble_shift_left_unal(uint8_t *out, const uint8_t *in,
+				unsigned int num_nibbles)
+{
+	unsigned int i, num_whole_bytes = num_nibbles / 2;
+	if (!num_whole_bytes)
+		return;
+
+	for (i = 0; i < num_whole_bytes; i++)
+		out[i] = ((in[i] & 0xF) << 4) | (in[i + 1] >> 4);
+
+	/* shift the last nibble, in case there's an odd count */
+	i = num_whole_bytes;
+	if (num_nibbles & 1)
+		out[i] = (in[i] & 0xF) << 4;
+}
+
+/*! convert unpacked bits to soft bits
+ *  \param[out] out output buffer of soft bits
+ *  \param[in] in input buffer of unpacked bits
+ *  \param[in] num_bits number of bits
+ */
+void osmo_ubit2sbit(sbit_t *out, const ubit_t *in, unsigned int num_bits)
+{
+	unsigned int i;
+	for (i = 0; i < num_bits; i++)
+		out[i] = in[i] ? -127 : 127;
+}
+
+/*! convert soft bits to unpacked bits
+ *  \param[out] out output buffer of unpacked bits
+ *  \param[in] in input buffer of soft bits
+ *  \param[in] num_bits number of bits
+ */
+void osmo_sbit2ubit(ubit_t *out, const sbit_t *in, unsigned int num_bits)
+{
+	unsigned int i;
+	for (i = 0; i < num_bits; i++)
+		out[i] = in[i] < 0;
+}
+
+/*! convert packed bits to unpacked bits, return length in bytes
+ *  \param[out] out output buffer of unpacked bits
+ *  \param[in] in input buffer of packed bits
+ *  \param[in] num_bits number of bits
+ *  \return number of bytes used in \ref out
+ */
+int osmo_pbit2ubit(ubit_t *out, const pbit_t *in, unsigned int num_bits)
+{
+	unsigned int i;
+	ubit_t *cur = out;
+	ubit_t *limit = out + num_bits;
+
+	for (i = 0; i < (num_bits/8)+1; i++) {
+		pbit_t byte = in[i];
+		*cur++ = (byte >> 7) & 1;
+		if (cur >= limit)
+			break;
+		*cur++ = (byte >> 6) & 1;
+		if (cur >= limit)
+			break;
+		*cur++ = (byte >> 5) & 1;
+		if (cur >= limit)
+			break;
+		*cur++ = (byte >> 4) & 1;
+		if (cur >= limit)
+			break;
+		*cur++ = (byte >> 3) & 1;
+		if (cur >= limit)
+			break;
+		*cur++ = (byte >> 2) & 1;
+		if (cur >= limit)
+			break;
+		*cur++ = (byte >> 1) & 1;
+		if (cur >= limit)
+			break;
+		*cur++ = (byte >> 0) & 1;
+		if (cur >= limit)
+			break;
+	}
+	return cur - out;
+}
+
+/*! convert unpacked bits to packed bits (extended options)
+ *  \param[out] out output buffer of packed bits
+ *  \param[in] out_ofs offset into output buffer
+ *  \param[in] in input buffer of unpacked bits
+ *  \param[in] in_ofs offset into input buffer
+ *  \param[in] num_bits number of bits
+ *  \param[in] lsb_mode Encode bits in LSB orde instead of MSB
+ *  \returns length in bytes (max written offset of output buffer + 1)
+ */
+int osmo_ubit2pbit_ext(pbit_t *out, unsigned int out_ofs,
+                       const ubit_t *in, unsigned int in_ofs,
+                       unsigned int num_bits, int lsb_mode)
+{
+	int i, op, bn;
+	for (i=0; i<num_bits; i++) {
+		op = out_ofs + i;
+		bn = lsb_mode ? (op&7) : (7-(op&7));
+		if (in[in_ofs+i])
+			out[op>>3] |= 1 << bn;
+		else
+			out[op>>3] &= ~(1 << bn);
+	}
+	return ((out_ofs + num_bits - 1) >> 3) + 1;
+}
+
+/*! convert packed bits to unpacked bits (extended options)
+ *  \param[out] out output buffer of unpacked bits
+ *  \param[in] out_ofs offset into output buffer
+ *  \param[in] in input buffer of packed bits
+ *  \param[in] in_ofs offset into input buffer
+ *  \param[in] num_bits number of bits
+ *  \param[in] lsb_mode Encode bits in LSB orde instead of MSB
+ *  \returns length in bytes (max written offset of output buffer + 1)
+ */
+int osmo_pbit2ubit_ext(ubit_t *out, unsigned int out_ofs,
+                       const pbit_t *in, unsigned int in_ofs,
+                       unsigned int num_bits, int lsb_mode)
+{
+	int i, ip, bn;
+	for (i=0; i<num_bits; i++) {
+		ip = in_ofs + i;
+		bn = lsb_mode ? (ip&7) : (7-(ip&7));
+		out[out_ofs+i] = !!(in[ip>>3] & (1<<bn));
+	}
+	return out_ofs + num_bits;
+}
+
+/*! generalized bit reversal function
+ *  \param[in] x the 32bit value to be reversed
+ *  \param[in] k the type of reversal requested
+ *  \returns the reversed 32bit dword
+ *
+ * This function reverses the bit order within a 32bit word. Depending
+ * on "k", it either reverses all bits in a 32bit dword, or the bytes in
+ * the dword, or the bits in each byte of a dword, or simply swaps the
+ * two 16bit words in a dword.  See Chapter 7 "Hackers Delight"
+ */
+uint32_t osmo_bit_reversal(uint32_t x, enum osmo_br_mode k)
+{
+	if (k &  1) x = (x & 0x55555555) <<  1 | (x & 0xAAAAAAAA) >>  1;
+	if (k &  2) x = (x & 0x33333333) <<  2 | (x & 0xCCCCCCCC) >>  2;
+	if (k &  4) x = (x & 0x0F0F0F0F) <<  4 | (x & 0xF0F0F0F0) >>  4;
+	if (k &  8) x = (x & 0x00FF00FF) <<  8 | (x & 0xFF00FF00) >>  8;
+	if (k & 16) x = (x & 0x0000FFFF) << 16 | (x & 0xFFFF0000) >> 16;
+
+	return x;
+}
+
+/*! reverse the bit-order in each byte of a dword
+ *  \param[in] x 32bit input value
+ *  \returns 32bit value where bits of each byte have been reversed
+ *
+ * See Chapter 7 "Hackers Delight"
+ */
+uint32_t osmo_revbytebits_32(uint32_t x)
+{
+	x = (x & 0x55555555) <<  1 | (x & 0xAAAAAAAA) >>  1;
+	x = (x & 0x33333333) <<  2 | (x & 0xCCCCCCCC) >>  2;
+	x = (x & 0x0F0F0F0F) <<  4 | (x & 0xF0F0F0F0) >>  4;
+
+	return x;
+}
+
+/*! reverse the bit order in a byte
+ *  \param[in] x 8bit input value
+ *  \returns 8bit value where bits order has been reversed
+ *
+ * See Chapter 7 "Hackers Delight"
+ */
+uint32_t osmo_revbytebits_8(uint8_t x)
+{
+	x = (x & 0x55) <<  1 | (x & 0xAA) >>  1;
+	x = (x & 0x33) <<  2 | (x & 0xCC) >>  2;
+	x = (x & 0x0F) <<  4 | (x & 0xF0) >>  4;
+
+	return x;
+}
+
+/*! reverse bit-order of each byte in a buffer
+ *  \param[in] buf buffer containing bytes to be bit-reversed
+ *  \param[in] len length of buffer in bytes
+ *
+ *  This function reverses the bits in each byte of the buffer
+ */
+void osmo_revbytebits_buf(uint8_t *buf, int len)
+{
+	unsigned int i;
+	unsigned int unaligned_cnt;
+	int len_remain = len;
+
+	unaligned_cnt = ((unsigned long)buf & 3);
+	for (i = 0; i < unaligned_cnt; i++) {
+		buf[i] = osmo_revbytebits_8(buf[i]);
+		len_remain--;
+		if (len_remain <= 0)
+			return;
+	}
+
+	for (i = unaligned_cnt; i + 3 < len; i += 4) {
+		osmo_store32be(osmo_revbytebits_32(osmo_load32be(buf + i)), buf + i);
+		len_remain -= 4;
+	}
+
+	for (i = len - len_remain; i < len; i++) {
+		buf[i] = osmo_revbytebits_8(buf[i]);
+		len_remain--;
+	}
+}
+
+/*! @} */
diff --git a/lib/decoding/osmocom/core/bits.h b/lib/decoding/osmocom/core/bits.h
new file mode 100644
index 0000000..b1b8040
--- /dev/null
+++ b/lib/decoding/osmocom/core/bits.h
@@ -0,0 +1,122 @@
+/*! \file bits.h
+ *  Osmocom bit level support code.
+ *
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <stddef.h>
+
+#include <osmocom/core/bit16gen.h>
+#include <osmocom/core/bit32gen.h>
+#include <osmocom/core/bit64gen.h>
+
+/*! \defgroup bits soft, unpacked and packed bits
+ *  @{
+ * \file bits.h */
+
+/*! soft bit with value (-127...127), as commonly used in
+ * communications receivers such as [viterbi] decoders */
+typedef int8_t  sbit_t;
+
+/*! unpacked bit (0 or 1): 1 bit per byte */
+typedef uint8_t ubit_t;
+
+/*! packed bits (8 bits in a byte).
+ *  NOTE on the endian-ness of \ref pbit_t:
+ *  - Bits in a \ref pbit_t are ordered MSB first, i.e. 0x80 is the first bit.
+ *  - Bit i in a \ref pbit_t array is array[i/8] & (1<<(7-i%8)) */
+typedef uint8_t pbit_t;
+
+/*! determine how many bytes we would need for \a num_bits packed bits
+ *  \param[in] num_bits Number of packed bits
+ *  \returns number of bytes needed for \a num_bits packed bits
+ */
+static inline unsigned int osmo_pbit_bytesize(unsigned int num_bits)
+{
+	unsigned int pbit_bytesize = num_bits / 8;
+
+	if (num_bits % 8)
+		pbit_bytesize++;
+
+	return pbit_bytesize;
+}
+
+int osmo_ubit2pbit(pbit_t *out, const ubit_t *in, unsigned int num_bits);
+
+int osmo_pbit2ubit(ubit_t *out, const pbit_t *in, unsigned int num_bits);
+
+void osmo_nibble_shift_right(uint8_t *out, const uint8_t *in,
+			     unsigned int num_nibbles);
+void osmo_nibble_shift_left_unal(uint8_t *out, const uint8_t *in,
+				 unsigned int num_nibbles);
+
+void osmo_ubit2sbit(sbit_t *out, const ubit_t *in, unsigned int num_bits);
+void osmo_sbit2ubit(ubit_t *out, const sbit_t *in, unsigned int num_bits);
+
+int osmo_ubit2pbit_ext(pbit_t *out, unsigned int out_ofs,
+                       const ubit_t *in, unsigned int in_ofs,
+                       unsigned int num_bits, int lsb_mode);
+
+int osmo_pbit2ubit_ext(ubit_t *out, unsigned int out_ofs,
+                       const pbit_t *in, unsigned int in_ofs,
+                       unsigned int num_bits, int lsb_mode);
+
+#define OSMO_BIN_SPEC "%d%d%d%d%d%d%d%d"
+#define OSMO_BIN_PRINT(byte)  \
+  (byte & 0x80 ? 1 : 0), \
+  (byte & 0x40 ? 1 : 0), \
+  (byte & 0x20 ? 1 : 0), \
+  (byte & 0x10 ? 1 : 0), \
+  (byte & 0x08 ? 1 : 0), \
+  (byte & 0x04 ? 1 : 0), \
+  (byte & 0x02 ? 1 : 0), \
+  (byte & 0x01 ? 1 : 0)
+
+#define OSMO_BIT_SPEC "%c%c%c%c%c%c%c%c"
+#define OSMO_BIT_PRINT_EX(byte, ch)		\
+  (byte & 0x80 ? ch : '.'), \
+  (byte & 0x40 ? ch : '.'), \
+  (byte & 0x20 ? ch : '.'), \
+  (byte & 0x10 ? ch : '.'), \
+  (byte & 0x08 ? ch : '.'), \
+  (byte & 0x04 ? ch : '.'), \
+  (byte & 0x02 ? ch : '.'), \
+  (byte & 0x01 ? ch : '.')
+
+#define OSMO_BIT_PRINT(byte)  OSMO_BIT_PRINT_EX(byte, '1')
+
+/* BIT REVERSAL */
+
+/*! bit-reversal mode for osmo_bit_reversal() */
+enum osmo_br_mode {
+	/*! reverse all bits in a 32bit dword */
+	OSMO_BR_BITS_IN_DWORD	= 31,
+	/*! reverse byte order in a 32bit dword */
+	OSMO_BR_BYTES_IN_DWORD	= 24,
+	/*! reverse bits of each byte in a 32bit dword */
+	OSMO_BR_BITS_IN_BYTE	= 7,
+	/*! swap the two 16bit words in a 32bit dword */
+	OSMO_BR_WORD_SWAP	= 16,
+};
+
+uint32_t osmo_bit_reversal(uint32_t x, enum osmo_br_mode k);
+
+uint32_t osmo_revbytebits_32(uint32_t x);
+
+uint32_t osmo_revbytebits_8(uint8_t x);
+
+void osmo_revbytebits_buf(uint8_t *buf, int len);
+
+/*! left circular shift
+ *  \param[in] in The 16 bit unsigned integer to be rotated
+ *  \param[in] shift Number of bits to shift \a in to, [0;16] bits
+ *  \returns shifted value
+ */
+static inline uint16_t osmo_rol16(uint16_t in, unsigned shift)
+{
+	return (in << shift) | (in >> (16 - shift));
+}
+
+/*! @} */
diff --git a/lib/decoding/osmocom/core/bitvec.c b/lib/decoding/osmocom/core/bitvec.c
new file mode 100644
index 0000000..414c719
--- /dev/null
+++ b/lib/decoding/osmocom/core/bitvec.c
@@ -0,0 +1,708 @@
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2012 Ivan Klyuchnikov
+ * (C) 2015 by sysmocom - s.f.m.c. GmbH
+ *
+ * All Rights Reserved
+ *
+ * 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.
+ *
+ */
+
+/*! \addtogroup bitvec
+ *  @{
+ *  Osmocom bit vector abstraction utility routines.
+ *
+ *  These functions assume a MSB (most significant bit) first layout of the
+ *  bits, so that for instance the 5 bit number abcde (a is MSB) can be
+ *  embedded into a byte sequence like in xxxxxxab cdexxxxx. The bit count
+ *  starts with the MSB, so the bits in a byte are numbered (MSB) 01234567 (LSB).
+ *  Note that there are other incompatible encodings, like it is used
+ *  for the EGPRS RLC data block headers (there the bits are numbered from LSB
+ *  to MSB).
+ *
+ * \file bitvec.c */
+
+#include <errno.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdbool.h>
+
+#include <osmocom/core/bits.h>
+#include <osmocom/core/bitvec.h>
+
+#define BITNUM_FROM_COMP(byte, bit)	((byte*8)+bit)
+
+static inline unsigned int bytenum_from_bitnum(unsigned int bitnum)
+{
+	unsigned int bytenum = bitnum / 8;
+
+	return bytenum;
+}
+
+/* convert ZERO/ONE/L/H to a bitmask at given pos in a byte */
+static uint8_t bitval2mask(enum bit_value bit, uint8_t bitnum)
+{
+	int bitval;
+
+	switch (bit) {
+	case ZERO:
+		bitval = (0 << bitnum);
+		break;
+	case ONE:
+		bitval = (1 << bitnum);
+		break;
+	case L:
+		bitval = ((0x2b ^ (0 << bitnum)) & (1 << bitnum));
+		break;
+	case H:
+		bitval = ((0x2b ^ (1 << bitnum)) & (1 << bitnum));
+		break;
+	default:
+		return 0;
+	}
+	return bitval;
+}
+
+/*! check if the bit is 0 or 1 for a given position inside a bitvec
+ *  \param[in] bv the bit vector on which to check
+ *  \param[in] bitnr the bit number inside the bit vector to check
+ *  \return value of the requested bit
+ */
+enum bit_value bitvec_get_bit_pos(const struct bitvec *bv, unsigned int bitnr)
+{
+	unsigned int bytenum = bytenum_from_bitnum(bitnr);
+	unsigned int bitnum = 7 - (bitnr % 8);
+	uint8_t bitval;
+
+	if (bytenum >= bv->data_len)
+		return -EINVAL;
+
+	bitval = bitval2mask(ONE, bitnum);
+
+	if (bv->data[bytenum] & bitval)
+		return ONE;
+
+	return ZERO;
+}
+
+/*! check if the bit is L or H for a given position inside a bitvec
+ *  \param[in] bv the bit vector on which to check
+ *  \param[in] bitnr the bit number inside the bit vector to check
+ *  \return value of the requested bit
+ */
+enum bit_value bitvec_get_bit_pos_high(const struct bitvec *bv,
+					unsigned int bitnr)
+{
+	unsigned int bytenum = bytenum_from_bitnum(bitnr);
+	unsigned int bitnum = 7 - (bitnr % 8);
+	uint8_t bitval;
+
+	if (bytenum >= bv->data_len)
+		return -EINVAL;
+
+	bitval = bitval2mask(H, bitnum);
+
+	if ((bv->data[bytenum] & (1 << bitnum)) == bitval)
+		return H;
+
+	return L;
+}
+
+/*! get the Nth set bit inside the bit vector
+ *  \param[in] bv the bit vector to use
+ *  \param[in] n the bit number to get
+ *  \returns the bit number (offset) of the Nth set bit in \a bv
+ */
+unsigned int bitvec_get_nth_set_bit(const struct bitvec *bv, unsigned int n)
+{
+	unsigned int i, k = 0;
+
+	for (i = 0; i < bv->data_len*8; i++) {
+		if (bitvec_get_bit_pos(bv, i) == ONE) {
+			k++;
+			if (k == n)
+				return i;
+		}
+	}
+
+	return 0;
+}
+
+/*! set a bit at given position in a bit vector
+ *  \param[in] bv bit vector on which to operate
+ *  \param[in] bitnr number of bit to be set
+ *  \param[in] bit value to which the bit is to be set
+ *  \returns 0 on success, negative value on error
+ */
+inline int bitvec_set_bit_pos(struct bitvec *bv, unsigned int bitnr,
+			enum bit_value bit)
+{
+	unsigned int bytenum = bytenum_from_bitnum(bitnr);
+	unsigned int bitnum = 7 - (bitnr % 8);
+	uint8_t bitval;
+
+	if (bytenum >= bv->data_len)
+		return -EINVAL;
+
+	/* first clear the bit */
+	bitval = bitval2mask(ONE, bitnum);
+	bv->data[bytenum] &= ~bitval;
+
+	/* then set it to desired value */
+	bitval = bitval2mask(bit, bitnum);
+	bv->data[bytenum] |= bitval;
+
+	return 0;
+}
+
+/*! set the next bit inside a bitvec
+ *  \param[in] bv bit vector to be used
+ *  \param[in] bit value of the bit to be set
+ *  \returns 0 on success, negative value on error
+ */
+inline int bitvec_set_bit(struct bitvec *bv, enum bit_value bit)
+{
+	int rc;
+
+	rc = bitvec_set_bit_pos(bv, bv->cur_bit, bit);
+	if (!rc)
+		bv->cur_bit++;
+
+	return rc;
+}
+
+/*! get the next bit (low/high) inside a bitvec
+ *  \return value of th next bit in the vector */
+int bitvec_get_bit_high(struct bitvec *bv)
+{
+	int rc;
+
+	rc = bitvec_get_bit_pos_high(bv, bv->cur_bit);
+	if (rc >= 0)
+		bv->cur_bit++;
+
+	return rc;
+}
+
+/*! set multiple bits (based on array of bitvals) at current pos
+ *  \param[in] bv bit vector
+ *  \param[in] bits array of \ref bit_value
+ *  \param[in] count number of bits to set
+ *  \return 0 on success; negative in case of error */
+int bitvec_set_bits(struct bitvec *bv, const enum bit_value *bits, unsigned int count)
+{
+	int i, rc;
+
+	for (i = 0; i < count; i++) {
+		rc = bitvec_set_bit(bv, bits[i]);
+		if (rc)
+			return rc;
+	}
+
+	return 0;
+}
+
+/*! set multiple bits (based on numeric value) at current pos.
+ *  \param[in] bv bit vector.
+ *  \param[in] v mask representing which bits needs to be set.
+ *  \param[in] num_bits number of meaningful bits in the mask.
+ *  \param[in] use_lh whether to interpret the bits as L/H values or as 0/1.
+ *  \return 0 on success; negative in case of error. */
+int bitvec_set_u64(struct bitvec *bv, uint64_t v, uint8_t num_bits, bool use_lh)
+{
+	uint8_t i;
+
+	if (num_bits > 64)
+		return -E2BIG;
+
+	for (i = 0; i < num_bits; i++) {
+		int rc;
+		enum bit_value bit = use_lh ? L : 0;
+
+		if (v & ((uint64_t)1 << (num_bits - i - 1)))
+			bit = use_lh ? H : 1;
+
+		rc = bitvec_set_bit(bv, bit);
+		if (rc != 0)
+			return rc;
+	}
+
+	return 0;
+}
+
+/*! set multiple bits (based on numeric value) at current pos.
+ *  \return 0 in case of success; negative in case of error. */
+int bitvec_set_uint(struct bitvec *bv, unsigned int ui, unsigned int num_bits)
+{
+	return bitvec_set_u64(bv, ui, num_bits, false);
+}
+
+/*! get multiple bits (num_bits) from beginning of vector (MSB side)
+ *  \return 16bit signed integer retrieved from bit vector */
+int16_t bitvec_get_int16_msb(const struct bitvec *bv, unsigned int num_bits)
+{
+	if (num_bits > 15 || bv->cur_bit < num_bits)
+		return -EINVAL;
+
+	if (num_bits < 9)
+		return bv->data[0] >> (8 - num_bits);
+
+	return osmo_load16be(bv->data) >> (16 - num_bits);
+}
+
+/*! get multiple bits (based on numeric value) from current pos
+ *  \return integer value retrieved from bit vector */
+int bitvec_get_uint(struct bitvec *bv, unsigned int num_bits)
+{
+	int i;
+	unsigned int ui = 0;
+
+	for (i = 0; i < num_bits; i++) {
+		int bit = bitvec_get_bit_pos(bv, bv->cur_bit);
+		if (bit < 0)
+			return bit;
+		if (bit)
+			ui |= (1 << (num_bits - i - 1));
+		bv->cur_bit++;
+	}
+
+	return ui;
+}
+
+/*! fill num_bits with \fill starting from the current position
+ *  \return 0 on success; negative otherwise (out of vector boundary)
+ */
+int bitvec_fill(struct bitvec *bv, unsigned int num_bits, enum bit_value fill)
+{
+	unsigned i, stop = bv->cur_bit + num_bits;
+	for (i = bv->cur_bit; i < stop; i++)
+		if (bitvec_set_bit(bv, fill) < 0)
+			return -EINVAL;
+
+	return 0;
+}
+
+/*! pad all remaining bits up to num_bits
+ *  \return 0 on success; negative otherwise */
+int bitvec_spare_padding(struct bitvec *bv, unsigned int up_to_bit)
+{
+	int n = up_to_bit - bv->cur_bit + 1;
+	if (n < 1)
+		return 0;
+
+	return bitvec_fill(bv, n, L);
+}
+
+/*! find first bit set in bit vector
+ *  \return 0 on success; negative otherwise */
+int bitvec_find_bit_pos(const struct bitvec *bv, unsigned int n,
+			enum bit_value val)
+{
+	unsigned int i;
+
+	for (i = n; i < bv->data_len*8; i++) {
+		if (bitvec_get_bit_pos(bv, i) == val)
+			return i;
+	}
+
+	return -1;
+}
+
+/*! get multiple bytes from current pos
+ *  Assumes MSB first encoding.
+ *  \param[in] bv bit vector
+ *  \param[in] bytes array
+ *  \param[in] count number of bytes to copy
+ *  \return 0 on success; negative otherwise
+ */
+int bitvec_get_bytes(struct bitvec *bv, uint8_t *bytes, unsigned int count)
+{
+	int byte_offs = bytenum_from_bitnum(bv->cur_bit);
+	int bit_offs = bv->cur_bit % 8;
+	uint8_t c, last_c;
+	int i;
+	uint8_t *src;
+
+	if (byte_offs + count + (bit_offs ? 1 : 0) > bv->data_len)
+		return -EINVAL;
+
+	if (bit_offs == 0) {
+		memcpy(bytes, bv->data + byte_offs, count);
+	} else {
+		src = bv->data + byte_offs;
+		last_c = *(src++);
+		for (i = count; i > 0; i--) {
+			c = *(src++);
+			*(bytes++) =
+				(last_c << bit_offs) |
+				(c >> (8 - bit_offs));
+			last_c = c;
+		}
+	}
+
+	bv->cur_bit += count * 8;
+	return 0;
+}
+
+/*! set multiple bytes at current pos
+ *  Assumes MSB first encoding.
+ *  \param[in] bv bit vector
+ *  \param[in] bytes array
+ *  \param[in] count number of bytes to copy
+ *  \return 0 on success; negative otherwise
+ */
+int bitvec_set_bytes(struct bitvec *bv, const uint8_t *bytes, unsigned int count)
+{
+	int byte_offs = bytenum_from_bitnum(bv->cur_bit);
+	int bit_offs = bv->cur_bit % 8;
+	uint8_t c, last_c;
+	int i;
+	uint8_t *dst;
+
+	if (byte_offs + count + (bit_offs ? 1 : 0) > bv->data_len)
+		return -EINVAL;
+
+	if (bit_offs == 0) {
+		memcpy(bv->data + byte_offs, bytes, count);
+	} else if (count > 0) {
+		dst = bv->data + byte_offs;
+		/* Get lower bits of first dst byte */
+		last_c = *dst >> (8 - bit_offs);
+		for (i = count; i > 0; i--) {
+			c = *(bytes++);
+			*(dst++) =
+				(last_c << (8 - bit_offs)) |
+				(c >> bit_offs);
+			last_c = c;
+		}
+		/* Overwrite lower bits of N+1 dst byte */
+		*dst = (*dst & ((1 << (8 - bit_offs)) - 1)) |
+			(last_c << (8 - bit_offs));
+	}
+
+	bv->cur_bit += count * 8;
+	return 0;
+}
+
+/*! Allocate a bit vector
+ *  \param[in] size Number of bits in the vector
+ *  \param[in] ctx Context from which to allocate
+ *  \return pointer to allocated vector; NULL in case of error /
+struct bitvec *bitvec_alloc(unsigned int size, TALLOC_CTX *ctx)
+{
+	struct bitvec *bv = talloc_zero(ctx, struct bitvec);
+	if (!bv)
+		return NULL;
+
+	bv->data = talloc_zero_array(bv, uint8_t, size);
+	if (!(bv->data)) {
+		talloc_free(bv);
+		return NULL;
+	}
+
+	bv->data_len = size;
+	bv->cur_bit = 0;
+	return bv;
+}
+
+/*! Free a bit vector (release its memory)
+ *  \param[in] bit vector to free *
+void bitvec_free(struct bitvec *bv)
+{
+	talloc_free(bv->data);
+	talloc_free(bv);
+}
+*/
+/*! Export a bit vector to a buffer
+ *  \param[in] bitvec (unpacked bits)
+ *  \param[out] buffer for the unpacked bits
+ *  \return number of bytes (= bits) copied */
+unsigned int bitvec_pack(const struct bitvec *bv, uint8_t *buffer)
+{
+	unsigned int i = 0;
+	for (i = 0; i < bv->data_len; i++)
+		buffer[i] = bv->data[i];
+
+	return i;
+}
+
+/*! Copy buffer of unpacked bits into bit vector
+ *  \param[in] buffer unpacked input bits
+ *  \param[out] bv unpacked bit vector
+ *  \return number of bytes (= bits) copied */
+unsigned int bitvec_unpack(struct bitvec *bv, const uint8_t *buffer)
+{
+	unsigned int i = 0;
+	for (i = 0; i < bv->data_len; i++)
+		bv->data[i] = buffer[i];
+
+	return i;
+}
+
+/*! read hexadecimap string into a bit vector
+ *  \param[in] src string containing hex digits
+ *  \param[out] bv unpacked bit vector
+ *  \return 0 in case of success; 1 in case of error
+ */
+int bitvec_unhex(struct bitvec *bv, const char *src)
+{
+	unsigned i;
+	unsigned val;
+	unsigned write_index = 0;
+	unsigned digits = bv->data_len * 2;
+
+	for (i = 0; i < digits; i++) {
+		if (sscanf(src + i, "%1x", &val) < 1) {
+			return 1;
+		}
+		bitvec_write_field(bv, &write_index, val, 4);
+	}
+	return 0;
+}
+
+/*! read part of the vector
+ *  \param[in] bv The boolean vector to work on
+ *  \param[in,out] read_index Where reading supposed to start in the vector
+ *  \param[in] len How many bits to read from vector
+ *  \returns read bits or negative value on error
+ */
+uint64_t bitvec_read_field(struct bitvec *bv, unsigned int *read_index, unsigned int len)
+{
+	unsigned int i;
+	uint64_t ui = 0;
+	bv->cur_bit = *read_index;
+
+	for (i = 0; i < len; i++) {
+		int bit = bitvec_get_bit_pos((const struct bitvec *)bv, bv->cur_bit);
+		if (bit < 0)
+			return bit;
+		if (bit)
+			ui |= ((uint64_t)1 << (len - i - 1));
+		bv->cur_bit++;
+	}
+	*read_index += len;
+	return ui;
+}
+
+/*! write into the vector
+ *  \param[in] bv The boolean vector to work on
+ *  \param[in,out] write_index Where writing supposed to start in the vector
+ *  \param[in] len How many bits to write
+ *  \returns next write index or negative value on error
+ */
+int bitvec_write_field(struct bitvec *bv, unsigned int *write_index, uint64_t val, unsigned int len)
+{
+	int rc;
+
+	bv->cur_bit = *write_index;
+
+	rc = bitvec_set_u64(bv, val, len, false);
+	if (rc != 0)
+		return rc;
+
+	*write_index += len;
+
+	return 0;
+}
+
+/*! convert enum to corresponding character
+ *  \param v input value (bit)
+ *  \return single character, either 0, 1, L or H */
+char bit_value_to_char(enum bit_value v)
+{
+	switch (v) {
+	case ZERO: return '0';
+	case ONE: return '1';
+	case L: return 'L';
+	case H: return 'H';
+	default: abort();
+	}
+}
+
+/*! prints bit vector to provided string
+ * It's caller's responsibility to ensure that we won't shoot him in the foot:
+ * the provided buffer should be at lest cur_bit + 1 bytes long
+ */
+void bitvec_to_string_r(const struct bitvec *bv, char *str)
+{
+	unsigned i, pos = 0;
+	char *cur = str;
+	for (i = 0; i < bv->cur_bit; i++) {
+		if (0 == i % 8)
+			*cur++ = ' ';
+		*cur++ = bit_value_to_char(bitvec_get_bit_pos(bv, i));
+		pos++;
+	}
+	*cur = 0;
+}
+
+/* we assume that x have at least 1 non-b bit */
+static inline unsigned leading_bits(uint8_t x, bool b)
+{
+	if (b) {
+		if (x < 0x80) return 0;
+		if (x < 0xC0) return 1;
+		if (x < 0xE0) return 2;
+		if (x < 0xF0) return 3;
+		if (x < 0xF8) return 4;
+		if (x < 0xFC) return 5;
+		if (x < 0xFE) return 6;
+	} else {
+		if (x > 0x7F) return 0;
+		if (x > 0x3F) return 1;
+		if (x > 0x1F) return 2;
+		if (x > 0xF) return 3;
+		if (x > 7) return 4;
+		if (x > 3) return 5;
+		if (x > 1) return 6;
+	}
+	return 7;
+}
+/*! force bit vector to all 0 and current bit to the beginnig of the vector */
+void bitvec_zero(struct bitvec *bv)
+{
+	bv->cur_bit = 0;
+	memset(bv->data, 0, bv->data_len);
+}
+
+/*! Return number (bits) of uninterrupted bit run in vector starting from the MSB
+ *  \param[in] bv The boolean vector to work on
+ *  \param[in] b The boolean, sequence of which is looked at from the vector start
+ *  \returns Number of consecutive bits of \p b in \p bv
+ */
+unsigned bitvec_rl(const struct bitvec *bv, bool b)
+{
+	unsigned i;
+	for (i = 0; i < (bv->cur_bit % 8 ? bv->cur_bit / 8 + 1 : bv->cur_bit / 8); i++) {
+		if ( (b ? 0xFF : 0) != bv->data[i])
+			return i * 8 + leading_bits(bv->data[i], b);
+	}
+
+	return bv->cur_bit;
+}
+
+/*! Return number (bits) of uninterrupted bit run in vector
+ *   starting from the current bit
+ *  \param[in] bv The boolean vector to work on
+ *  \param[in] b The boolean, sequence of 1's or 0's to be checked
+ *  \param[in] max_bits Total Number of Uncmopresed bits
+ *  \returns Number of consecutive bits of \p b in \p bv and cur_bit will
+ *  \go to cur_bit + number of consecutive bit
+ */
+unsigned bitvec_rl_curbit(struct bitvec *bv, bool b, int max_bits)
+{
+	unsigned i = 0;
+	unsigned j = 8;
+	int temp_res = 0;
+	int count = 0;
+	unsigned readIndex = bv->cur_bit;
+	unsigned remaining_bits = max_bits % 8;
+	unsigned remaining_bytes = max_bits / 8;
+	unsigned byte_mask = 0xFF;
+
+	if (readIndex % 8) {
+		for (j -= (readIndex % 8) ; j > 0 ; j--) {
+			if (readIndex < max_bits && bitvec_read_field(bv, &readIndex, 1) == b)
+				temp_res++;
+			else {
+				bv->cur_bit--;
+				return temp_res;
+			}
+		}
+	}
+	for (i = (readIndex / 8);
+			i < (remaining_bits ? remaining_bytes + 1 : remaining_bytes);
+			i++, count++) {
+		if ((b ? byte_mask : 0) != bv->data[i]) {
+			bv->cur_bit = (count * 8 +
+					leading_bits(bv->data[i], b) + readIndex);
+			return count * 8 +
+				leading_bits(bv->data[i], b) + temp_res;
+		}
+	}
+	bv->cur_bit = (temp_res + (count * 8)) + readIndex;
+	if (bv->cur_bit > max_bits)
+		bv->cur_bit = max_bits;
+	return (bv->cur_bit - readIndex + temp_res);
+}
+
+/*! Shifts bitvec to the left, n MSB bits lost */
+void bitvec_shiftl(struct bitvec *bv, unsigned n)
+{
+	if (0 == n)
+		return;
+	if (n >= bv->cur_bit) {
+		bitvec_zero(bv);
+		return;
+	}
+
+	memmove(bv->data, bv->data + n / 8, bv->data_len - n / 8);
+
+	uint8_t tmp[2];
+	unsigned i;
+	for (i = 0; i < bv->data_len - 2; i++) {
+		uint16_t t = osmo_load16be(bv->data + i);
+		osmo_store16be(t << (n % 8), &tmp);
+		bv->data[i] = tmp[0];
+	}
+
+	bv->data[bv->data_len - 1] <<= (n % 8);
+	bv->cur_bit -= n;
+}
+
+/*! Add given array to bitvec
+ *  \param[in,out] bv bit vector to work with
+ *  \param[in] array elements to be added
+ *  \param[in] array_len length of array
+ *  \param[in] dry_run indicates whether to return number of bits required
+ *  instead of adding anything to bv for real
+ *  \param[in] num_bits number of bits to consider in each element of array
+ *  \returns number of bits necessary to add array elements if dry_run is true,
+ *  0 otherwise (only in this case bv is actually changed)
+ *
+ * N. B: no length checks are performed on bv - it's caller's job to ensure
+ * enough space is available - for example by calling with dry_run = true first.
+ *
+ * Useful for common pattern in CSN.1 spec which looks like:
+ * { 1 < XXX : bit (num_bits) > } ** 0
+ * which means repeat any times (between 0 and infinity),
+ * start each repetition with 1, mark end of repetitions with 0 bit
+ * see app. note in 3GPP TS 24.007 § B.2.1 Rule A2
+ */
+unsigned int bitvec_add_array(struct bitvec *bv, const uint32_t *array,
+			      unsigned int array_len, bool dry_run,
+			      unsigned int num_bits)
+{
+	unsigned i, bits = 1; /* account for stop bit */
+	for (i = 0; i < array_len; i++) {
+		if (dry_run) {
+			bits += (1 + num_bits);
+		} else {
+			bitvec_set_bit(bv, 1);
+			bitvec_set_uint(bv, array[i], num_bits);
+		}
+	}
+
+	if (dry_run)
+		return bits;
+
+	bitvec_set_bit(bv, 0); /* stop bit - end of the sequence */
+	return 0;
+}
+
+/*! @} */
diff --git a/lib/decoding/osmocom/core/bitvec.h b/lib/decoding/osmocom/core/bitvec.h
new file mode 100644
index 0000000..84db9a5
--- /dev/null
+++ b/lib/decoding/osmocom/core/bitvec.h
@@ -0,0 +1,87 @@
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2012 Ivan Klyuchnikov
+ * (C) 2015 sysmocom - s.f.m.c. GmbH
+ *
+ * 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.
+ *
+ */
+
+#pragma once
+
+/*! \defgroup bitvec Bit vectors
+ *  @{
+ * \file bitvec.h */
+
+#include <stdint.h>
+//#include <osmocom/core/talloc.h>
+#include <osmocom/core/defs.h>
+#include <stdbool.h>
+
+/*! A single GSM bit
+ *
+ * In GSM mac blocks, every bit can be 0 or 1, or L or H.  L/H are
+ * defined relative to the 0x2b padding pattern */
+enum bit_value {
+	ZERO	= 0, 	/*!< A zero (0) bit */
+	ONE	= 1,	/*!< A one (1) bit */
+	L	= 2,	/*!< A CSN.1 "L" bit */
+	H	= 3,	/*!< A CSN.1 "H" bit */
+};
+
+/*! structure describing a bit vector */
+struct bitvec {
+	unsigned int cur_bit;	/*!< cursor to the next unused bit */
+	unsigned int data_len;	/*!< length of data array in bytes */
+	uint8_t *data;		/*!< pointer to data array */
+};
+
+enum bit_value bitvec_get_bit_pos(const struct bitvec *bv, unsigned int bitnr);
+enum bit_value bitvec_get_bit_pos_high(const struct bitvec *bv,
+					unsigned int bitnr);
+unsigned int bitvec_get_nth_set_bit(const struct bitvec *bv, unsigned int n);
+int bitvec_set_bit_pos(struct bitvec *bv, unsigned int bitnum,
+			enum bit_value bit);
+int bitvec_set_bit(struct bitvec *bv, enum bit_value bit);
+int bitvec_get_bit_high(struct bitvec *bv);
+int bitvec_set_bits(struct bitvec *bv, const enum bit_value *bits, unsigned int count);
+int bitvec_set_u64(struct bitvec *bv, uint64_t v, uint8_t num_bits, bool use_lh);
+int bitvec_set_uint(struct bitvec *bv, unsigned int in, unsigned int count);
+int bitvec_get_uint(struct bitvec *bv, unsigned int num_bits);
+int bitvec_find_bit_pos(const struct bitvec *bv, unsigned int n, enum bit_value val);
+int bitvec_spare_padding(struct bitvec *bv, unsigned int up_to_bit);
+int bitvec_get_bytes(struct bitvec *bv, uint8_t *bytes, unsigned int count);
+int bitvec_set_bytes(struct bitvec *bv, const uint8_t *bytes, unsigned int count);
+/*struct bitvec *bitvec_alloc(unsigned int size, TALLOC_CTX *bvctx);*/
+/*void bitvec_free(struct bitvec *bv);*/
+int bitvec_unhex(struct bitvec *bv, const char *src);
+unsigned int bitvec_pack(const struct bitvec *bv, uint8_t *buffer);
+unsigned int bitvec_unpack(struct bitvec *bv, const uint8_t *buffer);
+uint64_t bitvec_read_field(struct bitvec *bv, unsigned int *read_index, unsigned int len);
+int bitvec_write_field(struct bitvec *bv, unsigned int *write_index, uint64_t val, unsigned int len);
+int bitvec_fill(struct bitvec *bv, unsigned int num_bits, enum bit_value fill);
+char bit_value_to_char(enum bit_value v);
+void bitvec_to_string_r(const struct bitvec *bv, char *str);
+void bitvec_zero(struct bitvec *bv);
+unsigned bitvec_rl(const struct bitvec *bv, bool b);
+unsigned bitvec_rl_curbit(struct bitvec *bv, bool b, int max_bits);
+void bitvec_shiftl(struct bitvec *bv, unsigned int n);
+int16_t bitvec_get_int16_msb(const struct bitvec *bv, unsigned int num_bits);
+unsigned int bitvec_add_array(struct bitvec *bv, const uint32_t *array,
+			      unsigned int array_len, bool dry_run,
+			      unsigned int num_bits);
+
+/*! @} */
diff --git a/lib/decoding/osmocom/core/conv.c b/lib/decoding/osmocom/core/conv.c
new file mode 100644
index 0000000..e60ce35
--- /dev/null
+++ b/lib/decoding/osmocom/core/conv.c
@@ -0,0 +1,644 @@
+/*! \file conv.c
+ * Generic convolutional encoding / decoding. */
+/*
+ * Copyright (C) 2011  Sylvain Munaut <tnt@246tNt.com>
+ *
+ * All Rights Reserved
+ *
+ * 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.
+ */
+
+/*! \addtogroup conv
+ *  @{
+ *  Osmocom convolutional encoder and decoder.
+ *
+ * \file conv.c */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef HAVE_ALLOCA_H
+#include <alloca.h>
+#endif
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <osmocom/core/bits.h>
+#include <osmocom/core/conv.h>
+
+
+/* ------------------------------------------------------------------------ */
+/* Common                                                                   */
+/* ------------------------------------------------------------------------ */
+
+int
+osmo_conv_get_input_length(const struct osmo_conv_code *code, int len)
+{
+	return len <= 0 ? code->len : len;
+}
+
+int
+osmo_conv_get_output_length(const struct osmo_conv_code *code, int len)
+{
+	int pbits, in_len, out_len;
+
+	/* Input length */
+	in_len = osmo_conv_get_input_length(code, len);
+
+	/* Output length */
+	out_len = in_len * code->N;
+
+	if (code->term == CONV_TERM_FLUSH)
+		out_len += code->N * (code->K - 1);
+
+	/* Count punctured bits */
+	if (code->puncture) {
+		for (pbits=0; code->puncture[pbits] >= 0; pbits++);
+		out_len -= pbits;
+	}
+
+	return out_len;
+}
+
+
+/* ------------------------------------------------------------------------ */
+/* Encoding                                                                 */
+/* ------------------------------------------------------------------------ */
+
+/*! Initialize a convolutional encoder
+ *  \param[in,out] encoder Encoder state to initialize
+ *  \param[in] code Description of convolutional code
+ */
+void
+osmo_conv_encode_init(struct osmo_conv_encoder *encoder,
+                      const struct osmo_conv_code *code)
+{
+	memset(encoder, 0x00, sizeof(struct osmo_conv_encoder));
+	encoder->code = code;
+}
+
+void
+osmo_conv_encode_load_state(struct osmo_conv_encoder *encoder,
+                            const ubit_t *input)
+{
+	int i;
+	uint8_t state = 0;
+
+	for (i=0; i<(encoder->code->K-1); i++)
+		state = (state << 1) | input[i];
+
+	encoder->state = state;
+}
+
+static inline int
+_conv_encode_do_output(struct osmo_conv_encoder *encoder,
+                       uint8_t out, ubit_t *output)
+{
+	const struct osmo_conv_code *code = encoder->code;
+	int o_idx = 0;
+	int j;
+
+	if (code->puncture) {
+		for (j=0; j<code->N; j++)
+		{
+			int bit_no = code->N - j - 1;
+			int r_idx = encoder->i_idx * code->N + j;
+
+			if (code->puncture[encoder->p_idx] == r_idx)
+				encoder->p_idx++;
+			else
+				output[o_idx++] = (out >> bit_no) & 1;
+		}
+	} else {
+		for (j=0; j<code->N; j++)
+		{
+			int bit_no = code->N - j - 1;
+			output[o_idx++] = (out >> bit_no) & 1;
+		}
+	}
+
+	return o_idx;
+}
+
+int
+osmo_conv_encode_raw(struct osmo_conv_encoder *encoder,
+                     const ubit_t *input, ubit_t *output, int n)
+{
+	const struct osmo_conv_code *code = encoder->code;
+	uint8_t state;
+	int i;
+	int o_idx;
+
+	o_idx = 0;
+	state = encoder->state;
+
+	for (i=0; i<n; i++) {
+		int bit = input[i];
+		uint8_t out;
+
+		out   = code->next_output[state][bit];
+		state = code->next_state[state][bit];
+
+		o_idx += _conv_encode_do_output(encoder, out, &output[o_idx]);
+
+		encoder->i_idx++;
+	}
+
+	encoder->state = state;
+
+	return o_idx;
+}
+
+int
+osmo_conv_encode_flush(struct osmo_conv_encoder *encoder,
+                       ubit_t *output)
+{
+	const struct osmo_conv_code *code = encoder->code;
+	uint8_t state;
+	int n;
+	int i;
+	int o_idx;
+
+	n = code->K - 1;
+
+	o_idx = 0;
+	state = encoder->state;
+
+	for (i=0; i<n; i++) {
+		uint8_t out;
+
+		if (code->next_term_output) {
+			out   = code->next_term_output[state];
+			state = code->next_term_state[state];
+		} else {
+			out   = code->next_output[state][0];
+			state = code->next_state[state][0];
+		}
+
+		o_idx += _conv_encode_do_output(encoder, out, &output[o_idx]);
+
+		encoder->i_idx++;
+	}
+
+	encoder->state = state;
+
+	return o_idx;
+}
+
+/*! All-in-one convolutional encoding function
+ *  \param[in] code description of convolutional code to be used
+ *  \param[in] input array of unpacked bits (uncoded)
+ *  \param[out] output array of unpacked bits (encoded)
+ *  \return Number of produced output bits
+ *
+ * This is an all-in-one function, taking care of
+ * \ref osmo_conv_init, \ref osmo_conv_encode_load_state,
+ * \ref osmo_conv_encode_raw and \ref osmo_conv_encode_flush as needed.
+ */
+int
+osmo_conv_encode(const struct osmo_conv_code *code,
+                 const ubit_t *input, ubit_t *output)
+{
+	struct osmo_conv_encoder encoder;
+	int l;
+
+	osmo_conv_encode_init(&encoder, code);
+
+	if (code->term == CONV_TERM_TAIL_BITING) {
+		int eidx = code->len - code->K + 1;
+		osmo_conv_encode_load_state(&encoder, &input[eidx]);
+	}
+
+	l = osmo_conv_encode_raw(&encoder, input, output, code->len);
+
+	if (code->term == CONV_TERM_FLUSH)
+		l += osmo_conv_encode_flush(&encoder, &output[l]);
+
+	return l;
+}
+
+
+/* ------------------------------------------------------------------------ */
+/* Decoding (viterbi)                                                       */
+/* ------------------------------------------------------------------------ */
+
+#define MAX_AE 0x00ffffff
+
+/* Forward declaration for accerlated decoding with certain codes */
+int
+osmo_conv_decode_acc(const struct osmo_conv_code *code,
+                     const sbit_t *input, ubit_t *output);
+
+void
+osmo_conv_decode_init(struct osmo_conv_decoder *decoder,
+                      const struct osmo_conv_code *code, int len, int start_state)
+{
+	int n_states;
+
+	/* Init */
+	if (len <= 0)
+		len =  code->len;
+
+	n_states = 1 << (code->K - 1);
+
+	memset(decoder, 0x00, sizeof(struct osmo_conv_decoder));
+
+	decoder->code = code;
+	decoder->n_states = n_states;
+	decoder->len = len;
+
+	/* Allocate arrays */
+	decoder->ae      = malloc(sizeof(unsigned int) * n_states);
+	decoder->ae_next = malloc(sizeof(unsigned int) * n_states);
+
+	decoder->state_history = malloc(sizeof(uint8_t) * n_states * (len + decoder->code->K - 1));
+
+	/* Classic reset */
+	osmo_conv_decode_reset(decoder, start_state);
+}
+
+void
+osmo_conv_decode_reset(struct osmo_conv_decoder *decoder, int start_state)
+{
+	int i;
+
+	/* Reset indexes */
+	decoder->o_idx = 0;
+	decoder->p_idx = 0;
+
+	/* Initial error */
+	if (start_state < 0) {
+		/* All states possible */
+		memset(decoder->ae, 0x00, sizeof(unsigned int) * decoder->n_states);
+	} else {
+		/* Fixed start state */
+		for (i=0; i<decoder->n_states; i++) {
+			decoder->ae[i] = (i == start_state) ? 0 : MAX_AE;
+		}
+	}
+}
+
+void
+osmo_conv_decode_rewind(struct osmo_conv_decoder *decoder)
+{
+	int i;
+	unsigned int min_ae = MAX_AE;
+
+	/* Reset indexes */
+	decoder->o_idx = 0;
+	decoder->p_idx = 0;
+
+	/* Initial error normalize (remove constant) */
+	for (i=0; i<decoder->n_states; i++) {
+		if (decoder->ae[i] < min_ae)
+			min_ae = decoder->ae[i];
+	}
+
+	for (i=0; i<decoder->n_states; i++)
+		decoder->ae[i] -= min_ae;
+}
+
+void
+osmo_conv_decode_deinit(struct osmo_conv_decoder *decoder)
+{
+	free(decoder->ae);
+	free(decoder->ae_next);
+	free(decoder->state_history);
+
+	memset(decoder, 0x00, sizeof(struct osmo_conv_decoder));
+}
+
+int
+osmo_conv_decode_scan(struct osmo_conv_decoder *decoder,
+                      const sbit_t *input, int n)
+{
+	const struct osmo_conv_code *code = decoder->code;
+
+	int i, s, b, j;
+
+	int n_states;
+	unsigned int *ae;
+	unsigned int *ae_next;
+	uint8_t *state_history;
+	sbit_t *in_sym;
+
+	int i_idx, p_idx;
+
+	/* Prepare */
+	n_states = decoder->n_states;
+
+	ae      = decoder->ae;
+	ae_next = decoder->ae_next;
+	state_history = &decoder->state_history[n_states * decoder->o_idx];
+
+	in_sym  = malloc(sizeof(sbit_t) * code->N);
+
+	i_idx = 0;
+	p_idx = decoder->p_idx;
+
+	/* Scan the treillis */
+	for (i=0; i<n; i++)
+	{
+		/* Reset next accumulated error */
+		for (s=0; s<n_states; s++) {
+			ae_next[s] = MAX_AE;
+		}
+
+		/* Get input */
+		if (code->puncture) {
+			/* Hard way ... */
+			for (j=0; j<code->N; j++) {
+				int idx = ((decoder->o_idx + i) * code->N) + j;
+				if (idx == code->puncture[p_idx]) {
+					in_sym[j] = 0;	/* Undefined */
+					p_idx++;
+				} else {
+					in_sym[j] = input[i_idx];
+					i_idx++;
+				}
+			}
+		} else {
+			/* Easy, just copy N bits */
+			memcpy(in_sym, &input[i_idx], code->N);
+			i_idx += code->N;
+		}
+
+		/* Scan all state */
+		for (s=0; s<n_states; s++)
+		{
+			/* Scan possible input bits */
+			for (b=0; b<2; b++)
+			{
+				int nae, ov, e;
+				uint8_t m;
+
+				/* Next output and state */
+				uint8_t out   = code->next_output[s][b];
+				uint8_t state = code->next_state[s][b];
+
+				/* New error for this path */
+				nae = ae[s];			/* start from last error */
+				m = 1 << (code->N - 1);		/* mask for 'out' bit selection */
+
+				for (j=0; j<code->N; j++) {
+					int is = (int)in_sym[j];
+					if (is) {
+						ov = (out & m) ? -127 : 127; /* sbit_t value for it */
+						e = is - ov;                 /* raw error for this bit */
+						nae += (e * e) >> 9;         /* acc the squared/scaled value */
+					}
+					m >>= 1;                     /* next mask bit */
+				}
+
+				/* Is it survivor ? */
+				if (ae_next[state] > nae) {
+					ae_next[state] = nae;
+					state_history[(n_states * i) + state] = s;
+				}
+			}
+		}
+
+		/* Copy accumulated error */
+		memcpy(ae, ae_next, sizeof(unsigned int) * n_states);
+	}
+
+	/* Update decoder state */
+	decoder->p_idx = p_idx;
+	decoder->o_idx += n;
+
+	free(in_sym);
+	return i_idx;
+}
+
+int
+osmo_conv_decode_flush(struct osmo_conv_decoder *decoder,
+                       const sbit_t *input)
+{
+	const struct osmo_conv_code *code = decoder->code;
+
+	int i, s, j;
+
+	int n_states;
+	unsigned int *ae;
+	unsigned int *ae_next;
+	uint8_t *state_history;
+	sbit_t *in_sym;
+
+	int i_idx, p_idx;
+
+	/* Prepare */
+	n_states = decoder->n_states;
+
+	ae      = decoder->ae;
+	ae_next = decoder->ae_next;
+	state_history = &decoder->state_history[n_states * decoder->o_idx];
+
+	in_sym  = malloc(sizeof(sbit_t) * code->N);
+
+	i_idx = 0;
+	p_idx = decoder->p_idx;
+
+	/* Scan the treillis */
+	for (i=0; i<code->K-1; i++)
+	{
+		/* Reset next accumulated error */
+		for (s=0; s<n_states; s++) {
+			ae_next[s] = MAX_AE;
+		}
+
+		/* Get input */
+		if (code->puncture) {
+			/* Hard way ... */
+			for (j=0; j<code->N; j++) {
+				int idx = ((decoder->o_idx + i) * code->N) + j;
+				if (idx == code->puncture[p_idx]) {
+					in_sym[j] = 0;	/* Undefined */
+					p_idx++;
+				} else {
+					in_sym[j] = input[i_idx];
+					i_idx++;
+				}
+			}
+		} else {
+			/* Easy, just copy N bits */
+			memcpy(in_sym, &input[i_idx], code->N);
+			i_idx += code->N;
+		}
+
+		/* Scan all state */
+		for (s=0; s<n_states; s++)
+		{
+			int nae, ov, e;
+			uint8_t m;
+
+			/* Next output and state */
+			uint8_t out;
+			uint8_t state;
+
+			if (code->next_term_output) {
+				out   = code->next_term_output[s];
+				state = code->next_term_state[s];
+			} else {
+				out   = code->next_output[s][0];
+				state = code->next_state[s][0];
+			}
+
+			/* New error for this path */
+			nae = ae[s];			/* start from last error */
+			m = 1 << (code->N - 1);		/* mask for 'out' bit selection */
+
+			for (j=0; j<code->N; j++) {
+				int is = (int)in_sym[j];
+				if (is) {
+					ov = (out & m) ? -127 : 127; /* sbit_t value for it */
+					e = is - ov;                 /* raw error for this bit */
+					nae += (e * e) >> 9;         /* acc the squared/scaled value */
+				}
+				m >>= 1;                     /* next mask bit */
+			}
+
+			/* Is it survivor ? */
+			if (ae_next[state] > nae) {
+				ae_next[state] = nae;
+				state_history[(n_states * i) + state] = s;
+			}
+		}
+
+		/* Copy accumulated error */
+		memcpy(ae, ae_next, sizeof(unsigned int) * n_states);
+	}
+
+	/* Update decoder state */
+	decoder->p_idx = p_idx;
+	decoder->o_idx += code->K - 1;
+
+	free(in_sym);
+	return i_idx;
+}
+
+int
+osmo_conv_decode_get_output(struct osmo_conv_decoder *decoder,
+                            ubit_t *output, int has_flush, int end_state)
+{
+	const struct osmo_conv_code *code = decoder->code;
+
+	int min_ae;
+	uint8_t min_state, cur_state;
+	int i, s, n;
+
+	uint8_t *sh_ptr;
+
+	/* End state ? */
+	if (end_state < 0) {
+		/* Find state with least error */
+		min_ae = MAX_AE;
+		min_state = 0xff;
+
+		for (s=0; s<decoder->n_states; s++)
+		{
+			if (decoder->ae[s] < min_ae) {
+				min_ae = decoder->ae[s];
+				min_state = s;
+			}
+		}
+
+		if (min_state == 0xff)
+			return -1;
+	} else {
+		min_state = (uint8_t) end_state;
+		min_ae = decoder->ae[end_state];
+	}
+
+	/* Traceback */
+	cur_state = min_state;
+
+	n = decoder->o_idx;
+
+	sh_ptr = &decoder->state_history[decoder->n_states * (n-1)];
+
+		/* No output for the K-1 termination input bits */
+	if (has_flush) {
+		for (i=0; i<code->K-1; i++) {
+			cur_state = sh_ptr[cur_state];
+			sh_ptr -= decoder->n_states;
+		}
+		n -= code->K - 1;
+	}
+
+		/* Generate output backward */
+	for (i=n-1; i>=0; i--)
+	{
+		min_state = cur_state;
+		cur_state = sh_ptr[cur_state];
+
+		sh_ptr -= decoder->n_states;
+
+		if (code->next_state[cur_state][0] == min_state)
+			output[i] = 0;
+		else
+			output[i] = 1;
+	}
+
+	return min_ae;
+}
+
+/*! All-in-one convolutional decoding function
+ *  \param[in] code description of convolutional code to be used
+ *  \param[in] input array of soft bits (coded)
+ *  \param[out] output array of unpacked bits (decoded)
+ *
+ * This is an all-in-one function, taking care of
+ * \ref osmo_conv_decode_init, \ref osmo_conv_decode_scan,
+ * \ref osmo_conv_decode_flush, \ref osmo_conv_decode_get_output and
+ * \ref osmo_conv_decode_deinit.
+ */
+int
+osmo_conv_decode(const struct osmo_conv_code *code,
+                 const sbit_t *input, ubit_t *output)
+{
+	struct osmo_conv_decoder decoder;
+	int rv, l;
+
+	/* Use accelerated implementation for supported codes */
+	if ((code->N <= 4) && ((code->K == 5) || (code->K == 7)))
+		return osmo_conv_decode_acc(code, input, output);
+
+	osmo_conv_decode_init(&decoder, code, 0, 0);
+
+	if (code->term == CONV_TERM_TAIL_BITING) {
+		osmo_conv_decode_scan(&decoder, input, code->len);
+		osmo_conv_decode_rewind(&decoder);
+	}
+
+	l = osmo_conv_decode_scan(&decoder, input, code->len);
+
+	if (code->term == CONV_TERM_FLUSH)
+		osmo_conv_decode_flush(&decoder, &input[l]);
+
+	rv = osmo_conv_decode_get_output(&decoder, output,
+		code->term == CONV_TERM_FLUSH,		/* has_flush */
+		code->term == CONV_TERM_FLUSH ? 0 : -1	/* end_state */
+	);
+
+	osmo_conv_decode_deinit(&decoder);
+
+	return rv;
+}
+
+/*! @} */
diff --git a/lib/decoding/osmocom/core/conv.h b/lib/decoding/osmocom/core/conv.h
new file mode 100644
index 0000000..8b344f4
--- /dev/null
+++ b/lib/decoding/osmocom/core/conv.h
@@ -0,0 +1,139 @@
+/*! \file conv.h
+ * Osmocom convolutional encoder and decoder. */
+/*
+ * Copyright (C) 2011  Sylvain Munaut <tnt@246tNt.com>
+ *
+ * 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.
+ */
+
+/*! \defgroup conv Convolutional encoding and decoding routines
+ *  @{
+ * \file conv.h */
+
+#pragma once
+
+#include <stdint.h>
+
+#include <osmocom/core/bits.h>
+
+/*! possibe termination types
+ *
+ *  The termination type will determine which state the encoder/decoder
+ *  can start/end with. This is mostly taken care of in the high level API
+ *  call. So if you use the low level API, you must take care of making the
+ *  proper calls yourself.
+ */
+enum osmo_conv_term {
+	CONV_TERM_FLUSH = 0,	/*!< Flush encoder state */
+	CONV_TERM_TRUNCATION,	/*!< Direct truncation */
+	CONV_TERM_TAIL_BITING,	/*!< Tail biting */
+};
+
+/*! structure describing a given convolutional code
+ *
+ *  The only required fields are N,K and the next_output/next_state arrays. The
+ *  other can be left to default value of zero depending on what the code does.
+ *  If 'len' is left at 0 then only the low level API can be used.
+ */
+struct osmo_conv_code {
+	int N;				/*!< Inverse of code rate */
+	int K;				/*!< Constraint length */
+	int len;			/*!< # of data bits */
+
+	enum osmo_conv_term term;	/*!< Termination type */
+
+	const uint8_t (*next_output)[2];/*!< Next output array */
+	const uint8_t (*next_state)[2];	/*!< Next state array  */
+
+	const uint8_t *next_term_output;/*!< Flush termination output */
+	const uint8_t *next_term_state;	/*!< Flush termination state  */
+
+	const int *puncture;		/*!< Punctured bits indexes */
+};
+
+
+/* Common */
+
+int osmo_conv_get_input_length(const struct osmo_conv_code *code, int len);
+int osmo_conv_get_output_length(const struct osmo_conv_code *code, int len);
+
+
+/* Encoding */
+
+	/* Low level API */
+
+/*! convolutional encoder state */
+struct osmo_conv_encoder {
+	const struct osmo_conv_code *code; /*!< for which code? */
+	int i_idx;	/*!< Next input bit index */
+	int p_idx;	/*!< Current puncture index */
+	uint8_t state;	/*!< Current state */
+};
+
+void osmo_conv_encode_init(struct osmo_conv_encoder *encoder,
+                           const struct osmo_conv_code *code);
+void osmo_conv_encode_load_state(struct osmo_conv_encoder *encoder,
+                                 const ubit_t *input);
+int  osmo_conv_encode_raw(struct osmo_conv_encoder *encoder,
+                          const ubit_t *input, ubit_t *output, int n);
+int  osmo_conv_encode_flush(struct osmo_conv_encoder *encoder, ubit_t *output);
+
+	/* All-in-one */
+int  osmo_conv_encode(const struct osmo_conv_code *code,
+                      const ubit_t *input, ubit_t *output);
+
+
+/* Decoding */
+
+	/* Low level API */
+
+/*! convolutional decoder state */
+struct osmo_conv_decoder {
+	const struct osmo_conv_code *code; /*!< for which code? */
+
+	int n_states;		/*!< number of states */
+
+	int len;		/*!< Max o_idx (excl. termination) */
+
+	int o_idx;		/*!< output index */
+	int p_idx;		/*!< puncture index */
+
+	unsigned int *ae;	/*!< accumulated error */
+	unsigned int *ae_next;	/*!< next accumulated error (tmp in scan) */
+	uint8_t *state_history;	/*!< state history [len][n_states] */
+};
+
+void osmo_conv_decode_init(struct osmo_conv_decoder *decoder,
+                           const struct osmo_conv_code *code,
+                           int len, int start_state);
+void osmo_conv_decode_reset(struct osmo_conv_decoder *decoder, int start_state);
+void osmo_conv_decode_rewind(struct osmo_conv_decoder *decoder);
+void osmo_conv_decode_deinit(struct osmo_conv_decoder *decoder);
+
+int osmo_conv_decode_scan(struct osmo_conv_decoder *decoder,
+                          const sbit_t *input, int n);
+int osmo_conv_decode_flush(struct osmo_conv_decoder *decoder,
+                           const sbit_t *input);
+int osmo_conv_decode_get_output(struct osmo_conv_decoder *decoder,
+                                ubit_t *output, int has_flush, int end_state);
+
+	/* All-in-one */
+int osmo_conv_decode(const struct osmo_conv_code *code,
+                     const sbit_t *input, ubit_t *output);
+
+
+/*! @} */
diff --git a/lib/decoding/osmocom/core/conv_acc.c b/lib/decoding/osmocom/core/conv_acc.c
new file mode 100644
index 0000000..dce2682
--- /dev/null
+++ b/lib/decoding/osmocom/core/conv_acc.c
@@ -0,0 +1,720 @@
+/*! \file conv_acc.c
+ * Accelerated Viterbi decoder implementation. */
+/*
+ * Copyright (C) 2013, 2014 Thomas Tsou <tom@tsou.cc>
+ *
+ * All Rights Reserved
+ *
+ * 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 <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#define __attribute__(_arg_)
+
+#include <osmocom/core/conv.h>
+
+#define BIT2NRZ(REG,N)	(((REG >> N) & 0x01) * 2 - 1) * -1
+#define NUM_STATES(K)	(K == 7 ? 64 : 16)
+
+#define INIT_POINTERS(simd) \
+{ \
+	osmo_conv_metrics_k5_n2 = osmo_conv_##simd##_metrics_k5_n2; \
+	osmo_conv_metrics_k5_n3 = osmo_conv_##simd##_metrics_k5_n3; \
+	osmo_conv_metrics_k5_n4 = osmo_conv_##simd##_metrics_k5_n4; \
+	osmo_conv_metrics_k7_n2 = osmo_conv_##simd##_metrics_k7_n2; \
+	osmo_conv_metrics_k7_n3 = osmo_conv_##simd##_metrics_k7_n3; \
+	osmo_conv_metrics_k7_n4 = osmo_conv_##simd##_metrics_k7_n4; \
+	vdec_malloc = &osmo_conv_##simd##_vdec_malloc; \
+	vdec_free = &osmo_conv_##simd##_vdec_free; \
+}
+
+static int init_complete = 0;
+
+__attribute__ ((visibility("hidden"))) int avx2_supported = 0;
+__attribute__ ((visibility("hidden"))) int ssse3_supported = 0;
+__attribute__ ((visibility("hidden"))) int sse41_supported = 0;
+
+/**
+ * These pointers are being initialized at runtime by the
+ * osmo_conv_init() depending on supported SIMD extensions.
+ */
+static int16_t *(*vdec_malloc)(size_t n);
+static void (*vdec_free)(int16_t *ptr);
+
+void (*osmo_conv_metrics_k5_n2)(const int8_t *seq,
+	const int16_t *out, int16_t *sums, int16_t *paths, int norm);
+void (*osmo_conv_metrics_k5_n3)(const int8_t *seq,
+	const int16_t *out, int16_t *sums, int16_t *paths, int norm);
+void (*osmo_conv_metrics_k5_n4)(const int8_t *seq,
+	const int16_t *out, int16_t *sums, int16_t *paths, int norm);
+void (*osmo_conv_metrics_k7_n2)(const int8_t *seq,
+	const int16_t *out, int16_t *sums, int16_t *paths, int norm);
+void (*osmo_conv_metrics_k7_n3)(const int8_t *seq,
+	const int16_t *out, int16_t *sums, int16_t *paths, int norm);
+void (*osmo_conv_metrics_k7_n4)(const int8_t *seq,
+	const int16_t *out, int16_t *sums, int16_t *paths, int norm);
+
+/* Forward malloc wrappers */
+int16_t *osmo_conv_gen_vdec_malloc(size_t n);
+void osmo_conv_gen_vdec_free(int16_t *ptr);
+
+#if defined(HAVE_SSSE3)
+int16_t *osmo_conv_sse_vdec_malloc(size_t n);
+void osmo_conv_sse_vdec_free(int16_t *ptr);
+#endif
+
+#if defined(HAVE_SSSE3) && defined(HAVE_AVX2)
+int16_t *osmo_conv_sse_avx_vdec_malloc(size_t n);
+void osmo_conv_sse_avx_vdec_free(int16_t *ptr);
+#endif
+
+/* Forward Metric Units */
+void osmo_conv_gen_metrics_k5_n2(const int8_t *seq, const int16_t *out,
+	int16_t *sums, int16_t *paths, int norm);
+void osmo_conv_gen_metrics_k5_n3(const int8_t *seq, const int16_t *out,
+	int16_t *sums, int16_t *paths, int norm);
+void osmo_conv_gen_metrics_k5_n4(const int8_t *seq, const int16_t *out,
+	int16_t *sums, int16_t *paths, int norm);
+void osmo_conv_gen_metrics_k7_n2(const int8_t *seq, const int16_t *out,
+	int16_t *sums, int16_t *paths, int norm);
+void osmo_conv_gen_metrics_k7_n3(const int8_t *seq, const int16_t *out,
+	int16_t *sums, int16_t *paths, int norm);
+void osmo_conv_gen_metrics_k7_n4(const int8_t *seq, const int16_t *out,
+	int16_t *sums, int16_t *paths, int norm);
+
+#if defined(HAVE_SSSE3)
+void osmo_conv_sse_metrics_k5_n2(const int8_t *seq, const int16_t *out,
+	int16_t *sums, int16_t *paths, int norm);
+void osmo_conv_sse_metrics_k5_n3(const int8_t *seq, const int16_t *out,
+	int16_t *sums, int16_t *paths, int norm);
+void osmo_conv_sse_metrics_k5_n4(const int8_t *seq, const int16_t *out,
+	int16_t *sums, int16_t *paths, int norm);
+void osmo_conv_sse_metrics_k7_n2(const int8_t *seq, const int16_t *out,
+	int16_t *sums, int16_t *paths, int norm);
+void osmo_conv_sse_metrics_k7_n3(const int8_t *seq, const int16_t *out,
+	int16_t *sums, int16_t *paths, int norm);
+void osmo_conv_sse_metrics_k7_n4(const int8_t *seq, const int16_t *out,
+	int16_t *sums, int16_t *paths, int norm);
+#endif
+
+#if defined(HAVE_SSSE3) && defined(HAVE_AVX2)
+void osmo_conv_sse_avx_metrics_k5_n2(const int8_t *seq, const int16_t *out,
+	int16_t *sums, int16_t *paths, int norm);
+void osmo_conv_sse_avx_metrics_k5_n3(const int8_t *seq, const int16_t *out,
+	int16_t *sums, int16_t *paths, int norm);
+void osmo_conv_sse_avx_metrics_k5_n4(const int8_t *seq, const int16_t *out,
+	int16_t *sums, int16_t *paths, int norm);
+void osmo_conv_sse_avx_metrics_k7_n2(const int8_t *seq, const int16_t *out,
+	int16_t *sums, int16_t *paths, int norm);
+void osmo_conv_sse_avx_metrics_k7_n3(const int8_t *seq, const int16_t *out,
+	int16_t *sums, int16_t *paths, int norm);
+void osmo_conv_sse_avx_metrics_k7_n4(const int8_t *seq, const int16_t *out,
+	int16_t *sums, int16_t *paths, int norm);
+#endif
+
+/* Trellis State
+ * state - Internal lshift register value
+ * prev  - Register values of previous 0 and 1 states
+ */
+struct vstate {
+	unsigned state;
+	unsigned prev[2];
+};
+
+/* Trellis Object
+ * num_states - Number of states in the trellis
+ * sums       - Accumulated path metrics
+ * outputs    - Trellis output values
+ * vals       - Input value that led to each state
+ */
+struct vtrellis {
+	int num_states;
+	int16_t *sums;
+	int16_t *outputs;
+	uint8_t *vals;
+};
+
+/* Viterbi Decoder
+ * n         - Code order
+ * k         - Constraint length
+ * len       - Horizontal length of trellis
+ * recursive - Set to '1' if the code is recursive
+ * intrvl    - Normalization interval
+ * trellis   - Trellis object
+ * paths     - Trellis paths
+ */
+struct vdecoder {
+	int n;
+	int k;
+	int len;
+	int recursive;
+	int intrvl;
+	struct vtrellis trellis;
+	int16_t **paths;
+
+	void (*metric_func)(const int8_t *, const int16_t *,
+		int16_t *, int16_t *, int);
+};
+
+/* Accessor calls */
+static inline int conv_code_recursive(const struct osmo_conv_code *code)
+{
+	return code->next_term_output ? 1 : 0;
+}
+
+/* Left shift and mask for finding the previous state */
+static unsigned vstate_lshift(unsigned reg, int k, int val)
+{
+	unsigned mask;
+
+	if (k == 5)
+		mask = 0x0e;
+	else if (k == 7)
+		mask = 0x3e;
+	else
+		mask = 0;
+
+	return ((reg << 1) & mask) | val;
+}
+
+/* Bit endian manipulators */
+static inline unsigned bitswap2(unsigned v)
+{
+	return ((v & 0x02) >> 1) | ((v & 0x01) << 1);
+}
+
+static inline unsigned bitswap3(unsigned v)
+{
+	return ((v & 0x04) >> 2) | ((v & 0x02) >> 0) |
+		((v & 0x01) << 2);
+}
+
+static inline unsigned bitswap4(unsigned v)
+{
+	return ((v & 0x08) >> 3) | ((v & 0x04) >> 1) |
+		((v & 0x02) << 1) | ((v & 0x01) << 3);
+}
+
+static inline unsigned bitswap5(unsigned v)
+{
+	return ((v & 0x10) >> 4) | ((v & 0x08) >> 2) | ((v & 0x04) >> 0) |
+		((v & 0x02) << 2) | ((v & 0x01) << 4);
+}
+
+static inline unsigned bitswap6(unsigned v)
+{
+	return ((v & 0x20) >> 5) | ((v & 0x10) >> 3) | ((v & 0x08) >> 1) |
+		((v & 0x04) << 1) | ((v & 0x02) << 3) | ((v & 0x01) << 5);
+}
+
+static unsigned bitswap(unsigned v, unsigned n)
+{
+	switch (n) {
+	case 1:
+		return v;
+	case 2:
+		return bitswap2(v);
+	case 3:
+		return bitswap3(v);
+	case 4:
+		return bitswap4(v);
+	case 5:
+		return bitswap5(v);
+	case 6:
+		return bitswap6(v);
+	default:
+		return 0;
+	}
+}
+
+/* Generate non-recursive state output from generator state table
+ * Note that the shift register moves right (i.e. the most recent bit is
+ * shifted into the register at k-1 bit of the register), which is typical
+ * textbook representation. The API transition table expects the most recent
+ * bit in the low order bit, or left shift. A bitswap operation is required
+ * to accommodate the difference.
+ */
+static unsigned gen_output(struct vstate *state, int val,
+	const struct osmo_conv_code *code)
+{
+	unsigned out, prev;
+
+	prev = bitswap(state->prev[0], code->K - 1);
+	out = code->next_output[prev][val];
+	out = bitswap(out, code->N);
+
+	return out;
+}
+
+/* Populate non-recursive trellis state
+ * For a given state defined by the k-1 length shift register, find the
+ * value of the input bit that drove the trellis to that state. Also
+ * generate the N outputs of the generator polynomial at that state.
+ */
+static int gen_state_info(uint8_t *val, unsigned reg,
+	int16_t *output, const struct osmo_conv_code *code)
+{
+	int i;
+	unsigned out;
+	struct vstate state;
+
+	/* Previous '0' state */
+	state.state = reg;
+	state.prev[0] = vstate_lshift(reg, code->K, 0);
+	state.prev[1] = vstate_lshift(reg, code->K, 1);
+
+	*val = (reg >> (code->K - 2)) & 0x01;
+
+	/* Transition output */
+	out = gen_output(&state, *val, code);
+
+	/* Unpack to NRZ */
+	for (i = 0; i < code->N; i++)
+		output[i] = BIT2NRZ(out, i);
+
+	return 0;
+}
+
+/* Generate recursive state output from generator state table */
+static unsigned gen_recursive_output(struct vstate *state,
+	uint8_t *val, unsigned reg,
+	const struct osmo_conv_code *code, int pos)
+{
+	int val0, val1;
+	unsigned out, prev;
+
+	/* Previous '0' state */
+	prev = vstate_lshift(reg, code->K, 0);
+	prev = bitswap(prev, code->K - 1);
+
+	/* Input value */
+	val0 = (reg >> (code->K - 2)) & 0x01;
+	val1 = (code->next_term_output[prev] >> pos) & 0x01;
+	*val = val0 == val1 ? 0 : 1;
+
+	/* Wrapper for osmocom state access */
+	prev = bitswap(state->prev[0], code->K - 1);
+
+	/* Compute the transition output */
+	out = code->next_output[prev][*val];
+	out = bitswap(out, code->N);
+
+	return out;
+}
+
+/* Populate recursive trellis state
+ * The bit position of the systematic bit is not explicitly marked by the
+ * API, so it must be extracted from the generator table. Otherwise,
+ * populate the trellis similar to the non-recursive version.
+ * Non-systematic recursive codes are not supported.
+ */
+static int gen_recursive_state_info(uint8_t *val,
+	unsigned reg, int16_t *output, const struct osmo_conv_code *code)
+{
+	int i, j, pos = -1;
+	int ns = NUM_STATES(code->K);
+	unsigned out;
+	struct vstate state;
+
+	/* Previous '0' and '1' states */
+	state.state = reg;
+	state.prev[0] = vstate_lshift(reg, code->K, 0);
+	state.prev[1] = vstate_lshift(reg, code->K, 1);
+
+	/* Find recursive bit location */
+	for (i = 0; i < code->N; i++) {
+		for (j = 0; j < ns; j++) {
+			if ((code->next_output[j][0] >> i) & 0x01)
+				break;
+		}
+
+		if (j == ns) {
+			pos = i;
+			break;
+		}
+	}
+
+	/* Non-systematic recursive code not supported */
+	if (pos < 0)
+		return -EPROTO;
+
+	/* Transition output */
+	out = gen_recursive_output(&state, val, reg, code, pos);
+
+	/* Unpack to NRZ */
+	for (i = 0; i < code->N; i++)
+		output[i] = BIT2NRZ(out, i);
+
+	return 0;
+}
+
+/* Release the trellis */
+static void free_trellis(struct vtrellis *trellis)
+{
+	if (!trellis)
+		return;
+
+	vdec_free(trellis->outputs);
+	vdec_free(trellis->sums);
+	free(trellis->vals);
+}
+
+/* Initialize the trellis object
+ * Initialization consists of generating the outputs and output value of a
+ * given state. Due to trellis symmetry and anti-symmetry, only one of the
+ * transition paths is utilized by the butterfly operation in the forward
+ * recursion, so only one set of N outputs is required per state variable.
+ */
+static int generate_trellis(struct vdecoder *dec,
+	const struct osmo_conv_code *code)
+{
+	struct vtrellis *trellis = &dec->trellis;
+	int16_t *outputs;
+	int i, rc;
+
+	int ns = NUM_STATES(code->K);
+	int olen = (code->N == 2) ? 2 : 4;
+
+	trellis->num_states = ns;
+	trellis->sums =	vdec_malloc(ns);
+	trellis->outputs = vdec_malloc(ns * olen);
+	trellis->vals = (uint8_t *) malloc(ns * sizeof(uint8_t));
+
+	if (!trellis->sums || !trellis->outputs || !trellis->vals) {
+		rc = -ENOMEM;
+		goto fail;
+	}
+
+	/* Populate the trellis state objects */
+	for (i = 0; i < ns; i++) {
+		outputs = &trellis->outputs[olen * i];
+		if (dec->recursive) {
+			rc = gen_recursive_state_info(&trellis->vals[i],
+				i, outputs, code);
+		} else {
+			rc = gen_state_info(&trellis->vals[i],
+				i, outputs, code);
+		}
+
+		if (rc < 0)
+			goto fail;
+
+		/* Set accumulated path metrics to zero */
+		trellis->sums[i] = 0;
+	}
+
+	/**
+	 * For termination other than tail-biting, initialize the zero state
+	 * as the encoder starting state. Initialize with the maximum
+	 * accumulated sum at length equal to the constraint length.
+	 */
+	if (code->term != CONV_TERM_TAIL_BITING)
+		trellis->sums[0] = INT8_MAX * code->N * code->K;
+
+	return 0;
+
+fail:
+	free_trellis(trellis);
+	return rc;
+}
+
+static void _traceback(struct vdecoder *dec,
+	unsigned state, uint8_t *out, int len)
+{
+	int i;
+	unsigned path;
+
+	for (i = len - 1; i >= 0; i--) {
+		path = dec->paths[i][state] + 1;
+		out[i] = dec->trellis.vals[state];
+		state = vstate_lshift(state, dec->k, path);
+	}
+}
+
+static void _traceback_rec(struct vdecoder *dec,
+	unsigned state, uint8_t *out, int len)
+{
+	int i;
+	unsigned path;
+
+	for (i = len - 1; i >= 0; i--) {
+		path = dec->paths[i][state] + 1;
+		out[i] = path ^ dec->trellis.vals[state];
+		state = vstate_lshift(state, dec->k, path);
+	}
+}
+
+/* Traceback and generate decoded output
+ * Find the largest accumulated path metric at the final state except for
+ * the zero terminated case, where we assume the final state is always zero.
+ */
+static int traceback(struct vdecoder *dec, uint8_t *out, int term, int len)
+{
+	int i, sum, max = -1;
+	unsigned path, state = 0;
+
+	if (term != CONV_TERM_FLUSH) {
+		for (i = 0; i < dec->trellis.num_states; i++) {
+			sum = dec->trellis.sums[i];
+			if (sum > max) {
+				max = sum;
+				state = i;
+			}
+		}
+
+		if (max < 0)
+			return -EPROTO;
+	}
+
+	for (i = dec->len - 1; i >= len; i--) {
+		path = dec->paths[i][state] + 1;
+		state = vstate_lshift(state, dec->k, path);
+	}
+
+	if (dec->recursive)
+		_traceback_rec(dec, state, out, len);
+	else
+		_traceback(dec, state, out, len);
+
+	return 0;
+}
+
+/* Release decoder object */
+static void vdec_deinit(struct vdecoder *dec)
+{
+	if (!dec)
+		return;
+
+	free_trellis(&dec->trellis);
+
+	if (dec->paths != NULL) {
+		vdec_free(dec->paths[0]);
+		free(dec->paths);
+	}
+}
+
+/* Initialize decoder object with code specific params
+ * Subtract the constraint length K on the normalization interval to
+ * accommodate the initialization path metric at state zero.
+ */
+static int vdec_init(struct vdecoder *dec, const struct osmo_conv_code *code)
+{
+	int i, ns, rc;
+
+	ns = NUM_STATES(code->K);
+
+	dec->n = code->N;
+	dec->k = code->K;
+	dec->recursive = conv_code_recursive(code);
+	dec->intrvl = INT16_MAX / (dec->n * INT8_MAX) - dec->k;
+
+	if (dec->k == 5) {
+		switch (dec->n) {
+		case 2:
+			dec->metric_func = osmo_conv_metrics_k5_n2;
+			break;
+		case 3:
+			dec->metric_func = osmo_conv_metrics_k5_n3;
+			break;
+		case 4:
+			dec->metric_func = osmo_conv_metrics_k5_n4;
+			break;
+		default:
+			return -EINVAL;
+		}
+	} else if (dec->k == 7) {
+		switch (dec->n) {
+		case 2:
+			dec->metric_func = osmo_conv_metrics_k7_n2;
+			break;
+		case 3:
+			dec->metric_func = osmo_conv_metrics_k7_n3;
+			break;
+		case 4:
+			dec->metric_func = osmo_conv_metrics_k7_n4;
+			break;
+		default:
+			return -EINVAL;
+		}
+	} else {
+		return -EINVAL;
+	}
+
+	if (code->term == CONV_TERM_FLUSH)
+		dec->len = code->len + code->K - 1;
+	else
+		dec->len = code->len;
+
+	rc = generate_trellis(dec, code);
+	if (rc)
+		return rc;
+
+	dec->paths = (int16_t **) malloc(sizeof(int16_t *) * dec->len);
+	if (!dec->paths)
+		goto enomem;
+
+	dec->paths[0] = vdec_malloc(ns * dec->len);
+	if (!dec->paths[0])
+		goto enomem;
+
+	for (i = 1; i < dec->len; i++)
+		dec->paths[i] = &dec->paths[0][i * ns];
+
+	return 0;
+
+enomem:
+	vdec_deinit(dec);
+	return -ENOMEM;
+}
+
+/* Depuncture sequence with nagative value terminated puncturing matrix */
+static int depuncture(const int8_t *in, const int *punc, int8_t *out, int len)
+{
+	int i, n = 0, m = 0;
+
+	for (i = 0; i < len; i++) {
+		if (i == punc[n]) {
+			out[i] = 0;
+			n++;
+			continue;
+		}
+
+		out[i] = in[m++];
+	}
+
+	return 0;
+}
+
+/* Forward trellis recursion
+ * Generate branch metrics and path metrics with a combined function. Only
+ * accumulated path metric sums and path selections are stored. Normalize on
+ * the interval specified by the decoder.
+ */
+static void forward_traverse(struct vdecoder *dec, const int8_t *seq)
+{
+	int i;
+
+	for (i = 0; i < dec->len; i++) {
+		dec->metric_func(&seq[dec->n * i],
+			dec->trellis.outputs,
+			dec->trellis.sums,
+			dec->paths[i],
+			!(i % dec->intrvl));
+	}
+}
+
+/* Convolutional decode with a decoder object
+ * Initial puncturing run if necessary followed by the forward recursion.
+ * For tail-biting perform a second pass before running the backward
+ * traceback operation.
+ */
+static int conv_decode(struct vdecoder *dec, const int8_t *seq,
+	const int *punc, uint8_t *out, int len, int term)
+{
+	//int8_t depunc[dec->len * dec->n]; //!! this isn't portable, in strict C you can't use size of an array that is not known at compile time
+	int8_t * depunc = malloc(sizeof(int8_t)*dec->len * dec->n);
+
+	
+	if (punc) {
+		depuncture(seq, punc, depunc, dec->len * dec->n);
+		seq = depunc;
+	}
+
+	/* Propagate through the trellis with interval normalization */
+	forward_traverse(dec, seq);
+
+	if (term == CONV_TERM_TAIL_BITING)
+		forward_traverse(dec, seq);
+	
+	free(depunc);
+	return traceback(dec, out, term, len);
+}
+
+static void osmo_conv_init(void)
+{
+	init_complete = 1;
+
+#ifdef HAVE___BUILTIN_CPU_SUPPORTS
+	/* Detect CPU capabilities */
+	#ifdef HAVE_AVX2
+		avx2_supported = __builtin_cpu_supports("avx2");
+	#endif
+
+	#ifdef HAVE_SSSE3
+		ssse3_supported = __builtin_cpu_supports("ssse3");
+	#endif
+
+	#ifdef HAVE_SSE4_1
+		sse41_supported = __builtin_cpu_supports("sse4.1");
+	#endif
+#endif
+
+/**
+ * Usage of curly braces is mandatory,
+ * because we use multi-line define.
+ */
+#if defined(HAVE_SSSE3) && defined(HAVE_AVX2)
+	if (ssse3_supported && avx2_supported) {
+		INIT_POINTERS(sse_avx);
+	} else if (ssse3_supported) {
+		INIT_POINTERS(sse);
+	} else {
+		INIT_POINTERS(gen);
+	}
+#elif defined(HAVE_SSSE3)
+	if (ssse3_supported) {
+		INIT_POINTERS(sse);
+	} else {
+		INIT_POINTERS(gen);
+	}
+#else
+	INIT_POINTERS(gen);
+#endif
+}
+
+/* All-in-one Viterbi decoding  */
+int osmo_conv_decode_acc(const struct osmo_conv_code *code,
+	const sbit_t *input, ubit_t *output)
+{
+	int rc;
+	struct vdecoder dec;
+
+	if (!init_complete)
+		osmo_conv_init();
+
+	if ((code->N < 2) || (code->N > 4) || (code->len < 1) ||
+		((code->K != 5) && (code->K != 7)))
+		return -EINVAL;
+
+	rc = vdec_init(&dec, code);
+	if (rc)
+		return rc;
+
+	rc = conv_decode(&dec, input, code->puncture,
+		output, code->len, code->term);
+
+	vdec_deinit(&dec);
+
+	return rc;
+}
diff --git a/lib/decoding/osmocom/core/conv_acc_generic.c b/lib/decoding/osmocom/core/conv_acc_generic.c
new file mode 100644
index 0000000..7da0213
--- /dev/null
+++ b/lib/decoding/osmocom/core/conv_acc_generic.c
@@ -0,0 +1,213 @@
+/*! \file conv_acc_generic.c
+ * Accelerated Viterbi decoder implementation
+ * for generic architectures without SSE support. */
+/*
+ * Copyright (C) 2013, 2014 Thomas Tsou <tom@tsou.cc>
+ *
+ * All Rights Reserved
+ *
+ * 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 <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+
+#define __attribute__(_arg_)
+
+/* Add-Compare-Select (ACS-Butterfly)
+ * Compute 4 accumulated path metrics and 4 path selections. Note that path
+ * selections are store as -1 and 0 rather than 0 and 1. This is to match
+ * the output format of the SSE packed compare instruction 'pmaxuw'.
+ */
+
+static void acs_butterfly(int state, int num_states,
+	int16_t metric, int16_t *sum,
+	int16_t *new_sum, int16_t *path)
+{
+	int state0, state1;
+	int sum0, sum1, sum2, sum3;
+
+	state0 = *(sum + (2 * state + 0));
+	state1 = *(sum + (2 * state + 1));
+
+	sum0 = state0 + metric;
+	sum1 = state1 - metric;
+	sum2 = state0 - metric;
+	sum3 = state1 + metric;
+
+	if (sum0 >= sum1) {
+		*new_sum = sum0;
+		*path = -1;
+	} else {
+		*new_sum = sum1;
+		*path = 0;
+	}
+
+	if (sum2 >= sum3) {
+		*(new_sum + num_states / 2) = sum2;
+		*(path + num_states / 2) = -1;
+	} else {
+		*(new_sum + num_states / 2) = sum3;
+		*(path + num_states / 2) = 0;
+	}
+}
+
+/* Branch metrics unit N=2 */
+static void gen_branch_metrics_n2(int num_states, const int8_t *seq,
+	const int16_t *out, int16_t *metrics)
+{
+	int i;
+
+	for (i = 0; i < num_states / 2; i++) {
+		metrics[i] = seq[0] * out[2 * i + 0] +
+			seq[1] * out[2 * i + 1];
+	}
+}
+
+/* Branch metrics unit N=3 */
+static void gen_branch_metrics_n3(int num_states, const int8_t *seq,
+	const int16_t *out, int16_t *metrics)
+{
+	int i;
+
+	for (i = 0; i < num_states / 2; i++) {
+		metrics[i] = seq[0] * out[4 * i + 0] +
+			seq[1] * out[4 * i + 1] +
+			seq[2] * out[4 * i + 2];
+	}
+}
+
+/* Branch metrics unit N=4 */
+static void gen_branch_metrics_n4(int num_states, const int8_t *seq,
+	const int16_t *out, int16_t *metrics)
+{
+	int i;
+
+	for (i = 0; i < num_states / 2; i++) {
+		metrics[i] = seq[0] * out[4 * i + 0] +
+			seq[1] * out[4 * i + 1] +
+			seq[2] * out[4 * i + 2] +
+			seq[3] * out[4 * i + 3];
+	}
+}
+
+/* Path metric unit */
+static void gen_path_metrics(int num_states, int16_t *sums,
+	int16_t *metrics, int16_t *paths, int norm)
+{
+	int i;
+	int16_t min;
+	int16_t * new_sums = malloc(sizeof(int16_t)*num_states);
+
+	for (i = 0; i < num_states / 2; i++)
+		acs_butterfly(i, num_states, metrics[i],
+			sums, &new_sums[i], &paths[i]);
+
+	if (norm) {
+		min = new_sums[0];
+
+		for (i = 1; i < num_states; i++)
+			if (new_sums[i] < min)
+				min = new_sums[i];
+
+		for (i = 0; i < num_states; i++)
+			new_sums[i] -= min;
+	}
+	
+	free(new_sums);
+	memcpy(sums, new_sums, num_states * sizeof(int16_t));
+}
+
+/* Not-aligned Memory Allocator */
+__attribute__ ((visibility("hidden")))
+int16_t *osmo_conv_gen_vdec_malloc(size_t n)
+{
+	return (int16_t *) malloc(sizeof(int16_t) * n);
+}
+
+__attribute__ ((visibility("hidden")))
+void osmo_conv_gen_vdec_free(int16_t *ptr)
+{
+	free(ptr);
+}
+
+/* 16-state branch-path metrics units (K=5) */
+__attribute__ ((visibility("hidden")))
+void osmo_conv_gen_metrics_k5_n2(const int8_t *seq, const int16_t *out,
+	int16_t *sums, int16_t *paths, int norm)
+{
+	int16_t metrics[8];
+
+	gen_branch_metrics_n2(16, seq, out, metrics);
+	gen_path_metrics(16, sums, metrics, paths, norm);
+}
+
+__attribute__ ((visibility("hidden")))
+void osmo_conv_gen_metrics_k5_n3(const int8_t *seq, const int16_t *out,
+	int16_t *sums, int16_t *paths, int norm)
+{
+	int16_t metrics[8];
+
+	gen_branch_metrics_n3(16, seq, out, metrics);
+	gen_path_metrics(16, sums, metrics, paths, norm);
+
+}
+
+__attribute__ ((visibility("hidden")))
+void osmo_conv_gen_metrics_k5_n4(const int8_t *seq, const int16_t *out,
+	int16_t *sums, int16_t *paths, int norm)
+{
+	int16_t metrics[8];
+
+	gen_branch_metrics_n4(16, seq, out, metrics);
+	gen_path_metrics(16, sums, metrics, paths, norm);
+
+}
+
+/* 64-state branch-path metrics units (K=7) */
+__attribute__ ((visibility("hidden")))
+void osmo_conv_gen_metrics_k7_n2(const int8_t *seq, const int16_t *out,
+	int16_t *sums, int16_t *paths, int norm)
+{
+	int16_t metrics[32];
+
+	gen_branch_metrics_n2(64, seq, out, metrics);
+	gen_path_metrics(64, sums, metrics, paths, norm);
+
+}
+
+__attribute__ ((visibility("hidden")))
+void osmo_conv_gen_metrics_k7_n3(const int8_t *seq, const int16_t *out,
+	int16_t *sums, int16_t *paths, int norm)
+{
+	int16_t metrics[32];
+
+	gen_branch_metrics_n3(64, seq, out, metrics);
+	gen_path_metrics(64, sums, metrics, paths, norm);
+
+}
+
+__attribute__ ((visibility("hidden")))
+void osmo_conv_gen_metrics_k7_n4(const int8_t *seq, const int16_t *out,
+	int16_t *sums, int16_t *paths, int norm)
+{
+	int16_t metrics[32];
+
+	gen_branch_metrics_n4(64, seq, out, metrics);
+	gen_path_metrics(64, sums, metrics, paths, norm);
+}
diff --git a/lib/decoding/osmocom/core/crc16gen.c b/lib/decoding/osmocom/core/crc16gen.c
new file mode 100644
index 0000000..ea69d88
--- /dev/null
+++ b/lib/decoding/osmocom/core/crc16gen.c
@@ -0,0 +1,120 @@
+/*
+ * crc16gen.c
+ *
+ * Generic CRC routines (for max 16 bits poly)
+ *
+ * Copyright (C) 2011  Sylvain Munaut <tnt@246tNt.com>
+ *
+ * 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.
+ */
+
+/*! \addtogroup crcgen
+ *  @{
+ */
+
+/*! \file crc16gen.c
+ * Osmocom generic CRC routines (for max 16 bits poly)
+ */
+
+#include <stdint.h>
+
+#include <osmocom/core/bits.h>
+#include <osmocom/core/crc16gen.h>
+
+
+/*! \brief Compute the CRC value of a given array of hard-bits
+ *  \param[in] code The CRC code description to apply
+ *  \param[in] in Array of hard bits
+ *  \param[in] len Length of the array of hard bits
+ *  \returns The CRC value
+ */
+uint16_t
+osmo_crc16gen_compute_bits(const struct osmo_crc16gen_code *code,
+                           const ubit_t *in, int len)
+{
+	const uint16_t poly = code->poly;
+	uint16_t crc = code->init;
+	int i, n = code->bits-1;
+
+	for (i=0; i<len; i++) {
+		uint16_t bit = in[i] & 1;
+		crc ^= (bit << n);
+		if (crc & ((uint16_t)1 << n)) {
+			crc <<= 1;
+			crc ^= poly;
+		} else {
+			crc <<= 1;
+		}
+		crc &= ((uint16_t)1 << code->bits) - 1;
+	}
+
+	crc ^= code->remainder;
+
+	return crc;
+}
+
+
+/*! \brief Checks the CRC value of a given array of hard-bits
+ *  \param[in] code The CRC code description to apply
+ *  \param[in] in Array of hard bits
+ *  \param[in] len Length of the array of hard bits
+ *  \param[in] crc_bits Array of hard bits with the alleged CRC
+ *  \returns 0 if CRC matches. 1 in case of error.
+ *
+ * The crc_bits array must have a length of code->len
+ */
+int
+osmo_crc16gen_check_bits(const struct osmo_crc16gen_code *code,
+                         const ubit_t *in, int len, const ubit_t *crc_bits)
+{
+	uint16_t crc;
+	int i;
+
+	crc = osmo_crc16gen_compute_bits(code, in, len);
+
+	for (i=0; i<code->bits; i++)
+		if (crc_bits[i] ^ ((crc >> (code->bits-i-1)) & 1))
+			return 1;
+
+	return 0;
+}
+
+
+/*! \brief Computes and writes the CRC value of a given array of bits
+ *  \param[in] code The CRC code description to apply
+ *  \param[in] in Array of hard bits
+ *  \param[in] len Length of the array of hard bits
+ *  \param[in] crc_bits Array of hard bits to write the computed CRC to
+ *
+ * The crc_bits array must have a length of code->len
+ */
+void
+osmo_crc16gen_set_bits(const struct osmo_crc16gen_code *code,
+                       const ubit_t *in, int len, ubit_t *crc_bits)
+{
+	uint16_t crc;
+	int i;
+
+	crc = osmo_crc16gen_compute_bits(code, in, len);
+
+	for (i=0; i<code->bits; i++)
+		crc_bits[i] = ((crc >> (code->bits-i-1)) & 1);
+}
+
+/*! @} */
+
+/* vim: set syntax=c: */
diff --git a/lib/decoding/osmocom/core/crc16gen.h b/lib/decoding/osmocom/core/crc16gen.h
new file mode 100644
index 0000000..567b74e
--- /dev/null
+++ b/lib/decoding/osmocom/core/crc16gen.h
@@ -0,0 +1,56 @@
+/*
+ * crc16gen.h
+ *
+ * Copyright (C) 2011  Sylvain Munaut <tnt@246tNt.com>
+ *
+ * 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.
+ */
+
+#pragma once
+
+/*! \addtogroup crcgen
+ *  @{
+ */
+
+/*! \file crc16gen.h
+ * Osmocom generic CRC routines (for max 16 bits poly) header
+ */
+
+
+#include <stdint.h>
+#include <osmocom/core/bits.h>
+
+
+/*! \brief structure describing a given CRC code of max 16 bits */
+struct osmo_crc16gen_code {
+	int bits;           /*!< \brief Actual number of bits of the CRC */
+	uint16_t poly;      /*!< \brief Polynom (normal representation, MSB omitted */
+	uint16_t init;      /*!< \brief Initialization value of the CRC state */
+	uint16_t remainder; /*!< \brief Remainder of the CRC (final XOR) */
+};
+
+uint16_t osmo_crc16gen_compute_bits(const struct osmo_crc16gen_code *code,
+                                    const ubit_t *in, int len);
+int osmo_crc16gen_check_bits(const struct osmo_crc16gen_code *code,
+                             const ubit_t *in, int len, const ubit_t *crc_bits);
+void osmo_crc16gen_set_bits(const struct osmo_crc16gen_code *code,
+                            const ubit_t *in, int len, ubit_t *crc_bits);
+
+
+/*! @} */
+
+/* vim: set syntax=c: */
diff --git a/lib/decoding/osmocom/core/crc32gen.h b/lib/decoding/osmocom/core/crc32gen.h
new file mode 100644
index 0000000..3b95338
--- /dev/null
+++ b/lib/decoding/osmocom/core/crc32gen.h
@@ -0,0 +1,56 @@
+/*
+ * crc32gen.h
+ *
+ * Copyright (C) 2011  Sylvain Munaut <tnt@246tNt.com>
+ *
+ * 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.
+ */
+
+#pragma once
+
+/*! \addtogroup crcgen
+ *  @{
+ */
+
+/*! \file crc32gen.h
+ * Osmocom generic CRC routines (for max 32 bits poly) header
+ */
+
+
+#include <stdint.h>
+#include <osmocom/core/bits.h>
+
+
+/*! \brief structure describing a given CRC code of max 32 bits */
+struct osmo_crc32gen_code {
+	int bits;           /*!< \brief Actual number of bits of the CRC */
+	uint32_t poly;      /*!< \brief Polynom (normal representation, MSB omitted */
+	uint32_t init;      /*!< \brief Initialization value of the CRC state */
+	uint32_t remainder; /*!< \brief Remainder of the CRC (final XOR) */
+};
+
+uint32_t osmo_crc32gen_compute_bits(const struct osmo_crc32gen_code *code,
+                                    const ubit_t *in, int len);
+int osmo_crc32gen_check_bits(const struct osmo_crc32gen_code *code,
+                             const ubit_t *in, int len, const ubit_t *crc_bits);
+void osmo_crc32gen_set_bits(const struct osmo_crc32gen_code *code,
+                            const ubit_t *in, int len, ubit_t *crc_bits);
+
+
+/*! @} */
+
+/* vim: set syntax=c: */
diff --git a/lib/decoding/osmocom/core/crc64gen.c b/lib/decoding/osmocom/core/crc64gen.c
new file mode 100644
index 0000000..3e700d4
--- /dev/null
+++ b/lib/decoding/osmocom/core/crc64gen.c
@@ -0,0 +1,120 @@
+/*
+ * crc64gen.c
+ *
+ * Generic CRC routines (for max 64 bits poly)
+ *
+ * Copyright (C) 2011  Sylvain Munaut <tnt@246tNt.com>
+ *
+ * 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.
+ */
+
+/*! \addtogroup crcgen
+ *  @{
+ */
+
+/*! \file crc64gen.c
+ * Osmocom generic CRC routines (for max 64 bits poly)
+ */
+
+#include <stdint.h>
+
+#include <osmocom/core/bits.h>
+#include <osmocom/core/crc64gen.h>
+
+
+/*! \brief Compute the CRC value of a given array of hard-bits
+ *  \param[in] code The CRC code description to apply
+ *  \param[in] in Array of hard bits
+ *  \param[in] len Length of the array of hard bits
+ *  \returns The CRC value
+ */
+uint64_t
+osmo_crc64gen_compute_bits(const struct osmo_crc64gen_code *code,
+                           const ubit_t *in, int len)
+{
+	const uint64_t poly = code->poly;
+	uint64_t crc = code->init;
+	int i, n = code->bits-1;
+
+	for (i=0; i<len; i++) {
+		uint64_t bit = in[i] & 1;
+		crc ^= (bit << n);
+		if (crc & ((uint64_t)1 << n)) {
+			crc <<= 1;
+			crc ^= poly;
+		} else {
+			crc <<= 1;
+		}
+		crc &= ((uint64_t)1 << code->bits) - 1;
+	}
+
+	crc ^= code->remainder;
+
+	return crc;
+}
+
+
+/*! \brief Checks the CRC value of a given array of hard-bits
+ *  \param[in] code The CRC code description to apply
+ *  \param[in] in Array of hard bits
+ *  \param[in] len Length of the array of hard bits
+ *  \param[in] crc_bits Array of hard bits with the alleged CRC
+ *  \returns 0 if CRC matches. 1 in case of error.
+ *
+ * The crc_bits array must have a length of code->len
+ */
+int
+osmo_crc64gen_check_bits(const struct osmo_crc64gen_code *code,
+                         const ubit_t *in, int len, const ubit_t *crc_bits)
+{
+	uint64_t crc;
+	int i;
+
+	crc = osmo_crc64gen_compute_bits(code, in, len);
+
+	for (i=0; i<code->bits; i++)
+		if (crc_bits[i] ^ ((crc >> (code->bits-i-1)) & 1))
+			return 1;
+
+	return 0;
+}
+
+
+/*! \brief Computes and writes the CRC value of a given array of bits
+ *  \param[in] code The CRC code description to apply
+ *  \param[in] in Array of hard bits
+ *  \param[in] len Length of the array of hard bits
+ *  \param[in] crc_bits Array of hard bits to write the computed CRC to
+ *
+ * The crc_bits array must have a length of code->len
+ */
+void
+osmo_crc64gen_set_bits(const struct osmo_crc64gen_code *code,
+                       const ubit_t *in, int len, ubit_t *crc_bits)
+{
+	uint64_t crc;
+	int i;
+
+	crc = osmo_crc64gen_compute_bits(code, in, len);
+
+	for (i=0; i<code->bits; i++)
+		crc_bits[i] = ((crc >> (code->bits-i-1)) & 1);
+}
+
+/*! @} */
+
+/* vim: set syntax=c: */
diff --git a/lib/decoding/osmocom/core/crc64gen.h b/lib/decoding/osmocom/core/crc64gen.h
new file mode 100644
index 0000000..aa02e04
--- /dev/null
+++ b/lib/decoding/osmocom/core/crc64gen.h
@@ -0,0 +1,56 @@
+/*
+ * crc64gen.h
+ *
+ * Copyright (C) 2011  Sylvain Munaut <tnt@246tNt.com>
+ *
+ * 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.
+ */
+
+#pragma once
+
+/*! \addtogroup crcgen
+ *  @{
+ */
+
+/*! \file crc64gen.h
+ * Osmocom generic CRC routines (for max 64 bits poly) header
+ */
+
+
+#include <stdint.h>
+#include <osmocom/core/bits.h>
+
+
+/*! \brief structure describing a given CRC code of max 64 bits */
+struct osmo_crc64gen_code {
+	int bits;           /*!< \brief Actual number of bits of the CRC */
+	uint64_t poly;      /*!< \brief Polynom (normal representation, MSB omitted */
+	uint64_t init;      /*!< \brief Initialization value of the CRC state */
+	uint64_t remainder; /*!< \brief Remainder of the CRC (final XOR) */
+};
+
+uint64_t osmo_crc64gen_compute_bits(const struct osmo_crc64gen_code *code,
+                                    const ubit_t *in, int len);
+int osmo_crc64gen_check_bits(const struct osmo_crc64gen_code *code,
+                             const ubit_t *in, int len, const ubit_t *crc_bits);
+void osmo_crc64gen_set_bits(const struct osmo_crc64gen_code *code,
+                            const ubit_t *in, int len, ubit_t *crc_bits);
+
+
+/*! @} */
+
+/* vim: set syntax=c: */
diff --git a/lib/decoding/osmocom/core/crc8gen.c b/lib/decoding/osmocom/core/crc8gen.c
new file mode 100644
index 0000000..0b85b0d
--- /dev/null
+++ b/lib/decoding/osmocom/core/crc8gen.c
@@ -0,0 +1,120 @@
+/*
+ * crc8gen.c
+ *
+ * Generic CRC routines (for max 8 bits poly)
+ *
+ * Copyright (C) 2011  Sylvain Munaut <tnt@246tNt.com>
+ *
+ * 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.
+ */
+
+/*! \addtogroup crcgen
+ *  @{
+ */
+
+/*! \file crc8gen.c
+ * Osmocom generic CRC routines (for max 8 bits poly)
+ */
+
+#include <stdint.h>
+
+#include <osmocom/core/bits.h>
+#include <osmocom/core/crc8gen.h>
+
+
+/*! \brief Compute the CRC value of a given array of hard-bits
+ *  \param[in] code The CRC code description to apply
+ *  \param[in] in Array of hard bits
+ *  \param[in] len Length of the array of hard bits
+ *  \returns The CRC value
+ */
+uint8_t
+osmo_crc8gen_compute_bits(const struct osmo_crc8gen_code *code,
+                           const ubit_t *in, int len)
+{
+	const uint8_t poly = code->poly;
+	uint8_t crc = code->init;
+	int i, n = code->bits-1;
+
+	for (i=0; i<len; i++) {
+		uint8_t bit = in[i] & 1;
+		crc ^= (bit << n);
+		if (crc & ((uint8_t)1 << n)) {
+			crc <<= 1;
+			crc ^= poly;
+		} else {
+			crc <<= 1;
+		}
+		crc &= ((uint8_t)1 << code->bits) - 1;
+	}
+
+	crc ^= code->remainder;
+
+	return crc;
+}
+
+
+/*! \brief Checks the CRC value of a given array of hard-bits
+ *  \param[in] code The CRC code description to apply
+ *  \param[in] in Array of hard bits
+ *  \param[in] len Length of the array of hard bits
+ *  \param[in] crc_bits Array of hard bits with the alleged CRC
+ *  \returns 0 if CRC matches. 1 in case of error.
+ *
+ * The crc_bits array must have a length of code->len
+ */
+int
+osmo_crc8gen_check_bits(const struct osmo_crc8gen_code *code,
+                         const ubit_t *in, int len, const ubit_t *crc_bits)
+{
+	uint8_t crc;
+	int i;
+
+	crc = osmo_crc8gen_compute_bits(code, in, len);
+
+	for (i=0; i<code->bits; i++)
+		if (crc_bits[i] ^ ((crc >> (code->bits-i-1)) & 1))
+			return 1;
+
+	return 0;
+}
+
+
+/*! \brief Computes and writes the CRC value of a given array of bits
+ *  \param[in] code The CRC code description to apply
+ *  \param[in] in Array of hard bits
+ *  \param[in] len Length of the array of hard bits
+ *  \param[in] crc_bits Array of hard bits to write the computed CRC to
+ *
+ * The crc_bits array must have a length of code->len
+ */
+void
+osmo_crc8gen_set_bits(const struct osmo_crc8gen_code *code,
+                       const ubit_t *in, int len, ubit_t *crc_bits)
+{
+	uint8_t crc;
+	int i;
+
+	crc = osmo_crc8gen_compute_bits(code, in, len);
+
+	for (i=0; i<code->bits; i++)
+		crc_bits[i] = ((crc >> (code->bits-i-1)) & 1);
+}
+
+/*! @} */
+
+/* vim: set syntax=c: */
diff --git a/lib/decoding/osmocom/core/crc8gen.h b/lib/decoding/osmocom/core/crc8gen.h
new file mode 100644
index 0000000..9513276
--- /dev/null
+++ b/lib/decoding/osmocom/core/crc8gen.h
@@ -0,0 +1,56 @@
+/*
+ * crc8gen.h
+ *
+ * Copyright (C) 2011  Sylvain Munaut <tnt@246tNt.com>
+ *
+ * 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.
+ */
+
+#pragma once
+
+/*! \addtogroup crcgen
+ *  @{
+ */
+
+/*! \file crc8gen.h
+ * Osmocom generic CRC routines (for max 8 bits poly) header
+ */
+
+
+#include <stdint.h>
+#include <osmocom/core/bits.h>
+
+
+/*! \brief structure describing a given CRC code of max 8 bits */
+struct osmo_crc8gen_code {
+	int bits;           /*!< \brief Actual number of bits of the CRC */
+	uint8_t poly;      /*!< \brief Polynom (normal representation, MSB omitted */
+	uint8_t init;      /*!< \brief Initialization value of the CRC state */
+	uint8_t remainder; /*!< \brief Remainder of the CRC (final XOR) */
+};
+
+uint8_t osmo_crc8gen_compute_bits(const struct osmo_crc8gen_code *code,
+                                    const ubit_t *in, int len);
+int osmo_crc8gen_check_bits(const struct osmo_crc8gen_code *code,
+                             const ubit_t *in, int len, const ubit_t *crc_bits);
+void osmo_crc8gen_set_bits(const struct osmo_crc8gen_code *code,
+                            const ubit_t *in, int len, ubit_t *crc_bits);
+
+
+/*! @} */
+
+/* vim: set syntax=c: */
diff --git a/lib/decoding/osmocom/core/crcgen.h b/lib/decoding/osmocom/core/crcgen.h
new file mode 100644
index 0000000..7cfe869
--- /dev/null
+++ b/lib/decoding/osmocom/core/crcgen.h
@@ -0,0 +1,34 @@
+/*! \file crcgen.h
+ * Osmocom generic CRC routines global header. */
+/*
+ * Copyright (C) 2011  Sylvain Munaut <tnt@246tNt.com>
+ *
+ * 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.
+ */
+
+#pragma once
+
+/*! \defgroup crc Osmocom CRC routines
+ *  @{
+ * \file crcgen.h */
+
+#include <osmocom/core/crc8gen.h>
+#include <osmocom/core/crc16gen.h>
+#include <osmocom/core/crc32gen.h>
+#include <osmocom/core/crc64gen.h>
+
+/*! @} */
diff --git a/lib/decoding/osmocom/core/defs.h b/lib/decoding/osmocom/core/defs.h
new file mode 100644
index 0000000..5e5aa90
--- /dev/null
+++ b/lib/decoding/osmocom/core/defs.h
@@ -0,0 +1,53 @@
+/*! \file defs.h
+ *  General definitions that are meant to be included from header files.
+ */
+
+#pragma once
+
+/*! \defgroup utils General-purpose utility functions
+ *  @{
+ * \file defs.h */
+
+/*! Check for gcc and version.
+ *
+ * \note Albeit glibc provides a features.h file that contains a similar
+ *       definition (__GNUC_PREREQ), this definition has been copied from there
+ *       to have it available with other libraries, too.
+ *
+ * \return != 0 iff gcc is used and it's version is at least maj.min.
+ */
+#if defined __GNUC__ && defined __GNUC_MINOR__
+# define OSMO_GNUC_PREREQ(maj, min) \
+	((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min))
+#else
+# define OSMO_GNUC_PREREQ(maj, min) 0
+#endif
+
+/*! Set the deprecated attribute with a message.
+ */
+#if defined(__clang__)
+# define _OSMO_HAS_ATTRIBUTE_DEPRECATED __has_attribute(deprecated)
+# define _OSMO_HAS_ATTRIBUTE_DEPRECATED_WITH_MESSAGE __has_extension(attribute_deprecated_with_message)
+#elif defined(__GNUC__)
+# define _OSMO_HAS_ATTRIBUTE_DEPRECATED 1
+# define _OSMO_HAS_ATTRIBUTE_DEPRECATED_WITH_MESSAGE OSMO_GNUC_PREREQ(4,5)
+#endif
+
+#if _OSMO_HAS_ATTRIBUTE_DEPRECATED_WITH_MESSAGE
+# define OSMO_DEPRECATED(text)  __attribute__((__deprecated__(text)))
+#elif _OSMO_HAS_ATTRIBUTE_DEPRECATED
+# define OSMO_DEPRECATED(text)  __attribute__((__deprecated__))
+#else
+# define OSMO_DEPRECATED(text)
+#endif
+
+#if BUILDING_LIBOSMOCORE
+# define OSMO_DEPRECATED_OUTSIDE_LIBOSMOCORE
+#else
+# define OSMO_DEPRECATED_OUTSIDE_LIBOSMOCORE OSMO_DEPRECATED("For internal use inside libosmocore only.")
+#endif
+
+#undef _OSMO_HAS_ATTRIBUTE_DEPRECATED_WITH_MESSAGE
+#undef _OSMO_HAS_ATTRIBUTE_DEPRECATED
+
+/*! @} */
diff --git a/lib/decoding/osmocom/core/endian.h b/lib/decoding/osmocom/core/endian.h
new file mode 100644
index 0000000..6107b12
--- /dev/null
+++ b/lib/decoding/osmocom/core/endian.h
@@ -0,0 +1,62 @@
+/*! \file endian.h
+ *
+ * GNU and FreeBSD have various ways to express the
+ * endianess but none of them is similiar enough. This
+ * will create two defines that allows to decide on the
+ * endian. The following will be defined to either 0 or
+ * 1 at the end of the file.
+ *
+ *      OSMO_IS_LITTLE_ENDIAN
+ *      OSMO_IS_BIG_ENDIAN
+ *
+ */
+
+#pragma once
+
+#if defined(__FreeBSD__)
+#include <sys/endian.h>
+        #if BYTE_ORDER == LITTLE_ENDIAN
+                #define OSMO_IS_LITTLE_ENDIAN           1
+                #define OSMO_IS_BIG_ENDIAN              0
+        #elif BYTE_ORDER == BIG_ENDIAN
+                #define OSMO_IS_LITTLE_ENDIAN           0
+                #define OSMO_IS_BIG_ENDIAN              1
+        #else
+                #error "Unknown endian"
+        #endif
+#elif defined(__APPLE__)
+#include <machine/endian.h>
+	#if defined(__DARWIN_LITTLE_ENDIAN)
+		#define OSMO_IS_LITTLE_ENDIAN		1
+		#define OSMO_IS_BIG_ENDIAN		0
+	#elif defined(__DARWIN_BIG_ENDIAN)
+		#define OSMO_IS_LITTLE_ENDIAN		0
+		#define OSMO_IS_BIG_ENDIAN		1
+	#else
+		#error "Unknown endian"
+	#endif
+#elif defined(__linux__)
+#include <endian.h>
+        #if __BYTE_ORDER == __LITTLE_ENDIAN
+                #define OSMO_IS_LITTLE_ENDIAN           1
+                #define OSMO_IS_BIG_ENDIAN              0
+        #elif __BYTE_ORDER == __BIG_ENDIAN
+                #define OSMO_IS_LITTLE_ENDIAN           0
+                #define OSMO_IS_BIG_ENDIAN              1
+        #else
+                #error "Unknown endian"
+        #endif
+#else
+	/* let's try to rely on the compiler.  GCC and CLANG/LLVM seem
+	 * to support this ... */
+	#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+		#define OSMO_IS_LITTLE_ENDIAN           1
+		#define OSMO_IS_BIG_ENDIAN              0
+	#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+		#define OSMO_IS_LITTLE_ENDIAN           0
+		#define OSMO_IS_BIG_ENDIAN              1
+	#else
+		#error "Unknown endian"
+	#endif
+#endif
+
diff --git a/lib/decoding/osmocom/core/linuxlist.h b/lib/decoding/osmocom/core/linuxlist.h
new file mode 100644
index 0000000..fee0cc0
--- /dev/null
+++ b/lib/decoding/osmocom/core/linuxlist.h
@@ -0,0 +1,409 @@
+/*! \file linuxlist.h
+ *
+ * Simple doubly linked list implementation.
+ *
+ * Some of the internal functions ("__xxx") are useful when
+ * manipulating whole llists rather than single entries, as
+ * sometimes we already know the next/prev entries and we can
+ * generate better code by using them directly rather than
+ * using the generic single-entry routines.
+ */
+
+#pragma once
+
+/*! \defgroup linuxlist Simple doubly linked list implementation
+ *  @{
+ * \file linuxlist.h */
+
+#include <stddef.h>
+
+#if (defined(_WIN16) || defined(_WIN32) || defined(_WIN64)) && !defined(__WINDOWS__)
+#	define __WINDOWS__
+#endif
+
+#ifndef inline
+#  ifndef __WINDOWS__
+#    define inline __inline__
+#  else
+#    define inline __inline
+#  endif
+#endif
+
+static inline void prefetch(const void *x) {;}
+
+/*! cast a member of a structure out to the containing structure
+ *
+ * \param[in] ptr the pointer to the member.
+ * \param[in] type the type of the container struct this is embedded in.
+ * \param[in] member the name of the member within the struct.
+ */
+#define container_of(ptr, type, member) ({			\
+        const typeof( ((type *)0)->member ) *__mptr = (ptr);	\
+        (type *)( (char *)__mptr - offsetof(type, member) );})
+
+
+/*!
+ * These are non-NULL pointers that will result in page faults
+ * under normal circumstances, used to verify that nobody uses
+ * non-initialized llist entries.
+ */
+#define LLIST_POISON1  ((void *) 0x00100100)
+#define LLIST_POISON2  ((void *) 0x00200200)
+
+/*! (double) linked list header structure */
+struct llist_head {
+	/*! Pointer to next and previous item */
+	struct llist_head *next, *prev;
+};
+
+#define LLIST_HEAD_INIT(name) { &(name), &(name) }
+
+/*! define a statically-initialized \ref llist_head
+ *  \param[in] name Variable name
+ *
+ * This is a helper macro that will define a named variable of type
+ * \ref llist_head and initialize it */
+#define LLIST_HEAD(name) \
+	struct llist_head name = LLIST_HEAD_INIT(name)
+
+/*! initialize a \ref llist_head to point back to self */
+#define INIT_LLIST_HEAD(ptr) do { \
+	(ptr)->next = (ptr); (ptr)->prev = (ptr); \
+} while (0)
+
+/*! Insert a new entry between two known consecutive entries. 
+ *
+ * This is only for internal llist manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __llist_add(struct llist_head *_new,
+			      struct llist_head *prev,
+			      struct llist_head *next)
+{
+	next->prev = _new;
+	_new->next = next;
+	_new->prev = prev;
+	prev->next = _new;
+}
+
+/*! add a new entry into a linked list (at head)
+ *  \param _new New entry to be added
+ *  \param head \ref llist_head to add it after
+ *
+ * Insert a new entry after the specified head.
+ * This is good for implementing stacks.
+ */
+static inline void llist_add(struct llist_head *_new, struct llist_head *head)
+{
+	__llist_add(_new, head, head->next);
+}
+
+/*! add a new entry into a linked list (at tail)
+ *  \param _new  New entry to be added
+ *  \param head  Head of linked list to whose tail we shall add \a _new
+ *
+ * Insert a new entry before the specified head.
+ * This is useful for implementing queues.
+ */
+static inline void llist_add_tail(struct llist_head *_new, struct llist_head *head)
+{
+	__llist_add(_new, head->prev, head);
+}
+
+/*
+ * Delete a llist entry by making the prev/next entries
+ * point to each other.
+ *
+ * This is only for internal llist manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __llist_del(struct llist_head * prev, struct llist_head * next)
+{
+	next->prev = prev;
+	prev->next = next;
+}
+
+/*! Delete entry from linked list
+ *  \param entry  The element to delete from the llist
+ *
+ * Note: llist_empty on entry does not return true after this, the entry is
+ * in an undefined state.
+ */
+static inline void llist_del(struct llist_head *entry)
+{
+	__llist_del(entry->prev, entry->next);
+	entry->next = (struct llist_head *)LLIST_POISON1;
+	entry->prev = (struct llist_head *)LLIST_POISON2;
+}
+
+/*! Delete entry from linked list and reinitialize it
+ *  \param entry  The element to delete from the list
+ */
+static inline void llist_del_init(struct llist_head *entry)
+{
+	__llist_del(entry->prev, entry->next);
+	INIT_LLIST_HEAD(entry); 
+}
+
+/*! Delete from one llist and add as another's head
+ *  \param llist The entry to move
+ *  \param head	The head that will precede our entry
+ */
+static inline void llist_move(struct llist_head *llist, struct llist_head *head)
+{
+        __llist_del(llist->prev, llist->next);
+        llist_add(llist, head);
+}
+
+/*! Delete from one llist and add as another's tail
+ *  \param llist The entry to move
+ *  \param head The head that will follow our entry
+ */
+static inline void llist_move_tail(struct llist_head *llist,
+				  struct llist_head *head)
+{
+        __llist_del(llist->prev, llist->next);
+        llist_add_tail(llist, head);
+}
+
+/*! Test whether a linked list is empty
+ *  \param[in] head  The llist to test.
+ *  \returns 1 if the list is empty, 0 otherwise
+ */
+static inline int llist_empty(const struct llist_head *head)
+{
+	return head->next == head;
+}
+
+static inline void __llist_splice(struct llist_head *llist,
+				 struct llist_head *head)
+{
+	struct llist_head *first = llist->next;
+	struct llist_head *last = llist->prev;
+	struct llist_head *at = head->next;
+
+	first->prev = head;
+	head->next = first;
+
+	last->next = at;
+	at->prev = last;
+}
+
+/*! Join two llists
+ *  \param llist The new linked list to add
+ *  \param head The place to add \a llist in the other list
+ */
+static inline void llist_splice(struct llist_head *llist, struct llist_head *head)
+{
+	if (!llist_empty(llist))
+		__llist_splice(llist, head);
+}
+
+/*! join two llists and reinitialise the emptied llist.
+ * \param llist The new linked list to add.
+ * \param head  The place to add it in the first llist.
+ *
+ * The llist at @llist is reinitialised
+ */
+static inline void llist_splice_init(struct llist_head *llist,
+				    struct llist_head *head)
+{
+	if (!llist_empty(llist)) {
+		__llist_splice(llist, head);
+		INIT_LLIST_HEAD(llist);
+	}
+}
+
+/*! Get the struct containing this list entry
+ *  \param ptr The \ref llist_head pointer
+ *  \param type The type of the struct this is embedded in
+ *  \param @member The name of the \ref llist_head within the struct
+ */
+#define llist_entry(ptr, type, member) \
+	container_of(ptr, type, member)
+
+/*! Get the first element from a list
+ *  \param ptr    the list head to take the element from.
+ *  \param type   the type of the struct this is embedded in.
+ *  \param member the name of the list_head within the struct.
+ *
+ * Note, that list is expected to be not empty.
+ */
+#define llist_first_entry(ptr, type, member) \
+	llist_entry((ptr)->next, type, member)
+
+/*! Get the last element from a list
+ *  \param ptr    the list head to take the element from.
+ *  \param type   the type of the struct this is embedded in.
+ *  \param member the name of the llist_head within the struct.
+ *
+ * Note, that list is expected to be not empty.
+ */
+#define llist_last_entry(ptr, type, member) \
+	llist_entry((ptr)->prev, type, member)
+
+/*! Get the first element from a list, or NULL
+ *  \param ptr    the list head to take the element from.
+ *  \param type   the type of the struct this is embedded in.
+ *  \param member the name of the list_head within the struct.
+ *
+ * Note that if the list is empty, it returns NULL.
+ */
+#define llist_first_entry_or_null(ptr, type, member) \
+	(!llist_empty(ptr) ? llist_first_entry(ptr, type, member) : NULL)
+
+/*! Iterate over a linked list
+ *  \param pos 	The \ref llist_head to use as a loop counter
+ *  \param head The head of the list over which to iterate
+ */
+#define llist_for_each(pos, head) \
+	for (pos = (head)->next, prefetch(pos->next); pos != (head); \
+        	pos = pos->next, prefetch(pos->next))
+
+/*! Iterate over a llist (no prefetch)
+ *  \param pos 	The \ref llist_head to use as a loop counter
+ *  \param head The head of the list over which to iterate
+ *
+ * This variant differs from llist_for_each() in that it's the
+ * simplest possible llist iteration code, no prefetching is done.
+ * Use this for code that knows the llist to be very short (empty
+ * or 1 entry) most of the time.
+ */
+#define __llist_for_each(pos, head) \
+	for (pos = (head)->next; pos != (head); pos = pos->next)
+
+/*! Iterate over a llist backwards
+ *  \param pos 	The \ref llist_head to use as a loop counter
+ *  \param head The head of the list over which to iterate
+ */
+#define llist_for_each_prev(pos, head) \
+	for (pos = (head)->prev, prefetch(pos->prev); pos != (head); \
+        	pos = pos->prev, prefetch(pos->prev))
+
+/*! Iterate over a list; safe against removal of llist entry
+ *  \param pos 	The \ref llist_head to use as a loop counter
+ *  \param n Another \ref llist_head to use as temporary storage
+ *  \param head The head of the list over which to iterate
+ */
+#define llist_for_each_safe(pos, n, head) \
+	for (pos = (head)->next, n = pos->next; pos != (head); \
+		pos = n, n = pos->next)
+
+/*! Iterate over llist of given type
+ *  \param pos The 'type *' to use as a loop counter
+ *  \param head The head of the list over which to iterate
+ *  \param member The name of the \ref llist_head within struct \a pos
+ */
+#define llist_for_each_entry(pos, head, member)				\
+	for (pos = llist_entry((head)->next, typeof(*pos), member),	\
+		     prefetch(pos->member.next);			\
+	     &pos->member != (head); 					\
+	     pos = llist_entry(pos->member.next, typeof(*pos), member),	\
+		     prefetch(pos->member.next))
+
+/*! Iterate backwards over llist of given type.
+ *  \param pos The 'type *' to use as a loop counter
+ *  \param head The head of the list over which to iterate
+ *  \param member The name of the \ref llist_head within struct \a pos
+ */
+#define llist_for_each_entry_reverse(pos, head, member)			\
+	for (pos = llist_entry((head)->prev, typeof(*pos), member),	\
+		     prefetch(pos->member.prev);			\
+	     &pos->member != (head); 					\
+	     pos = llist_entry(pos->member.prev, typeof(*pos), member),	\
+		     prefetch(pos->member.prev))
+
+/*! iterate over llist of given type continuing after existing
+ * point
+ *  \param pos The 'type *' to use as a loop counter
+ *  \param head The head of the list over which to iterate
+ *  \param member The name of the \ref llist_head within struct \a pos
+ */
+#define llist_for_each_entry_continue(pos, head, member) 		\
+	for (pos = llist_entry(pos->member.next, typeof(*pos), member),	\
+		     prefetch(pos->member.next);			\
+	     &pos->member != (head);					\
+	     pos = llist_entry(pos->member.next, typeof(*pos), member),	\
+		     prefetch(pos->member.next))
+
+/*! iterate over llist of given type, safe against removal of
+ * non-consecutive(!) llist entries
+ *  \param pos The 'type *' to use as a loop counter
+ *  \param n Another type * to use as temporary storage
+ *  \param head The head of the list over which to iterate
+ *  \param member The name of the \ref llist_head within struct \a pos
+ */
+#define llist_for_each_entry_safe(pos, n, head, member)			\
+	for (pos = llist_entry((head)->next, typeof(*pos), member),	\
+		n = llist_entry(pos->member.next, typeof(*pos), member);	\
+	     &pos->member != (head); 					\
+	     pos = n, n = llist_entry(n->member.next, typeof(*n), member))
+
+/**
+ * llist_for_each_rcu	-	iterate over an rcu-protected llist
+ * @pos:	the &struct llist_head to use as a loop counter.
+ * @head:	the head for your llist.
+ */
+#define llist_for_each_rcu(pos, head) \
+	for (pos = (head)->next, prefetch(pos->next); pos != (head); \
+        	pos = pos->next, ({ smp_read_barrier_depends(); 0;}), prefetch(pos->next))
+        	
+#define __llist_for_each_rcu(pos, head) \
+	for (pos = (head)->next; pos != (head); \
+        	pos = pos->next, ({ smp_read_barrier_depends(); 0;}))
+        	
+/**
+ * llist_for_each_safe_rcu	-	iterate over an rcu-protected llist safe
+ *					against removal of llist entry
+ * @pos:	the &struct llist_head to use as a loop counter.
+ * @n:		another &struct llist_head to use as temporary storage
+ * @head:	the head for your llist.
+ */
+#define llist_for_each_safe_rcu(pos, n, head) \
+	for (pos = (head)->next, n = pos->next; pos != (head); \
+		pos = n, ({ smp_read_barrier_depends(); 0;}), n = pos->next)
+
+/**
+ * llist_for_each_entry_rcu	-	iterate over rcu llist of given type
+ * @pos:	the type * to use as a loop counter.
+ * @head:	the head for your llist.
+ * @member:	the name of the llist_struct within the struct.
+ */
+#define llist_for_each_entry_rcu(pos, head, member)			\
+	for (pos = llist_entry((head)->next, typeof(*pos), member),	\
+		     prefetch(pos->member.next);			\
+	     &pos->member != (head); 					\
+	     pos = llist_entry(pos->member.next, typeof(*pos), member),	\
+		     ({ smp_read_barrier_depends(); 0;}),		\
+		     prefetch(pos->member.next))
+
+
+/**
+ * llist_for_each_continue_rcu	-	iterate over an rcu-protected llist 
+ *			continuing after existing point.
+ * @pos:	the &struct llist_head to use as a loop counter.
+ * @head:	the head for your llist.
+ */
+#define llist_for_each_continue_rcu(pos, head) \
+	for ((pos) = (pos)->next, prefetch((pos)->next); (pos) != (head); \
+        	(pos) = (pos)->next, ({ smp_read_barrier_depends(); 0;}), prefetch((pos)->next))
+
+/*! count nr of llist items by iterating.
+ *  \param head The llist head to count items of.
+ *  \returns Number of items.
+ *
+ * This function is not efficient, mostly useful for small lists and non time
+ * critical cases like unit tests.
+ */
+static inline unsigned int llist_count(struct llist_head *head)
+{
+	struct llist_head *entry;
+	unsigned int i = 0;
+	llist_for_each(entry, head)
+		i++;
+	return i;
+}
+
+/*!
+ *  @}
+ */
diff --git a/lib/decoding/osmocom/core/panic.c b/lib/decoding/osmocom/core/panic.c
new file mode 100644
index 0000000..d545a60
--- /dev/null
+++ b/lib/decoding/osmocom/core/panic.c
@@ -0,0 +1,100 @@
+/*! \file panic.c
+ *  Routines for panic handling. */
+/*
+ * (C) 2010 by Sylvain Munaut <tnt@246tNt.com>
+ *
+ * All Rights Reserved
+ *
+ * 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.
+ *
+ */
+
+/*! \addtogroup utils
+ *  @{
+ * \file panic.c */
+
+#include <osmocom/core/panic.h>
+//#include <osmocom/core/backtrace.h>
+
+//#include "../config.h"
+
+
+static osmo_panic_handler_t osmo_panic_handler = (void*)0;
+
+
+#ifndef PANIC_INFLOOP
+
+#include <stdio.h>
+#include <stdlib.h>
+
+static void osmo_panic_default(const char *fmt, va_list args)
+{
+	vfprintf(stderr, fmt, args);
+	//osmo_generate_backtrace();
+	abort();
+}
+
+#else
+
+static void osmo_panic_default(const char *fmt, va_list args)
+{
+	while (1);
+}
+
+#endif
+
+
+/*! Terminate the current program with a panic
+ *
+ * You can call this function in case some severely unexpected situation
+ * is detected and the program is supposed to terminate in a way that
+ * reports the fact that it terminates.
+ *
+ * The application can register a panic handler function using \ref
+ * osmo_set_panic_handler.  If it doesn't, a default panic handler
+ * function is called automatically.
+ *
+ * The default function on most systems will generate a backtrace and
+ * then abort() the process.
+ */
+void osmo_panic(const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+
+	if (osmo_panic_handler)
+		osmo_panic_handler(fmt, args);
+	else
+		osmo_panic_default(fmt, args);
+
+	va_end(args);
+}
+ 
+
+/*! Set the panic handler
+ *  \param[in] h New panic handler function
+ *
+ *  This changes the panic handling function from the currently active
+ *  function to a new call-back function supplied by the caller.
+ */
+void osmo_set_panic_handler(osmo_panic_handler_t h)
+{
+	osmo_panic_handler = h;
+}
+
+/*! @} */
diff --git a/lib/decoding/osmocom/core/panic.h b/lib/decoding/osmocom/core/panic.h
new file mode 100644
index 0000000..2bb4240
--- /dev/null
+++ b/lib/decoding/osmocom/core/panic.h
@@ -0,0 +1,15 @@
+#pragma once
+
+/*! \addtogroup utils
+ *  @{
+ * \file panic.h */
+
+#include <stdarg.h>
+
+/*! panic handler callback function type */
+typedef void (*osmo_panic_handler_t)(const char *fmt, va_list args);
+
+extern void osmo_panic(const char *fmt, ...);
+extern void osmo_set_panic_handler(osmo_panic_handler_t h);
+
+/*! @} */
diff --git a/lib/decoding/osmocom/core/utils.h b/lib/decoding/osmocom/core/utils.h
new file mode 100644
index 0000000..54c8216
--- /dev/null
+++ b/lib/decoding/osmocom/core/utils.h
@@ -0,0 +1,129 @@
+#pragma once
+
+#include <stdbool.h>
+
+//#include <osmocom/core/backtrace.h>
+//#include <osmocom/core/talloc.h>
+
+/*! \defgroup utils General-purpose utility functions
+ *  @{
+ * \file utils.h */
+
+/*! Determine number of elements in an array of static size */
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+/*! Return the maximum of two specified values */
+#define OSMO_MAX(a, b) ((a) >= (b) ? (a) : (b))
+/*! Return the minimum of two specified values */
+#define OSMO_MIN(a, b) ((a) >= (b) ? (b) : (a))
+/*! Stringify the name of a macro x, e.g. an FSM event name.
+ * Note: if nested within another preprocessor macro, this will
+ * stringify the value of x instead of its name. */
+#define OSMO_STRINGIFY(x) #x
+/*! Stringify the value of a macro x, e.g. a port number. */
+#define OSMO_STRINGIFY_VAL(x) OSMO_STRINGIFY(x)
+/*! Make a value_string entry from an enum value name */
+#define OSMO_VALUE_STRING(x) { x, #x }
+/*! Number of bytes necessary to store given BITS */
+#define OSMO_BYTES_FOR_BITS(BITS) ((BITS + 8 - 1) / 8)
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#define __attribute__(_arg_)
+#define __deprecated__
+
+/*! A mapping between human-readable string and numeric value */
+struct value_string {
+	unsigned int value;	/*!< numeric value */
+	const char *str;	/*!< human-readable string */
+};
+
+//const char *get_value_string(const struct value_string *vs, uint32_t val);
+const char *get_value_string_or_null(const struct value_string *vs,
+				     uint32_t val);
+
+int get_string_value(const struct value_string *vs, const char *str);
+
+char osmo_bcd2char(uint8_t bcd);
+/* only works for numbers in ascci */
+uint8_t osmo_char2bcd(char c);
+
+int osmo_hexparse(const char *str, uint8_t *b, int max_len);
+
+char *osmo_ubit_dump(const uint8_t *bits, unsigned int len);
+char *osmo_hexdump(const unsigned char *buf, int len);
+char *osmo_hexdump_nospc(const unsigned char *buf, int len);
+char *osmo_osmo_hexdump_nospc(const unsigned char *buf, int len) __attribute__((__deprecated__));
+
+#define osmo_static_assert(exp, name) typedef int dummy##name [(exp) ? 1 : -1] __attribute__((__unused__));
+
+void osmo_str2lower(char *out, const char *in);
+void osmo_str2upper(char *out, const char *in);
+
+#define OSMO_SNPRINTF_RET(ret, rem, offset, len)		\
+do {								\
+	len += ret;						\
+	if (ret > rem)						\
+		ret = rem;					\
+	offset += ret;						\
+	rem -= ret;						\
+} while (0)
+
+/*! Helper macro to terminate when an assertion failes
+ *  \param[in] exp Predicate to verify
+ *  This function will generate a backtrace and terminate the program if
+ *  the predicate evaluates to false (0).
+ */
+#define OSMO_ASSERT(exp)    \
+	if (!(exp)) { \
+		fprintf(stderr, "Assert failed %s %s:%d\n", #exp, __BASE_FILE__, __LINE__); \
+		/*osmo_generate_backtrace(); \ */\
+		abort(); \
+	}
+
+/*! duplicate a string using talloc and release its prior content (if any)
+ * \param[in] ctx Talloc context to use for allocation
+ * \param[out] dst pointer to string, will be updated with ptr to new string
+ * \param[in] newstr String that will be copieed to newly allocated string */
+/*static inline void osmo_talloc_replace_string(void *ctx, char **dst, const char *newstr)*/
+/*{*/
+/*	if (*dst)*/
+/*		talloc_free(*dst);*/
+/*	*dst = talloc_strdup(ctx, newstr);*/
+/*}*/
+
+/*! Append to a string and re-/allocate if necessary.
+ * \param[in] ctx  Talloc context to use for initial allocation.
+ * \param[in,out] dest  char* to re-/allocate and append to.
+ * \param[in] fmt  printf-like string format.
+ * \param[in] args  Arguments for fmt.
+ *
+ * \a dest may be passed in NULL, or a string previously allocated by talloc.
+ * If an existing string is passed in, it will remain associated with whichever
+ * ctx it was allocated before, regardless whether it matches \a ctx or not.
+ */
+/*#define osmo_talloc_asprintf(ctx, dest, fmt, args ...) \*/
+/*	do { \*/
+/*		if (!dest) \*/
+/*			dest = talloc_asprintf(ctx, fmt, ## args); \*/
+/*		else \*/
+/*			dest = talloc_asprintf_append((char*)dest, fmt, ## args); \*/
+/*	} while (0)*/
+
+int osmo_constant_time_cmp(const uint8_t *exp, const uint8_t *rel, const int count);
+uint64_t osmo_decode_big_endian(const uint8_t *data, size_t data_len);
+uint8_t *osmo_encode_big_endian(uint64_t value, size_t data_len);
+
+size_t osmo_strlcpy(char *dst, const char *src, size_t siz);
+
+bool osmo_is_hexstr(const char *str, int min_digits, int max_digits,
+		    bool require_even);
+
+bool osmo_identifier_valid(const char *str);
+bool osmo_separated_identifiers_valid(const char *str, const char *sep_chars);
+
+const char *osmo_escape_str(const char *str, int len);
+const char *osmo_escape_str_buf(const char *str, int in_len, char *buf, size_t bufsize);
+
+/*! @} */