Change in osmo-pcu[master]: Convert GprsMS and helpers classes to C

This is merely a historical archive of years 2008-2021, before the migration to mailman3.

A maintained and still updated list archive can be found at https://lists.osmocom.org/hyperkitty/list/gerrit-log@lists.osmocom.org/.

pespin gerrit-no-reply at lists.osmocom.org
Tue Jan 5 10:34:25 UTC 2021


pespin has submitted this change. ( https://gerrit.osmocom.org/c/osmo-pcu/+/21748 )

Change subject: Convert GprsMS and helpers classes to C
......................................................................

Convert GprsMS and helpers classes to C

As we integrate osmo-pcu more and more with libosmocore features, it
becomes really hard to use them since libosmocore relies heavily on C
specific compilation features, which are not available in old C++
compilers (such as designated initializers for complex types in FSMs).

GprsMs is right now a quite simple object since initial design of
osmo-pcu made it optional and most of the logic was placed and stored
duplicated in TBF objects. However, that's changing as we introduce more
features, with the GprsMS class getting more weight. Hence, let's move
it now to be a C struct in order to be able to easily use libosmocore
features there, such as FSMs.

Some helper classes which GprsMs uses are also mostly move to C since
they are mostly structs with methods, so there's no point in having
duplicated APIs for C++ and C for such simple cases.

For some more complex classes, like (ul_,dl_)tbf, C API bindings are
added where needed so that GprsMs can use functionalitites from that
class. Most of those APIs can be kept afterwards and drop the C++ ones
since they provide no benefit in general.

Change-Id: I0b50e3367aaad9dcada76da97b438e452c8b230c
---
M src/Makefile.am
M src/bts.cpp
M src/bts.h
M src/encoding.cpp
A src/gprs_ms.c
D src/gprs_ms.cpp
M src/gprs_ms.h
M src/gprs_ms_storage.cpp
M src/gprs_ms_storage.h
M src/gprs_rlcmac_sched.cpp
M src/gprs_rlcmac_ts_alloc.cpp
M src/llc.cpp
M src/llc.h
M src/pcu_l1_if.cpp
M src/pcu_l1_if.h
M src/pcu_utils.h
M src/pcu_vty_functions.cpp
M src/pdch.cpp
M src/tbf.cpp
M src/tbf.h
M src/tbf_dl.cpp
M src/tbf_dl.h
M src/tbf_ul.cpp
M src/tbf_ul.h
M tests/alloc/AllocTest.cpp
M tests/app_info/AppInfoTest.cpp
M tests/app_info/AppInfoTest.err
M tests/edge/EdgeTest.cpp
M tests/llc/LlcTest.cpp
M tests/ms/MsTest.cpp
M tests/tbf/TbfTest.cpp
M tests/types/TypesTest.cpp
32 files changed, 1,977 insertions(+), 1,920 deletions(-)

Approvals:
  laforge: Looks good to me, but someone else must approve
  pespin: Looks good to me, approved
  Jenkins Builder: Verified



diff --git a/src/Makefile.am b/src/Makefile.am
index ece372d..386a1f6 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -47,7 +47,7 @@
 	gprs_rlcmac_sched.cpp \
 	gprs_rlcmac_meas.cpp \
 	gprs_rlcmac_ts_alloc.cpp \
-	gprs_ms.cpp \
+	gprs_ms.c \
 	gprs_ms_storage.cpp \
 	gsm_timer.cpp \
 	pcu_l1_if.cpp \
diff --git a/src/bts.cpp b/src/bts.cpp
index b4f902a..bd0a1d2 100644
--- a/src/bts.cpp
+++ b/src/bts.cpp
@@ -712,7 +712,7 @@
 
 	ms = ms_by_tlli(tlli);
 	if (ms)
-		dl_tbf = ms->dl_tbf();
+		dl_tbf = ms_dl_tbf(ms);
 	if (!dl_tbf) {
 		LOGP(DRLCMAC, LOGL_ERROR, "Got IMM.ASS confirm, but TLLI=%08x "
 			"does not exit\n", tlli);
@@ -1140,9 +1140,9 @@
 	GprsMs *ms;
 	ms = ms_store().create_ms();
 
-	ms->set_timeout(osmo_tdef_get(m_bts.T_defs_pcu, -2030, OSMO_TDEF_S, -1));
-	ms->set_ms_class(ms_class);
-	ms->set_egprs_ms_class(egprs_ms_class);
+	ms_set_timeout(ms, osmo_tdef_get(m_bts.T_defs_pcu, -2030, OSMO_TDEF_S, -1));
+	ms_set_ms_class(ms, ms_class);
+	ms_set_egprs_ms_class(ms, egprs_ms_class);
 
 	return ms;
 }
@@ -1206,22 +1206,22 @@
 	}
 }
 
-void gprs_rlcmac_trx::reserve_slots(enum gprs_rlcmac_tbf_direction dir,
+void bts_trx_reserve_slots(struct gprs_rlcmac_trx *trx, enum gprs_rlcmac_tbf_direction dir,
 	uint8_t slots)
 {
 	unsigned i;
-	for (i = 0; i < ARRAY_SIZE(pdch); i += 1)
+	for (i = 0; i < ARRAY_SIZE(trx->pdch); i += 1)
 		if (slots & (1 << i))
-			pdch[i].reserve(dir);
+			trx->pdch[i].reserve(dir);
 }
 
-void gprs_rlcmac_trx::unreserve_slots(enum gprs_rlcmac_tbf_direction dir,
+void bts_trx_unreserve_slots(struct gprs_rlcmac_trx *trx, enum gprs_rlcmac_tbf_direction dir,
 	uint8_t slots)
 {
 	unsigned i;
-	for (i = 0; i < ARRAY_SIZE(pdch); i += 1)
+	for (i = 0; i < ARRAY_SIZE(trx->pdch); i += 1)
 		if (slots & (1 << i))
-			pdch[i].unreserve(dir);
+			trx->pdch[i].unreserve(dir);
 }
 
 void bts_set_max_cs(struct gprs_rlcmac_bts *bts, uint8_t cs_dl, uint8_t cs_ul)
@@ -1277,3 +1277,34 @@
 	bts->bts->set_max_mcs_dl(mcs_dl);
 	bts->bts->set_max_mcs_ul(mcs_ul);
 }
+
+
+struct gprs_rlcmac_bts *bts_data(struct BTS *bts)
+{
+	return &bts->m_bts;
+}
+
+struct GprsMs *bts_ms_by_imsi(struct BTS *bts, const char *imsi)
+{
+	return bts->ms_by_imsi(imsi);
+}
+
+uint8_t bts_max_cs_dl(const struct BTS *bts)
+{
+	return bts->max_cs_dl();
+}
+
+uint8_t bts_max_cs_ul(const struct BTS *bts)
+{
+	return bts->max_cs_ul();
+}
+
+uint8_t bts_max_mcs_dl(const struct BTS *bts)
+{
+	return bts->max_mcs_dl();
+}
+
+uint8_t bts_max_mcs_ul(const struct BTS *bts)
+{
+	return bts->max_mcs_ul();
+}
diff --git a/src/bts.h b/src/bts.h
index 055b131..f10542f 100644
--- a/src/bts.h
+++ b/src/bts.h
@@ -90,15 +90,14 @@
 	struct BTS *bts;
 	uint8_t trx_no;
 
-#ifdef __cplusplus
-	void reserve_slots(enum gprs_rlcmac_tbf_direction dir, uint8_t slots);
-	void unreserve_slots(enum gprs_rlcmac_tbf_direction dir, uint8_t slots);
-#endif
 };
 
 #ifdef __cplusplus
 extern "C" {
 #endif
+void bts_trx_reserve_slots(struct gprs_rlcmac_trx *trx, enum gprs_rlcmac_tbf_direction dir, uint8_t slots);
+void bts_trx_unreserve_slots(struct gprs_rlcmac_trx *trx, enum gprs_rlcmac_tbf_direction dir, uint8_t slots);
+
 void bts_update_tbf_ta(const char *p, uint32_t fn, uint8_t trx_no, uint8_t ts, int8_t ta, bool is_rach);
 #ifdef __cplusplus
 }
@@ -372,10 +371,11 @@
 
 	LListHead<gprs_rlcmac_tbf>& ul_tbfs();
 	LListHead<gprs_rlcmac_tbf>& dl_tbfs();
+
+	struct gprs_rlcmac_bts m_bts;
 private:
 	int m_cur_fn;
 	int m_cur_blk_fn;
-	struct gprs_rlcmac_bts m_bts;
 	uint8_t m_max_cs_dl, m_max_cs_ul;
 	uint8_t m_max_mcs_dl, m_max_mcs_ul;
 	PollController m_pollController;
@@ -459,11 +459,17 @@
 extern "C" {
 #endif
 	void bts_cleanup();
+	struct gprs_rlcmac_bts *bts_data(struct BTS *bts);
 	struct gprs_rlcmac_bts *bts_main_data();
 	struct rate_ctr_group *bts_main_data_stats();
 	struct osmo_stat_item_group *bts_main_data_stat_items();
 	void bts_set_max_cs(struct gprs_rlcmac_bts *bts, uint8_t cs_dl, uint8_t cs_ul);
 	void bts_set_max_mcs(struct gprs_rlcmac_bts *bts, uint8_t mcs_dl, uint8_t mcs_ul);
+	struct GprsMs *bts_ms_by_imsi(struct BTS *bts, const char *imsi);
+	uint8_t bts_max_cs_dl(const struct BTS *bts);
+	uint8_t bts_max_cs_ul(const struct BTS *bts);
+	uint8_t bts_max_mcs_dl(const struct BTS *bts);
+	uint8_t bts_max_mcs_ul(const struct BTS *bts);
 #ifdef __cplusplus
 }
 
diff --git a/src/encoding.cpp b/src/encoding.cpp
index a16962a..e7b1fb4 100644
--- a/src/encoding.cpp
+++ b/src/encoding.cpp
@@ -1388,7 +1388,7 @@
 	delimiter = data_block + *num_chunks;
 	e_pointer = (*num_chunks ? delimiter - 1 : NULL);
 
-	chunk = llc->chunk_size();
+	chunk = llc_chunk_size(llc);
 	space = rdbi->data_len - *offset;
 
 	/* if chunk will exceed block limit */
@@ -1402,7 +1402,7 @@
 			*e_pointer |= 0x02; /* set previous M bit = 1 */
 		}
 		/* fill only space */
-		llc->consume(data, space);
+		llc_consume_data(llc, data, space);
 		if (count_payload)
 			*count_payload = space;
 		/* return data block as message */
@@ -1421,7 +1421,7 @@
 		if (e_pointer)
 			*e_pointer |= 0x01;
 		/* fill space */
-		llc->consume(data, space);
+		llc_consume_data(llc, data, space);
 		if (count_payload)
 			*count_payload = space;
 		*offset = rdbi->data_len;
@@ -1454,7 +1454,7 @@
 		rdbi->e = 0; /* 0: extensions present */
 		// no need to set e_pointer nor increase delimiter
 		/* fill only space, which is 1 octet less than chunk */
-		llc->consume(data, space);
+		llc_consume_data(llc, data, space);
 		if (count_payload)
 			*count_payload = space;
 		/* return data block as message */
@@ -1485,7 +1485,7 @@
 	rdbi->e = 0; /* 0: extensions present */
 	(*num_chunks)++;
 	/* copy (rest of) LLC frame to space and reset later */
-	llc->consume(data, chunk);
+	llc_consume_data(llc, data, chunk);
 	if (count_payload)
 		*count_payload = chunk;
 	data += chunk;
@@ -1536,7 +1536,7 @@
 	prev_li = (struct rlc_li_field_egprs *)
 		(*num_chunks ? delimiter - 1 : NULL);
 
-	chunk = llc->chunk_size();
+	chunk = llc_chunk_size(llc);
 	space = rdbi->data_len - *offset;
 
 	/* if chunk will exceed block limit */
@@ -1546,7 +1546,7 @@
 			"only remaining space, and we are done\n",
 			chunk, space);
 		/* fill only space */
-		llc->consume(data, space);
+		llc_consume_data(llc, data, space);
 		if (count_payload)
 			*count_payload = space;
 		/* return data block as message */
@@ -1562,7 +1562,7 @@
 			"this is a final block, we don't add length "
 			"header, and we are done\n", chunk, space);
 		/* fill space */
-		llc->consume(data, space);
+		llc_consume_data(llc, data, space);
 		if (count_payload)
 			*count_payload = space;
 		*offset = rdbi->data_len;
@@ -1578,7 +1578,7 @@
 			"to start with an empty chunk\n",
 			chunk, space);
 		/* fill space */
-		llc->consume(data, space);
+		llc_consume_data(llc, data, space);
 		if (count_payload)
 			*count_payload = space;
 		*offset = rdbi->data_len;
@@ -1610,7 +1610,7 @@
 	prev_li = li;
 	(*num_chunks)++;
 	/* copy (rest of) LLC frame to space and reset later */
-	llc->consume(data, chunk);
+	llc_consume_data(llc, data, chunk);
 	if (count_payload)
 		*count_payload = chunk;
 	data += chunk;
