diff options
author | Alexander Couzens <lynxis@fe80.eu> | 2020-07-12 13:45:50 +0200 |
---|---|---|
committer | lynxis lazus <lynxis@fe80.eu> | 2020-09-15 11:54:41 +0000 |
commit | 6a161497cfd8cacfef10c2e81cff85a45447e107 (patch) | |
tree | 0d068f1a51d256545183a9f2690b75099c6c050e /src/gb/gprs_ns2_vc_fsm.c | |
parent | f6db7653270f4c926cd64f2597f19fd657520ae8 (diff) |
Gb: add a second NS implementation
Reimplement NS with FSM.
Change-Id: I3525beef205588dfab9d3880a34115f1a2676e48
Diffstat (limited to 'src/gb/gprs_ns2_vc_fsm.c')
-rw-r--r-- | src/gb/gprs_ns2_vc_fsm.c | 675 |
1 files changed, 675 insertions, 0 deletions
diff --git a/src/gb/gprs_ns2_vc_fsm.c b/src/gb/gprs_ns2_vc_fsm.c new file mode 100644 index 00000000..7487ecde --- /dev/null +++ b/src/gb/gprs_ns2_vc_fsm.c @@ -0,0 +1,675 @@ +/*! \file gprs_ns2_vc_fsm.c + * NS virtual circuit FSM implementation + * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05) + * as well as its successor 3GPP TS 48.016 */ + +/* (C) 2020 sysmocom - s.f.m.c. GmbH + * Author: Alexander Couzens <lynxis@fe80.eu> + * + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +/* The BSS NSE only has one SGSN IP address configured, and it will use the SNS procedures + * to communicated its local IPs/ports as well as all the SGSN side IPs/ports and + * associated weights. In theory, the BSS then uses this to establish a full mesh + * of NSVCs between all BSS-side IPs/ports and SGSN-side IPs/ports */ + +#include <errno.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <osmocom/core/fsm.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/rate_ctr.h> +#include <osmocom/core/socket.h> +#include <osmocom/core/stat_item.h> +#include <osmocom/gsm/prim.h> +#include <osmocom/gsm/tlv.h> +#include <osmocom/gprs/gprs_msgb.h> +#include <osmocom/gprs/protocol/gsm_08_16.h> + +#include "gprs_ns2_internal.h" + +#define S(x) (1 << (x)) + +#define DNS 10 + +struct gprs_ns2_vc_priv { + struct gprs_ns2_vc *nsvc; + /* how often the timer was triggered */ + int N; + /* The initiater is responsible to UNBLOCK the VC. The BSS is usually the initiater. + * It can change while runtime. The side which blocks an unblocked side.*/ + bool initiater; + + /* the alive counter is present in all states */ + struct { + struct osmo_timer_list timer; + enum ns2_timeout mode; + int N; + struct timeval timer_started; + } alive; +}; + + +/* The FSM covers both the VC with RESET/BLOCK and without RESET/BLOCK procedure.. + * + * With RESET/BLOCK, the state should follow: + * - UNCONFIGURED -> RESET -> BLOCK -> UNBLOCKED + * + * Without RESET/BLOCK, the state should follow: + * - UNCONFIGURED -> ALIVE -> UNBLOCKED + * + * The UNBLOCKED and TEST states are used to send ALIVE PDU using the timeout Tns-test and Tns-alive. + * UNBLOCKED -> TEST: on expire of Tns-Test, send Alive PDU. + * TEST -> UNBLOCKED: on receive of Alive_Ack PDU, go into UNBLOCKED. + * + * The ALIVE state is used as intermediate, because a VC is only valid if it received an Alive ACK when + * not using RESET/BLOCK procedure. + */ + +enum gprs_ns2_vc_state { + GPRS_NS2_ST_UNCONFIGURED, + GPRS_NS2_ST_RESET, + GPRS_NS2_ST_BLOCKED, + GPRS_NS2_ST_UNBLOCKED, /* allows sending NS_UNITDATA */ + + GPRS_NS2_ST_ALIVE, /* only used when not using RESET/BLOCK procedure */ +}; + +enum gprs_ns2_vc_event { + GPRS_NS2_EV_START, + + /* received messages */ + GPRS_NS2_EV_RESET, + GPRS_NS2_EV_RESET_ACK, + GPRS_NS2_EV_UNBLOCK, + GPRS_NS2_EV_UNBLOCK_ACK, + GPRS_NS2_EV_BLOCK, + GPRS_NS2_EV_BLOCK_ACK, + GPRS_NS2_EV_ALIVE, + GPRS_NS2_EV_ALIVE_ACK, + GPRS_NS2_EV_STATUS, + + GPRS_NS2_EV_UNITDATA, +}; + +static const struct value_string gprs_ns2_vc_event_names[] = { + { GPRS_NS2_EV_START, "START" }, + { GPRS_NS2_EV_RESET, "RESET" }, + { GPRS_NS2_EV_RESET_ACK, "RESET_ACK" }, + { GPRS_NS2_EV_UNBLOCK, "UNBLOCK" }, + { GPRS_NS2_EV_UNBLOCK_ACK, "UNBLOCK_ACK" }, + { GPRS_NS2_EV_BLOCK, "BLOCK" }, + { GPRS_NS2_EV_BLOCK_ACK, "BLOCK_ACK" }, + { GPRS_NS2_EV_ALIVE, "ALIVE" }, + { GPRS_NS2_EV_ALIVE_ACK, "ALIVE_ACK" }, + { GPRS_NS2_EV_STATUS, "STATUS" }, + { GPRS_NS2_EV_UNITDATA, "UNITDATA" }, + { 0, NULL } +}; + +static inline struct gprs_ns2_inst *ns_inst_from_fi(struct osmo_fsm_inst *fi) +{ + struct gprs_ns2_vc_priv *priv = fi->priv; + return priv->nsvc->nse->nsi; +} + +static void start_test_procedure(struct gprs_ns2_vc_priv *priv) +{ + struct gprs_ns2_inst *nsi = priv->nsvc->nse->nsi; + + if (osmo_timer_pending(&priv->alive.timer)) + return; + + priv->alive.mode = NS_TOUT_TNS_ALIVE; + priv->alive.N = 0; + + osmo_gettimeofday(&priv->alive.timer_started, NULL); + ns2_tx_alive(priv->nsvc); + osmo_timer_schedule(&priv->alive.timer, nsi->timeout[NS_TOUT_TNS_ALIVE], 0); +} + +static void stop_test_procedure(struct gprs_ns2_vc_priv *priv) +{ + osmo_timer_del(&priv->alive.timer); +} + +static int alive_timer_elapsed_ms(struct gprs_ns2_vc_priv *priv) +{ + struct timeval now, elapsed; + osmo_gettimeofday(&now, NULL); + timersub(&now, &priv->alive.timer_started, &elapsed); + + return 1000 * elapsed.tv_sec + elapsed.tv_usec / 1000; +} + +static void recv_test_procedure(struct osmo_fsm_inst *fi) +{ + struct gprs_ns2_vc_priv *priv = fi->priv; + struct gprs_ns2_inst *nsi = ns_inst_from_fi(fi); + struct gprs_ns2_vc *nsvc = priv->nsvc; + + /* ignoring ACKs without sending an ALIVE */ + if (priv->alive.mode != NS_TOUT_TNS_ALIVE) + return; + + priv->alive.mode = NS_TOUT_TNS_TEST; + osmo_timer_schedule(&priv->alive.timer, nsi->timeout[NS_TOUT_TNS_TEST], 0); + osmo_stat_item_set(nsvc->statg->items[NS_STAT_ALIVE_DELAY], + alive_timer_elapsed_ms(priv)); +} + + +static void alive_timeout_handler(void *data) +{ + struct osmo_fsm_inst *fi = data; + struct gprs_ns2_inst *nsi = ns_inst_from_fi(fi); + struct gprs_ns2_vc_priv *priv = fi->priv; + + switch (priv->alive.mode) { + case NS_TOUT_TNS_TEST: + priv->alive.mode = NS_TOUT_TNS_ALIVE; + ns2_tx_alive(priv->nsvc); + osmo_timer_schedule(&priv->alive.timer, nsi->timeout[NS_TOUT_TNS_ALIVE], 0); + break; + case NS_TOUT_TNS_ALIVE: + priv->alive.N++; + + if (priv->alive.N <= nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES]) { + /* retransmission */ + ns2_tx_alive(priv->nsvc); + osmo_timer_schedule(&priv->alive.timer, nsi->timeout[NS_TOUT_TNS_ALIVE], 0); + } else { + /* lost connection */ + if (priv->nsvc->mode == NS2_VC_MODE_BLOCKRESET) { + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RESET, nsi->timeout[NS_TOUT_TNS_RESET], 0); + } else { + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_ALIVE, nsi->timeout[NS_TOUT_TNS_ALIVE], 0); + } + } + break; + default: + break; + } +} + +static void gprs_ns2_st_unconfigured(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gprs_ns2_vc_priv *priv = fi->priv; + struct gprs_ns2_inst *nsi = priv->nsvc->nse->nsi; + + switch (event) { + case GPRS_NS2_EV_START: + switch (priv->nsvc->mode) { + case NS2_VC_MODE_ALIVE: + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_ALIVE, nsi->timeout[NS_TOUT_TNS_ALIVE], NS_TOUT_TNS_ALIVE); + break; + case NS2_VC_MODE_BLOCKRESET: + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RESET, nsi->timeout[NS_TOUT_TNS_RESET], NS_TOUT_TNS_RESET); + break; + } + + break; + default: + OSMO_ASSERT(0); + } +} + + +static void gprs_ns2_st_reset_onenter(struct osmo_fsm_inst *fi, uint32_t old_state) +{ + struct gprs_ns2_vc_priv *priv = fi->priv; + + if (old_state != GPRS_NS2_ST_RESET) + priv->N = 0; + + if (priv->initiater) + ns2_tx_reset(priv->nsvc, NS_CAUSE_OM_INTERVENTION); + + stop_test_procedure(priv); + ns2_nse_notify_unblocked(priv->nsvc, false); +} + +static void gprs_ns2_st_reset(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gprs_ns2_inst *nsi = ns_inst_from_fi(fi); + struct gprs_ns2_vc_priv *priv = fi->priv; + + if (priv->initiater) { + switch (event) { + case GPRS_NS2_EV_RESET_ACK: + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED, + nsi->timeout[NS_TOUT_TNS_BLOCK], NS_TOUT_TNS_BLOCK); + break; + } + } else { + /* we are on the receiving end */ + switch (event) { + case GPRS_NS2_EV_RESET: + ns2_tx_reset_ack(priv->nsvc); + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED, + 0, 0); + break; + } + } +} + +static void gprs_ns2_st_blocked_onenter(struct osmo_fsm_inst *fi, uint32_t old_state) +{ + struct gprs_ns2_vc_priv *priv = fi->priv; + + if (old_state != GPRS_NS2_ST_BLOCKED) + priv->N = 0; + + if (priv->initiater) + ns2_tx_unblock(priv->nsvc); + + start_test_procedure(priv); +} + +static void gprs_ns2_st_blocked(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gprs_ns2_vc_priv *priv = fi->priv; + + if (priv->initiater) { + switch (event) { + case GPRS_NS2_EV_BLOCK: + /* TODO: BLOCK is a UNBLOCK_NACK */ + ns2_tx_block_ack(priv->nsvc); + break; + case GPRS_NS2_EV_UNBLOCK: + ns2_tx_unblock_ack(priv->nsvc); + /* fall through */ + case GPRS_NS2_EV_UNBLOCK_ACK: + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_UNBLOCKED, + 0, NS_TOUT_TNS_TEST); + break; + } + } else { + /* we are on the receiving end. The initiator who sent RESET is responsible to UNBLOCK! */ + switch (event) { + case GPRS_NS2_EV_UNBLOCK: + ns2_tx_unblock_ack(priv->nsvc); + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_UNBLOCKED, + 0, 0); + break; + } + } +} + +static void gprs_ns2_st_unblocked_on_enter(struct osmo_fsm_inst *fi, uint32_t old_state) +{ + struct gprs_ns2_vc_priv *priv = fi->priv; + + ns2_nse_notify_unblocked(priv->nsvc, true); +} + +static void gprs_ns2_st_unblocked(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gprs_ns2_vc_priv *priv = fi->priv; + + switch (event) { + case GPRS_NS2_EV_BLOCK: + priv->initiater = false; + ns2_tx_block_ack(priv->nsvc); + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED, + 0, 2); + break; + } +} + +static void gprs_ns2_st_alive(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case GPRS_NS2_EV_ALIVE_ACK: + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_UNBLOCKED, 0, 0); + break; + } +} + +static void gprs_ns2_st_alive_onenter(struct osmo_fsm_inst *fi, uint32_t old_state) +{ + struct gprs_ns2_vc_priv *priv = fi->priv; + struct gprs_ns2_inst *nsi = ns_inst_from_fi(fi); + + priv->alive.mode = NS_TOUT_TNS_TEST; + osmo_timer_schedule(&priv->alive.timer, nsi->timeout[NS_TOUT_TNS_TEST], 0); + + if (old_state != GPRS_NS2_ST_ALIVE) + priv->N = 0; + + ns2_tx_alive(priv->nsvc); + ns2_nse_notify_unblocked(priv->nsvc, false); +} + +static void gprs_ns2_st_alive_onleave(struct osmo_fsm_inst *fi, uint32_t next_state) +{ + start_test_procedure(fi->priv); +} + +static const struct osmo_fsm_state gprs_ns2_vc_states[] = { + [GPRS_NS2_ST_UNCONFIGURED] = { + .in_event_mask = S(GPRS_NS2_EV_START), + .out_state_mask = S(GPRS_NS2_ST_RESET) | S(GPRS_NS2_ST_ALIVE), + .name = "UNCONFIGURED", + .action = gprs_ns2_st_unconfigured, + }, + [GPRS_NS2_ST_RESET] = { + .in_event_mask = S(GPRS_NS2_EV_RESET_ACK) | S(GPRS_NS2_EV_RESET), + .out_state_mask = S(GPRS_NS2_ST_RESET) | + S(GPRS_NS2_ST_BLOCKED), + .name = "RESET", + .action = gprs_ns2_st_reset, + .onenter = gprs_ns2_st_reset_onenter, + }, + [GPRS_NS2_ST_BLOCKED] = { + .in_event_mask = S(GPRS_NS2_EV_BLOCK) | S(GPRS_NS2_EV_BLOCK_ACK) | + S(GPRS_NS2_EV_UNBLOCK) | S(GPRS_NS2_EV_UNBLOCK_ACK), + .out_state_mask = S(GPRS_NS2_ST_RESET) | + S(GPRS_NS2_ST_UNBLOCKED) | + S(GPRS_NS2_ST_BLOCKED), + .name = "BLOCKED", + .action = gprs_ns2_st_blocked, + .onenter = gprs_ns2_st_blocked_onenter, + }, + [GPRS_NS2_ST_UNBLOCKED] = { + .in_event_mask = S(GPRS_NS2_EV_BLOCK), + .out_state_mask = S(GPRS_NS2_ST_RESET) | S(GPRS_NS2_ST_ALIVE) | + S(GPRS_NS2_ST_BLOCKED), + .name = "UNBLOCKED", + .action = gprs_ns2_st_unblocked, + .onenter = gprs_ns2_st_unblocked_on_enter, + }, + + /* ST_ALIVE is only used on VC without RESET/BLOCK */ + [GPRS_NS2_ST_ALIVE] = { + .in_event_mask = S(GPRS_NS2_EV_ALIVE_ACK), + .out_state_mask = S(GPRS_NS2_ST_RESET) | + S(GPRS_NS2_ST_UNBLOCKED), + .name = "ALIVE", + .action = gprs_ns2_st_alive, + .onenter = gprs_ns2_st_alive_onenter, + .onleave = gprs_ns2_st_alive_onleave, + }, +}; + +static int gprs_ns2_vc_fsm_timer_cb(struct osmo_fsm_inst *fi) +{ + struct gprs_ns2_inst *nsi = ns_inst_from_fi(fi); + struct gprs_ns2_vc_priv *priv = fi->priv; + + if (priv->initiater) { + /* PCU timeouts */ + switch (fi->state) { + case GPRS_NS2_ST_RESET: + priv->N++; + if (priv->N <= nsi->timeout[NS_TOUT_TNS_RESET_RETRIES]) { + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RESET, nsi->timeout[NS_TOUT_TNS_RESET], 0); + } else { + priv->N = 0; + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RESET, nsi->timeout[NS_TOUT_TNS_RESET], 0); + } + break; + case GPRS_NS2_ST_BLOCKED: + priv->N++; + if (priv->N <= nsi->timeout[NS_TOUT_TNS_BLOCK_RETRIES]) { + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED, nsi->timeout[NS_TOUT_TNS_BLOCK], 0); + } else { + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RESET, nsi->timeout[NS_TOUT_TNS_RESET], 0); + } + break; + case GPRS_NS2_ST_ALIVE: + priv->N++; + if (priv->N <= nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES]) { + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_ALIVE, 0, 0); + } else { + priv->N = 0; + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_ALIVE, 0, 0); + } + break; + } + } + return 0; +} + +static void gprs_ns2_recv_unitdata(struct osmo_fsm_inst *fi, + struct msgb *msg) +{ + struct gprs_ns2_vc_priv *priv = fi->priv; + struct gprs_ns2_inst *nsi = ns_inst_from_fi(fi); + struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h; + struct osmo_gprs_ns2_prim nsp = {}; + uint16_t bvci; + + if (msgb_l2len(msg) < sizeof(*nsh) + 3) + return; + + /* TODO: 7.1: For an IP sub-network, an NS-UNITDATA PDU + * for a PTP BVC may indicate a request to change the IP endpoint + * and/or a response to a change in the IP endpoint. */ + + /* TODO: nsh->data[0] -> C/R only valid in IP SNS */ + bvci = nsh->data[1] << 8 | nsh->data[2]; + + msgb_bssgph(msg) = &nsh->data[3]; + msgb_bvci(msg) = nsp.bvci = bvci; + msgb_nsei(msg) = nsp.nsei = priv->nsvc->nse->nsei; + + if (nsh->data[0]) + nsp.u.unitdata.change = NS_ENDPOINT_REQUEST_CHANGE; + + osmo_prim_init(&nsp.oph, SAP_NS, PRIM_NS_UNIT_DATA, + PRIM_OP_INDICATION, msg); + nsi->cb(&nsp.oph, nsi->cb_data); +} + +static void gprs_ns2_vc_fsm_allstate_action(struct osmo_fsm_inst *fi, + uint32_t event, + void *data) +{ + struct gprs_ns2_vc_priv *priv = fi->priv; + struct gprs_ns2_inst *nsi = ns_inst_from_fi(fi); + + switch (event) { + case GPRS_NS2_EV_RESET: + if (priv->nsvc->mode != NS2_VC_MODE_BLOCKRESET) + break; + + /* move the FSM into reset */ + if (fi->state != GPRS_NS2_ST_RESET) { + priv->initiater = false; + osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RESET, nsi->timeout[NS_TOUT_TNS_RESET], NS_TOUT_TNS_RESET); + } + /* pass the event down into FSM action */ + gprs_ns2_st_reset(fi, event, data); + break; + case GPRS_NS2_EV_ALIVE: + switch (fi->state) { + case GPRS_NS2_ST_UNCONFIGURED: + case GPRS_NS2_ST_RESET: + /* ignore ALIVE */ + break; + default: + ns2_tx_alive_ack(priv->nsvc); + } + break; + case GPRS_NS2_EV_ALIVE_ACK: + /* for VCs without RESET/BLOCK/UNBLOCK, the connections comes after ALIVE_ACK unblocked */ + if (fi->state == GPRS_NS2_ST_ALIVE) + gprs_ns2_st_alive(fi, event, data); + else + recv_test_procedure(fi); + break; + case GPRS_NS2_EV_UNITDATA: + switch (fi->state) { + case GPRS_NS2_ST_BLOCKED: + /* 7.2.1: the BLOCKED_ACK might be lost */ + if (priv->initiater) + gprs_ns2_recv_unitdata(fi, data); + else + ns2_tx_status(priv->nsvc, + NS_CAUSE_NSVC_BLOCKED, + 0, data); + break; + /* ALIVE can receive UNITDATA if the ALIVE_ACK is lost */ + case GPRS_NS2_ST_ALIVE: + case GPRS_NS2_ST_UNBLOCKED: + gprs_ns2_recv_unitdata(fi, data); + break; + } + break; + } +} + +static struct osmo_fsm gprs_ns2_vc_fsm = { + .name = "GPRS-NS2-VC", + .states = gprs_ns2_vc_states, + .num_states = ARRAY_SIZE(gprs_ns2_vc_states), + .allstate_event_mask = S(GPRS_NS2_EV_UNITDATA) | + S(GPRS_NS2_EV_RESET) | + S(GPRS_NS2_EV_ALIVE) | + S(GPRS_NS2_EV_ALIVE_ACK), + .allstate_action = gprs_ns2_vc_fsm_allstate_action, + .cleanup = NULL, + .timer_cb = gprs_ns2_vc_fsm_timer_cb, + /* .log_subsys = DNS, "is not constant" */ + .event_names = gprs_ns2_vc_event_names, + .pre_term = NULL, + .log_subsys = DLNS, +}; + +/*! + * \brief gprs_ns2_vc_fsm_alloc + * \param ctx + * \param vc + * \param id a char representation of the virtual curcuit + * \param initiater initiater is the site which starts the connection. Usually the BSS. + * \return NULL on error, otherwise the fsm + */ +struct osmo_fsm_inst *gprs_ns2_vc_fsm_alloc(struct gprs_ns2_vc *nsvc, + const char *id, bool initiater) +{ + struct osmo_fsm_inst *fi; + struct gprs_ns2_vc_priv *priv; + + fi = osmo_fsm_inst_alloc(&gprs_ns2_vc_fsm, nsvc, NULL, LOGL_DEBUG, id); + if (!fi) + return fi; + + nsvc->fi = fi; + priv = fi->priv = talloc_zero(fi, struct gprs_ns2_vc_priv); + priv->nsvc = nsvc; + priv->initiater = initiater; + + osmo_timer_setup(&priv->alive.timer, alive_timeout_handler, fi); + + return fi; +} + +/*! + * \brief gprs_ns2_vc_fsm_start start the FSM + * \param vc the virtual circuit + * \return 0 on success + */ +int gprs_ns2_vc_fsm_start(struct gprs_ns2_vc *nsvc) +{ + /* allows to call this function even for started nsvc by gprs_ns2_start_alive_all_nsvcs */ + if (nsvc->fi->state == GPRS_NS2_ST_UNCONFIGURED) + return osmo_fsm_inst_dispatch(nsvc->fi, GPRS_NS2_EV_START, NULL); + return 0; +} + +/*! + * \brief gprs_ns2_vc_rx entry point for messages from the driver/VL + * \param vc the virtual circuit on which is recived + * \param msg the message + * \param tp the parsed TLVs + * \return 0 on success + */ +int gprs_ns2_vc_rx(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp) +{ + struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h; + struct osmo_fsm_inst *fi = nsvc->fi; + uint8_t cause; + + /* TODO: 7.2: on UNBLOCK/BLOCK: check if NS-VCI is correct, + * if not answer STATUS with "NS-VC unknown" */ + /* TODO: handle RESET with different VCI */ + /* TODO: handle BLOCK/UNBLOCK/ALIVE with different VCI */ + + if (gprs_ns2_validate(nsvc, nsh->pdu_type, msg, tp, &cause)) { + if (nsh->pdu_type != NS_PDUT_STATUS) { + return ns2_tx_status(nsvc, cause, 0, msg); + } + } + + switch (nsh->pdu_type) { + case NS_PDUT_RESET: + osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RESET, tp); + break; + case NS_PDUT_RESET_ACK: + osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RESET_ACK, tp); + break; + case NS_PDUT_BLOCK: + osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_BLOCK, tp); + break; + case NS_PDUT_BLOCK_ACK: + osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_BLOCK_ACK, tp); + break; + case NS_PDUT_UNBLOCK: + osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_UNBLOCK, tp); + break; + case NS_PDUT_UNBLOCK_ACK: + osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_UNBLOCK_ACK, tp); + break; + case NS_PDUT_ALIVE: + osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_ALIVE, tp); + break; + case NS_PDUT_ALIVE_ACK: + osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_ALIVE_ACK, tp); + break; + case NS_PDUT_UNITDATA: + osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_UNITDATA, msg); + break; + default: + LOGP(DLNS, LOGL_ERROR, "NSEI=%u Rx unknown NS PDU type %s\n", nsvc->nse->nsei, + get_value_string(gprs_ns_pdu_strings, nsh->pdu_type)); + return -EINVAL; + } + + return 0; +} + +/*! + * \brief gprs_ns2_vc_is_alive says if this + * \param vc + * \return + */ +int gprs_ns2_vc_is_unblocked(struct gprs_ns2_vc *nsvc) +{ + return (nsvc->fi->state == GPRS_NS2_ST_UNBLOCKED); +} + +/* initialize osmo_ctx on main tread */ +static __attribute__((constructor)) void on_dso_load_ctx(void) +{ + OSMO_ASSERT(osmo_fsm_register(&gprs_ns2_vc_fsm) == 0); +} |