aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/osmo-bts/gsm_data_shared.h10
-rw-r--r--src/common/bts.c3
-rw-r--r--src/common/gsm_data_shared.c1
-rw-r--r--src/common/paging.c140
-rw-r--r--src/common/rsl.c43
-rw-r--r--tests/paging/paging_test.c19
6 files changed, 202 insertions, 14 deletions
diff --git a/include/osmo-bts/gsm_data_shared.h b/include/osmo-bts/gsm_data_shared.h
index dd2a14c0..65e984d4 100644
--- a/include/osmo-bts/gsm_data_shared.h
+++ b/include/osmo-bts/gsm_data_shared.h
@@ -479,6 +479,7 @@ enum gsm_bts_features {
BTS_FEAT_SPEECH_F_EFR,
BTS_FEAT_SPEECH_F_AMR,
BTS_FEAT_SPEECH_H_AMR,
+ BTS_FEAT_ETWS_PN,
_NUM_BTS_FEAT
};
@@ -718,6 +719,15 @@ struct gsm_bts {
uint64_t pch_msgs;
} agch_queue;
+ struct {
+ uint8_t *prim_notif; /* ETWS primary notification (NULL if none) */
+ ssize_t prim_notif_len; /* Length of prim_notif; expected 56 bytes */
+ uint8_t page_size;
+ uint8_t num_pages; /* total number of pages */
+ uint8_t next_page; /* next page number to be sent */
+ bool pni; /* Primary Notification Identifier */
+ } etws;
+
struct paging_state *paging_state;
char *bsc_oml_host;
struct llist_head oml_queue;
diff --git a/src/common/bts.c b/src/common/bts.c
index 5c415e86..73631ae6 100644
--- a/src/common/bts.c
+++ b/src/common/bts.c
@@ -192,6 +192,9 @@ int bts_init(struct gsm_bts *bts)
tall_rtp_ctx = talloc_pool(tall_bts_ctx, 262144);
osmo_rtp_init(tall_rtp_ctx);
+ /* features implemented in 'common', available for all models */
+ gsm_bts_set_feature(bts, BTS_FEAT_ETWS_PN);
+
rc = bts_model_init(bts);
if (rc < 0) {
llist_del(&bts->list);
diff --git a/src/common/gsm_data_shared.c b/src/common/gsm_data_shared.c
index b1785b80..1ba43aa6 100644
--- a/src/common/gsm_data_shared.c
+++ b/src/common/gsm_data_shared.c
@@ -106,6 +106,7 @@ const struct value_string gsm_bts_features_descs[] = {
{ BTS_FEAT_SPEECH_F_EFR, "Fullrate speech EFR" },
{ BTS_FEAT_SPEECH_F_AMR, "Fullrate speech AMR" },
{ BTS_FEAT_SPEECH_H_AMR, "Halfrate speech AMR" },
+ { BTS_FEAT_ETWS_PN, "ETWS Primary Notification on PCH" },
{ 0, NULL }
};
diff --git a/src/common/paging.c b/src/common/paging.c
index 111f9475..fca58b5f 100644
--- a/src/common/paging.c
+++ b/src/common/paging.c
@@ -21,7 +21,7 @@
/* TODO:
* eMLPP priprity
- * add P1/P2/P3 rest octets
+ * add P2/P3 rest octets
*/
#include <stdlib.h>
@@ -274,11 +274,86 @@ int paging_add_imm_ass(struct paging_state *ps, const uint8_t *data,
#define L2_PLEN(len) (((len - 1) << 2) | 0x01)
+/* abstract representation of P1 rest octets; we only implement those parts we need for now */
+struct p1_rest_octets {
+ bool packet_page_ind[2];
+ bool r8_present;
+ struct {
+ bool prio_ul_access;
+ bool etws_present;
+ struct {
+ bool is_first;
+ uint8_t page_nr;
+ const uint8_t *page;
+ size_t page_bytes;
+ } etws;
+ } r8;
+};
+
+/* 3GPP TS 44.018 10.5.2.23 append a segment/page of an ETWS primary notification to given bitvec */
+static void append_etws_prim_notif(struct bitvec *bv, bool is_first, uint8_t page_nr,
+ const uint8_t *etws, ssize_t etws_len)
+{
+ OSMO_ASSERT(etws_len < 128/8);
+
+ /* ETWS primary Notification struct
+ * 0 NNNN / 1 NNNN
+ * PNI n
+ * LEN nnnnnnn (at least 13 bits before paylod)
+ * number of bits (LEN; up to 128) */
+
+ if (is_first)
+ bitvec_set_bit(bv, 0);
+ else
+ bitvec_set_bit(bv, 1);
+ bitvec_set_uint(bv, page_nr, 4); /* Segment Number / Total Number */
+ bitvec_set_bit(bv, 0); /* PNI to distinguish different ETWS */
+ bitvec_set_uint(bv, etws_len*8, 7); /* length of payload in number of bits */
+ bitvec_set_bytes(bv, etws, etws_len);
+
+ /* 17 bytes = 136bit - (11+13) = 112 bits = 14 bytes per PT1
+ * => at least 4x PT1 RO for complete primary notification (56 bytes) */
+}
+
+/* 3GPP TS 44.018 10.5.2.23 append P1 Rest Octets to given bit-vector */
+static void append_p1_rest_octets(struct bitvec *bv, const struct p1_rest_octets *p1ro)
+{
+ /* Paging 1 RO (at least 10 bits before ETWS struct) */
+ bitvec_set_bit(bv, L); /* no NLN */
+ bitvec_set_bit(bv, L); /* no Priority1 */
+ bitvec_set_bit(bv, L); /* no Priority2 */
+ bitvec_set_bit(bv, L); /* no Group Call Info */
+ if (p1ro->packet_page_ind[0])
+ bitvec_set_bit(bv, H); /* Packet Page Indication 1 */
+ else
+ bitvec_set_bit(bv, L); /* Packet Page Indication 1 */
+ if (p1ro->packet_page_ind[1])
+ bitvec_set_bit(bv, H); /* Packet Page Indication 2 */
+ else
+ bitvec_set_bit(bv, L); /* Packet Page Indication 2 */
+
+ bitvec_set_bit(bv, L); /* No Release 6 additions */
+ bitvec_set_bit(bv, L); /* No Release 7 additions */
+
+ if (p1ro->r8_present) {
+ bitvec_set_bit(bv, H); /* Release 8 */
+ bitvec_set_bit(bv, p1ro->r8.prio_ul_access); /* Priority Uplink Access */
+ if (p1ro->r8.etws_present) {
+ bitvec_set_bit(bv, 1); /* ETWS present */
+ append_etws_prim_notif(bv, p1ro->r8.etws.is_first, p1ro->r8.etws.page_nr,
+ p1ro->r8.etws.page, p1ro->r8.etws.page_bytes);
+ } else
+ bitvec_set_bit(bv, 0);
+ }
+}
+
static int fill_paging_type_1(uint8_t *out_buf, const uint8_t *identity1_lv,
uint8_t chan1, const uint8_t *identity2_lv,
- uint8_t chan2)
+ uint8_t chan2, const struct p1_rest_octets *p1ro)
{
struct gsm48_paging1 *pt1 = (struct gsm48_paging1 *) out_buf;
+ struct bitvec bv;
+ unsigned int paging_len;
uint8_t *cur;
memset(out_buf, 0, sizeof(*pt1));
@@ -294,7 +369,19 @@ static int fill_paging_type_1(uint8_t *out_buf, const uint8_t *identity1_lv,
pt1->l2_plen = L2_PLEN(cur - out_buf);
- return cur - out_buf;
+ paging_len = cur - out_buf;
+
+ memset(&bv, 0, sizeof(bv));
+ bv.data = cur;
+ bv.data_len = GSM_MACBLOCK_LEN - paging_len;
+
+ if (p1ro)
+ append_p1_rest_octets(&bv, p1ro);
+
+ /* pad to the end of the MAC block */
+ bitvec_spare_padding(&bv, bv.data_len *8);
+
+ return GSM_MACBLOCK_LEN;
}
static int fill_paging_type_2(uint8_t *out_buf, const uint8_t *tmsi1_lv,
@@ -406,16 +493,43 @@ static void sort_pr_tmsi_imsi(struct paging_record *pr[], unsigned int n)
}
}
+static void build_p1_rest_octets(struct p1_rest_octets *p1ro, struct gsm_bts *bts)
+{
+ memset(p1ro, 0, sizeof(*p1ro));
+ p1ro->packet_page_ind[0] = false;
+ p1ro->packet_page_ind[1] = false;
+ p1ro->r8_present = true;
+ p1ro->r8.prio_ul_access = false;
+ p1ro->r8.etws_present = true;
+ unsigned int offset = bts->etws.page_size * bts->etws.next_page;
+
+ if (bts->etws.next_page == 0) {
+ p1ro->r8.etws.is_first = true;
+ p1ro->r8.etws.page_nr = bts->etws.num_pages;
+ } else {
+ p1ro->r8.etws.is_first = false;
+ p1ro->r8.etws.page_nr = bts->etws.next_page + 1;
+ }
+ p1ro->r8.etws.page = bts->etws.prim_notif + offset;
+ /* last page may be smaller than first pages */
+ if (bts->etws.next_page < bts->etws.num_pages-1)
+ p1ro->r8.etws.page_bytes = bts->etws.page_size;
+ else
+ p1ro->r8.etws.page_bytes = bts->etws.prim_notif_len - offset;
+ bts->etws.next_page = (bts->etws.next_page + 1) % bts->etws.num_pages;
+}
+
/* generate paging message for given gsm time */
int paging_gen_msg(struct paging_state *ps, uint8_t *out_buf, struct gsm_time *gt,
int *is_empty)
{
struct llist_head *group_q;
+ struct gsm_bts *bts = ps->bts;
int group;
int len;
*is_empty = 0;
- ps->bts->load.ccch.pch_total += 1;
+ bts->load.ccch.pch_total += 1;
group = get_pag_subch_nr(ps, gt);
if (group < 0) {
@@ -427,11 +541,15 @@ int paging_gen_msg(struct paging_state *ps, uint8_t *out_buf, struct gsm_time *g
group_q = &ps->paging_queue[group];
- /* There is nobody to be paged, send Type1 with two empty ID */
- if (llist_empty(group_q)) {
+ if (ps->bts->etws.prim_notif) {
+ struct p1_rest_octets p1ro;
+ build_p1_rest_octets(&p1ro, bts);
+ len = fill_paging_type_1(out_buf, empty_id_lv, 0, NULL, 0, &p1ro);
+ } else if (llist_empty(group_q)) {
+ /* There is nobody to be paged, send Type1 with two empty ID */
//DEBUGP(DPAG, "Tx PAGING TYPE 1 (empty)\n");
len = fill_paging_type_1(out_buf, empty_id_lv, 0,
- NULL, 0);
+ NULL, 0, NULL);
*is_empty = 1;
} else {
struct paging_record *pr[4];
@@ -439,7 +557,7 @@ int paging_gen_msg(struct paging_state *ps, uint8_t *out_buf, struct gsm_time *g
time_t now = time(NULL);
unsigned int i, num_imsi = 0;
- ps->bts->load.ccch.pch_used += 1;
+ bts->load.ccch.pch_used += 1;
/* get (if we have) up to four paging records */
for (i = 0; i < ARRAY_SIZE(pr); i++) {
@@ -509,7 +627,7 @@ int paging_gen_msg(struct paging_state *ps, uint8_t *out_buf, struct gsm_time *g
len = fill_paging_type_1(out_buf,
pr[0]->u.paging.identity_lv,
pr[0]->u.paging.chan_needed,
- NULL, 0);
+ NULL, 0, NULL);
} else {
/* 2 (any type) or
* 3 or 4, of which only 2 will be sent */
@@ -518,7 +636,7 @@ int paging_gen_msg(struct paging_state *ps, uint8_t *out_buf, struct gsm_time *g
pr[0]->u.paging.identity_lv,
pr[0]->u.paging.chan_needed,
pr[1]->u.paging.identity_lv,
- pr[1]->u.paging.chan_needed);
+ pr[1]->u.paging.chan_needed, NULL);
if (num_pr >= 3) {
/* re-add #4 for next time */
llist_add(&pr[2]->list, group_q);
@@ -535,7 +653,7 @@ int paging_gen_msg(struct paging_state *ps, uint8_t *out_buf, struct gsm_time *g
/* skip those that we might have re-added above */
if (pr[i] == NULL)
continue;
- rate_ctr_inc2(ps->bts->ctrs, BTS_CTR_PAGING_SENT);
+ rate_ctr_inc2(bts->ctrs, BTS_CTR_PAGING_SENT);
/* check if we can expire the paging record,
* or if we need to re-queue it */
if (pr[i]->u.paging.expiration_time <= now) {
diff --git a/src/common/rsl.c b/src/common/rsl.c
index d09dc4a4..0bcad4c0 100644
--- a/src/common/rsl.c
+++ b/src/common/rsl.c
@@ -522,6 +522,46 @@ static int rsl_rx_sms_bcast_cmd(struct gsm_bts_trx *trx, struct msgb *msg)
return 0;
}
+/* OSMO_ETWS_CMD - proprietary extension as TS 48.058 has no standardized way to do this :( */
+static int rsl_rx_osmo_etws_cmd(struct gsm_bts_trx *trx, struct msgb *msg)
+{
+ struct abis_rsl_cchan_hdr *cch = msgb_l2(msg);
+ struct gsm_bts *bts = trx->bts;
+ struct tlv_parsed tp;
+
+ rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg));
+
+ if (!TLVP_PRESENT(&tp, RSL_IE_SMSCB_MSG))
+ return rsl_tx_error_report(trx, RSL_ERR_MAND_IE_ERROR, &cch->chan_nr, NULL, msg);
+
+ bts->etws.prim_notif_len = TLVP_LEN(&tp, RSL_IE_SMSCB_MSG);
+ if (bts->etws.prim_notif_len == 0) {
+ LOGP(DRSL, LOGL_NOTICE, "ETWS Primary Notification OFF\n");
+ talloc_free(bts->etws.prim_notif);
+ bts->etws.prim_notif = NULL;
+ bts->etws.prim_notif_len = 0;
+ bts->etws.page_size = 0;
+ bts->etws.num_pages = 0;
+ bts->etws.next_page = 0;
+ } else {
+ LOGP(DRSL, LOGL_NOTICE, "ETWS Primary Notification: %s\n",
+ osmo_hexdump(TLVP_VAL(&tp, RSL_IE_SMSCB_MSG),
+ TLVP_LEN(&tp, RSL_IE_SMSCB_MSG)));
+ talloc_free(bts->etws.prim_notif);
+ bts->etws.prim_notif = talloc_memdup(bts, TLVP_VAL(&tp, RSL_IE_SMSCB_MSG),
+ bts->etws.prim_notif_len);
+
+ bts->etws.page_size = 14; /* maximum possible in SI1 Rest Octets */
+ bts->etws.num_pages = bts->etws.prim_notif_len / bts->etws.page_size;
+ if (bts->etws.prim_notif_len % bts->etws.page_size)
+ bts->etws.num_pages++;
+
+ /* toggle the PNI to allow phones to distinguish new from old primary notification */
+ bts->etws.pni = !bts->etws.pni;
+ }
+ return 0;
+}
+
/*! Prefix a given SACCH frame with a L2/LAPDm UI header and store it in given output buffer.
* \param[out] buf Output buffer, must be caller-allocated and hold at least len + 2 or sizeof(sysinfo_buf_t) bytes
* \param[out] valid pointer to bit-mask of 'valid' System information types
@@ -2951,6 +2991,9 @@ static int rsl_rx_cchan(struct gsm_bts_trx *trx, struct msgb *msg)
rsl_msg_name(cch->c.msg_type));
rsl_tx_error_report(trx, RSL_ERR_MSG_TYPE, &cch->chan_nr, NULL, msg);
break;
+ case RSL_MT_OSMO_ETWS_CMD:
+ ret = rsl_rx_osmo_etws_cmd(trx, msg);
+ break;
default:
LOGPLCHAN(msg->lchan, DRSL, LOGL_NOTICE, "undefined RSL cchan msg_type 0x%02x\n",
cch->c.msg_type);
diff --git a/tests/paging/paging_test.c b/tests/paging/paging_test.c
index f112404a..af8accc5 100644
--- a/tests/paging/paging_test.c
+++ b/tests/paging/paging_test.c
@@ -42,6 +42,16 @@ static const uint8_t static_ilv[] = {
abort(); \
}
+static bool is_padding(const uint8_t *in, size_t len)
+{
+ int i;
+ for (i = 0; i < len; i++) {
+ if (in[i] != 0x2b)
+ return false;
+ }
+ return true;
+}
+
static void test_paging_smoke(void)
{
int rc;
@@ -61,7 +71,8 @@ static void test_paging_smoke(void)
g_time.t2 = 0;
g_time.t3 = 6;
rc = paging_gen_msg(bts->paging_state, out_buf, &g_time, &is_empty);
- ASSERT_TRUE(rc == 13);
+ ASSERT_TRUE(rc == 23);
+ ASSERT_TRUE(is_padding(out_buf+13, 23-13));
ASSERT_TRUE(is_empty == 0);
ASSERT_TRUE(paging_group_queue_empty(bts->paging_state, 0));
@@ -73,7 +84,8 @@ static void test_paging_smoke(void)
g_time.t2 = 0;
g_time.t3 = 6;
rc = paging_gen_msg(bts->paging_state, out_buf, &g_time, &is_empty);
- ASSERT_TRUE(rc == 6);
+ ASSERT_TRUE(rc == 23);
+ ASSERT_TRUE(is_padding(out_buf+6, 23-6));
ASSERT_TRUE(is_empty == 1);
/*
@@ -104,7 +116,8 @@ static void test_paging_sleep(void)
g_time.t2 = 0;
g_time.t3 = 6;
rc = paging_gen_msg(bts->paging_state, out_buf, &g_time, &is_empty);
- ASSERT_TRUE(rc == 13);
+ ASSERT_TRUE(rc == 23);
+ ASSERT_TRUE(is_padding(out_buf+13, 23-13));
ASSERT_TRUE(is_empty == 0);
ASSERT_TRUE(paging_group_queue_empty(bts->paging_state, 0));