diff --git a/src/gprs_ms.c b/src/gprs_ms.c
new file mode 100644
index 0000000..94f69cd
--- /dev/null
+++ b/src/gprs_ms.c
@@ -0,0 +1,885 @@
+/* gprs_ms.c
+ *
+ * Copyright (C) 2015-2020 by Sysmocom s.f.m.c. GmbH
+ * Author: Jacob Erlbeck <jerlbeck at sysmocom.de>
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+
+#include "gprs_ms.h"
+#include "bts.h"
+#include "tbf.h"
+#include "tbf_ul.h"
+#include "gprs_debug.h"
+#include "gprs_codel.h"
+#include "pcu_utils.h"
+
+#include <time.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/core/logging.h>
+#include "coding_scheme.h"
+
+#define GPRS_CODEL_SLOW_INTERVAL_MS 4000
+
+extern void *tall_pcu_ctx;
+
+static int64_t now_msec()
+{
+	struct timespec ts;
+	osmo_clock_gettime(CLOCK_MONOTONIC, &ts);
+
+	return (int64_t)(ts.tv_sec) * 1000 + ts.tv_nsec / 1000000;
+}
+
+void gprs_default_cb_ms_idle(struct GprsMs *ms)
+{
+	talloc_free(ms);
+}
+
+void gprs_default_cb_ms_active(struct GprsMs *ms)
+{
+	/* do nothing */
+}
+
+static struct gpr_ms_callback gprs_default_cb = {
+	.ms_idle = gprs_default_cb_ms_idle,
+	.ms_active = gprs_default_cb_ms_active,
+};
+
+void ms_timeout(void *data)
+{
+	struct GprsMs *ms = (struct GprsMs *) data;
+	LOGP(DRLCMAC, LOGL_INFO, "Timeout for MS object, TLLI = 0x%08x\n",
+		ms_tlli(ms));
+
+	if (ms->timer.data) {
+		ms->timer.data = NULL;
+		ms_unref(ms);
+	}
+}
+
+static int ms_talloc_destructor(struct GprsMs *ms);
+struct GprsMs *ms_alloc(struct BTS *bts, uint32_t tlli)
+{
+	struct GprsMs *ms = talloc_zero(tall_pcu_ctx, struct GprsMs);
+
+	talloc_set_destructor(ms, ms_talloc_destructor);
+
+	ms->bts = bts;
+	ms->cb = gprs_default_cb;
+	ms->tlli = tlli;
+	ms->new_ul_tlli = GSM_RESERVED_TMSI;
+	ms->new_dl_tlli = GSM_RESERVED_TMSI;
+	ms->ta = GSM48_TA_INVALID;
+	ms->current_cs_ul = UNKNOWN;
+	ms->current_cs_dl = UNKNOWN;
+	ms->is_idle = true;
+	INIT_LLIST_HEAD(&ms->list);
+	INIT_LLIST_HEAD(&ms->old_tbfs);
+
+	int codel_interval = LLC_CODEL_USE_DEFAULT;
+
+	LOGP(DRLCMAC, LOGL_INFO, "Creating MS object, TLLI = 0x%08x\n", tlli);
+
+	ms->imsi[0] = '\0';
+	memset(&ms->timer, 0, sizeof(ms->timer));
+	ms->timer.cb = ms_timeout;
+	llc_queue_init(&ms->llc_queue);
+
+	ms_set_mode(ms, GPRS);
+
+	if (ms->bts)
+		codel_interval = bts_data(ms->bts)->llc_codel_interval_msec;
+
+	if (codel_interval) {
+		if (codel_interval == LLC_CODEL_USE_DEFAULT)
+			codel_interval = GPRS_CODEL_SLOW_INTERVAL_MS;
+		ms->codel_state = talloc(ms, struct gprs_codel);
+		gprs_codel_init(ms->codel_state);
+		gprs_codel_set_interval(ms->codel_state, codel_interval);
+	}
+	ms->last_cs_not_low = now_msec();
+	ms->app_info_pending = false;
+	return ms;
+}
+
+static int ms_talloc_destructor(struct GprsMs *ms)
+{
+	struct llist_item *pos, *tmp;
+
+	LOGP(DRLCMAC, LOGL_INFO, "Destroying MS object, TLLI = 0x%08x\n", ms_tlli(ms));
+
+	ms_set_reserved_slots(ms, NULL, 0, 0);
+
+	if (osmo_timer_pending(&ms->timer))
+		osmo_timer_del(&ms->timer);
+
+	if (ms->ul_tbf) {
+		tbf_set_ms((struct gprs_rlcmac_tbf *)ms->ul_tbf, NULL);
+		ms->ul_tbf = NULL;
+	}
+
+	if (ms->dl_tbf) {
+		tbf_set_ms((struct gprs_rlcmac_tbf *)ms->dl_tbf, NULL);
+		ms->dl_tbf = NULL;
+	}
+
+	llist_for_each_entry_safe(pos, tmp, &ms->old_tbfs, list) {
+		struct gprs_rlcmac_tbf *tbf = (struct gprs_rlcmac_tbf *)pos->entry;
+		tbf_set_ms(tbf, NULL);
+	}
+
+	llc_queue_clear(&ms->llc_queue, ms->bts);
+	return 0;
+}
+
+
+void ms_set_callback(struct GprsMs *ms, struct gpr_ms_callback *cb)
+{
+	if (cb)
+		ms->cb = *cb;
+	else
+		ms->cb = gprs_default_cb;
+}
+
+static void ms_update_status(struct GprsMs *ms)
+{
+	if (ms->ref > 0)
+		return;
+
+	if (ms_is_idle(ms) && !ms->is_idle) {
+		ms->is_idle = true;
+		ms->cb.ms_idle(ms);
+		/* this can be deleted by now, do not access it */
+		return;
+	}
+
+	if (!ms_is_idle(ms) && ms->is_idle) {
+		ms->is_idle = false;
+		ms->cb.ms_active(ms);
+	}
+}
+
+struct GprsMs *ms_ref(struct GprsMs *ms)
+{
+	ms->ref += 1;
+	return ms;
+}
+
+void ms_unref(struct GprsMs *ms)
+{
+	OSMO_ASSERT(ms->ref >= 0);
+	ms->ref -= 1;
+	if (ms->ref == 0)
+		ms_update_status(ms);
+}
+
+void ms_start_timer(struct GprsMs *ms)
+{
+	if (ms->delay == 0)
+		return;
+
+	if (!ms->timer.data)
+		ms->timer.data = ms_ref(ms);
+
+	osmo_timer_schedule(&ms->timer, ms->delay, 0);
+}
+
+void ms_stop_timer(struct GprsMs *ms)
+{
+	if (!ms->timer.data)
+		return;
+
+	osmo_timer_del(&ms->timer);
+	ms->timer.data = NULL;
+	ms_unref(ms);
+}
+
+void ms_set_mode(struct GprsMs *ms, enum mcs_kind mode)
+{
+	ms->mode = mode;
+
+	if (!ms->bts)
+		return;
+
+	switch (ms->mode) {
+	case GPRS:
+		if (!mcs_is_gprs(ms->current_cs_ul)) {
+			ms->current_cs_ul = mcs_get_gprs_by_num(
+				bts_data(ms->bts)->initial_cs_ul);
+			if (!mcs_is_valid(ms->current_cs_ul))
+				ms->current_cs_ul = CS1;
+		}
+		if (!mcs_is_gprs(ms->current_cs_dl)) {
+			ms->current_cs_dl = mcs_get_gprs_by_num(
+				bts_data(ms->bts)->initial_cs_dl);
+			if (!mcs_is_valid(ms->current_cs_dl))
+				ms->current_cs_dl = CS1;
+		}
+		break;
+
+	case EGPRS_GMSK:
+	case EGPRS:
+		if (!mcs_is_edge(ms->current_cs_ul)) {
+			ms->current_cs_ul = mcs_get_egprs_by_num(
+				bts_data(ms->bts)->initial_mcs_ul);
+			if (!mcs_is_valid(ms->current_cs_ul))
+				ms->current_cs_ul = MCS1;
+		}
+		if (!mcs_is_edge(ms->current_cs_dl)) {
+			ms->current_cs_dl = mcs_get_egprs_by_num(
+				bts_data(ms->bts)->initial_mcs_dl);
+			if (!mcs_is_valid(ms->current_cs_dl))
+				ms->current_cs_dl = MCS1;
+		}
+		break;
+	}
+}
+
+static void ms_attach_ul_tbf(struct GprsMs *ms, struct gprs_rlcmac_ul_tbf *tbf)
+{
+	if (ms->ul_tbf == tbf)
+		return;
+
+	LOGP(DRLCMAC, LOGL_INFO, "Attaching TBF to MS object, TLLI = 0x%08x, TBF = %s\n",
+		ms_tlli(ms), tbf_name((struct gprs_rlcmac_tbf *)tbf));
+
+	ms_ref(ms);
+
+	if (ms->ul_tbf)
+		llist_add_tail(tbf_ms_list((struct gprs_rlcmac_tbf *)ms->ul_tbf), &ms->old_tbfs);
+
+	ms->ul_tbf = tbf;
+
+	if (tbf)
+		ms_stop_timer(ms);
+
+	ms_unref(ms);
+}
+
+static void ms_attach_dl_tbf(struct GprsMs *ms, struct gprs_rlcmac_dl_tbf *tbf)
+{
+	if (ms->dl_tbf == tbf)
+		return;
+
+	LOGP(DRLCMAC, LOGL_INFO, "Attaching TBF to MS object, TLLI = 0x%08x, TBF = %s\n",
+		ms_tlli(ms), tbf_name((struct gprs_rlcmac_tbf *)tbf));
+
+	ms_ref(ms);
+
+	if (ms->dl_tbf)
+		llist_add_tail(tbf_ms_list((struct gprs_rlcmac_tbf *)ms->dl_tbf), &ms->old_tbfs);
+
+	ms->dl_tbf = tbf;
+
+	if (tbf)
+		ms_stop_timer(ms);
+
+	ms_unref(ms);
+}
+
+void ms_attach_tbf(struct GprsMs *ms, struct gprs_rlcmac_tbf *tbf)
+{
+	if (tbf_direction(tbf) == GPRS_RLCMAC_DL_TBF)
+		ms_attach_dl_tbf(ms, as_dl_tbf(tbf));
+	else
+		ms_attach_ul_tbf(ms, as_ul_tbf(tbf));
+}
+
+void ms_detach_tbf(struct GprsMs *ms, struct gprs_rlcmac_tbf *tbf)
+{
+	if (tbf == (struct gprs_rlcmac_tbf *)(ms->ul_tbf)) {
+		ms->ul_tbf = NULL;
+	} else if (tbf == (struct gprs_rlcmac_tbf *)(ms->dl_tbf)) {
+		ms->dl_tbf = NULL;
+	} else {
+		bool found = false;
+
+		struct llist_item *pos, *tmp;
+		llist_for_each_entry_safe(pos, tmp, &ms->old_tbfs, list) {
+			struct gprs_rlcmac_tbf *tmp_tbf = (struct gprs_rlcmac_tbf *)pos->entry;
+			if (tmp_tbf == tbf) {
+				llist_del(&pos->list);
+				found = true;
+				break;
+			}
+		}
+
+		/* Protect against recursive calls via set_ms() */
+		if (!found)
+			return;
+	}
+
+	LOGP(DRLCMAC, LOGL_INFO, "Detaching TBF from MS object, TLLI = 0x%08x, TBF = %s\n",
+		ms_tlli(ms), tbf_name(tbf));
+
+	if (tbf_ms(tbf) == ms)
+		tbf_set_ms(tbf, NULL);
+
+	if (!ms->dl_tbf && !ms->ul_tbf) {
+		ms_set_reserved_slots(ms, NULL, 0, 0);
+
+		if (ms_tlli(ms) != 0)
+			ms_start_timer(ms);
+	}
+
+	ms_update_status(ms);
+}
+
+void ms_reset(struct GprsMs *ms)
+{
+	LOGP(DRLCMAC, LOGL_INFO,
+		"Clearing MS object, TLLI: 0x%08x, IMSI: '%s'\n",
+		ms_tlli(ms), ms_imsi(ms));
+
+	ms_stop_timer(ms);
+
+	ms->tlli = GSM_RESERVED_TMSI;
+	ms->new_dl_tlli = ms->tlli;
+	ms->new_ul_tlli = ms->tlli;
+	ms->imsi[0] = '\0';
+}
+
+static void ms_merge_old_ms(struct GprsMs *ms, struct GprsMs *old_ms)
+{
+	OSMO_ASSERT(old_ms != ms);
+
+	if (strlen(ms_imsi(ms)) == 0 && strlen(ms_imsi(old_ms)) != 0)
+		osmo_strlcpy(ms->imsi, ms_imsi(old_ms), sizeof(ms->imsi));
+
+	if (!ms_ms_class(ms) && ms_ms_class(old_ms))
+		ms_set_ms_class(ms, ms_ms_class(old_ms));
+
+	if (!ms_egprs_ms_class(ms) && ms_egprs_ms_class(old_ms))
+		ms_set_egprs_ms_class(ms, ms_egprs_ms_class(old_ms));
+
+	llc_queue_move_and_merge(&ms->llc_queue, &old_ms->llc_queue);
+
+	ms_reset(old_ms);
+}
+
+void ms_merge_and_clear_ms(struct GprsMs *ms, struct GprsMs *old_ms)
+{
+	OSMO_ASSERT(old_ms != ms);
+
+	ms_ref(old_ms);
+
+	/* Clean up the old MS object */
+	/* TODO: Use timer? */
+	if (ms_ul_tbf(old_ms) && !tbf_timers_pending((struct gprs_rlcmac_tbf *)ms_ul_tbf(old_ms), T_MAX))
+			tbf_free((struct gprs_rlcmac_tbf *)ms_ul_tbf(old_ms));
+	if (ms_dl_tbf(old_ms) && !tbf_timers_pending((struct gprs_rlcmac_tbf *)ms_dl_tbf(old_ms), T_MAX))
+			tbf_free((struct gprs_rlcmac_tbf *)ms_dl_tbf(old_ms));
+
+	ms_merge_old_ms(ms, old_ms);
+
+	ms_unref(old_ms);
+}
+
+void ms_set_tlli(struct GprsMs *ms, uint32_t tlli)
+{
+	if (tlli == ms->tlli || tlli == ms->new_ul_tlli)
+		return;
+
+	if (tlli != ms->new_dl_tlli) {
+		LOGP(DRLCMAC, LOGL_INFO,
+			"Modifying MS object, UL TLLI: 0x%08x -> 0x%08x, "
+			"not yet confirmed\n",
+			ms_tlli(ms), tlli);
+		ms->new_ul_tlli = tlli;
+		return;
+	}
+
+	LOGP(DRLCMAC, LOGL_INFO,
+		"Modifying MS object, TLLI: 0x%08x -> 0x%08x, "
+		"already confirmed partly\n",
+		ms->tlli, tlli);
+
+	ms->tlli = tlli;
+	ms->new_dl_tlli = GSM_RESERVED_TMSI;
+	ms->new_ul_tlli = GSM_RESERVED_TMSI;
+}
+
+bool ms_confirm_tlli(struct GprsMs *ms, uint32_t tlli)
+{
+	if (tlli == ms->tlli || tlli == ms->new_dl_tlli)
+		return false;
+
+	if (tlli != ms->new_ul_tlli) {
+		/* The MS has not sent a message with the new TLLI, which may
+		 * happen according to the spec [TODO: add reference]. */
+
+		LOGP(DRLCMAC, LOGL_INFO,
+			"The MS object cannot fully confirm an unexpected TLLI: 0x%08x, "
+			"partly confirmed\n", tlli);
+		/* Use the network's idea of TLLI as candidate, this does not
+		 * change the result value of tlli() */
+		ms->new_dl_tlli = tlli;
+		return false;
+	}
+
+	LOGP(DRLCMAC, LOGL_INFO,
+		"Modifying MS object, TLLI: 0x%08x confirmed\n", tlli);
+
+	ms->tlli = tlli;
+	ms->new_dl_tlli = GSM_RESERVED_TMSI;
+	ms->new_ul_tlli = GSM_RESERVED_TMSI;
+
+	return true;
+}
+
+void ms_set_imsi(struct GprsMs *ms, const char *imsi)
+{
+	if (!imsi) {
+		LOGP(DRLCMAC, LOGL_ERROR, "Expected IMSI!\n");
+		return;
+	}
+
+	if (imsi[0] && strlen(imsi) < 3) {
+		LOGP(DRLCMAC, LOGL_ERROR, "No valid IMSI '%s'!\n",
+			imsi);
+		return;
+	}
+
+	if (strcmp(imsi, ms->imsi) == 0)
+		return;
+
+	LOGP(DRLCMAC, LOGL_INFO,
+		"Modifying MS object, TLLI = 0x%08x, IMSI '%s' -> '%s'\n",
+		ms_tlli(ms), ms->imsi, imsi);
+
+	struct GprsMs *old_ms = bts_ms_by_imsi(ms->bts, imsi);
+	/* Check if we are going to store a different MS object with already
+	   existing IMSI. This is probably a bug in code calling this function,
+	   since it should take care of this explicitly */
+	if (old_ms) {
+		/* We cannot find ms->ms by IMSI since we know that it has a
+		* different IMSI */
+		OSMO_ASSERT(old_ms != ms);
+
+		LOGPMS(ms, DRLCMAC, LOGL_NOTICE,
+		       "IMSI '%s' was already assigned to another "
+		       "MS object: TLLI = 0x%08x, that IMSI will be removed\n",
+		       imsi, ms_tlli(old_ms));
+
+		ms_merge_and_clear_ms(ms, old_ms);
+	}
+
+
+	osmo_strlcpy(ms->imsi, imsi, sizeof(ms->imsi));
+}
+
+void ms_set_ta(struct GprsMs *ms, uint8_t ta_)
+{
+	if (ta_ == ms->ta)
+		return;
+
+	if (gsm48_ta_is_valid(ta_)) {
+		LOGP(DRLCMAC, LOGL_INFO,
+		     "Modifying MS object, TLLI = 0x%08x, TA %d -> %d\n",
+		     ms_tlli(ms), ms->ta, ta_);
+		ms->ta = ta_;
+	} else
+		LOGP(DRLCMAC, LOGL_NOTICE,
+		     "MS object, TLLI = 0x%08x, invalid TA %d rejected (old "
+		     "value %d kept)\n", ms_tlli(ms), ta_, ms->ta);
+}
+
+void ms_set_ms_class(struct GprsMs *ms, uint8_t ms_class_)
+{
+	if (ms_class_ == ms->ms_class)
+		return;
+
+	LOGP(DRLCMAC, LOGL_INFO,
+		"Modifying MS object, TLLI = 0x%08x, MS class %d -> %d\n",
+		ms_tlli(ms), ms->ms_class, ms_class_);
+
+	ms->ms_class = ms_class_;
+}
+
+void ms_set_egprs_ms_class(struct GprsMs *ms, uint8_t ms_class_)
+{
+	if (ms_class_ == ms->egprs_ms_class)
+		return;
+
+	LOGP(DRLCMAC, LOGL_INFO,
+		"Modifying MS object, TLLI = 0x%08x, EGPRS MS class %d -> %d\n",
+		ms_tlli(ms), ms->egprs_ms_class, ms_class_);
+
+	ms->egprs_ms_class = ms_class_;
+
+	if (!bts_max_mcs_ul(ms->bts) || !bts_max_mcs_dl(ms->bts)) {
+		LOGPMS(ms, DRLCMAC, LOGL_DEBUG,
+		       "Avoid enabling EGPRS because use of MCS is disabled: ul=%u dl=%u\n",
+			bts_max_mcs_ul(ms->bts), bts_max_mcs_dl(ms->bts));
+		return;
+	}
+
+	if (mcs_is_edge_gmsk(mcs_get_egprs_by_num(bts_max_mcs_ul(ms->bts))) &&
+		mcs_is_edge_gmsk(mcs_get_egprs_by_num(bts_max_mcs_dl(ms->bts))) &&
+		ms_mode(ms) != EGPRS)
+	{
+		ms_set_mode(ms, EGPRS_GMSK);
+	} else {
+		ms_set_mode(ms, EGPRS);
+	}
+	LOGPMS(ms, DRLCMAC, LOGL_INFO, "Enabled EGPRS, mode %s\n", mode_name(ms_mode(ms)));
+}
+
+void ms_update_error_rate(struct GprsMs *ms, struct gprs_rlcmac_tbf *tbf, int error_rate)
+{
+	struct gprs_rlcmac_bts *bts_;
+	int64_t now;
+	enum CodingScheme max_cs_dl = ms_max_cs_dl(ms);
+
+	OSMO_ASSERT(max_cs_dl);
+	bts_ = bts_data(ms->bts);
+
+	if (error_rate < 0)
+		return;
+
+	now = now_msec();
+
+	/* TODO: Check for TBF direction */
+	/* TODO: Support different CS values for UL and DL */
+
+	ms->nack_rate_dl = error_rate;
+
+	if (error_rate > bts_->cs_adj_upper_limit) {
+		if (mcs_chan_code(ms->current_cs_dl) > 0) {
+			mcs_dec_kind(&ms->current_cs_dl, ms_mode(ms));
+			LOGP(DRLCMACDL, LOGL_INFO,
+				"MS (IMSI %s): High error rate %d%%, "
+				"reducing CS level to %s\n",
+				ms_imsi(ms), error_rate, mcs_name(ms->current_cs_dl));
+			ms->last_cs_not_low = now;
+		}
+	} else if (error_rate < bts_->cs_adj_lower_limit) {
+		if (ms->current_cs_dl < max_cs_dl) {
+		       if (now - ms->last_cs_not_low > 1000) {
+			       mcs_inc_kind(&ms->current_cs_dl, ms_mode(ms));
+
+			       LOGP(DRLCMACDL, LOGL_INFO,
+				       "MS (IMSI %s): Low error rate %d%%, "
+				       "increasing DL CS level to %s\n",
+				       ms_imsi(ms), error_rate,
+				       mcs_name(ms->current_cs_dl));
+			       ms->last_cs_not_low = now;
+		       } else {
+			       LOGP(DRLCMACDL, LOGL_DEBUG,
+				       "MS (IMSI %s): Low error rate %d%%, "
+				       "ignored (within blocking period)\n",
+				       ms_imsi(ms), error_rate);
+		       }
+		}
+	} else {
+		LOGP(DRLCMACDL, LOGL_DEBUG,
+			"MS (IMSI %s): Medium error rate %d%%, ignored\n",
+			ms_imsi(ms), error_rate);
+		ms->last_cs_not_low = now;
+	}
+}
+
+enum CodingScheme ms_max_cs_ul(const struct GprsMs *ms)
+{
+	OSMO_ASSERT(ms->bts != NULL);
+
+	if (mcs_is_gprs(ms->current_cs_ul)) {
+		if (!bts_max_cs_ul(ms->bts)) {
+			return CS4;
+		}
+
+		return mcs_get_gprs_by_num(bts_max_cs_ul(ms->bts));
+	}
+
+	if (!mcs_is_edge(ms->current_cs_ul))
+		return UNKNOWN;
+
+	if (bts_max_mcs_ul(ms->bts))
+		return mcs_get_egprs_by_num(bts_max_mcs_ul(ms->bts));
+	else if (bts_max_cs_ul(ms->bts))
+		return mcs_get_gprs_by_num(bts_max_cs_ul(ms->bts));
+
+	return MCS4;
+}
+
+void ms_set_current_cs_dl(struct GprsMs *ms, enum CodingScheme scheme)
+{
+	ms->current_cs_dl = scheme;
+}
+
+enum CodingScheme ms_max_cs_dl(const struct GprsMs *ms)
+{
+	OSMO_ASSERT(ms->bts != NULL);
+
+	if (mcs_is_gprs(ms->current_cs_dl)) {
+		if (!bts_max_cs_dl(ms->bts)) {
+			return CS4;
+		}
+
+		return mcs_get_gprs_by_num(bts_max_cs_dl(ms->bts));
+	}
+
+	if (!mcs_is_edge(ms->current_cs_dl))
+		return UNKNOWN;
+
+	if (bts_max_mcs_dl(ms->bts))
+		return mcs_get_egprs_by_num(bts_max_mcs_dl(ms->bts));
+	else if (bts_max_cs_dl(ms->bts))
+		return mcs_get_gprs_by_num(bts_max_cs_dl(ms->bts));
+
+	return MCS4;
+}
+
+void ms_update_cs_ul(struct GprsMs *ms, const struct pcu_l1_meas *meas)
+{
+	struct gprs_rlcmac_bts *bts_;
+	enum CodingScheme max_cs_ul = ms_max_cs_ul(ms);
+
+	int old_link_qual;
+	int low;
+	int high;
+	enum CodingScheme new_cs_ul = ms->current_cs_ul;
+	uint8_t current_cs = mcs_chan_code(ms->current_cs_ul);
+
+	bts_ = bts_data(ms->bts);
+
+	if (!max_cs_ul) {
+		LOGP(DRLCMACMEAS, LOGL_ERROR,
+			"max_cs_ul cannot be derived (current UL CS: %s)\n",
+			mcs_name(ms->current_cs_ul));
+		return;
+	}
+
+	if (!ms->current_cs_ul) {
+		LOGP(DRLCMACMEAS, LOGL_ERROR,
+		     "Unable to update UL (M)CS because it's not set: %s\n",
+		     mcs_name(ms->current_cs_ul));
+		return;
+	}
+
+	if (!meas->have_link_qual) {
+		LOGP(DRLCMACMEAS, LOGL_ERROR,
+		     "Unable to update UL (M)CS %s because we don't have link quality measurements.\n",
+		     mcs_name(ms->current_cs_ul));
+		return;
+	}
+
+	if (mcs_is_gprs(ms->current_cs_ul)) {
+		if (current_cs >= MAX_GPRS_CS)
+			current_cs = MAX_GPRS_CS - 1;
+		low  = bts_->cs_lqual_ranges[current_cs].low;
+		high = bts_->cs_lqual_ranges[current_cs].high;
+	} else if (mcs_is_edge(ms->current_cs_ul)) {
+		if (current_cs >= MAX_EDGE_MCS)
+			current_cs = MAX_EDGE_MCS - 1;
+		low  = bts_->mcs_lqual_ranges[current_cs].low;
+		high = bts_->mcs_lqual_ranges[current_cs].high;
+	} else {
+		LOGP(DRLCMACMEAS, LOGL_ERROR,
+		     "Unable to update UL (M)CS because it's neither GPRS nor EDGE: %s\n",
+		     mcs_name(ms->current_cs_ul));
+		return;
+	}
+
+	/* To avoid rapid changes of the coding scheme, we also take
+	 * the old link quality value into account (if present). */
+	if (ms->l1_meas.have_link_qual)
+		old_link_qual = ms->l1_meas.link_qual;
+	else
+		old_link_qual = meas->link_qual;
+
+	if (meas->link_qual < low &&  old_link_qual < low)
+		mcs_dec_kind(&new_cs_ul, ms_mode(ms));
+	else if (meas->link_qual > high &&  old_link_qual > high &&
+		ms->current_cs_ul < max_cs_ul)
+		mcs_inc_kind(&new_cs_ul, ms_mode(ms));
+
+	if (ms->current_cs_ul != new_cs_ul) {
+		LOGPMS(ms, DRLCMACMEAS, LOGL_INFO,
+		       "Link quality %ddB (old %ddB) left window [%d, %d], "
+		       "modifying uplink CS level: %s -> %s\n",
+		       meas->link_qual, old_link_qual,
+		       low, high,
+		       mcs_name(ms->current_cs_ul), mcs_name(new_cs_ul));
+
+		ms->current_cs_ul = new_cs_ul;
+	}
+}
+
+void ms_update_l1_meas(struct GprsMs *ms, const struct pcu_l1_meas *meas)
+{
+	unsigned i;
+
+	ms_update_cs_ul(ms, meas);
+
+	if (meas->have_rssi)
+		pcu_l1_meas_set_rssi(&ms->l1_meas, meas->rssi);
+	if (meas->have_bto)
+		pcu_l1_meas_set_bto(&ms->l1_meas, meas->bto);
+	if (meas->have_ber)
+		pcu_l1_meas_set_ber(&ms->l1_meas, meas->ber);
+	if (meas->have_link_qual)
+		pcu_l1_meas_set_link_qual(&ms->l1_meas, meas->link_qual);
+
+	if (meas->have_ms_rx_qual)
+		pcu_l1_meas_set_ms_rx_qual(&ms->l1_meas, meas->ms_rx_qual);
+	if (meas->have_ms_c_value)
+		pcu_l1_meas_set_ms_c_value(&ms->l1_meas, meas->ms_c_value);
+	if (meas->have_ms_sign_var)
+		pcu_l1_meas_set_ms_sign_var(&ms->l1_meas, meas->ms_sign_var);
+
+	if (meas->have_ms_i_level) {
+		for (i = 0; i < ARRAY_SIZE(meas->ts); ++i) {
+			if (meas->ts[i].have_ms_i_level)
+				pcu_l1_meas_set_ms_i_level(&ms->l1_meas, i, meas->ts[i].ms_i_level);
+			else
+				ms->l1_meas.ts[i].have_ms_i_level = 0;
+		}
+	}
+}
+
+enum CodingScheme ms_current_cs_dl(const struct GprsMs *ms)
+{
+	enum CodingScheme cs = ms->current_cs_dl;
+	size_t unencoded_octets;
+
+	if (!ms->bts)
+		return cs;
+
+	unencoded_octets = llc_queue_octets(&ms->llc_queue);
+
+	/* If the DL TBF is active, add number of unencoded chunk octets */
+	if (ms->dl_tbf)
+		unencoded_octets += llc_chunk_size(tbf_llc((struct gprs_rlcmac_tbf *)ms->dl_tbf));
+
+	/* There are many unencoded octets, don't reduce */
+	if (unencoded_octets >= bts_data(ms->bts)->cs_downgrade_threshold)
+		return cs;
+
+	/* RF conditions are good, don't reduce */
+	if (ms->nack_rate_dl < bts_data(ms->bts)->cs_adj_lower_limit)
+		return cs;
+
+	/* The throughput would probably be better if the CS level was reduced */
+	mcs_dec_kind(&cs, ms_mode(ms));
+
+	/* CS-2 doesn't gain throughput with small packets, further reduce to CS-1 */
+	if (cs == CS2)
+		mcs_dec_kind(&cs, ms_mode(ms));
+
+	return cs;
+}
+
+int ms_first_common_ts(const struct GprsMs *ms)
+{
+	if (ms->dl_tbf)
+		return tbf_first_common_ts((struct gprs_rlcmac_tbf *)ms->dl_tbf);
+
+	if (ms->ul_tbf)
+		return tbf_first_common_ts((struct gprs_rlcmac_tbf *)ms->ul_tbf);
+
+	return -1;
+}
+
+uint8_t ms_dl_slots(const struct GprsMs *ms)
+{
+	uint8_t slots = 0;
+
+	if (ms->dl_tbf)
+		slots |= tbf_dl_slots((struct gprs_rlcmac_tbf *)ms->dl_tbf);
+
+	if (ms->ul_tbf)
+		slots |= tbf_dl_slots((struct gprs_rlcmac_tbf *)ms->ul_tbf);
+
+	return slots;
+}
+
+uint8_t ms_ul_slots(const struct GprsMs *ms)
+{
+	uint8_t slots = 0;
+
+	if (ms->dl_tbf)
+		slots |= tbf_ul_slots((struct gprs_rlcmac_tbf *)ms->dl_tbf);
+
+	if (ms->ul_tbf)
+		slots |= tbf_ul_slots((struct gprs_rlcmac_tbf *)ms->ul_tbf);
+
+	return slots;
+}
+
+uint8_t ms_current_pacch_slots(const struct GprsMs *ms)
+{
+	uint8_t slots = 0;
+
+	bool is_dl_active = ms->dl_tbf && tbf_is_tfi_assigned((struct gprs_rlcmac_tbf *)ms->dl_tbf);
+	bool is_ul_active = ms->ul_tbf && tbf_is_tfi_assigned((struct gprs_rlcmac_tbf *)ms->ul_tbf);
+
+	if (!is_dl_active && !is_ul_active)
+		return 0;
+
+	/* see TS 44.060, 8.1.1.2.2 */
+	if (is_dl_active && !is_ul_active)
+		slots =  tbf_dl_slots((struct gprs_rlcmac_tbf *)ms->dl_tbf);
+	else if (!is_dl_active && is_ul_active)
+		slots =  tbf_ul_slots((struct gprs_rlcmac_tbf *)ms->ul_tbf);
+	else
+		slots =  tbf_ul_slots((struct gprs_rlcmac_tbf *)ms->ul_tbf) &
+			 tbf_dl_slots((struct gprs_rlcmac_tbf *)ms->dl_tbf);
+
+	/* Assume a multislot class 1 device */
+	/* TODO: For class 2 devices, this could be removed */
+	slots = pcu_lsb(slots);
+
+	return slots;
+}
+
+void ms_set_reserved_slots(struct GprsMs *ms, struct gprs_rlcmac_trx *trx,
+	uint8_t ul_slots, uint8_t dl_slots)
+{
+	if (ms->current_trx) {
+		bts_trx_unreserve_slots(ms->current_trx, GPRS_RLCMAC_DL_TBF,
+			ms->reserved_dl_slots);
+		bts_trx_unreserve_slots(ms->current_trx, GPRS_RLCMAC_UL_TBF,
+			ms->reserved_ul_slots);
+		ms->reserved_dl_slots = 0;
+		ms->reserved_ul_slots = 0;
+	}
+	ms->current_trx = trx;
+	if (trx) {
+		ms->reserved_dl_slots = dl_slots;
+		ms->reserved_ul_slots = ul_slots;
+		bts_trx_reserve_slots(ms->current_trx, GPRS_RLCMAC_DL_TBF,
+			ms->reserved_dl_slots);
+		bts_trx_reserve_slots(ms->current_trx, GPRS_RLCMAC_UL_TBF,
+			ms->reserved_ul_slots);
+	}
+}
+
+struct gprs_rlcmac_tbf *ms_tbf(const struct GprsMs *ms, enum gprs_rlcmac_tbf_direction dir)
+{
+	switch (dir) {
+	case GPRS_RLCMAC_DL_TBF: return (struct gprs_rlcmac_tbf *)ms->dl_tbf;
+	case GPRS_RLCMAC_UL_TBF: return (struct gprs_rlcmac_tbf *)ms->ul_tbf;
+	}
+
+	return NULL;
+}
diff --git a/src/gprs_ms.cpp b/src/gprs_ms.cpp
deleted file mode 100644
index c891cdf..0000000
--- a/src/gprs_ms.cpp
+++ /dev/null
@@ -1,900 +0,0 @@
-/* gprs_ms.cpp
- *
- * Copyright (C) 2015 by Sysmocom s.f.m.c. GmbH
- * Author: Jacob Erlbeck <jerlbeck at sysmocom.de>
- *
- * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
- */
-
-
-#include "gprs_ms.h"
-#include "bts.h"
-#include "tbf.h"
-#include "tbf_ul.h"
-#include "gprs_debug.h"
-#include "gprs_codel.h"
-#include "pcu_utils.h"
-
-#include <time.h>
-
-extern "C" {
-	#include <osmocom/core/talloc.h>
-	#include <osmocom/core/utils.h>
-	#include <osmocom/core/timer.h>
-	#include <osmocom/gsm/protocol/gsm_04_08.h>
-	#include <osmocom/gsm/gsm48.h>
-	#include <osmocom/core/logging.h>
-	#include "coding_scheme.h"
-}
-
-#define GPRS_CODEL_SLOW_INTERVAL_MS 4000
-
-extern void *tall_pcu_ctx;
-
-static int64_t now_msec()
-{
-	struct timespec ts;
-	osmo_clock_gettime(CLOCK_MONOTONIC, &ts);
-
-	return int64_t(ts.tv_sec) * 1000 + ts.tv_nsec / 1000000;
-}
-
-struct GprsMsDefaultCallback: public GprsMs::Callback {
-	virtual void ms_idle(class GprsMs *ms) {
-		delete ms;
-	}
-	virtual void ms_active(class GprsMs *) {}
-};
-
-static GprsMsDefaultCallback gprs_default_cb;
-
-GprsMs::Guard::Guard(GprsMs *ms) :
-	m_ms(ms ? ms->ref() : NULL)
-{
-}
-
-GprsMs::Guard::~Guard()
-{
-	if (m_ms)
-		m_ms->unref();
-}
-
-bool GprsMs::Guard::is_idle() const
-{
-	if (!m_ms)
-		return true;
-
-	return !m_ms->m_ul_tbf && !m_ms->m_dl_tbf && m_ms->m_ref == 1;
-}
-
-void GprsMs::timeout(void *priv_)
-{
-	GprsMs *ms = static_cast<GprsMs *>(priv_);
-
-	LOGP(DRLCMAC, LOGL_INFO, "Timeout for MS object, TLLI = 0x%08x\n",
-		ms->tlli());
-
-	if (ms->m_timer.data) {
-		ms->m_timer.data = NULL;
-		ms->unref();
-	}
-}
-
-GprsMs::GprsMs(BTS *bts, uint32_t tlli) :
-	m_bts(bts),
-	m_cb(&gprs_default_cb),
-	m_ul_tbf(NULL),
-	m_dl_tbf(NULL),
-	m_tlli(tlli),
-	m_new_ul_tlli(GSM_RESERVED_TMSI),
-	m_new_dl_tlli(GSM_RESERVED_TMSI),
-	m_ta(GSM48_TA_INVALID),
-	m_ms_class(0),
-	m_egprs_ms_class(0),
-	m_current_cs_ul(UNKNOWN),
-	m_current_cs_dl(UNKNOWN),
-	m_is_idle(true),
-	m_ref(0),
-	m_list(this),
-	m_delay(0),
-	m_nack_rate_dl(0),
-	m_reserved_dl_slots(0),
-	m_reserved_ul_slots(0),
-	m_current_trx(NULL),
-	m_codel_state(NULL),
-	m_mode(GPRS),
-	m_dl_ctrl_msg(0)
-{
-	int codel_interval = LLC_CODEL_USE_DEFAULT;
-
-	LOGP(DRLCMAC, LOGL_INFO, "Creating MS object, TLLI = 0x%08x\n", tlli);
-
-	m_imsi[0] = '\0';
-	memset(&m_timer, 0, sizeof(m_timer));
-	m_timer.cb = GprsMs::timeout;
-	m_llc_queue.init();
-
-	set_mode(m_mode);
-
-	if (m_bts)
-		codel_interval = m_bts->bts_data()->llc_codel_interval_msec;
-
-	if (codel_interval) {
-		if (codel_interval == LLC_CODEL_USE_DEFAULT)
-			codel_interval = GPRS_CODEL_SLOW_INTERVAL_MS;
-		m_codel_state = talloc(this, struct gprs_codel);
-		gprs_codel_init(m_codel_state);
-		gprs_codel_set_interval(m_codel_state, codel_interval);
-	}
-	m_last_cs_not_low = now_msec();
-	app_info_pending = false;
-}
-
-GprsMs::~GprsMs()
-{
-	LListHead<gprs_rlcmac_tbf> *pos, *tmp;
-
-	LOGP(DRLCMAC, LOGL_INFO, "Destroying MS object, TLLI = 0x%08x\n", tlli());
-
-	set_reserved_slots(NULL, 0, 0);
-
-	if (osmo_timer_pending(&m_timer))
-		osmo_timer_del(&m_timer);
-
-	if (m_ul_tbf) {
-		m_ul_tbf->set_ms(NULL);
-		m_ul_tbf = NULL;
-	}
-
-	if (m_dl_tbf) {
-		m_dl_tbf->set_ms(NULL);
-		m_dl_tbf = NULL;
-	}
-
-	llist_for_each_safe(pos, tmp, &m_old_tbfs)
-		pos->entry()->set_ms(NULL);
-
-	m_llc_queue.clear(m_bts);
-}
-
-void* GprsMs::operator new(size_t size)
-{
-	static void *tall_ms_ctx = NULL;
-	if (!tall_ms_ctx)
-		tall_ms_ctx = talloc_named_const(tall_pcu_ctx, 0, __PRETTY_FUNCTION__);
-
-	return talloc_size(tall_ms_ctx, size);
-}
-
-void GprsMs::operator delete(void* p)
-{
-	talloc_free(p);
-}
-
-GprsMs *GprsMs::ref()
-{
-	m_ref += 1;
-	return this;
-}
-
-void GprsMs::unref()
-{
-	OSMO_ASSERT(m_ref >= 0);
-	m_ref -= 1;
-	if (m_ref == 0)
-		update_status();
-}
-
-void GprsMs::start_timer()
-{
-	if (m_delay == 0)
-		return;
-
-	if (!m_timer.data)
-		m_timer.data = ref();
-
-	osmo_timer_schedule(&m_timer, m_delay, 0);
-}
-
-void GprsMs::stop_timer()
-{
-	if (!m_timer.data)
-		return;
-
-	osmo_timer_del(&m_timer);
-	m_timer.data = NULL;
-	unref();
-}
-
-void GprsMs::set_mode(enum mcs_kind mode)
-{
-	m_mode = mode;
-
-	if (!m_bts)
-		return;
-
-	switch (m_mode) {
-	case GPRS:
-		if (!mcs_is_gprs(m_current_cs_ul)) {
-			m_current_cs_ul = mcs_get_gprs_by_num(
-				m_bts->bts_data()->initial_cs_ul);
-			if (!mcs_is_valid(m_current_cs_ul))
-				m_current_cs_ul = CS1;
-		}
-		if (!mcs_is_gprs(m_current_cs_dl)) {
-			m_current_cs_dl = mcs_get_gprs_by_num(
-				m_bts->bts_data()->initial_cs_dl);
-			if (!mcs_is_valid(m_current_cs_dl))
-				m_current_cs_dl = CS1;
-		}
-		break;
-
-	case EGPRS_GMSK:
-	case EGPRS:
-		if (!mcs_is_edge(m_current_cs_ul)) {
-			m_current_cs_ul = mcs_get_egprs_by_num(
-				m_bts->bts_data()->initial_mcs_ul);
-			if (!mcs_is_valid(m_current_cs_ul))
-				m_current_cs_ul = MCS1;
-		}
-		if (!mcs_is_edge(m_current_cs_dl)) {
-			m_current_cs_dl = mcs_get_egprs_by_num(
-				m_bts->bts_data()->initial_mcs_dl);
-			if (!mcs_is_valid(m_current_cs_dl))
-				m_current_cs_dl = MCS1;
-		}
-		break;
-	}
-}
-
-void GprsMs::attach_tbf(struct gprs_rlcmac_tbf *tbf)
-{
-	if (tbf->direction == GPRS_RLCMAC_DL_TBF)
-		attach_dl_tbf(as_dl_tbf(tbf));
-	else
-		attach_ul_tbf(as_ul_tbf(tbf));
-}
-
-void GprsMs::attach_ul_tbf(struct gprs_rlcmac_ul_tbf *tbf)
-{
-	if (m_ul_tbf == tbf)
-		return;
-
-	LOGP(DRLCMAC, LOGL_INFO, "Attaching TBF to MS object, TLLI = 0x%08x, TBF = %s\n",
-		tlli(), tbf->name());
-
-	Guard guard(this);
-
-	if (m_ul_tbf)
-		llist_add_tail(&m_ul_tbf->ms_list(), &m_old_tbfs);
-
-	m_ul_tbf = tbf;
-
-	if (tbf)
-		stop_timer();
-}
-
-void GprsMs::attach_dl_tbf(struct gprs_rlcmac_dl_tbf *tbf)
-{
-	if (m_dl_tbf == tbf)
-		return;
-
-	LOGP(DRLCMAC, LOGL_INFO, "Attaching TBF to MS object, TLLI = 0x%08x, TBF = %s\n",
-		tlli(), tbf->name());
-
-	Guard guard(this);
-
-	if (m_dl_tbf)
-		llist_add_tail(&m_dl_tbf->ms_list(), &m_old_tbfs);
-
-	m_dl_tbf = tbf;
-
-	if (tbf)
-		stop_timer();
-}
-
-void GprsMs::detach_tbf(gprs_rlcmac_tbf *tbf)
-{
-	if (tbf == static_cast<gprs_rlcmac_tbf *>(m_ul_tbf)) {
-		m_ul_tbf = NULL;
-	} else if (tbf == static_cast<gprs_rlcmac_tbf *>(m_dl_tbf)) {
-		m_dl_tbf = NULL;
-	} else {
-		bool found = false;
-
-		LListHead<gprs_rlcmac_tbf> *pos, *tmp;
-		llist_for_each_safe(pos, tmp, &m_old_tbfs) {
-			if (pos->entry() == tbf) {
-				llist_del(pos);
-				found = true;
-				break;
-			}
-		}
-
-		/* Protect against recursive calls via set_ms() */
-		if (!found)
-			return;
-	}
-
-	LOGP(DRLCMAC, LOGL_INFO, "Detaching TBF from MS object, TLLI = 0x%08x, TBF = %s\n",
-		tlli(), tbf->name());
-
-	if (tbf->ms() == this)
-		tbf->set_ms(NULL);
-
-	if (!m_dl_tbf && !m_ul_tbf) {
-		set_reserved_slots(NULL, 0, 0);
-
-		if (tlli() != 0)
-			start_timer();
-	}
-
-	update_status();
-}
-
-void GprsMs::update_status()
-{
-	if (m_ref > 0)
-		return;
-
-	if (is_idle() && !m_is_idle) {
-		m_is_idle = true;
-		m_cb->ms_idle(this);
-		/* this can be deleted by now, do not access it */
-		return;
-	}
-
-	if (!is_idle() && m_is_idle) {
-		m_is_idle = false;
-		m_cb->ms_active(this);
-	}
-}
-
-void GprsMs::reset()
-{
-	LOGP(DRLCMAC, LOGL_INFO,
-		"Clearing MS object, TLLI: 0x%08x, IMSI: '%s'\n",
-		tlli(), imsi());
-
-	stop_timer();
-
-	m_tlli = GSM_RESERVED_TMSI;
-	m_new_dl_tlli = m_tlli;
-	m_new_ul_tlli = m_tlli;
-	m_imsi[0] = '\0';
-}
-
-void GprsMs::merge_old_ms(GprsMs *old_ms)
-{
-	OSMO_ASSERT(old_ms != this);
-
-	if (strlen(imsi()) == 0 && strlen(old_ms->imsi()) != 0)
-		osmo_strlcpy(m_imsi, old_ms->imsi(), sizeof(m_imsi));
-
-	if (!ms_class() && old_ms->ms_class())
-		set_ms_class(old_ms->ms_class());
-
-	if (!egprs_ms_class() && old_ms->egprs_ms_class())
-		set_egprs_ms_class(old_ms->egprs_ms_class());
-
-	m_llc_queue.move_and_merge(&old_ms->m_llc_queue);
-
-	old_ms->reset();
-}
-
-void GprsMs::merge_and_clear_ms(GprsMs *old_ms)
-{
-	OSMO_ASSERT(old_ms != this);
-
-	GprsMs::Guard guard_old(old_ms);
-
-	/* Clean up the old MS object */
-	/* TODO: Use timer? */
-	if (old_ms->ul_tbf() && !old_ms->ul_tbf()->timers_pending(T_MAX))
-			tbf_free(old_ms->ul_tbf());
-	if (old_ms->dl_tbf() && !old_ms->dl_tbf()->timers_pending(T_MAX))
-			tbf_free(old_ms->dl_tbf());
-
-	merge_old_ms(old_ms);
-}
-
-void GprsMs::set_tlli(uint32_t tlli)
-{
-	if (tlli == m_tlli || tlli == m_new_ul_tlli)
-		return;
-
-	if (tlli != m_new_dl_tlli) {
-		LOGP(DRLCMAC, LOGL_INFO,
-			"Modifying MS object, UL TLLI: 0x%08x -> 0x%08x, "
-			"not yet confirmed\n",
-			this->tlli(), tlli);
-		m_new_ul_tlli = tlli;
-		return;
-	}
-
-	LOGP(DRLCMAC, LOGL_INFO,
-		"Modifying MS object, TLLI: 0x%08x -> 0x%08x, "
-		"already confirmed partly\n",
-		m_tlli, tlli);
-
-	m_tlli = tlli;
-	m_new_dl_tlli = GSM_RESERVED_TMSI;
-	m_new_ul_tlli = GSM_RESERVED_TMSI;
-}
-
-bool GprsMs::confirm_tlli(uint32_t tlli)
-{
-	if (tlli == m_tlli || tlli == m_new_dl_tlli)
-		return false;
-
-	if (tlli != m_new_ul_tlli) {
-		/* The MS has not sent a message with the new TLLI, which may
-		 * happen according to the spec [TODO: add reference]. */
-
-		LOGP(DRLCMAC, LOGL_INFO,
-			"The MS object cannot fully confirm an unexpected TLLI: 0x%08x, "
-			"partly confirmed\n", tlli);
-		/* Use the network's idea of TLLI as candidate, this does not
-		 * change the result value of tlli() */
-		m_new_dl_tlli = tlli;
-		return false;
-	}
-
-	LOGP(DRLCMAC, LOGL_INFO,
-		"Modifying MS object, TLLI: 0x%08x confirmed\n", tlli);
-
-	m_tlli = tlli;
-	m_new_dl_tlli = GSM_RESERVED_TMSI;
-	m_new_ul_tlli = GSM_RESERVED_TMSI;
-
-	return true;
-}
-
-void GprsMs::set_imsi(const char *imsi)
-{
-	if (!imsi) {
-		LOGP(DRLCMAC, LOGL_ERROR, "Expected IMSI!\n");
-		return;
-	}
-
-	if (imsi[0] && strlen(imsi) < 3) {
-		LOGP(DRLCMAC, LOGL_ERROR, "No valid IMSI '%s'!\n",
-			imsi);
-		return;
-	}
-
-	if (strcmp(imsi, m_imsi) == 0)
-		return;
-
-	LOGP(DRLCMAC, LOGL_INFO,
-		"Modifying MS object, TLLI = 0x%08x, IMSI '%s' -> '%s'\n",
-		tlli(), m_imsi, imsi);
-
-	GprsMs *old_ms = m_bts->ms_store().get_ms(0, 0, imsi);
-	/* Check if we are going to store a different MS object with already
-	   existing IMSI. This is probably a bug in code calling this function,
-	   since it should take care of this explicitly */
-	if (old_ms) {
-		/* We cannot find m_ms by IMSI since we know that it has a
-		* different IMSI */
-		OSMO_ASSERT(old_ms != this);
-
-		LOGPMS(this, DRLCMAC, LOGL_NOTICE,
-		       "IMSI '%s' was already assigned to another "
-		       "MS object: TLLI = 0x%08x, that IMSI will be removed\n",
-		       imsi, old_ms->tlli());
-
-		merge_and_clear_ms(old_ms);
-	}
-
-
-	osmo_strlcpy(m_imsi, imsi, sizeof(m_imsi));
-}
-
-void GprsMs::set_ta(uint8_t ta_)
-{
-	if (ta_ == m_ta)
-		return;
-
-	if (gsm48_ta_is_valid(ta_)) {
-		LOGP(DRLCMAC, LOGL_INFO,
-		     "Modifying MS object, TLLI = 0x%08x, TA %d -> %d\n",
-		     tlli(), m_ta, ta_);
-		m_ta = ta_;
-	} else
-		LOGP(DRLCMAC, LOGL_NOTICE,
-		     "MS object, TLLI = 0x%08x, invalid TA %d rejected (old "
-		     "value %d kept)\n", tlli(), ta_, m_ta);
-}
-
-void GprsMs::set_ms_class(uint8_t ms_class_)
-{
-	if (ms_class_ == m_ms_class)
-		return;
-
-	LOGP(DRLCMAC, LOGL_INFO,
-		"Modifying MS object, TLLI = 0x%08x, MS class %d -> %d\n",
-		tlli(), m_ms_class, ms_class_);
-
-	m_ms_class = ms_class_;
-}
-
-void GprsMs::set_egprs_ms_class(uint8_t ms_class_)
-{
-	if (ms_class_ == m_egprs_ms_class)
-		return;
-
-	LOGP(DRLCMAC, LOGL_INFO,
-		"Modifying MS object, TLLI = 0x%08x, EGPRS MS class %d -> %d\n",
-		tlli(), m_egprs_ms_class, ms_class_);
-
-	m_egprs_ms_class = ms_class_;
-
-	if (!m_bts->max_mcs_ul() || !m_bts->max_mcs_dl()) {
-		LOGPMS(this, DRLCMAC, LOGL_DEBUG,
-		       "Avoid enabling EGPRS because use of MCS is disabled: ul=%u dl=%u\n",
-			m_bts->max_mcs_ul(), m_bts->max_mcs_dl());
-		return;
-	}
-
-	if (mcs_is_edge_gmsk(mcs_get_egprs_by_num(m_bts->max_mcs_ul())) &&
-		mcs_is_edge_gmsk(mcs_get_egprs_by_num(m_bts->max_mcs_dl())) &&
-		mode() != EGPRS)
-	{
-		set_mode(EGPRS_GMSK);
-	} else {
-		set_mode(EGPRS);
-	}
-	LOGPMS(this, DRLCMAC, LOGL_INFO, "Enabled EGPRS, mode %s\n", mode_name(mode()));
-}
-
-void GprsMs::update_error_rate(gprs_rlcmac_tbf *tbf, int error_rate)
-{
-	struct gprs_rlcmac_bts *bts_data;
-	int64_t now;
-	enum CodingScheme max_cs_dl = this->max_cs_dl();
-
-	OSMO_ASSERT(max_cs_dl);
-	bts_data = m_bts->bts_data();
-
-	if (error_rate < 0)
-		return;
-
-	now = now_msec();
-
-	/* TODO: Check for TBF direction */
-	/* TODO: Support different CS values for UL and DL */
-
-	m_nack_rate_dl = error_rate;
-
-	if (error_rate > bts_data->cs_adj_upper_limit) {
-		if (mcs_chan_code(m_current_cs_dl) > 0) {
-			mcs_dec_kind(&m_current_cs_dl, mode());
-			LOGP(DRLCMACDL, LOGL_INFO,
-				"MS (IMSI %s): High error rate %d%%, "
-				"reducing CS level to %s\n",
-				imsi(), error_rate, mcs_name(m_current_cs_dl));
-			m_last_cs_not_low = now;
-		}
-	} else if (error_rate < bts_data->cs_adj_lower_limit) {
-		if (m_current_cs_dl < max_cs_dl) {
-		       if (now - m_last_cs_not_low > 1000) {
-			       mcs_inc_kind(&m_current_cs_dl, mode());
-
-			       LOGP(DRLCMACDL, LOGL_INFO,
-				       "MS (IMSI %s): Low error rate %d%%, "
-				       "increasing DL CS level to %s\n",
-				       imsi(), error_rate,
-				       mcs_name(m_current_cs_dl));
-			       m_last_cs_not_low = now;
-		       } else {
-			       LOGP(DRLCMACDL, LOGL_DEBUG,
-				       "MS (IMSI %s): Low error rate %d%%, "
-				       "ignored (within blocking period)\n",
-				       imsi(), error_rate);
-		       }
-		}
-	} else {
-		LOGP(DRLCMACDL, LOGL_DEBUG,
-			"MS (IMSI %s): Medium error rate %d%%, ignored\n",
-			imsi(), error_rate);
-		m_last_cs_not_low = now;
-	}
-}
-
-enum CodingScheme GprsMs::max_cs_ul() const
-{
-	OSMO_ASSERT(m_bts != NULL);
-
-	if (mcs_is_gprs(m_current_cs_ul)) {
-		if (!m_bts->max_cs_ul()) {
-			return CS4;
-		}
-
-		return mcs_get_gprs_by_num(m_bts->max_cs_ul());
-	}
-
-	if (!mcs_is_edge(m_current_cs_ul))
-		return UNKNOWN;
-
-	if (m_bts->max_mcs_ul())
-		return mcs_get_egprs_by_num(m_bts->max_mcs_ul());
-	else if (m_bts->max_cs_ul())
-		return mcs_get_gprs_by_num(m_bts->max_cs_ul());
-
-	return MCS4;
-}
-
-void GprsMs::set_current_cs_dl(enum CodingScheme scheme)
-{
-	m_current_cs_dl = scheme;
-}
-
-enum CodingScheme GprsMs::max_cs_dl() const
-{
-	OSMO_ASSERT(m_bts != NULL);
-
-	if (mcs_is_gprs(m_current_cs_dl)) {
-		if (!m_bts->max_cs_dl()) {
-			return CS4;
-		}
-
-		return mcs_get_gprs_by_num(m_bts->max_cs_dl());
-	}
-
-	if (!mcs_is_edge(m_current_cs_dl))
-		return UNKNOWN;
-
-	if (m_bts->max_mcs_dl())
-		return mcs_get_egprs_by_num(m_bts->max_mcs_dl());
-	else if (m_bts->max_cs_dl())
-		return mcs_get_gprs_by_num(m_bts->max_cs_dl());
-
-	return MCS4;
-}
-
-void GprsMs::update_cs_ul(const pcu_l1_meas *meas)
-{
-	struct gprs_rlcmac_bts *bts_data;
-	enum CodingScheme max_cs_ul = this->max_cs_ul();
-
-	int old_link_qual;
-	int low;
-	int high;
-	enum CodingScheme new_cs_ul = m_current_cs_ul;
-	uint8_t current_cs = mcs_chan_code(m_current_cs_ul);
-
-	bts_data = m_bts->bts_data();
-
-	if (!max_cs_ul) {
-		LOGP(DRLCMACMEAS, LOGL_ERROR,
-			"max_cs_ul cannot be derived (current UL CS: %s)\n",
-			mcs_name(m_current_cs_ul));
-		return;
-	}
-
-	if (!m_current_cs_ul) {
-		LOGP(DRLCMACMEAS, LOGL_ERROR,
-		     "Unable to update UL (M)CS because it's not set: %s\n",
-		     mcs_name(m_current_cs_ul));
-		return;
-	}
-
-	if (!meas->have_link_qual) {
-		LOGP(DRLCMACMEAS, LOGL_ERROR,
-		     "Unable to update UL (M)CS %s because we don't have link quality measurements.\n",
-		     mcs_name(m_current_cs_ul));
-		return;
-	}
-
-	if (mcs_is_gprs(m_current_cs_ul)) {
-		if (current_cs >= MAX_GPRS_CS)
-			current_cs = MAX_GPRS_CS - 1;
-		low  = bts_data->cs_lqual_ranges[current_cs].low;
-		high = bts_data->cs_lqual_ranges[current_cs].high;
-	} else if (mcs_is_edge(m_current_cs_ul)) {
-		if (current_cs >= MAX_EDGE_MCS)
-			current_cs = MAX_EDGE_MCS - 1;
-		low  = bts_data->mcs_lqual_ranges[current_cs].low;
-		high = bts_data->mcs_lqual_ranges[current_cs].high;
-	} else {
-		LOGP(DRLCMACMEAS, LOGL_ERROR,
-		     "Unable to update UL (M)CS because it's neither GPRS nor EDGE: %s\n",
-		     mcs_name(m_current_cs_ul));
-		return;
-	}
-
-	/* To avoid rapid changes of the coding scheme, we also take
-	 * the old link quality value into account (if present). */
-	if (m_l1_meas.have_link_qual)
-		old_link_qual = m_l1_meas.link_qual;
-	else
-		old_link_qual = meas->link_qual;
-
-	if (meas->link_qual < low &&  old_link_qual < low)
-		mcs_dec_kind(&new_cs_ul, mode());
-	else if (meas->link_qual > high &&  old_link_qual > high &&
-		m_current_cs_ul < max_cs_ul)
-		mcs_inc_kind(&new_cs_ul, mode());
-
-	if (m_current_cs_ul != new_cs_ul) {
-		LOGPMS(this, DRLCMACMEAS, LOGL_INFO,
-		       "Link quality %ddB (old %ddB) left window [%d, %d], "
-		       "modifying uplink CS level: %s -> %s\n",
-		       meas->link_qual, old_link_qual,
-		       low, high,
-		       mcs_name(m_current_cs_ul), mcs_name(new_cs_ul));
-
-		m_current_cs_ul = new_cs_ul;
-	}
-}
-
-void GprsMs::update_l1_meas(const pcu_l1_meas *meas)
-{
-	unsigned i;
-
-	update_cs_ul(meas);
-
-	if (meas->have_rssi)
-		m_l1_meas.set_rssi(meas->rssi);
-	if (meas->have_bto)
-		m_l1_meas.set_bto(meas->bto);
-	if (meas->have_ber)
-		m_l1_meas.set_ber(meas->ber);
-	if (meas->have_link_qual)
-		m_l1_meas.set_link_qual(meas->link_qual);
-
-	if (meas->have_ms_rx_qual)
-		m_l1_meas.set_ms_rx_qual(meas->ms_rx_qual);
-	if (meas->have_ms_c_value)
-		m_l1_meas.set_ms_c_value(meas->ms_c_value);
-	if (meas->have_ms_sign_var)
-		m_l1_meas.set_ms_sign_var(meas->ms_sign_var);
-
-	if (meas->have_ms_i_level) {
-		for (i = 0; i < ARRAY_SIZE(meas->ts); ++i) {
-			if (meas->ts[i].have_ms_i_level)
-				m_l1_meas.set_ms_i_level(i, meas->ts[i].ms_i_level);
-			else
-				m_l1_meas.ts[i].have_ms_i_level = 0;
-		}
-	}
-}
-
-enum CodingScheme GprsMs::current_cs_dl() const
-{
-	enum CodingScheme cs = m_current_cs_dl;
-	size_t unencoded_octets;
-
-	if (!m_bts)
-		return cs;
-
-	unencoded_octets = m_llc_queue.octets();
-
-	/* If the DL TBF is active, add number of unencoded chunk octets */
-	if (m_dl_tbf)
-		unencoded_octets += m_dl_tbf->m_llc.chunk_size();
-
-	/* There are many unencoded octets, don't reduce */
-	if (unencoded_octets >= m_bts->bts_data()->cs_downgrade_threshold)
-		return cs;
-
-	/* RF conditions are good, don't reduce */
-	if (m_nack_rate_dl < m_bts->bts_data()->cs_adj_lower_limit)
-		return cs;
-
-	/* The throughput would probably be better if the CS level was reduced */
-	mcs_dec_kind(&cs, mode());
-
-	/* CS-2 doesn't gain throughput with small packets, further reduce to CS-1 */
-	if (cs == CS2)
-		mcs_dec_kind(&cs, mode());
-
-	return cs;
-}
-
-int GprsMs::first_common_ts() const
-{
-	if (m_dl_tbf)
-		return m_dl_tbf->first_common_ts;
-
-	if (m_ul_tbf)
-		return m_ul_tbf->first_common_ts;
-
-	return -1;
-}
-
-uint8_t GprsMs::dl_slots() const
-{
-	uint8_t slots = 0;
-
-	if (m_dl_tbf)
-		slots |= m_dl_tbf->dl_slots();
-
-	if (m_ul_tbf)
-		slots |= m_ul_tbf->dl_slots();
-
-	return slots;
-}
-
-uint8_t GprsMs::ul_slots() const
-{
-	uint8_t slots = 0;
-
-	if (m_dl_tbf)
-		slots |= m_dl_tbf->ul_slots();
-
-	if (m_ul_tbf)
-		slots |= m_ul_tbf->ul_slots();
-
-	return slots;
-}
-
-uint8_t GprsMs::current_pacch_slots() const
-{
-	uint8_t slots = 0;
-
-	bool is_dl_active = m_dl_tbf && m_dl_tbf->is_tfi_assigned();
-	bool is_ul_active = m_ul_tbf && m_ul_tbf->is_tfi_assigned();
-
-	if (!is_dl_active && !is_ul_active)
-		return 0;
-
-	/* see TS 44.060, 8.1.1.2.2 */
-	if (is_dl_active && !is_ul_active)
-		slots =  m_dl_tbf->dl_slots();
-	else if (!is_dl_active && is_ul_active)
-		slots =  m_ul_tbf->ul_slots();
-	else
-		slots =  m_ul_tbf->ul_slots() & m_dl_tbf->dl_slots();
-
-	/* Assume a multislot class 1 device */
-	/* TODO: For class 2 devices, this could be removed */
-	slots = pcu_lsb(slots);
-
-	return slots;
-}
-
-void GprsMs::set_reserved_slots(gprs_rlcmac_trx *trx,
-	uint8_t ul_slots, uint8_t dl_slots)
-{
-	if (m_current_trx) {
-		m_current_trx->unreserve_slots(GPRS_RLCMAC_DL_TBF,
-			m_reserved_dl_slots);
-		m_current_trx->unreserve_slots(GPRS_RLCMAC_UL_TBF,
-			m_reserved_ul_slots);
-		m_reserved_dl_slots = 0;
-		m_reserved_ul_slots = 0;
-	}
-	m_current_trx = trx;
-	if (trx) {
-		m_reserved_dl_slots = dl_slots;
-		m_reserved_ul_slots = ul_slots;
-		m_current_trx->reserve_slots(GPRS_RLCMAC_DL_TBF,
-			m_reserved_dl_slots);
-		m_current_trx->reserve_slots(GPRS_RLCMAC_UL_TBF,
-			m_reserved_ul_slots);
-	}
-}
-
-gprs_rlcmac_tbf *GprsMs::tbf(enum gprs_rlcmac_tbf_direction dir) const
-{
-	switch (dir) {
-	case GPRS_RLCMAC_DL_TBF: return m_dl_tbf;
-	case GPRS_RLCMAC_UL_TBF: return m_ul_tbf;
-	}
-
-	return NULL;
-}
diff --git a/src/gprs_ms.h b/src/gprs_ms.h
index 8b8940b..ade3f3b 100644
--- a/src/gprs_ms.h
+++ b/src/gprs_ms.h
@@ -1,6 +1,6 @@
 /* gprs_ms.h
  *
- * Copyright (C) 2015 by Sysmocom s.f.m.c. GmbH
+ * Copyright (C) 2015-2020 by Sysmocom s.f.m.c. GmbH
  * Author: Jacob Erlbeck <jerlbeck at sysmocom.de>
  *
  * This program is free software; you can redistribute it and/or
@@ -22,22 +22,23 @@
 
 struct gprs_codel;
 
-#include "cxx_linuxlist.h"
 #include "llc.h"
 #include "tbf.h"
 #include "tbf_ul.h"
 #include "tbf_dl.h"
 #include "pcu_l1_if.h"
 
+#ifdef __cplusplus
 extern "C" {
-	#include <osmocom/core/timer.h>
-	#include <osmocom/core/linuxlist.h>
+#endif
 
-	#include <osmocom/gsm/protocol/gsm_23_003.h>
-	#include <osmocom/gsm/gsm48.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/linuxlist.h>
 
-	#include "coding_scheme.h"
-}
+#include <osmocom/gsm/protocol/gsm_23_003.h>
+#include <osmocom/gsm/gsm48.h>
+
+#include "coding_scheme.h"
 
 #include <stdint.h>
 #include <stddef.h>
@@ -45,268 +46,207 @@
 
 struct BTS;
 struct gprs_rlcmac_trx;
+struct GprsMs;
 
-class GprsMs {
-public:
-	struct Callback {
-		virtual void ms_idle(class GprsMs *) = 0;
-		virtual void ms_active(class GprsMs *) = 0;
-	};
-
-	class Guard {
-		public:
-		Guard(GprsMs *ms);
-		~Guard();
-
-		bool is_idle() const;
-
-		private:
-		GprsMs * const m_ms;
-	};
-
-	GprsMs(BTS *bts, uint32_t tlli);
-	~GprsMs();
-
-	void set_callback(Callback *cb) {m_cb = cb;}
-
-	void merge_and_clear_ms(GprsMs *old_ms);
-
-	gprs_rlcmac_ul_tbf *ul_tbf() const {return m_ul_tbf;}
-	gprs_rlcmac_dl_tbf *dl_tbf() const {return m_dl_tbf;}
-	gprs_rlcmac_tbf *tbf(enum gprs_rlcmac_tbf_direction dir) const;
-	uint32_t tlli() const;
-	void set_tlli(uint32_t tlli);
-	bool confirm_tlli(uint32_t tlli);
-	bool check_tlli(uint32_t tlli);
-
-	void reset();
-	enum mcs_kind mode() const;
-	void set_mode(enum mcs_kind mode);
-
-	const char *imsi() const;
-	void set_imsi(const char *imsi);
-
-	uint8_t ta() const;
-	void set_ta(uint8_t ta);
-	uint8_t ms_class() const;
-	uint8_t egprs_ms_class() const;
-	void set_ms_class(uint8_t ms_class);
-	void set_egprs_ms_class(uint8_t ms_class);
-	void set_current_cs_dl(enum CodingScheme scheme);
-
-	enum CodingScheme current_cs_ul() const;
-	enum CodingScheme current_cs_dl() const;
-	enum CodingScheme max_cs_ul() const;
-	enum CodingScheme max_cs_dl() const;
-
-	int first_common_ts() const;
-	uint8_t dl_slots() const;
-	uint8_t ul_slots() const;
-	uint8_t reserved_dl_slots() const;
-	uint8_t reserved_ul_slots() const;
-	uint8_t current_pacch_slots() const;
-	gprs_rlcmac_trx *current_trx() const;
-	void set_reserved_slots(gprs_rlcmac_trx *trx,
-		uint8_t ul_slots, uint8_t dl_slots);
-
-	gprs_llc_queue *llc_queue();
-	const gprs_llc_queue *llc_queue() const;
-	gprs_codel *codel_state() const;
-
-	void set_timeout(unsigned secs);
-
-	void attach_tbf(gprs_rlcmac_tbf *tbf);
-	void attach_ul_tbf(gprs_rlcmac_ul_tbf *tbf);
-	void attach_dl_tbf(gprs_rlcmac_dl_tbf *tbf);
-
-	void detach_tbf(gprs_rlcmac_tbf *tbf);
-
-	void update_error_rate(gprs_rlcmac_tbf *tbf, int percent);
-
-	bool is_idle() const;
-	bool need_dl_tbf() const;
-
-	void* operator new(size_t num);
-	void operator delete(void* p);
-
-	LListHead<GprsMs>& list() {return this->m_list;}
-	const LListHead<GprsMs>& list() const {return this->m_list;}
-	const LListHead<gprs_rlcmac_tbf>& old_tbfs() const {return m_old_tbfs;}
-
-	void update_l1_meas(const pcu_l1_meas *meas);
-	const pcu_l1_meas* l1_meas() const {return &m_l1_meas;};
-	unsigned nack_rate_dl() const;
-	unsigned dl_ctrl_msg() const;
-	void update_dl_ctrl_msg();
-
-	/* internal use */
-	static void timeout(void *priv_);
-
-	bool app_info_pending;
-
-protected:
-	void merge_old_ms(GprsMs *old_ms);
-	void update_status();
-	GprsMs *ref();
-	void unref();
-	void start_timer();
-	void stop_timer();
-	void update_cs_ul(const pcu_l1_meas*);
-
-private:
-	BTS *m_bts;
-	Callback * m_cb;
-	gprs_rlcmac_ul_tbf *m_ul_tbf;
-	gprs_rlcmac_dl_tbf *m_dl_tbf;
-	LListHead<gprs_rlcmac_tbf> m_old_tbfs;
-
-	uint32_t m_tlli;
-	uint32_t m_new_ul_tlli;
-	uint32_t m_new_dl_tlli;
-
-	/* store IMSI for look-up and PCH retransmission */
-	char m_imsi[OSMO_IMSI_BUF_SIZE];
-	uint8_t m_ta;
-	uint8_t m_ms_class;
-	uint8_t m_egprs_ms_class;
-	/* current coding scheme */
-	enum CodingScheme m_current_cs_ul;
-	enum CodingScheme m_current_cs_dl;
-
-	gprs_llc_queue m_llc_queue;
-
-	bool m_is_idle;
-	int m_ref;
-	LListHead<GprsMs> m_list;
-	struct osmo_timer_list m_timer;
-	unsigned m_delay;
-
-	int64_t m_last_cs_not_low;
-
-	pcu_l1_meas m_l1_meas;
-	unsigned m_nack_rate_dl;
-	uint8_t m_reserved_dl_slots;
-	uint8_t m_reserved_ul_slots;
-	gprs_rlcmac_trx *m_current_trx;
-
-	struct gprs_codel *m_codel_state;
-	enum mcs_kind m_mode;
-
-	unsigned m_dl_ctrl_msg;
+struct gpr_ms_callback {
+	void (*ms_idle)(struct GprsMs *);
+	void (*ms_active)(struct GprsMs *);
 };
 
