From f53fde91a36eff2601df9811fddee97b8f89d6ee Mon Sep 17 00:00:00 2001 From: Harald Welte Date: Tue, 3 Sep 2019 13:54:37 +0200 Subject: ETWS Primary Notification via P1 Rest Octets The ETWS (Earthquake and Tsunami Warning System) uses a so-called ETWS Primary Notification which is sent * to phones in dedicated mode (via DCCH from the BSC) * to phones in idle mode (via P1 Rest Octets on PCH/CCCH) This patch implements the second part of the functionality, i.e. transmitting the related ETWS Primary Notification via PCH. As 3GPP doesn't specify how this is communicated over Abis, we use a new, vendor-specific RSL message type. Closes: OS#4047 Depends: libosmocore I89c24a81ada6627694a9632e87485a61cbd3e680 Depends: libosmocore I36fc2ffc22728887d1cb8768c7fcd9739a8ec0fc Change-Id: I18c60cdb86b9c19e09f5ec06d66e9b91608880e6 --- include/osmo-bts/gsm_data_shared.h | 10 +++ src/common/bts.c | 3 + src/common/gsm_data_shared.c | 1 + src/common/paging.c | 140 ++++++++++++++++++++++++++++++++++--- src/common/rsl.c | 43 ++++++++++++ tests/paging/paging_test.c | 19 ++++- 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 @@ -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)); -- cgit v1.2.3