diff --git a/include/osmocom/cbc/smscb_peer_fsm.h b/include/osmocom/cbc/smscb_peer_fsm.h index 7bf1c54..7ae8968 100644 --- a/include/osmocom/cbc/smscb_peer_fsm.h +++ b/include/osmocom/cbc/smscb_peer_fsm.h @@ -35,3 +35,6 @@ enum smscb_peer_fsm_event { }; extern const struct value_string smscb_peer_fsm_event_names[]; + +extern struct osmo_fsm cbsp_smscb_peer_fsm; +extern struct osmo_fsm sbcap_smscb_peer_fsm; diff --git a/src/Makefile.am b/src/Makefile.am index de40dea..ade53d0 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -18,14 +18,16 @@ osmo_cbc_SOURCES = \ cbsp_link.c \ cbsp_link_fsm.c \ cbsp_msg.c \ + cbsp_smscb_peer_fsm.c \ rest_api.c \ charset.c \ rest_it_op.c \ sbcap_msg.c \ sbcap_link.c \ sbcap_link_fsm.c \ - smscb_peer_fsm.c \ + sbcap_smscb_peer_fsm.c \ smscb_message_fsm.c \ + smscb_peer_fsm.c \ $(NULL) osmo_cbc_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) \ diff --git a/src/cbsp_smscb_peer_fsm.c b/src/cbsp_smscb_peer_fsm.c new file mode 100644 index 0000000..872008c --- /dev/null +++ b/src/cbsp_smscb_peer_fsm.c @@ -0,0 +1,563 @@ +/* SMSCB Peer FSM: Represents state of one SMSCB for one peer (BSC) */ + +/* This FSM exists per tuple of (message, bsc peer) */ + +/* (C) 2019 by Harald Welte + * All Rights Reserved + * + * SPDX-License-Identifier: AGPL-3.0+ + * + * 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 . + * + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#define S(x) (1 << (x)) + +/*********************************************************************** + * Helper functions + ***********************************************************************/ + +/* covert TS 08.08 Cell Identity value to CBC internal type */ +static enum cbc_cell_id_type cci_discr_from_cell_id(enum CELL_IDENT id_discr) +{ + switch (id_discr) { + case CELL_IDENT_NO_CELL: + return CBC_CELL_ID_NONE; + case CELL_IDENT_WHOLE_GLOBAL: + return CBC_CELL_ID_CGI; + case CELL_IDENT_LAC_AND_CI: + return CBC_CELL_ID_LAC_CI; + case CELL_IDENT_CI: + return CBC_CELL_ID_CI; + case CELL_IDENT_LAI: + return CBC_CELL_ID_LAI; + case CELL_IDENT_LAC: + return CBC_CELL_ID_LAC; + case CELL_IDENT_BSS: + return CBC_CELL_ID_BSS; + default: + return -1; + } +} + +/* covert CBC internal type to TS 08.08 Cell Identity */ +static enum CELL_IDENT cell_id_from_ccid_discr(enum cbc_cell_id_type in) +{ + switch (in) { + case CBC_CELL_ID_NONE: + return CELL_IDENT_NO_CELL; + case CBC_CELL_ID_CGI: + return CELL_IDENT_WHOLE_GLOBAL; + case CBC_CELL_ID_LAC_CI: + return CELL_IDENT_LAC_AND_CI; + case CBC_CELL_ID_CI: + return CELL_IDENT_CI; + case CBC_CELL_ID_LAI: + return CELL_IDENT_LAI; + case CBC_CELL_ID_LAC: + return CELL_IDENT_LAC; + case CBC_CELL_ID_BSS: + return CELL_IDENT_BSS; + default: + return -1; + } +} + +/* convert TS 08.08 Cell Identifier Union to CBC internal type */ +static void cci_from_cbsp(struct cbc_cell_id *cci, enum CELL_IDENT id_discr, + const union gsm0808_cell_id_u *u) +{ + cci->id_discr = cci_discr_from_cell_id(id_discr); + + switch (id_discr) { + case CELL_IDENT_NO_CELL: + break; + case CELL_IDENT_WHOLE_GLOBAL: + cci->u.cgi = u->global; + break; + case CELL_IDENT_LAC_AND_CI: + cci->u.lac_and_ci = u->lac_and_ci; + break; + case CELL_IDENT_CI: + cci->u.ci = u->ci; + break; + case CELL_IDENT_LAI: + cci->u.lai = u->lai_and_lac; + break; + case CELL_IDENT_LAC: + cci->u.lac = u->lac; + break; + case CELL_IDENT_BSS: + break; + default: + break; + } +} + +/* convert TS 08.08 Cell Identifier Union to CBC internal type */ +static void cbsp_from_cci(union gsm0808_cell_id_u *u, const struct cbc_cell_id *cci) +{ + switch (cci->id_discr) { + case CBC_CELL_ID_NONE: + break; + case CBC_CELL_ID_CGI: + u->global = cci->u.cgi; + printf("u->gobal: %s\n", osmo_hexdump((uint8_t *) &u->global, sizeof(u->global))); + break; + case CBC_CELL_ID_LAC_CI: + u->lac_and_ci = cci->u.lac_and_ci; + break; + case CBC_CELL_ID_CI: + u->ci = cci->u.ci; + break; + case CBC_CELL_ID_LAI: + u->lai_and_lac = cci->u.lai; + break; + case CBC_CELL_ID_LAC: + u->lac = cci->u.lac; + break; + case CBC_CELL_ID_BSS: + break; + default: + OSMO_ASSERT(0); + } +} + +/* read a single osmo_cbsp_num_compl_ent and add it to cbc_message_peer */ +static void cci_from_cbsp_compl_ent(struct cbc_message_peer *mp, + struct osmo_cbsp_num_compl_ent *ce, enum CELL_IDENT id_discr) +{ + struct cbc_cell_id *cci; + + cci = NULL; // FIXME: lookup + if (!cci) { + cci = talloc_zero(mp, struct cbc_cell_id); + if (!cci) + return; + llist_add_tail(&cci->list, &mp->num_compl_list); + } + cci_from_cbsp(cci, id_discr, &ce->cell_id); + cci->num_compl.num_compl += ce->num_compl; + cci->num_compl.num_bcast_info += ce->num_bcast_info; +} +static void msg_peer_append_cbsp_compl(struct cbc_message_peer *mp, + struct osmo_cbsp_num_compl_list *nclist) +{ + struct osmo_cbsp_num_compl_ent *ce; + + llist_for_each_entry(ce, &nclist->list, list) + cci_from_cbsp_compl_ent(mp, ce, nclist->id_discr); +} + +/* read a single osmo_cbsp_cell_ent and add it to cbc_message_peer */ +static void cci_from_cbsp_cell_ent(struct cbc_message_peer *mp, + struct osmo_cbsp_cell_ent *ce, enum CELL_IDENT id_discr) +{ + struct cbc_cell_id *cci; + + cci = NULL; // FIXME: lookup + if (!cci) { + cci = talloc_zero(mp, struct cbc_cell_id); + if (!cci) + return; + llist_add_tail(&cci->list, &mp->cell_list); + } + cci_from_cbsp(cci, id_discr, &ce->cell_id); +} +static void msg_peer_append_cbsp_cell(struct cbc_message_peer *mp, + struct osmo_cbsp_cell_list *clist) +{ + struct osmo_cbsp_cell_ent *ce; + + llist_for_each_entry(ce, &clist->list, list) + cci_from_cbsp_cell_ent(mp, ce, clist->id_discr); +} + +/* read a single osmo_cbsp_fail_ent and add it to cbc_message_peer */ +static void cci_from_cbsp_fail_ent(struct cbc_message_peer *mp, + struct osmo_cbsp_fail_ent *fe) +{ + struct cbc_cell_id *cci; + cci = NULL; // lookup */ + if (!cci) { + cci = talloc_zero(mp, struct cbc_cell_id); + if (!cci) + return; + llist_add_tail(&cci->list, &mp->fail_list); + } + cci->id_discr = cci_discr_from_cell_id(fe->id_discr); + cci->fail.cause = fe->cause; +} +static void msg_peer_append_cbsp_fail(struct cbc_message_peer *mp, struct llist_head *flist) +{ + struct osmo_cbsp_fail_ent *fe; + + llist_for_each_entry(fe, flist, list) + cci_from_cbsp_fail_ent(mp, fe); +} + +/* append all cells from cbc_message_peer to given CBSP cell_list */ +static void cbsp_append_cell_list(struct osmo_cbsp_cell_list *out, void *ctx, + const struct cbc_message_peer *mp) +{ + struct cbc_cell_id *cci; + enum cbc_cell_id_type id_discr = CBC_CELL_ID_NONE; + + llist_for_each_entry(cci, &mp->cell_list, list) { + struct osmo_cbsp_cell_ent *ent; + + if (id_discr == CBC_CELL_ID_NONE) + id_discr = cci->id_discr; + else if (id_discr != cci->id_discr) { + LOGPFSML(mp->fi, LOGL_ERROR, "Cannot encode CBSP cell_list as not all " + "entries are of same type (%u != %u)\n", id_discr, cci->id_discr); + continue; + } + ent = talloc_zero(ctx, struct osmo_cbsp_cell_ent); + OSMO_ASSERT(ent); + cbsp_from_cci(&ent->cell_id, cci); + llist_add_tail(&ent->list, &out->list); + } + out->id_discr = cell_id_from_ccid_discr(id_discr); +} + +/*********************************************************************** + * actual FSM + ***********************************************************************/ + +static void smscb_p_fsm_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct cbc_message_peer *mp = (struct cbc_message_peer *) fi->priv; + int rc; + + switch (event) { + case SMSCB_PEER_E_CREATE: + /* send it to peer */ + rc = peer_new_cbc_message(mp->peer, mp->cbcmsg); + if (rc == 0) { + /* wait for peers' response */ + osmo_fsm_inst_state_chg(fi, SMSCB_S_WAIT_WRITE_ACK, 10, + T_WAIT_WRITE_ACK); + } + break; + default: + OSMO_ASSERT(0); + } +} + +static void smscb_p_fsm_wait_write_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct cbc_message_peer *mp = (struct cbc_message_peer *) fi->priv; + struct osmo_cbsp_decoded *dec = NULL; + + switch (event) { + case SMSCB_PEER_E_CBSP_WRITE_ACK: + dec = data; + msg_peer_append_cbsp_compl(mp, &dec->u.write_replace_compl.num_compl_list); + msg_peer_append_cbsp_cell(mp, &dec->u.write_replace_compl.cell_list); + osmo_fsm_inst_state_chg(fi, SMSCB_S_ACTIVE, 0, 0); + /* Signal parent fsm about completion */ + osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_MSG_E_WRITE_ACK, mp); + break; + case SMSCB_PEER_E_CBSP_WRITE_NACK: + dec = data; + msg_peer_append_cbsp_compl(mp, &dec->u.write_replace_fail.num_compl_list); + msg_peer_append_cbsp_cell(mp, &dec->u.write_replace_fail.cell_list); + msg_peer_append_cbsp_fail(mp, &dec->u.write_replace_fail.fail_list); + osmo_fsm_inst_state_chg(fi, SMSCB_S_ACTIVE, 0, 0); + /* Signal parent fsm about completion */ + osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_MSG_E_WRITE_NACK, mp); + break; + default: + OSMO_ASSERT(0); + } +} + +static void smscb_p_fsm_active(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct cbc_message_peer *mp = (struct cbc_message_peer *) fi->priv; + struct osmo_cbsp_decoded *cbsp; + + switch (event) { + case SMSCB_PEER_E_REPLACE: /* send WRITE-REPLACE to BSC */ + cbsp = osmo_cbsp_decoded_alloc(mp->peer, CBSP_MSGT_WRITE_REPLACE); + OSMO_ASSERT(cbsp); + cbsp->u.write_replace.msg_id = mp->cbcmsg->msg.message_id; + cbsp->u.write_replace.old_serial_nr = &mp->cbcmsg->msg.serial_nr; + //cbsp->u.write_replace.new_serial_nr + /* TODO: we assume that the replace will always affect all original cells */ + cbsp_append_cell_list(&cbsp->u.write_replace.cell_list, cbsp, mp); + // TODO: ALL OTHER DATA + cbc_cbsp_link_tx(mp->peer->link.cbsp, cbsp); + osmo_fsm_inst_state_chg(fi, SMSCB_S_WAIT_REPLACE_ACK, 10, T_WAIT_REPLACE_ACK); + break; + case SMSCB_PEER_E_STATUS: /* send MSG-STATUS-QUERY to BSC */ + cbsp = osmo_cbsp_decoded_alloc(mp->peer, CBSP_MSGT_MSG_STATUS_QUERY); + OSMO_ASSERT(cbsp); + cbsp->u.msg_status_query.msg_id = mp->cbcmsg->msg.message_id; + cbsp->u.msg_status_query.old_serial_nr = mp->cbcmsg->msg.serial_nr; + cbsp_append_cell_list(&cbsp->u.msg_status_query.cell_list, cbsp, mp); + cbsp->u.msg_status_query.channel_ind = CBSP_CHAN_IND_BASIC; + cbc_cbsp_link_tx(mp->peer->link.cbsp, cbsp); + osmo_fsm_inst_state_chg(fi, SMSCB_S_WAIT_STATUS_ACK, 10, T_WAIT_STATUS_ACK); + break; + default: + OSMO_ASSERT(0); + } +} + +static void smscb_p_fsm_wait_status_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct cbc_message_peer *mp = (struct cbc_message_peer *) fi->priv; + struct osmo_cbsp_decoded *dec = NULL; + + switch (event) { + case SMSCB_PEER_E_CBSP_STATUS_ACK: + dec = data; + msg_peer_append_cbsp_compl(mp, &dec->u.msg_status_query_compl.num_compl_list); + osmo_fsm_inst_state_chg(fi, SMSCB_S_ACTIVE, 0, 0); + /* Signal parent fsm about completion */ + osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_MSG_E_STATUS_ACK, mp); + break; + case SMSCB_PEER_E_CBSP_STATUS_NACK: + dec = data; + msg_peer_append_cbsp_compl(mp, &dec->u.msg_status_query_fail.num_compl_list); + msg_peer_append_cbsp_fail(mp, &dec->u.msg_status_query_fail.fail_list); + osmo_fsm_inst_state_chg(fi, SMSCB_S_ACTIVE, 0, 0); + /* Signal parent fsm about completion */ + osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_MSG_E_STATUS_NACK, mp); + break; + default: + OSMO_ASSERT(0); + } +} + + +static void smscb_p_fsm_wait_replace_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct cbc_message_peer *mp = (struct cbc_message_peer *) fi->priv; + struct osmo_cbsp_decoded *dec = NULL; + + switch (event) { + case SMSCB_PEER_E_CBSP_REPLACE_ACK: + dec = data; + msg_peer_append_cbsp_compl(mp, &dec->u.write_replace_compl.num_compl_list); + msg_peer_append_cbsp_cell(mp, &dec->u.write_replace_compl.cell_list); + osmo_fsm_inst_state_chg(fi, SMSCB_S_ACTIVE, 0, 0); + /* Signal parent fsm about completion */ + osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_MSG_E_REPLACE_ACK, mp); + break; + case SMSCB_PEER_E_CBSP_REPLACE_NACK: + dec = data; + msg_peer_append_cbsp_compl(mp, &dec->u.write_replace_fail.num_compl_list); + msg_peer_append_cbsp_cell(mp, &dec->u.write_replace_fail.cell_list); + msg_peer_append_cbsp_fail(mp, &dec->u.write_replace_fail.fail_list); + osmo_fsm_inst_state_chg(fi, SMSCB_S_ACTIVE, 0, 0); + /* Signal parent fsm about completion */ + osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_MSG_E_REPLACE_NACK, mp); + break; + default: + OSMO_ASSERT(0); + } +} + +static void smscb_p_fsm_wait_delete_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct cbc_message_peer *mp = (struct cbc_message_peer *) fi->priv; + struct osmo_cbsp_decoded *dec = NULL; + + switch (event) { + case SMSCB_PEER_E_CBSP_DELETE_ACK: + dec = data; + /* append results */ + msg_peer_append_cbsp_compl(mp, &dec->u.kill_compl.num_compl_list); + msg_peer_append_cbsp_cell(mp, &dec->u.kill_compl.cell_list); + osmo_fsm_inst_state_chg(fi, SMSCB_S_DELETED, 0, 0); + /* Signal parent fsm about completion */ + osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_MSG_E_DELETE_ACK, mp); + break; + case SMSCB_PEER_E_CBSP_DELETE_NACK: + dec = data; + /* append results */ + msg_peer_append_cbsp_compl(mp, &dec->u.kill_fail.num_compl_list); + msg_peer_append_cbsp_cell(mp, &dec->u.kill_fail.cell_list); + msg_peer_append_cbsp_fail(mp, &dec->u.kill_fail.fail_list); + osmo_fsm_inst_state_chg(fi, SMSCB_S_DELETED, 0, 0); + /* Signal parent fsm about completion */ + osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_MSG_E_DELETE_NACK, mp); + break; + default: + OSMO_ASSERT(0); + } +} + + + +static int smscb_p_fsm_timer_cb(struct osmo_fsm_inst *fi) +{ + switch (fi->T) { + case T_WAIT_WRITE_ACK: + osmo_fsm_inst_state_chg(fi, SMSCB_S_ACTIVE, 0, 0); + osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_PEER_E_CBSP_WRITE_NACK, NULL); + break; + case T_WAIT_REPLACE_ACK: + osmo_fsm_inst_state_chg(fi, SMSCB_S_ACTIVE, 0, 0); + osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_MSG_E_REPLACE_NACK, NULL); + break; + case T_WAIT_STATUS_ACK: + osmo_fsm_inst_state_chg(fi, SMSCB_S_ACTIVE, 0, 0); + osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_MSG_E_STATUS_NACK, NULL); + break; + case T_WAIT_DELETE_ACK: + osmo_fsm_inst_state_chg(fi, SMSCB_S_DELETED, 0, 0); + osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_PEER_E_CBSP_DELETE_NACK, NULL); + break; + default: + OSMO_ASSERT(0); + } + return 0; +} + +static void smscb_p_fsm_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct cbc_message_peer *mp = (struct cbc_message_peer *) fi->priv; + struct osmo_cbsp_decoded *cbsp; + + switch (event) { + case SMSCB_PEER_E_DELETE: /* send KILL to BSC */ + switch (fi->state) { + case SMSCB_S_DELETED: + case SMSCB_S_INIT: + LOGPFSML(fi, LOGL_ERROR, "Event %s not permitted\n", + osmo_fsm_event_name(fi->fsm, event)); + return; + default: + break; + } + cbsp = osmo_cbsp_decoded_alloc(mp->peer, CBSP_MSGT_KILL); + OSMO_ASSERT(cbsp); + cbsp->u.kill.msg_id = mp->cbcmsg->msg.message_id; + cbsp->u.kill.old_serial_nr = mp->cbcmsg->msg.serial_nr; + /* TODO: we assume that the delete will always affect all original cells */ + cbsp_append_cell_list(&cbsp->u.kill.cell_list, cbsp, mp); + if (!mp->cbcmsg->msg.is_etws) { + /* Channel Indication IE is only present in CBS, not in ETWS! */ + cbsp->u.kill.channel_ind = talloc_zero(cbsp, enum cbsp_channel_ind); + OSMO_ASSERT(cbsp->u.kill.channel_ind); + *(cbsp->u.kill.channel_ind) = CBSP_CHAN_IND_BASIC; + } + cbc_cbsp_link_tx(mp->peer->link.cbsp, cbsp); + osmo_fsm_inst_state_chg(fi, SMSCB_S_WAIT_DELETE_ACK, 10, T_WAIT_DELETE_ACK); + break; + default: + OSMO_ASSERT(0); + } +} + +static void smscb_p_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) +{ + struct cbc_message_peer *mp = (struct cbc_message_peer *) fi->priv; + llist_del(&mp->list); + /* memory of mp is child of fi and hence automatically free'd */ +} + +static const struct osmo_fsm_state smscb_p_fsm_states[] = { + [SMSCB_S_INIT] = { + .name = "INIT", + .in_event_mask = S(SMSCB_PEER_E_CREATE), + .out_state_mask = S(SMSCB_S_WAIT_WRITE_ACK), + .action = smscb_p_fsm_init, + }, + [SMSCB_S_WAIT_WRITE_ACK] = { + .name = "WAIT_WRITE_ACK", + .in_event_mask = S(SMSCB_PEER_E_CBSP_WRITE_ACK) | + S(SMSCB_PEER_E_CBSP_WRITE_NACK), + .out_state_mask = S(SMSCB_S_ACTIVE) | + S(SMSCB_S_WAIT_DELETE_ACK), + .action = smscb_p_fsm_wait_write_ack, + }, + [SMSCB_S_ACTIVE] = { + .name = "ACTIVE", + .in_event_mask = S(SMSCB_PEER_E_REPLACE) | + S(SMSCB_PEER_E_STATUS), + .out_state_mask = S(SMSCB_S_WAIT_REPLACE_ACK) | + S(SMSCB_S_WAIT_STATUS_ACK) | + S(SMSCB_S_WAIT_DELETE_ACK), + .action = smscb_p_fsm_active, + }, + [SMSCB_S_WAIT_STATUS_ACK] = { + .name = "WAIT_STATUS_ACK", + .in_event_mask = S(SMSCB_PEER_E_CBSP_STATUS_ACK) | + S(SMSCB_PEER_E_CBSP_STATUS_NACK), + .out_state_mask = S(SMSCB_S_ACTIVE) | + S(SMSCB_S_WAIT_DELETE_ACK), + .action = smscb_p_fsm_wait_status_ack, + }, + [SMSCB_S_WAIT_REPLACE_ACK] = { + .name = "WAIT_REPLACE_ACK", + .in_event_mask = S(SMSCB_PEER_E_CBSP_REPLACE_ACK) | + S(SMSCB_PEER_E_CBSP_REPLACE_NACK), + .out_state_mask = S(SMSCB_S_ACTIVE) | + S(SMSCB_S_WAIT_DELETE_ACK), + .action = smscb_p_fsm_wait_replace_ack, + }, + [SMSCB_S_WAIT_DELETE_ACK] = { + .name = "WAIT_DELETE_ACK", + .in_event_mask = S(SMSCB_PEER_E_CBSP_DELETE_ACK) | + S(SMSCB_PEER_E_CBSP_DELETE_NACK), + .out_state_mask = S(SMSCB_S_DELETED), + .action = smscb_p_fsm_wait_delete_ack, + }, + [SMSCB_S_DELETED] = { + .name = "DELETED", + }, +}; + +struct osmo_fsm cbsp_smscb_peer_fsm = { + .name = "SMSCB-PEER-CBSP", + .states = smscb_p_fsm_states, + .num_states = ARRAY_SIZE(smscb_p_fsm_states), + .allstate_event_mask = S(SMSCB_PEER_E_DELETE), + .allstate_action = smscb_p_fsm_allstate, + .timer_cb = smscb_p_fsm_timer_cb, + .log_subsys = DCBSP, + .event_names = smscb_peer_fsm_event_names, + .cleanup = smscb_p_fsm_cleanup, +}; + +static __attribute__((constructor)) void on_dso_load_smscb_p_fsm(void) +{ + OSMO_ASSERT(osmo_fsm_register(&cbsp_smscb_peer_fsm) == 0); +} diff --git a/src/sbcap_smscb_peer_fsm.c b/src/sbcap_smscb_peer_fsm.c new file mode 100644 index 0000000..706a98f --- /dev/null +++ b/src/sbcap_smscb_peer_fsm.c @@ -0,0 +1,370 @@ +/* SMSCB Peer FSM: Represents state of one SMSCB for one peer (MME) */ + +/* This FSM exists per tuple of (message, mme peer) */ + +/* (C) 2019 by Harald Welte + * All Rights Reserved + * + * SPDX-License-Identifier: AGPL-3.0+ + * + * 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 . + * + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#define S(x) (1 << (x)) + +/*********************************************************************** + * Helper functions + ***********************************************************************/ + +/* append SBcAP cells to msg_peer compl list */ +void msg_peer_append_compl_sbcap_bcast_area_list(struct cbc_message_peer *mp, + const SBcAP_Broadcast_Scheduled_Area_List_t *bcast) +{ + SBcAP_CellId_Broadcast_List_t *cell_id_bscat = bcast->cellId_Broadcast_List; + A_SEQUENCE_OF(struct SBcAP_CellId_Broadcast_List_Item) *as_cell_id_bcast; + SBcAP_CellId_Broadcast_List_Item_t *it; + unsigned int i; + + if (!cell_id_bscat) + return; + + as_cell_id_bcast = (void *) &cell_id_bscat->list; + for (i = 0; i < as_cell_id_bcast->count; i++) { + it = (SBcAP_CellId_Broadcast_List_Item_t *)(as_cell_id_bcast->array[i]); + OSMO_ASSERT(it); + struct cbc_cell_id *cci = NULL; // FIXME: lookup + if (!cci) { + cci = talloc_zero(mp, struct cbc_cell_id); + if (!cci) + return; + llist_add_tail(&cci->list, &mp->num_compl_list); + } + cci_from_sbcap_bcast_cell_id(cci, it); + LOGPFSML(mp->fi, LOGL_DEBUG, "Appending CellId %s to Broadcast Completed list\n", + cbc_cell_id2str(cci)); + cci->num_compl.num_compl += 1; + cci->num_compl.num_bcast_info += 1; + } +} + +/* append SBcAP cells to msg_peer fail list */ +void msg_peer_append_fail_sbcap_tai_list(struct cbc_message_peer *mp, + const SBcAP_List_of_TAIs_t *tais) +{ + A_SEQUENCE_OF(List_of_TAIs__Member) *as_tais = (void *)&tais->list; + List_of_TAIs__Member *it; + unsigned int i; + + for (i = 0; i < as_tais->count; i++) { + it = (List_of_TAIs__Member *)(as_tais->array[i]); + OSMO_ASSERT(it); + struct cbc_cell_id *cci = NULL; // FIXME: lookup + if (!cci) { + cci = talloc_zero(mp, struct cbc_cell_id); + if (!cci) + return; + llist_add_tail(&cci->list, &mp->fail_list); + } + cci_from_sbcap_tai(cci, &it->tai); + cci->fail.cause = SBcAP_Cause_tracking_area_not_valid; + LOGPFSML(mp->fi, LOGL_DEBUG, "Appending CellId %s (cause: %s) to Failed list\n", + cbc_cell_id2str(cci), sbcap_cause_str(cci->fail.cause)); + } +} + +/*********************************************************************** + * actual FSM + ***********************************************************************/ + +static void smscb_p_fsm_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct cbc_message_peer *mp = (struct cbc_message_peer *) fi->priv; + int rc; + + switch (event) { + case SMSCB_PEER_E_CREATE: + /* send it to peer */ + rc = peer_new_cbc_message(mp->peer, mp->cbcmsg); + if (rc == 0) { + /* wait for peers' response */ + osmo_fsm_inst_state_chg(fi, SMSCB_S_WAIT_WRITE_ACK, 10, + T_WAIT_WRITE_ACK); + } + break; + default: + OSMO_ASSERT(0); + } +} + +static void smscb_p_fsm_wait_write_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct cbc_message_peer *mp = (struct cbc_message_peer *) fi->priv; + SBcAP_SBC_AP_PDU_t *sbcap = NULL; + A_SEQUENCE_OF(void) *as_pdu; + SBcAP_Write_Replace_Warning_Response_IEs_t *ie; + + switch (event) { + case SMSCB_PEER_E_SBCAP_WRITE_ACK: + sbcap = data; + OSMO_ASSERT(sbcap->present == SBcAP_SBC_AP_PDU_PR_successfulOutcome); + OSMO_ASSERT(sbcap->choice.successfulOutcome.procedureCode == SBcAP_ProcedureId_Write_Replace_Warning); + as_pdu = (void *)&sbcap->choice.successfulOutcome.value.choice.Write_Replace_Warning_Response.protocolIEs.list; + /* static const long asn_VAL_21_SBcAP_id_Unknown_Tracking_Area_List = 22; */ + ie = sbcap_as_find_ie(as_pdu, 22); + if (ie) { /* IE is optional */ + OSMO_ASSERT(ie->value.present == SBcAP_Write_Replace_Warning_Response_IEs__value_PR_List_of_TAIs); + msg_peer_append_fail_sbcap_tai_list(mp, &ie->value.choice.List_of_TAIs); + } + osmo_fsm_inst_state_chg(fi, SMSCB_S_ACTIVE, 0, 0); + /* Signal parent fsm about completion */ + osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_MSG_E_WRITE_ACK, mp); + break; + case SMSCB_PEER_E_SBCAP_WRITE_NACK: + osmo_fsm_inst_state_chg(fi, SMSCB_S_ACTIVE, 0, 0); + /* Signal parent fsm about completion */ + osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_MSG_E_WRITE_NACK, mp); + break; + default: + OSMO_ASSERT(0); + } +} + +static void smscb_p_fsm_active(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + //struct cbc_message_peer *mp = (struct cbc_message_peer *) fi->priv; + + switch (event) { + case SMSCB_PEER_E_REPLACE: /* send WRITE-REPLACE to MME */ + /* NOT IMPLEMENETED */ + osmo_fsm_inst_state_chg(fi, SMSCB_S_WAIT_REPLACE_ACK, 10, T_WAIT_REPLACE_ACK); + break; + case SMSCB_PEER_E_STATUS: + /* NOT IMPLEMENETED */ + osmo_fsm_inst_state_chg(fi, SMSCB_S_WAIT_STATUS_ACK, 10, T_WAIT_STATUS_ACK); + break; + default: + OSMO_ASSERT(0); + } +} + +static void smscb_p_fsm_wait_status_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + //struct cbc_message_peer *mp = (struct cbc_message_peer *) fi->priv; + + switch (event) { + /* NOT IMPLEMENETED */ + default: + OSMO_ASSERT(0); + } +} + + +static void smscb_p_fsm_wait_replace_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + //struct cbc_message_peer *mp = (struct cbc_message_peer *) fi->priv; + + switch (event) { + /* NOT IMPLEMENETED */ + default: + OSMO_ASSERT(0); + } +} + +static void smscb_p_fsm_wait_delete_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct cbc_message_peer *mp = (struct cbc_message_peer *) fi->priv; + + switch (event) { + case SMSCB_PEER_E_SBCAP_DELETE_ACK: + //pdu = data; + osmo_fsm_inst_state_chg(fi, SMSCB_S_DELETED, 0, 0); + /* Signal parent fsm about completion */ + osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_MSG_E_DELETE_ACK, mp); + break; + case SMSCB_PEER_E_SBCAP_DELETE_NACK: + //pdu = data; + osmo_fsm_inst_state_chg(fi, SMSCB_S_DELETED, 0, 0); + /* Signal parent fsm about completion */ + osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_MSG_E_DELETE_NACK, mp); + break; + default: + OSMO_ASSERT(0); + } +} + + + +static int smscb_p_fsm_timer_cb(struct osmo_fsm_inst *fi) +{ + switch (fi->T) { + case T_WAIT_WRITE_ACK: + osmo_fsm_inst_state_chg(fi, SMSCB_S_ACTIVE, 0, 0); + osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_MSG_E_WRITE_NACK, NULL); + break; + case T_WAIT_REPLACE_ACK: + osmo_fsm_inst_state_chg(fi, SMSCB_S_ACTIVE, 0, 0); + osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_MSG_E_REPLACE_NACK, NULL); + break; + case T_WAIT_STATUS_ACK: + osmo_fsm_inst_state_chg(fi, SMSCB_S_ACTIVE, 0, 0); + osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_MSG_E_STATUS_NACK, NULL); + break; + case T_WAIT_DELETE_ACK: + osmo_fsm_inst_state_chg(fi, SMSCB_S_DELETED, 0, 0); + osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_MSG_E_DELETE_NACK, NULL); + break; + default: + OSMO_ASSERT(0); + } + return 0; +} + +static void smscb_p_fsm_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct cbc_message_peer *mp = (struct cbc_message_peer *) fi->priv; + SBcAP_SBC_AP_PDU_t *sbcap; + A_SEQUENCE_OF(void) *as_pdu; + SBcAP_Write_Replace_Warning_Indication_IEs_t *ie; + + switch (event) { + case SMSCB_PEER_E_DELETE: /* send Stop-Warning to MME */ + switch (fi->state) { + case SMSCB_S_DELETED: + case SMSCB_S_INIT: + LOGPFSML(fi, LOGL_ERROR, "Event %s not permitted\n", + osmo_fsm_event_name(fi->fsm, event)); + return; + default: + break; + } + if ((sbcap = sbcap_gen_stop_warning_req(mp->peer, mp->cbcmsg))) { + cbc_sbcap_link_tx(mp->peer->link.sbcap, sbcap); + } else { + LOGP(DSBcAP, LOGL_ERROR, + "[%s] Tx SBc-AP Stop-Warning-Request: msg gen failed\n", + mp->peer->name); + } + osmo_fsm_inst_state_chg(fi, SMSCB_S_WAIT_DELETE_ACK, 10, T_WAIT_DELETE_ACK); + break; + case SMSCB_PEER_E_SBCAP_WRITE_IND: + sbcap = (SBcAP_SBC_AP_PDU_t *)data; + OSMO_ASSERT(sbcap->present == SBcAP_SBC_AP_PDU_PR_initiatingMessage); + OSMO_ASSERT(sbcap->choice.initiatingMessage.procedureCode == SBcAP_ProcedureId_Write_Replace_Warning_Indication); + as_pdu = (void *)&sbcap->choice.initiatingMessage.value.choice.Write_Replace_Warning_Indication.protocolIEs.list; + /* static const long asn_VAL_36_SBcAP_id_Broadcast_Scheduled_Area_List = 23; */ + ie = sbcap_as_find_ie(as_pdu, 23); + if (!ie) + return; /* IE is optional */ + OSMO_ASSERT(ie->value.present == SBcAP_Write_Replace_Warning_Indication_IEs__value_PR_Broadcast_Scheduled_Area_List); + msg_peer_append_compl_sbcap_bcast_area_list(mp, &ie->value.choice.Broadcast_Scheduled_Area_List); + break; + default: + OSMO_ASSERT(0); + } +} + +static void smscb_p_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) +{ + struct cbc_message_peer *mp = (struct cbc_message_peer *) fi->priv; + llist_del(&mp->list); + /* memory of mp is child of fi and hence automatically free'd */ +} + +static const struct osmo_fsm_state smscb_p_fsm_states[] = { + [SMSCB_S_INIT] = { + .name = "INIT", + .in_event_mask = S(SMSCB_PEER_E_CREATE), + .out_state_mask = S(SMSCB_S_WAIT_WRITE_ACK), + .action = smscb_p_fsm_init, + }, + [SMSCB_S_WAIT_WRITE_ACK] = { + .name = "WAIT_WRITE_ACK", + .in_event_mask = S(SMSCB_PEER_E_SBCAP_WRITE_ACK) | + S(SMSCB_PEER_E_SBCAP_WRITE_NACK), + .out_state_mask = S(SMSCB_S_ACTIVE) | + S(SMSCB_S_WAIT_DELETE_ACK), + .action = smscb_p_fsm_wait_write_ack, + }, + [SMSCB_S_ACTIVE] = { + .name = "ACTIVE", + .in_event_mask = S(SMSCB_PEER_E_REPLACE) | + S(SMSCB_PEER_E_STATUS), + .out_state_mask = S(SMSCB_S_WAIT_REPLACE_ACK) | + S(SMSCB_S_WAIT_STATUS_ACK) | + S(SMSCB_S_WAIT_DELETE_ACK), + .action = smscb_p_fsm_active, + }, + [SMSCB_S_WAIT_STATUS_ACK] = { + .name = "WAIT_STATUS_ACK", + .in_event_mask = 0 /* NOT IMPLEMENTED */, + .out_state_mask = S(SMSCB_S_ACTIVE) | + S(SMSCB_S_WAIT_DELETE_ACK), + .action = smscb_p_fsm_wait_status_ack, + }, + [SMSCB_S_WAIT_REPLACE_ACK] = { + .name = "WAIT_REPLACE_ACK", + .in_event_mask = 0 /* NOT IMPLEMENTED */, + .out_state_mask = S(SMSCB_S_ACTIVE) | + S(SMSCB_S_WAIT_DELETE_ACK), + .action = smscb_p_fsm_wait_replace_ack, + }, + [SMSCB_S_WAIT_DELETE_ACK] = { + .name = "WAIT_DELETE_ACK", + .in_event_mask = S(SMSCB_PEER_E_SBCAP_DELETE_ACK) | + S(SMSCB_PEER_E_SBCAP_DELETE_NACK), + .out_state_mask = S(SMSCB_S_DELETED), + .action = smscb_p_fsm_wait_delete_ack, + }, + [SMSCB_S_DELETED] = { + .name = "DELETED", + }, +}; + +struct osmo_fsm sbcap_smscb_peer_fsm = { + .name = "SMSCB-PEER-SBcAP", + .states = smscb_p_fsm_states, + .num_states = ARRAY_SIZE(smscb_p_fsm_states), + .allstate_event_mask = S(SMSCB_PEER_E_DELETE) | + S(SMSCB_PEER_E_SBCAP_WRITE_IND), + .allstate_action = smscb_p_fsm_allstate, + .timer_cb = smscb_p_fsm_timer_cb, + .log_subsys = DSBcAP, + .event_names = smscb_peer_fsm_event_names, + .cleanup = smscb_p_fsm_cleanup, +}; + +static __attribute__((constructor)) void on_dso_load_smscb_p_fsm(void) +{ + OSMO_ASSERT(osmo_fsm_register(&sbcap_smscb_peer_fsm) == 0); +} diff --git a/src/smscb_peer_fsm.c b/src/smscb_peer_fsm.c index 589a832..20eec95 100644 --- a/src/smscb_peer_fsm.c +++ b/src/smscb_peer_fsm.c @@ -27,13 +27,6 @@ #include #include -#include -#include -#include -#include - -#include - #include #include #include @@ -43,8 +36,6 @@ #include #include -#define S(x) (1 << (x)) - const struct value_string smscb_peer_fsm_event_names[] = { { SMSCB_PEER_E_CREATE, "CREATE" }, { SMSCB_PEER_E_REPLACE, "REPLACE" }, @@ -66,666 +57,24 @@ const struct value_string smscb_peer_fsm_event_names[] = { { 0, NULL } }; -#if 0 -static const struct value_string smscb_p_fsm_timer_names[] = { - OSMO_VALUE_STRING(T_WAIT_WRITE_ACK), - OSMO_VALUE_STRING(T_WAIT_REPLACE_ACK), - OSMO_VALUE_STRING(T_WAIT_DELETE_ACK), - { 0, NULL } -}; -#endif - -/*********************************************************************** - * Helper functions - ***********************************************************************/ - -/* covert TS 08.08 Cell Identity value to CBC internal type */ -static enum cbc_cell_id_type cci_discr_from_cell_id(enum CELL_IDENT id_discr) -{ - switch (id_discr) { - case CELL_IDENT_NO_CELL: - return CBC_CELL_ID_NONE; - case CELL_IDENT_WHOLE_GLOBAL: - return CBC_CELL_ID_CGI; - case CELL_IDENT_LAC_AND_CI: - return CBC_CELL_ID_LAC_CI; - case CELL_IDENT_CI: - return CBC_CELL_ID_CI; - case CELL_IDENT_LAI: - return CBC_CELL_ID_LAI; - case CELL_IDENT_LAC: - return CBC_CELL_ID_LAC; - case CELL_IDENT_BSS: - return CBC_CELL_ID_BSS; - default: - return -1; - } -} - -/* covert CBC internal type to TS 08.08 Cell Identity */ -static enum CELL_IDENT cell_id_from_ccid_discr(enum cbc_cell_id_type in) -{ - switch (in) { - case CBC_CELL_ID_NONE: - return CELL_IDENT_NO_CELL; - case CBC_CELL_ID_CGI: - return CELL_IDENT_WHOLE_GLOBAL; - case CBC_CELL_ID_LAC_CI: - return CELL_IDENT_LAC_AND_CI; - case CBC_CELL_ID_CI: - return CELL_IDENT_CI; - case CBC_CELL_ID_LAI: - return CELL_IDENT_LAI; - case CBC_CELL_ID_LAC: - return CELL_IDENT_LAC; - case CBC_CELL_ID_BSS: - return CELL_IDENT_BSS; - default: - return -1; - } -} - -/* convert TS 08.08 Cell Identifier Union to CBC internal type */ -static void cci_from_cbsp(struct cbc_cell_id *cci, enum CELL_IDENT id_discr, - const union gsm0808_cell_id_u *u) -{ - cci->id_discr = cci_discr_from_cell_id(id_discr); - - switch (id_discr) { - case CELL_IDENT_NO_CELL: - break; - case CELL_IDENT_WHOLE_GLOBAL: - cci->u.cgi = u->global; - break; - case CELL_IDENT_LAC_AND_CI: - cci->u.lac_and_ci = u->lac_and_ci; - break; - case CELL_IDENT_CI: - cci->u.ci = u->ci; - break; - case CELL_IDENT_LAI: - cci->u.lai = u->lai_and_lac; - break; - case CELL_IDENT_LAC: - cci->u.lac = u->lac; - break; - case CELL_IDENT_BSS: - break; - default: - break; - } -} - -/* convert TS 08.08 Cell Identifier Union to CBC internal type */ -static void cbsp_from_cci(union gsm0808_cell_id_u *u, const struct cbc_cell_id *cci) -{ - switch (cci->id_discr) { - case CBC_CELL_ID_NONE: - break; - case CBC_CELL_ID_CGI: - u->global = cci->u.cgi; - printf("u->gobal: %s\n", osmo_hexdump((uint8_t *) &u->global, sizeof(u->global))); - break; - case CBC_CELL_ID_LAC_CI: - u->lac_and_ci = cci->u.lac_and_ci; - break; - case CBC_CELL_ID_CI: - u->ci = cci->u.ci; - break; - case CBC_CELL_ID_LAI: - u->lai_and_lac = cci->u.lai; - break; - case CBC_CELL_ID_LAC: - u->lac = cci->u.lac; - break; - case CBC_CELL_ID_BSS: - break; - default: - OSMO_ASSERT(0); - } -} - -/* read a single osmo_cbsp_num_compl_ent and add it to cbc_message_peer */ -static void cci_from_cbsp_compl_ent(struct cbc_message_peer *mp, - struct osmo_cbsp_num_compl_ent *ce, enum CELL_IDENT id_discr) -{ - struct cbc_cell_id *cci; - - cci = NULL; // FIXME: lookup - if (!cci) { - cci = talloc_zero(mp, struct cbc_cell_id); - if (!cci) - return; - llist_add_tail(&cci->list, &mp->num_compl_list); - } - cci_from_cbsp(cci, id_discr, &ce->cell_id); - cci->num_compl.num_compl += ce->num_compl; - cci->num_compl.num_bcast_info += ce->num_bcast_info; -} -static void msg_peer_append_cbsp_compl(struct cbc_message_peer *mp, - struct osmo_cbsp_num_compl_list *nclist) -{ - struct osmo_cbsp_num_compl_ent *ce; - - llist_for_each_entry(ce, &nclist->list, list) - cci_from_cbsp_compl_ent(mp, ce, nclist->id_discr); -} - -/* read a single osmo_cbsp_cell_ent and add it to cbc_message_peer */ -static void cci_from_cbsp_cell_ent(struct cbc_message_peer *mp, - struct osmo_cbsp_cell_ent *ce, enum CELL_IDENT id_discr) -{ - struct cbc_cell_id *cci; - - cci = NULL; // FIXME: lookup - if (!cci) { - cci = talloc_zero(mp, struct cbc_cell_id); - if (!cci) - return; - llist_add_tail(&cci->list, &mp->cell_list); - } - cci_from_cbsp(cci, id_discr, &ce->cell_id); -} -static void msg_peer_append_cbsp_cell(struct cbc_message_peer *mp, - struct osmo_cbsp_cell_list *clist) -{ - struct osmo_cbsp_cell_ent *ce; - - llist_for_each_entry(ce, &clist->list, list) - cci_from_cbsp_cell_ent(mp, ce, clist->id_discr); -} - -/* read a single osmo_cbsp_fail_ent and add it to cbc_message_peer */ -static void cci_from_cbsp_fail_ent(struct cbc_message_peer *mp, - struct osmo_cbsp_fail_ent *fe) -{ - struct cbc_cell_id *cci; - cci = NULL; // lookup */ - if (!cci) { - cci = talloc_zero(mp, struct cbc_cell_id); - if (!cci) - return; - llist_add_tail(&cci->list, &mp->fail_list); - } - cci->id_discr = cci_discr_from_cell_id(fe->id_discr); - cci->fail.cause = fe->cause; -} -static void msg_peer_append_cbsp_fail(struct cbc_message_peer *mp, struct llist_head *flist) -{ - struct osmo_cbsp_fail_ent *fe; - - llist_for_each_entry(fe, flist, list) - cci_from_cbsp_fail_ent(mp, fe); -} - -/* append all cells from cbc_message_peer to given CBSP cell_list */ -static void cbsp_append_cell_list(struct osmo_cbsp_cell_list *out, void *ctx, - const struct cbc_message_peer *mp) -{ - struct cbc_cell_id *cci; - enum cbc_cell_id_type id_discr = CBC_CELL_ID_NONE; - - llist_for_each_entry(cci, &mp->cell_list, list) { - struct osmo_cbsp_cell_ent *ent; - - if (id_discr == CBC_CELL_ID_NONE) - id_discr = cci->id_discr; - else if (id_discr != cci->id_discr) { - LOGPFSML(mp->fi, LOGL_ERROR, "Cannot encode CBSP cell_list as not all " - "entries are of same type (%u != %u)\n", id_discr, cci->id_discr); - continue; - } - ent = talloc_zero(ctx, struct osmo_cbsp_cell_ent); - OSMO_ASSERT(ent); - cbsp_from_cci(&ent->cell_id, cci); - llist_add_tail(&ent->list, &out->list); - } - out->id_discr = cell_id_from_ccid_discr(id_discr); -} - -/* append SBcAP cells to msg_peer compl list */ -void msg_peer_append_compl_sbcap_bcast_area_list(struct cbc_message_peer *mp, - const SBcAP_Broadcast_Scheduled_Area_List_t *bcast) -{ - SBcAP_CellId_Broadcast_List_t *cell_id_bscat = bcast->cellId_Broadcast_List; - A_SEQUENCE_OF(struct SBcAP_CellId_Broadcast_List_Item) *as_cell_id_bcast; - SBcAP_CellId_Broadcast_List_Item_t *it; - unsigned int i; - - if (!cell_id_bscat) - return; - - as_cell_id_bcast = (void *) &cell_id_bscat->list; - for (i = 0; i < as_cell_id_bcast->count; i++) { - it = (SBcAP_CellId_Broadcast_List_Item_t *)(as_cell_id_bcast->array[i]); - OSMO_ASSERT(it); - struct cbc_cell_id *cci = NULL; // FIXME: lookup - if (!cci) { - cci = talloc_zero(mp, struct cbc_cell_id); - if (!cci) - return; - llist_add_tail(&cci->list, &mp->num_compl_list); - } - cci_from_sbcap_bcast_cell_id(cci, it); - LOGPFSML(mp->fi, LOGL_DEBUG, "Appending CellId %s to Broadcast Completed list\n", - cbc_cell_id2str(cci)); - cci->num_compl.num_compl += 1; - cci->num_compl.num_bcast_info += 1; - } -} - -/* append SBcAP cells to msg_peer fail list */ -void msg_peer_append_fail_sbcap_tai_list(struct cbc_message_peer *mp, - const SBcAP_List_of_TAIs_t *tais) -{ - A_SEQUENCE_OF(List_of_TAIs__Member) *as_tais = (void *)&tais->list; - List_of_TAIs__Member *it; - unsigned int i; - - for (i = 0; i < as_tais->count; i++) { - it = (List_of_TAIs__Member *)(as_tais->array[i]); - OSMO_ASSERT(it); - struct cbc_cell_id *cci = NULL; // FIXME: lookup - if (!cci) { - cci = talloc_zero(mp, struct cbc_cell_id); - if (!cci) - return; - llist_add_tail(&cci->list, &mp->fail_list); - } - cci_from_sbcap_tai(cci, &it->tai); - cci->fail.cause = SBcAP_Cause_tracking_area_not_valid; - LOGPFSML(mp->fi, LOGL_DEBUG, "Appending CellId %s (cause: %s) to Failed list\n", - cbc_cell_id2str(cci), sbcap_cause_str(cci->fail.cause)); - } -} - -/*********************************************************************** - * actual FSM - ***********************************************************************/ - -static void smscb_p_fsm_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct cbc_message_peer *mp = (struct cbc_message_peer *) fi->priv; - int rc; - - switch (event) { - case SMSCB_PEER_E_CREATE: - /* send it to peer */ - rc = peer_new_cbc_message(mp->peer, mp->cbcmsg); - if (rc == 0) { - /* wait for peers' response */ - osmo_fsm_inst_state_chg(fi, SMSCB_S_WAIT_WRITE_ACK, 10, - T_WAIT_WRITE_ACK); - } - break; - default: - OSMO_ASSERT(0); - } -} - -static void smscb_p_fsm_wait_write_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct cbc_message_peer *mp = (struct cbc_message_peer *) fi->priv; - struct osmo_cbsp_decoded *dec = NULL; - SBcAP_SBC_AP_PDU_t *sbcap = NULL; - A_SEQUENCE_OF(void) *as_pdu; - SBcAP_Write_Replace_Warning_Response_IEs_t *ie; - - switch (event) { - case SMSCB_PEER_E_CBSP_WRITE_ACK: - dec = data; - msg_peer_append_cbsp_compl(mp, &dec->u.write_replace_compl.num_compl_list); - msg_peer_append_cbsp_cell(mp, &dec->u.write_replace_compl.cell_list); - osmo_fsm_inst_state_chg(fi, SMSCB_S_ACTIVE, 0, 0); - /* Signal parent fsm about completion */ - osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_MSG_E_WRITE_ACK, mp); - break; - case SMSCB_PEER_E_CBSP_WRITE_NACK: - dec = data; - msg_peer_append_cbsp_compl(mp, &dec->u.write_replace_fail.num_compl_list); - msg_peer_append_cbsp_cell(mp, &dec->u.write_replace_fail.cell_list); - msg_peer_append_cbsp_fail(mp, &dec->u.write_replace_fail.fail_list); - osmo_fsm_inst_state_chg(fi, SMSCB_S_ACTIVE, 0, 0); - /* Signal parent fsm about completion */ - osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_MSG_E_WRITE_NACK, mp); - break; - case SMSCB_PEER_E_SBCAP_WRITE_ACK: - sbcap = data; - OSMO_ASSERT(sbcap->present == SBcAP_SBC_AP_PDU_PR_successfulOutcome); - OSMO_ASSERT(sbcap->choice.successfulOutcome.procedureCode == SBcAP_ProcedureId_Write_Replace_Warning); - as_pdu = (void *)&sbcap->choice.successfulOutcome.value.choice.Write_Replace_Warning_Response.protocolIEs.list; - /* static const long asn_VAL_21_SBcAP_id_Unknown_Tracking_Area_List = 22; */ - ie = sbcap_as_find_ie(as_pdu, 22); - if (ie) { /* IE is optional */ - OSMO_ASSERT(ie->value.present == SBcAP_Write_Replace_Warning_Response_IEs__value_PR_List_of_TAIs); - msg_peer_append_fail_sbcap_tai_list(mp, &ie->value.choice.List_of_TAIs); - } - osmo_fsm_inst_state_chg(fi, SMSCB_S_ACTIVE, 0, 0); - /* Signal parent fsm about completion */ - osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_MSG_E_WRITE_ACK, mp); - break; - case SMSCB_PEER_E_SBCAP_WRITE_NACK: - osmo_fsm_inst_state_chg(fi, SMSCB_S_ACTIVE, 0, 0); - /* Signal parent fsm about completion */ - osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_MSG_E_WRITE_NACK, mp); - break; - default: - OSMO_ASSERT(0); - } -} - -static void smscb_p_fsm_active(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct cbc_message_peer *mp = (struct cbc_message_peer *) fi->priv; - struct osmo_cbsp_decoded *cbsp; - - switch (event) { - case SMSCB_PEER_E_REPLACE: /* send WRITE-REPLACE to BSC */ - cbsp = osmo_cbsp_decoded_alloc(mp->peer, CBSP_MSGT_WRITE_REPLACE); - OSMO_ASSERT(cbsp); - cbsp->u.write_replace.msg_id = mp->cbcmsg->msg.message_id; - cbsp->u.write_replace.old_serial_nr = &mp->cbcmsg->msg.serial_nr; - //cbsp->u.write_replace.new_serial_nr - /* TODO: we assume that the replace will always affect all original cells */ - cbsp_append_cell_list(&cbsp->u.write_replace.cell_list, cbsp, mp); - // TODO: ALL OTHER DATA - cbc_cbsp_link_tx(mp->peer->link.cbsp, cbsp); - osmo_fsm_inst_state_chg(fi, SMSCB_S_WAIT_REPLACE_ACK, 10, T_WAIT_REPLACE_ACK); - break; - case SMSCB_PEER_E_STATUS: /* send MSG-STATUS-QUERY to BSC */ - cbsp = osmo_cbsp_decoded_alloc(mp->peer, CBSP_MSGT_MSG_STATUS_QUERY); - OSMO_ASSERT(cbsp); - cbsp->u.msg_status_query.msg_id = mp->cbcmsg->msg.message_id; - cbsp->u.msg_status_query.old_serial_nr = mp->cbcmsg->msg.serial_nr; - cbsp_append_cell_list(&cbsp->u.msg_status_query.cell_list, cbsp, mp); - cbsp->u.msg_status_query.channel_ind = CBSP_CHAN_IND_BASIC; - cbc_cbsp_link_tx(mp->peer->link.cbsp, cbsp); - osmo_fsm_inst_state_chg(fi, SMSCB_S_WAIT_STATUS_ACK, 10, T_WAIT_STATUS_ACK); - break; - default: - OSMO_ASSERT(0); - } -} - -static void smscb_p_fsm_wait_status_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct cbc_message_peer *mp = (struct cbc_message_peer *) fi->priv; - struct osmo_cbsp_decoded *dec = NULL; - - switch (event) { - case SMSCB_PEER_E_CBSP_STATUS_ACK: - dec = data; - msg_peer_append_cbsp_compl(mp, &dec->u.msg_status_query_compl.num_compl_list); - osmo_fsm_inst_state_chg(fi, SMSCB_S_ACTIVE, 0, 0); - /* Signal parent fsm about completion */ - osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_MSG_E_STATUS_ACK, mp); - break; - case SMSCB_PEER_E_CBSP_STATUS_NACK: - dec = data; - msg_peer_append_cbsp_compl(mp, &dec->u.msg_status_query_fail.num_compl_list); - msg_peer_append_cbsp_fail(mp, &dec->u.msg_status_query_fail.fail_list); - osmo_fsm_inst_state_chg(fi, SMSCB_S_ACTIVE, 0, 0); - /* Signal parent fsm about completion */ - osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_MSG_E_STATUS_NACK, mp); - break; - default: - OSMO_ASSERT(0); - } -} - - -static void smscb_p_fsm_wait_replace_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct cbc_message_peer *mp = (struct cbc_message_peer *) fi->priv; - struct osmo_cbsp_decoded *dec = NULL; - - switch (event) { - case SMSCB_PEER_E_CBSP_REPLACE_ACK: - dec = data; - msg_peer_append_cbsp_compl(mp, &dec->u.write_replace_compl.num_compl_list); - msg_peer_append_cbsp_cell(mp, &dec->u.write_replace_compl.cell_list); - osmo_fsm_inst_state_chg(fi, SMSCB_S_ACTIVE, 0, 0); - /* Signal parent fsm about completion */ - osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_MSG_E_REPLACE_ACK, mp); - break; - case SMSCB_PEER_E_CBSP_REPLACE_NACK: - dec = data; - msg_peer_append_cbsp_compl(mp, &dec->u.write_replace_fail.num_compl_list); - msg_peer_append_cbsp_cell(mp, &dec->u.write_replace_fail.cell_list); - msg_peer_append_cbsp_fail(mp, &dec->u.write_replace_fail.fail_list); - osmo_fsm_inst_state_chg(fi, SMSCB_S_ACTIVE, 0, 0); - /* Signal parent fsm about completion */ - osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_MSG_E_REPLACE_NACK, mp); - break; - default: - OSMO_ASSERT(0); - } -} - -static void smscb_p_fsm_wait_delete_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct cbc_message_peer *mp = (struct cbc_message_peer *) fi->priv; - struct osmo_cbsp_decoded *dec = NULL; - //SBcAP_SBC_AP_PDU_t *pdu = NULL; - - switch (event) { - case SMSCB_PEER_E_CBSP_DELETE_ACK: - dec = data; - /* append results */ - msg_peer_append_cbsp_compl(mp, &dec->u.kill_compl.num_compl_list); - msg_peer_append_cbsp_cell(mp, &dec->u.kill_compl.cell_list); - osmo_fsm_inst_state_chg(fi, SMSCB_S_DELETED, 0, 0); - /* Signal parent fsm about completion */ - osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_MSG_E_DELETE_ACK, mp); - break; - case SMSCB_PEER_E_CBSP_DELETE_NACK: - dec = data; - /* append results */ - msg_peer_append_cbsp_compl(mp, &dec->u.kill_fail.num_compl_list); - msg_peer_append_cbsp_cell(mp, &dec->u.kill_fail.cell_list); - msg_peer_append_cbsp_fail(mp, &dec->u.kill_fail.fail_list); - osmo_fsm_inst_state_chg(fi, SMSCB_S_DELETED, 0, 0); - /* Signal parent fsm about completion */ - osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_MSG_E_DELETE_NACK, mp); - break; - case SMSCB_PEER_E_SBCAP_DELETE_ACK: - //pdu = data; - osmo_fsm_inst_state_chg(fi, SMSCB_S_DELETED, 0, 0); - /* Signal parent fsm about completion */ - osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_MSG_E_DELETE_ACK, mp); - break; - case SMSCB_PEER_E_SBCAP_DELETE_NACK: - //pdu = data; - osmo_fsm_inst_state_chg(fi, SMSCB_S_DELETED, 0, 0); - /* Signal parent fsm about completion */ - osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_MSG_E_DELETE_NACK, mp); - break; - default: - OSMO_ASSERT(0); - } -} - - - -static int smscb_p_fsm_timer_cb(struct osmo_fsm_inst *fi) -{ - switch (fi->T) { - case T_WAIT_WRITE_ACK: - osmo_fsm_inst_state_chg(fi, SMSCB_S_ACTIVE, 0, 0); - osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_MSG_E_WRITE_NACK, NULL); - break; - case T_WAIT_REPLACE_ACK: - osmo_fsm_inst_state_chg(fi, SMSCB_S_ACTIVE, 0, 0); - osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_MSG_E_REPLACE_NACK, NULL); - break; - case T_WAIT_STATUS_ACK: - osmo_fsm_inst_state_chg(fi, SMSCB_S_ACTIVE, 0, 0); - osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_MSG_E_STATUS_NACK, NULL); - break; - case T_WAIT_DELETE_ACK: - osmo_fsm_inst_state_chg(fi, SMSCB_S_DELETED, 0, 0); - osmo_fsm_inst_dispatch(fi->proc.parent, SMSCB_MSG_E_DELETE_NACK, NULL); - break; - default: - OSMO_ASSERT(0); - } - return 0; -} - -static void smscb_p_fsm_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct cbc_message_peer *mp = (struct cbc_message_peer *) fi->priv; - struct osmo_cbsp_decoded *cbsp; - SBcAP_SBC_AP_PDU_t *sbcap; - A_SEQUENCE_OF(void) *as_pdu; - SBcAP_Write_Replace_Warning_Indication_IEs_t *ie; - - switch (event) { - case SMSCB_PEER_E_DELETE: /* send KILL to BSC */ - switch (fi->state) { - case SMSCB_S_DELETED: - case SMSCB_S_INIT: - LOGPFSML(fi, LOGL_ERROR, "Event %s not permitted\n", - osmo_fsm_event_name(fi->fsm, event)); - return; - default: - break; - } - switch (mp->peer->proto) { - case CBC_PEER_PROTO_CBSP: - cbsp = osmo_cbsp_decoded_alloc(mp->peer, CBSP_MSGT_KILL); - OSMO_ASSERT(cbsp); - cbsp->u.kill.msg_id = mp->cbcmsg->msg.message_id; - cbsp->u.kill.old_serial_nr = mp->cbcmsg->msg.serial_nr; - /* TODO: we assume that the delete will always affect all original cells */ - cbsp_append_cell_list(&cbsp->u.kill.cell_list, cbsp, mp); - if (!mp->cbcmsg->msg.is_etws) { - /* Channel Indication IE is only present in CBS, not in ETWS! */ - cbsp->u.kill.channel_ind = talloc_zero(cbsp, enum cbsp_channel_ind); - OSMO_ASSERT(cbsp->u.kill.channel_ind); - *(cbsp->u.kill.channel_ind) = CBSP_CHAN_IND_BASIC; - } - cbc_cbsp_link_tx(mp->peer->link.cbsp, cbsp); - break; - case CBC_PEER_PROTO_SBcAP: - if ((sbcap = sbcap_gen_stop_warning_req(mp->peer, mp->cbcmsg))) { - cbc_sbcap_link_tx(mp->peer->link.sbcap, sbcap); - } else { - LOGP(DSBcAP, LOGL_ERROR, - "[%s] Tx SBc-AP Stop-Warning-Request: msg gen failed\n", - mp->peer->name); - } - break; - case CBC_PEER_PROTO_SABP: - default: - osmo_panic("SMSCB_PEER_E_DELETE not implemented for proto %u", mp->peer->proto); - } - osmo_fsm_inst_state_chg(fi, SMSCB_S_WAIT_DELETE_ACK, 10, T_WAIT_DELETE_ACK); - break; - case SMSCB_PEER_E_SBCAP_WRITE_IND: - sbcap = (SBcAP_SBC_AP_PDU_t *)data; - OSMO_ASSERT(sbcap->present == SBcAP_SBC_AP_PDU_PR_initiatingMessage); - OSMO_ASSERT(sbcap->choice.initiatingMessage.procedureCode == SBcAP_ProcedureId_Write_Replace_Warning_Indication); - as_pdu = (void *)&sbcap->choice.initiatingMessage.value.choice.Write_Replace_Warning_Indication.protocolIEs.list; - /* static const long asn_VAL_36_SBcAP_id_Broadcast_Scheduled_Area_List = 23; */ - ie = sbcap_as_find_ie(as_pdu, 23); - if (!ie) - return; /* IE is optional */ - OSMO_ASSERT(ie->value.present == SBcAP_Write_Replace_Warning_Indication_IEs__value_PR_Broadcast_Scheduled_Area_List); - msg_peer_append_compl_sbcap_bcast_area_list(mp, &ie->value.choice.Broadcast_Scheduled_Area_List); - break; - default: - OSMO_ASSERT(0); - } -} - -static void smscb_p_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) -{ - struct cbc_message_peer *mp = (struct cbc_message_peer *) fi->priv; - llist_del(&mp->list); - /* memory of mp is child of fi and hence automatically free'd */ -} - -static const struct osmo_fsm_state smscb_p_fsm_states[] = { - [SMSCB_S_INIT] = { - .name = "INIT", - .in_event_mask = S(SMSCB_PEER_E_CREATE), - .out_state_mask = S(SMSCB_S_WAIT_WRITE_ACK), - .action = smscb_p_fsm_init, - }, - [SMSCB_S_WAIT_WRITE_ACK] = { - .name = "WAIT_WRITE_ACK", - .in_event_mask = S(SMSCB_PEER_E_CBSP_WRITE_ACK) | - S(SMSCB_PEER_E_CBSP_WRITE_NACK) | - S(SMSCB_PEER_E_SBCAP_WRITE_ACK) | - S(SMSCB_PEER_E_SBCAP_WRITE_NACK), - .out_state_mask = S(SMSCB_S_ACTIVE) | - S(SMSCB_S_WAIT_DELETE_ACK), - .action = smscb_p_fsm_wait_write_ack, - }, - [SMSCB_S_ACTIVE] = { - .name = "ACTIVE", - .in_event_mask = S(SMSCB_PEER_E_REPLACE) | - S(SMSCB_PEER_E_STATUS), - .out_state_mask = S(SMSCB_S_WAIT_REPLACE_ACK) | - S(SMSCB_S_WAIT_STATUS_ACK) | - S(SMSCB_S_WAIT_DELETE_ACK), - .action = smscb_p_fsm_active, - }, - [SMSCB_S_WAIT_STATUS_ACK] = { - .name = "WAIT_STATUS_ACK", - .in_event_mask = S(SMSCB_PEER_E_CBSP_STATUS_ACK) | - S(SMSCB_PEER_E_CBSP_STATUS_NACK), - .out_state_mask = S(SMSCB_S_ACTIVE) | - S(SMSCB_S_WAIT_DELETE_ACK), - .action = smscb_p_fsm_wait_status_ack, - }, - [SMSCB_S_WAIT_REPLACE_ACK] = { - .name = "WAIT_REPLACE_ACK", - .in_event_mask = S(SMSCB_PEER_E_CBSP_REPLACE_ACK) | - S(SMSCB_PEER_E_CBSP_REPLACE_NACK), - .out_state_mask = S(SMSCB_S_ACTIVE) | - S(SMSCB_S_WAIT_DELETE_ACK), - .action = smscb_p_fsm_wait_replace_ack, - }, - [SMSCB_S_WAIT_DELETE_ACK] = { - .name = "WAIT_DELETE_ACK", - .in_event_mask = S(SMSCB_PEER_E_CBSP_DELETE_ACK) | - S(SMSCB_PEER_E_CBSP_DELETE_NACK) | - S(SMSCB_PEER_E_SBCAP_DELETE_ACK) | - S(SMSCB_PEER_E_SBCAP_DELETE_NACK), - .out_state_mask = S(SMSCB_S_DELETED), - .action = smscb_p_fsm_wait_delete_ack, - }, - [SMSCB_S_DELETED] = { - .name = "DELETED", - }, -}; - -struct osmo_fsm smscb_p_fsm = { - .name = "SMSCB-PEER", - .states = smscb_p_fsm_states, - .num_states = ARRAY_SIZE(smscb_p_fsm_states), - .allstate_event_mask = S(SMSCB_PEER_E_DELETE) | - S(SMSCB_PEER_E_SBCAP_WRITE_IND), - .allstate_action = smscb_p_fsm_allstate, - .timer_cb = smscb_p_fsm_timer_cb, - .log_subsys = DCBSP, - .event_names = smscb_peer_fsm_event_names, - .cleanup = smscb_p_fsm_cleanup, -}; - -static __attribute__((constructor)) void on_dso_load_smscb_p_fsm(void) -{ - OSMO_ASSERT(osmo_fsm_register(&smscb_p_fsm) == 0); -} - struct cbc_message_peer *smscb_peer_fsm_alloc(struct cbc_peer *peer, struct cbc_message *cbcmsg) { struct cbc_message_peer *mp; struct osmo_fsm_inst *fi; + struct osmo_fsm *fsm_def; - fi = osmo_fsm_inst_alloc_child(&smscb_p_fsm, cbcmsg->fi, SMSCB_MSG_E_CHILD_DIED); + switch (peer->proto) { + case CBC_PEER_PROTO_CBSP: + fsm_def = &cbsp_smscb_peer_fsm; + break; + case CBC_PEER_PROTO_SBcAP: + fsm_def = &sbcap_smscb_peer_fsm; + break; + case CBC_PEER_PROTO_SABP: + default: + osmo_panic("smscb_peer FSM not implemented for proto %u", peer->proto); + } + fi = osmo_fsm_inst_alloc_child(fsm_def, cbcmsg->fi, SMSCB_MSG_E_CHILD_DIED); if (!fi) return NULL; /* include the peer name in the ID of the child FSM */