-inline bool GprsMs::is_idle() const
+struct GprsMs {
+	struct llist_head list; /* list of all GprsMs */
+	struct gpr_ms_callback cb;
+	bool app_info_pending;
+
+	struct BTS *bts;
+	struct gprs_rlcmac_ul_tbf *ul_tbf;
+	struct gprs_rlcmac_dl_tbf *dl_tbf;
+	struct llist_head old_tbfs; /* list of gprs_rlcmac_tbf */
+
+	uint32_t tlli;
+	uint32_t new_ul_tlli;
+	uint32_t new_dl_tlli;
+
+	/* store IMSI for look-up and PCH retransmission */
+	char imsi[OSMO_IMSI_BUF_SIZE];
+	uint8_t ta;
+	uint8_t ms_class;
+	uint8_t egprs_ms_class;
+	/* current coding scheme */
+	enum CodingScheme current_cs_ul;
+	enum CodingScheme current_cs_dl;
+
+	struct gprs_llc_queue llc_queue;
+
+	bool is_idle;
+	int ref;
+	struct osmo_timer_list timer;
+	unsigned delay;
+
+	int64_t last_cs_not_low;
+
+	struct pcu_l1_meas l1_meas;
+	unsigned nack_rate_dl;
+	uint8_t reserved_dl_slots;
+	uint8_t reserved_ul_slots;
+	struct gprs_rlcmac_trx *current_trx;
+
+	struct gprs_codel *codel_state;
+	enum mcs_kind mode;
+
+	unsigned dl_ctrl_msg;
+};
+
+struct GprsMs *ms_alloc(struct BTS *bts, uint32_t tlli);
+
+int ms_first_common_ts(const struct GprsMs *ms);
+void ms_set_reserved_slots(struct GprsMs *ms, struct gprs_rlcmac_trx *trx,
+			   uint8_t ul_slots, uint8_t dl_slots);
+struct GprsMs *ms_ref(struct GprsMs *ms);
+void ms_unref(struct GprsMs *ms);
+void ms_set_mode(struct GprsMs *ms, enum mcs_kind mode);
+void ms_set_ms_class(struct GprsMs *ms, uint8_t ms_class_);
+void ms_set_egprs_ms_class(struct GprsMs *ms, uint8_t ms_class_);
+void ms_set_ta(struct GprsMs *ms, uint8_t ta_);
+
+enum CodingScheme ms_current_cs_dl(const struct GprsMs *ms);
+enum CodingScheme ms_max_cs_ul(const struct GprsMs *ms);
+enum CodingScheme ms_max_cs_dl(const struct GprsMs *ms);
+void ms_set_current_cs_dl(struct GprsMs *ms, enum CodingScheme scheme);
+
+void ms_update_error_rate(struct GprsMs *ms, struct gprs_rlcmac_tbf *tbf, int error_rate);
+uint8_t ms_current_pacch_slots(const struct GprsMs *ms);
+
+void ms_merge_and_clear_ms(struct GprsMs *ms, struct GprsMs *old_ms);
+
+void ms_attach_tbf(struct GprsMs *ms, struct gprs_rlcmac_tbf *tbf);
+void ms_detach_tbf(struct GprsMs *ms, struct gprs_rlcmac_tbf *tbf);
+
+void ms_set_tlli(struct GprsMs *ms, uint32_t tlli);
+bool ms_confirm_tlli(struct GprsMs *ms, uint32_t tlli);
+void ms_set_imsi(struct GprsMs *ms, const char *imsi);
+
+void ms_update_l1_meas(struct GprsMs *ms, const struct pcu_l1_meas *meas);
+
+struct gprs_rlcmac_tbf *ms_tbf(const struct GprsMs *ms, enum gprs_rlcmac_tbf_direction dir);
+static inline struct gprs_rlcmac_ul_tbf *ms_ul_tbf(const struct GprsMs *ms) {return ms->ul_tbf;}
+static inline struct gprs_rlcmac_dl_tbf *ms_dl_tbf(const struct GprsMs *ms) {return ms->dl_tbf;}
+
+
+void ms_set_callback(struct GprsMs *ms, struct gpr_ms_callback *cb);
+
+static inline bool ms_is_idle(const struct GprsMs *ms)
 {
-	return !m_ul_tbf && !m_dl_tbf && !m_ref && llist_empty(&m_old_tbfs);
+	return !ms->ul_tbf && !ms->dl_tbf && !ms->ref && llist_empty(&ms->old_tbfs);
 }
 
-inline bool GprsMs::need_dl_tbf() const
+static inline struct gprs_llc_queue *ms_llc_queue(struct GprsMs *ms)
 {
-	if (dl_tbf() != NULL && dl_tbf()->state_is_not(GPRS_RLCMAC_WAIT_RELEASE))
+	return &ms->llc_queue;
+}
+
+static inline bool ms_need_dl_tbf(struct GprsMs *ms)
+{
+	if (ms_dl_tbf(ms) != NULL &&
+	    tbf_state((const struct gprs_rlcmac_tbf *)ms_dl_tbf(ms)) != GPRS_RLCMAC_WAIT_RELEASE)
 		return false;
 
-	return llc_queue()->size() > 0;
+	return llc_queue_size(ms_llc_queue(ms)) > 0;
 }
 
-inline uint32_t GprsMs::tlli() const
+static inline uint32_t ms_tlli(const struct GprsMs *ms)
 {
-	if (m_new_ul_tlli != GSM_RESERVED_TMSI)
-		return m_new_ul_tlli;
-	if (m_tlli != GSM_RESERVED_TMSI)
-		return m_tlli;
+	if (ms->new_ul_tlli != GSM_RESERVED_TMSI)
+		return ms->new_ul_tlli;
+	if (ms->tlli != GSM_RESERVED_TMSI)
+		return ms->tlli;
 
-	return m_new_dl_tlli;
+	return ms->new_dl_tlli;
 }
 
-inline bool GprsMs::check_tlli(uint32_t tlli)
+static inline bool ms_check_tlli(struct GprsMs *ms, uint32_t tlli)
 {
 	return tlli != GSM_RESERVED_TMSI &&
-		(tlli == m_tlli || tlli == m_new_ul_tlli || tlli == m_new_dl_tlli);
+		(tlli == ms->tlli || tlli == ms->new_ul_tlli || tlli == ms->new_dl_tlli);
 }
 
-inline const char *GprsMs::imsi() const
+static inline const char *ms_imsi(const struct GprsMs *ms)
 {
-	return m_imsi;
+	return ms->imsi;
 }
 
-inline uint8_t GprsMs::ta() const
+static inline uint8_t ms_ta(const struct GprsMs *ms)
 {
-	return m_ta;
+	return ms->ta;
 }
 
-inline uint8_t GprsMs::ms_class() const
+static inline uint8_t ms_ms_class(const struct GprsMs *ms)
 {
-	return m_ms_class;
+	return ms->ms_class;
 }
 
-inline uint8_t GprsMs::egprs_ms_class() const
+static inline uint8_t ms_egprs_ms_class(const struct GprsMs *ms)
 {
-	return m_egprs_ms_class;
+	return ms->egprs_ms_class;
 }
 
-inline enum CodingScheme GprsMs::current_cs_ul() const
+static inline enum CodingScheme ms_current_cs_ul(const struct GprsMs *ms)
 {
-	return m_current_cs_ul;
+	return ms->current_cs_ul;
 }
 
-inline enum mcs_kind GprsMs::mode() const
+static inline enum mcs_kind ms_mode(const struct GprsMs *ms)
 {
-	return m_mode;
+	return ms->mode;
 }
 
-inline void GprsMs::set_timeout(unsigned secs)
+static inline void ms_set_timeout(struct GprsMs *ms, unsigned secs)
 {
-	m_delay = secs;
+	ms->delay = secs;
 }
 
-inline gprs_llc_queue *GprsMs::llc_queue()
+static inline struct gprs_codel *ms_codel_state(const struct GprsMs *ms)
 {
-	return &m_llc_queue;
+	return ms->codel_state;
 }
 
-inline const gprs_llc_queue *GprsMs::llc_queue() const
+static inline unsigned ms_nack_rate_dl(const struct GprsMs *ms)
 {
-	return &m_llc_queue;
+	return ms->nack_rate_dl;
 }
 
-inline gprs_codel *GprsMs::codel_state() const
+static inline unsigned ms_dl_ctrl_msg(const struct GprsMs *ms)
 {
-	return m_codel_state;
+	return ms->dl_ctrl_msg;
 }
 
-inline unsigned GprsMs::nack_rate_dl() const
+static inline void ms_update_dl_ctrl_msg(struct GprsMs *ms)
 {
-	return m_nack_rate_dl;
+	ms->dl_ctrl_msg++;
 }
 
-inline unsigned GprsMs::dl_ctrl_msg() const
+static inline uint8_t ms_reserved_dl_slots(const struct GprsMs *ms)
 {
-	return m_dl_ctrl_msg;
+	return ms->reserved_dl_slots;
 }
 
-inline void GprsMs::update_dl_ctrl_msg()
+static inline uint8_t ms_reserved_ul_slots(const struct GprsMs *ms)
 {
-	m_dl_ctrl_msg++;
+	return ms->reserved_ul_slots;
 }
 
