aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorOliver Smith <osmith@sysmocom.de>2019-09-05 17:13:33 +0200
committerlaforge <laforge@gnumonks.org>2019-09-14 15:28:43 +0000
commitcfb6321b88391ff58e5915c67ef0398024572566 (patch)
tree2d8be0aaccca7d9afbaf84b7d4ede5f6db579f9c
parent5360ef5447e192f20c86a2f92af031c45c724e18 (diff)
Forward ETWS Primary Notification to MS
Receive an Application Information Request from the BTS via PCU interface. Construct a Packet Application Information message from it (3GPP TS 44.060 11.2.47) and send it to all MS with active TBF. The TTCN-3 test infrastructure to test this feature is not quite ready yet, so I've added C unit tests instead. Related: OS#4048 Change-Id: Ie35959f833f46bde5f2126314b6f96763f863b36
-rw-r--r--include/osmocom/pcu/pcuif_proto.h9
-rw-r--r--src/Makefile.am3
-rw-r--r--src/bts.cpp6
-rw-r--r--src/bts.h5
-rw-r--r--src/gprs_ms.cpp1
-rw-r--r--src/gprs_ms.h2
-rw-r--r--src/gprs_rlcmac.cpp21
-rw-r--r--src/gprs_rlcmac.h3
-rw-r--r--src/gprs_rlcmac_sched.cpp89
-rw-r--r--src/pcu_l1_if.cpp39
-rw-r--r--tests/Makefile.am13
-rw-r--r--tests/app_info/AppInfoTest.cpp191
-rw-r--r--tests/app_info/AppInfoTest.err50
-rw-r--r--tests/app_info/AppInfoTest.ok0
-rw-r--r--tests/testsuite.at7
15 files changed, 408 insertions, 31 deletions
diff --git a/include/osmocom/pcu/pcuif_proto.h b/include/osmocom/pcu/pcuif_proto.h
index 144fba68..fd989a5e 100644
--- a/include/osmocom/pcu/pcuif_proto.h
+++ b/include/osmocom/pcu/pcuif_proto.h
@@ -13,6 +13,7 @@
#define PCU_IF_MSG_DATA_CNF 0x01 /* confirm (e.g. transmission on PCH) */
#define PCU_IF_MSG_DATA_IND 0x02 /* receive data from given channel */
#define PCU_IF_MSG_SUSP_REQ 0x03 /* BTS forwards GPRS SUSP REQ to PCU */
+#define PCU_IF_MSG_APP_INFO_REQ 0x04 /* BTS asks PCU to transmit APP INFO via PACCH */
#define PCU_IF_MSG_RTS_REQ 0x10 /* ready to send request */
#define PCU_IF_MSG_DATA_CNF_DT 0x11 /* confirm (with direct tlli) */
#define PCU_IF_MSG_RACH_IND 0x22 /* receive RACH */
@@ -172,6 +173,13 @@ struct gsm_pcu_if_pag_req {
uint8_t identity_lv[9];
} __attribute__ ((packed));
+/* BTS tells PCU to [once] send given application data via PACCH to all UE with active TBF */
+struct gsm_pcu_if_app_info_req {
+ uint8_t application_type; /* 4bit field, see TS 44.060 11.2.47 */
+ uint8_t len; /* length of data */
+ uint8_t data[162]; /* random size choice; ETWS needs 56 bytes */
+} __attribute__ ((packed));
+
/* BTS tells PCU about a GPRS SUSPENSION REQUEST received on DCCH */
struct gsm_pcu_if_susp_req {
uint32_t tlli;
@@ -198,6 +206,7 @@ struct gsm_pcu_if {
struct gsm_pcu_if_act_req act_req;
struct gsm_pcu_if_time_ind time_ind;
struct gsm_pcu_if_pag_req pag_req;
+ struct gsm_pcu_if_app_info_req app_info_req;
} u;
} __attribute__ ((packed));
diff --git a/src/Makefile.am b/src/Makefile.am
index 233e24de..7148267b 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -69,7 +69,8 @@ libgprs_la_SOURCES = \
gprs_codel.c \
coding_scheme.c \
gprs_coding_scheme.cpp \
- egprs_rlc_compression.cpp
+ egprs_rlc_compression.cpp \
+ gprs_rlcmac_sched.cpp
bin_PROGRAMS = \
osmo-pcu
diff --git a/src/bts.cpp b/src/bts.cpp
index 8b32e2ed..1b2ab2fb 100644
--- a/src/bts.cpp
+++ b/src/bts.cpp
@@ -231,6 +231,7 @@ BTS::BTS()
{
memset(&m_bts, 0, sizeof(m_bts));
m_bts.bts = this;
+ m_bts.app_info = NULL;
m_bts.dl_tbf_preemptive_retransmission = true;
m_bts.T_defs_bts = T_defs_bts;
m_bts.T_defs_pcu = T_defs_pcu;
@@ -279,6 +280,11 @@ void BTS::cleanup()
osmo_stat_item_group_free(m_statg);
m_statg = NULL;
}
+
+ if (m_bts.app_info) {
+ msgb_free(m_bts.app_info);
+ m_bts.app_info = NULL;
+ }
}
BTS::~BTS()
diff --git a/src/bts.h b/src/bts.h
index 6d92ae15..6af6d520 100644
--- a/src/bts.h
+++ b/src/bts.h
@@ -164,6 +164,11 @@ struct gprs_rlcmac_bts {
/* Are we talking Gb with IP-SNS (true) or classic Gb? */
bool gb_dialect_sns;
+
+ /* Packet Application Information (3GPP TS 44.060 11.2.47, usually ETWS primary message). We don't need to store
+ * more than one message, because they get sent so rarely. */
+ struct msgb *app_info;
+ uint32_t app_info_pending; /* Count of MS with active TBF, to which we did not send app_info yet */
};
#ifdef __cplusplus
diff --git a/src/gprs_ms.cpp b/src/gprs_ms.cpp
index 19f2ecb2..75f75e17 100644
--- a/src/gprs_ms.cpp
+++ b/src/gprs_ms.cpp
@@ -135,6 +135,7 @@ GprsMs::GprsMs(BTS *bts, uint32_t tlli) :
gprs_codel_set_interval(m_codel_state, codel_interval);
}
m_last_cs_not_low = now_msec();
+ app_info_pending = false;
}
GprsMs::~GprsMs()
diff --git a/src/gprs_ms.h b/src/gprs_ms.h
index ad8ca1da..781dd59f 100644
--- a/src/gprs_ms.h
+++ b/src/gprs_ms.h
@@ -136,6 +136,8 @@ public:
/* internal use */
static void timeout(void *priv_);
+ bool app_info_pending;
+
protected:
void update_status();
GprsMs *ref();
diff --git a/src/gprs_rlcmac.cpp b/src/gprs_rlcmac.cpp
index 5a223c13..4d93f8f0 100644
--- a/src/gprs_rlcmac.cpp
+++ b/src/gprs_rlcmac.cpp
@@ -41,4 +41,25 @@ int gprs_rlcmac_paging_request(uint8_t *ptmsi, uint16_t ptmsi_len,
return 0;
}
+/* Encode Application Information Request to Packet Application Information (3GPP TS 44.060 11.2.47) */
+struct msgb *gprs_rlcmac_app_info_msg(const struct gsm_pcu_if_app_info_req *req) {
+ struct msgb *msg;
+ uint16_t msgb_len = req->len + 1;
+ struct bitvec bv = {0, msgb_len, NULL};
+ const enum bit_value page_mode[] = {ZERO, ZERO}; /* Normal Paging (3GPP TS 44.060 12.20) */
+ if (!req->len) {
+ LOGP(DRLCMAC, LOGL_ERROR, "Application Information Request with zero length received!\n");
+ return NULL;
+ }
+
+ msg = msgb_alloc(msgb_len, "app_info_msg");
+ if (!msg)
+ return NULL;
+
+ bv.data = msgb_put(msg, msgb_len);
+ bitvec_set_bits(&bv, page_mode, 2);
+ bitvec_set_uint(&bv, req->application_type, 4);
+ bitvec_set_bytes(&bv, req->data, req->len);
+ return msg;
+}
diff --git a/src/gprs_rlcmac.h b/src/gprs_rlcmac.h
index 7a3a7af5..16cb05f2 100644
--- a/src/gprs_rlcmac.h
+++ b/src/gprs_rlcmac.h
@@ -31,6 +31,7 @@ extern "C" {
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/timer.h>
#include <osmocom/core/bitvec.h>
+#include <osmocom/pcu/pcuif_proto.h>
}
#endif
@@ -94,6 +95,8 @@ int gprs_rlcmac_tx_ul_ud(gprs_rlcmac_tbf *tbf);
int gprs_rlcmac_paging_request(uint8_t *ptmsi, uint16_t ptmsi_len,
const char *imsi);
+struct msgb *gprs_rlcmac_app_info_msg(const struct gsm_pcu_if_app_info_req *req);
+
int gprs_rlcmac_rcv_rts_block(struct gprs_rlcmac_bts *bts,
uint8_t trx, uint8_t ts,
uint32_t fn, uint8_t block_nr);
diff --git a/src/gprs_rlcmac_sched.cpp b/src/gprs_rlcmac_sched.cpp
index 57756e3b..7356523f 100644
--- a/src/gprs_rlcmac_sched.cpp
+++ b/src/gprs_rlcmac_sched.cpp
@@ -123,6 +123,34 @@ static uint8_t sched_select_uplink(uint8_t trx, uint8_t ts, uint32_t fn,
return usf;
}
+struct msgb *sched_app_info(struct gprs_rlcmac_tbf *tbf) {
+ struct gprs_rlcmac_bts *bts_data;
+ struct msgb *msg = NULL;
+
+ if (!tbf || !tbf->ms()->app_info_pending)
+ return NULL;
+
+ bts_data = BTS::main_bts()->bts_data();
+
+ if (bts_data->app_info) {
+ LOGP(DRLCMACSCHED, LOGL_DEBUG, "Sending Packet Application Information message\n");
+ msg = msgb_copy(bts_data->app_info, "app_info_msg_sched");
+ } else
+ LOGP(DRLCMACSCHED, LOGL_ERROR, "MS has app_info_pending flag set, but no Packet Application Information"
+ " message stored in BTS!\n");
+
+ tbf->ms()->app_info_pending = false;
+ bts_data->app_info_pending--;
+
+ if (!bts_data->app_info_pending) {
+ LOGP(DRLCMACSCHED, LOGL_DEBUG, "Packet Application Information successfully sent to all MS with active"
+ " TBF\n");
+ msgb_free(bts_data->app_info);
+ bts_data->app_info = NULL;
+ }
+ return msg;
+}
+
static struct msgb *sched_select_ctrl_msg(
uint8_t trx, uint8_t ts, uint32_t fn,
uint8_t block_nr, struct gprs_rlcmac_pdch *pdch,
@@ -134,37 +162,42 @@ static struct msgb *sched_select_ctrl_msg(
struct gprs_rlcmac_tbf *tbf = NULL;
struct gprs_rlcmac_tbf *next_list[3] = { ul_ass_tbf, dl_ass_tbf, ul_ack_tbf };
- for (size_t i = 0; i < ARRAY_SIZE(next_list); ++i) {
- tbf = next_list[(pdch->next_ctrl_prio + i) % 3];
- if (!tbf)
- continue;
+ /* Send Packet Application Information first (ETWS primary notifications) */
+ msg = sched_app_info(dl_ass_tbf);
- /*
- * Assignments for the same direction have lower precedence,
- * because they may kill the TBF when the CONTROL ACK is
- * received, thus preventing the others from being processed.
- */
- if (tbf == ul_ass_tbf && tbf->ul_ass_state_is(GPRS_RLCMAC_UL_ASS_SEND_ASS_REJ))
- msg = ul_ass_tbf->create_packet_access_reject();
- else if (tbf == ul_ass_tbf && tbf->direction ==
- GPRS_RLCMAC_DL_TBF)
- if (tbf->ul_ass_state_is(GPRS_RLCMAC_UL_ASS_SEND_ASS_REJ))
+ if (!msg) {
+ for (size_t i = 0; i < ARRAY_SIZE(next_list); ++i) {
+ tbf = next_list[(pdch->next_ctrl_prio + i) % 3];
+ if (!tbf)
+ continue;
+
+ /*
+ * Assignments for the same direction have lower precedence,
+ * because they may kill the TBF when the CONTROL ACK is
+ * received, thus preventing the others from being processed.
+ */
+ if (tbf == ul_ass_tbf && tbf->ul_ass_state_is(GPRS_RLCMAC_UL_ASS_SEND_ASS_REJ))
msg = ul_ass_tbf->create_packet_access_reject();
- else
- msg = ul_ass_tbf->create_ul_ass(fn, ts);
- else if (tbf == dl_ass_tbf && tbf->direction == GPRS_RLCMAC_UL_TBF)
- msg = dl_ass_tbf->create_dl_ass(fn, ts);
- else if (tbf == ul_ack_tbf)
- msg = ul_ack_tbf->create_ul_ack(fn, ts);
-
- if (!msg) {
- tbf = NULL;
- continue;
+ else if (tbf == ul_ass_tbf && tbf->direction ==
+ GPRS_RLCMAC_DL_TBF)
+ if (tbf->ul_ass_state_is(GPRS_RLCMAC_UL_ASS_SEND_ASS_REJ))
+ msg = ul_ass_tbf->create_packet_access_reject();
+ else
+ msg = ul_ass_tbf->create_ul_ass(fn, ts);
+ else if (tbf == dl_ass_tbf && tbf->direction == GPRS_RLCMAC_UL_TBF)
+ msg = dl_ass_tbf->create_dl_ass(fn, ts);
+ else if (tbf == ul_ack_tbf)
+ msg = ul_ack_tbf->create_ul_ack(fn, ts);
+
+ if (!msg) {
+ tbf = NULL;
+ continue;
+ }
+
+ pdch->next_ctrl_prio += 1;
+ pdch->next_ctrl_prio %= 3;
+ break;
}
-
- pdch->next_ctrl_prio += 1;
- pdch->next_ctrl_prio %= 3;
- break;
}
if (!msg) {
diff --git a/src/pcu_l1_if.cpp b/src/pcu_l1_if.cpp
index 6ffebcf6..8901ce6f 100644
--- a/src/pcu_l1_if.cpp
+++ b/src/pcu_l1_if.cpp
@@ -620,6 +620,42 @@ static int pcu_rx_susp_req(struct gsm_pcu_if_susp_req *susp_req)
return bssgp_tx_suspend(bctx->nsei, susp_req->tlli, &ra_id);
}
+static int pcu_rx_app_info_req(struct gsm_pcu_if_app_info_req *app_info_req)
+{
+ LListHead<GprsMs> *ms_iter;
+ BTS *bts = BTS::main_bts();
+ struct gprs_rlcmac_bts *bts_data = bts->bts_data();
+
+ LOGP(DL1IF, LOGL_DEBUG, "Application Information Request received: type=0x%08x len=%i\n",
+ 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())
+ continue;
+ bts_data->app_info_pending++;
+ ms->app_info_pending = true;
+ }
+
+ if (!bts_data->app_info_pending) {
+ LOGP(DL1IF, LOGL_NOTICE, "Packet Application Information will not be sent, no subscribers with active"
+ " TBF\n");
+ return -1;
+ }
+
+ if (bts_data->app_info) {
+ LOGP(DL1IF, LOGL_NOTICE, "Previous Packet Application Information was not sent to all subscribers,"
+ " overwriting with new one\n");
+ msgb_free(bts_data->app_info);
+ }
+
+ LOGP(DL1IF, LOGL_INFO, "Sending Packet Application Information to %i subscribers with active TBF\n",
+ bts_data->app_info_pending);
+ bts_data->app_info = gprs_rlcmac_app_info_msg(app_info_req);
+ return 0;
+}
+
int pcu_rx(uint8_t msg_type, struct gsm_pcu_if *pcu_prim)
{
int rc = 0;
@@ -649,6 +685,9 @@ int pcu_rx(uint8_t msg_type, struct gsm_pcu_if *pcu_prim)
case PCU_IF_MSG_SUSP_REQ:
rc = pcu_rx_susp_req(&pcu_prim->u.susp_req);
break;
+ case PCU_IF_MSG_APP_INFO_REQ:
+ rc = pcu_rx_app_info_req(&pcu_prim->u.app_info_req);
+ break;
default:
LOGP(DL1IF, LOGL_ERROR, "Received unknown PCU msg type %d\n",
msg_type);
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 887200d0..42dade91 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1,7 +1,7 @@
AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES) $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGB_CFLAGS) $(LIBOSMOGSM_CFLAGS) -I$(top_srcdir)/src/ -I$(top_srcdir)/include/
AM_LDFLAGS = -lrt -no-install
-check_PROGRAMS = rlcmac/RLCMACTest alloc/AllocTest alloc/MslotTest tbf/TbfTest types/TypesTest ms/MsTest llist/LListTest llc/LlcTest codel/codel_test edge/EdgeTest bitcomp/BitcompTest fn/FnTest
+check_PROGRAMS = rlcmac/RLCMACTest alloc/AllocTest alloc/MslotTest tbf/TbfTest types/TypesTest ms/MsTest llist/LListTest llc/LlcTest codel/codel_test edge/EdgeTest bitcomp/BitcompTest fn/FnTest app_info/AppInfoTest
noinst_PROGRAMS = emu/pcu_emu
rlcmac_RLCMACTest_SOURCES = rlcmac/RLCMACTest.cpp
@@ -108,6 +108,14 @@ fn_FnTest_LDADD = \
$(LIBOSMOCORE_LIBS) \
$(COMMON_LA)
+app_info_AppInfoTest_SOURCES = app_info/AppInfoTest.cpp
+app_info_AppInfoTest_LDADD = \
+ $(top_builddir)/src/libgprs.la \
+ $(LIBOSMOGB_LIBS) \
+ $(LIBOSMOGSM_LIBS) \
+ $(LIBOSMOCORE_LIBS) \
+ $(COMMON_LA)
+
# The `:;' works around a Bash 3.2 bug when the output is not writeable.
$(srcdir)/package.m4: $(top_srcdir)/configure.ac
:;{ \
@@ -138,7 +146,8 @@ EXTRA_DIST = \
llist/LListTest.ok llist/LListTest.err \
codel/codel_test.ok \
edge/EdgeTest.ok \
- fn/FnTest.ok
+ fn/FnTest.ok \
+ app_info/AppInfoTest.ok app_info/AppInfoTest.err
DISTCLEANFILES = atconfig
diff --git a/tests/app_info/AppInfoTest.cpp b/tests/app_info/AppInfoTest.cpp
new file mode 100644
index 00000000..c4cf548d
--- /dev/null
+++ b/tests/app_info/AppInfoTest.cpp
@@ -0,0 +1,191 @@
+/* Copyright (C) 2019 by sysmocom - s.f.m.c. GmbH <info@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 <cstdlib>
+#include <cstring>
+#include <assert.h>
+#include "gprs_rlcmac.h"
+#include "bts.h"
+
+extern "C" {
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/application.h>
+}
+
+using namespace std;
+gprs_rlcmac_dl_tbf *tbf1, *tbf2;
+GprsMs *ms1, *ms2;
+struct msgb *sched_app_info(struct gprs_rlcmac_tbf *tbf);
+
+/* globals used by the code */
+void *tall_pcu_ctx;
+int16_t spoof_mnc = 0, spoof_mcc = 0;
+bool spoof_mnc_3_digits = false;
+
+void test_enc_zero_len() {
+ struct gsm_pcu_if_app_info_req req = {0, 0, {0}};
+
+ fprintf(stderr, "--- %s ---\n", __func__);
+ assert(gprs_rlcmac_app_info_msg(&req) == NULL);
+ fprintf(stderr, "\n");
+}
+
+void test_enc() {
+ struct gsm_pcu_if_app_info_req req = {0, 15, {0xff, 0x00, 0xff}};
+ const char *exp = "03 fc 03 fc 00 00 00 00 00 00 00 00 00 00 00 00 "; /* shifted by two bits to the right */
+ struct msgb *msg;
+ char *msg_dump;
+
+ fprintf(stderr, "--- %s ---\n", __func__);
+ msg = gprs_rlcmac_app_info_msg(&req);
+ msg_dump = msgb_hexdump_c(tall_pcu_ctx, msg);
+
+ fprintf(stderr, "exp: %s\n", exp);
+ fprintf(stderr, "msg: %s\n", msg_dump);
+ assert(strcmp(msg_dump, exp) == 0);
+
+ msgb_free(msg);
+ talloc_free(msg_dump);
+ fprintf(stderr, "\n");
+}
+
+void test_pcu_rx_no_subscr_with_active_tbf()
+{
+ struct gsm_pcu_if pcu_prim = {PCU_IF_MSG_APP_INFO_REQ, };
+
+ fprintf(stderr, "--- %s ---\n", __func__);
+ pcu_rx(PCU_IF_MSG_APP_INFO_REQ, &pcu_prim);
+ fprintf(stderr, "\n");
+}
+
+void prepare_bts_with_two_dl_tbf_subscr()
+{
+ BTS *bts = BTS::main_bts();
+ struct gprs_rlcmac_bts *bts_data;
+ struct gprs_rlcmac_trx *trx;
+
+ fprintf(stderr, "--- %s ---\n", __func__);
+
+ bts_data = bts->bts_data();
+ bts_data->alloc_algorithm = alloc_algorithm_b;
+
+ trx = bts_data->trx;
+ trx->pdch[4].enable();
+ trx->pdch[5].enable();
+ trx->pdch[6].enable();
+ trx->pdch[7].enable();
+
+ ms1 = bts->ms_alloc(10, 11);
+ tbf1 = tbf_alloc_dl_tbf(bts_data, ms1, 0, 10, 11, false);
+ ms2 = bts->ms_alloc(12, 13);
+ tbf2 = tbf_alloc_dl_tbf(bts_data, ms2, 0, 12, 13, false);
+
+ fprintf(stderr, "\n");
+}
+
+void test_sched_app_info_ok()
+{
+ struct gsm_pcu_if pcu_prim = {PCU_IF_MSG_APP_INFO_REQ, };
+ struct msgb *msg;
+
+ fprintf(stderr, "--- %s ---\n", __func__);
+ pcu_prim.u.app_info_req = {0, 15, {0xff, 0x00, 0xff}};
+ pcu_rx(PCU_IF_MSG_APP_INFO_REQ, &pcu_prim);
+
+ msg = sched_app_info(tbf1);
+ assert(msg);
+ msgb_free(msg);
+
+ msg = sched_app_info(tbf2);
+ assert(msg);
+ msgb_free(msg);
+
+ fprintf(stderr, "\n");
+}
+
+void test_sched_app_info_missing_app_info_in_bts()
+{
+ struct gprs_rlcmac_bts *bts_data = BTS::main_bts()->bts_data();
+ struct gsm_pcu_if pcu_prim = {PCU_IF_MSG_APP_INFO_REQ, };
+
+ fprintf(stderr, "--- %s ---\n", __func__);
+ pcu_prim.u.app_info_req = {0, 15, {0xff, 0x00, 0xff}};
+ pcu_rx(PCU_IF_MSG_APP_INFO_REQ, &pcu_prim);
+
+ msgb_free(bts_data->app_info);
+ bts_data->app_info = NULL;
+
+ assert(sched_app_info(tbf1) == NULL);
+
+ fprintf(stderr, "\n");
+}
+
+void test_pcu_rx_overwrite_app_info()
+{
+ struct gsm_pcu_if pcu_prim = {PCU_IF_MSG_APP_INFO_REQ, };
+
+ fprintf(stderr, "--- %s ---\n", __func__);
+ pcu_prim.u.app_info_req = {0, 15, {0xff, 0x00, 0xff}};
+ pcu_rx(PCU_IF_MSG_APP_INFO_REQ, &pcu_prim);
+ pcu_rx(PCU_IF_MSG_APP_INFO_REQ, &pcu_prim);
+ fprintf(stderr, "\n");
+}
+
+void cleanup()
+{
+ fprintf(stderr, "--- %s ---\n", __func__);
+
+ 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);
+}
+
+int main(int argc, char *argv[])
+{
+ tall_pcu_ctx = talloc_named_const(NULL, 1, "AppInfoTest");
+ osmo_init_logging2(tall_pcu_ctx, &gprs_log_info);
+ log_set_use_color(osmo_stderr_target, 0);
+ log_set_print_filename(osmo_stderr_target, 0);
+ log_parse_category_mask(osmo_stderr_target, "DL1IF,1:DRLCMAC,3:DRLCMACSCHED,1");
+
+ test_enc_zero_len();
+ test_enc();
+ test_pcu_rx_no_subscr_with_active_tbf();
+
+ prepare_bts_with_two_dl_tbf_subscr();
+ test_sched_app_info_ok();
+ test_sched_app_info_missing_app_info_in_bts();
+ test_pcu_rx_overwrite_app_info();
+
+ cleanup();
+}
+
+/*
+ * stubs that should not be reached
+ */
+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/app_info/AppInfoTest.err b/tests/app_info/AppInfoTest.err
new file mode 100644
index 00000000..6ef5d831
--- /dev/null
+++ b/tests/app_info/AppInfoTest.err
@@ -0,0 +1,50 @@
+--- test_enc_zero_len ---
+Application Information Request with zero length received!
+
+--- test_enc ---
+exp: 03 fc 03 fc 00 00 00 00 00 00 00 00 00 00 00 00
+msg: 03 fc 03 fc 00 00 00 00 00 00 00 00 00 00 00 00
+
+--- test_pcu_rx_no_subscr_with_active_tbf ---
+Application Information Request received: type=0x00000000 len=0
+Packet Application Information will not be sent, no subscribers with active TBF
+
+--- prepare_bts_with_two_dl_tbf_subscr ---
+Creating MS object, TLLI = 0x00000000
+Modifying MS object, TLLI = 0x00000000, MS class 0 -> 10
+Modifying MS object, TLLI = 0x00000000, EGPRS MS class 0 -> 11
+[DL] algo B <multi> (suggested TRX: 0): using 4 slots
+PDCH(TS 4, TRX 0): Attaching TBF(TFI=0 TLLI=0x00000000 DIR=DL STATE=NULL), 1 TBFs, USFs = 00, TFIs = 00000001.
+PDCH(TS 5, TRX 0): Attaching TBF(TFI=0 TLLI=0x00000000 DIR=DL STATE=NULL), 1 TBFs, USFs = 00, TFIs = 00000001.
+PDCH(TS 6, TRX 0): Attaching TBF(TFI=0 TLLI=0x00000000 DIR=DL STATE=NULL), 1 TBFs, USFs = 00, TFIs = 00000001.
+PDCH(TS 7, TRX 0): Attaching TBF(TFI=0 TLLI=0x00000000 DIR=DL STATE=NULL), 1 TBFs, USFs = 00, TFIs = 00000001.
+Attaching TBF to MS object, TLLI = 0x00000000, TBF = TBF(TFI=0 TLLI=0x00000000 DIR=DL STATE=NULL)
+Creating MS object, TLLI = 0x00000000
+Modifying MS object, TLLI = 0x00000000, MS class 0 -> 12
+Modifying MS object, TLLI = 0x00000000, EGPRS MS class 0 -> 13
+[DL] algo B <multi> (suggested TRX: 0): using 3 slots
+PDCH(TS 4, TRX 0): Attaching TBF(TFI=1 TLLI=0x00000000 DIR=DL STATE=NULL), 2 TBFs, USFs = 00, TFIs = 00000003.
+PDCH(TS 5, TRX 0): Attaching TBF(TFI=1 TLLI=0x00000000 DIR=DL STATE=NULL), 2 TBFs, USFs = 00, TFIs = 00000003.
+PDCH(TS 6, TRX 0): Attaching TBF(TFI=1 TLLI=0x00000000 DIR=DL STATE=NULL), 2 TBFs, USFs = 00, TFIs = 00000003.
+Attaching TBF to MS object, TLLI = 0x00000000, TBF = TBF(TFI=1 TLLI=0x00000000 DIR=DL STATE=NULL)
+
+--- test_sched_app_info_ok ---
+Application Information Request received: type=0x00000000 len=15
+Sending Packet Application Information to 2 subscribers with active TBF
+Sending Packet Application Information message
+Sending Packet Application Information message
+Packet Application Information successfully sent to all MS with active TBF
+
+--- test_sched_app_info_missing_app_info_in_bts ---
+Application Information Request received: type=0x00000000 len=15
+Sending Packet Application Information to 2 subscribers with active TBF
+MS has app_info_pending flag set, but no Packet Application Information message stored in BTS!
+
+--- test_pcu_rx_overwrite_app_info ---
+Application Information Request received: type=0x00000000 len=15
+Sending Packet Application Information to 2 subscribers with active TBF
+Application Information Request received: type=0x00000000 len=15
+Previous Packet Application Information was not sent to all subscribers, overwriting with new one
+Sending Packet Application Information to 2 subscribers with active TBF
+
+--- cleanup ---
diff --git a/tests/app_info/AppInfoTest.ok b/tests/app_info/AppInfoTest.ok
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/tests/app_info/AppInfoTest.ok
diff --git a/tests/testsuite.at b/tests/testsuite.at
index 86f45a82..09b02475 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -82,3 +82,10 @@ AT_KEYWORDS([fn])
cat $abs_srcdir/fn/FnTest.ok > expout
AT_CHECK([$OSMO_QEMU $abs_top_builddir/tests/fn/FnTest], [0], [expout], [ignore])
AT_CLEANUP
+
+AT_SETUP([app_info])
+AT_KEYWORDS([app_info])
+cat $abs_srcdir/app_info/AppInfoTest.ok > expout
+cat $abs_srcdir/app_info/AppInfoTest.err > experr
+AT_CHECK([$OSMO_QEMU $abs_top_builddir/tests/app_info/AppInfoTest], [0], [expout], [experr])
+AT_CLEANUP