diff options
Diffstat (limited to 'src/osmo-bsc/smscb.c')
-rw-r--r-- | src/osmo-bsc/smscb.c | 823 |
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(¢->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(¢->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); +} |