-inline uint8_t GprsMs::reserved_dl_slots() const
+static inline struct gprs_rlcmac_trx *ms_current_trx(const struct GprsMs *ms)
 {
-	return m_reserved_dl_slots;
-}
-
-inline uint8_t GprsMs::reserved_ul_slots() const
-{
-	return m_reserved_ul_slots;
-}
-
-inline gprs_rlcmac_trx *GprsMs::current_trx() const
-{
-	return m_current_trx;
+	return ms->current_trx;
 }
 
 #define LOGPMS(ms, category, level, fmt, args...) \
 	LOGP(category, level, "MS(TLLI=0x%08x, IMSI=%s, TA=%" PRIu8 ", %" PRIu8 "/%" PRIu8 ",%s%s) " fmt, \
-	     (ms)->tlli(), (ms)->imsi(), (ms)->ta(), (ms)->ms_class(), (ms)->egprs_ms_class(), \
-	     (ms)->ul_tbf() ? " UL": "", \
-	     (ms)->dl_tbf() ? " DL": "", \
+	     ms_tlli(ms), ms_imsi(ms), ms_ta(ms), ms_ms_class(ms), ms_egprs_ms_class(ms), \
+	     ms_ul_tbf(ms) ? " UL": "", \
+	     ms_dl_tbf(ms) ? " DL": "", \
 	     ## args)
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/src/gprs_ms_storage.cpp b/src/gprs_ms_storage.cpp
index 73570b3..6d5b09e 100644
--- a/src/gprs_ms_storage.cpp
+++ b/src/gprs_ms_storage.cpp
@@ -31,9 +31,29 @@
 
 #define GPRS_UNDEFINED_IMSI "000"
 
+static void ms_storage_ms_idle_cb(struct GprsMs *ms)
+{
+	llist_del(&ms->list);
+	if (ms->bts)
+		ms->bts->stat_item_add(STAT_MS_PRESENT, -1);
+	if (ms_is_idle(ms))
+		talloc_free(ms);
+}
+
+static void ms_storage_ms_active_cb(struct GprsMs *ms)
+{
+	/* Nothing to do */
+}
+
+static struct gpr_ms_callback ms_storage_ms_cb = {
+	.ms_idle = ms_storage_ms_idle_cb,
+	.ms_active = ms_storage_ms_active_cb,
+};
+
 GprsMsStorage::GprsMsStorage(BTS *bts) :
 	m_bts(bts)
 {
+	INIT_LLIST_HEAD(&m_list);
 }
 
 GprsMsStorage::~GprsMsStorage()
@@ -43,40 +63,26 @@
 
 void GprsMsStorage::cleanup()
 {
-	LListHead<GprsMs> *pos, *tmp;
+	struct llist_head *pos, *tmp;
 
 	llist_for_each_safe(pos, tmp, &m_list) {
-		GprsMs *ms = pos->entry();
-		ms->set_callback(NULL);
-		ms_idle(ms);
+		struct GprsMs *ms = llist_entry(pos, typeof(*ms), list);
+		ms_set_callback(ms, NULL);
+		ms_storage_ms_idle_cb(ms);
 	}
 }
 
-void GprsMsStorage::ms_idle(class GprsMs *ms)
-{
-	llist_del(&ms->list());
-	if (m_bts)
-		m_bts->stat_item_add(STAT_MS_PRESENT, -1);
-	if (ms->is_idle())
-		delete ms;
-}
-
-void GprsMsStorage::ms_active(class GprsMs *ms)
-{
-	/* Nothing to do */
-}
-
 GprsMs *GprsMsStorage::get_ms(uint32_t tlli, uint32_t old_tlli, const char *imsi) const
 {
+	struct llist_head *tmp;
 	GprsMs *ms;
-	LListHead<GprsMs> *pos;
 
 	if (tlli != GSM_RESERVED_TMSI || old_tlli != GSM_RESERVED_TMSI) {
-		llist_for_each(pos, &m_list) {
-			ms = pos->entry();
-			if (ms->check_tlli(tlli))
+		llist_for_each(tmp, &m_list) {
+			ms = llist_entry(tmp, typeof(*ms), list);
+			if (ms_check_tlli(ms, tlli))
 				return ms;
-			if (ms->check_tlli(old_tlli))
+			if (ms_check_tlli(ms, old_tlli))
 				return ms;
 		}
 	}
@@ -84,9 +90,9 @@
 	/* not found by TLLI */
 
 	if (imsi && imsi[0] && strcmp(imsi, GPRS_UNDEFINED_IMSI) != 0) {
-		llist_for_each(pos, &m_list) {
-			ms = pos->entry();
-			if (strcmp(imsi, ms->imsi()) == 0)
+		llist_for_each(tmp, &m_list) {
+			ms = llist_entry(tmp, typeof(*ms), list);
+			if (strcmp(imsi, ms_imsi(ms)) == 0)
 				return ms;
 		}
 	}
@@ -98,10 +104,10 @@
 {
 	GprsMs *ms;
 
-	ms = new GprsMs(m_bts, GSM_RESERVED_TMSI);
+	ms = ms_alloc(m_bts, GSM_RESERVED_TMSI);
 
-	ms->set_callback(this);
-	llist_add(&ms->list(), &m_list);
+	ms_set_callback(ms, &ms_storage_ms_cb);
+	llist_add(&ms->list, &m_list);
 	if (m_bts)
 		m_bts->stat_item_add(STAT_MS_PRESENT, 1);
 
diff --git a/src/gprs_ms_storage.h b/src/gprs_ms_storage.h
index 35062f3..af49688 100644
--- a/src/gprs_ms_storage.h
+++ b/src/gprs_ms_storage.h
@@ -21,28 +21,24 @@
 #pragma once
 
 #include "gprs_ms.h"
-#include "cxx_linuxlist.h"
 #include "tbf.h"
 #include <stdint.h>
 #include <stddef.h>
 
 struct BTS;
 
-class GprsMsStorage : public GprsMs::Callback {
+class GprsMsStorage {
 public:
 	GprsMsStorage(BTS *bts);
 	~GprsMsStorage();
 
 	void cleanup();
 
-	virtual void ms_idle(class GprsMs *);
-	virtual void ms_active(class GprsMs *);
-
 	GprsMs *get_ms(uint32_t tlli, uint32_t old_tlli = GSM_RESERVED_TMSI, const char *imsi = NULL) const;
 	GprsMs *create_ms();
 
-	const LListHead<GprsMs>& ms_list() const {return m_list;}
+	const struct llist_head* ms_list() const {return &m_list;}
 private:
 	BTS *m_bts;
-	LListHead<GprsMs> m_list;
+	struct llist_head m_list; /* list of struct GprsMs */
 };
diff --git a/src/gprs_rlcmac_sched.cpp b/src/gprs_rlcmac_sched.cpp
index 70d8237..caf121f 100644
--- a/src/gprs_rlcmac_sched.cpp
+++ b/src/gprs_rlcmac_sched.cpp
@@ -234,7 +234,7 @@
 			"message at RTS for %s (TRX=%d, TS=%d)\n",
 			tbf_name(tbf), trx, ts);
 		/* Updates the dl ctrl msg counter for ms */
-		tbf->ms()->update_dl_ctrl_msg();
+		ms_update_dl_ctrl_msg(tbf->ms());
 		return msg;
 	}
 
@@ -342,7 +342,7 @@
 		pdch->next_dl_tfi = (prio_tfi + 1) & 31;
 		/* generate DL data block */
 		msg = prio_tbf->create_dl_acked_block(fn, ts, req_mcs_kind);
-		*is_egprs = prio_tbf->ms()->mode() != GPRS;
+		*is_egprs = ms_mode(prio_tbf->ms()) != GPRS;
 	}
 
 	return msg;
@@ -463,7 +463,7 @@
 			 * only be able to read USF if dl block uses GMSK
 			 * (CS1-4, MCS1-4)
 			 */
-			if (req_mcs_kind == EGPRS && usf_tbf->ms()->mode() != EGPRS)
+			if (req_mcs_kind == EGPRS && ms_mode(usf_tbf->ms()) != EGPRS)
 				req_mcs_kind = EGPRS_GMSK;
 		} else {
 			usf = USF_UNUSED;
diff --git a/src/gprs_rlcmac_ts_alloc.cpp b/src/gprs_rlcmac_ts_alloc.cpp
index 9551c59..1ef32f2 100644
--- a/src/gprs_rlcmac_ts_alloc.cpp
+++ b/src/gprs_rlcmac_ts_alloc.cpp
@@ -245,8 +245,8 @@
 	unsigned ts;
 
 	/* We must use the TRX currently actively used by an MS */
-	if (ms && ms->current_trx())
-		return ms->current_trx()->trx_no;
+	if (ms && ms_current_trx(ms))
+		return ms_current_trx(ms)->trx_no;
 
 	if (use_trx >= 0 && use_trx < 8)
 		return use_trx;
@@ -320,8 +320,8 @@
 		use_trx = trx->trx_no;
 	}
 
-	if (use_trx == -1 && ms->current_trx())
-		use_trx = ms->current_trx()->trx_no;
+	if (use_trx == -1 && ms_current_trx(ms))
+		use_trx = ms_current_trx(ms)->trx_no;
 
 	tfi = bts->tfi_find_free(dir, &trx_no, use_trx);
 	if (tfi < 0)
@@ -357,7 +357,7 @@
 	const char *mask_reason = NULL;
 	const GprsMs *ms = ms_;
 	const gprs_rlcmac_tbf *tbf = tbf_;
-	gprs_rlcmac_trx *trx = ms->current_trx();
+	gprs_rlcmac_trx *trx = ms_current_trx(ms);
 
 	LOGPAL(tbf, "A", single, use_trx, LOGL_DEBUG, "Alloc start\n");
 
@@ -370,10 +370,10 @@
 	if (!trx)
 		trx = &bts->trx[trx_no];
 
-	dl_slots = ms->reserved_dl_slots();
-	ul_slots = ms->reserved_ul_slots();
+	dl_slots = ms_reserved_dl_slots(ms);
+	ul_slots = ms_reserved_ul_slots(ms);
 
-	ts = ms->first_common_ts();
+	ts = ms_first_common_ts(ms);
 
 	if (ts >= 0) {
 		mask_reason = "need to reuse TS";
@@ -420,7 +420,7 @@
 	tbf_->trx = trx;
 	/* the only one TS is the common TS */
 	tbf_->first_ts = tbf_->first_common_ts = ts;
-	ms_->set_reserved_slots(trx, 1 << ts, 1 << ts);
+	ms_set_reserved_slots(ms_, trx, 1 << ts, 1 << ts);
 
 	tbf_->upgrade_to_multislot = 0;
 	bts->bts->do_rate_ctr_inc(CTR_TBF_ALLOC_ALGO_A);
@@ -774,11 +774,11 @@
 {
 	char slot_info[9] = { 0 };
 
-	if (res_ul_slots == ms->reserved_ul_slots() && res_dl_slots == ms->reserved_dl_slots())
+	if (res_ul_slots == ms_reserved_ul_slots(ms) && res_dl_slots == ms_reserved_dl_slots(ms))
 		return;
 
 	/* The reserved slots have changed, update the MS */
-	ms->set_reserved_slots(trx, res_ul_slots, res_dl_slots);
+	ms_set_reserved_slots(ms, trx, res_ul_slots, res_dl_slots);
 
 	ts_format(slot_info, dl_slots, ul_slots);
 	LOGP(DRLCMAC, LOGL_DEBUG, "- Reserved DL/UL slots: (TS=0)\"%s\"(TS=7)\n", slot_info);
@@ -867,10 +867,10 @@
 		return -EINVAL;
 	}
 
-	dl_slots = ms->reserved_dl_slots();
-	ul_slots = ms->reserved_ul_slots();
-	first_common_ts = ms->first_common_ts();
-	trx = ms->current_trx();
+	dl_slots = ms_reserved_dl_slots(ms);
+	ul_slots = ms_reserved_ul_slots(ms);
+	first_common_ts = ms_first_common_ts(ms);
+	trx = ms_current_trx(ms);
 
 	/* Step 2a: Find usable TRX and TFI */
 	tfi = tfi_find_free(bts->bts, trx, ms, tbf->direction, use_trx, &trx_no);
@@ -884,7 +884,7 @@
 		trx = &bts->trx[trx_no];
 
 	if (!dl_slots || !ul_slots) {
-		rc = find_multi_slots(trx, ms->ms_class(), &ul_slots, &dl_slots);
+		rc = find_multi_slots(trx, ms_ms_class(ms), &ul_slots, &dl_slots);
 		if (rc < 0)
 			return rc;
 	}
diff --git a/src/llc.cpp b/src/llc.cpp
index e2d01c2..d1122f5 100644
--- a/src/llc.cpp
+++ b/src/llc.cpp
@@ -97,12 +97,12 @@
 	return true;
 }
 
-void gprs_llc_queue::init()
+void llc_queue_init(struct gprs_llc_queue *q)
 {
-	INIT_LLIST_HEAD(&m_queue);
-	m_queue_size = 0;
-	m_queue_octets = 0;
-	m_avg_queue_delay = 0;
+	INIT_LLIST_HEAD(&q->m_queue);
+	q->m_queue_size = 0;
+	q->m_queue_octets = 0;
+	q->m_avg_queue_delay = 0;
 }
 
 
@@ -122,21 +122,21 @@
 	msgb_enqueue(&m_queue, llc_msg);
 }
 
-void gprs_llc_queue::clear(BTS *bts)
+void llc_queue_clear(struct gprs_llc_queue *q, struct BTS *bts)
 {
 	struct msgb *msg;
 
-	while ((msg = msgb_dequeue(&m_queue))) {
+	while ((msg = msgb_dequeue(&q->m_queue))) {
 		if (bts)
 			bts->do_rate_ctr_inc(CTR_LLC_FRAME_DROPPED);
 		msgb_free(msg);
 	}
 
-	m_queue_size = 0;
-	m_queue_octets = 0;
+	q->m_queue_size = 0;
+	q->m_queue_octets = 0;
 }
 
-void gprs_llc_queue::move_and_merge(gprs_llc_queue *o)
+void llc_queue_move_and_merge(struct gprs_llc_queue *q, struct gprs_llc_queue *o)
 {
 	struct msgb *msg, *msg1 = NULL, *msg2 = NULL;
 	struct llist_head new_queue;
@@ -146,7 +146,7 @@
 
 	while (1) {
 		if (msg1 == NULL)
-			msg1 = msgb_dequeue(&m_queue);
+			msg1 = msgb_dequeue(&q->m_queue);
 
 		if (msg2 == NULL)
 			msg2 = msgb_dequeue(&o->m_queue);
@@ -178,15 +178,15 @@
 		queue_octets += msgb_length(msg);
 	}
 
-	OSMO_ASSERT(llist_empty(&m_queue));
+	OSMO_ASSERT(llist_empty(&q->m_queue));
 	OSMO_ASSERT(llist_empty(&o->m_queue));
 
 	o->m_queue_size = 0;
 	o->m_queue_octets = 0;
 
-	llist_splice_init(&new_queue, &m_queue);
-	m_queue_size = queue_size;
-	m_queue_octets = queue_octets;
+	llist_splice_init(&new_queue, &q->m_queue);
+	q->m_queue_size = queue_size;
+	q->m_queue_octets = queue_octets;
 }
 
 #define ALPHA 0.5f
diff --git a/src/llc.h b/src/llc.h
index 3c2e57a..72fa62e 100644
--- a/src/llc.h
+++ b/src/llc.h
@@ -18,9 +18,13 @@
 
 #pragma once
 
+#ifdef __cplusplus
 extern "C" {
+#endif
 	#include <osmocom/core/linuxlist.h>
+#ifdef __cplusplus
 }
+#endif
 
 #include <stdint.h>
 #include <string.h>
@@ -34,6 +38,8 @@
  * I represent the LLC data to a MS
  */
 struct gprs_llc {
+
+#ifdef __cplusplus
 	static bool is_user_data_frame(uint8_t *data, size_t len);
 
 	void init();
@@ -43,92 +49,86 @@
 	void put_frame(const uint8_t *data, size_t len);
 	void put_dummy_frame(size_t req_len);
 	void append_frame(const uint8_t *data, size_t len);
-
-	void consume(size_t len);
-	void consume(uint8_t *data, size_t len);
-
-	uint16_t chunk_size() const;
-	uint16_t remaining_space() const;
-	uint16_t frame_length() const;
-
-	bool fits_in_current_frame(uint8_t size) const;
+#endif
 
 	uint8_t frame[LLC_MAX_LEN]; /* current DL or UL frame */
 	uint16_t m_index; /* current write/read position of frame */
 	uint16_t m_length; /* len of current DL LLC_frame, 0 == no frame */
 };
 
+struct MetaInfo {
+	struct timespec recv_time;
+	struct timespec expire_time;
+};
 /**
  * I store the LLC frames that come from the SGSN.
  */
 struct gprs_llc_queue {
-	struct MetaInfo {
-		struct timespec recv_time;
-		struct timespec expire_time;
-	};
-
+#ifdef __cplusplus
 	static void calc_pdu_lifetime(BTS *bts, const uint16_t pdu_delay_csec,
 		struct timespec *tv);
 	static bool is_frame_expired(const struct timespec *now,
 		const struct timespec *tv);
 	static bool is_user_data_frame(uint8_t *data, size_t len);
 
-	void init();
-
 	void enqueue(struct msgb *llc_msg, const struct timespec *expire_time);
 	struct msgb *dequeue(const MetaInfo **info = 0);
-	void clear(BTS *bts);
-	void move_and_merge(gprs_llc_queue *o);
-	size_t size() const;
-	size_t octets() const;
-
-private:
+#endif
 	uint32_t m_avg_queue_delay; /* Average delay of data going through the queue */
 	size_t m_queue_size;
 	size_t m_queue_octets;
 	struct llist_head m_queue; /* queued LLC DL data */
-
 };
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+void llc_queue_init(struct gprs_llc_queue *q);
+void llc_queue_clear(struct gprs_llc_queue *q, struct BTS *bts);
+void llc_queue_move_and_merge(struct gprs_llc_queue *q, struct gprs_llc_queue *o);
 
-inline uint16_t gprs_llc::chunk_size() const
+static inline uint16_t llc_chunk_size(const struct gprs_llc *llc)
 {
-	return m_length - m_index;
+	return llc->m_length - llc->m_index;
 }
 
-inline uint16_t gprs_llc::remaining_space() const
+static inline uint16_t llc_remaining_space(const struct gprs_llc *llc)
 {
-	return LLC_MAX_LEN - m_length;
+	return LLC_MAX_LEN - llc->m_length;
 }
 
-inline uint16_t gprs_llc::frame_length() const
+static inline uint16_t llc_frame_length(const struct gprs_llc *llc)
 {
-	return m_length;
+	return llc->m_length;
 }
 
-inline void gprs_llc::consume(size_t len)
+static inline void llc_consume(struct gprs_llc *llc, size_t len)
 {
-	m_index += len;
+	llc->m_index += len;
 }
 
-inline void gprs_llc::consume(uint8_t *data, size_t len)
+static inline void llc_consume_data(struct gprs_llc *llc, uint8_t *data, size_t len)
 {
 	/* copy and increment index */
-	memcpy(data, frame + m_index, len);
-	consume(len);
+	memcpy(data, llc->frame + llc->m_index, len);
+	llc_consume(llc, len);
 }
 
-inline bool gprs_llc::fits_in_current_frame(uint8_t chunk_size) const
+static inline bool llc_fits_in_current_frame(const struct gprs_llc *llc, uint8_t chunk_size)
 {
-	return m_length + chunk_size <= LLC_MAX_LEN;
+	return llc->m_length + chunk_size <= LLC_MAX_LEN;
 }
 
-inline size_t gprs_llc_queue::size() const
+static inline size_t llc_queue_size(const struct gprs_llc_queue *q)
 {
-	return m_queue_size;
+	return q->m_queue_size;
 }
 
-inline size_t gprs_llc_queue::octets() const
+static inline size_t llc_queue_octets(const struct gprs_llc_queue *q)
 {
-	return m_queue_octets;
+	return q->m_queue_octets;
 }
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/src/pcu_l1_if.cpp b/src/pcu_l1_if.cpp
index a984591..9cc6270 100644
--- a/src/pcu_l1_if.cpp
+++ b/src/pcu_l1_if.cpp
@@ -318,7 +318,7 @@
 	struct gprs_rlcmac_bts *bts = bts_main_data();
 	int rc;
 	int current_fn = get_current_fn();
-	pcu_l1_meas meas;
+	struct pcu_l1_meas meas = {0};
 	uint8_t gsmtap_chantype;
 
 	LOGP(DL1IF, LOGL_DEBUG, "Data indication received: sapi=%d arfcn=%d "
@@ -328,11 +328,11 @@
 
 	switch (data_ind->sapi) {
 	case PCU_IF_SAPI_PDTCH:
-		meas.set_rssi(data_ind->rssi);
+		pcu_l1_meas_set_rssi(&meas, data_ind->rssi);
 		/* convert BER to % value */
-		meas.set_ber(data_ind->ber10k / 100);
-		meas.set_bto(data_ind->ta_offs_qbits);
-		meas.set_link_qual(data_ind->lqual_cb / 10);
+		pcu_l1_meas_set_ber(&meas, data_ind->ber10k / 100);
+		pcu_l1_meas_set_bto(&meas, data_ind->ta_offs_qbits);
+		pcu_l1_meas_set_link_qual(&meas, data_ind->lqual_cb / 10);
 
 		LOGP(DL1IF, LOGL_DEBUG, "Data indication with raw measurements received: BER10k = %d, BTO = %d, Q = %d\n",
 		     data_ind->ber10k, data_ind->ta_offs_qbits, data_ind->lqual_cb);
@@ -824,8 +824,8 @@
 	if ((ms = bts->ms_store().get_ms(susp_req->tlli))) {
 		/* We need to catch both pointers here since MS may become freed
 		   after first tbf_free(dl_tbf) if only DL TBF was available */
-		dl_tbf = ms->dl_tbf();
-		ul_tbf = ms->ul_tbf();
+		dl_tbf = ms_dl_tbf(ms);
+		ul_tbf = ms_ul_tbf(ms);
 		if (dl_tbf)
 			tbf_free(dl_tbf);
 		if (ul_tbf)
@@ -840,7 +840,7 @@
 
 static int pcu_rx_app_info_req(struct gsm_pcu_if_app_info_req *app_info_req)
 {
-	LListHead<GprsMs> *ms_iter;
+	GprsMs *ms;
 	BTS *bts = BTS::main_bts();
 	struct gprs_rlcmac_bts *bts_data = bts->bts_data();
 
@@ -848,9 +848,8 @@
 	     app_info_req->application_type, app_info_req->len);
 
 	bts_data->app_info_pending = 0;
-	llist_for_each(ms_iter, &bts->ms_store().ms_list()) {
-		GprsMs *ms = ms_iter->entry();
-		if (!ms->dl_tbf())
+	llist_for_each_entry(ms, bts->ms_store().ms_list(), list) {
+		if (!ms_dl_tbf(ms))
 			continue;
 		bts_data->app_info_pending++;
 		ms->app_info_pending = true;
diff --git a/src/pcu_l1_if.h b/src/pcu_l1_if.h
index f86e708..65fea9c 100644
--- a/src/pcu_l1_if.h
+++ b/src/pcu_l1_if.h
@@ -79,19 +79,13 @@
 	unsigned have_ms_i_level:1;
 
 	int16_t ms_i_level; /* I_LEVEL in dB */
-
-#ifdef __cplusplus
-	pcu_l1_meas_ts& set_ms_i_level(int16_t v) {
-		ms_i_level = v; have_ms_i_level = 1; return *this;
-	}
-
-	pcu_l1_meas_ts() :
-		have_ms_i_level(0),
-		ms_i_level(0)
-	{}
-#endif
 };
 
+static inline void pcu_l1_meas_ts_set_ms_i_level(struct pcu_l1_meas_ts* ts, int16_t v) {
+	ts->ms_i_level = v;
+	ts->have_ms_i_level = 1;
+}
+
 struct pcu_l1_meas {
 	unsigned have_rssi:1;
 	unsigned have_ber:1;
@@ -111,48 +105,43 @@
 	int16_t ms_sign_var; /* SIGN_VAR in dB */
 
 	struct pcu_l1_meas_ts ts[8];
-
-#ifdef __cplusplus
-	pcu_l1_meas& set_rssi(int8_t v) { rssi = v; have_rssi = 1; return *this;}
-	pcu_l1_meas& set_ber(uint8_t v) { ber = v; have_ber = 1; return *this;}
-	pcu_l1_meas& set_bto(int16_t v) { bto = v; have_bto = 1; return *this;}
-	pcu_l1_meas& set_link_qual(int16_t v) {
-		link_qual = v; have_link_qual = 1; return *this;
-	}
-	pcu_l1_meas& set_ms_rx_qual(int16_t v) {
-		ms_rx_qual = v; have_ms_rx_qual = 1; return *this;
-	}
-	pcu_l1_meas& set_ms_c_value(int16_t v) {
-		ms_c_value = v; have_ms_c_value = 1; return *this;
-	}
-	pcu_l1_meas& set_ms_sign_var(int16_t v) {
-		ms_sign_var = v; have_ms_sign_var = 1; return *this;
-	}
-	pcu_l1_meas& set_ms_i_level(size_t idx, int16_t v) {
-		ts[idx].set_ms_i_level(v); have_ms_i_level = 1; return *this;
-	}
-	pcu_l1_meas() :
-		have_rssi(0),
-		have_ber(0),
-		have_bto(0),
-		have_link_qual(0),
-		have_ms_rx_qual(0),
-		have_ms_c_value(0),
-		have_ms_sign_var(0),
-		have_ms_i_level(0),
-		rssi(0),
-		ber(0),
-		bto(0),
-		link_qual(0),
-		ms_rx_qual(0),
-		ms_c_value(0),
-		ms_sign_var(0)
-	{}
-#endif
 };
 
+static inline void pcu_l1_meas_set_rssi(struct pcu_l1_meas *m, int8_t v) {
+	m->rssi = v;
+	m->have_rssi = 1;
+}
+static inline void pcu_l1_meas_set_ber(struct pcu_l1_meas *m, uint8_t v) {
+	m->ber = v;
+	m->have_ber = 1;
+}
+static inline void pcu_l1_meas_set_bto(struct pcu_l1_meas *m, int16_t v) {
+	m->bto = v;
+	m->have_bto = 1;
+}
+static inline void pcu_l1_meas_set_link_qual(struct pcu_l1_meas *m, int16_t v) {
+	m->link_qual = v;
+	m->have_link_qual = 1;
+}
+static inline void pcu_l1_meas_set_ms_rx_qual(struct pcu_l1_meas *m, int16_t v) {
+	m->ms_rx_qual = v;
+	m->have_ms_rx_qual = 1;
+}
+static inline void pcu_l1_meas_set_ms_c_value(struct pcu_l1_meas *m, int16_t v) {
+	m->ms_c_value = v;
+	m->have_ms_c_value = 1;
+}
+static inline void pcu_l1_meas_set_ms_sign_var(struct pcu_l1_meas *m, int16_t v) {
+	m->ms_sign_var = v;
+	m->have_ms_sign_var = 1;
+}
+static inline void pcu_l1_meas_set_ms_i_level(struct pcu_l1_meas *m, size_t idx, int16_t v) {
+	pcu_l1_meas_ts_set_ms_i_level(&m->ts[idx], v);
+	m->have_ms_i_level = 1;
+}
+
 #ifdef __cplusplus
-void pcu_l1if_tx_pdtch(msgb *msg, uint8_t trx, uint8_t ts, uint16_t arfcn, 
+void pcu_l1if_tx_pdtch(msgb *msg, uint8_t trx, uint8_t ts, uint16_t arfcn,
         uint32_t fn, uint8_t block_nr);
 void pcu_l1if_tx_ptcch(uint8_t trx, uint8_t ts, uint16_t arfcn,
 		       uint32_t fn, uint8_t block_nr,
diff --git a/src/pcu_utils.h b/src/pcu_utils.h
index 8196a93..cb2fd91 100644
--- a/src/pcu_utils.h
+++ b/src/pcu_utils.h
@@ -15,26 +15,32 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  */
-
+#pragma once 
+#ifdef __cplusplus
 extern "C" {
+#endif
 #include <osmocom/gsm/gsm_utils.h>
+#ifdef __cplusplus
 }
+#endif
+
 #include <time.h>
 
-inline int msecs_to_frames(int msecs) {
+static inline int msecs_to_frames(int msecs) {
 	return (msecs * (1024 * 1000 / 4615)) / 1024;
 }
 
-inline uint32_t next_fn(uint32_t fn, uint32_t offset)
+static inline uint32_t next_fn(uint32_t fn, uint32_t offset)
 {
 	return (fn + offset) % GSM_MAX_FN;
 }
 
-inline void csecs_to_timespec(unsigned csecs, struct timespec *ts) {
+static inline void csecs_to_timespec(unsigned csecs, struct timespec *ts) {
 	ts->tv_sec  = csecs / 100;
 	ts->tv_nsec = (csecs % 100) * 10000000;
 }
 
+#ifdef __cplusplus
 template <typename T>
 inline unsigned int pcu_bitcount(T x)
 {
@@ -44,8 +50,15 @@
 
 	return count;
 }
+#endif
 
-inline uint8_t pcu_lsb(uint8_t x)
+static inline uint8_t pcu_lsb(uint8_t x)
 {
 	return x & -x;
 }
+
+/* Used to store a C++ class in a llist used by C code */
+struct llist_item {
+	struct llist_head list; /* item used by llist */
+	void *entry;
+};
diff --git a/src/pcu_vty_functions.cpp b/src/pcu_vty_functions.cpp
index 416c006..6100b13 100644
--- a/src/pcu_vty_functions.cpp
+++ b/src/pcu_vty_functions.cpp
@@ -61,7 +61,7 @@
 		tbf->first_ts,
 		tbf->first_common_ts, tbf->control_ts,
 		tbf->ms_class(),
-		tbf->ms() ? tbf->ms()->egprs_ms_class() : -1,
+		tbf->ms() ? ms_egprs_ms_class(tbf->ms()) : -1,
 		VTY_NEWLINE);
 	vty_out(vty, " TS_alloc=");
 	for (int i = 0; i < 8; i++) {
@@ -79,7 +79,7 @@
 			ul_tbf->window_size(), win->v_q(), win->v_r());
 		vty_out(vty, "%s", VTY_NEWLINE);
 		vty_out(vty, " TBF Statistics:%s", VTY_NEWLINE);
-		if (GPRS == tbf->ms()->mode()) {
+		if (GPRS == ms_mode(tbf->ms())) {
 			vty_out_rate_ctr_group(vty, " ", ul_tbf->m_ul_gprs_ctrs);
 		} else {
 			vty_out_rate_ctr_group(vty, " ", ul_tbf->m_ul_egprs_ctrs);
@@ -92,7 +92,7 @@
 			win->window_stalled() ? " STALLED" : "");
 		vty_out(vty, "%s", VTY_NEWLINE);
 		vty_out_rate_ctr_group(vty, " ", tbf->m_ctrs);
-		if (GPRS == tbf->ms()->mode()) {
+		if (GPRS == ms_mode(tbf->ms())) {
 			vty_out_rate_ctr_group(vty, " ", dl_tbf->m_dl_gprs_ctrs);
 		} else {
 			vty_out_rate_ctr_group(vty, " ", dl_tbf->m_dl_egprs_ctrs);
@@ -124,81 +124,83 @@
 static int show_ms(struct vty *vty, GprsMs *ms)
 {
 	unsigned i;
-	LListHead<gprs_rlcmac_tbf> *i_tbf;
+	struct llist_item *i_tbf;
 	uint8_t slots;
 
-	vty_out(vty, "MS TLLI=%08x, IMSI=%s%s", ms->tlli(), ms->imsi(), VTY_NEWLINE);
-	vty_out(vty, "  Timing advance (TA):    %d%s", ms->ta(), VTY_NEWLINE);
-	vty_out(vty, "  Coding scheme uplink:   %s%s", mcs_name(ms->current_cs_ul()),
+	vty_out(vty, "MS TLLI=%08x, IMSI=%s%s", ms_tlli(ms), ms_imsi(ms), VTY_NEWLINE);
+	vty_out(vty, "  Timing advance (TA):    %d%s", ms_ta(ms), VTY_NEWLINE);
+	vty_out(vty, "  Coding scheme uplink:   %s%s", mcs_name(ms_current_cs_ul(ms)),
 		VTY_NEWLINE);
-	vty_out(vty, "  Coding scheme downlink: %s%s", mcs_name(ms->current_cs_dl()),
+	vty_out(vty, "  Coding scheme downlink: %s%s", mcs_name(ms_current_cs_dl(ms)),
 		VTY_NEWLINE);
-	vty_out(vty, "  Mode:                   %s%s", mode_name(ms->mode()), VTY_NEWLINE);
-	vty_out(vty, "  MS class:               %d%s", ms->ms_class(), VTY_NEWLINE);
-	vty_out(vty, "  EGPRS MS class:         %d%s", ms->egprs_ms_class(), VTY_NEWLINE);
+	vty_out(vty, "  Mode:                   %s%s", mode_name(ms_mode(ms)), VTY_NEWLINE);
+	vty_out(vty, "  MS class:               %d%s", ms_ms_class(ms), VTY_NEWLINE);
+	vty_out(vty, "  EGPRS MS class:         %d%s", ms_egprs_ms_class(ms), VTY_NEWLINE);
 	vty_out(vty, "  PACCH:                  ");
-	slots = ms->current_pacch_slots();
+	slots = ms_current_pacch_slots(ms);
 	for (int i = 0; i < 8; i++)
 		if (slots & (1 << i))
 			vty_out(vty, "%d ", i);
 	vty_out(vty, "%s", VTY_NEWLINE);
-	vty_out(vty, "  LLC queue length:       %zd%s", ms->llc_queue()->size(),
+	vty_out(vty, "  LLC queue length:       %zd%s", llc_queue_size(ms_llc_queue(ms)),
 		VTY_NEWLINE);
-	vty_out(vty, "  LLC queue octets:       %zd%s", ms->llc_queue()->octets(),
+	vty_out(vty, "  LLC queue octets:       %zd%s", llc_queue_octets(ms_llc_queue(ms)),
 		VTY_NEWLINE);
-	if (ms->l1_meas()->have_rssi)
+	if (ms->l1_meas.have_rssi)
 		vty_out(vty, "  RSSI:                   %d dBm%s",
-			ms->l1_meas()->rssi, VTY_NEWLINE);
-	if (ms->l1_meas()->have_ber)
+			ms->l1_meas.rssi, VTY_NEWLINE);
+	if (ms->l1_meas.have_ber)
 		vty_out(vty, "  Bit error rate:         %d %%%s",
-			ms->l1_meas()->ber, VTY_NEWLINE);
-	if (ms->l1_meas()->have_link_qual)
+			ms->l1_meas.ber, VTY_NEWLINE);
+	if (ms->l1_meas.have_link_qual)
 		vty_out(vty, "  Link quality:           %d dB%s",
-			ms->l1_meas()->link_qual, VTY_NEWLINE);
-	if (ms->l1_meas()->have_bto)
+			ms->l1_meas.link_qual, VTY_NEWLINE);
+	if (ms->l1_meas.have_bto)
 		vty_out(vty, "  Burst timing offset:    %d/4 bit%s",
-			ms->l1_meas()->bto, VTY_NEWLINE);
-	if (ms->l1_meas()->have_ms_rx_qual)
+			ms->l1_meas.bto, VTY_NEWLINE);
+	if (ms->l1_meas.have_ms_rx_qual)
 		vty_out(vty, "  Downlink NACK rate:     %d %%%s",
-			ms->nack_rate_dl(), VTY_NEWLINE);
-	if (ms->l1_meas()->have_ms_rx_qual)
+			ms_nack_rate_dl(ms), VTY_NEWLINE);
+	if (ms->l1_meas.have_ms_rx_qual)
 		vty_out(vty, "  MS RX quality:          %d %%%s",
-			ms->l1_meas()->ms_rx_qual, VTY_NEWLINE);
-	if (ms->l1_meas()->have_ms_c_value)
+			ms->l1_meas.ms_rx_qual, VTY_NEWLINE);
+	if (ms->l1_meas.have_ms_c_value)
 		vty_out(vty, "  MS C value:             %d dB%s",
-			ms->l1_meas()->ms_c_value, VTY_NEWLINE);
-	if (ms->l1_meas()->have_ms_sign_var)
+			ms->l1_meas.ms_c_value, VTY_NEWLINE);
+	if (ms->l1_meas.have_ms_sign_var)
 		vty_out(vty, "  MS SIGN variance:       %d dB%s",
-			ms->l1_meas()->ms_sign_var, VTY_NEWLINE);
-	for (i = 0; i < ARRAY_SIZE(ms->l1_meas()->ts); ++i) {
-		if (ms->l1_meas()->ts[i].have_ms_i_level)
+			ms->l1_meas.ms_sign_var, VTY_NEWLINE);
+	for (i = 0; i < ARRAY_SIZE(ms->l1_meas.ts); ++i) {
+		if (ms->l1_meas.ts[i].have_ms_i_level)
 			vty_out(vty, "  MS I level (slot %d):    %d dB%s",
-				i, ms->l1_meas()->ts[i].ms_i_level, VTY_NEWLINE);
+				i, ms->l1_meas.ts[i].ms_i_level, VTY_NEWLINE);
 	}
-	vty_out(vty, "  RLC/MAC DL Control Msg: %d%s", ms->dl_ctrl_msg(),
+	vty_out(vty, "  RLC/MAC DL Control Msg: %d%s", ms_dl_ctrl_msg(ms),
 		VTY_NEWLINE);
-	if (ms->ul_tbf())
+	if (ms_ul_tbf(ms))
 		vty_out(vty, "  Uplink TBF:             TFI=%d, state=%s%s",
-			ms->ul_tbf()->tfi(),
-			ms->ul_tbf()->state_name(),
+			ms_ul_tbf(ms)->tfi(),
+			ms_ul_tbf(ms)->state_name(),
 			VTY_NEWLINE);
-	if (ms->dl_tbf()) {
+	if (ms_dl_tbf(ms)) {
 		vty_out(vty, "  Downlink TBF:           TFI=%d, state=%s%s",
-			ms->dl_tbf()->tfi(),
-			ms->dl_tbf()->state_name(),
+			ms_dl_tbf(ms)->tfi(),
+			ms_dl_tbf(ms)->state_name(),
 			VTY_NEWLINE);
 		vty_out(vty, "  Current DL Throughput:  %d Kbps %s",
-			ms->dl_tbf()->m_bw.dl_throughput,
+			ms_dl_tbf(ms)->m_bw.dl_throughput,
 			VTY_NEWLINE);
 	}
 
-	llist_for_each(i_tbf, &ms->old_tbfs())
+	llist_for_each_entry(i_tbf, &ms->old_tbfs, list) {
+		struct gprs_rlcmac_tbf *tbf = (struct gprs_rlcmac_tbf *)i_tbf->entry;
 		vty_out(vty, "  Old %-19s TFI=%d, state=%s%s",
-			i_tbf->entry()->direction == GPRS_RLCMAC_UL_TBF ?
+			tbf_direction(tbf) == GPRS_RLCMAC_UL_TBF ?
 			"Uplink TBF:" : "Downlink TBF:",
-			i_tbf->entry()->tfi(),
-			i_tbf->entry()->state_name(),
+			tbf->tfi(),
+			tbf->state_name(),
 			VTY_NEWLINE);
+	}
 
 	return CMD_SUCCESS;
 }
@@ -206,10 +208,10 @@
 int pcu_vty_show_ms_all(struct vty *vty, struct gprs_rlcmac_bts *bts_data)
 {
 	BTS *bts = bts_data->bts;
-	LListHead<GprsMs> *ms_iter;
+	GprsMs *ms_iter;
 
-	llist_for_each(ms_iter, &bts->ms_store().ms_list())
-		show_ms(vty, ms_iter->entry());
+	llist_for_each_entry(ms_iter, bts->ms_store().ms_list(), list)
+		show_ms(vty, ms_iter);
 
 	return CMD_SUCCESS;
 }
diff --git a/src/pdch.cpp b/src/pdch.cpp
index 4a0ff06..49cce8d 100644
--- a/src/pdch.cpp
+++ b/src/pdch.cpp
@@ -68,9 +68,9 @@
 		18, /* 18,10 % */
 	};
 
-	meas->set_ms_rx_qual(rx_qual_map[
-		OSMO_MIN(rx_qual_enc, ARRAY_SIZE(rx_qual_map)-1)
-		]);
+	pcu_l1_meas_set_ms_rx_qual(meas, rx_qual_map[
+					OSMO_MIN(rx_qual_enc, ARRAY_SIZE(rx_qual_map)-1)
+					]);
 }
 
 static void get_meas(struct pcu_l1_meas *meas,
@@ -78,9 +78,9 @@
 {
 	unsigned i;
 
-	meas->set_ms_c_value(qr->C_VALUE);
+	pcu_l1_meas_set_ms_c_value(meas, qr->C_VALUE);
 	if (qr->Exist_SIGN_VAR)
-		meas->set_ms_sign_var((qr->SIGN_VAR + 2) / 4); /* SIGN_VAR * 0.25 dB */
+		pcu_l1_meas_set_ms_sign_var(meas, (qr->SIGN_VAR + 2) / 4); /* SIGN_VAR * 0.25 dB */
 
 	for (i = 0; i < OSMO_MIN(ARRAY_SIZE(qr->I_LEVEL_TN), ARRAY_SIZE(meas->ts)); i++)
 	{
@@ -88,7 +88,7 @@
 			LOGP(DRLCMAC, LOGL_INFO,
 				"Packet resource request: i_level[%d] = %d\n",
 				i, qr->I_LEVEL_TN[i].I_LEVEL);
-			meas->set_ms_i_level(i, -2 * qr->I_LEVEL_TN[i].I_LEVEL);
+			pcu_l1_meas_set_ms_i_level(meas, i, -2 * qr->I_LEVEL_TN[i].I_LEVEL);
 		}
 	}
 }
@@ -99,8 +99,8 @@
 	unsigned i;
 
 	get_rx_qual_meas(meas, qr->RXQUAL);
-	meas->set_ms_c_value(qr->C_VALUE);
-	meas->set_ms_sign_var((qr->SIGN_VAR + 2) / 4); /* SIGN_VAR * 0.25 dB */
+	pcu_l1_meas_set_ms_c_value(meas, qr->C_VALUE);
+	pcu_l1_meas_set_ms_sign_var(meas, (qr->SIGN_VAR + 2) / 4); /* SIGN_VAR * 0.25 dB */
 
 	for (i = 0; i < OSMO_MIN(ARRAY_SIZE(qr->Slot), ARRAY_SIZE(meas->ts)); i++)
 	{
@@ -108,7 +108,7 @@
 			LOGP(DRLCMAC, LOGL_DEBUG,
 				"Channel quality report: i_level[%d] = %d\n",
 				i, qr->Slot[i].I_LEVEL_TN);
-			meas->set_ms_i_level(i, -2 * qr->Slot[i].I_LEVEL_TN);
+			pcu_l1_meas_set_ms_i_level(meas, i, -2 * qr->Slot[i].I_LEVEL_TN);
 		}
 	}
 }
@@ -307,11 +307,11 @@
 			LOGP(DRLCMAC, LOGL_NOTICE, "PACKET CONTROL ACK with "
 			     "unknown TBF corresponds to MS with IMSI %s, TA %d, "
 			     "uTBF (TFI=%d, state=%s), dTBF (TFI=%d, state=%s)\n",
-			     ms->imsi(), ms->ta(),
-			     ms->ul_tbf() ? ms->ul_tbf()->tfi() : 0,
-			     ms->ul_tbf() ? ms->ul_tbf()->state_name() : "None",
-			     ms->dl_tbf() ? ms->dl_tbf()->tfi() : 0,
-			     ms->dl_tbf() ? ms->dl_tbf()->state_name() : "None");
+			     ms_imsi(ms), ms_ta(ms),
+			     ms_ul_tbf(ms) ? ms_ul_tbf(ms)->tfi() : 0,
+			     ms_ul_tbf(ms) ? ms_ul_tbf(ms)->state_name() : "None",
+			     ms_dl_tbf(ms) ? ms_dl_tbf(ms)->tfi() : 0,
+			     ms_dl_tbf(ms) ? ms_dl_tbf(ms)->state_name() : "None");
 		return;
 	}
 
@@ -339,7 +339,7 @@
 		tbf->n_reset(N3105);
 		TBF_SET_ASS_STATE_DL(tbf, GPRS_RLCMAC_DL_ASS_NONE);
 
-		new_tbf = tbf->ms() ? tbf->ms()->dl_tbf() : NULL;
+		new_tbf = tbf->ms() ? ms_dl_tbf(tbf->ms()) : NULL;
 		if (!new_tbf) {
 			LOGP(DRLCMAC, LOGL_ERROR, "Got ACK, but DL "
 				"TBF is gone TLLI=0x%08x\n", tlli);
@@ -371,7 +371,7 @@
 		tbf->n_reset(N3105);
 		TBF_SET_ASS_STATE_UL(tbf, GPRS_RLCMAC_UL_ASS_NONE);
 
-		new_tbf = tbf->ms() ? tbf->ms()->ul_tbf() : NULL;
+		new_tbf = tbf->ms() ? ms_ul_tbf(tbf->ms()) : NULL;
 		if (!new_tbf) {
 			LOGP(DRLCMAC, LOGL_ERROR, "Got ACK, but UL "
 				"TBF is gone TLLI=0x%08x\n", tlli);
@@ -389,7 +389,7 @@
 		/* there might be LLC packets waiting in the queue, but the DL
 		 * TBF might have been released while the UL TBF has been
 		 * established */
-		if (new_tbf->ms()->need_dl_tbf())
+		if (ms_need_dl_tbf(new_tbf->ms()))
 			new_tbf->establish_dl_tbf_on_pacch();
 
 		return;
@@ -460,7 +460,7 @@
 	/* get measurements */
 	if (tbf->ms()) {
 		get_meas(meas, &ack_nack->Channel_Quality_Report);
-		tbf->ms()->update_l1_meas(meas);
+		ms_update_l1_meas(tbf->ms(), meas);
 	}
 }
 
@@ -570,19 +570,19 @@
 		if (!ms) {
 			ms_found = false;
 			ms = bts()->ms_alloc(0, 0); /* ms class updated later */
-			ms->set_tlli(tlli);
+			ms_set_tlli(ms, tlli);
 		}
-		ul_tbf = ms->ul_tbf(); /* hence ul_tbf may be NULL */
+		ul_tbf = ms_ul_tbf(ms); /* hence ul_tbf may be NULL */
 
 		/* Keep the ms, even if it gets idle temporarily */
-		GprsMs::Guard guard(ms);
+		ms_ref(ms);
 
 		LOGP(DRLCMAC, LOGL_DEBUG, "MS requests UL TBF "
 			"in packet resource request of single "
 			"block, so we provide one:\n");
 		sba = bts()->sba()->find(this, fn);
 		if (sba) {
-			ms->set_ta(sba->ta);
+			ms_set_ta(ms, sba->ta);
 			bts()->sba()->free_sba(sba);
 		} else if (!ul_tbf || !ul_tbf->state_is(GPRS_RLCMAC_FINISHED)) {
 			LOGPTBFUL(ul_tbf, LOGL_NOTICE,
@@ -599,9 +599,9 @@
 			ms_class = Decoding::get_ms_class_by_capability(&request->MS_Radio_Access_capability2);
 			egprs_ms_class = Decoding::get_egprs_ms_class_by_capability(&request->MS_Radio_Access_capability2);
 			if (ms_class)
-				ms->set_ms_class(ms_class);
+				ms_set_ms_class(ms, ms_class);
 			if (egprs_ms_class)
-				ms->set_egprs_ms_class(egprs_ms_class);
+				ms_set_egprs_ms_class(ms, egprs_ms_class);
 		}
 
 		/* Get rid of previous finished UL TBF before providing a new one */
@@ -616,7 +616,7 @@
 		if (!ul_tbf) {
 			handle_tbf_reject(bts_data(), ms, tlli,
 				trx_no(), ts_no);
-			return;
+			goto return_unref;
 		}
 
 		/* set control ts to current MS's TS, until assignment complete */
@@ -630,8 +630,10 @@
 		/* get measurements */
 		if (ul_tbf->ms()) {
 			get_meas(meas, request);
-			ul_tbf->ms()->update_l1_meas(meas);
+			ms_update_l1_meas(ul_tbf->ms(), meas);
 		}
+return_unref:
+		ms_unref(ms);
 		return;
 	}
 
@@ -674,10 +676,10 @@
 		LOGP(DRLCMAC, LOGL_NOTICE, "MS send measurement "
 		     "but TLLI 0x%08x is unknown\n", report->TLLI);
 		ms = bts()->ms_alloc(0, 0);
-		ms->set_tlli(report->TLLI);
+		ms_set_tlli(ms, report->TLLI);
 	}
 	if ((sba = bts()->sba()->find(this, fn))) {
-		ms->set_ta(sba->ta);
+		ms_set_ta(ms, sba->ta);
 		bts()->sba()->free_sba(sba);
 	}
 	gprs_rlcmac_meas_rep(ms, report);
diff --git a/src/tbf.cpp b/src/tbf.cpp
index e92dfb6..fde44ba 100644
--- a/src/tbf.cpp
+++ b/src/tbf.cpp
@@ -141,14 +141,13 @@
 	m_tfi(0),
 	m_created_ts(0),
 	m_ctrs(NULL),
-	m_ms(ms),
 	state(GPRS_RLCMAC_NULL),
+	m_ms(ms),
 	dl_ass_state(GPRS_RLCMAC_DL_ASS_NONE),
 	ul_ass_state(GPRS_RLCMAC_UL_ASS_NONE),
 	ul_ack_state(GPRS_RLCMAC_UL_ACK_NONE),
 	poll_state(GPRS_RLCMAC_POLL_NONE),
 	m_list(this),
-	m_ms_list(this),
 	m_egprs_enabled(false)
 {
 	/* The classes of these members do not have proper constructors yet.
@@ -158,6 +157,9 @@
 	memset(&Narr, 0, sizeof(Narr));
 	memset(&gsm_timer, 0, sizeof(gsm_timer));
 
+	memset(&m_ms_list, 0, sizeof(m_ms_list));
+	m_ms_list.entry = this;
+
 	m_rlc.init();
 	m_llc.init();
 
@@ -171,27 +173,27 @@
 
 uint32_t gprs_rlcmac_tbf::tlli() const
 {
-	return m_ms ? m_ms->tlli() : GSM_RESERVED_TMSI;
+	return m_ms ? ms_tlli(m_ms) : GSM_RESERVED_TMSI;
 }
 
 const char *gprs_rlcmac_tbf::imsi() const
 {
-	return m_ms->imsi();
+	return ms_imsi(m_ms);
 }
 
 uint8_t gprs_rlcmac_tbf::ta() const
 {
-	return m_ms->ta();
+	return ms_ta(m_ms);
 }
 
 void gprs_rlcmac_tbf::set_ta(uint8_t ta)
 {
-	ms()->set_ta(ta);
+	ms_set_ta(m_ms, ta);
 }
 
 uint8_t gprs_rlcmac_tbf::ms_class() const
 {
-	return m_ms->ms_class();
+	return ms_ms_class(m_ms);
 }
 
 enum CodingScheme gprs_rlcmac_tbf::current_cs() const
@@ -199,28 +201,21 @@
 	enum CodingScheme cs;
 
 	if (direction == GPRS_RLCMAC_UL_TBF)
-		cs = m_ms ? m_ms->current_cs_ul() : UNKNOWN;
+		cs = m_ms ? ms_current_cs_ul(m_ms) : UNKNOWN;
 	else
-		cs = m_ms ? m_ms->current_cs_dl() : UNKNOWN;
+		cs = m_ms ? ms_current_cs_dl(m_ms) : UNKNOWN;
 
 	return cs;
 }
 
 gprs_llc_queue *gprs_rlcmac_tbf::llc_queue()
 {
-	return m_ms ? m_ms->llc_queue() : NULL;
+	return m_ms ? ms_llc_queue(m_ms) : NULL;
 }
 
 const gprs_llc_queue *gprs_rlcmac_tbf::llc_queue() const
 {
-	return m_ms ? m_ms->llc_queue() : NULL;
-}
-
-size_t gprs_rlcmac_tbf::llc_queue_size() const
-{
-	/* m_ms->llc_queue() never returns NULL: GprsMs::m_llc_queue is a
-	 * member instance. */
-	return m_ms ? m_ms->llc_queue()->size() : 0;
+	return ms_llc_queue(m_ms);
 }
 
 void gprs_rlcmac_tbf::set_ms(GprsMs *ms)
@@ -229,13 +224,13 @@
 		return;
 
 	if (m_ms) {
-		m_ms->detach_tbf(this);
+		ms_detach_tbf(m_ms, this);
 	}
 
 	m_ms = ms;
 
 	if (m_ms)
-		m_ms->attach_tbf(this);
+		ms_attach_tbf(m_ms, this);
 }
 
 void gprs_rlcmac_tbf::update_ms(uint32_t tlli, enum gprs_rlcmac_tbf_direction dir)
@@ -247,18 +242,18 @@
 	 * MS object that belongs to that TLLI and if yes make sure one of them
 	 * gets deleted. This is the same problem that can arise with
 	 * IMSI in gprs_rlcmac_dl_tbf::handle() so there should be a unified solution */
-	if (!ms()->check_tlli(tlli)) {
+	if (!ms_check_tlli(ms(), tlli)) {
 		GprsMs *old_ms;
 
 		old_ms = bts->ms_store().get_ms(tlli, 0, NULL);
 		if (old_ms)
-			ms()->merge_and_clear_ms(old_ms);
+			ms_merge_and_clear_ms(ms(), old_ms);
 	}
 
 	if (dir == GPRS_RLCMAC_UL_TBF)
-		ms()->set_tlli(tlli);
+		ms_set_tlli(ms(), tlli);
 	else
-		ms()->confirm_tlli(tlli);
+		ms_confirm_tlli(ms(), tlli);
 }
 
 static void tbf_unlink_pdch(struct gprs_rlcmac_tbf *tbf)
@@ -745,7 +740,7 @@
 	struct gprs_rlcmac_bts *bts_data = bts->bts_data();
 	int rc;
 
-	if (m_ms->mode() != GPRS)
+	if (ms_mode(m_ms) != GPRS)
 		enable_egprs();
 
 	m_created_ts = time(NULL);
@@ -775,7 +770,7 @@
 		return -1;
 	}
 
-	m_ms->attach_tbf(this);
+	ms_attach_tbf(m_ms, this);
 
 	return 0;
 }
@@ -895,7 +890,7 @@
 	}
 
 	if (ms())
