aboutsummaryrefslogtreecommitdiffstats
path: root/src/osmo-bsc/smscb.c
diff options
context:
space:
mode:
authorHarald Welte <laforge@gnumonks.org>2019-06-13 09:41:58 +0200
committerHarald Welte <laforge@gnumonks.org>2019-09-02 12:06:25 +0200
commitd41b7c7f830e90c7c4ce1d8ed97f13cfd3ed8cad (patch)
tree7a8a53f81cd42f34c8c761ef5c8bbd7a05a9944c /src/osmo-bsc/smscb.c
parent9508e2232f72a368ab87b5903e0764c4ae4985ad (diff)
Cell Broadcast: CBSP and CBCH scheduling support
This adds code to handle CBSP (Cell Broadcast Service Protocol) from the CBC (Cell Broadcast Centre), as well as BSC-internal data structures for scheduling the various SMSCB on the CBCH of each BTS. There are currently one known shortcoming in the code: We don't yet verify if keepalives are received within repetition period. Change-Id: Ia0a0de862a104d0f447a5d6e56c7c83981b825c7
Diffstat (limited to 'src/osmo-bsc/smscb.c')
-rw-r--r--src/osmo-bsc/smscb.c823
1 files changed, 823 insertions, 0 deletions
diff --git a/src/osmo-bsc/smscb.c b/src/osmo-bsc/smscb.c
new file mode 100644
index 000000000..6b9608652
--- /dev/null
+++ b/src/osmo-bsc/smscb.c
@@ -0,0 +1,823 @@
+/* SMSCB (SMS Cell Broadcast) Handling for OsmoBSC */
+/*
+ * (C) 2019 by Harald Welte <laforge@gnumonks.org>
+ *
+ * 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 Affero 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 <limits.h>
+
+#include <osmocom/core/stats.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+
+#include <osmocom/gsm/cbsp.h>
+#include <osmocom/gsm/protocol/gsm_23_041.h>
+#include <osmocom/gsm/protocol/gsm_48_049.h>
+
+#include <osmocom/netif/stream.h>
+
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/smscb.h>
+#include <osmocom/bsc/vty.h>
+
+/*********************************************************************************
+ * Helper Functions
+ *********************************************************************************/
+
+/* replace the old head of an entire list with a new head; effectively moves the entire
+ * list from old to new head */
+static void llist_replace_head(struct llist_head *new, struct llist_head *old)
+{
+ if (llist_empty(old))
+ INIT_LLIST_HEAD(new);
+ else
+ __llist_add(new, old->prev, old->next);
+ INIT_LLIST_HEAD(old);
+}
+
+/*! Obtain SMSCB Channel State for given BTS (basic or extended CBCH) */
+struct bts_smscb_chan_state *bts_get_smscb_chan(struct gsm_bts *bts, bool extended)
+{
+ struct bts_smscb_chan_state *chan_state;
+
+ if (extended)
+ chan_state = &bts->cbch_extended;
+ else
+ chan_state = &bts->cbch_basic;
+
+ return chan_state;
+}
+
+/* do an ordered list insertion. we keep the list with increasing period, i.e. the most
+ * frequent message first */
+static void __bts_smscb_add(struct bts_smscb_chan_state *cstate, struct bts_smscb_message *new)
+{
+ struct bts_smscb_message *tmp, *tmp2;
+
+ if (llist_empty(&cstate->messages)) {
+ llist_add(&new->list, &cstate->messages);
+ return;
+ }
+
+ llist_for_each_entry_safe(tmp, tmp2, &cstate->messages, list) {
+ if (tmp->input.rep_period > new->input.rep_period) {
+ /* we found the first message with longer period than the new message,
+ * we must insert ourselves before that one */
+ __llist_add(&new->list, tmp->list.prev, &tmp->list);
+ return;
+ }
+ }
+}
+
+/* stringify a SMSCB for logging */
+const char *bts_smscb_msg2str(const struct bts_smscb_message *smscb)
+{
+ static char buf[128];
+ snprintf(buf, sizeof(buf), "MsgId=0x%04x/SerialNr=0x%04x/Pages=%u/Period=%u/NumBcastReq=%u",
+ smscb->input.msg_id, smscb->input.serial_nr, smscb->num_pages,
+ smscb->input.rep_period, smscb->input.num_bcast_req);
+ return buf;
+}
+
+const char *bts_smscb_chan_state_name(const struct bts_smscb_chan_state *cstate)
+{
+ if (cstate == &cstate->bts->cbch_basic)
+ return "BASIC";
+ else if (cstate == &cstate->bts->cbch_extended)
+ return "EXTENDED";
+ else
+ return "UNKNOWN";
+}
+
+unsigned int bts_smscb_chan_load_percent(const struct bts_smscb_chan_state *cstate)
+{
+ unsigned int sched_arr_used = 0;
+ unsigned int i;
+
+ if (cstate->sched_arr_size == 0)
+ return 0;
+
+ /* count the number of used slots */
+ for (i = 0; i < cstate->sched_arr_size; i++) {
+ if (cstate->sched_arr[i])
+ sched_arr_used++;
+ }
+
+ OSMO_ASSERT(sched_arr_used <= UINT_MAX/100);
+ return (sched_arr_used * 100) / cstate->sched_arr_size;
+}
+
+unsigned int bts_smscb_chan_page_count(const struct bts_smscb_chan_state *cstate)
+{
+ struct bts_smscb_message *smscb;
+ unsigned int page_count = 0;
+
+ llist_for_each_entry(smscb, &cstate->messages, list)
+ page_count += smscb->num_pages;
+
+ return page_count;
+}
+
+
+/*! Obtain the Cell Global Identifier (CGI) of given BTS; returned in static buffer. */
+static struct osmo_cell_global_id *bts_get_cgi(struct gsm_bts *bts)
+{
+ static struct osmo_cell_global_id cgi;
+ cgi.lai.plmn = bts->network->plmn;
+ cgi.lai.lac = bts->location_area_code;
+ cgi.cell_identity = bts->cell_identity;
+ return &cgi;
+}
+
+/* represents the various lists that the BSC can create as part of a response */
+struct response_state {
+ struct osmo_cbsp_cell_list success; /* osmo_cbsp_cell_ent */
+ struct llist_head fail; /* osmo_cbsp_fail_ent */
+ struct osmo_cbsp_num_compl_list num_completed; /* osmo_cbsp_num_compl_ent */
+ struct osmo_cbsp_loading_list loading; /* osmo_cbsp_loading_ent */
+};
+
+/*! per-BTS callback function used by cbsp_per_bts().
+ * \param[in] bts BTS currently being processed
+ * \param[in] dec decoded CBSP message currently being processed
+ * \param r_state response state accumulating cell lists (success/failure/...)
+ * \param priv opaque private data provided by caller of cbsp_per_bts()
+ * \returns 0 on success; negative TS 48.049 cause value on error */
+typedef int bts_cb_fn(struct gsm_bts *bts, const struct osmo_cbsp_decoded *dec,
+ struct response_state *r_state, void *priv);
+
+/* append a success for given cell to response state */
+static void append_success(struct response_state *r_state, struct gsm_bts *bts)
+{
+ struct osmo_cbsp_cell_ent *cent = talloc_zero(r_state, struct osmo_cbsp_cell_ent);
+ struct osmo_cell_global_id *cgi = bts_get_cgi(bts);
+
+ LOG_BTS(bts, DCBS, LOGL_INFO, "Success\n");
+
+ OSMO_ASSERT(cent);
+
+ cent->cell_id.global = *cgi;
+ llist_add_tail(&cent->list, &r_state->success.list);
+}
+
+/* append a failure for given cell to response state */
+static void append_fail(struct response_state *r_state, struct gsm_bts *bts, uint8_t cause)
+{
+ struct osmo_cbsp_fail_ent *fent = talloc_zero(r_state, struct osmo_cbsp_fail_ent);
+ struct osmo_cell_global_id *cgi = bts_get_cgi(bts);
+
+ LOG_BTS(bts, DCBS, LOGL_NOTICE, "Failure Cause 0x%02x\n", cause);
+
+ OSMO_ASSERT(fent);
+
+ fent->id_discr = CELL_IDENT_WHOLE_GLOBAL;
+ fent->cell_id.global = *cgi;
+ fent->cause = cause;
+ llist_add_tail(&fent->list, &r_state->fail);
+}
+
+/* append a 'number of broadcasts completed' for given cell to response state */
+static void append_bcast_compl(struct response_state *r_state, struct gsm_bts *bts,
+ struct bts_smscb_message *smscb)
+{
+ struct osmo_cbsp_num_compl_ent *cent = talloc_zero(r_state, struct osmo_cbsp_num_compl_ent);
+ struct osmo_cell_global_id *cgi = bts_get_cgi(bts);
+
+ LOG_BTS(bts, DCBS, LOGL_DEBUG, "Number of Broadcasts Completed: %u\n", smscb->bcast_count);
+
+ OSMO_ASSERT(cent);
+
+ r_state->num_completed.id_discr = CELL_IDENT_WHOLE_GLOBAL;
+ cent->cell_id.global = *cgi;
+ if (smscb->bcast_count > INT16_MAX) {
+ cent->num_compl = INT16_MAX;
+ cent->num_bcast_info = 0x01; /* Overflow */
+ } else {
+ cent->num_compl = smscb->bcast_count;
+ cent->num_bcast_info = 0x00;
+ }
+ llist_add_tail(&cent->list, &r_state->num_completed.list);
+}
+
+/*! Iterate over all BTSs, find matching ones, execute command on BTS, add result
+ * to succeeded/failed lists.
+ * \param[in] net GSM network in which we operate
+ * \param[in] caller-allocated Response state structure collecting results
+ * \param[in] cell_list Decoded CBSP cell list describing BTSs to operate on
+ * \param[in] cb_fn Call-back function to call for each matching BTS
+ * \param[in] priv Opqaue private data; passed to cb_fn
+ * */
+static int cbsp_per_bts(struct gsm_network *net, struct response_state *r_state,
+ const struct osmo_cbsp_cell_list *cell_list,
+ bts_cb_fn *cb_fn, const struct osmo_cbsp_decoded *dec, void *priv)
+{
+ struct osmo_cbsp_cell_ent *ent;
+ struct gsm_bts *bts;
+ uint8_t bts_status[net->num_bts];
+ int rc, ret = 0;
+
+ memset(bts_status, 0, sizeof(bts_status));
+ INIT_LLIST_HEAD(&r_state->success.list);
+ INIT_LLIST_HEAD(&r_state->fail);
+ INIT_LLIST_HEAD(&r_state->num_completed.list);
+ INIT_LLIST_HEAD(&r_state->loading.list);
+
+ /* special case as cell_list->list is empty in this case */
+ if (cell_list->id_discr == CELL_IDENT_BSS) {
+ llist_for_each_entry(bts, &net->bts_list, list) {
+ bts_status[bts->nr] = 1;
+ /* call function on this BTS */
+ rc = cb_fn(bts, dec, r_state, priv);
+ if (rc < 0) {
+ append_fail(r_state, bts, -rc);
+ ret = -1;
+ } else
+ append_success(r_state, bts);
+ }
+ } else {
+ /* normal case: iterate over cell list */
+ llist_for_each_entry(ent, &cell_list->list, list) {
+ bool found_at_least_one = false;
+ /* find all matching BTSs for this entry */
+ llist_for_each_entry(bts, &net->bts_list, list) {
+ struct gsm0808_cell_id cell_id = {
+ .id_discr = cell_list->id_discr,
+ .id = ent->cell_id
+ };
+ if (!gsm_bts_matches_cell_id(bts, &cell_id))
+ continue;
+ found_at_least_one = true;
+ /* skip any BTSs which we've already processed */
+ if (bts_status[bts->nr])
+ continue;
+ bts_status[bts->nr] = 1;
+ /* call function on this BTS */
+ rc = cb_fn(bts, dec, r_state, priv);
+ if (rc < 0) {
+ append_fail(r_state, bts, -rc);
+ ret = -1;
+ } else
+ append_success(r_state, bts);
+ }
+ if (!found_at_least_one) {
+ struct osmo_cbsp_fail_ent *fent;
+ LOGP(DCBS, LOGL_NOTICE, "CBSP: Couldn't find a single matching BTS\n");
+ fent = talloc_zero(r_state, struct osmo_cbsp_fail_ent);
+ OSMO_ASSERT(fent);
+ fent->id_discr = cell_list->id_discr;
+ fent->cell_id = ent->cell_id;
+ llist_add_tail(&fent->list, &r_state->fail);
+ ret = -1;
+ }
+ }
+ }
+ return ret;
+}
+
+/*! Find an existing SMSCB message within given BTS.
+ * \param[in] chan_state BTS CBCH channel state
+ * \param[in] msg_id Message Id of to-be-found message
+ * \param[in] serial_nr Serial Number of to-be-found message
+ * \returns SMSCB message if found; NULL otherwise */
+struct bts_smscb_message *bts_find_smscb(struct bts_smscb_chan_state *chan_state,
+ uint16_t msg_id, uint16_t serial_nr)
+{
+ struct bts_smscb_message *smscb;
+
+ llist_for_each_entry(smscb, &chan_state->messages, list) {
+ if (smscb->input.msg_id == msg_id && smscb->input.serial_nr == serial_nr)
+ return smscb;
+ }
+ return NULL;
+}
+
+/*! create a new SMSCB message for specified BTS; don't link it yet.
+ * \param[in] bts BTS for which the SMSCB is to be allocated
+ * \param[in] wrepl CBSP write-replace message
+ * \returns callee-allocated SMSCB message filled with data from wrepl */
+static struct bts_smscb_message *bts_smscb_msg_from_wrepl(struct gsm_bts *bts,
+ const struct osmo_cbsp_write_replace *wrepl)
+{
+ struct bts_smscb_message *smscb = talloc_zero(bts, struct bts_smscb_message);
+ struct osmo_cbsp_content *cont;
+ int i;
+
+ if (!smscb)
+ return NULL;
+
+ OSMO_ASSERT(wrepl->is_cbs);
+
+ /* initialize all pages inside the message */
+ for (i = 0; i < ARRAY_SIZE(smscb->page); i++) {
+ struct bts_smscb_page *page = &smscb->page[i];
+ page->nr = i+1; /* page numbers are 1-based */
+ page->msg = smscb;
+ }
+
+ /* initialize "header" part */
+ smscb->input.msg_id = wrepl->msg_id;
+ smscb->input.serial_nr = wrepl->new_serial_nr;
+ smscb->input.category = wrepl->u.cbs.category;
+ smscb->input.rep_period = wrepl->u.cbs.rep_period;
+ smscb->input.num_bcast_req = wrepl->u.cbs.num_bcast_req;
+ smscb->input.dcs = wrepl->u.cbs.dcs;
+ smscb->num_pages = llist_count(&wrepl->u.cbs.msg_content);
+ if (smscb->num_pages > ARRAY_SIZE(smscb->page)) {
+ LOG_BTS(bts, DCBS, LOGL_ERROR, "SMSCB with too many pages (%u > %lu)\n",
+ smscb->num_pages, ARRAY_SIZE(smscb->page));
+ talloc_free(smscb);
+ return NULL;
+ }
+
+ i = 0;
+ llist_for_each_entry(cont, &wrepl->u.cbs.msg_content, list) {
+ struct gsm23041_msg_param_gsm *msg_param;
+ struct bts_smscb_page *page;
+ size_t bytes_used;
+
+ /* we have just ensured a few lines above that this cannot overflow */
+ page = &smscb->page[i++];
+ msg_param = (struct gsm23041_msg_param_gsm *) &page->data[0];
+
+ /* build 6 byte header according to TS 23.041 9.4.1.2 */
+ osmo_store16be(wrepl->new_serial_nr, &msg_param->serial_nr);
+ osmo_store16be(wrepl->msg_id, &msg_param->message_id);
+ msg_param->dcs = wrepl->u.cbs.dcs;
+ msg_param->page_param.num_pages = smscb->num_pages;
+ msg_param->page_param.page_nr = page->nr;
+
+ OSMO_ASSERT(cont->user_len <= ARRAY_SIZE(cont->data));
+ OSMO_ASSERT(cont->user_len <= ARRAY_SIZE(page->data) - sizeof(*msg_param));
+ memcpy(&msg_param->content, cont->data, cont->user_len);
+ bytes_used = sizeof(*msg_param) + cont->user_len;
+ /* compute number of valid blocks in page */
+ page->num_blocks = bytes_used / 22;
+ if (bytes_used % 22)
+ page->num_blocks += 1;
+ }
+
+ return smscb;
+}
+
+/*! remove a SMSCB message */
+void bts_smscb_del(struct bts_smscb_message *smscb, struct bts_smscb_chan_state *cstate,
+ const char *reason)
+{
+ struct bts_smscb_page **arr;
+ int rc;
+
+ LOG_BTS(cstate->bts, DCBS, LOGL_INFO, "%s Deleting %s (Reason: %s)\n",
+ bts_smscb_chan_state_name(cstate), bts_smscb_msg2str(smscb), reason);
+ llist_del(&smscb->list);
+
+ /* we must recompute the scheduler array here, as the old one will have pointers
+ * to the pages of the just-to-be-deleted message */
+ rc = bts_smscb_gen_sched_arr(cstate, &arr);
+ if (rc < 0) {
+ LOG_BTS(cstate->bts, DCBS, LOGL_ERROR, "Cannot generate new CBCH scheduler array after "
+ "removing message %s. WTF?\n", bts_smscb_msg2str(smscb));
+ /* we cannot free the message now, to ensure the page pointers in the old
+ * array are still valid. let's re-add it to keep things sane */
+ __bts_smscb_add(cstate, smscb);
+ } else {
+ /* success */
+ talloc_free(smscb);
+
+ /* replace array with new one */
+ talloc_free(cstate->sched_arr);
+ cstate->sched_arr = arr;
+ cstate->sched_arr_size = rc;
+ cstate->next_idx = 0;
+ }
+}
+
+
+/*********************************************************************************
+ * Transmit of CBSP to CBC
+ *********************************************************************************/
+
+/* transmit a CBSP RESTART message stating all message data was lost for entire BSS */
+int cbsp_tx_restart(struct bsc_cbc_link *cbc, bool is_emerg)
+{
+ struct osmo_cbsp_decoded *cbsp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_RESTART);
+
+ if (is_emerg)
+ cbsp->u.restart.bcast_msg_type = 0x01;
+ cbsp->u.restart.recovery_ind = 0x01; /* message data lost */
+ cbsp->u.restart.cell_list.id_discr = CELL_IDENT_BSS;
+
+ return cbsp_tx_decoded(cbc, cbsp);
+}
+
+/* transmit a CBSP KEEPALIVE COMPLETE to the CBC */
+static int tx_cbsp_keepalive_compl(struct bsc_cbc_link *cbc)
+{
+ struct osmo_cbsp_decoded *cbsp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_KEEP_ALIVE_COMPL);
+ return cbsp_tx_decoded(cbc, cbsp);
+}
+
+/*********************************************************************************
+ * Per-BTS Processing of CBSP from CBC, called via cbsp_per_bts()
+ *********************************************************************************/
+
+/*! Try to execute a write-replace operation; roll-back if it fails.
+ * \param[in] chan_state BTS CBCH channel state
+ * \param[in] extended_cbch Basic (false) or Extended (true) CBCH
+ * \param[in] new_msg New SMSCB message which should be added
+ * \param[in] exclude_msg Existing SMSCB message that shall be replaced (if possible). Can be NULL
+ * \return 0 on success; negative on error */
+static int bts_try_write_replace(struct bts_smscb_chan_state *chan_state,
+ struct bts_smscb_message *new_msg,
+ struct bts_smscb_message *exclude_msg,
+ struct response_state *r_state)
+{
+ struct bts_smscb_page **arr;
+ int rc;
+
+ if (exclude_msg) {
+ /* temporarily remove from list of SMSCB */
+ llist_del(&exclude_msg->list);
+ }
+ /* temporarily add new_msg to list of SMSCB */
+ __bts_smscb_add(chan_state, new_msg);
+
+ /* attempt to create scheduling array */
+ rc = bts_smscb_gen_sched_arr(chan_state, &arr);
+ if (rc < 0) {
+ /* it didn't work out; we couldn't schedule it */
+ /* remove the new message again */
+ llist_del(&new_msg->list);
+ /* up to the caller to free() it */
+ if (exclude_msg) {
+ /* re-add the temporarily removed message */
+ __bts_smscb_add(chan_state, new_msg);
+ }
+ return -1;
+ }
+
+ /* success! */
+ if (exclude_msg) {
+ LOG_BTS(chan_state->bts, DCBS, LOGL_INFO, "%s Replaced MsgId=0x%04x/Serial=0x%04x, "
+ "pages(%u -> %u), period(%u -> %u), num_bcast(%u -> %u)\n",
+ bts_smscb_chan_state_name(chan_state),
+ new_msg->input.msg_id, new_msg->input.serial_nr,
+ exclude_msg->num_pages, new_msg->num_pages,
+ exclude_msg->input.rep_period, new_msg->input.rep_period,
+ exclude_msg->input.num_bcast_req, new_msg->input.num_bcast_req);
+ append_bcast_compl(r_state, chan_state->bts, exclude_msg);
+ talloc_free(exclude_msg);
+ } else
+ LOG_BTS(chan_state->bts, DCBS, LOGL_INFO, "%s Added %s\n",
+ bts_smscb_chan_state_name(chan_state), bts_smscb_msg2str(new_msg));
+
+ /* replace array with new one */
+ talloc_free(chan_state->sched_arr);
+ chan_state->sched_arr = arr;
+ chan_state->sched_arr_size = rc;
+ chan_state->next_idx = 0;
+ return 0;
+}
+
+
+static int bts_rx_write_replace(struct gsm_bts *bts, const struct osmo_cbsp_decoded *dec,
+ struct response_state *r_state, void *priv)
+{
+ const struct osmo_cbsp_write_replace *wrepl = &dec->u.write_replace;
+ bool extended_cbch = wrepl->u.cbs.channel_ind;
+ struct bts_smscb_chan_state *chan_state = bts_get_smscb_chan(bts, extended_cbch);
+ struct bts_smscb_message *smscb;
+ int rc;
+
+ if (!wrepl->is_cbs) {
+ LOG_BTS(bts, DCBS, LOGL_ERROR, "(Primary) Emergency Message not supported\n");
+ return -CBSP_CAUSE_CB_NOT_SUPPORTED;
+ }
+
+ /* check if cell has a CBCH at all */
+ if (!gsm_bts_get_cbch(bts))
+ return -CBSP_CAUSE_CB_NOT_SUPPORTED;
+
+ /* check for duplicate */
+ if (bts_find_smscb(chan_state, wrepl->msg_id, wrepl->new_serial_nr))
+ return -CBSP_CAUSE_MSG_REF_ALREADY_USED;
+
+ if (!wrepl->old_serial_nr) { /* new message */
+ /* create new message */
+ smscb = bts_smscb_msg_from_wrepl(bts, wrepl);
+ if (!smscb)
+ return -CBSP_CAUSE_BSC_MEMORY_EXCEEDED;
+ /* check if scheduling permits this additional message */
+ rc = bts_try_write_replace(chan_state, smscb, NULL, r_state);
+ if (rc < 0) {
+ talloc_free(smscb);
+ return -CBSP_CAUSE_BSC_CAPACITY_EXCEEDED;
+ }
+ } else { /* modify / replace existing message */
+ struct bts_smscb_message *smscb_old;
+ /* find existing message */
+ smscb_old = bts_find_smscb(chan_state, wrepl->msg_id, *wrepl->old_serial_nr);
+ if (!smscb_old)
+ return -CBSP_CAUSE_MSG_REF_NOT_IDENTIFIED;
+ /* create new message */
+ smscb = bts_smscb_msg_from_wrepl(bts, wrepl);
+ if (!smscb)
+ return -CBSP_CAUSE_BSC_MEMORY_EXCEEDED;
+ /* check if scheduling permits this modified message */
+ rc = bts_try_write_replace(chan_state, smscb, smscb_old, r_state);
+ if (rc < 0) {
+ talloc_free(smscb);
+ return -CBSP_CAUSE_BSC_CAPACITY_EXCEEDED;
+ }
+ }
+ return 0;
+}
+
+static int bts_rx_kill(struct gsm_bts *bts, const struct osmo_cbsp_decoded *dec,
+ struct response_state *r_state, void *priv)
+{
+ const struct osmo_cbsp_kill *kill = &dec->u.kill;
+ struct bts_smscb_chan_state *chan_state;
+ struct bts_smscb_message *smscb;
+ bool extended = false;
+
+ if (kill->channel_ind && *kill->channel_ind == 0x01)
+ extended = true;
+ chan_state = bts_get_smscb_chan(bts, extended);
+
+ /* Find message by msg_id + old_serial_nr */
+ smscb = bts_find_smscb(chan_state, kill->msg_id, kill->old_serial_nr);
+ if (!smscb)
+ return -CBSP_CAUSE_MSG_REF_NOT_IDENTIFIED;
+
+ /* Remove it */
+ bts_smscb_del(smscb, chan_state, "KILL");
+ return 0;
+}
+
+static int bts_rx_reset(struct gsm_bts *bts, const struct osmo_cbsp_decoded *dec,
+ struct response_state *r_state, void *priv)
+{
+ struct bts_smscb_chan_state *chan_state;
+ struct bts_smscb_message *smscb, *smscb2;
+
+ /* remove all SMSCB from CBCH BASIC this BTS */
+ chan_state = bts_get_smscb_chan(bts, false);
+ llist_for_each_entry_safe(smscb, smscb2, &chan_state->messages, list)
+ bts_smscb_del(smscb, chan_state, "RESET");
+
+ /* remove all SMSCB from CBCH EXTENDED this BTS */
+ chan_state = bts_get_smscb_chan(bts, true);
+ llist_for_each_entry_safe(smscb, smscb2, &chan_state->messages, list)
+ bts_smscb_del(smscb, chan_state, "RESET");
+
+ return 0;
+}
+
+/*********************************************************************************
+ * Receive of CBSP from CBC
+ *********************************************************************************/
+
+static int cbsp_rx_write_replace(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded *dec)
+{
+ const struct osmo_cbsp_write_replace *wrepl = &dec->u.write_replace;
+ struct gsm_network *net = cbc->net;
+ struct response_state *r_state = talloc_zero(cbc, struct response_state);
+ struct osmo_cbsp_decoded *resp;
+ enum cbsp_channel_ind channel_ind;
+ int rc;
+
+ LOGP(DCBS, LOGL_INFO, "CBSP Rx WRITE_REPLACE (%s)\n", wrepl->is_cbs ? "CBS" : "EMERGENCY");
+
+ rc = cbsp_per_bts(net, r_state, &dec->u.write_replace.cell_list,
+ bts_rx_write_replace, dec, NULL);
+ /* generate response */
+ if (rc < 0) {
+ resp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_WRITE_REPLACE_FAIL);
+ struct osmo_cbsp_write_replace_failure *fail = &resp->u.write_replace_fail;
+ fail->msg_id = wrepl->msg_id;
+ fail->new_serial_nr = wrepl->new_serial_nr;
+ fail->old_serial_nr = wrepl->old_serial_nr;
+ llist_replace_head(&fail->fail_list, &r_state->fail);
+ fail->cell_list.id_discr = r_state->success.id_discr;
+ llist_replace_head(&fail->cell_list.list, &r_state->success.list);
+ if (wrepl->is_cbs) {
+ channel_ind = wrepl->u.cbs.channel_ind;
+ fail->channel_ind = &channel_ind;
+ }
+ if (wrepl->old_serial_nr) {
+ fail->num_compl_list.id_discr = r_state->num_completed.id_discr;
+ llist_replace_head(&fail->num_compl_list.list, &r_state->num_completed.list);
+ }
+ } else {
+ resp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_WRITE_REPLACE_COMPL);
+ struct osmo_cbsp_write_replace_complete *compl = &resp->u.write_replace_compl;
+ compl->msg_id = wrepl->msg_id;
+ compl->new_serial_nr = wrepl->new_serial_nr;
+ compl->old_serial_nr = wrepl->old_serial_nr;
+ compl->cell_list.id_discr = r_state->success.id_discr;
+ llist_replace_head(&compl->cell_list.list, &r_state->success.list);
+ if (wrepl->is_cbs) {
+ channel_ind = wrepl->u.cbs.channel_ind;
+ compl->channel_ind = &channel_ind;
+ }
+ if (wrepl->old_serial_nr) {
+ compl->num_compl_list.id_discr = r_state->num_completed.id_discr;
+ llist_replace_head(&compl->num_compl_list.list, &r_state->num_completed.list);
+ }
+ }
+
+ cbsp_tx_decoded(cbc, resp);
+ talloc_free(r_state);
+ return rc;
+}
+
+static int cbsp_rx_keep_alive(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded *dec)
+{
+ LOGP(DCBS, LOGL_DEBUG, "CBSP Rx KEEP_ALIVE\n");
+
+ /* FIXME: repetition period */
+ return tx_cbsp_keepalive_compl(cbc);
+}
+
+static int cbsp_rx_kill(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded *dec)
+{
+ const struct osmo_cbsp_kill *kill = &dec->u.kill;
+ struct gsm_network *net = cbc->net;
+ struct response_state *r_state = talloc_zero(cbc, struct response_state);
+ struct osmo_cbsp_decoded *resp;
+ int rc;
+
+ LOGP(DCBS, LOGL_DEBUG, "CBSP Rx KILL\n");
+
+ rc = cbsp_per_bts(net, r_state, &dec->u.kill.cell_list, bts_rx_kill, dec, NULL);
+ if (rc < 0) {
+ resp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_KILL_FAIL);
+ struct osmo_cbsp_kill_failure *fail = &resp->u.kill_fail;
+ fail->msg_id = kill->msg_id;
+ fail->old_serial_nr = kill->old_serial_nr;
+ fail->channel_ind = kill->channel_ind;
+ llist_replace_head(&fail->fail_list, &r_state->fail);
+
+ fail->cell_list.id_discr = r_state->success.id_discr;
+ llist_replace_head(&fail->cell_list.list, &r_state->success.list);
+
+ fail->num_compl_list.id_discr = r_state->num_completed.id_discr;
+ llist_replace_head(&fail->num_compl_list.list, &r_state->num_completed.list);
+ } else {
+ resp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_KILL_COMPL);
+ struct osmo_cbsp_kill_complete *compl = &resp->u.kill_compl;
+ compl->msg_id = kill->msg_id;
+ compl->old_serial_nr = kill->old_serial_nr;
+ compl->channel_ind = kill->channel_ind;
+
+ compl->cell_list.id_discr = r_state->success.id_discr;
+ llist_replace_head(&compl->cell_list.list, &r_state->success.list);
+
+ compl->num_compl_list.id_discr = r_state->num_completed.id_discr;
+ llist_replace_head(&compl->num_compl_list.list, &r_state->num_completed.list);
+ }
+
+ cbsp_tx_decoded(cbc, resp);
+ talloc_free(r_state);
+ return rc;
+}
+
+static int cbsp_rx_reset(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded *dec)
+{
+ struct gsm_network *net = cbc->net;
+ struct response_state *r_state = talloc_zero(cbc, struct response_state);
+ struct osmo_cbsp_decoded *resp;
+ int rc;
+
+ LOGP(DCBS, LOGL_DEBUG, "CBSP Rx RESET\n");
+
+ rc = cbsp_per_bts(net, r_state, &dec->u.reset.cell_list, bts_rx_reset, dec, NULL);
+ if (rc < 0) {
+ resp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_RESET_FAIL);
+ struct osmo_cbsp_reset_failure *fail = &resp->u.reset_fail;
+ llist_replace_head(&fail->fail_list, &r_state->fail);
+
+ fail->cell_list.id_discr = r_state->success.id_discr;
+ llist_replace_head(&fail->cell_list.list, &r_state->success.list);
+ } else {
+ resp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_RESET_COMPL);
+ struct osmo_cbsp_reset_complete *compl = &resp->u.reset_compl;
+ if (dec->u.reset.cell_list.id_discr == CELL_IDENT_BSS) {
+ /* replace the list of individual cell identities with CELL_IDENT_BSS */
+ compl->cell_list.id_discr = CELL_IDENT_BSS;
+ /* no need to free success_list entries, hierarchical talloc works */
+ } else {
+ compl->cell_list.id_discr = r_state->success.id_discr;
+ llist_replace_head(&compl->cell_list.list, &r_state->success.list);
+ }
+ }
+ cbsp_tx_decoded(cbc, resp);
+ talloc_free(r_state);
+ return rc;
+}
+
+
+/*! process an incoming, already decoded CBSP message from the CBC.
+ * \param[in] cbc link to the CBC
+ * \param[in] dec decoded CBSP message structure. Ownership not transferred.
+ * \returns 0 on success; negative on error. */
+int cbsp_rx_decoded(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded *dec)
+{
+ int rc = -1;
+
+ switch (dec->msg_type) {
+ case CBSP_MSGT_WRITE_REPLACE: /* create or modify message */
+ rc = cbsp_rx_write_replace(cbc, dec);
+ break;
+ case CBSP_MSGT_KEEP_ALIVE: /* solicit an acknowledgement */
+ rc = cbsp_rx_keep_alive(cbc, dec);
+ break;
+ case CBSP_MSGT_KILL: /* remove message */
+ rc = cbsp_rx_kill(cbc, dec);
+ break;
+ case CBSP_MSGT_RESET: /* stop broadcasting of all messages */
+ rc = cbsp_rx_reset(cbc, dec);
+ break;
+ case CBSP_MSGT_LOAD_QUERY:
+ case CBSP_MSGT_MSG_STATUS_QUERY:
+ case CBSP_MSGT_SET_DRX:
+ LOGP(DCBS, LOGL_ERROR, "Received Unimplemented CBSP Message Type %s",
+ get_value_string(cbsp_msg_type_names, dec->msg_type));
+ /* we should implement those eventually */
+ break;
+ default:
+ LOGP(DCBS, LOGL_ERROR, "Received Unknown/Unexpected CBSP Message Type %s",
+ get_value_string(cbsp_msg_type_names, dec->msg_type));
+ break;
+ }
+ return rc;
+}
+
+/*********************************************************************************
+ * VTY Interface (Introspection)
+ *********************************************************************************/
+
+static void vty_dump_smscb_chan_state(struct vty *vty, const struct bts_smscb_chan_state *cs)
+{
+ const struct bts_smscb_message *sm;
+
+ vty_out(vty, "%s CBCH:%s", cs == &cs->bts->cbch_basic ? "BASIC" : "EXTENDED", VTY_NEWLINE);
+
+ vty_out(vty, " MsgId | SerNo | Pg | Category | Perd | #Tx | #Req | DCS%s", VTY_NEWLINE);
+ vty_out(vty, "-------|-------|----|---------------|------|------|------|----%s", VTY_NEWLINE);
+ llist_for_each_entry(sm, &cs->messages, list) {
+ vty_out(vty, " %04x | %04x | %2u | %13s | %4u | %4u | %4u | %02x%s",
+ sm->input.msg_id, sm->input.serial_nr, sm->num_pages,
+ get_value_string(cbsp_category_names, sm->input.category),
+ sm->input.rep_period, sm->bcast_count, sm->input.num_bcast_req,
+ sm->input.dcs, VTY_NEWLINE);
+ }
+ vty_out(vty, "%s", VTY_NEWLINE);
+}
+
+DEFUN(bts_show_cbs, bts_show_cbs_cmd,
+ "show bts <0-255> smscb [(basic|extended)]",
+ SHOW_STR "Display information about a BTS\n" "BTS number\n"
+ "SMS Cell Broadcast State\n"
+ "Show only information related to CBCH BASIC\n"
+ "Show only information related to CBCH EXTENDED\n")
+{
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ int bts_nr = atoi(argv[0]);
+ struct gsm_bts *bts;
+
+ if (bts_nr >= net->num_bts) {
+ vty_out(vty, "%% can't find BTS '%s'%s", argv[0], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ bts = gsm_bts_num(net, bts_nr);
+
+ if (argc < 2 || !strcmp(argv[1], "basic"))
+ vty_dump_smscb_chan_state(vty, &bts->cbch_basic);
+ if (argc < 2 || !strcmp(argv[1], "extended"))
+ vty_dump_smscb_chan_state(vty, &bts->cbch_extended);
+
+ return CMD_SUCCESS;
+}
+
+void smscb_vty_init(void)
+{
+ install_element_ve(&bts_show_cbs_cmd);
+}