diff --git a/smscb.schema.json b/smscb.schema.json index e95b90c..a683263 100644 --- a/smscb.schema.json +++ b/smscb.schema.json @@ -86,7 +86,7 @@ "warning_type_encoded": { "type": "integer", "minimum": 0, - "maximum": 255 + "maximum": 127 }, "warning_type_decoded": { "enum": [ "earthquake", "tsunami", "earthquake_and_tsunami", "test", @@ -105,6 +105,8 @@ "type": "object", "properties": { "warning_type": { "$ref": "#/definitions/warning_type" }, + "emergency_user_alert": "boolean", + "popup_on_display": "boolean", "warning_sec_info": { "$ref": "#/definitions/warning_sec_info" } }, "required": [ "warning_type" ] diff --git a/src/cbc_data.h b/src/cbc_data.h index bec40ec..9ad7c70 100644 --- a/src/cbc_data.h +++ b/src/cbc_data.h @@ -90,15 +90,16 @@ struct smscb_message { unsigned int num_pages; /* actual page data, concatenated */ uint8_t data[SMSCB_MAX_NUM_PAGES][SMSCB_RAW_PAGE_LEN]; - /* FIXME: do we need information on the total length to - * determine which is the last block used in [at least the last] - * page? */ /* total number of octets user data over _all_ the pages */ uint16_t data_user_len; } cbs; struct { - /* WarningType 16bit parameter as per 23.041 9.3.24 */ + /* WarningTypeValue 7bit parameter as per 23.041 9.3.24 */ uint16_t warning_type; + /* Emergency User Alert */ + bool user_alert; + /* Popup on Display */ + bool popup_on_display; uint8_t warning_sec_info[50]; } etws; }; diff --git a/src/cbc_vty.c b/src/cbc_vty.c index d4ede9a..8e2e022 100644 --- a/src/cbc_vty.c +++ b/src/cbc_vty.c @@ -24,18 +24,32 @@ #include #include +#include #include #include #include #include "cbc_data.h" +#include "internal.h" +#include "cbsp_server.h" static void dump_one_cbc_peer(struct vty *vty, const struct cbc_peer *peer) { - vty_out(vty, " %-20s | %-15s | %-5d | %s |%s", + const char *state = ""; + + switch (peer->proto) { + case CBC_PEER_PROTO_CBSP: + if (peer->client.cbsp) + state = osmo_fsm_inst_state_name(peer->client.cbsp->fi); + break; + case CBC_PEER_PROTO_SABP: + break; + } + + vty_out(vty, "|%-20s| %-15s| %-5d| %-6s| %-20s|%s", peer->name ? peer->name : "", peer->remote_host, peer->remote_port, - get_value_string(cbc_peer_proto_name, peer->proto), VTY_NEWLINE); + get_value_string(cbc_peer_proto_name, peer->proto), state, VTY_NEWLINE); } DEFUN(show_peers, show_peers_cmd, @@ -44,8 +58,10 @@ DEFUN(show_peers, show_peers_cmd, { struct cbc_peer *peer; - vty_out(vty, " Name | IP | Port | Proto |%s", VTY_NEWLINE); - vty_out(vty, "---------------------|----------------|-------|-------|%s", VTY_NEWLINE); + vty_out(vty, +"|Name | IP | Port | Proto | State |%s", VTY_NEWLINE); + vty_out(vty, +"|--------------------|----------------|------|-------|---------------------|%s", VTY_NEWLINE); llist_for_each_entry(peer, &g_cbc->peers, list) dump_one_cbc_peer(vty, peer); @@ -53,6 +69,7 @@ DEFUN(show_peers, show_peers_cmd, } #define MESSAGES_STR "Display information about currently active SMSCB messages\n" +#define MESSAGES_CBS_STR "Display Cell Broadcast Service (CBS) messages\n" static void dump_one_cbc_msg(struct vty *vty, const struct cbc_message *cbc_msg) { @@ -69,7 +86,7 @@ static void dump_one_cbc_msg(struct vty *vty, const struct cbc_message *cbc_msg) DEFUN(show_messages_cbs, show_messages_cbs_cmd, "show messages cbs", - SHOW_STR MESSAGES_STR "Display Cell Broadcast Service (CBS) messages\n") + SHOW_STR MESSAGES_STR MESSAGES_CBS_STR) { struct cbc_message *cbc_msg; @@ -87,13 +104,127 @@ DEFUN(show_messages_cbs, show_messages_cbs_cmd, return CMD_SUCCESS; } +static const char *cbc_cell_id2str(const struct cbc_cell_id *cid) +{ + static char buf[256]; + + switch (cid->id_discr) { + case CBC_CELL_ID_NONE: + snprintf(buf, sizeof(buf), "NONE"); + break; + case CBC_CELL_ID_BSS: + snprintf(buf, sizeof(buf), "BSS"); + break; + case CBC_CELL_ID_CGI: + snprintf(buf, sizeof(buf), "CGI %s", osmo_cgi_name(&cid->u.cgi)); + break; + case CBC_CELL_ID_LAC_CI: + snprintf(buf, sizeof(buf), "LAC %u CI %u", cid->u.lac_and_ci.lac, cid->u.lac_and_ci.ci); + break; + case CBC_CELL_ID_LAI: + snprintf(buf, sizeof(buf), "LAI %s", osmo_lai_name(&cid->u.lai)); + break; + case CBC_CELL_ID_LAC: + snprintf(buf, sizeof(buf), "LAC %u", cid->u.lac); + break; + case CBC_CELL_ID_CI: + snprintf(buf, sizeof(buf), "CI %u", cid->u.ci); + break; + default: + snprintf(buf, sizeof(buf), ""); + break; + } + return buf; +} + +static void dump_one_msg_peer(struct vty *vty, const struct cbc_message_peer *msg_peer, const char *pfx) +{ + struct cbc_cell_id *cid; + + vty_out(vty, "%sPeer: '%s', State: %s%s", pfx, msg_peer->peer->name, + osmo_fsm_inst_state_name(msg_peer->fi), VTY_NEWLINE); + + + vty_out(vty, "%s Cells Installed:%s", pfx, VTY_NEWLINE); + llist_for_each_entry(cid, &msg_peer->cell_list, list) { + vty_out(vty, "%s %s%s", pfx, cbc_cell_id2str(cid), VTY_NEWLINE); + } + + vty_out(vty, "%s Cells Failed:%s", pfx, VTY_NEWLINE); + llist_for_each_entry(cid, &msg_peer->fail_list, list) { + vty_out(vty, "%s %s (cause=%d)%s", pfx, cbc_cell_id2str(cid), cid->fail.cause, VTY_NEWLINE); + } + + vty_out(vty, "%s Number of Broadcasts Completed:%s", pfx, VTY_NEWLINE); + llist_for_each_entry(cid, &msg_peer->num_compl_list, list) { + vty_out(vty, "%s %s (%u/%u)%s", pfx, cbc_cell_id2str(cid), + cid->num_compl.num_compl, cid->num_compl.num_bcast_info, VTY_NEWLINE); + } +} + + +DEFUN(show_message_cbs, show_message_cbs_cmd, + "show message id <0-65535>", + SHOW_STR MESSAGES_STR "Message ID\n" "Message ID\n") +{ + const struct cbc_message *cbc_msg; + const struct smscb_message *smscb; + struct cbc_message_peer *msg_peer; + char *timestr; + + cbc_msg = cbc_message_by_id(atoi(argv[0])); + if (!cbc_msg) { + vty_out(vty, "Unknown Messsage ID %s%s", argv[0], VTY_NEWLINE); + return CMD_WARNING; + } + smscb = &cbc_msg->msg; + + vty_out(vty, "Message ID %04X, Serial Number %04X, State: %s%s", smscb->message_id, smscb->serial_nr, + osmo_fsm_inst_state_name(cbc_msg->fi), VTY_NEWLINE); + timestr = ctime(&cbc_msg->time.created); + timestr[strlen(timestr)-1] = '\0'; /* stupid \n termination of ctime() */ + vty_out(vty, " Created by CBE '%s' at %s%s", cbc_msg->cbe_name, timestr, VTY_NEWLINE); + vty_out(vty, " Repetition Period: %u (%5.2fs), Number of broadcasts: %u%s", + cbc_msg->rep_period, (float)cbc_msg->rep_period * 1.883, + cbc_msg->num_bcast, VTY_NEWLINE); + + if (!smscb->is_etws) { + int i; + vty_out(vty, " Warning Period: %us%s", cbc_msg->warning_period_sec, VTY_NEWLINE); + vty_out(vty, " DCS: 0x%02x, Number of pages: %u, User Data Bytes: %u%s", smscb->cbs.dcs, + smscb->cbs.num_pages, smscb->cbs.data_user_len, VTY_NEWLINE); + for (i = 0; i < smscb->cbs.num_pages; i++) { + vty_out(vty, " Page %u: %s%s", i, + osmo_hexdump_nospc(smscb->cbs.data[i], sizeof(smscb->cbs.data[i])), + VTY_NEWLINE); + } + /* FIXME: more */ + } else { + vty_out(vty, " ETWS Warning Type Value: 0x%02x, User Alert: %s, Popup: %s%s", + smscb->etws.warning_type, smscb->etws.user_alert ? "On" : "Off", + smscb->etws.popup_on_display ? "On" : "Off", VTY_NEWLINE); + vty_out(vty, " Security: %s%s", + osmo_hexdump_nospc(smscb->etws.warning_sec_info, sizeof(smscb->etws.warning_sec_info)), + VTY_NEWLINE); + } + + llist_for_each_entry(msg_peer, &cbc_msg->peers, list) + dump_one_msg_peer(vty, msg_peer, " "); + + return CMD_SUCCESS; +} + static void dump_one_etws_msg(struct vty *vty, const struct cbc_message *cbc_msg) { const struct smscb_message *smscb = &cbc_msg->msg; OSMO_ASSERT(smscb->is_etws); - /* FIXME */ + vty_out(vty, "| %04X| %04X|%-20s|%-13s| %-4u|%c| %04d|%s", + smscb->message_id, smscb->serial_nr, cbc_msg->cbe_name, + get_value_string(cbsp_category_names, cbc_msg->priority), cbc_msg->rep_period, + cbc_msg->extended_cbch ? 'E' : 'N', smscb->etws.warning_type, + VTY_NEWLINE); } DEFUN(show_messages_etws, show_messages_etws_cmd, @@ -102,7 +233,12 @@ DEFUN(show_messages_etws, show_messages_etws_cmd, { struct cbc_message *cbc_msg; - /* FIXME: header */ + vty_out(vty, +"|MsgId|SerNo| CBE Name | Category |Period|E|Warning Type|%s", VTY_NEWLINE); + vty_out(vty, +"|-----|-----|--------------------|-------------|------|-|------------|%s", VTY_NEWLINE); + + llist_for_each_entry(cbc_msg, &g_cbc->messages, list) { if (!cbc_msg->msg.is_etws) @@ -264,6 +400,7 @@ static int config_write_peer(struct vty *vty) void cbc_vty_init(void) { install_lib_element_ve(&show_peers_cmd); + install_lib_element_ve(&show_message_cbs_cmd); install_lib_element_ve(&show_messages_cbs_cmd); install_lib_element_ve(&show_messages_etws_cmd); diff --git a/src/cbsp_server.c b/src/cbsp_server.c index ea7faf8..8a62790 100644 --- a/src/cbsp_server.c +++ b/src/cbsp_server.c @@ -44,6 +44,8 @@ struct osmo_cbsp_bsc { const char *cbsp_cbc_client_name(const struct osmo_cbsp_cbc_client *client) { + OSMO_ASSERT(client); + if (client->peer && client->peer->name) { return client->peer->name; } else { @@ -102,6 +104,8 @@ static int cbsp_cbc_closed_cb(struct osmo_stream_srv *conn) struct osmo_cbsp_cbc_client *client = osmo_stream_srv_get_data(conn); LOGPCC(client, LOGL_NOTICE, "connection closed\n"); + if (client->peer) + client->peer->client.cbsp = NULL; client->conn = NULL; osmo_fsm_inst_dispatch(client->fi, CBSP_SRV_E_CMD_CLOSE, NULL); @@ -170,9 +174,18 @@ static int cbsp_cbc_accept_cb(struct osmo_stream_srv_link *link, int fd) void cbsp_cbc_client_tx(struct osmo_cbsp_cbc_client *client, struct osmo_cbsp_decoded *cbsp) { - struct msgb *msg = osmo_cbsp_encode(client, cbsp); + struct msgb *msg; + + if (!client) { + LOGP(DCBSP, LOGL_NOTICE, "Cannot transmit %s: no connection\n", + get_value_string(cbsp_msg_type_names, cbsp->msg_type)); + return ; + } + LOGPCC(client, LOGL_INFO, "Transmitting %s\n", get_value_string(cbsp_msg_type_names, cbsp->msg_type)); + + msg = osmo_cbsp_encode(client, cbsp); if (!msg) { LOGPCC(client, LOGL_ERROR, "Failed to encode CBSP %s: %s\n", get_value_string(cbsp_msg_type_names, cbsp->msg_type), osmo_cbsp_errstr); diff --git a/src/cbsp_server_fsm.c b/src/cbsp_server_fsm.c index 1521e1e..8fc16a8 100644 --- a/src/cbsp_server_fsm.c +++ b/src/cbsp_server_fsm.c @@ -106,6 +106,7 @@ static void cbsp_server_s_keepalive_pending(struct osmo_fsm_inst *fi, uint32_t e { switch (event) { case CBSP_SRV_E_RX_KA_COMPL: + osmo_fsm_inst_state_chg(fi, CBSP_SRV_S_IDLE, 0, 0); break; default: OSMO_ASSERT(0); @@ -157,12 +158,30 @@ static int cbsp_server_fsm_timer_cb(struct osmo_fsm_inst *fi) static void cbsp_server_fsm_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data) { + struct osmo_cbsp_cbc_client *client = (struct osmo_cbsp_cbc_client *) fi->priv; + struct osmo_cbsp_decoded *dec; + switch (event) { case CBSP_SRV_E_CMD_CLOSE: osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REQUEST, NULL); break; case CBSP_SRV_E_RX_RESTART: - /* FIXME: */ + dec = data; + /* if BSC has _not_ lost messages, skip */ + if (dec->u.restart.recovery_ind == 0) + break; + switch (dec->u.restart.bcast_msg_type) { + case 0: /* CBS */ + /* TODO: delete any CBS state we have for this peer */ + /* TODO: re-send CBS messages we have matching the scope of the peer */ + LOGPCC(client, LOGL_NOTICE, "RESTART (CBS) but re-sending not implemented yet\n"); + break; + case 1: /* ETWS */ + /* TODO: delete any ETWS state we have for this peer */ + /* TODO: re-send ETWS messages we have matching the scope of the peer */ + LOGPCC(client, LOGL_NOTICE, "RESTART (ETWS) but re-sending not implemented yet\n"); + break; + } break; default: OSMO_ASSERT(0); diff --git a/src/message_handling.c b/src/message_handling.c index b7092cf..8d66d01 100644 --- a/src/message_handling.c +++ b/src/message_handling.c @@ -96,9 +96,18 @@ struct osmo_cbsp_decoded *cbcmsg_to_cbsp(void *ctx, const struct cbc_message *cb llist_add_tail(&ce->list, &wrepl->u.cbs.msg_content); } } else { - /* FIXME */ - talloc_free(cbsp); - return NULL; + wrepl->u.emergency.indicator = 1; + wrepl->u.emergency.warning_type = (smscb->etws.warning_type & 0x7f) << 9; + if (smscb->etws.user_alert) + wrepl->u.emergency.warning_type |= 0x0100; + if (smscb->etws.popup_on_display) + wrepl->u.emergency.warning_type |= 0x0080; + memcpy(wrepl->u.emergency.warning_sec_info, smscb->etws.warning_sec_info, + sizeof(wrepl->u.emergency.warning_sec_info)); + if (cbcmsg->warning_period_sec == 0xffffffff) + wrepl->u.emergency.warning_period = 0; + else + wrepl->u.emergency.warning_period = cbcmsg->warning_period_sec; } return cbsp; } diff --git a/src/rest_api.c b/src/rest_api.c index 2a976b2..747abb5 100644 --- a/src/rest_api.c +++ b/src/rest_api.c @@ -131,10 +131,10 @@ static const struct value_string ts23041_warning_type_vals[] = { }; /* parse a smscb.schema.json/warning_type (either encoded or decoded) */ -static int parse_warning_type(uint16_t *out, json_t *in, const char **errstr) +static int parse_warning_type(json_t *in, const char **errstr) { json_t *jtmp; - int i, rc; + int i, rc, val; if (!in || !json_is_object(in)) { *errstr = "'warning_type' must be object"; @@ -142,7 +142,7 @@ static int parse_warning_type(uint16_t *out, json_t *in, const char **errstr) } rc = json_get_integer_range(&i, in, "warning_type_encoded", 0, 255); if (rc == 0) { - *out = i; + val = i; } else if (rc == -ENOENT && (jtmp = json_object_get(in, "warning_type_decoded"))) { const char *tstr = json_string_value(jtmp); if (!tstr) { @@ -154,13 +154,13 @@ static int parse_warning_type(uint16_t *out, json_t *in, const char **errstr) *errstr = "'warning_type_decoded' is invalid"; return -EINVAL; } - *out = i; + val = i; } else { *errstr = "either 'warning_type_encoded' or 'warning_type_decoded' must be present"; return -EINVAL; } - return 0; + return val; } /* parse a smscb.schema.json/serial_nr type (either encoded or decoded) */ @@ -260,6 +260,7 @@ static int parse_payload_decoded(struct smscb_message *out, json_t *jtmp, const out->cbs.dcs = rc; else { /* TODO: we must encode it in the first 3 characters */ + out->cbs.dcs = 0x0f; } } else { if (json_object_get(jtmp, "dcs_class")) { @@ -359,17 +360,36 @@ static int json2payload(struct smscb_message *out, json_t *in, const char **errs out->is_etws = false; return parse_payload_decoded(out, jtmp, errstr); } else if ((jtmp = json_object_get(in, "payload_etws"))) { - json_t *jwtype; + json_t *jwtype, *jtmp2; const char *wsecinfo_str; + out->is_etws = true; - /* Warning Type */ + + /* Warning Type (value) */ jwtype = json_object_get(jtmp, "warning_type"); if (!jwtype) { *errstr = "'warning_type' must be object"; return -EINVAL; } - if (parse_warning_type(&out->etws.warning_type, jwtype, errstr) < 0) + rc = parse_warning_type(jwtype, errstr); + if (rc < 0) return -EINVAL; + out->etws.warning_type = rc; + + /* Emergency User Alert */ + jtmp2 = json_object_get(jtmp, "emergency_user_alert"); + if (jtmp && json_is_true(jtmp2)) + out->etws.user_alert = true; + else + out->etws.user_alert = false; + + /* Popup */ + jtmp2 = json_object_get(jtmp, "popup_on_display"); + if (jtmp && json_is_true(jtmp2)) + out->etws.popup_on_display = true; + else + out->etws.popup_on_display = false; + /* Warning Security Info */ wsecinfo_str = json_get_string(jtmp, "warning_sec_info"); if (wsecinfo_str) { @@ -531,7 +551,8 @@ static int api_cb_message_post(const struct _u_request *req, struct _u_response if (!riop) { LOGP(DREST, LOGL_ERROR, "Out of memory\n"); - return -ENOMEM; + ulfius_set_string_body_response(resp, 500, "Out of memory"); + return U_CALLBACK_COMPLETE; } riop->operation = REST_IT_OP_MSG_CREATE; @@ -595,7 +616,7 @@ static int api_cb_message_del(const struct _u_request *req, struct _u_response * } if (!riop) { - status = 999; /* FIXME */ + status = 500; goto err; } diff --git a/src/smscb_message_fsm.c b/src/smscb_message_fsm.c index 7dd083e..0c3bdec 100644 --- a/src/smscb_message_fsm.c +++ b/src/smscb_message_fsm.c @@ -47,7 +47,7 @@ static void smscb_fsm_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) cbcmsg->it_op = data; osmo_fsm_inst_state_chg(fi, SMSCB_S_WAIT_WRITE_ACK, 15, T_WAIT_WRITE_ACK); /* forward this event to all child FSMs (i.e. all smscb_message_peer) */ - osmo_fsm_inst_broadcast_children(fi, SMSCB_E_CREATE, data); + osmo_fsm_inst_broadcast_children(fi, SMSCB_E_CREATE, NULL); break; default: OSMO_ASSERT(0); @@ -127,7 +127,7 @@ static void smscb_fsm_wait_replace_ack(struct osmo_fsm_inst *fi, uint32_t event, case SMSCB_E_CBSP_REPLACE_ACK: case SMSCB_E_CBSP_REPLACE_NACK: mp = data; - llist_for_each_entry(peer_fi, &fi->proc.children, list) { + llist_for_each_entry(peer_fi, &fi->proc.children, proc.child) { if (peer_fi->state == SMSCB_S_WAIT_REPLACE_ACK) break; } @@ -157,7 +157,7 @@ static void smscb_fsm_wait_status_ack(struct osmo_fsm_inst *fi, uint32_t event, case SMSCB_E_CBSP_STATUS_ACK: case SMSCB_E_CBSP_STATUS_NACK: mp = data; - llist_for_each_entry(peer_fi, &fi->proc.children, list) { + llist_for_each_entry(peer_fi, &fi->proc.children, proc.child) { if (peer_fi->state == SMSCB_S_WAIT_STATUS_ACK) break; } @@ -187,7 +187,7 @@ static void smscb_fsm_wait_delete_ack(struct osmo_fsm_inst *fi, uint32_t event, case SMSCB_E_CBSP_DELETE_ACK: case SMSCB_E_CBSP_DELETE_NACK: mp = data; - llist_for_each_entry(peer_fi, &fi->proc.children, list) { + llist_for_each_entry(peer_fi, &fi->proc.children, proc.child) { if (peer_fi->state == SMSCB_S_WAIT_DELETE_ACK) break; } @@ -266,7 +266,7 @@ static struct osmo_fsm_state smscb_fsm_states[] = { .name = "WAIT_DELETE_ACK", .in_event_mask = S(SMSCB_E_CBSP_DELETE_ACK) | S(SMSCB_E_CBSP_DELETE_NACK), - .out_state_mask = S(SMSCB_S_ACTIVE), + .out_state_mask = S(SMSCB_S_DELETED), .action = smscb_fsm_wait_delete_ack, .onleave = smscb_fsm_wait_delete_ack_onleave, }, @@ -365,6 +365,7 @@ struct cbc_message *cbc_message_alloc(void *ctx, const struct cbc_message *orig_ } /* copy data from original message */ memcpy(smscb, orig_msg, sizeof(*smscb)); + smscb->cbe_name = talloc_strdup(smscb, orig_msg->cbe_name); /* initialize other members */ INIT_LLIST_HEAD(&smscb->peers); smscb->fi = fi; diff --git a/src/smscb_peer_fsm.c b/src/smscb_peer_fsm.c index ac60603..f6ac15c 100644 --- a/src/smscb_peer_fsm.c +++ b/src/smscb_peer_fsm.c @@ -458,6 +458,13 @@ static int smscb_p_fsm_timer_cb(struct osmo_fsm_inst *fi) return 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", @@ -503,6 +510,9 @@ static const struct osmo_fsm_state smscb_p_fsm_states[] = { .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 = { @@ -512,6 +522,7 @@ struct osmo_fsm smscb_p_fsm = { .timer_cb = smscb_p_fsm_timer_cb, .log_subsys = DCBSP, .event_names = smscb_fsm_event_names, + .cleanup = smscb_p_fsm_cleanup, }; static __attribute__((constructor)) void on_dso_load_smscb_p_fsm(void)