-		new_dl_tbf = ms()->dl_tbf();
+		new_dl_tbf = ms_dl_tbf(ms());
 
 	if (!new_dl_tbf) {
 		LOGPTBFDL(this, LOGL_ERROR,
@@ -1012,7 +1007,7 @@
 		return NULL;
 
 	if (ms())
-		new_tbf = ms()->ul_tbf();
+		new_tbf = ms_ul_tbf(ms());
 	if (!new_tbf) {
 		LOGPTBFUL(this, LOGL_ERROR,
 			  "We have a schedule for uplink assignment, but there is no uplink TBF\n");
@@ -1170,3 +1165,58 @@
 {
 	return ts == control_ts;
 }
+
+/* C API */
+enum gprs_rlcmac_tbf_state tbf_state(const struct gprs_rlcmac_tbf *tbf)
+{
+	return tbf->state;
+}
+
+enum gprs_rlcmac_tbf_direction tbf_direction(const struct gprs_rlcmac_tbf *tbf)
+{
+	return tbf->direction;
+}
+
+void tbf_set_ms(struct gprs_rlcmac_tbf *tbf, GprsMs *ms)
+{
+	tbf->set_ms(ms);
+}
+
+struct llist_head *tbf_ms_list(struct gprs_rlcmac_tbf *tbf)
+{
+	return &tbf->m_ms_list.list;
+}
+
+struct GprsMs *tbf_ms(struct gprs_rlcmac_tbf *tbf)
+{
+	return tbf->ms();
+}
+
+bool tbf_timers_pending(struct gprs_rlcmac_tbf *tbf, enum tbf_timers t)
+{
+	return tbf->timers_pending(t);
+}
+
+struct gprs_llc *tbf_llc(struct gprs_rlcmac_tbf *tbf)
+{
+	return &tbf->m_llc;
+}
+
+uint8_t tbf_first_common_ts(const struct gprs_rlcmac_tbf *tbf)
+{
+	return tbf->first_common_ts;
+}
+
+uint8_t tbf_dl_slots(const struct gprs_rlcmac_tbf *tbf)
+{
+	return tbf->dl_slots();
+}
+uint8_t tbf_ul_slots(const struct gprs_rlcmac_tbf *tbf)
+{
+	return tbf->ul_slots();
+}
+
+bool tbf_is_tfi_assigned(const struct gprs_rlcmac_tbf *tbf)
+{
+	return tbf->is_tfi_assigned();
+}
diff --git a/src/tbf.h b/src/tbf.h
index c97477b..4bbfea2 100644
--- a/src/tbf.h
+++ b/src/tbf.h
@@ -25,11 +25,21 @@
 #include "llc.h"
 #include "rlc.h"
 #include "cxx_linuxlist.h"
+#include "pcu_utils.h"
 #include <gprs_debug.h>
 #include <gsm_timer.h>
 #include <stdint.h>
 
+struct bssgp_bvc_ctx;
+struct gprs_rlcmac_bts;
+
+#endif
+
+struct GprsMs;
+
+#ifdef __cplusplus
 extern "C" {
+#endif
 #include <osmocom/core/utils.h>
 #include <osmocom/core/linuxlist.h>
 #include <osmocom/core/logging.h>
@@ -37,12 +47,8 @@
 #include <osmocom/gsm/gsm48.h>
 
 #include "coding_scheme.h"
+#ifdef __cplusplus
 }
-
-struct bssgp_bvc_ctx;
-class GprsMs;
-struct gprs_rlcmac_bts;
-
 #endif
 
 /*
@@ -182,6 +188,28 @@
 #define TBF_ASS_TYPE_UNSET(t, kind) do { t->ass_type_mod(kind, true, __FILE__, __LINE__); } while(0)
 
 #ifdef __cplusplus
+extern "C" {
+#endif
+struct gprs_rlcmac_tbf;
+const char *tbf_name(struct gprs_rlcmac_tbf *tbf);
+enum gprs_rlcmac_tbf_state tbf_state(const struct gprs_rlcmac_tbf *tbf);
+enum gprs_rlcmac_tbf_direction tbf_direction(const struct gprs_rlcmac_tbf *tbf);
+void tbf_set_ms(struct gprs_rlcmac_tbf *tbf, struct GprsMs *ms);
+struct llist_head *tbf_ms_list(struct gprs_rlcmac_tbf *tbf);
+struct GprsMs *tbf_ms(struct gprs_rlcmac_tbf *tbf);
+bool tbf_timers_pending(struct gprs_rlcmac_tbf *tbf, enum tbf_timers t);
+void tbf_free(struct gprs_rlcmac_tbf *tbf);
+struct gprs_llc *tbf_llc(struct gprs_rlcmac_tbf *tbf);
+uint8_t tbf_first_common_ts(const struct gprs_rlcmac_tbf *tbf);
+uint8_t tbf_dl_slots(const struct gprs_rlcmac_tbf *tbf);
+uint8_t tbf_ul_slots(const struct gprs_rlcmac_tbf *tbf);
+bool tbf_is_tfi_assigned(const struct gprs_rlcmac_tbf *tbf);
+int tbf_assign_control_ts(struct gprs_rlcmac_tbf *tbf);
+#ifdef __cplusplus
+}
+#endif
+
+#ifdef __cplusplus
 
 struct gprs_rlcmac_tbf {
 	gprs_rlcmac_tbf(BTS *bts_, GprsMs *ms, gprs_rlcmac_tbf_direction dir);
@@ -255,7 +283,6 @@
 	void set_ta(uint8_t);
 	uint8_t ms_class() const;
 	enum CodingScheme current_cs() const;
-	size_t llc_queue_size() const;
 
 	time_t created_ts() const;
 	uint8_t dl_slots() const;
@@ -269,9 +296,6 @@
 	/* attempt to make things a bit more fair */
 	void rotate_in_list();
 
-	LListHead<gprs_rlcmac_tbf>& ms_list() {return this->m_ms_list;}
-	const LListHead<gprs_rlcmac_tbf>& ms_list() const {return this->m_ms_list;}
-
 	LListHead<gprs_rlcmac_tbf>& list();
 	const LListHead<gprs_rlcmac_tbf>& list() const;
 
@@ -321,6 +345,8 @@
 	time_t m_created_ts;
 
 	struct rate_ctr_group *m_ctrs;
+	enum gprs_rlcmac_tbf_state state;
+	struct llist_item m_ms_list;
 
 protected:
 	gprs_rlcmac_bts *bts_data() const;
@@ -331,26 +357,20 @@
 
 	static const char *tbf_state_name[6];
 
-	class GprsMs *m_ms;
+	struct GprsMs *m_ms;
 private:
 	void enable_egprs();
-	enum gprs_rlcmac_tbf_state state;
 	enum gprs_rlcmac_tbf_dl_ass_state dl_ass_state;
 	enum gprs_rlcmac_tbf_ul_ass_state ul_ass_state;
 	enum gprs_rlcmac_tbf_ul_ack_state ul_ack_state;
 	enum gprs_rlcmac_tbf_poll_state poll_state;
 	LListHead<gprs_rlcmac_tbf> m_list;
-	LListHead<gprs_rlcmac_tbf> m_ms_list;
 	bool m_egprs_enabled;
 	struct osmo_timer_list Tarr[T_MAX];
 	uint8_t Narr[N_MAX];
 	mutable char m_name_buf[60];
 };
 
-void tbf_free(struct gprs_rlcmac_tbf *tbf);
-
-int tbf_assign_control_ts(struct gprs_rlcmac_tbf *tbf);
-
 inline bool gprs_rlcmac_tbf::state_is(enum gprs_rlcmac_tbf_state rhs) const
 {
 	return state == rhs;
@@ -381,8 +401,6 @@
 	return state != rhs;
 }
 
-const char *tbf_name(gprs_rlcmac_tbf *tbf);
-
 inline const char *gprs_rlcmac_tbf::state_name() const
 {
 	return tbf_state_name[state];
diff --git a/src/tbf_dl.cpp b/src/tbf_dl.cpp
index bb89e81..613f7b8 100644
--- a/src/tbf_dl.cpp
+++ b/src/tbf_dl.cpp
@@ -131,7 +131,7 @@
 
 	LOGP(DTBF, LOGL_DEBUG, "********** DL-TBF starts here **********\n");
 	LOGP(DTBF, LOGL_INFO, "Allocating DL TBF: MS_CLASS=%d/%d\n",
-	     ms->ms_class(), ms->egprs_ms_class());
+	     ms_ms_class(ms), ms_egprs_ms_class(ms));
 
 	tbf = talloc(tall_pcu_ctx, struct gprs_rlcmac_dl_tbf);
 
@@ -241,7 +241,7 @@
 	struct gprs_rlcmac_ul_tbf *ul_tbf = NULL, *old_ul_tbf;
 	struct gprs_rlcmac_dl_tbf *dl_tbf = NULL;
 
-	ul_tbf = ms->ul_tbf();
+	ul_tbf = ms_ul_tbf(ms);
 
 	if (ul_tbf && ul_tbf->m_contention_resolution_done
 	 && !ul_tbf->m_final_ack_sent) {
@@ -291,41 +291,43 @@
 	/* check for existing TBF */
 	ms = bts->bts->ms_store().get_ms(tlli, tlli_old, imsi);
 
-	if (ms && strlen(ms->imsi()) == 0) {
+	if (ms && strlen(ms_imsi(ms)) == 0) {
 		ms_old = bts->bts->ms_store().get_ms(0, 0, imsi);
 		if (ms_old && ms_old != ms) {
 			/* The TLLI has changed (RAU), so there are two MS
 			 * objects for the same MS */
 			LOGP(DTBF, LOGL_NOTICE,
 			     "There is a new MS object for the same MS: (0x%08x, '%s') -> (0x%08x, '%s')\n",
-			     ms_old->tlli(), ms_old->imsi(), ms->tlli(), ms->imsi());
+			     ms_tlli(ms_old), ms_imsi(ms_old), ms_tlli(ms), ms_imsi(ms));
 
-			GprsMs::Guard guard_old(ms_old);
+			ms_ref(ms_old);
 
-			if (!ms->dl_tbf() && ms_old->dl_tbf()) {
+			if (!ms_dl_tbf(ms) && ms_dl_tbf(ms_old)) {
 				LOGP(DTBF, LOGL_NOTICE,
 				     "IMSI %s, old TBF %s: moving DL TBF to new MS object\n",
-				     imsi, ms_old->dl_tbf()->name());
-				dl_tbf = ms_old->dl_tbf();
+				     imsi, ms_dl_tbf(ms_old)->name());
+				dl_tbf = ms_dl_tbf(ms_old);
 				/* Move the DL TBF to the new MS */
 				dl_tbf->set_ms(ms);
 			}
-			ms->merge_and_clear_ms(ms_old);
+			ms_merge_and_clear_ms(ms, ms_old);
+
+			ms_unref(ms_old);
 		}
 	}
 
 	if (!ms)
 		ms = bts->bts->ms_alloc(ms_class, egprs_ms_class);
-	ms->set_imsi(imsi);
-	ms->confirm_tlli(tlli);
-	if (!ms->ms_class() && ms_class) {
-		ms->set_ms_class(ms_class);
+	ms_set_imsi(ms, imsi);
+	ms_confirm_tlli(ms, tlli);
+	if (!ms_ms_class(ms) && ms_class) {
+		ms_set_ms_class(ms, ms_class);
 	}
-	if (!ms->egprs_ms_class() && egprs_ms_class) {
-		ms->set_egprs_ms_class(egprs_ms_class);
+	if (!ms_egprs_ms_class(ms) && egprs_ms_class) {
+		ms_set_egprs_ms_class(ms, egprs_ms_class);
 	}
 
-	dl_tbf = ms->dl_tbf();
+	dl_tbf = ms_dl_tbf(ms);
 	if (!dl_tbf) {
 		rc = tbf_new_dl_assignment(bts, ms, &dl_tbf);
 		if (rc < 0)
@@ -344,7 +346,7 @@
 	uint32_t octets = 0, frames = 0;
 	struct timespec hyst_delta = {0, 0};
 	const unsigned keep_small_thresh = 60;
-	const gprs_llc_queue::MetaInfo *info;
+	const MetaInfo *info;
 
 	if (bts_data()->llc_discard_csec)
 		csecs_to_timespec(bts_data()->llc_discard_csec, &hyst_delta);
@@ -358,9 +360,9 @@
 
 		gprs_bssgp_update_queue_delay(tv_recv, &tv_now);
 
-		if (ms() && ms()->codel_state()) {
-			int bytes = llc_queue()->octets();
-			if (gprs_codel_control(ms()->codel_state(),
+		if (ms() && ms_codel_state(ms())) {
+			int bytes = llc_queue_octets(llc_queue());
+			if (gprs_codel_control(ms_codel_state(ms()),
 					tv_recv, &tv_now, bytes))
 				goto drop_frame;
 		}
@@ -402,7 +404,7 @@
 		LOGPTBFDL(this, LOGL_NOTICE, "Discarding LLC PDU "
 			"because lifetime limit reached, "
 			"count=%u new_queue_size=%zu\n",
-			  frames, llc_queue_size());
+			  frames, llc_queue_size(llc_queue()));
 		if (frames > 0xff)
 			frames = 0xff;
 		if (octets > 0xffffff)
@@ -459,7 +461,7 @@
 	} else if (bsn < 0 && is_egprs_enabled() && req_mcs_kind == EGPRS_GMSK) {
 		/* New data to be sent for EGPRS TBF but we are required to downgrade to
 		 * MCS1-4, because USF for GPRS-only MS will be sent */
-		force_cs = m_ms->current_cs_dl();
+		force_cs = ms_current_cs_dl(m_ms);
 		if (force_cs > MCS4) {
 			force_cs = bts->cs_dl_is_supported(MCS4) ? MCS4 :
 				   bts->cs_dl_is_supported(MCS3) ? MCS3 :
@@ -467,7 +469,7 @@
 				   MCS1;
 			LOGPTBFDL(this, LOGL_DEBUG,
 				  "Force downgrading DL %s -> %s due to USF for GPRS-only MS\n",
-				  mcs_name(m_ms->current_cs_dl()), mcs_name(force_cs));
+				  mcs_name(ms_current_cs_dl(m_ms)), mcs_name(force_cs));
 		}
 	}
 
@@ -483,14 +485,14 @@
 		if (is_egprs_enabled()) {
 			/* Table 8.1.1.2 and Table 8.1.1.1 of 44.060 */
 			m_rlc.block(bsn)->cs_current_trans = get_retx_mcs(m_rlc.block(bsn)->cs_init,
-									  ms()->current_cs_dl(),
+									  ms_current_cs_dl(ms()),
 									  !bts->bts_data()->dl_arq_type);
 
 			LOGPTBFDL(this, LOGL_DEBUG,
 				  "initial_cs_dl(%s) last_mcs(%s) demanded_mcs(%s) cs_trans(%s) arq_type(%d) bsn(%d)\n",
 				  mcs_name(m_rlc.block(bsn)->cs_init),
 				  mcs_name(m_rlc.block(bsn)->cs_last),
-				  mcs_name(ms()->current_cs_dl()),
+				  mcs_name(ms_current_cs_dl(ms())),
 				  mcs_name(m_rlc.block(bsn)->cs_current_trans),
 				  bts->bts_data()->dl_arq_type, bsn);
 
@@ -635,7 +637,7 @@
 {
 	struct msgb *msg;
 
-	if (m_llc.frame_length() != 0)
+	if (llc_frame_length(&m_llc) != 0)
 		return;
 
 	/* dequeue next LLC frame, if any */
@@ -661,7 +663,7 @@
 	int write_offset = 0;
 	Encoding::AppendResult ar;
 
-	if (m_llc.frame_length() == 0)
+	if (llc_frame_length(&m_llc) == 0)
 		schedule_next_frame();
 
 	OSMO_ASSERT(mcs_is_valid(cs));
@@ -693,7 +695,7 @@
 		bool is_final;
 		int payload_written = 0;
 
-		if (m_llc.frame_length() == 0) {
+		if (llc_frame_length(&m_llc) == 0) {
 			/* It is not clear, when the next real data will
 			 * arrive, so request a DL ack/nack now */
 			request_dl_ack();
@@ -731,10 +733,10 @@
 
 			LOGPTBFDL(this, LOGL_DEBUG,
 				  "Empty chunk, added LLC dummy command of size %d, drained_since=%d\n",
-				  m_llc.frame_length(), frames_since_last_drain(fn));
+				  llc_frame_length(&m_llc), frames_since_last_drain(fn));
 		}
 
-		is_final = llc_queue_size() == 0 && !keep_open(fn);
+		is_final = llc_queue_size(llc_queue()) == 0 && !keep_open(fn);
 
 		ar = Encoding::rlc_data_to_dl_append(rdbi, cs,
 			&m_llc, &write_offset, &num_chunks, data, is_final, &payload_written);
@@ -745,9 +747,9 @@
 		if (ar == Encoding::AR_NEED_MORE_BLOCKS)
 			break;
 
-		LOGPTBFDL(this, LOGL_DEBUG, "Complete DL frame, len=%d\n", m_llc.frame_length());
-		gprs_rlcmac_dl_bw(this, m_llc.frame_length());
-		bts->do_rate_ctr_add(CTR_LLC_DL_BYTES, m_llc.frame_length());
+		LOGPTBFDL(this, LOGL_DEBUG, "Complete DL frame, len=%d\n", llc_frame_length(&m_llc));
+		gprs_rlcmac_dl_bw(this, llc_frame_length(&m_llc));
+		bts->do_rate_ctr_add(CTR_LLC_DL_BYTES, llc_frame_length(&m_llc));
 		m_llc.reset();
 
 		if (is_final) {
@@ -1131,7 +1133,7 @@
 	error_rate = analyse_errors(show_rbb, behind_last_bsn, &ana_res);
 
 	if (bts_data()->cs_adj_enabled && ms())
-		ms()->update_error_rate(this, error_rate);
+		ms_update_error_rate(ms(), this, error_rate);
 
 	m_window.update(bts, rbb, first_bsn, &lost, &received);
 	rate_ctr_add(&m_ctrs->ctr[TBF_CTR_RLC_NACKED], lost);
@@ -1186,7 +1188,7 @@
 	error_rate = analyse_errors(show_rbb, ssn, &ana_res);
 
 	if (bts_data()->cs_adj_enabled && ms())
-		ms()->update_error_rate(this, error_rate);
+		ms_update_error_rate(ms(), this, error_rate);
 
 	m_window.update(bts, show_rbb, ssn,
 			&lost, &received);
@@ -1221,7 +1223,7 @@
 	release();
 
 	/* check for LLC PDU in the LLC Queue */
-	if (llc_queue_size() > 0)
+	if (llc_queue_size(llc_queue()) > 0)
 		/* we have more data so we will re-use this tbf */
 		establish_dl_tbf_on_pacch();
 
@@ -1331,8 +1333,8 @@
 
 bool gprs_rlcmac_dl_tbf::have_data() const
 {
-	return m_llc.chunk_size() > 0 ||
-		(llc_queue_size() > 0);
+	return llc_chunk_size(&m_llc) > 0 ||
+		(llc_queue_size(llc_queue()) > 0);
 }
 
 static inline int frames_since_last(int32_t last, unsigned fn)
@@ -1556,3 +1558,11 @@
 			  mcs_name(cs));
 	}
 }
+
+struct gprs_rlcmac_dl_tbf *as_dl_tbf(struct gprs_rlcmac_tbf *tbf)
+{
+	if (tbf && tbf->direction == GPRS_RLCMAC_DL_TBF)
+		return static_cast<gprs_rlcmac_dl_tbf *>(tbf);
+	else
+		return NULL;
+}
diff --git a/src/tbf_dl.h b/src/tbf_dl.h
index ffb370c..3cd88c9 100644
--- a/src/tbf_dl.h
+++ b/src/tbf_dl.h
@@ -141,15 +141,17 @@
 	return m_window.ws();
 }
 
-inline gprs_rlcmac_dl_tbf *as_dl_tbf(gprs_rlcmac_tbf *tbf)
-{
-	if (tbf && tbf->direction == GPRS_RLCMAC_DL_TBF)
-		return static_cast<gprs_rlcmac_dl_tbf *>(tbf);
-	else
-		return NULL;
-}
-
 struct gprs_rlcmac_dl_tbf *tbf_alloc_dl_tbf(struct gprs_rlcmac_bts *bts, GprsMs *ms,
 					    int8_t use_trx, bool single_slot);
 
+#else /* ifdef __cplusplus */
+struct gprs_rlcmac_dl_tbf;
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+struct gprs_rlcmac_dl_tbf *as_dl_tbf(struct gprs_rlcmac_tbf *tbf);
+#ifdef __cplusplus
+}
 #endif
diff --git a/src/tbf_ul.cpp b/src/tbf_ul.cpp
index 80a8eaa..f8c860c 100644
--- a/src/tbf_ul.cpp
+++ b/src/tbf_ul.cpp
@@ -105,7 +105,7 @@
 
 	LOGP(DTBF, LOGL_DEBUG, "********** UL-TBF starts here **********\n");
 	LOGP(DTBF, LOGL_INFO, "Allocating UL TBF: MS_CLASS=%d/%d\n",
-	     ms->ms_class(), ms->egprs_ms_class());
+	     ms_ms_class(ms), ms_egprs_ms_class(ms));
 
 	tbf = talloc(tall_pcu_ctx, struct gprs_rlcmac_ul_tbf);
 	if (!tbf)
@@ -172,7 +172,7 @@
 
 	if (!ms)
 		ms = bts->bts->ms_alloc(0, 0);
-	ms->set_tlli(tlli);
+	ms_set_tlli(ms, tlli);
 
 	ul_tbf = talloc(tall_pcu_ctx, struct gprs_rlcmac_ul_tbf);
 	if (!ul_tbf)
@@ -185,7 +185,7 @@
 	ul_tbf->bts->do_rate_ctr_inc(CTR_TBF_UL_ALLOCATED);
 	TBF_SET_ASS_ON(ul_tbf, GPRS_RLCMAC_FLAG_PACCH, false);
 
-	ms->attach_tbf(ul_tbf);
+	ms_attach_tbf(ms, ul_tbf);
 	ul_tbf->update_ms(tlli, GPRS_RLCMAC_UL_TBF);
 	TBF_SET_ASS_STATE_UL(ul_tbf, GPRS_RLCMAC_UL_ASS_SEND_ASS_REJ);
 	ul_tbf->control_ts = ts;
@@ -252,14 +252,14 @@
 				frame->is_complete);
 
 			m_llc.append_frame(data + frame->offset, frame->length);
-			m_llc.consume(frame->length);
+			llc_consume(&m_llc, frame->length);
 		}
 
 		if (frame->is_complete) {
 			/* send frame to SGSN */
-			LOGPTBFUL(this, LOGL_DEBUG, "complete UL frame len=%d\n", m_llc.frame_length());
+			LOGPTBFUL(this, LOGL_DEBUG, "complete UL frame len=%d\n", llc_frame_length(&m_llc));
 			snd_ul_ud();
-			bts->do_rate_ctr_add(CTR_LLC_UL_BYTES, m_llc.frame_length());
+			bts->do_rate_ctr_add(CTR_LLC_UL_BYTES, llc_frame_length(&m_llc));
 			m_llc.reset();
 		}
 	}
