aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--src/Makefile.am2
-rw-r--r--src/gprs_ms.cpp182
-rw-r--r--src/gprs_ms.h79
-rw-r--r--tests/Makefile.am15
-rw-r--r--tests/ms/MsTest.cpp283
-rw-r--r--tests/ms/MsTest.err20
-rw-r--r--tests/ms/MsTest.ok10
-rw-r--r--tests/testsuite.at7
9 files changed, 597 insertions, 2 deletions
diff --git a/.gitignore b/.gitignore
index 554879a2..4ae71dae 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,6 +36,7 @@ tests/alloc/AllocTest
tests/rlcmac/RLCMACTest
tests/tbf/TbfTest
tests/types/TypesTest
+tests/ms/MsTest
tests/emu/pcu_emu
tests/testsuite
tests/testsuite.log
diff --git a/src/Makefile.am b/src/Makefile.am
index 210774c1..8a422f64 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -37,6 +37,7 @@ libgprs_la_SOURCES = \
gprs_rlcmac_sched.cpp \
gprs_rlcmac_meas.cpp \
gprs_rlcmac_ts_alloc.cpp \
+ gprs_ms.cpp \
gsm_timer.cpp \
bitvector.cpp \
pcu_l1_if.cpp \
@@ -77,6 +78,7 @@ noinst_HEADERS = \
gsm_rlcmac.h \
gprs_bssgp_pcu.h \
gprs_rlcmac.h \
+ gprs_ms.h \
pcuif_proto.h \
pcu_l1_if.h \
gsm_timer.h \
diff --git a/src/gprs_ms.cpp b/src/gprs_ms.cpp
new file mode 100644
index 00000000..db29d206
--- /dev/null
+++ b/src/gprs_ms.cpp
@@ -0,0 +1,182 @@
+/* gprs_ms.cpp
+ *
+ * Copyright (C) 2015 by Sysmocom s.f.m.c. GmbH
+ * Author: Jacob Erlbeck <jerlbeck@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 "tbf.h"
+#include "gprs_debug.h"
+
+extern "C" {
+ #include <osmocom/core/talloc.h>
+ #include <osmocom/core/utils.h>
+}
+
+extern void *tall_pcu_ctx;
+
+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)
+{
+ if (m_ms)
+ m_ms->ref();
+}
+
+GprsMs::Guard::~Guard()
+{
+ if (m_ms)
+ m_ms->unref();
+}
+
+GprsMs::GprsMs(uint32_t tlli) :
+ m_cb(&gprs_default_cb),
+ m_ul_tbf(NULL),
+ m_dl_tbf(NULL),
+ m_tlli(tlli),
+ m_is_idle(true),
+ m_ref(0)
+{
+ LOGP(DRLCMAC, LOGL_INFO, "Creating MS object, TLLI = 0x%08x\n", tlli);
+}
+
+GprsMs::~GprsMs()
+{
+ LOGP(DRLCMAC, LOGL_INFO, "Destroying MS object, TLLI = 0x%08x\n", tlli());
+}
+
+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);
+}
+
+void GprsMs::ref()
+{
+ m_ref += 1;
+}
+
+void GprsMs::unref()
+{
+ OSMO_ASSERT(m_ref >= 0);
+ m_ref -= 1;
+ if (m_ref == 0)
+ update_status();
+}
+
+void GprsMs::attach_tbf(struct gprs_rlcmac_tbf *tbf)
+{
+ if (tbf->direction == GPRS_RLCMAC_DL_TBF)
+ attach_dl_tbf(static_cast<gprs_rlcmac_dl_tbf *>(tbf));
+ else
+ attach_ul_tbf(static_cast<gprs_rlcmac_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)
+ detach_tbf(m_ul_tbf);
+
+ m_ul_tbf = tbf;
+}
+
+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)
+ detach_tbf(m_dl_tbf);
+
+ m_dl_tbf = tbf;
+}
+
+void GprsMs::detach_tbf(gprs_rlcmac_tbf *tbf)
+{
+ if (m_ul_tbf && tbf == static_cast<gprs_rlcmac_tbf *>(m_ul_tbf))
+ m_ul_tbf = NULL;
+ else if (m_dl_tbf && tbf == static_cast<gprs_rlcmac_tbf *>(m_dl_tbf))
+ m_dl_tbf = NULL;
+ else
+ return;
+
+ LOGP(DRLCMAC, LOGL_INFO, "Detaching TBF from MS object, TLLI = 0x%08x, TBF = %s\n",
+ tlli(), tbf->name());
+
+ 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::set_tlli(uint32_t tlli)
+{
+ if (tlli == m_tlli)
+ return;
+
+ LOGP(DRLCMAC, LOGL_INFO,
+ "Modifying MS object, TLLI: 0x%08x -> 0x%08x\n",
+ m_tlli, tlli);
+
+ m_tlli = tlli;
+}
diff --git a/src/gprs_ms.h b/src/gprs_ms.h
new file mode 100644
index 00000000..a59fc5be
--- /dev/null
+++ b/src/gprs_ms.h
@@ -0,0 +1,79 @@
+/* gprs_ms.h
+ *
+ * Copyright (C) 2015 by Sysmocom s.f.m.c. GmbH
+ * Author: Jacob Erlbeck <jerlbeck@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.
+ */
+
+#pragma once
+
+struct gprs_rlcmac_tbf;
+struct gprs_rlcmac_dl_tbf;
+struct gprs_rlcmac_ul_tbf;
+
+#include <stdint.h>
+#include <stddef.h>
+
+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();
+
+ private:
+ GprsMs * const m_ms;
+ };
+
+ GprsMs(uint32_t tlli);
+ ~GprsMs();
+
+ void set_callback(Callback *cb) {m_cb = cb;}
+
+ gprs_rlcmac_ul_tbf *ul_tbf() const {return m_ul_tbf;}
+ gprs_rlcmac_dl_tbf *dl_tbf() const {return m_dl_tbf;}
+ uint32_t tlli() const {return m_tlli;}
+ void set_tlli(uint32_t tlli);
+
+ 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);
+
+ bool is_idle() const {return !m_ul_tbf && !m_dl_tbf && !m_ref;}
+
+ void* operator new(size_t num);
+ void operator delete(void* p);
+
+protected:
+ void update_status();
+ void ref();
+ void unref();
+
+private:
+ Callback * m_cb;
+ gprs_rlcmac_ul_tbf *m_ul_tbf;
+ gprs_rlcmac_dl_tbf *m_dl_tbf;
+ uint32_t m_tlli;
+ bool m_is_idle;
+ int m_ref;
+};
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 683a64c6..7b2d32d4 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1,6 +1,6 @@
AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES) $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGB_CFLAGS) $(LIBOSMOGSM_CFLAGS) -I$(top_srcdir)/src/
-check_PROGRAMS = rlcmac/RLCMACTest alloc/AllocTest tbf/TbfTest types/TypesTest
+check_PROGRAMS = rlcmac/RLCMACTest alloc/AllocTest tbf/TbfTest types/TypesTest ms/MsTest
noinst_PROGRAMS = emu/pcu_emu
rlcmac_RLCMACTest_SOURCES = rlcmac/RLCMACTest.cpp
@@ -43,6 +43,16 @@ types_TypesTest_LDADD = \
$(LIBOSMOCORE_LIBS) \
$(COMMON_LA)
+ms_MsTest_SOURCES = ms/MsTest.cpp
+ms_MsTest_LDADD = \
+ $(top_builddir)/src/libgprs.la \
+ $(LIBOSMOGB_LIBS) \
+ $(LIBOSMOGSM_LIBS) \
+ $(LIBOSMOCORE_LIBS) \
+ $(COMMON_LA)
+
+ms_MsTest_LDFLAGS = \
+ -Wl,-u,bssgp_prim_cb
# The `:;' works around a Bash 3.2 bug when the output is not writeable.
$(srcdir)/package.m4: $(top_srcdir)/configure.ac
@@ -67,7 +77,8 @@ EXTRA_DIST = \
rlcmac/RLCMACTest.ok rlcmac/RLCMACTest.err \
alloc/AllocTest.ok alloc/AllocTest.err \
tbf/TbfTest.ok tbf/TbfTest.err \
- types/TypesTest.ok types/TypesTest.err
+ types/TypesTest.ok types/TypesTest.err \
+ ms/MsTest.ok ms/MsTest.err
DISTCLEANFILES = atconfig
diff --git a/tests/ms/MsTest.cpp b/tests/ms/MsTest.cpp
new file mode 100644
index 00000000..59c92b38
--- /dev/null
+++ b/tests/ms/MsTest.cpp
@@ -0,0 +1,283 @@
+/*
+ * MsTest.cpp
+ *
+ * Copyright (C) 2015 by Sysmocom s.f.m.c. GmbH
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "tbf.h"
+#include "gprs_debug.h"
+#include "gprs_ms.h"
+
+extern "C" {
+#include "pcu_vty.h"
+
+#include <osmocom/core/application.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/vty/vty.h>
+}
+
+#include <errno.h>
+
+void *tall_pcu_ctx;
+int16_t spoof_mnc = 0, spoof_mcc = 0;
+
+static void test_ms_state()
+{
+ uint32_t tlli = 0xffeeddbb;
+ gprs_rlcmac_dl_tbf *dl_tbf;
+ gprs_rlcmac_ul_tbf *ul_tbf;
+ GprsMs *ms;
+
+ printf("=== start %s ===\n", __func__);
+
+ ms = new GprsMs(tlli);
+ OSMO_ASSERT(ms->is_idle());
+
+ dl_tbf = talloc_zero(tall_pcu_ctx, struct gprs_rlcmac_dl_tbf);
+ dl_tbf->direction = GPRS_RLCMAC_DL_TBF;
+ ul_tbf = talloc_zero(tall_pcu_ctx, struct gprs_rlcmac_ul_tbf);
+ ul_tbf->direction = GPRS_RLCMAC_UL_TBF;
+
+ 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(dl_tbf);
+ OSMO_ASSERT(!ms->is_idle());
+ OSMO_ASSERT(ms->ul_tbf() == ul_tbf);
+ OSMO_ASSERT(ms->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(dl_tbf);
+ /* The ms object is freed now */
+ ms = NULL;
+
+ talloc_free(dl_tbf);
+ talloc_free(ul_tbf);
+
+ printf("=== end %s ===\n", __func__);
+}
+
+static void test_ms_callback()
+{
+ uint32_t tlli = 0xffeeddbb;
+ gprs_rlcmac_dl_tbf *dl_tbf;
+ gprs_rlcmac_ul_tbf *ul_tbf;
+ 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;
+
+ printf("=== start %s ===\n", __func__);
+
+ ms = new GprsMs(tlli);
+ ms->set_callback(&cb);
+
+ OSMO_ASSERT(ms->is_idle());
+
+ dl_tbf = talloc_zero(tall_pcu_ctx, struct gprs_rlcmac_dl_tbf);
+ dl_tbf->direction = GPRS_RLCMAC_DL_TBF;
+ ul_tbf = talloc_zero(tall_pcu_ctx, struct gprs_rlcmac_ul_tbf);
+ ul_tbf->direction = GPRS_RLCMAC_UL_TBF;
+
+ OSMO_ASSERT(last_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);
+
+ last_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->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(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);
+
+ last_cb = UNKNOWN;
+ delete ms;
+
+ talloc_free(dl_tbf);
+ talloc_free(ul_tbf);
+
+ printf("=== end %s ===\n", __func__);
+}
+
+static void test_ms_replace_tbf()
+{
+ uint32_t tlli = 0xffeeddbb;
+ gprs_rlcmac_dl_tbf *dl_tbf[2];
+ gprs_rlcmac_ul_tbf *ul_tbf;
+ 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(tlli);
+ ms->set_callback(&cb);
+
+ OSMO_ASSERT(ms->is_idle());
+ was_idle = false;
+
+ dl_tbf[0] = talloc_zero(tall_pcu_ctx, struct gprs_rlcmac_dl_tbf);
+ dl_tbf[0]->direction = GPRS_RLCMAC_DL_TBF;
+ dl_tbf[1] = talloc_zero(tall_pcu_ctx, struct gprs_rlcmac_dl_tbf);
+ dl_tbf[1]->direction = GPRS_RLCMAC_DL_TBF;
+ ul_tbf = talloc_zero(tall_pcu_ctx, struct gprs_rlcmac_ul_tbf);
+ ul_tbf->direction = GPRS_RLCMAC_UL_TBF;
+
+ 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(!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(!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(!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(!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(!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(was_idle);
+
+ delete ms;
+
+ talloc_free(dl_tbf[0]);
+ talloc_free(dl_tbf[1]);
+ talloc_free(ul_tbf);
+
+ printf("=== end %s ===\n", __func__);
+}
+
+
+static const struct log_info_cat default_categories[] = {
+ {"DPCU", "", "GPRS Packet Control Unit (PCU)", LOGL_INFO, 1},
+};
+
+static int filter_fn(const struct log_context *ctx,
+ struct log_target *tar)
+{
+ return 1;
+}
+
+const struct log_info debug_log_info = {
+ filter_fn,
+ (struct log_info_cat*)default_categories,
+ ARRAY_SIZE(default_categories),
+};
+
+int main(int argc, char **argv)
+{
+ struct vty_app_info pcu_vty_info = {0};
+
+ tall_pcu_ctx = talloc_named_const(NULL, 1, "MsTest context");
+ if (!tall_pcu_ctx)
+ abort();
+
+ msgb_set_talloc_ctx(tall_pcu_ctx);
+ osmo_init_logging(&debug_log_info);
+ log_set_use_color(osmo_stderr_target, 0);
+ log_set_print_filename(osmo_stderr_target, 0);
+ log_set_log_level(osmo_stderr_target, LOGL_INFO);
+
+ vty_init(&pcu_vty_info);
+ pcu_vty_init(&debug_log_info);
+
+ test_ms_state();
+ test_ms_callback();
+ test_ms_replace_tbf();
+
+ if (getenv("TALLOC_REPORT_FULL"))
+ talloc_report_full(tall_pcu_ctx, stderr);
+
+ return EXIT_SUCCESS;
+}
+
+extern "C" {
+void l1if_pdch_req() { abort(); }
+void l1if_connect_pdch() { abort(); }
+void l1if_close_pdch() { abort(); }
+void l1if_open_pdch() { abort(); }
+}
diff --git a/tests/ms/MsTest.err b/tests/ms/MsTest.err
new file mode 100644
index 00000000..d2e20c43
--- /dev/null
+++ b/tests/ms/MsTest.err
@@ -0,0 +1,20 @@
+Creating MS object, TLLI = 0xffeeddbb
+Attaching TBF to MS object, TLLI = 0xffeeddbb, TBF = TBF(TFI=0 TLLI=0x00000000 DIR=UL STATE=NULL)
+Attaching TBF to MS object, TLLI = 0xffeeddbb, TBF = TBF(TFI=0 TLLI=0x00000000 DIR=DL STATE=NULL)
+Detaching TBF from MS object, TLLI = 0xffeeddbb, TBF = TBF(TFI=0 TLLI=0x00000000 DIR=UL STATE=NULL)
+Detaching TBF from MS object, TLLI = 0xffeeddbb, TBF = TBF(TFI=0 TLLI=0x00000000 DIR=DL STATE=NULL)
+Destroying MS object, TLLI = 0xffeeddbb
+Creating MS object, TLLI = 0xffeeddbb
+Attaching TBF to MS object, TLLI = 0xffeeddbb, TBF = TBF(TFI=0 TLLI=0x00000000 DIR=UL STATE=NULL)
+Attaching TBF to MS object, TLLI = 0xffeeddbb, TBF = TBF(TFI=0 TLLI=0x00000000 DIR=DL STATE=NULL)
+Detaching TBF from MS object, TLLI = 0xffeeddbb, TBF = TBF(TFI=0 TLLI=0x00000000 DIR=UL STATE=NULL)
+Detaching TBF from MS object, TLLI = 0xffeeddbb, TBF = TBF(TFI=0 TLLI=0x00000000 DIR=DL STATE=NULL)
+Destroying MS object, TLLI = 0xffeeddbb
+Creating MS object, TLLI = 0xffeeddbb
+Attaching TBF to MS object, TLLI = 0xffeeddbb, TBF = TBF(TFI=0 TLLI=0x00000000 DIR=DL STATE=NULL)
+Attaching TBF to MS object, TLLI = 0xffeeddbb, TBF = TBF(TFI=0 TLLI=0x00000000 DIR=DL STATE=NULL)
+Detaching TBF from MS object, TLLI = 0xffeeddbb, TBF = TBF(TFI=0 TLLI=0x00000000 DIR=DL STATE=NULL)
+Attaching TBF to MS object, TLLI = 0xffeeddbb, TBF = TBF(TFI=0 TLLI=0x00000000 DIR=UL STATE=NULL)
+Detaching TBF from MS object, TLLI = 0xffeeddbb, TBF = TBF(TFI=0 TLLI=0x00000000 DIR=UL STATE=NULL)
+Detaching TBF from MS object, TLLI = 0xffeeddbb, TBF = TBF(TFI=0 TLLI=0x00000000 DIR=DL STATE=NULL)
+Destroying MS object, TLLI = 0xffeeddbb
diff --git a/tests/ms/MsTest.ok b/tests/ms/MsTest.ok
new file mode 100644
index 00000000..a0d4e9e6
--- /dev/null
+++ b/tests/ms/MsTest.ok
@@ -0,0 +1,10 @@
+=== start test_ms_state ===
+=== end test_ms_state ===
+=== start test_ms_callback ===
+ ms_active() was called
+ ms_idle() was called
+=== end test_ms_callback ===
+=== start test_ms_replace_tbf ===
+ ms_active() was called
+ ms_idle() was called
+=== end test_ms_replace_tbf ===
diff --git a/tests/testsuite.at b/tests/testsuite.at
index 2a21a936..f1f40320 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -29,3 +29,10 @@ cat $abs_srcdir/types/TypesTest.ok > expout
cat $abs_srcdir/types/TypesTest.err > experr
AT_CHECK([$OSMO_QEMU $abs_top_builddir/tests/types/TypesTest], [0], [expout], [experr])
AT_CLEANUP
+
+AT_SETUP([ms])
+AT_KEYWORDS([ms])
+cat $abs_srcdir/ms/MsTest.ok > expout
+cat $abs_srcdir/ms/MsTest.err > experr
+AT_CHECK([$OSMO_QEMU $abs_top_builddir/tests/ms/MsTest], [0], [expout], [experr])
+AT_CLEANUP