More massive CBC WIP

Using this state we can actually successfully add mesasges via
the REST interface and see them being sent via CBSP to the BSCs,
who then transmit to BTSs who send it to MSs and the MSs show them...

Change-Id: If29bd4bbbf88a0ca58de9ff29ad524b0a7262a8e
This commit is contained in:
Harald Welte 2021-01-02 16:11:27 +01:00
parent ddb5057462
commit 76101c4a86
9 changed files with 246 additions and 32 deletions

View File

@ -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" ]

View File

@ -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;
};

View File

@ -24,18 +24,32 @@
#include <stdlib.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/fsm.h>
#include <osmocom/vty/command.h>
#include <osmocom/vty/buffer.h>
#include <osmocom/vty/vty.h>
#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 = "<disconnected>";
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 : "<unnamed>", 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), "<invalid>");
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);

View File

@ -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);

View File

@ -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);

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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)