@@ -357,7 +357,7 @@
 
 	/* store measurement values */
 	if (ms())
-		ms()->update_l1_meas(meas);
+		ms_update_l1_meas(ms(), meas);
 
 	uint32_t new_tlli = GSM_RESERVED_TMSI;
 	unsigned int block_idx;
@@ -559,10 +559,10 @@
 {
 	uint8_t qos_profile[3];
 	struct msgb *llc_pdu;
-	unsigned msg_len = NS_HDR_LEN + BSSGP_HDR_LEN + m_llc.frame_length();
+	unsigned msg_len = NS_HDR_LEN + BSSGP_HDR_LEN + llc_frame_length(&m_llc);
 	struct bssgp_bvc_ctx *bctx = gprs_bssgp_pcu_current_bctx();
 
-	LOGP(DBSSGP, LOGL_INFO, "LLC [PCU -> SGSN] %s len=%d\n", tbf_name(this), m_llc.frame_length());
+	LOGP(DBSSGP, LOGL_INFO, "LLC [PCU -> SGSN] %s len=%d\n", tbf_name(this), llc_frame_length(&m_llc));
 	if (!bctx) {
 		LOGP(DBSSGP, LOGL_ERROR, "No bctx\n");
 		m_llc.reset_frame_space();
@@ -570,8 +570,8 @@
 	}
 
 	llc_pdu = msgb_alloc_headroom(msg_len, msg_len,"llc_pdu");
-	uint8_t *buf = msgb_push(llc_pdu, TL16V_GROSS_LEN(sizeof(uint8_t)*m_llc.frame_length()));
-	tl16v_put(buf, BSSGP_IE_LLC_PDU, sizeof(uint8_t)*m_llc.frame_length(), m_llc.frame);
+	uint8_t *buf = msgb_push(llc_pdu, TL16V_GROSS_LEN(sizeof(uint8_t)*llc_frame_length(&m_llc)));
+	tl16v_put(buf, BSSGP_IE_LLC_PDU, sizeof(uint8_t)*llc_frame_length(&m_llc), m_llc.frame);
 	qos_profile[0] = QOS_PROFILE >> 16;
 	qos_profile[1] = QOS_PROFILE >> 8;
 	qos_profile[2] = QOS_PROFILE;
@@ -772,3 +772,11 @@
 {
 	return &m_window;
 }
+
+struct gprs_rlcmac_ul_tbf *as_ul_tbf(struct gprs_rlcmac_tbf *tbf)
+{
+	if (tbf && tbf->direction == GPRS_RLCMAC_UL_TBF)
+		return static_cast<gprs_rlcmac_ul_tbf *>(tbf);
+	else
+		return NULL;
+}
diff --git a/src/tbf_ul.h b/src/tbf_ul.h
index 9ccdf62..1d9cf50 100644
--- a/src/tbf_ul.h
+++ b/src/tbf_ul.h
@@ -108,32 +108,28 @@
 	gprs_rlc_ul_window m_window;
 };
 
-#ifdef __cplusplus
-extern "C" {
-#endif
-void update_tbf_ta(struct gprs_rlcmac_ul_tbf *tbf, int8_t ta_delta);
-void set_tbf_ta(struct gprs_rlcmac_ul_tbf *tbf, uint8_t ta);
-#ifdef __cplusplus
-}
-#endif
-
 inline uint16_t gprs_rlcmac_ul_tbf::window_size() const
 {
 	return m_window.ws();
 }
 
-inline gprs_rlcmac_ul_tbf *as_ul_tbf(gprs_rlcmac_tbf *tbf)
-{
-	if (tbf && tbf->direction == GPRS_RLCMAC_UL_TBF)
-		return static_cast<gprs_rlcmac_ul_tbf *>(tbf);
-	else
-		return NULL;
-}
-
 struct gprs_rlcmac_ul_tbf *tbf_alloc_ul_tbf(struct gprs_rlcmac_bts *bts, GprsMs *ms, int8_t use_trx, bool single_slot);
 struct gprs_rlcmac_ul_tbf *tbf_alloc_ul(struct gprs_rlcmac_bts *bts, GprsMs *ms,
 					int8_t use_trx, uint32_t tlli);
 struct gprs_rlcmac_ul_tbf *handle_tbf_reject(struct gprs_rlcmac_bts *bts,
 	GprsMs *ms, uint32_t tlli, uint8_t trx_no, uint8_t ts_no);
 
+#else /* ifdef __cplusplus */
+struct gprs_rlcmac_ul_tbf;
+#endif
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+void update_tbf_ta(struct gprs_rlcmac_ul_tbf *tbf, int8_t ta_delta);
+void set_tbf_ta(struct gprs_rlcmac_ul_tbf *tbf, uint8_t ta);
+struct gprs_rlcmac_ul_tbf *as_ul_tbf(struct gprs_rlcmac_tbf *tbf);
+#ifdef __cplusplus
+}
 #endif
diff --git a/tests/alloc/AllocTest.cpp b/tests/alloc/AllocTest.cpp
index 8ebf159..17cee46 100644
--- a/tests/alloc/AllocTest.cpp
+++ b/tests/alloc/AllocTest.cpp
@@ -224,12 +224,12 @@
 		return false;
 
 	OSMO_ASSERT(ul_tbf->ms());
-	OSMO_ASSERT(ul_tbf->ms()->current_trx());
+	OSMO_ASSERT(ms_current_trx(ul_tbf->ms()));
 
 	dump_assignment(ul_tbf, "UL", verbose);
 
 	/* assume final ack has not been sent */
-	dl_tbf = tbf_alloc_dl_tbf(bts, ms, ms->current_trx()->trx_no, false);
+	dl_tbf = tbf_alloc_dl_tbf(bts, ms, ms_current_trx(ms)->trx_no, false);
 	if (!dl_tbf)
 		return false;
 
@@ -268,11 +268,11 @@
 
 	dl_tbf->update_ms(0x23, GPRS_RLCMAC_DL_TBF);
 	OSMO_ASSERT(dl_tbf->ms() == ms);
-	OSMO_ASSERT(dl_tbf->ms()->current_trx());
+	OSMO_ASSERT(ms_current_trx(dl_tbf->ms()));
 
 	dump_assignment(dl_tbf, "DL", verbose);
 
-	ul_tbf = tbf_alloc_ul_tbf(bts, ms, ms->current_trx()->trx_no, false);
+	ul_tbf = tbf_alloc_ul_tbf(bts, ms, ms_current_trx(ms)->trx_no, false);
 	if (!ul_tbf)
 		return false;
 
@@ -319,8 +319,8 @@
 		return false;
 
 	OSMO_ASSERT(ul_tbf->ms() == ms);
-	OSMO_ASSERT(ul_tbf->ms()->current_trx());
-	trx_no = ms->current_trx()->trx_no;
+	OSMO_ASSERT(ms_current_trx(ul_tbf->ms()));
+	trx_no = ms_current_trx(ms)->trx_no;
 	dump_assignment(ul_tbf, "UL", true);
 
 	/* assume final ack has not been sent */
@@ -453,50 +453,55 @@
 	}
 }
 
-static GprsMs *alloc_tbfs(BTS *the_bts, GprsMs *ms, enum test_mode mode)
+static GprsMs *alloc_tbfs(BTS *the_bts, struct GprsMs *old_ms, enum test_mode mode)
 {
 	struct gprs_rlcmac_bts *bts;
+	struct GprsMs *ms, *new_ms;
 	uint8_t trx_no = -1;
 
-	OSMO_ASSERT(ms != NULL);
+	OSMO_ASSERT(old_ms != NULL);
 
 	bts = the_bts->bts_data();
 
 	gprs_rlcmac_tbf *tbf = NULL;
 
-	if (ms && ms->current_trx())
-		trx_no = ms->current_trx()->trx_no;
+	if (ms_current_trx(old_ms))
+		trx_no = ms_current_trx(old_ms)->trx_no;
 
-	GprsMs::Guard guard1(ms);
+	ms_ref(old_ms);
 
 	/* Allocate what is needed first */
 	switch (mode) {
 	case TEST_MODE_UL_ONLY:
 	case TEST_MODE_DL_AFTER_UL:
 	case TEST_MODE_UL_AND_DL:
-		if (ms->ul_tbf())
-			tbf_free(ms->ul_tbf());
-		tbf = tbf_alloc_ul_tbf(bts, ms, trx_no, false);
-		if (tbf == NULL)
+		if (ms_ul_tbf(old_ms))
+			tbf_free(ms_ul_tbf(old_ms));
+		tbf = tbf_alloc_ul_tbf(bts, old_ms, trx_no, false);
+		if (tbf == NULL) {
+			ms_unref(old_ms);
 			return NULL;
+		}
 		break;
 	case TEST_MODE_DL_ONLY:
 	case TEST_MODE_UL_AFTER_DL:
 	case TEST_MODE_DL_AND_UL:
-		if (ms->dl_tbf())
-			tbf_free(ms->dl_tbf());
-		tbf = tbf_alloc_dl_tbf(bts, ms, trx_no, false);
-		if (tbf == NULL)
+		if (ms_dl_tbf(old_ms))
+			tbf_free(ms_dl_tbf(old_ms));
+		tbf = tbf_alloc_dl_tbf(bts, old_ms, trx_no, false);
+		if (tbf == NULL) {
+			ms_unref(old_ms);
 			return NULL;
+		}
 	}
 
 	OSMO_ASSERT(tbf);
 	OSMO_ASSERT(tbf->ms());
-	OSMO_ASSERT(ms == NULL || ms == tbf->ms());
+	OSMO_ASSERT(old_ms == tbf->ms());
 	ms = tbf->ms();
 
-	GprsMs::Guard guard2(ms);
-
+	ms_ref(ms);
+	new_ms = ms;
 	/* Continue with what is needed next */
 	switch (mode) {
 	case TEST_MODE_UL_ONLY:
@@ -506,12 +511,12 @@
 
 	case TEST_MODE_DL_AFTER_UL:
 	case TEST_MODE_UL_AND_DL:
-		ms = alloc_tbfs(the_bts, ms, TEST_MODE_DL_ONLY);
+		new_ms = alloc_tbfs(the_bts, ms, TEST_MODE_DL_ONLY);
 		break;
 
 	case TEST_MODE_UL_AFTER_DL:
 	case TEST_MODE_DL_AND_UL:
-		ms = alloc_tbfs(the_bts, ms, TEST_MODE_UL_ONLY);
+		new_ms = alloc_tbfs(the_bts, ms, TEST_MODE_UL_ONLY);
 		break;
 	}
 
@@ -527,10 +532,12 @@
 		break;
 	}
 
-	if (!ms && tbf)
+	if (!new_ms && tbf)
 		tbf_free(tbf);
 
-	return  guard2.is_idle() ? NULL : ms;
+	ms_unref(old_ms);
+	ms_unref(ms);
+	return new_ms;
 }
 
 static unsigned alloc_many_tbfs(BTS *the_bts, unsigned min_class,
@@ -556,16 +563,16 @@
 		ms = the_bts->ms_by_tlli(tlli);
 		if (!ms)
 			ms = the_bts->ms_alloc(0, 0);
-		ms->set_ms_class(ms_class);
+		ms_set_ms_class(ms, ms_class);
 		ms = alloc_tbfs(the_bts, ms, mode);
 		if (!ms)
 			break;
 
-		ms->set_tlli(tlli);
+		ms_set_tlli(ms, tlli);
 
-		ul_tbf = ms->ul_tbf();
-		dl_tbf = ms->dl_tbf();
-		trx = ms->current_trx();
+		ul_tbf = ms_ul_tbf(ms);
+		dl_tbf = ms_dl_tbf(ms);
+		trx = ms_current_trx(ms);
 
 		OSMO_ASSERT(ul_tbf || dl_tbf);
 
@@ -616,12 +623,12 @@
 			get_dir_char(0x80, ul_slots, dl_slots, busy_slots));
 
 		if (tfi >= 0) {
-			OSMO_ASSERT(ms->current_trx());
+			OSMO_ASSERT(ms_current_trx(ms));
 			tfi2 = the_bts->tfi_find_free(dir, &trx_no2,
-				ms->current_trx()->trx_no);
+				ms_current_trx(ms)->trx_no);
 			OSMO_ASSERT(tfi != tfi2);
 			OSMO_ASSERT(tfi2 < 0 ||
-				trx_no2 == ms->current_trx()->trx_no);
+				trx_no2 == ms_current_trx(ms)->trx_no);
 		}
 
 		ms_class += 1;
diff --git a/tests/app_info/AppInfoTest.cpp b/tests/app_info/AppInfoTest.cpp
index cd4454d..6e33538 100644
--- a/tests/app_info/AppInfoTest.cpp
+++ b/tests/app_info/AppInfoTest.cpp
@@ -152,9 +152,9 @@
 {
 	fprintf(stderr, "--- %s ---\n",  __func__);
 
+	tbf_free(tbf1);
+	tbf_free(tbf2);
 	BTS::main_bts()->cleanup();
-	talloc_free(tbf1);
-	talloc_free(tbf2);
 	/* FIXME: talloc report disabled, because bts->ms_alloc() in prepare_bts_with_two_dl_tbf_subscr() causes leak */
 	/* talloc_report_full(tall_pcu_ctx, stderr); */
 	talloc_free(tall_pcu_ctx);
diff --git a/tests/app_info/AppInfoTest.err b/tests/app_info/AppInfoTest.err
index ae20ea3..9b0910f 100644
--- a/tests/app_info/AppInfoTest.err
+++ b/tests/app_info/AppInfoTest.err
@@ -52,3 +52,14 @@
 Sending Packet Application Information to 2 subscribers with active TBF
 
 --- cleanup ---
+PDCH(TS 4, TRX 0): Detaching TBF(TFI=0 TLLI=0xffffffff DIR=DL STATE=RELEASING EGPRS), 1 TBFs, USFs = 00, TFIs = 00000002.
+PDCH(TS 5, TRX 0): Detaching TBF(TFI=0 TLLI=0xffffffff DIR=DL STATE=RELEASING EGPRS), 1 TBFs, USFs = 00, TFIs = 00000002.
+PDCH(TS 6, TRX 0): Detaching TBF(TFI=0 TLLI=0xffffffff DIR=DL STATE=RELEASING EGPRS), 1 TBFs, USFs = 00, TFIs = 00000002.
+PDCH(TS 7, TRX 0): Detaching TBF(TFI=0 TLLI=0xffffffff DIR=DL STATE=RELEASING EGPRS), 0 TBFs, USFs = 00, TFIs = 00000000.
+Detaching TBF from MS object, TLLI = 0xffffffff, TBF = TBF(TFI=0 TLLI=0xffffffff DIR=DL STATE=RELEASING EGPRS)
+PDCH(TS 4, TRX 0): Detaching TBF(TFI=1 TLLI=0xffffffff DIR=DL STATE=RELEASING EGPRS), 0 TBFs, USFs = 00, TFIs = 00000000.
+PDCH(TS 5, TRX 0): Detaching TBF(TFI=1 TLLI=0xffffffff DIR=DL STATE=RELEASING EGPRS), 0 TBFs, USFs = 00, TFIs = 00000000.
+PDCH(TS 6, TRX 0): Detaching TBF(TFI=1 TLLI=0xffffffff DIR=DL STATE=RELEASING EGPRS), 0 TBFs, USFs = 00, TFIs = 00000000.
+Detaching TBF from MS object, TLLI = 0xffffffff, TBF = TBF(TFI=1 TLLI=0xffffffff DIR=DL STATE=RELEASING EGPRS)
+Destroying MS object, TLLI = 0xffffffff
+Destroying MS object, TLLI = 0xffffffff
diff --git a/tests/edge/EdgeTest.cpp b/tests/edge/EdgeTest.cpp
index 67ed2a7..8fa76dd 100644
--- a/tests/edge/EdgeTest.cpp
+++ b/tests/edge/EdgeTest.cpp
@@ -619,7 +619,7 @@
 	write_offset = 0;
 	memset(data, 0, sizeof(data));
 
-	OSMO_ASSERT(llc.chunk_size() == 1);
+	OSMO_ASSERT(llc_chunk_size(&llc) == 1);
 
 	count_payload = -1;
 
@@ -767,7 +767,7 @@
 	write_offset = 0;
 	memset(data, 0, sizeof(data));
 
-	OSMO_ASSERT(llc.chunk_size() == 10);
+	OSMO_ASSERT(llc_chunk_size(&llc) == 10);
 	count_payload = -1;
 
 	ar = Encoding::rlc_data_to_dl_append(&rdbi, cs,
@@ -899,7 +899,7 @@
 	write_offset = 0;
 	memset(data, 0, sizeof(data));
 
-	OSMO_ASSERT(llc.chunk_size() == 0);
+	OSMO_ASSERT(llc_chunk_size(&llc) == 0);
 	count_payload = -1;
 
 	ar = Encoding::rlc_data_to_dl_append(&rdbi, cs,
@@ -1167,7 +1167,6 @@
 	uint8_t ts_no, uint32_t tlli, uint32_t *fn, uint16_t qta,
 	uint8_t ms_class)
 {
-	struct pcu_l1_meas meas;
 	int tfi = 0;
 	uint8_t data[79] = {0};
 	struct gprs_rlc_ul_header_egprs_2 *egprs2  = NULL;
diff --git a/tests/llc/LlcTest.cpp b/tests/llc/LlcTest.cpp
index 10cd96b..bfcac77 100644
--- a/tests/llc/LlcTest.cpp
+++ b/tests/llc/LlcTest.cpp
@@ -60,10 +60,10 @@
 }
 
 static void dequeue_and_check(gprs_llc_queue *queue, const uint8_t *exp_data,
-	size_t len, const gprs_llc_queue::MetaInfo *exp_info)
+	size_t len, const MetaInfo *exp_info)
 {
 	struct msgb *llc_msg;
-	const gprs_llc_queue::MetaInfo *info_res;
+	const MetaInfo *info_res;
 
 	llc_msg = queue->dequeue(&info_res);
 	OSMO_ASSERT(llc_msg != NULL);
@@ -88,7 +88,7 @@
 }
 
 static void dequeue_and_check(gprs_llc_queue *queue, const char *exp_message,
-	const gprs_llc_queue::MetaInfo *exp_info = 0)
+	const MetaInfo *exp_info = 0)
 {
 	dequeue_and_check(queue,
 		(uint8_t *)(exp_message), strlen(exp_message), exp_info);
@@ -101,33 +101,33 @@
 
 	printf("=== start %s ===\n", __func__);
 
-	queue.init();
-	OSMO_ASSERT(queue.size() == 0);
-	OSMO_ASSERT(queue.octets() == 0);
+	llc_queue_init(&queue);
+	OSMO_ASSERT(llc_queue_size(&queue) == 0);
+	OSMO_ASSERT(llc_queue_octets(&queue) == 0);
 
 	enqueue_data(&queue, "LLC message", &expire_time);
-	OSMO_ASSERT(queue.size() == 1);
-	OSMO_ASSERT(queue.octets() == 11);
+	OSMO_ASSERT(llc_queue_size(&queue) == 1);
+	OSMO_ASSERT(llc_queue_octets(&queue) == 11);
 
 	enqueue_data(&queue, "other LLC message", &expire_time);
-	OSMO_ASSERT(queue.size() == 2);
-	OSMO_ASSERT(queue.octets() == 28);
+	OSMO_ASSERT(llc_queue_size(&queue) == 2);
+	OSMO_ASSERT(llc_queue_octets(&queue) == 28);
 
 	dequeue_and_check(&queue, "LLC message");
-	OSMO_ASSERT(queue.size() == 1);
-	OSMO_ASSERT(queue.octets() == 17);
+	OSMO_ASSERT(llc_queue_size(&queue) == 1);
+	OSMO_ASSERT(llc_queue_octets(&queue) == 17);
 
 	dequeue_and_check(&queue, "other LLC message");
-	OSMO_ASSERT(queue.size() == 0);
-	OSMO_ASSERT(queue.octets() == 0);
+	OSMO_ASSERT(llc_queue_size(&queue) == 0);
+	OSMO_ASSERT(llc_queue_octets(&queue) == 0);
 
 	enqueue_data(&queue, "LLC",  &expire_time);
-	OSMO_ASSERT(queue.size() == 1);
-	OSMO_ASSERT(queue.octets() == 3);
+	OSMO_ASSERT(llc_queue_size(&queue) == 1);
+	OSMO_ASSERT(llc_queue_octets(&queue) == 3);
 
-	queue.clear(NULL);
-	OSMO_ASSERT(queue.size() == 0);
-	OSMO_ASSERT(queue.octets() == 0);
+	llc_queue_clear(&queue, NULL);
+	OSMO_ASSERT(llc_queue_size(&queue) == 0);
+	OSMO_ASSERT(llc_queue_octets(&queue) == 0);
 
 	printf("=== end %s ===\n", __func__);
 }
@@ -135,14 +135,14 @@
 static void test_llc_meta()
 {
 	gprs_llc_queue queue;
-	gprs_llc_queue::MetaInfo info1 = {0};
-	gprs_llc_queue::MetaInfo info2 = {0};
+	MetaInfo info1 = {0};
+	MetaInfo info2 = {0};
 
 	printf("=== start %s ===\n", __func__);
 
-	queue.init();
-	OSMO_ASSERT(queue.size() == 0);
-	OSMO_ASSERT(queue.octets() == 0);
+	llc_queue_init(&queue);
+	OSMO_ASSERT(llc_queue_size(&queue) == 0);
+	OSMO_ASSERT(llc_queue_octets(&queue) == 0);
 
 	info1.recv_time.tv_sec = 123456777;
 	info1.recv_time.tv_nsec = 123456000;
@@ -161,9 +161,9 @@
 	dequeue_and_check(&queue, "LLC message 1", &info1);
 	dequeue_and_check(&queue, "LLC message 2", &info2);
 
-	queue.clear(NULL);
-	OSMO_ASSERT(queue.size() == 0);
-	OSMO_ASSERT(queue.octets() == 0);
+	llc_queue_clear(&queue, NULL);
+	OSMO_ASSERT(llc_queue_size(&queue) == 0);
+	OSMO_ASSERT(llc_queue_octets(&queue) == 0);
 
 	printf("=== end %s ===\n", __func__);
 }
@@ -176,8 +176,8 @@
 
 	printf("=== start %s ===\n", __func__);
 
-	queue1.init();
-	queue2.init();
+	llc_queue_init(&queue1);
+	llc_queue_init(&queue2);
 
 	clk_mono_override_time->tv_sec += 1;
 	enqueue_data(&queue1, "*A*", &expire_time);
@@ -194,17 +194,17 @@
 	clk_mono_override_time->tv_sec += 1;
 	enqueue_data(&queue2, "*E*", &expire_time);
 
-	OSMO_ASSERT(queue1.size() == 3);
-	OSMO_ASSERT(queue1.octets() == 9);
-	OSMO_ASSERT(queue2.size() == 2);
-	OSMO_ASSERT(queue2.octets() == 6);
+	OSMO_ASSERT(llc_queue_size(&queue1) == 3);
+	OSMO_ASSERT(llc_queue_octets(&queue1) == 9);
+	OSMO_ASSERT(llc_queue_size(&queue2) == 2);
+	OSMO_ASSERT(llc_queue_octets(&queue2) == 6);
 
