libosmo-sccp/src/xua_as_fsm.c

309 lines
7.6 KiB
C

/* SCCP M3UA / SUA AS osmo_fsm according to RFC3868 4.3.1 / RFC4666 4.3.2 */
/* (C) Copyright 2017 by Harald Welte <laforge@gnumonks.org>
*
* All Rights reserved.
*
* Based on Erlang implementation xua_as_fsm.erl in osmo-ss7.git
*/
#include <string.h>
#include <arpa/inet.h>
#include <osmocom/core/fsm.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/timer.h>
#include <osmocom/core/prim.h>
#include <osmocom/core/logging.h>
#include <osmocom/sigtran/osmo_ss7.h>
#include <osmocom/sigtran/sigtran_sap.h>
#include <osmocom/sigtran/xua_msg.h>
#include <osmocom/sigtran/protocol/sua.h>
#include <osmocom/sigtran/protocol/m3ua.h>
#include "xua_asp_fsm.h"
#include "xua_as_fsm.h"
#include "xua_internal.h"
static struct msgb *encode_notify(const struct m3ua_notify_params *npar)
{
struct xua_msg *xua = m3ua_encode_notify(npar);
struct msgb *msg = xua_to_msg(M3UA_VERSION, xua);
xua_msg_free(xua);
return msg;
}
static int asp_notify_all_as(struct osmo_ss7_as *as, struct m3ua_notify_params *npar)
{
struct msgb *msg;
unsigned int i, sent = 0;
/* iterate over all non-DOWN ASPs and send them the message */
for (i = 0; i < ARRAY_SIZE(as->cfg.asps); i++) {
struct osmo_ss7_asp *asp = as->cfg.asps[i];
if (!asp)
continue;
if (!asp->fi || asp->fi->state == XUA_ASP_S_DOWN)
continue;
/* Optional: ASP Identifier (if sent in ASP-UP) */
if (asp->asp_id_present) {
npar->presence |= NOTIFY_PAR_P_ASP_ID;
npar->asp_id = asp->asp_id;
} else
npar->presence &= ~NOTIFY_PAR_P_ASP_ID;
/* TODO: Optional Routing Context */
msg = encode_notify(npar);
osmo_ss7_asp_send(asp, msg);
sent++;
}
return sent;
}
/***********************************************************************
* Actual FSM
***********************************************************************/
#define S(x) (1 << (x))
enum xua_as_state {
XUA_AS_S_DOWN,
XUA_AS_S_INACTIVE,
XUA_AS_S_ACTIVE,
XUA_AS_S_PENDING,
};
static const struct value_string xua_as_event_names[] = {
{ XUA_ASPAS_ASP_INACTIVE_IND, "ASPAS-ASP_INACTIVE.ind" },
{ XUA_ASPAS_ASP_DOWN_IND, "ASPAS-ASP_DOWN.ind" },
{ XUA_ASPAS_ASP_ACTIVE_IND, "ASPAS-ASP_ACTIVE.ind" },
{ 0, NULL }
};
struct xua_as_fsm_priv {
struct osmo_ss7_as *as;
};
/* is any other ASP in this AS in state != DOWN? */
static bool check_any_other_asp_not_down(struct osmo_ss7_as *as, struct osmo_ss7_asp *asp_cmp)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(as->cfg.asps); i++) {
struct osmo_ss7_asp *asp = as->cfg.asps[i];
if (!asp)
continue;
if (asp_cmp == asp)
continue;
if (asp->fi && asp->fi->state != XUA_ASP_S_DOWN)
return true;
}
return false;
}
/* is any other ASP in this AS in state ACTIVE? */
static bool check_any_other_asp_in_active(struct osmo_ss7_as *as, struct osmo_ss7_asp *asp_cmp)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(as->cfg.asps); i++) {
struct osmo_ss7_asp *asp = as->cfg.asps[i];
if (!asp)
continue;
if (asp_cmp == asp)
continue;
if (asp->fi && asp->fi->state == XUA_ASP_S_ACTIVE)
return true;
}
return false;
}
static void xua_as_fsm_down(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
case XUA_ASPAS_ASP_INACTIVE_IND:
/* one ASP transitions into ASP-INACTIVE */
osmo_fsm_inst_state_chg(fi, XUA_AS_S_INACTIVE, 0, 0);
break;
case XUA_ASPAS_ASP_DOWN_IND:
/* ignore */
break;
}
}
/* onenter call-back responsible of transmitting NTFY to all ASPs in
* case of AS state changes */
static void xua_as_fsm_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
{
struct xua_as_fsm_priv *xafp = (struct xua_as_fsm_priv *) fi->priv;
struct m3ua_notify_params npar = {
.status_type = M3UA_NOTIFY_T_STATCHG,
};
switch (fi->state) {
case XUA_AS_S_INACTIVE:
npar.status_info = M3UA_NOTIFY_I_AS_INACT;
break;
case XUA_AS_S_ACTIVE:
npar.status_info = M3UA_NOTIFY_I_AS_ACT;
break;
case XUA_AS_S_PENDING:
npar.status_info = M3UA_NOTIFY_I_AS_PEND;
break;
default:
return;
}
asp_notify_all_as(xafp->as, &npar);
};
static void xua_as_fsm_inactive(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct xua_as_fsm_priv *xafp = (struct xua_as_fsm_priv *) fi->priv;
struct osmo_ss7_asp *asp = data;
switch (event) {
case XUA_ASPAS_ASP_DOWN_IND:
/* one ASP transitions into ASP-DOWN */
if (check_any_other_asp_not_down(xafp->as, asp)) {
/* ignore, we stay AS_INACTIVE */
} else
osmo_fsm_inst_state_chg(fi, XUA_AS_S_DOWN, 0, 0);
break;
case XUA_ASPAS_ASP_ACTIVE_IND:
/* one ASP transitions into ASP-ACTIVE */
osmo_fsm_inst_state_chg(fi, XUA_AS_S_ACTIVE, 0, 0);
break;
case XUA_ASPAS_ASP_INACTIVE_IND:
/* ignore */
break;
}
}
static void xua_as_fsm_active(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct xua_as_fsm_priv *xafp = (struct xua_as_fsm_priv *) fi->priv;
struct osmo_ss7_asp *asp = data;
switch (event) {
case XUA_ASPAS_ASP_DOWN_IND:
case XUA_ASPAS_ASP_INACTIVE_IND:
if (check_any_other_asp_in_active(xafp->as, asp)) {
/* ignore, we stay AS_ACTIVE */
} else {
osmo_fsm_inst_state_chg(fi, XUA_AS_S_PENDING, 0, 0);
/* FIXME: Start T(r) */
/* FIXME: Queue all signalling messages until
* recovery or T(r) expiry */
}
break;
case XUA_ASPAS_ASP_ACTIVE_IND:
/* ignore */
break;
}
}
static void xua_as_fsm_pending(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
case XUA_ASPAS_ASP_ACTIVE_IND:
/* one ASP transitions into ASP-ACTIVE */
osmo_fsm_inst_state_chg(fi, XUA_AS_S_ACTIVE, 0, 0);
break;
case XUA_ASPAS_ASP_INACTIVE_IND:
/* ignore */
break;
case XUA_ASPAS_ASP_DOWN_IND:
/* ignore */
break;
}
}
static const struct osmo_fsm_state xua_as_fsm_states[] = {
[XUA_AS_S_DOWN] = {
.in_event_mask = S(XUA_ASPAS_ASP_INACTIVE_IND) |
S(XUA_ASPAS_ASP_DOWN_IND),
.out_state_mask = S(XUA_AS_S_DOWN) |
S(XUA_AS_S_INACTIVE),
.name = "AS_DOWN",
.action = xua_as_fsm_down,
},
[XUA_AS_S_INACTIVE] = {
.in_event_mask = S(XUA_ASPAS_ASP_DOWN_IND) |
S(XUA_ASPAS_ASP_ACTIVE_IND) |
S(XUA_ASPAS_ASP_INACTIVE_IND),
.out_state_mask = S(XUA_AS_S_DOWN) |
S(XUA_AS_S_INACTIVE) |
S(XUA_AS_S_ACTIVE),
.name = "AS_INACTIVE",
.action = xua_as_fsm_inactive,
.onenter = xua_as_fsm_onenter,
},
[XUA_AS_S_ACTIVE] = {
.in_event_mask = S(XUA_ASPAS_ASP_DOWN_IND) |
S(XUA_ASPAS_ASP_INACTIVE_IND) |
S(XUA_ASPAS_ASP_ACTIVE_IND),
.out_state_mask = S(XUA_AS_S_ACTIVE) |
S(XUA_AS_S_PENDING),
.name = "AS_ACTIVE",
.action = xua_as_fsm_active,
.onenter = xua_as_fsm_onenter,
},
[XUA_AS_S_PENDING] = {
.in_event_mask = S(XUA_ASPAS_ASP_INACTIVE_IND) |
S(XUA_ASPAS_ASP_DOWN_IND) |
S(XUA_ASPAS_ASP_ACTIVE_IND),
.out_state_mask = S(XUA_AS_S_DOWN) |
S(XUA_AS_S_INACTIVE) |
S(XUA_AS_S_ACTIVE) |
S(XUA_AS_S_PENDING),
.name = "AS_PENDING",
.action = xua_as_fsm_pending,
.onenter = xua_as_fsm_onenter,
},
};
struct osmo_fsm xua_as_fsm = {
.name = "XUA_AS",
.states = xua_as_fsm_states,
.num_states = ARRAY_SIZE(xua_as_fsm_states),
.log_subsys = DLSS7,
.event_names = xua_as_event_names,
};
/*! \brief Start an AS FSM for a given Application Server
* \param[in] as Application Server for which to start the AS FSM
* \param[in] log_level Logging level for logging of this FSM
* \returns FSM instance in case of success; NULL in case of error */
struct osmo_fsm_inst *xua_as_fsm_start(struct osmo_ss7_as *as, int log_level)
{
struct osmo_fsm_inst *fi;
struct xua_as_fsm_priv *xafp;
fi = osmo_fsm_inst_alloc(&xua_as_fsm, as, NULL, log_level, as->cfg.name);
xafp = talloc_zero(fi, struct xua_as_fsm_priv);
if (!xafp) {
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
return NULL;
}
xafp->as = as;
fi->priv = xafp;
return fi;
}