-	queue2.move_and_merge(&queue1);
+	llc_queue_move_and_merge(&queue2, &queue1);
 
-	OSMO_ASSERT(queue1.size() == 0);
-	OSMO_ASSERT(queue1.octets() == 0);
-	OSMO_ASSERT(queue2.size() == 5);
-	OSMO_ASSERT(queue2.octets() == 15);
+	OSMO_ASSERT(llc_queue_size(&queue1) == 0);
+	OSMO_ASSERT(llc_queue_octets(&queue1) == 0);
+	OSMO_ASSERT(llc_queue_size(&queue2) == 5);
+	OSMO_ASSERT(llc_queue_octets(&queue2) == 15);
 
 	dequeue_and_check(&queue2, "*A*");
 	dequeue_and_check(&queue2, "*B*");
@@ -212,8 +212,8 @@
 	dequeue_and_check(&queue2, "*D*");
 	dequeue_and_check(&queue2, "*E*");
 
-	OSMO_ASSERT(queue2.size() == 0);
-	OSMO_ASSERT(queue2.octets() == 0);
+	OSMO_ASSERT(llc_queue_size(&queue2) == 0);
+	OSMO_ASSERT(llc_queue_octets(&queue2) == 0);
 
 	printf("=== end %s ===\n", __func__);
 }
diff --git a/tests/ms/MsTest.cpp b/tests/ms/MsTest.cpp
index 4f47bc9..c164409 100644
--- a/tests/ms/MsTest.cpp
+++ b/tests/ms/MsTest.cpp
@@ -56,33 +56,33 @@
 
 	printf("=== start %s ===\n", __func__);
 
-	ms = new GprsMs(&the_bts, tlli);
-	OSMO_ASSERT(ms->is_idle());
+	ms = ms_alloc(&the_bts, tlli);
+	OSMO_ASSERT(ms_is_idle(ms));
 
 	dl_tbf = talloc_zero(tall_pcu_ctx, struct gprs_rlcmac_dl_tbf);
 	new (dl_tbf) gprs_rlcmac_dl_tbf(&the_bts, ms);
 	ul_tbf = talloc_zero(tall_pcu_ctx, struct gprs_rlcmac_ul_tbf);
 	new (ul_tbf) gprs_rlcmac_ul_tbf(&the_bts, ms);
 
-	ms->attach_tbf(ul_tbf);
-	OSMO_ASSERT(!ms->is_idle());
-	OSMO_ASSERT(ms->ul_tbf() == ul_tbf);
-	OSMO_ASSERT(ms->dl_tbf() == NULL);
+	ms_attach_tbf(ms, ul_tbf);
+	OSMO_ASSERT(!ms_is_idle(ms));
+	OSMO_ASSERT(ms_ul_tbf(ms) == ul_tbf);
+	OSMO_ASSERT(ms_dl_tbf(ms) == NULL);
 
-	ms->attach_tbf(dl_tbf);
-	OSMO_ASSERT(!ms->is_idle());
-	OSMO_ASSERT(ms->ul_tbf() == ul_tbf);
-	OSMO_ASSERT(ms->dl_tbf() == dl_tbf);
+	ms_attach_tbf(ms, dl_tbf);
+	OSMO_ASSERT(!ms_is_idle(ms));
+	OSMO_ASSERT(ms_ul_tbf(ms) == ul_tbf);
+	OSMO_ASSERT(ms_dl_tbf(ms) == dl_tbf);
 
-	OSMO_ASSERT(ms->tbf(GPRS_RLCMAC_UL_TBF) == ul_tbf);
-	OSMO_ASSERT(ms->tbf(GPRS_RLCMAC_DL_TBF) == dl_tbf);
+	OSMO_ASSERT(ms_tbf(ms, GPRS_RLCMAC_UL_TBF) == ul_tbf);
+	OSMO_ASSERT(ms_tbf(ms, GPRS_RLCMAC_DL_TBF) == dl_tbf);
 
-	ms->detach_tbf(ul_tbf);
-	OSMO_ASSERT(!ms->is_idle());
-	OSMO_ASSERT(ms->ul_tbf() == NULL);
-	OSMO_ASSERT(ms->dl_tbf() == dl_tbf);
+	ms_detach_tbf(ms, ul_tbf);
+	OSMO_ASSERT(!ms_is_idle(ms));
+	OSMO_ASSERT(ms_ul_tbf(ms) == NULL);
+	OSMO_ASSERT(ms_dl_tbf(ms) == dl_tbf);
 
-	ms->detach_tbf(dl_tbf);
+	ms_detach_tbf(ms, dl_tbf);
 	/* The ms object is freed now */
 	ms = NULL;
 
@@ -92,6 +92,23 @@
 	printf("=== end %s ===\n", __func__);
 }
 
+static enum {CB_UNKNOWN, CB_IS_IDLE, CB_IS_ACTIVE} last_cb = CB_UNKNOWN;
+static void ms_idle_cb(struct GprsMs *ms)
+{
+	OSMO_ASSERT(ms_is_idle(ms));
+	printf("  ms_idle() was called\n");
+	last_cb = CB_IS_IDLE;
+}
+static void ms_active_cb(struct GprsMs *ms)
+{
+	OSMO_ASSERT(!ms_is_idle(ms));
+	printf("  ms_active() was called\n");
+	last_cb = CB_IS_ACTIVE;
+}
+static struct gpr_ms_callback ms_cb = {
+	.ms_idle = ms_idle_cb,
+	.ms_active = ms_active_cb
+};
 static void test_ms_callback()
 {
 	uint32_t tlli = 0xffeeddbb;
@@ -99,63 +116,50 @@
 	gprs_rlcmac_ul_tbf *ul_tbf;
 	BTS the_bts;
 	GprsMs *ms;
-	static enum {UNKNOWN, IS_IDLE, IS_ACTIVE} last_cb = UNKNOWN;
-
-	struct MyCallback: public GprsMs::Callback {
-		virtual void ms_idle(class GprsMs *ms) {
-			OSMO_ASSERT(ms->is_idle());
-			printf("  ms_idle() was called\n");
-			last_cb = IS_IDLE;
-		}
-		virtual void ms_active(class GprsMs *ms) {
-			OSMO_ASSERT(!ms->is_idle());
-			printf("  ms_active() was called\n");
-			last_cb = IS_ACTIVE;
-		}
-	} cb;
+	last_cb = CB_UNKNOWN;
 
 	printf("=== start %s ===\n", __func__);
 
-	ms = new GprsMs(&the_bts, tlli);
-	ms->set_callback(&cb);
+	ms = ms_alloc(&the_bts, tlli);
+	ms_set_callback(ms, &ms_cb);
 
-	OSMO_ASSERT(ms->is_idle());
+	OSMO_ASSERT(ms_is_idle(ms));
 
 	dl_tbf = talloc_zero(tall_pcu_ctx, struct gprs_rlcmac_dl_tbf);
 	new (dl_tbf) gprs_rlcmac_dl_tbf(&the_bts, ms);
 	ul_tbf = talloc_zero(tall_pcu_ctx, struct gprs_rlcmac_ul_tbf);
 	new (ul_tbf) gprs_rlcmac_ul_tbf(&the_bts, ms);
 
-	OSMO_ASSERT(last_cb == UNKNOWN);
+	OSMO_ASSERT(last_cb == CB_UNKNOWN);
 
-	ms->attach_tbf(ul_tbf);
-	OSMO_ASSERT(!ms->is_idle());
-	OSMO_ASSERT(ms->ul_tbf() == ul_tbf);
-	OSMO_ASSERT(ms->dl_tbf() == NULL);
-	OSMO_ASSERT(last_cb == IS_ACTIVE);
+	ms_attach_tbf(ms, ul_tbf);
+	OSMO_ASSERT(!ms_is_idle(ms));
+	OSMO_ASSERT(ms_ul_tbf(ms) == ul_tbf);
+	OSMO_ASSERT(ms_dl_tbf(ms) == NULL);
+	OSMO_ASSERT(last_cb == CB_IS_ACTIVE);
 
-	last_cb = UNKNOWN;
+	last_cb = CB_UNKNOWN;
 
-	ms->attach_tbf(dl_tbf);
-	OSMO_ASSERT(!ms->is_idle());
-	OSMO_ASSERT(ms->ul_tbf() == ul_tbf);
-	OSMO_ASSERT(ms->dl_tbf() == dl_tbf);
-	OSMO_ASSERT(last_cb == UNKNOWN);
+	ms_attach_tbf(ms, dl_tbf);
+	OSMO_ASSERT(!ms_is_idle(ms));
+	OSMO_ASSERT(ms_ul_tbf(ms) == ul_tbf);
+	OSMO_ASSERT(ms_dl_tbf(ms) == dl_tbf);
+	OSMO_ASSERT(last_cb == CB_UNKNOWN);
 
-	ms->detach_tbf(ul_tbf);
-	OSMO_ASSERT(!ms->is_idle());
-	OSMO_ASSERT(ms->ul_tbf() == NULL);
-	OSMO_ASSERT(ms->dl_tbf() == dl_tbf);
-	OSMO_ASSERT(last_cb == UNKNOWN);
+	ms_detach_tbf(ms, ul_tbf);
+	OSMO_ASSERT(!ms_is_idle(ms));
+	OSMO_ASSERT(ms_ul_tbf(ms) == NULL);
+	OSMO_ASSERT(ms_dl_tbf(ms) == dl_tbf);
+	OSMO_ASSERT(last_cb == CB_UNKNOWN);
 
-	ms->detach_tbf(dl_tbf);
-	OSMO_ASSERT(ms->is_idle());
-	OSMO_ASSERT(ms->ul_tbf() == NULL);
-	OSMO_ASSERT(ms->dl_tbf() == NULL);
-	OSMO_ASSERT(last_cb == IS_IDLE);
+	ms_detach_tbf(ms, dl_tbf);
+	OSMO_ASSERT(ms_is_idle(ms));
+	OSMO_ASSERT(ms_ul_tbf(ms) == NULL);
+	OSMO_ASSERT(ms_dl_tbf(ms) == NULL);
+	OSMO_ASSERT(last_cb == CB_IS_IDLE);
 
-	last_cb = UNKNOWN;
-	delete ms;
+	last_cb = CB_UNKNOWN;
+	talloc_free(ms);
 
 	talloc_free(dl_tbf);
 	talloc_free(ul_tbf);
@@ -163,6 +167,22 @@
 	printf("=== end %s ===\n", __func__);
 }
 
+static bool was_idle;
+static void ms_replace_tbf_idle_cb(struct GprsMs *ms)
+{
+	OSMO_ASSERT(ms_is_idle(ms));
+	printf("  ms_idle() was called\n");
+	was_idle = true;
+}
+static void ms_replace_tbf_active_cb(struct GprsMs *ms)
+{
+	OSMO_ASSERT(!ms_is_idle(ms));
+	printf("  ms_active() was called\n");
+}
+static struct gpr_ms_callback ms_replace_tbf_cb = {
+	.ms_idle = ms_replace_tbf_idle_cb,
+	.ms_active = ms_replace_tbf_active_cb
+};
 static void test_ms_replace_tbf()
 {
 	uint32_t tlli = 0xffeeddbb;
@@ -170,26 +190,13 @@
 	gprs_rlcmac_ul_tbf *ul_tbf;
 	BTS the_bts;
 	GprsMs *ms;
-	static bool was_idle;
-
-	struct MyCallback: public GprsMs::Callback {
-		virtual void ms_idle(class GprsMs *ms) {
-			OSMO_ASSERT(ms->is_idle());
-			printf("  ms_idle() was called\n");
-			was_idle = true;
-		}
-		virtual void ms_active(class GprsMs *ms) {
-			OSMO_ASSERT(!ms->is_idle());
-			printf("  ms_active() was called\n");
-		}
-	} cb;
 
 	printf("=== start %s ===\n", __func__);
 
-	ms = new GprsMs(&the_bts, tlli);
-	ms->set_callback(&cb);
+	ms = ms_alloc(&the_bts, tlli);
+	ms_set_callback(ms, &ms_replace_tbf_cb);
 
-	OSMO_ASSERT(ms->is_idle());
+	OSMO_ASSERT(ms_is_idle(ms));
 	was_idle = false;
 
 	dl_tbf[0] = talloc_zero(tall_pcu_ctx, struct gprs_rlcmac_dl_tbf);
@@ -199,49 +206,49 @@
 	ul_tbf = talloc_zero(tall_pcu_ctx, struct gprs_rlcmac_ul_tbf);
 	new (ul_tbf) gprs_rlcmac_ul_tbf(&the_bts, ms);
 
-	ms->attach_tbf(dl_tbf[0]);
-	OSMO_ASSERT(!ms->is_idle());
-	OSMO_ASSERT(ms->ul_tbf() == NULL);
-	OSMO_ASSERT(ms->dl_tbf() == dl_tbf[0]);
-	OSMO_ASSERT(llist_empty(&ms->old_tbfs()));
+	ms_attach_tbf(ms, dl_tbf[0]);
+	OSMO_ASSERT(!ms_is_idle(ms));
+	OSMO_ASSERT(ms_ul_tbf(ms) == NULL);
+	OSMO_ASSERT(ms_dl_tbf(ms) == dl_tbf[0]);
+	OSMO_ASSERT(llist_empty(&ms->old_tbfs));
 	OSMO_ASSERT(!was_idle);
 
-	ms->attach_tbf(dl_tbf[1]);
-	OSMO_ASSERT(!ms->is_idle());
-	OSMO_ASSERT(ms->ul_tbf() == NULL);
-	OSMO_ASSERT(ms->dl_tbf() == dl_tbf[1]);
-	OSMO_ASSERT(!llist_empty(&ms->old_tbfs()));
+	ms_attach_tbf(ms, dl_tbf[1]);
+	OSMO_ASSERT(!ms_is_idle(ms));
+	OSMO_ASSERT(ms_ul_tbf(ms) == NULL);
+	OSMO_ASSERT(ms_dl_tbf(ms) == dl_tbf[1]);
+	OSMO_ASSERT(!llist_empty(&ms->old_tbfs));
 	OSMO_ASSERT(!was_idle);
 
-	ms->attach_tbf(ul_tbf);
-	OSMO_ASSERT(!ms->is_idle());
-	OSMO_ASSERT(ms->ul_tbf() == ul_tbf);
-	OSMO_ASSERT(ms->dl_tbf() == dl_tbf[1]);
-	OSMO_ASSERT(!llist_empty(&ms->old_tbfs()));
+	ms_attach_tbf(ms, ul_tbf);
+	OSMO_ASSERT(!ms_is_idle(ms));
+	OSMO_ASSERT(ms_ul_tbf(ms) == ul_tbf);
+	OSMO_ASSERT(ms_dl_tbf(ms) == dl_tbf[1]);
+	OSMO_ASSERT(!llist_empty(&ms->old_tbfs));
 	OSMO_ASSERT(!was_idle);
 
-	ms->detach_tbf(ul_tbf);
-	OSMO_ASSERT(!ms->is_idle());
-	OSMO_ASSERT(ms->ul_tbf() == NULL);
-	OSMO_ASSERT(ms->dl_tbf() == dl_tbf[1]);
-	OSMO_ASSERT(!llist_empty(&ms->old_tbfs()));
+	ms_detach_tbf(ms, ul_tbf);
+	OSMO_ASSERT(!ms_is_idle(ms));
+	OSMO_ASSERT(ms_ul_tbf(ms) == NULL);
+	OSMO_ASSERT(ms_dl_tbf(ms) == dl_tbf[1]);
+	OSMO_ASSERT(!llist_empty(&ms->old_tbfs));
 	OSMO_ASSERT(!was_idle);
 
-	ms->detach_tbf(dl_tbf[0]);
-	OSMO_ASSERT(!ms->is_idle());
-	OSMO_ASSERT(ms->ul_tbf() == NULL);
-	OSMO_ASSERT(ms->dl_tbf() == dl_tbf[1]);
-	OSMO_ASSERT(llist_empty(&ms->old_tbfs()));
+	ms_detach_tbf(ms, dl_tbf[0]);
+	OSMO_ASSERT(!ms_is_idle(ms));
+	OSMO_ASSERT(ms_ul_tbf(ms) == NULL);
+	OSMO_ASSERT(ms_dl_tbf(ms) == dl_tbf[1]);
+	OSMO_ASSERT(llist_empty(&ms->old_tbfs));
 	OSMO_ASSERT(!was_idle);
 
-	ms->detach_tbf(dl_tbf[1]);
-	OSMO_ASSERT(ms->is_idle());
-	OSMO_ASSERT(ms->ul_tbf() == NULL);
-	OSMO_ASSERT(ms->dl_tbf() == NULL);
-	OSMO_ASSERT(llist_empty(&ms->old_tbfs()));
+	ms_detach_tbf(ms, dl_tbf[1]);
+	OSMO_ASSERT(ms_is_idle(ms));
+	OSMO_ASSERT(ms_ul_tbf(ms) == NULL);
+	OSMO_ASSERT(ms_dl_tbf(ms) == NULL);
+	OSMO_ASSERT(llist_empty(&ms->old_tbfs));
 	OSMO_ASSERT(was_idle);
 
-	delete ms;
+	talloc_free(ms);
 
 	talloc_free(dl_tbf[0]);
 	talloc_free(dl_tbf[1]);
@@ -260,86 +267,86 @@
 
 	printf("=== start %s ===\n", __func__);
 
-	ms = new GprsMs(&the_bts, start_tlli);
+	ms = ms_alloc(&the_bts, start_tlli);
 
-	OSMO_ASSERT(ms->is_idle());
+	OSMO_ASSERT(ms_is_idle(ms));
 
 	/* MS announces TLLI, SGSN uses it immediately */
-	ms->set_tlli(new_ms_tlli);
-	OSMO_ASSERT(ms->tlli() == new_ms_tlli);
-	OSMO_ASSERT(ms->check_tlli(new_ms_tlli));
-	OSMO_ASSERT(ms->check_tlli(start_tlli));
+	ms_set_tlli(ms, new_ms_tlli);
+	OSMO_ASSERT(ms_tlli(ms) == new_ms_tlli);
+	OSMO_ASSERT(ms_check_tlli(ms, new_ms_tlli));
+	OSMO_ASSERT(ms_check_tlli(ms, start_tlli));
 
-	ms->confirm_tlli(new_ms_tlli);
-	OSMO_ASSERT(ms->tlli() == new_ms_tlli);
-	OSMO_ASSERT(ms->check_tlli(new_ms_tlli));
-	OSMO_ASSERT(!ms->check_tlli(start_tlli));
+	ms_confirm_tlli(ms, new_ms_tlli);
+	OSMO_ASSERT(ms_tlli(ms) == new_ms_tlli);
+	OSMO_ASSERT(ms_check_tlli(ms, new_ms_tlli));
+	OSMO_ASSERT(!ms_check_tlli(ms, start_tlli));
 
 	/* MS announces TLLI, SGSN uses it later */
-	ms->set_tlli(start_tlli);
-	ms->confirm_tlli(start_tlli);
+	ms_set_tlli(ms, start_tlli);
+	ms_confirm_tlli(ms, start_tlli);
 
-	ms->set_tlli(new_ms_tlli);
-	OSMO_ASSERT(ms->tlli() == new_ms_tlli);
-	OSMO_ASSERT(ms->check_tlli(new_ms_tlli));
-	OSMO_ASSERT(ms->check_tlli(start_tlli));
+	ms_set_tlli(ms, new_ms_tlli);
+	OSMO_ASSERT(ms_tlli(ms) == new_ms_tlli);
+	OSMO_ASSERT(ms_check_tlli(ms, new_ms_tlli));
+	OSMO_ASSERT(ms_check_tlli(ms, start_tlli));
 
-	ms->confirm_tlli(start_tlli);
-	OSMO_ASSERT(ms->tlli() == new_ms_tlli);
-	OSMO_ASSERT(ms->check_tlli(new_ms_tlli));
-	OSMO_ASSERT(ms->check_tlli(start_tlli));
+	ms_confirm_tlli(ms, start_tlli);
+	OSMO_ASSERT(ms_tlli(ms) == new_ms_tlli);
+	OSMO_ASSERT(ms_check_tlli(ms, new_ms_tlli));
+	OSMO_ASSERT(ms_check_tlli(ms, start_tlli));
 
-	ms->set_tlli(new_ms_tlli);
-	OSMO_ASSERT(ms->tlli() == new_ms_tlli);
-	OSMO_ASSERT(ms->check_tlli(new_ms_tlli));
-	OSMO_ASSERT(ms->check_tlli(start_tlli));
+	ms_set_tlli(ms, new_ms_tlli);
+	OSMO_ASSERT(ms_tlli(ms) == new_ms_tlli);
+	OSMO_ASSERT(ms_check_tlli(ms, new_ms_tlli));
+	OSMO_ASSERT(ms_check_tlli(ms, start_tlli));
 
-	ms->confirm_tlli(new_ms_tlli);
-	OSMO_ASSERT(ms->tlli() == new_ms_tlli);
-	OSMO_ASSERT(ms->check_tlli(new_ms_tlli));
-	OSMO_ASSERT(!ms->check_tlli(start_tlli));
+	ms_confirm_tlli(ms, new_ms_tlli);
+	OSMO_ASSERT(ms_tlli(ms) == new_ms_tlli);
+	OSMO_ASSERT(ms_check_tlli(ms, new_ms_tlli));
+	OSMO_ASSERT(!ms_check_tlli(ms, start_tlli));
 
 	/* MS announces TLLI, SGSN uses it later after another new TLLI */
-	ms->set_tlli(start_tlli);
-	ms->confirm_tlli(start_tlli);
+	ms_set_tlli(ms, start_tlli);
+	ms_confirm_tlli(ms, start_tlli);
 
-	ms->set_tlli(new_ms_tlli);
-	OSMO_ASSERT(ms->tlli() == new_ms_tlli);
-	OSMO_ASSERT(ms->check_tlli(new_ms_tlli));
-	OSMO_ASSERT(ms->check_tlli(start_tlli));
+	ms_set_tlli(ms, new_ms_tlli);
+	OSMO_ASSERT(ms_tlli(ms) == new_ms_tlli);
+	OSMO_ASSERT(ms_check_tlli(ms, new_ms_tlli));
+	OSMO_ASSERT(ms_check_tlli(ms, start_tlli));
 
-	ms->confirm_tlli(other_sgsn_tlli);
-	OSMO_ASSERT(ms->tlli() == new_ms_tlli);
-	OSMO_ASSERT(ms->check_tlli(new_ms_tlli));
-	OSMO_ASSERT(ms->check_tlli(other_sgsn_tlli));
+	ms_confirm_tlli(ms, other_sgsn_tlli);
+	OSMO_ASSERT(ms_tlli(ms) == new_ms_tlli);
+	OSMO_ASSERT(ms_check_tlli(ms, new_ms_tlli));
+	OSMO_ASSERT(ms_check_tlli(ms, other_sgsn_tlli));
 
-	ms->set_tlli(new_ms_tlli);
-	OSMO_ASSERT(ms->tlli() == new_ms_tlli);
-	OSMO_ASSERT(ms->check_tlli(new_ms_tlli));
-	OSMO_ASSERT(ms->check_tlli(other_sgsn_tlli));
+	ms_set_tlli(ms, new_ms_tlli);
+	OSMO_ASSERT(ms_tlli(ms) == new_ms_tlli);
+	OSMO_ASSERT(ms_check_tlli(ms, new_ms_tlli));
+	OSMO_ASSERT(ms_check_tlli(ms, other_sgsn_tlli));
 
-	ms->confirm_tlli(new_ms_tlli);
-	OSMO_ASSERT(ms->tlli() == new_ms_tlli);
-	OSMO_ASSERT(ms->check_tlli(new_ms_tlli));
-	OSMO_ASSERT(!ms->check_tlli(start_tlli));
-	OSMO_ASSERT(!ms->check_tlli(other_sgsn_tlli));
+	ms_confirm_tlli(ms, new_ms_tlli);
+	OSMO_ASSERT(ms_tlli(ms) == new_ms_tlli);
+	OSMO_ASSERT(ms_check_tlli(ms, new_ms_tlli));
+	OSMO_ASSERT(!ms_check_tlli(ms, start_tlli));
+	OSMO_ASSERT(!ms_check_tlli(ms, other_sgsn_tlli));
 
 	/* SGSN uses the new TLLI before it is announced by the MS (shouldn't
 	 * happen in normal use) */
-	ms->set_tlli(start_tlli);
-	ms->confirm_tlli(start_tlli);
+	ms_set_tlli(ms, start_tlli);
+	ms_confirm_tlli(ms, start_tlli);
 
-	ms->confirm_tlli(new_ms_tlli);
-	OSMO_ASSERT(ms->tlli() == start_tlli);
-	OSMO_ASSERT(ms->check_tlli(new_ms_tlli));
-	OSMO_ASSERT(ms->check_tlli(start_tlli));
+	ms_confirm_tlli(ms, new_ms_tlli);
+	OSMO_ASSERT(ms_tlli(ms) == start_tlli);
+	OSMO_ASSERT(ms_check_tlli(ms, new_ms_tlli));
+	OSMO_ASSERT(ms_check_tlli(ms, start_tlli));
 
-	ms->set_tlli(new_ms_tlli);
-	OSMO_ASSERT(ms->tlli() == new_ms_tlli);
-	OSMO_ASSERT(ms->check_tlli(new_ms_tlli));
-	OSMO_ASSERT(!ms->check_tlli(start_tlli));
+	ms_set_tlli(ms, new_ms_tlli);
+	OSMO_ASSERT(ms_tlli(ms) == new_ms_tlli);
+	OSMO_ASSERT(ms_check_tlli(ms, new_ms_tlli));
+	OSMO_ASSERT(!ms_check_tlli(ms, start_tlli));
 
-	delete ms;
+	talloc_free(ms);
 
 	printf("=== end %s ===\n", __func__);
 }
@@ -353,9 +360,9 @@
 	ms = st->create_ms();
 
 	if (dir == GPRS_RLCMAC_UL_TBF)
-		ms->set_tlli(tlli);
+		ms_set_tlli(ms, tlli);
 	else
-		ms->confirm_tlli(tlli);
+		ms_confirm_tlli(ms, tlli);
 
 	return ms;
 }
@@ -378,44 +385,44 @@
 
 	ms = prepare_ms(&store, tlli + 0, GPRS_RLCMAC_UL_TBF);
 	OSMO_ASSERT(ms != NULL);
-	OSMO_ASSERT(ms->tlli() == tlli + 0);
-	ms->set_imsi(imsi1);
-	OSMO_ASSERT(strcmp(ms->imsi(), imsi1) == 0);
+	OSMO_ASSERT(ms_tlli(ms) == tlli + 0);
+	ms_set_imsi(ms, imsi1);
+	OSMO_ASSERT(strcmp(ms_imsi(ms), imsi1) == 0);
 
 	ms_tmp = store.get_ms(tlli + 0);
 	OSMO_ASSERT(ms == ms_tmp);
-	OSMO_ASSERT(ms->tlli() == tlli + 0);
+	OSMO_ASSERT(ms_tlli(ms) == tlli + 0);
 
 	ms_tmp = store.get_ms(0, 0, imsi1);
 	OSMO_ASSERT(ms == ms_tmp);
-	OSMO_ASSERT(strcmp(ms->imsi(), imsi1) == 0);
+	OSMO_ASSERT(strcmp(ms_imsi(ms), imsi1) == 0);
 	ms_tmp = store.get_ms(0, 0, imsi2);
 	OSMO_ASSERT(ms_tmp == NULL);
 
 	ms = prepare_ms(&store, tlli + 1, GPRS_RLCMAC_UL_TBF);
 	OSMO_ASSERT(ms != NULL);
-	OSMO_ASSERT(ms->tlli() == tlli + 1);
-	ms->set_imsi(imsi2);
-	OSMO_ASSERT(strcmp(ms->imsi(), imsi2) == 0);
+	OSMO_ASSERT(ms_tlli(ms) == tlli + 1);
+	ms_set_imsi(ms, imsi2);
+	OSMO_ASSERT(strcmp(ms_imsi(ms), imsi2) == 0);
 
 	ms_tmp = store.get_ms(tlli + 1);
 	OSMO_ASSERT(ms == ms_tmp);
-	OSMO_ASSERT(ms->tlli() == tlli + 1);
+	OSMO_ASSERT(ms_tlli(ms) == tlli + 1);
 
 	ms_tmp = store.get_ms(0, 0, imsi1);
 	OSMO_ASSERT(ms_tmp != NULL);
 	OSMO_ASSERT(ms_tmp != ms);
 	ms_tmp = store.get_ms(0, 0, imsi2);
 	OSMO_ASSERT(ms == ms_tmp);
-	OSMO_ASSERT(strcmp(ms->imsi(), imsi2) == 0);
+	OSMO_ASSERT(strcmp(ms_imsi(ms), imsi2) == 0);
 
 	/* delete ms */
 	ms = store.get_ms(tlli + 0);
 	OSMO_ASSERT(ms != NULL);
 	ul_tbf = talloc_zero(tall_pcu_ctx, struct gprs_rlcmac_ul_tbf);
 	new (ul_tbf) gprs_rlcmac_ul_tbf(&the_bts, ms);
-	ms->attach_tbf(ul_tbf);
-	ms->detach_tbf(ul_tbf);
+	ms_attach_tbf(ms, ul_tbf);
+	ms_detach_tbf(ms, ul_tbf);
 	ms = store.get_ms(tlli + 0);
 	OSMO_ASSERT(ms == NULL);
 	ms = store.get_ms(tlli + 1);
@@ -424,8 +431,8 @@
 	/* delete ms */
 	ms = store.get_ms(tlli + 1);
 	OSMO_ASSERT(ms != NULL);
-	ms->attach_tbf(ul_tbf);
-	ms->detach_tbf(ul_tbf);
+	ms_attach_tbf(ms, ul_tbf);
+	ms_detach_tbf(ms, ul_tbf);
 	ms = store.get_ms(tlli + 1);
 	OSMO_ASSERT(ms == NULL);
 
@@ -441,62 +448,49 @@
 	gprs_rlcmac_ul_tbf *ul_tbf;
 	BTS the_bts;
 	GprsMs *ms;
-	static enum {UNKNOWN, IS_IDLE, IS_ACTIVE} last_cb = UNKNOWN;
-
-	struct MyCallback: public GprsMs::Callback {
-		virtual void ms_idle(class GprsMs *ms) {
-			OSMO_ASSERT(ms->is_idle());
-			printf("  ms_idle() was called\n");
-			last_cb = IS_IDLE;
-		}
-		virtual void ms_active(class GprsMs *ms) {
-			OSMO_ASSERT(!ms->is_idle());
-			printf("  ms_active() was called\n");
-			last_cb = IS_ACTIVE;
-		}
-	} cb;
+	last_cb = CB_UNKNOWN;
 
 	printf("=== start %s ===\n", __func__);
 
-	ms = new GprsMs(&the_bts, tlli);
-	ms->set_callback(&cb);
-	ms->set_timeout(1);
+	ms = ms_alloc(&the_bts, tlli);
+	ms_set_callback(ms, &ms_cb);
+	ms_set_timeout(ms, 1);
 
-	OSMO_ASSERT(ms->is_idle());
+	OSMO_ASSERT(ms_is_idle(ms));
 
 	dl_tbf = talloc_zero(tall_pcu_ctx, struct gprs_rlcmac_dl_tbf);
 	new (dl_tbf) gprs_rlcmac_dl_tbf(&the_bts, ms);
 	ul_tbf = talloc_zero(tall_pcu_ctx, struct gprs_rlcmac_ul_tbf);
 	new (ul_tbf) gprs_rlcmac_ul_tbf(&the_bts, ms);
 
-	OSMO_ASSERT(last_cb == UNKNOWN);
+	OSMO_ASSERT(last_cb == CB_UNKNOWN);
 
-	ms->attach_tbf(ul_tbf);
-	OSMO_ASSERT(!ms->is_idle());
-	OSMO_ASSERT(last_cb == IS_ACTIVE);
+	ms_attach_tbf(ms, ul_tbf);
+	OSMO_ASSERT(!ms_is_idle(ms));
+	OSMO_ASSERT(last_cb == CB_IS_ACTIVE);
 
-	last_cb = UNKNOWN;
+	last_cb = CB_UNKNOWN;
 
-	ms->attach_tbf(dl_tbf);
-	OSMO_ASSERT(!ms->is_idle());
-	OSMO_ASSERT(last_cb == UNKNOWN);
+	ms_attach_tbf(ms, dl_tbf);
+	OSMO_ASSERT(!ms_is_idle(ms));
+	OSMO_ASSERT(last_cb == CB_UNKNOWN);
 
-	ms->detach_tbf(ul_tbf);
-	OSMO_ASSERT(!ms->is_idle());
-	OSMO_ASSERT(last_cb == UNKNOWN);
+	ms_detach_tbf(ms, ul_tbf);
+	OSMO_ASSERT(!ms_is_idle(ms));
+	OSMO_ASSERT(last_cb == CB_UNKNOWN);
 
-	ms->detach_tbf(dl_tbf);
-	OSMO_ASSERT(!ms->is_idle());
-	OSMO_ASSERT(last_cb == UNKNOWN);
+	ms_detach_tbf(ms, dl_tbf);
+	OSMO_ASSERT(!ms_is_idle(ms));
+	OSMO_ASSERT(last_cb == CB_UNKNOWN);
 
 	usleep(1100000);
 	osmo_timers_update();
 
-	OSMO_ASSERT(ms->is_idle());
-	OSMO_ASSERT(last_cb == IS_IDLE);
+	OSMO_ASSERT(ms_is_idle(ms));
+	OSMO_ASSERT(last_cb == CB_IS_IDLE);
 
-	last_cb = UNKNOWN;
-	delete ms;
+	last_cb = CB_UNKNOWN;
+	talloc_free(ms);
 	talloc_free(dl_tbf);
 	talloc_free(ul_tbf);
 
@@ -519,21 +513,21 @@
 	bts->cs_downgrade_threshold = 0;
 	bts->cs_adj_lower_limit = 0;
 
-	ms = new GprsMs(&the_bts, tlli);
+	ms = ms_alloc(&the_bts, tlli);
 
-	OSMO_ASSERT(ms->is_idle());
+	OSMO_ASSERT(ms_is_idle(ms));
 
 	dl_tbf = talloc_zero(tall_pcu_ctx, struct gprs_rlcmac_dl_tbf);
 	new (dl_tbf) gprs_rlcmac_dl_tbf(&the_bts, ms);
-	ms->attach_tbf(dl_tbf);
+	ms_attach_tbf(ms, dl_tbf);
 
-	OSMO_ASSERT(!ms->is_idle());
+	OSMO_ASSERT(!ms_is_idle(ms));
 
-	OSMO_ASSERT(mcs_chan_code(ms->current_cs_dl()) == 3);
+	OSMO_ASSERT(mcs_chan_code(ms_current_cs_dl(ms)) == 3);
 
 	bts->cs_downgrade_threshold = 200;
 
-	OSMO_ASSERT(mcs_chan_code(ms->current_cs_dl()) == 2);
+	OSMO_ASSERT(mcs_chan_code(ms_current_cs_dl(ms)) == 2);
 
 	talloc_free(dl_tbf);
 
@@ -543,10 +537,10 @@
 static void dump_ms(const GprsMs *ms, const char *pref)
 {
 	printf("%s MS DL %s/%s, UL %s/%s, mode %s, <%s>\n", pref,
-	       mcs_name(ms->current_cs_dl()), mcs_name(ms->max_cs_dl()),
-	       mcs_name(ms->current_cs_ul()), mcs_name(ms->max_cs_ul()),
-	       mode_name(ms->mode()),
-	       ms->is_idle() ? "IDLE" : "ACTIVE");
+	       mcs_name(ms_current_cs_dl(ms)), mcs_name(ms_max_cs_dl(ms)),
+	       mcs_name(ms_current_cs_ul(ms)), mcs_name(ms_max_cs_ul(ms)),
+	       mode_name(ms_mode(ms)),
+	       ms_is_idle(ms) ? "IDLE" : "ACTIVE");
 }
 
 static void test_ms_mcs_mode()
@@ -560,48 +554,48 @@
 
 	printf("=== start %s ===\n", __func__);
 
-	ms1 = new GprsMs(&the_bts, tlli);
+	ms1 = ms_alloc(&the_bts, tlli);
 	dump_ms(ms1, "1: no BTS defaults  ");
 
 	bts->initial_cs_dl = 4;
 	bts->initial_cs_ul = 1;
 	bts->cs_downgrade_threshold = 0;
 
-	ms2 = new GprsMs(&the_bts, tlli + 1);
+	ms2 = ms_alloc(&the_bts, tlli + 1);
 	dump_ms(ms2, "2: with BTS defaults");
 
 	dl_tbf = talloc_zero(tall_pcu_ctx, struct gprs_rlcmac_dl_tbf);
 	new (dl_tbf) gprs_rlcmac_dl_tbf(&the_bts, ms2);
-	ms2->attach_tbf(dl_tbf);
+	ms_attach_tbf(ms2, dl_tbf);
 
 	dump_ms(ms2, "2: after TBF attach ");
 
-	ms1->set_mode(EGPRS);
+	ms_set_mode(ms1, EGPRS);
 	dump_ms(ms1, "1: after mode set   ");
 
-	ms2->set_mode(EGPRS);
+	ms_set_mode(ms2, EGPRS);
 	dump_ms(ms2, "2: after mode set   ");
 
-	ms1->set_current_cs_dl(MCS7);
+	ms_set_current_cs_dl(ms1, MCS7);
 	dump_ms(ms1, "1: after MCS set    ");
 
-	ms2->set_current_cs_dl(MCS8);
+	ms_set_current_cs_dl(ms2, MCS8);
 	dump_ms(ms2, "2: after MCS set    ");
 
-	ms1->set_mode(EGPRS_GMSK);
+	ms_set_mode(ms1, EGPRS_GMSK);
 	dump_ms(ms1, "1: after mode set   ");
 
-	ms2->set_mode(EGPRS_GMSK);
+	ms_set_mode(ms2, EGPRS_GMSK);
 	dump_ms(ms2, "2: after mode set   ");
 
 	// FIXME: following code triggers ASAN failure:
 	// ms2->detach_tbf(dl_tbf);
 	// dump_ms(ms2, "2: after TBF detach ");
 
-	ms1->set_mode(GPRS);
+	ms_set_mode(ms1, GPRS);
 	dump_ms(ms1, "1: after mode set   ");
 
-	ms2->set_mode(GPRS);
+	ms_set_mode(ms2, GPRS);
 	dump_ms(ms2, "2: after mode set   ");
 
 	talloc_free(dl_tbf);
diff --git a/tests/tbf/TbfTest.cpp b/tests/tbf/TbfTest.cpp
index 1a1dc6f..e6041a3 100644
--- a/tests/tbf/TbfTest.cpp
+++ b/tests/tbf/TbfTest.cpp
@@ -109,14 +109,14 @@
 	OSMO_ASSERT(dl_tbf != NULL);
 	dl_tbf->update_ms(0x2342, GPRS_RLCMAC_DL_TBF);
 	dl_tbf->set_ta(4);
-	OSMO_ASSERT(ms->dl_tbf() == dl_tbf);
+	OSMO_ASSERT(ms_dl_tbf(ms) == dl_tbf);
 	OSMO_ASSERT(dl_tbf->ms() == ms);
 
 	gprs_rlcmac_tbf *ul_tbf = tbf_alloc_ul_tbf(the_bts.bts_data(),
 						   ms, 0, false);
 	OSMO_ASSERT(ul_tbf != NULL);
 	ul_tbf->update_ms(0x2342, GPRS_RLCMAC_UL_TBF);
-	OSMO_ASSERT(ms->ul_tbf() == ul_tbf);
+	OSMO_ASSERT(ms_ul_tbf(ms) == ul_tbf);
 	OSMO_ASSERT(ul_tbf->ms() == ms);
 
 	OSMO_ASSERT(the_bts.ms_by_tlli(0x2342) == ms);
@@ -133,8 +133,8 @@
 
 	ms_new = the_bts.ms_by_tlli(0x4232);
 	OSMO_ASSERT(ms == ms_new);
-	OSMO_ASSERT(ms->dl_tbf() == dl_tbf);
-	OSMO_ASSERT(ms->ul_tbf() == ul_tbf);
+	OSMO_ASSERT(ms_dl_tbf(ms) == dl_tbf);
+	OSMO_ASSERT(ms_ul_tbf(ms) == ul_tbf);
 
 	/* Now use the new TLLI for UL */
 	ul_tbf->update_ms(0x4232, GPRS_RLCMAC_UL_TBF);
@@ -143,7 +143,7 @@
 
 	ms_new = the_bts.ms_by_tlli(0x4232);
 	OSMO_ASSERT(ms_new != NULL);
-	OSMO_ASSERT(ms_new->ta() == 4);
+	OSMO_ASSERT(ms_ta(ms_new) == 4);
 
 	OSMO_ASSERT(ul_tbf->ta() == 4);
 	OSMO_ASSERT(dl_tbf->ta() == 4);
@@ -298,25 +298,27 @@
 
 	/* Clean up and ensure tbfs are in the correct state */
 	OSMO_ASSERT(dl_tbf->state_is(GPRS_RLCMAC_WAIT_RELEASE));
-	new_tbf = ms->dl_tbf();
+	new_tbf = ms_dl_tbf(ms);
 	check_tbf(new_tbf);
 	OSMO_ASSERT(new_tbf != dl_tbf);
 	OSMO_ASSERT(new_tbf->tfi() == 1);
 	check_tbf(dl_tbf);
 	TBF_SET_ASS_STATE_DL(dl_tbf, GPRS_RLCMAC_DL_ASS_NONE);
 	if (test_mode == TEST_MODE_REVERSE_FREE) {
-		GprsMs::Guard guard(ms);
+		ms_ref(ms);
 		tbf_free(new_tbf);
-		OSMO_ASSERT(ms->dl_tbf() == NULL);
+		OSMO_ASSERT(ms_dl_tbf(ms) == NULL);
 		check_tbf(dl_tbf);
 		tbf_free(dl_tbf);
+		ms_unref(ms);
 	} else {
-		GprsMs::Guard guard(ms);
+		ms_ref(ms);
 		tbf_free(dl_tbf);
-		OSMO_ASSERT(ms->dl_tbf() == new_tbf);
+		OSMO_ASSERT(ms_dl_tbf(ms) == new_tbf);
 		check_tbf(new_tbf);
 		tbf_free(new_tbf);
-		OSMO_ASSERT(ms->dl_tbf() == NULL);
+		OSMO_ASSERT(ms_dl_tbf(ms) == NULL);
+		ms_unref(ms);
 	}
 
 	fprintf(stderr, "=== end %s ===\n", __func__);
@@ -421,32 +423,33 @@
 	dl_tbf[0]->update_ms(0xf1000001, GPRS_RLCMAC_DL_TBF);
 	dl_tbf[1]->update_ms(0xf1000002, GPRS_RLCMAC_DL_TBF);
 
-	dl_tbf[0]->ms()->set_imsi("001001000000001");
+	ms_set_imsi(dl_tbf[0]->ms(), "001001000000001");
 	ms1 = the_bts.ms_store().get_ms(0, 0, "001001000000001");
 	OSMO_ASSERT(ms1 != NULL);
 	ms2 = the_bts.ms_store().get_ms(0xf1000001);
 	OSMO_ASSERT(ms2 != NULL);
-	OSMO_ASSERT(strcmp(ms2->imsi(), "001001000000001") == 0);
+	OSMO_ASSERT(strcmp(ms_imsi(ms2), "001001000000001") == 0);
 	OSMO_ASSERT(ms1 == ms2);
 
 	/* change the IMSI on TBF 0 */
-	dl_tbf[0]->ms()->set_imsi("001001000000002");
+	ms_set_imsi(dl_tbf[0]->ms(), "001001000000002");
 	ms1 = the_bts.ms_store().get_ms(0, 0, "001001000000001");
 	OSMO_ASSERT(ms1 == NULL);
 	ms1 = the_bts.ms_store().get_ms(0, 0, "001001000000002");
 	OSMO_ASSERT(ms1 != NULL);
-	OSMO_ASSERT(strcmp(ms2->imsi(), "001001000000002") == 0);
+	OSMO_ASSERT(strcmp(ms_imsi(ms2), "001001000000002") == 0);
 	OSMO_ASSERT(ms1 == ms2);
 
 	/* use the same IMSI on TBF 1 */
 	{
-		GprsMs::Guard guard(ms2);
-		dl_tbf[1]->ms()->set_imsi("001001000000002");
+		ms_ref(ms2);
+		ms_set_imsi(dl_tbf[1]->ms(), "001001000000002");
 		ms1 = the_bts.ms_store().get_ms(0, 0, "001001000000002");
 		OSMO_ASSERT(ms1 != NULL);
 		OSMO_ASSERT(ms1 != ms2);
-		OSMO_ASSERT(strcmp(ms1->imsi(), "001001000000002") == 0);
-		OSMO_ASSERT(strcmp(ms2->imsi(), "") == 0);
+		OSMO_ASSERT(strcmp(ms_imsi(ms1), "001001000000002") == 0);
+		OSMO_ASSERT(strcmp(ms_imsi(ms2), "") == 0);
+		ms_unref(ms2);
 	}
 
 	ms2 = the_bts.ms_store().get_ms(0xf1000001);
@@ -539,8 +542,8 @@
 
 	ms = the_bts.ms_store().get_ms(0, 0, imsi);
 	OSMO_ASSERT(ms != NULL);
-	OSMO_ASSERT(ms->dl_tbf() != NULL);
-	ms->dl_tbf()->set_ta(0);
+	OSMO_ASSERT(ms_dl_tbf(ms) != NULL);
+	ms_dl_tbf(ms)->set_ta(0);
 
 	/* Handle LLC frame 2 */
 	memset(buf, 2, sizeof(buf));
@@ -549,7 +552,7 @@
 	OSMO_ASSERT(rc >= 0);
 
 	/* TBF establishment fails (timeout) */
-	tbf_free(ms->dl_tbf());
+	tbf_free(ms_dl_tbf(ms));
 
 	/* Handle LLC frame 3 */
 	memset(buf, 3, sizeof(buf));
@@ -557,7 +560,7 @@
 		delay_csec, buf, sizeof(buf));
 	OSMO_ASSERT(rc >= 0);
 
-	OSMO_ASSERT(ms->dl_tbf() != NULL);
+	OSMO_ASSERT(ms_dl_tbf(ms) != NULL);
 
 	/* Get first BSN */
 	struct msgb *msg;
@@ -572,8 +575,8 @@
 		  0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03 },
 	};
 
-	while (ms->dl_tbf()->have_data()) {
-		msg = ms->dl_tbf()->create_dl_acked_block(fn += 4, 7);
+	while (ms_dl_tbf(ms)->have_data()) {
+		msg = ms_dl_tbf(ms)->create_dl_acked_block(fn += 4, 7);
 		fprintf(stderr, "MSG = %s\n", msgb_hexdump(msg));
 		if (!msgb_eq_data_print(msg, exp[expected_data - 1], GSM_MACBLOCK_LEN))
 			fprintf(stderr, "%s failed at %u\n", __func__, expected_data);
@@ -742,8 +745,8 @@
 
 	ms = the_bts->ms_by_tlli(tlli);
 	OSMO_ASSERT(ms != NULL);
-	OSMO_ASSERT(ms->ta() == qta/4);
-	OSMO_ASSERT(ms->ul_tbf() == ul_tbf);
+	OSMO_ASSERT(ms_ta(ms) == qta/4);
+	OSMO_ASSERT(ms_ul_tbf(ms) == ul_tbf);
 
 	/*
 	 * TS 44.060, B.8.1
@@ -890,8 +893,8 @@
 
 	ms = the_bts->ms_by_tlli(tlli);
 	OSMO_ASSERT(ms != NULL);
-	OSMO_ASSERT(ms->ta() == qta/4);
-	OSMO_ASSERT(ms->ul_tbf() == ul_tbf);
+	OSMO_ASSERT(ms_ta(ms) == qta/4);
+	OSMO_ASSERT(ms_ul_tbf(ms) == ul_tbf);
 
 	/*
 	 * TS 44.060, B.8.1
@@ -1386,8 +1389,8 @@
 
 	ms = the_bts->ms_by_tlli(tlli);
 	OSMO_ASSERT(ms != NULL);
-	OSMO_ASSERT(ms->ta() == qta/4);
-	OSMO_ASSERT(ms->ul_tbf() == ul_tbf);
+	OSMO_ASSERT(ms_ta(ms) == qta/4);
+	OSMO_ASSERT(ms_ul_tbf(ms) == ul_tbf);
 
 	return ul_tbf;
 }
@@ -1469,8 +1472,8 @@
 
 	ms = the_bts->ms_by_tlli(tlli);
 	OSMO_ASSERT(ms != NULL);
-	OSMO_ASSERT(ms->ta() == qta/4);
-	OSMO_ASSERT(ms->ul_tbf() == ul_tbf);
+	OSMO_ASSERT(ms_ta(ms) == qta/4);
+	OSMO_ASSERT(ms_ul_tbf(ms) == ul_tbf);
 
 	return ul_tbf;
 }
@@ -1554,8 +1557,8 @@
 
 	ms = the_bts->ms_by_tlli(tlli);
 	OSMO_ASSERT(ms != NULL);
-	OSMO_ASSERT(ms->ta() == qta/4);
-	OSMO_ASSERT(ms->ul_tbf() == ul_tbf);
+	OSMO_ASSERT(ms_ta(ms) == qta/4);
+	OSMO_ASSERT(ms_ul_tbf(ms) == ul_tbf);
 
 	return ul_tbf;
 }
@@ -1637,8 +1640,8 @@
 
 	ms = the_bts->ms_by_tlli(tlli);
 	OSMO_ASSERT(ms != NULL);
-	OSMO_ASSERT(ms->ta() == qta/4);
-	OSMO_ASSERT(ms->ul_tbf() == ul_tbf);
+	OSMO_ASSERT(ms_ta(ms) == qta/4);
+	OSMO_ASSERT(ms_ul_tbf(ms) == ul_tbf);
 
 	return ul_tbf;
 }
@@ -1655,7 +1658,7 @@
 
 	ms = the_bts->ms_by_imsi(imsi);
 	OSMO_ASSERT(ms != NULL);
-	OSMO_ASSERT(ms->dl_tbf() != NULL);
+	OSMO_ASSERT(ms_dl_tbf(ms) != NULL);
 
 	if (imsi[0] && strcmp(imsi, "000") != 0) {
 		ms2 = the_bts->ms_by_tlli(tlli);
@@ -1672,7 +1675,7 @@
 
 	ms = the_bts->ms_by_tlli(tlli);
 	OSMO_ASSERT(ms);
-	dl_tbf = ms->dl_tbf();
+	dl_tbf = ms_dl_tbf(ms);
 	OSMO_ASSERT(dl_tbf);
 
 	while (dl_tbf->have_data()) {
@@ -1692,7 +1695,7 @@
 {
 	fprintf(stderr, "Got '%s', TA=%d\n", ul_tbf->name(), ul_tbf->ta());
 	if (print_ms)
-		fprintf(stderr, "Got MS: TLLI = 0x%08x, TA = %d\n", ul_tbf->ms()->tlli(), ul_tbf->ms()->ta());
+		fprintf(stderr, "Got MS: TLLI = 0x%08x, TA = %d\n", ms_tlli(ul_tbf->ms()), ms_ta(ul_tbf->ms()));
 }
 
 static void test_tbf_single_phase()
@@ -1866,10 +1869,10 @@
 	fprintf(stderr, "=== end %s ===\n", __func__);
 }
 
-static inline void print_ms(const GprsMs *ms, bool old)
+static inline void print_ms(GprsMs *ms, bool old)
 {
 	fprintf(stderr, "%s MS: TLLI = 0x%08x, TA = %d, IMSI = %s, LLC = %zu\n",
-		old ? "Old" : "New", ms->tlli(), ms->ta(), ms->imsi(), ms->llc_queue()->size());
+		old ? "Old" : "New", ms_tlli(ms), ms_ta(ms), ms_imsi(ms), llc_queue_size(ms_llc_queue(ms)));
 }
 
 static void test_tbf_ra_update_rach()
@@ -1905,9 +1908,9 @@
 	send_control_ack(ul_tbf);
 
 	/* Make sure the RAU Accept gets sent to the MS */
-	OSMO_ASSERT(ms1->llc_queue()->size() == 1);
+	OSMO_ASSERT(llc_queue_size(ms_llc_queue(ms1)) == 1);
 	transmit_dl_data(&the_bts, tlli1, &fn);
-	OSMO_ASSERT(ms1->llc_queue()->size() == 0);
+	OSMO_ASSERT(llc_queue_size(ms_llc_queue(ms1)) == 0);
 
 	/* Now establish a new TBF for the RA UPDATE COMPLETE (new TLLI) */
 	ul_tbf = establish_ul_tbf_two_phase(&the_bts, ts_no, tlli2, &fn, qta,
@@ -1963,8 +1966,8 @@
 	send_dl_data(&the_bts, tlli1, imsi, (const uint8_t *)"DATA 2 *************", 20);
 	print_ms(ms1, true);
 
-	OSMO_ASSERT(ms1->llc_queue()->size() == 2);
-	dl_tbf = ms1->dl_tbf();
+	OSMO_ASSERT(llc_queue_size(ms_llc_queue(ms1)) == 2);
+	dl_tbf = ms_dl_tbf(ms1);
 	OSMO_ASSERT(dl_tbf != NULL);
 
 	/* Get rid of old UL TBF */
@@ -1986,10 +1989,10 @@
 	OSMO_ASSERT(ms2 == ms);
 
 	/* A DL TBF should still exist */
-	OSMO_ASSERT(ms->dl_tbf());
+	OSMO_ASSERT(ms_dl_tbf(ms));
 
 	/* No queued packets should be lost */
-	OSMO_ASSERT(ms->llc_queue()->size() == 2);
+	OSMO_ASSERT(llc_queue_size(ms_llc_queue(ms)) == 2);
 
 	fprintf(stderr, "=== end %s ===\n", __func__);
 }
@@ -2022,8 +2025,8 @@
 	send_dl_data(&the_bts, tlli1, imsi, (const uint8_t *)"DATA 2 *************", 20);
 	print_ms(ms1, true);
 
-	OSMO_ASSERT(ms1->llc_queue()->size() == 2);
-	dl_tbf = ms1->dl_tbf();
+	OSMO_ASSERT(llc_queue_size(ms_llc_queue(ms1)) == 2);
+	dl_tbf = ms_dl_tbf(ms1);
 	OSMO_ASSERT(dl_tbf != NULL);
 
 	/* Get rid of old UL TBF */
@@ -2045,10 +2048,10 @@
 	OSMO_ASSERT(ms1 != ms);
 
 	/* DL TBF should be removed */
-	OSMO_ASSERT(!ms->dl_tbf());
+	OSMO_ASSERT(!ms_dl_tbf(ms));
 
 	/* No queued packets should be lost */
-	OSMO_ASSERT(ms->llc_queue()->size() == 2);
+	OSMO_ASSERT(llc_queue_size(ms_llc_queue(ms)) == 2);
 
 	fprintf(stderr, "=== end %s ===\n", __func__);
 }
@@ -2099,11 +2102,11 @@
 
 	/* Transmit all data */
 	transmit_dl_data(&the_bts, tlli1, &fn);
-	OSMO_ASSERT(ms1->llc_queue()->size() == 0);
-	OSMO_ASSERT(ms1->dl_tbf());
-	OSMO_ASSERT(ms1->dl_tbf()->state_is(GPRS_RLCMAC_FINISHED));
+	OSMO_ASSERT(llc_queue_size(ms_llc_queue(ms1)) == 0);
+	OSMO_ASSERT(ms_dl_tbf(ms1));
+	OSMO_ASSERT(ms_dl_tbf(ms1)->state_is(GPRS_RLCMAC_FINISHED));
 
-	dl_tbf1 = ms1->dl_tbf();
+	dl_tbf1 = ms_dl_tbf(ms1);
 
 	/* Send some LLC frames */
 	for (i = 0; i < 10; i++) {
@@ -2132,10 +2135,10 @@
 
 	ms2 = the_bts.ms_by_tlli(tlli1);
 	OSMO_ASSERT(ms2 == ms1);
-	OSMO_ASSERT(ms2->dl_tbf());
-	OSMO_ASSERT(ms2->dl_tbf()->state_is(GPRS_RLCMAC_ASSIGN));
+	OSMO_ASSERT(ms_dl_tbf(ms2));
+	OSMO_ASSERT(ms_dl_tbf(ms2)->state_is(GPRS_RLCMAC_ASSIGN));
 
-	dl_tbf2 = ms2->dl_tbf();
+	dl_tbf2 = ms_dl_tbf(ms2);
 
 	OSMO_ASSERT(dl_tbf1 != dl_tbf2);
 
@@ -2144,9 +2147,9 @@
 
 	/* Transmit all data */
 	transmit_dl_data(&the_bts, tlli1, &fn);
-	OSMO_ASSERT(ms2->llc_queue()->size() == 0);
-	OSMO_ASSERT(ms2->dl_tbf());
-	OSMO_ASSERT(ms2->dl_tbf()->state_is(GPRS_RLCMAC_FINISHED));
+	OSMO_ASSERT(llc_queue_size(ms_llc_queue(ms2)) == 0);
+	OSMO_ASSERT(ms_dl_tbf(ms2));
+	OSMO_ASSERT(ms_dl_tbf(ms2)->state_is(GPRS_RLCMAC_FINISHED));
 
 	fprintf(stderr, "=== end %s ===\n", __func__);
 }
@@ -2411,8 +2414,8 @@
 
 	ms = the_bts->ms_by_tlli(tlli);
 	OSMO_ASSERT(ms != NULL);
-	OSMO_ASSERT(ms->ta() == qta/4);
-	OSMO_ASSERT(ms->ul_tbf() == ul_tbf);
+	OSMO_ASSERT(ms_ta(ms) == qta/4);
+	OSMO_ASSERT(ms_ul_tbf(ms) == ul_tbf);
 
 	egprs3 = (struct gprs_rlc_ul_header_egprs_3 *) data_msg;
 	egprs3->si = 0;
@@ -2799,9 +2802,7 @@
 
 	OSMO_ASSERT(bsn1 == 0);
 
-	dl_tbf->ms()->set_current_cs_dl
-		(static_cast < enum CodingScheme >
-			(CS4 + demanded_mcs));
+	ms_set_current_cs_dl(dl_tbf->ms(), static_cast < enum CodingScheme > (CS4 + demanded_mcs));
 
 	fn = fn_add_blocks(fn, 1);
 
@@ -2831,9 +2832,7 @@
 	OSMO_ASSERT(egprs3->cps == 3);
 
 	/* Handle (MCS3, MCS3) -> MCS6 case */
-	dl_tbf->ms()->set_current_cs_dl
-		(static_cast < enum CodingScheme >
-			(CS4 + mcs));
+	ms_set_current_cs_dl(dl_tbf->ms(), static_cast < enum CodingScheme > (CS4 + mcs));
 
 	NACK(dl_tbf, 0);
 
@@ -2880,9 +2879,7 @@
 
 	NACK(dl_tbf, 0);
 
-	dl_tbf->ms()->set_current_cs_dl
-		(static_cast < enum CodingScheme >
-			(CS4 + demanded_mcs));
+	ms_set_current_cs_dl(dl_tbf->ms(), static_cast < enum CodingScheme > (CS4 + demanded_mcs));
 
 	fn = fn_add_blocks(fn, 1);
 
@@ -2967,9 +2964,7 @@
 		NACK(dl_tbf, 1);
 
 		/* Set the demanded MCS to demanded_mcs */
-		dl_tbf->ms()->set_current_cs_dl
-			(static_cast < enum CodingScheme >
-				(CS4 + demanded_mcs));
+		ms_set_current_cs_dl(dl_tbf->ms(), static_cast < enum CodingScheme > (CS4 + demanded_mcs));
 
 		fn = fn_add_blocks(fn, 1);
 		/* Retransmit the first RLC data block with demanded_mcs */
@@ -2994,9 +2989,7 @@
 		NACK(dl_tbf, 0);
 		NACK(dl_tbf, 1);
 
-		dl_tbf->ms()->set_current_cs_dl
-			(static_cast < enum CodingScheme >
-				(CS4 + demanded_mcs));
+		ms_set_current_cs_dl(dl_tbf->ms(), static_cast < enum CodingScheme > (CS4 + demanded_mcs));
 
 		fn = fn_add_blocks(fn, 1);
 		/* Send first, second RLC data blocks with demanded_mcs */
@@ -3264,8 +3257,8 @@
 	pcu_vty_init();
 
 	/* Initialize shared UL measurements */
-	meas.set_link_qual(12);
-	meas.set_rssi(31);
+	pcu_l1_meas_set_link_qual(&meas, 12);
+	pcu_l1_meas_set_rssi(&meas, 31);
 
 	test_tbf_base();
 	test_tbf_tlli_update();
diff --git a/tests/types/TypesTest.cpp b/tests/types/TypesTest.cpp
index f224146..bc24b30 100644
--- a/tests/types/TypesTest.cpp
+++ b/tests/types/TypesTest.cpp
@@ -58,27 +58,27 @@
 		gprs_llc llc;
 		llc.init();
 
-		OSMO_ASSERT(llc.chunk_size() == 0);
-		OSMO_ASSERT(llc.remaining_space() == LLC_MAX_LEN);
-		OSMO_ASSERT(llc.frame_length() == 0);
+		OSMO_ASSERT(llc_chunk_size(&llc) == 0);
+		OSMO_ASSERT(llc_remaining_space(&llc) == LLC_MAX_LEN);
+		OSMO_ASSERT(llc_frame_length(&llc) == 0);
 
 		llc.put_frame(data, 2);
-		OSMO_ASSERT(llc.remaining_space() == LLC_MAX_LEN - 2);
-		OSMO_ASSERT(llc.frame_length() == 2);
-		OSMO_ASSERT(llc.chunk_size() == 2);
+		OSMO_ASSERT(llc_remaining_space(&llc) == LLC_MAX_LEN - 2);
+		OSMO_ASSERT(llc_frame_length(&llc) == 2);
+		OSMO_ASSERT(llc_chunk_size(&llc) == 2);
 		OSMO_ASSERT(llc.frame[0] == 1);
 		OSMO_ASSERT(llc.frame[1] == 2);
 
 		llc.append_frame(&data[3], 1);
-		OSMO_ASSERT(llc.remaining_space() == LLC_MAX_LEN - 3);
-		OSMO_ASSERT(llc.frame_length() == 3);
-		OSMO_ASSERT(llc.chunk_size() == 3);
+		OSMO_ASSERT(llc_remaining_space(&llc) == LLC_MAX_LEN - 3);
+		OSMO_ASSERT(llc_frame_length(&llc) == 3);
+		OSMO_ASSERT(llc_chunk_size(&llc) == 3);
 
 		/* consume two bytes */
-		llc.consume(&out, 1);
-		OSMO_ASSERT(llc.remaining_space() == LLC_MAX_LEN - 3);
-		OSMO_ASSERT(llc.frame_length() == 3);
-		OSMO_ASSERT(llc.chunk_size() == 2);
+		llc_consume_data(&llc, &out, 1);
+		OSMO_ASSERT(llc_remaining_space(&llc) == LLC_MAX_LEN - 3);
+		OSMO_ASSERT(llc_frame_length(&llc) == 3);
+		OSMO_ASSERT(llc_chunk_size(&llc) == 2);
 
 		/* check that the bytes are as we expected */
 		OSMO_ASSERT(llc.frame[0] == 1);
@@ -86,9 +86,9 @@
 		OSMO_ASSERT(llc.frame[2] == 4);
 
 		/* now fill the frame */
-		llc.append_frame(data, llc.remaining_space() - 1);
-		OSMO_ASSERT(llc.fits_in_current_frame(1));
-		OSMO_ASSERT(!llc.fits_in_current_frame(2));
+		llc.append_frame(data, llc_remaining_space(&llc) - 1);
+		OSMO_ASSERT(llc_fits_in_current_frame(&llc, 1));
+		OSMO_ASSERT(!llc_fits_in_current_frame(&llc, 2));
 	}
 }
 

-- 
To view, visit https://gerrit.osmocom.org/c/osmo-pcu/+/21748
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings

Gerrit-Project: osmo-pcu
Gerrit-Branch: master
Gerrit-Change-Id: I0b50e3367aaad9dcada76da97b438e452c8b230c
Gerrit-Change-Number: 21748
Gerrit-PatchSet: 7
Gerrit-Owner: pespin <pespin at sysmocom.de>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: laforge <laforge at osmocom.org>
Gerrit-Reviewer: pespin <pespin at sysmocom.de>
Gerrit-MessageType: merged
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.osmocom.org/pipermail/gerrit-log/attachments/20210105/74c06ddf/attachment.htm>


More information about the gerrit-log mailing list