Move v51_le_ctrl to src/v5x_le_ctrl_fsm
Also changed serveral names of functions/structures/defintions.
This commit is contained in:
parent
60c6a6edd5
commit
db4658b430
|
@ -0,0 +1,583 @@
|
|||
/* (C) 2021 by Harald Welte <laforge@gnumonks.org>
|
||||
* (C) 2022 by Andreas Eversberg <jolly@eversberg.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, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <unistd.h>
|
||||
#include <stdint.h>
|
||||
#include <errno.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/core/msgb.h>
|
||||
#include <osmocom/gsm/tlv.h>
|
||||
|
||||
#include "v5x_internal.h"
|
||||
#include "v5x_protocol.h"
|
||||
#include "v5x_le_ctrl_fsm.h"
|
||||
#include "v5x_le_port_fsm.h"
|
||||
#include "v5x_le_provisioning.h"
|
||||
#include "logging.h"
|
||||
|
||||
#define S(x) (1 << (x))
|
||||
|
||||
#define TIMEOUT 1
|
||||
|
||||
/* uncomment to test lost TX messages at the first transmission */
|
||||
//#define TEST_TX_FAILURE
|
||||
|
||||
/* PSTN Protocol FSM */
|
||||
enum v5x_le_ctrl_fsm_state {
|
||||
V5X_PSTN_PROT_S_OUT_OF_SERVICE, /* LE0 */
|
||||
V5X_PSTN_PROT_S_NULL, /* LE1 */
|
||||
V5X_PSTN_PROT_S_PATH_INIT_BY_LE, /* LE2 */
|
||||
V5X_PSTN_PROT_S_PATH_INIT_BY_AN, /* LE3 */
|
||||
V5X_PSTN_PROT_S_PATH_ACTIVE, /* LE4 */
|
||||
V5X_PSTN_PROT_S_PATH_DISC_REQ, /* LE5 */
|
||||
V5X_PSTN_PROT_S_PORT_BLOCKED, /* LE6 */
|
||||
};
|
||||
|
||||
/***********************************************************************
|
||||
* port/common CTRL protocol FSM / G.964 Section 14
|
||||
***********************************************************************/
|
||||
|
||||
enum v5x_ctrl_fsm_state {
|
||||
V5X_CTRL_ST_OUT_OF_SERVICE = 0,
|
||||
V5X_CTRL_ST_IN_SERVICE,
|
||||
V5X_CTRL_ST_AWAIT_ACK,
|
||||
};
|
||||
|
||||
enum v5x_ctrl_fsm_event {
|
||||
V5X_CTRL_EV_MDU_START_TRAFFIC,
|
||||
V5X_CTRL_EV_MDU_STOP_TRAFFIC,
|
||||
V5X_CTRL_EV_MDU_CTRL,
|
||||
V5X_CTRL_EV_RX_CONTROL,
|
||||
V5X_CTRL_EV_RX_CONTROL_ACK,
|
||||
};
|
||||
|
||||
static const struct value_string v5x_ctrl_fsm_event_names[] = {
|
||||
{ V5X_CTRL_EV_MDU_START_TRAFFIC, "MDU-start_traffic" },
|
||||
{ V5X_CTRL_EV_MDU_STOP_TRAFFIC, "MDU-stop_traffic" },
|
||||
{ V5X_CTRL_EV_MDU_CTRL, "MDU-CTRL" },
|
||||
{ V5X_CTRL_EV_RX_CONTROL, "CONTROL" },
|
||||
{ V5X_CTRL_EV_RX_CONTROL_ACK, "CONTROL_ACK" },
|
||||
{ 0, NULL }
|
||||
};
|
||||
|
||||
static void v5x_ctrl_mdu(struct osmo_fsm_inst *fi, const struct tlv_parsed *tp)
|
||||
{
|
||||
struct v5x_ctrl_proto *ctrl = fi->priv;
|
||||
|
||||
if (ctrl->v5if) {
|
||||
enum v5x_ctrl_func_id cfi = *TLVP_VAL(tp, V5X_CTRL_IEI_CTRL_F_ID) & 0x7f;
|
||||
uint8_t variant = 0;
|
||||
uint8_t rej_cause = 0;
|
||||
uint32_t interface_id = 0;
|
||||
|
||||
LOGP(DV5CTRL, LOGL_DEBUG, "MDU-CTRL received.\n");
|
||||
|
||||
if (TLVP_PRESENT(tp, V5X_CTRL_IEI_VARIANT))
|
||||
variant = *TLVP_VAL(tp, V5X_CTRL_IEI_VARIANT) & 0x7f;
|
||||
if (TLVP_PRESENT(tp, V5X_CTRL_IEI_REJECTION_CAUSE))
|
||||
rej_cause = *TLVP_VAL(tp, V5X_CTRL_IEI_REJECTION_CAUSE) & 0x0f;
|
||||
if (TLVP_PRESENT(tp, V5X_CTRL_IEI_INTERFACE_ID))
|
||||
interface_id = osmo_load32be_ext_2(TLVP_VAL(tp, V5X_CTRL_IEI_INTERFACE_ID), 3);
|
||||
v5x_le_provisioning_rcv(ctrl->v5if, cfi, variant, rej_cause, interface_id);
|
||||
}
|
||||
if (ctrl->v5up) {
|
||||
enum v5x_ctrl_func_id cfe = *TLVP_VAL(tp, V5X_CTRL_IEI_CTRL_F_ELEMENT) & 0x7f;
|
||||
uint8_t perf_grading = 0;
|
||||
|
||||
LOGP(DV5CTRL, LOGL_DEBUG, "FE received for address %d.\n", ctrl->v5up->nr);
|
||||
|
||||
if (TLVP_PRESENT(tp, V5X_CTRL_IEI_PERFORMANCE_GRADING))
|
||||
perf_grading = *TLVP_VAL(tp, V5X_CTRL_IEI_PERFORMANCE_GRADING) & 0x0f;
|
||||
switch (ctrl->v5up->type) {
|
||||
case V5X_USER_TYPE_ISDN:
|
||||
v5x_le_port_isdn_rcv(ctrl->v5up, cfe, perf_grading);
|
||||
break;
|
||||
case V5X_USER_TYPE_PSTN:
|
||||
v5x_le_port_pstn_rcv(ctrl->v5up, cfe);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* display MDU-error_indication */
|
||||
static void v5x_mdu_error(struct osmo_fsm_inst *fi, const char *error)
|
||||
{
|
||||
LOGP(DV5CTRL, LOGL_NOTICE, "MDU-error_indication: %s\n", error);
|
||||
}
|
||||
|
||||
/* send control towards lower (DL) layer */
|
||||
static void v5x_ctrl_send(struct v5x_ctrl_proto *ctrl, struct msgb *msg)
|
||||
{
|
||||
struct osmo_fsm_inst *fi = ctrl->fi;
|
||||
|
||||
switch (fi->state) {
|
||||
case V5X_CTRL_ST_IN_SERVICE:
|
||||
LOGP(DV5CTRL, LOGL_DEBUG, "We are in service, so we send our message now.\n");
|
||||
/* no message, clone current message and store to be repeated */
|
||||
ctrl->tx_msg = msgb_copy(msg, NULL);
|
||||
/* go to AWAIT_PENDING_ACK */
|
||||
osmo_fsm_inst_state_chg(fi, V5X_CTRL_ST_AWAIT_ACK, TIMEOUT, (ctrl->v5up) ? 1 : 2);
|
||||
/* send message towards DL */
|
||||
#ifdef TEST_TX_FAILURE
|
||||
msgb_free(msg);
|
||||
#else
|
||||
v5x_snd((ctrl->v5if) ? : ctrl->v5up->inst, V5X_DLADDR_CTRL, msg);
|
||||
#endif
|
||||
break;
|
||||
case V5X_CTRL_ST_AWAIT_ACK:
|
||||
LOGP(DV5CTRL, LOGL_DEBUG, "We are waiting for ack, so we queue our message.\n");
|
||||
/* pending message, save message in queue */
|
||||
msgb_enqueue(&ctrl->tx_queue, msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* receive acknowledge */
|
||||
static void v5x_ctrl_ack(struct osmo_fsm_inst *fi, const struct tlv_parsed *tp)
|
||||
{
|
||||
struct v5x_ctrl_proto *ctrl = fi->priv;
|
||||
struct msgb *msg;
|
||||
|
||||
LOGP(DV5CTRL, LOGL_DEBUG, "Received acknowledge, removing pending message, if any.\n");
|
||||
/* free pending copy of message, if acked before retry */
|
||||
if (ctrl->tx_msg) {
|
||||
msgb_free(ctrl->tx_msg);
|
||||
ctrl->tx_msg = NULL;
|
||||
}
|
||||
/* go to IN_SERVICE */
|
||||
osmo_fsm_inst_state_chg(fi, V5X_CTRL_ST_IN_SERVICE, 0, 0);
|
||||
/* sending next pending message in queue */
|
||||
msg = msgb_dequeue(&ctrl->tx_queue);
|
||||
if (msg) {
|
||||
LOGP(DV5CTRL, LOGL_DEBUG, "Found pending message in queue.\n");
|
||||
v5x_ctrl_send(ctrl, msg);
|
||||
}
|
||||
}
|
||||
|
||||
/* stop traffic */
|
||||
static void v5x_ctrl_stop(struct osmo_fsm_inst *fi)
|
||||
{
|
||||
struct v5x_ctrl_proto *ctrl = fi->priv;
|
||||
struct msgb *msg;
|
||||
|
||||
/* flush pending messages */
|
||||
if (ctrl->tx_msg) {
|
||||
msgb_free(ctrl->tx_msg);
|
||||
ctrl->tx_msg = NULL;
|
||||
}
|
||||
while ((msg = msgb_dequeue(&ctrl->tx_queue)))
|
||||
msgb_free(msg);
|
||||
/* go to OUT_OF_SERVICE */
|
||||
osmo_fsm_inst_state_chg(fi, V5X_CTRL_ST_OUT_OF_SERVICE, 0, 0);
|
||||
}
|
||||
|
||||
/* T01 / T02 */
|
||||
static int v5x_ctrl_fsm_timer_cb(struct osmo_fsm_inst *fi)
|
||||
{
|
||||
struct v5x_ctrl_proto *ctrl = fi->priv;
|
||||
struct msgb *msg;
|
||||
|
||||
if (ctrl->tx_msg) {
|
||||
LOGP(DV5CTRL, LOGL_DEBUG, "Timer fired the first time, resending message.\n");
|
||||
/* first expiry: repeat CONTROL; re-start T01 */
|
||||
/* send message towards DL */
|
||||
osmo_timer_schedule(&fi->timer, TIMEOUT, 0);
|
||||
msg = ctrl->tx_msg;
|
||||
ctrl->tx_msg = NULL;
|
||||
v5x_snd((ctrl->v5if) ? : ctrl->v5up->inst, V5X_DLADDR_CTRL, msg);
|
||||
} else {
|
||||
LOGP(DV5CTRL, LOGL_DEBUG, "Timer fired the second time, indicate an error.\n");
|
||||
/* second expiry: send MDU-error_ind; go to IN_SERVICE */
|
||||
v5x_mdu_error(fi, "Second timeout while waiting for CONTROL ACK.");
|
||||
osmo_fsm_inst_state_chg(fi, V5X_CTRL_ST_IN_SERVICE, 0, 0);
|
||||
/* sending next pending message in queue */
|
||||
msg = msgb_dequeue(&ctrl->tx_queue);
|
||||
if (msg) {
|
||||
LOGP(DV5CTRL, LOGL_DEBUG, "Found pending message in queue.\n");
|
||||
v5x_ctrl_send(ctrl, msg);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* events in state OUT OF SERVICE */
|
||||
static void v5x_ctrl_fsm_oos(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||||
{
|
||||
switch (event) {
|
||||
case V5X_CTRL_EV_MDU_START_TRAFFIC:
|
||||
/* go to IN_SERVICE */
|
||||
osmo_fsm_inst_state_chg(fi, V5X_CTRL_ST_IN_SERVICE, 0, 0);
|
||||
break;
|
||||
case V5X_CTRL_EV_MDU_STOP_TRAFFIC:
|
||||
v5x_mdu_error(fi, "Got MDU-stop, but traffic not started.");
|
||||
break;
|
||||
case V5X_CTRL_EV_MDU_CTRL:
|
||||
msgb_free(data);
|
||||
v5x_mdu_error(fi, "Got MDU-CTRL, but traffic not started.");
|
||||
break;
|
||||
case V5X_CTRL_EV_RX_CONTROL:
|
||||
v5x_mdu_error(fi, "Received CONTROL, but traffic not started.");
|
||||
break;
|
||||
case V5X_CTRL_EV_RX_CONTROL_ACK:
|
||||
v5x_mdu_error(fi, "Received CONTROL ACK, but traffic not started.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* events in state IN SERVICE */
|
||||
static void v5x_ctrl_fsm_ins(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||||
{
|
||||
switch (event) {
|
||||
case V5X_CTRL_EV_MDU_START_TRAFFIC:
|
||||
break;
|
||||
case V5X_CTRL_EV_MDU_STOP_TRAFFIC:
|
||||
v5x_ctrl_stop(fi);
|
||||
break;
|
||||
case V5X_CTRL_EV_MDU_CTRL:
|
||||
v5x_ctrl_send(fi->priv, data);
|
||||
break;
|
||||
case V5X_CTRL_EV_RX_CONTROL:
|
||||
v5x_ctrl_mdu(fi, data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* events in state AWAIT PORT/COMMON ACK */
|
||||
static void v5x_ctrl_fsm_aa(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||||
{
|
||||
switch (event) {
|
||||
case V5X_CTRL_EV_MDU_START_TRAFFIC:
|
||||
break;
|
||||
case V5X_CTRL_EV_MDU_STOP_TRAFFIC:
|
||||
v5x_ctrl_stop(fi);
|
||||
break;
|
||||
case V5X_CTRL_EV_MDU_CTRL:
|
||||
v5x_ctrl_send(fi->priv, data);
|
||||
break;
|
||||
case V5X_CTRL_EV_RX_CONTROL:
|
||||
v5x_ctrl_mdu(fi, data);
|
||||
break;
|
||||
case V5X_CTRL_EV_RX_CONTROL_ACK:
|
||||
v5x_ctrl_ack(fi, data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct osmo_fsm_state v5x_ctrl_fsm_states[] = {
|
||||
[V5X_CTRL_ST_OUT_OF_SERVICE] = {
|
||||
.name = "OUT_OF_SERVICE",
|
||||
.in_event_mask = S(V5X_CTRL_EV_MDU_START_TRAFFIC) |
|
||||
S(V5X_CTRL_EV_MDU_STOP_TRAFFIC) |
|
||||
S(V5X_CTRL_EV_MDU_CTRL) |
|
||||
S(V5X_CTRL_EV_RX_CONTROL) |
|
||||
S(V5X_CTRL_EV_RX_CONTROL_ACK),
|
||||
.out_state_mask = S(V5X_CTRL_ST_IN_SERVICE),
|
||||
.action = v5x_ctrl_fsm_oos,
|
||||
},
|
||||
[V5X_CTRL_ST_IN_SERVICE] = {
|
||||
.name = "IN_SERVICE",
|
||||
.in_event_mask = S(V5X_CTRL_EV_MDU_START_TRAFFIC) |
|
||||
S(V5X_CTRL_EV_MDU_STOP_TRAFFIC) |
|
||||
S(V5X_CTRL_EV_MDU_CTRL) |
|
||||
S(V5X_CTRL_EV_RX_CONTROL),
|
||||
|
||||
.out_state_mask = S(V5X_CTRL_ST_OUT_OF_SERVICE) |
|
||||
S(V5X_CTRL_ST_AWAIT_ACK),
|
||||
.action = v5x_ctrl_fsm_ins,
|
||||
},
|
||||
[V5X_CTRL_ST_AWAIT_ACK] = {
|
||||
.name = "AWAIT_ACK",
|
||||
.in_event_mask = S(V5X_CTRL_EV_MDU_START_TRAFFIC) |
|
||||
S(V5X_CTRL_EV_MDU_STOP_TRAFFIC) |
|
||||
S(V5X_CTRL_EV_MDU_CTRL) |
|
||||
S(V5X_CTRL_EV_RX_CONTROL) |
|
||||
S(V5X_CTRL_EV_RX_CONTROL_ACK),
|
||||
.out_state_mask = S(V5X_CTRL_ST_OUT_OF_SERVICE) |
|
||||
S(V5X_CTRL_ST_IN_SERVICE),
|
||||
.action = v5x_ctrl_fsm_aa,
|
||||
},
|
||||
};
|
||||
|
||||
struct osmo_fsm v5x_ctrl_fsm = {
|
||||
.name = "V5X_CTRL",
|
||||
.states = v5x_ctrl_fsm_states,
|
||||
.num_states = ARRAY_SIZE(v5x_ctrl_fsm_states),
|
||||
.allstate_event_mask = 0,
|
||||
.allstate_action = NULL,
|
||||
.cleanup = NULL,
|
||||
.timer_cb = v5x_ctrl_fsm_timer_cb,
|
||||
.log_subsys = DV5CTRL,
|
||||
.event_names = v5x_ctrl_fsm_event_names,
|
||||
};
|
||||
|
||||
void v5x_le_ctrl_init(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = osmo_fsm_register(&v5x_ctrl_fsm);
|
||||
OSMO_ASSERT(!rc);
|
||||
LOGP(DV5CTRL, LOGL_NOTICE, "Using V5x control protocol\n");
|
||||
}
|
||||
|
||||
struct v5x_ctrl_proto *v5x_le_ctrl_create(struct v5x_interface *v5if, struct v5x_user_port *v5up, uint16_t nr)
|
||||
{
|
||||
struct v5x_ctrl_proto *ctrl;
|
||||
|
||||
OSMO_ASSERT((v5if && !v5up) || (!v5if && v5up));
|
||||
|
||||
ctrl = talloc_zero((v5if) ? : v5up->inst, struct v5x_ctrl_proto);
|
||||
if (!ctrl)
|
||||
return NULL;
|
||||
ctrl->v5if = v5if;
|
||||
ctrl->v5up = v5up;
|
||||
INIT_LLIST_HEAD(&ctrl->tx_queue);
|
||||
|
||||
ctrl->fi = osmo_fsm_inst_alloc(&v5x_ctrl_fsm, ctrl, ctrl, LOGL_DEBUG, NULL);
|
||||
if (!ctrl->fi) {
|
||||
v5x_le_ctrl_destroy(ctrl);
|
||||
return NULL;
|
||||
}
|
||||
osmo_fsm_inst_update_id_f(ctrl->fi, "%d", nr);
|
||||
|
||||
return ctrl;
|
||||
}
|
||||
|
||||
void v5x_le_ctrl_destroy(struct v5x_ctrl_proto *ctrl)
|
||||
{
|
||||
/* get rid of pending messages */
|
||||
msgb_queue_free(&ctrl->tx_queue);
|
||||
if (ctrl->tx_msg)
|
||||
msgb_free(ctrl->tx_msg);
|
||||
|
||||
if (ctrl->fi)
|
||||
osmo_fsm_inst_free(ctrl->fi);
|
||||
|
||||
talloc_free(ctrl);
|
||||
}
|
||||
|
||||
/***********************************************************************
|
||||
* V5 Message encoding / sending
|
||||
***********************************************************************/
|
||||
|
||||
/* G.964 Section 14.4.1.1 / Table 48 */
|
||||
static struct msgb *v5x_enc_ctrl_port(struct v5x_user_port *v5up, enum v5x_ctrl_func_el cfe, bool is_isdn)
|
||||
{
|
||||
uint8_t cfe_ie = cfe | 0x80;
|
||||
struct v5x_l3_hdr *l3h;
|
||||
struct msgb *msg = msgb_alloc_v5x();
|
||||
if (!msg)
|
||||
return NULL;
|
||||
|
||||
l3h = (struct v5x_l3_hdr *) msgb_put(msg, sizeof(*l3h));
|
||||
l3h->pdisc = V5X_CTRL_PDISC;
|
||||
l3h->l3_addr = ntohs(v5x_l3_addr_enc(v5up->nr, is_isdn));
|
||||
l3h->msg_type = V5X_CTRL_MSGT_PORT_CTRL;
|
||||
|
||||
msgb_tlv_put(msg, V5X_CTRL_IEI_CTRL_F_ELEMENT, 1, &cfe_ie);
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
/* G.964 Section 14.4.1.2 / Table 49 */
|
||||
static struct msgb *v5x_enc_ctrl_port_ack(struct v5x_user_port *v5up, enum v5x_ctrl_func_el cfe, bool is_isdn)
|
||||
{
|
||||
uint8_t cfe_ie = cfe | 0x80;
|
||||
struct v5x_l3_hdr *l3h;
|
||||
struct msgb *msg = msgb_alloc_v5x();
|
||||
if (!msg)
|
||||
return NULL;
|
||||
|
||||
l3h = (struct v5x_l3_hdr *) msgb_put(msg, sizeof(*l3h));
|
||||
l3h->pdisc = V5X_CTRL_PDISC;
|
||||
l3h->l3_addr = ntohs(v5x_l3_addr_enc(v5up->nr, is_isdn));
|
||||
l3h->msg_type = V5X_CTRL_MSGT_PORT_CTRL_ACK;
|
||||
|
||||
msgb_tlv_put(msg, V5X_CTRL_IEI_CTRL_F_ELEMENT, 1, &cfe_ie);
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
/* G.964 Section 14.4.1.3 / Table 50 */
|
||||
static struct msgb *v5x_enc_ctrl_common(struct v5x_interface *v5if, enum v5x_ctrl_func_id cfi,
|
||||
uint8_t *variant, uint8_t *rej_cause, uint32_t *interface_id)
|
||||
{
|
||||
uint8_t cfi_ie = cfi | 0x80;
|
||||
struct v5x_l3_hdr *l3h;
|
||||
struct msgb *msg = msgb_alloc_v5x();
|
||||
if (!msg)
|
||||
return NULL;
|
||||
|
||||
l3h = (struct v5x_l3_hdr *) msgb_put(msg, sizeof(*l3h));
|
||||
l3h->pdisc = V5X_CTRL_PDISC;
|
||||
l3h->l3_addr = ntohs(v5x_l3_addr_enc(V5X_DLADDR_CTRL, true));
|
||||
l3h->msg_type = V5X_CTRL_MSGT_COMMON_CTRL;
|
||||
|
||||
msgb_tlv_put(msg, V5X_CTRL_IEI_CTRL_F_ID, 1, &cfi_ie);
|
||||
if (variant) {
|
||||
/* Conditional: Variant */
|
||||
uint8_t variant_ie = *variant | 0x80;
|
||||
msgb_tlv_put(msg, V5X_CTRL_IEI_VARIANT, 1, &variant_ie);
|
||||
}
|
||||
if (rej_cause) {
|
||||
/* Conditional: Rejection Cause */
|
||||
msgb_put_u8(msg, 0xF0 | (*rej_cause & 0x0F));
|
||||
}
|
||||
if (interface_id) {
|
||||
/* Conditional: Interface Id */
|
||||
uint8_t iid_ie[3];
|
||||
osmo_store32be_ext(*interface_id, iid_ie, 3);
|
||||
msgb_tlv_put(msg, V5X_CTRL_IEI_INTERFACE_ID, sizeof(iid_ie), iid_ie);
|
||||
}
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
/* G.964 Section 14.4.1.4 / Table 51 */
|
||||
static struct msgb *v5x_enc_ctrl_common_ack(struct v5x_interface *v5if, enum v5x_ctrl_func_id cfi)
|
||||
{
|
||||
uint8_t cfi_ie = cfi | 0x80;
|
||||
struct v5x_l3_hdr *l3h;
|
||||
struct msgb *msg = msgb_alloc_v5x();
|
||||
if (!msg)
|
||||
return NULL;
|
||||
|
||||
l3h = (struct v5x_l3_hdr *) msgb_put(msg, sizeof(*l3h));
|
||||
l3h->pdisc = V5X_CTRL_PDISC;
|
||||
l3h->l3_addr = htons(v5x_l3_addr_enc(V5X_DLADDR_CTRL, true));
|
||||
l3h->msg_type = V5X_CTRL_MSGT_COMMON_CTRL_ACK;
|
||||
|
||||
msgb_tlv_put(msg, V5X_CTRL_IEI_CTRL_F_ID, 1, &cfi_ie);
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
/***********************************************************************
|
||||
* V5 Message receiving / decoding
|
||||
***********************************************************************/
|
||||
|
||||
static int v5x_rcv_ctrl_port(struct v5x_user_port *v5up, uint8_t msg_type, const struct tlv_parsed *tp)
|
||||
{
|
||||
enum v5x_ctrl_func_el cfe = *TLVP_VAL(tp, V5X_CTRL_IEI_CTRL_F_ELEMENT) & 0x7f;
|
||||
|
||||
switch (msg_type) {
|
||||
case V5X_CTRL_MSGT_PORT_CTRL:
|
||||
/* send ACK to AN */
|
||||
v5x_snd(v5up->inst, V5X_DLADDR_CTRL, v5x_enc_ctrl_port_ack(v5up, cfe, v5up->type == V5X_USER_TYPE_ISDN));
|
||||
/* FIXME: send event to FSM */
|
||||
osmo_fsm_inst_dispatch(v5up->ctrl->fi, V5X_CTRL_EV_RX_CONTROL, (void *)tp);
|
||||
return 0;
|
||||
case V5X_CTRL_MSGT_PORT_CTRL_ACK:
|
||||
osmo_fsm_inst_dispatch(v5up->ctrl->fi, V5X_CTRL_EV_RX_CONTROL_ACK, (void *)tp);
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int v5x_rcv_ctrl_common(struct v5x_interface *v5if, uint8_t msg_type, const struct tlv_parsed *tp)
|
||||
{
|
||||
enum v5x_ctrl_func_id cfi = *TLVP_VAL(tp, V5X_CTRL_IEI_CTRL_F_ID) & 0x7f;
|
||||
|
||||
switch (msg_type) {
|
||||
case V5X_CTRL_MSGT_COMMON_CTRL:
|
||||
v5x_snd(v5if, V5X_DLADDR_CTRL, v5x_enc_ctrl_common_ack(v5if, cfi));
|
||||
/* send event to FSM */
|
||||
osmo_fsm_inst_dispatch(v5if->control.ctrl->fi, V5X_CTRL_EV_RX_CONTROL, (void *)tp);
|
||||
/* send ACK to AN */
|
||||
return 0;
|
||||
case V5X_CTRL_MSGT_COMMON_CTRL_ACK:
|
||||
/* send event to FSM */
|
||||
osmo_fsm_inst_dispatch(v5if->control.ctrl->fi, V5X_CTRL_EV_RX_CONTROL_ACK, (void *)tp);
|
||||
return 0;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
/* receive message from lower (DL) layer */
|
||||
int v5x_le_ctrl_rcv(struct v5x_interface *v5if, uint16_t l3_addr, bool is_isdn, uint8_t msg_type, const struct tlv_parsed *tp)
|
||||
{
|
||||
struct v5x_user_port *v5up;
|
||||
|
||||
switch (msg_type) {
|
||||
case V5X_CTRL_MSGT_PORT_CTRL:
|
||||
case V5X_CTRL_MSGT_PORT_CTRL_ACK:
|
||||
v5up = v5x_user_port_find(v5if, l3_addr, is_isdn);
|
||||
if (!v5up) {
|
||||
LOGP(DV5CTRL, LOGL_ERROR, "Received port control message from unknown layer 3 address %d. Please check provisioning!\n", l3_addr);
|
||||
return -ENODEV;
|
||||
}
|
||||
if (v5up->type == V5X_USER_TYPE_ISDN && !is_isdn) {
|
||||
LOGP(DV5CTRL, LOGL_ERROR, "Received port control message from layer 3 address %d. AN is set to PSTN, LE to ISDN, please check provisioning!", l3_addr);
|
||||
return -EIO;
|
||||
}
|
||||
if (v5up->type == V5X_USER_TYPE_PSTN && is_isdn) {
|
||||
LOGP(DV5CTRL, LOGL_ERROR, "Received port control message from layer 3 address %d. AN is set to ISDN, LE to PSTN, please check provisioning!", l3_addr);
|
||||
return -EIO;
|
||||
}
|
||||
return v5x_rcv_ctrl_port(v5up, msg_type, tp);
|
||||
case V5X_CTRL_MSGT_COMMON_CTRL:
|
||||
case V5X_CTRL_MSGT_COMMON_CTRL_ACK:
|
||||
if (l3_addr != V5X_DLADDR_CTRL)
|
||||
return -EINVAL;
|
||||
return v5x_rcv_ctrl_common(v5if, msg_type, tp);
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/***********************************************************************
|
||||
* V5 Message sending / encoding
|
||||
***********************************************************************/
|
||||
|
||||
/* send common message from upper layer */
|
||||
int v5x_le_ctrl_common_snd(struct v5x_interface *v5if, enum v5x_ctrl_func_id cfi,
|
||||
uint8_t *rej_cause, uint8_t *variant, uint32_t *interface_id)
|
||||
{
|
||||
LOGP(DV5CTRL, LOGL_DEBUG, "Sending MDU-CTRL.\n");
|
||||
|
||||
osmo_fsm_inst_dispatch(v5if->control.ctrl->fi, V5X_CTRL_EV_MDU_CTRL,
|
||||
v5x_enc_ctrl_common(v5if, cfi, variant, rej_cause, interface_id));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* send port message from upper layer */
|
||||
int v5x_le_ctrl_port_snd(struct v5x_user_port *v5up, enum v5x_ctrl_func_el cfe)
|
||||
{
|
||||
LOGP(DV5CTRL, LOGL_DEBUG, "Sending FE.\n");
|
||||
|
||||
osmo_fsm_inst_dispatch(v5up->ctrl->fi, V5X_CTRL_EV_MDU_CTRL,
|
||||
v5x_enc_ctrl_port(v5up, cfe, v5up->type == V5X_USER_TYPE_ISDN));
|
||||
return 0;
|
||||
}
|
||||
|
||||
void v5x_le_ctrl_start(struct v5x_ctrl_proto *ctrl)
|
||||
{
|
||||
osmo_fsm_inst_dispatch(ctrl->fi, V5X_CTRL_EV_MDU_START_TRAFFIC, NULL);
|
||||
}
|
||||
void v5x_le_ctrl_stop(struct v5x_ctrl_proto *ctrl)
|
||||
{
|
||||
osmo_fsm_inst_dispatch(ctrl->fi, V5X_CTRL_EV_MDU_STOP_TRAFFIC, NULL);
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
|
||||
void v5x_le_ctrl_init(void);
|
||||
struct v5x_ctrl_proto *v5x_le_ctrl_create(struct v5x_interface *v5if, struct v5x_user_port *v5up, uint16_t nr);
|
||||
void v5x_le_ctrl_destroy(struct v5x_ctrl_proto *ctrl);
|
||||
int v5x_le_ctrl_rcv(struct v5x_interface *v5if, uint16_t l3_addr, bool is_isdn, uint8_t msg_type, const struct tlv_parsed *tp);
|
||||
int v5x_le_ctrl_common_snd(struct v5x_interface *v5if, enum v5x_ctrl_func_id cfi,
|
||||
uint8_t *rej_cause, uint8_t *variant, uint32_t *interface_id);
|
||||
int v5x_le_ctrl_port_snd(struct v5x_user_port *v5up, enum v5x_ctrl_func_el cfe);
|
||||
void v5x_le_ctrl_start(struct v5x_ctrl_proto *ctrl);
|
||||
void v5x_le_ctrl_stop(struct v5x_ctrl_proto *ctrl);
|
582
v51_le_ctrl.c
582
v51_le_ctrl.c
|
@ -1,582 +0,0 @@
|
|||
/* (C) 2021 by Harald Welte <laforge@gnumonks.org>
|
||||
*
|
||||
* 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, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <unistd.h>
|
||||
#include <stdint.h>
|
||||
#include <errno.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/core/msgb.h>
|
||||
#include <osmocom/gsm/tlv.h>
|
||||
|
||||
#include "v5x_internal.h"
|
||||
#include "v5x_protocol.h"
|
||||
#include "v51_le_ctrl.h"
|
||||
#include "v51_le_provisioning.h"
|
||||
#include "v52_le_user_port_fsm.h"
|
||||
#include "logging.h"
|
||||
|
||||
#define S(x) (1 << (x))
|
||||
|
||||
#define TIMEOUT 1
|
||||
|
||||
/* uncomment to test lost TX messages at the first transmission */
|
||||
//#define TEST_TX_FAILURE
|
||||
|
||||
/* PSTN Protocol FSM */
|
||||
enum v51_le_ctrl_fsm_state {
|
||||
V51_PSTN_PROT_S_OUT_OF_SERVICE, /* LE0 */
|
||||
V51_PSTN_PROT_S_NULL, /* LE1 */
|
||||
V51_PSTN_PROT_S_PATH_INIT_BY_LE, /* LE2 */
|
||||
V51_PSTN_PROT_S_PATH_INIT_BY_AN, /* LE3 */
|
||||
V51_PSTN_PROT_S_PATH_ACTIVE, /* LE4 */
|
||||
V51_PSTN_PROT_S_PATH_DISC_REQ, /* LE5 */
|
||||
V51_PSTN_PROT_S_PORT_BLOCKED, /* LE6 */
|
||||
};
|
||||
|
||||
/***********************************************************************
|
||||
* port/common CTRL protocol FSM / G.964 Section 14
|
||||
***********************************************************************/
|
||||
|
||||
enum v51_ctrl_fsm_state {
|
||||
V51_CTRL_ST_OUT_OF_SERVICE = 0,
|
||||
V51_CTRL_ST_IN_SERVICE,
|
||||
V51_CTRL_ST_AWAIT_ACK,
|
||||
};
|
||||
|
||||
enum v51_ctrl_fsm_event {
|
||||
V51_CTRL_EV_MDU_START_TRAFFIC,
|
||||
V51_CTRL_EV_MDU_STOP_TRAFFIC,
|
||||
V51_CTRL_EV_MDU_CTRL,
|
||||
V51_CTRL_EV_RX_CONTROL,
|
||||
V51_CTRL_EV_RX_CONTROL_ACK,
|
||||
};
|
||||
|
||||
static const struct value_string v51_ctrl_fsm_event_names[] = {
|
||||
{ V51_CTRL_EV_MDU_START_TRAFFIC, "MDU-start_traffic" },
|
||||
{ V51_CTRL_EV_MDU_STOP_TRAFFIC, "MDU-stop_traffic" },
|
||||
{ V51_CTRL_EV_MDU_CTRL, "MDU-CTRL" },
|
||||
{ V51_CTRL_EV_RX_CONTROL, "CONTROL" },
|
||||
{ V51_CTRL_EV_RX_CONTROL_ACK, "CONTROL_ACK" },
|
||||
{ 0, NULL }
|
||||
};
|
||||
|
||||
static void v51_mdu_ctrl(struct osmo_fsm_inst *fi, const struct tlv_parsed *tp)
|
||||
{
|
||||
struct v5x_ctrl_proto *ctrl = fi->priv;
|
||||
|
||||
if (ctrl->v5if) {
|
||||
enum v51_ctrl_func_id cfi = *TLVP_VAL(tp, V51_CTRL_IEI_CTRL_F_ID) & 0x7f;
|
||||
uint8_t variant = 0;
|
||||
uint8_t rej_cause = 0;
|
||||
uint32_t interface_id = 0;
|
||||
|
||||
LOGP(DV5CTRL, LOGL_DEBUG, "MDU-CTRL received.\n");
|
||||
|
||||
if (TLVP_PRESENT(tp, V51_CTRL_IEI_VARIANT))
|
||||
variant = *TLVP_VAL(tp, V51_CTRL_IEI_VARIANT) & 0x7f;
|
||||
if (TLVP_PRESENT(tp, V51_CTRL_IEI_REJECTION_CAUSE))
|
||||
rej_cause = *TLVP_VAL(tp, V51_CTRL_IEI_REJECTION_CAUSE) & 0x0f;
|
||||
if (TLVP_PRESENT(tp, V51_CTRL_IEI_INTERFACE_ID))
|
||||
interface_id = osmo_load32be_ext_2(TLVP_VAL(tp, V51_CTRL_IEI_INTERFACE_ID), 3);
|
||||
v51_rcv_provisioning(ctrl->v5if, cfi, variant, rej_cause, interface_id);
|
||||
}
|
||||
if (ctrl->v5up) {
|
||||
enum v51_ctrl_func_id cfe = *TLVP_VAL(tp, V51_CTRL_IEI_CTRL_F_ELEMENT) & 0x7f;
|
||||
uint8_t perf_grading = 0;
|
||||
|
||||
LOGP(DV5CTRL, LOGL_DEBUG, "FE received for address %d.\n", ctrl->v5up->nr);
|
||||
|
||||
if (TLVP_PRESENT(tp, V51_CTRL_IEI_PERFORMANCE_GRADING))
|
||||
perf_grading = *TLVP_VAL(tp, V51_CTRL_IEI_PERFORMANCE_GRADING) & 0x0f;
|
||||
switch (ctrl->v5up->type) {
|
||||
case V5X_USER_TYPE_ISDN:
|
||||
v51_ctrl_le_i_port_rcv(ctrl->v5up, cfe, perf_grading);
|
||||
break;
|
||||
case V5X_USER_TYPE_PSTN:
|
||||
v51_ctrl_le_p_port_rcv(ctrl->v5up, cfe);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* display MDU-error_indication */
|
||||
static void v51_mdu_error(struct osmo_fsm_inst *fi, const char *error)
|
||||
{
|
||||
LOGP(DV5CTRL, LOGL_NOTICE, "MDU-error_indication: %s\n", error);
|
||||
}
|
||||
|
||||
/* send control towards lower (DL) layer */
|
||||
static void v51_ctrl_send(struct v5x_ctrl_proto *ctrl, struct msgb *msg)
|
||||
{
|
||||
struct osmo_fsm_inst *fi = ctrl->fi;
|
||||
|
||||
switch (fi->state) {
|
||||
case V51_CTRL_ST_IN_SERVICE:
|
||||
LOGP(DV5CTRL, LOGL_DEBUG, "We are in service, so we send our message now.\n");
|
||||
/* no message, clone current message and store to be repeated */
|
||||
ctrl->tx_msg = msgb_copy(msg, NULL);
|
||||
/* go to AWAIT_PENDING_ACK */
|
||||
osmo_fsm_inst_state_chg(fi, V51_CTRL_ST_AWAIT_ACK, TIMEOUT, (ctrl->v5up) ? 1 : 2);
|
||||
/* send message towards DL */
|
||||
#ifdef TEST_TX_FAILURE
|
||||
msgb_free(msg);
|
||||
#else
|
||||
v5x_snd((ctrl->v5if) ? : ctrl->v5up->inst, V51_DLADDR_CTRL, msg);
|
||||
#endif
|
||||
break;
|
||||
case V51_CTRL_ST_AWAIT_ACK:
|
||||
LOGP(DV5CTRL, LOGL_DEBUG, "We are waiting for ack, so we queue our message.\n");
|
||||
/* pending message, save message in queue */
|
||||
msgb_enqueue(&ctrl->tx_queue, msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* receive acknowledge */
|
||||
static void v51_ctrl_ack(struct osmo_fsm_inst *fi, const struct tlv_parsed *tp)
|
||||
{
|
||||
struct v5x_ctrl_proto *ctrl = fi->priv;
|
||||
struct msgb *msg;
|
||||
|
||||
LOGP(DV5CTRL, LOGL_DEBUG, "Received acknowledge, removing pending message, if any.\n");
|
||||
/* free pending copy of message, if acked before retry */
|
||||
if (ctrl->tx_msg) {
|
||||
msgb_free(ctrl->tx_msg);
|
||||
ctrl->tx_msg = NULL;
|
||||
}
|
||||
/* go to IN_SERVICE */
|
||||
osmo_fsm_inst_state_chg(fi, V51_CTRL_ST_IN_SERVICE, 0, 0);
|
||||
/* sending next pending message in queue */
|
||||
msg = msgb_dequeue(&ctrl->tx_queue);
|
||||
if (msg) {
|
||||
LOGP(DV5CTRL, LOGL_DEBUG, "Found pending message in queue.\n");
|
||||
v51_ctrl_send(ctrl, msg);
|
||||
}
|
||||
}
|
||||
|
||||
/* stop traffic */
|
||||
static void v51_ctrl_stop(struct osmo_fsm_inst *fi)
|
||||
{
|
||||
struct v5x_ctrl_proto *ctrl = fi->priv;
|
||||
struct msgb *msg;
|
||||
|
||||
/* flush pending messages */
|
||||
if (ctrl->tx_msg) {
|
||||
msgb_free(ctrl->tx_msg);
|
||||
ctrl->tx_msg = NULL;
|
||||
}
|
||||
while ((msg = msgb_dequeue(&ctrl->tx_queue)))
|
||||
msgb_free(msg);
|
||||
/* go to OUT_OF_SERVICE */
|
||||
osmo_fsm_inst_state_chg(fi, V51_CTRL_ST_OUT_OF_SERVICE, 0, 0);
|
||||
}
|
||||
|
||||
/* T01 / T02 */
|
||||
static int v51_ctrl_fsm_timer_cb(struct osmo_fsm_inst *fi)
|
||||
{
|
||||
struct v5x_ctrl_proto *ctrl = fi->priv;
|
||||
struct msgb *msg;
|
||||
|
||||
if (ctrl->tx_msg) {
|
||||
LOGP(DV5CTRL, LOGL_DEBUG, "Timer fired the first time, resending message.\n");
|
||||
/* first expiry: repeat CONTROL; re-start T01 */
|
||||
/* send message towards DL */
|
||||
osmo_timer_schedule(&fi->timer, TIMEOUT, 0);
|
||||
msg = ctrl->tx_msg;
|
||||
ctrl->tx_msg = NULL;
|
||||
v5x_snd((ctrl->v5if) ? : ctrl->v5up->inst, V51_DLADDR_CTRL, msg);
|
||||
} else {
|
||||
LOGP(DV5CTRL, LOGL_DEBUG, "Timer fired the second time, indicate an error.\n");
|
||||
/* second expiry: send MDU-error_ind; go to IN_SERVICE */
|
||||
v51_mdu_error(fi, "Second timeout while waiting for CONTROL ACK.");
|
||||
osmo_fsm_inst_state_chg(fi, V51_CTRL_ST_IN_SERVICE, 0, 0);
|
||||
/* sending next pending message in queue */
|
||||
msg = msgb_dequeue(&ctrl->tx_queue);
|
||||
if (msg) {
|
||||
LOGP(DV5CTRL, LOGL_DEBUG, "Found pending message in queue.\n");
|
||||
v51_ctrl_send(ctrl, msg);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* events in state OUT OF SERVICE */
|
||||
static void v51_ctrl_fsm_oos(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||||
{
|
||||
switch (event) {
|
||||
case V51_CTRL_EV_MDU_START_TRAFFIC:
|
||||
/* go to IN_SERVICE */
|
||||
osmo_fsm_inst_state_chg(fi, V51_CTRL_ST_IN_SERVICE, 0, 0);
|
||||
break;
|
||||
case V51_CTRL_EV_MDU_STOP_TRAFFIC:
|
||||
v51_mdu_error(fi, "Got MDU-stop, but traffic not started.");
|
||||
break;
|
||||
case V51_CTRL_EV_MDU_CTRL:
|
||||
msgb_free(data);
|
||||
v51_mdu_error(fi, "Got MDU-CTRL, but traffic not started.");
|
||||
break;
|
||||
case V51_CTRL_EV_RX_CONTROL:
|
||||
v51_mdu_error(fi, "Received CONTROL, but traffic not started.");
|
||||
break;
|
||||
case V51_CTRL_EV_RX_CONTROL_ACK:
|
||||
v51_mdu_error(fi, "Received CONTROL ACK, but traffic not started.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* events in state IN SERVICE */
|
||||
static void v51_ctrl_fsm_ins(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||||
{
|
||||
switch (event) {
|
||||
case V51_CTRL_EV_MDU_START_TRAFFIC:
|
||||
break;
|
||||
case V51_CTRL_EV_MDU_STOP_TRAFFIC:
|
||||
v51_ctrl_stop(fi);
|
||||
break;
|
||||
case V51_CTRL_EV_MDU_CTRL:
|
||||
v51_ctrl_send(fi->priv, data);
|
||||
break;
|
||||
case V51_CTRL_EV_RX_CONTROL:
|
||||
v51_mdu_ctrl(fi, data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* events in state AWAIT PORT/COMMON ACK */
|
||||
static void v51_ctrl_fsm_aa(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||||
{
|
||||
switch (event) {
|
||||
case V51_CTRL_EV_MDU_START_TRAFFIC:
|
||||
break;
|
||||
case V51_CTRL_EV_MDU_STOP_TRAFFIC:
|
||||
v51_ctrl_stop(fi);
|
||||
break;
|
||||
case V51_CTRL_EV_MDU_CTRL:
|
||||
v51_ctrl_send(fi->priv, data);
|
||||
break;
|
||||
case V51_CTRL_EV_RX_CONTROL:
|
||||
v51_mdu_ctrl(fi, data);
|
||||
break;
|
||||
case V51_CTRL_EV_RX_CONTROL_ACK:
|
||||
v51_ctrl_ack(fi, data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct osmo_fsm_state v51_ctrl_fsm_states[] = {
|
||||
[V51_CTRL_ST_OUT_OF_SERVICE] = {
|
||||
.name = "OUT_OF_SERVICE",
|
||||
.in_event_mask = S(V51_CTRL_EV_MDU_START_TRAFFIC) |
|
||||
S(V51_CTRL_EV_MDU_STOP_TRAFFIC) |
|
||||
S(V51_CTRL_EV_MDU_CTRL) |
|
||||
S(V51_CTRL_EV_RX_CONTROL) |
|
||||
S(V51_CTRL_EV_RX_CONTROL_ACK),
|
||||
.out_state_mask = S(V51_CTRL_ST_IN_SERVICE),
|
||||
.action = v51_ctrl_fsm_oos,
|
||||
},
|
||||
[V51_CTRL_ST_IN_SERVICE] = {
|
||||
.name = "IN_SERVICE",
|
||||
.in_event_mask = S(V51_CTRL_EV_MDU_START_TRAFFIC) |
|
||||
S(V51_CTRL_EV_MDU_STOP_TRAFFIC) |
|
||||
S(V51_CTRL_EV_MDU_CTRL) |
|
||||
S(V51_CTRL_EV_RX_CONTROL),
|
||||
|
||||
.out_state_mask = S(V51_CTRL_ST_OUT_OF_SERVICE) |
|
||||
S(V51_CTRL_ST_AWAIT_ACK),
|
||||
.action = v51_ctrl_fsm_ins,
|
||||
},
|
||||
[V51_CTRL_ST_AWAIT_ACK] = {
|
||||
.name = "AWAIT_ACK",
|
||||
.in_event_mask = S(V51_CTRL_EV_MDU_START_TRAFFIC) |
|
||||
S(V51_CTRL_EV_MDU_STOP_TRAFFIC) |
|
||||
S(V51_CTRL_EV_MDU_CTRL) |
|
||||
S(V51_CTRL_EV_RX_CONTROL) |
|
||||
S(V51_CTRL_EV_RX_CONTROL_ACK),
|
||||
.out_state_mask = S(V51_CTRL_ST_OUT_OF_SERVICE) |
|
||||
S(V51_CTRL_ST_IN_SERVICE),
|
||||
.action = v51_ctrl_fsm_aa,
|
||||
},
|
||||
};
|
||||
|
||||
struct osmo_fsm v51_ctrl_fsm = {
|
||||
.name = "V51_CTRL",
|
||||
.states = v51_ctrl_fsm_states,
|
||||
.num_states = ARRAY_SIZE(v51_ctrl_fsm_states),
|
||||
.allstate_event_mask = 0,
|
||||
.allstate_action = NULL,
|
||||
.cleanup = NULL,
|
||||
.timer_cb = v51_ctrl_fsm_timer_cb,
|
||||
.log_subsys = DV5CTRL,
|
||||
.event_names = v51_ctrl_fsm_event_names,
|
||||
};
|
||||
|
||||
void v51_ctrl_init(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = osmo_fsm_register(&v51_ctrl_fsm);
|
||||
OSMO_ASSERT(!rc);
|
||||
LOGP(DV5CTRL, LOGL_NOTICE, "Using V5x control protocol\n");
|
||||
}
|
||||
|
||||
struct v5x_ctrl_proto *v51_ctrl_create(struct v5x_interface *v5if, struct v5x_user_port *v5up, uint16_t nr)
|
||||
{
|
||||
struct v5x_ctrl_proto *ctrl;
|
||||
|
||||
OSMO_ASSERT((v5if && !v5up) || (!v5if && v5up));
|
||||
|
||||
ctrl = talloc_zero((v5if) ? : v5up->inst, struct v5x_ctrl_proto);
|
||||
if (!ctrl)
|
||||
return NULL;
|
||||
ctrl->v5if = v5if;
|
||||
ctrl->v5up = v5up;
|
||||
INIT_LLIST_HEAD(&ctrl->tx_queue);
|
||||
|
||||
ctrl->fi = osmo_fsm_inst_alloc(&v51_ctrl_fsm, ctrl, ctrl, LOGL_DEBUG, NULL);
|
||||
if (!ctrl->fi) {
|
||||
v51_ctrl_destroy(ctrl);
|
||||
return NULL;
|
||||
}
|
||||
osmo_fsm_inst_update_id_f(ctrl->fi, "%d", nr);
|
||||
|
||||
return ctrl;
|
||||
}
|
||||
|
||||
void v51_ctrl_destroy(struct v5x_ctrl_proto *ctrl)
|
||||
{
|
||||
/* get rid of pending messages */
|
||||
msgb_queue_free(&ctrl->tx_queue);
|
||||
if (ctrl->tx_msg)
|
||||
msgb_free(ctrl->tx_msg);
|
||||
|
||||
if (ctrl->fi)
|
||||
osmo_fsm_inst_free(ctrl->fi);
|
||||
|
||||
free(ctrl);
|
||||
}
|
||||
|
||||
/***********************************************************************
|
||||
* V5 Message encoding / sending
|
||||
***********************************************************************/
|
||||
|
||||
/* G.964 Section 14.4.1.1 / Table 48 */
|
||||
struct msgb *v51_enc_ctrl_port(struct v5x_user_port *v5up, enum v51_ctrl_func_el cfe, bool is_isdn)
|
||||
{
|
||||
uint8_t cfe_ie = cfe | 0x80;
|
||||
struct v51_l3_hdr *l3h;
|
||||
struct msgb *msg = msgb_alloc_v5x();
|
||||
if (!msg)
|
||||
return NULL;
|
||||
|
||||
l3h = (struct v51_l3_hdr *) msgb_put(msg, sizeof(*l3h));
|
||||
l3h->pdisc = V51_CTRL_PDISC;
|
||||
l3h->l3_addr = ntohs(v51_l3_addr_enc(v5up->nr, is_isdn));
|
||||
l3h->msg_type = V51_CTRL_MSGT_PORT_CTRL;
|
||||
|
||||
msgb_tlv_put(msg, V51_CTRL_IEI_CTRL_F_ELEMENT, 1, &cfe_ie);
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
/* G.964 Section 14.4.1.2 / Table 49 */
|
||||
static struct msgb *v51_enc_ctrl_port_ack(struct v5x_user_port *v5up, enum v51_ctrl_func_el cfe, bool is_isdn)
|
||||
{
|
||||
uint8_t cfe_ie = cfe | 0x80;
|
||||
struct v51_l3_hdr *l3h;
|
||||
struct msgb *msg = msgb_alloc_v5x();
|
||||
if (!msg)
|
||||
return NULL;
|
||||
|
||||
l3h = (struct v51_l3_hdr *) msgb_put(msg, sizeof(*l3h));
|
||||
l3h->pdisc = V51_CTRL_PDISC;
|
||||
l3h->l3_addr = ntohs(v51_l3_addr_enc(v5up->nr, is_isdn));
|
||||
l3h->msg_type = V51_CTRL_MSGT_PORT_CTRL_ACK;
|
||||
|
||||
msgb_tlv_put(msg, V51_CTRL_IEI_CTRL_F_ELEMENT, 1, &cfe_ie);
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
/* G.964 Section 14.4.1.3 / Table 50 */
|
||||
static struct msgb *v51_enc_ctrl_common(struct v5x_interface *v5if, enum v51_ctrl_func_id cfi,
|
||||
uint8_t *variant, uint8_t *rej_cause, uint32_t *interface_id)
|
||||
{
|
||||
uint8_t cfi_ie = cfi | 0x80;
|
||||
struct v51_l3_hdr *l3h;
|
||||
struct msgb *msg = msgb_alloc_v5x();
|
||||
if (!msg)
|
||||
return NULL;
|
||||
|
||||
l3h = (struct v51_l3_hdr *) msgb_put(msg, sizeof(*l3h));
|
||||
l3h->pdisc = V51_CTRL_PDISC;
|
||||
l3h->l3_addr = ntohs(v51_l3_addr_enc(V51_DLADDR_CTRL, true));
|
||||
l3h->msg_type = V51_CTRL_MSGT_COMMON_CTRL;
|
||||
|
||||
msgb_tlv_put(msg, V51_CTRL_IEI_CTRL_F_ID, 1, &cfi_ie);
|
||||
if (variant) {
|
||||
/* Conditional: Variant */
|
||||
uint8_t variant_ie = *variant | 0x80;
|
||||
msgb_tlv_put(msg, V51_CTRL_IEI_VARIANT, 1, &variant_ie);
|
||||
}
|
||||
if (rej_cause) {
|
||||
/* Conditional: Rejection Cause */
|
||||
msgb_put_u8(msg, 0xF0 | (*rej_cause & 0x0F));
|
||||
}
|
||||
if (interface_id) {
|
||||
/* Conditional: Interface Id */
|
||||
uint8_t iid_ie[3];
|
||||
osmo_store32be_ext(*interface_id, iid_ie, 3);
|
||||
msgb_tlv_put(msg, V51_CTRL_IEI_INTERFACE_ID, sizeof(iid_ie), iid_ie);
|
||||
}
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
/* G.964 Section 14.4.1.4 / Table 51 */
|
||||
static struct msgb *v51_enc_ctrl_common_ack(struct v5x_interface *v5if, enum v51_ctrl_func_id cfi)
|
||||
{
|
||||
uint8_t cfi_ie = cfi | 0x80;
|
||||
struct v51_l3_hdr *l3h;
|
||||
struct msgb *msg = msgb_alloc_v5x();
|
||||
if (!msg)
|
||||
return NULL;
|
||||
|
||||
l3h = (struct v51_l3_hdr *) msgb_put(msg, sizeof(*l3h));
|
||||
l3h->pdisc = V51_CTRL_PDISC;
|
||||
l3h->l3_addr = htons(v51_l3_addr_enc(V51_DLADDR_CTRL, true));
|
||||
l3h->msg_type = V51_CTRL_MSGT_COMMON_CTRL_ACK;
|
||||
|
||||
msgb_tlv_put(msg, V51_CTRL_IEI_CTRL_F_ID, 1, &cfi_ie);
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
/***********************************************************************
|
||||
* V5 Message receiving / decoding
|
||||
***********************************************************************/
|
||||
|
||||
static int v51_rcv_ctrl_port(struct v5x_user_port *v5up, uint8_t msg_type, const struct tlv_parsed *tp)
|
||||
{
|
||||
enum v51_ctrl_func_el cfe = *TLVP_VAL(tp, V51_CTRL_IEI_CTRL_F_ELEMENT) & 0x7f;
|
||||
|
||||
switch (msg_type) {
|
||||
case V51_CTRL_MSGT_PORT_CTRL:
|
||||
/* send ACK to AN */
|
||||
v5x_snd(v5up->inst, V51_DLADDR_CTRL, v51_enc_ctrl_port_ack(v5up, cfe, v5up->type == V5X_USER_TYPE_ISDN));
|
||||
/* FIXME: send event to FSM */
|
||||
osmo_fsm_inst_dispatch(v5up->ctrl->fi, V51_CTRL_EV_RX_CONTROL, (void *)tp);
|
||||
return 0;
|
||||
case V51_CTRL_MSGT_PORT_CTRL_ACK:
|
||||
osmo_fsm_inst_dispatch(v5up->ctrl->fi, V51_CTRL_EV_RX_CONTROL_ACK, (void *)tp);
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int v51_rcv_ctrl_common(struct v5x_interface *v5if, uint8_t msg_type, const struct tlv_parsed *tp)
|
||||
{
|
||||
enum v51_ctrl_func_id cfi = *TLVP_VAL(tp, V51_CTRL_IEI_CTRL_F_ID) & 0x7f;
|
||||
|
||||
switch (msg_type) {
|
||||
case V51_CTRL_MSGT_COMMON_CTRL:
|
||||
v5x_snd(v5if, V51_DLADDR_CTRL, v51_enc_ctrl_common_ack(v5if, cfi));
|
||||
/* send event to FSM */
|
||||
osmo_fsm_inst_dispatch(v5if->control.ctrl->fi, V51_CTRL_EV_RX_CONTROL, (void *)tp);
|
||||
/* send ACK to AN */
|
||||
return 0;
|
||||
case V51_CTRL_MSGT_COMMON_CTRL_ACK:
|
||||
/* send event to FSM */
|
||||
osmo_fsm_inst_dispatch(v5if->control.ctrl->fi, V51_CTRL_EV_RX_CONTROL_ACK, (void *)tp);
|
||||
return 0;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
/* receive message from lower (DL) layer */
|
||||
int v51_rcv_ctrl(struct v5x_interface *v5if, uint16_t l3_addr, bool is_isdn, uint8_t msg_type, const struct tlv_parsed *tp)
|
||||
{
|
||||
struct v5x_user_port *v5up;
|
||||
|
||||
switch (msg_type) {
|
||||
case V51_CTRL_MSGT_PORT_CTRL:
|
||||
case V51_CTRL_MSGT_PORT_CTRL_ACK:
|
||||
v5up = v5x_user_port_find(v5if, l3_addr, is_isdn);
|
||||
if (!v5up) {
|
||||
LOGP(DV5CTRL, LOGL_ERROR, "Received port control message from unknown layer 3 address %d. Please check provisioning!\n", l3_addr);
|
||||
return -ENODEV;
|
||||
}
|
||||
if (v5up->type == V5X_USER_TYPE_ISDN && !is_isdn) {
|
||||
LOGP(DV5CTRL, LOGL_ERROR, "Received port control message from layer 3 address %d. AN is set to PSTN, LE to ISDN, please check provisioning!", l3_addr);
|
||||
return -EIO;
|
||||
}
|
||||
if (v5up->type == V5X_USER_TYPE_PSTN && is_isdn) {
|
||||
LOGP(DV5CTRL, LOGL_ERROR, "Received port control message from layer 3 address %d. AN is set to ISDN, LE to PSTN, please check provisioning!", l3_addr);
|
||||
return -EIO;
|
||||
}
|
||||
return v51_rcv_ctrl_port(v5up, msg_type, tp);
|
||||
case V51_CTRL_MSGT_COMMON_CTRL:
|
||||
case V51_CTRL_MSGT_COMMON_CTRL_ACK:
|
||||
if (l3_addr != V51_DLADDR_CTRL)
|
||||
return -EINVAL;
|
||||
return v51_rcv_ctrl_common(v5if, msg_type, tp);
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/***********************************************************************
|
||||
* V5 Message sending / encoding
|
||||
***********************************************************************/
|
||||
|
||||
/* send common message from upper layer */
|
||||
int v51_snd_ctrl_common(struct v5x_interface *v5if, enum v51_ctrl_func_id cfi,
|
||||
uint8_t *rej_cause, uint8_t *variant, uint32_t *interface_id)
|
||||
{
|
||||
LOGP(DV5CTRL, LOGL_DEBUG, "Sending MDU-CTRL.\n");
|
||||
|
||||
osmo_fsm_inst_dispatch(v5if->control.ctrl->fi, V51_CTRL_EV_MDU_CTRL,
|
||||
v51_enc_ctrl_common(v5if, cfi, variant, rej_cause, interface_id));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* send port message from upper layer */
|
||||
int v51_snd_ctrl_port(struct v5x_user_port *v5up, enum v51_ctrl_func_el cfe)
|
||||
{
|
||||
LOGP(DV5CTRL, LOGL_DEBUG, "Sending FE.\n");
|
||||
|
||||
osmo_fsm_inst_dispatch(v5up->ctrl->fi, V51_CTRL_EV_MDU_CTRL,
|
||||
v51_enc_ctrl_port(v5up, cfe, v5up->type == V5X_USER_TYPE_ISDN));
|
||||
return 0;
|
||||
}
|
||||
|
||||
void v51_start_ctrl(struct v5x_ctrl_proto *ctrl)
|
||||
{
|
||||
osmo_fsm_inst_dispatch(ctrl->fi, V51_CTRL_EV_MDU_START_TRAFFIC, NULL);
|
||||
}
|
||||
void v51_stop_ctrl(struct v5x_ctrl_proto *ctrl)
|
||||
{
|
||||
osmo_fsm_inst_dispatch(ctrl->fi, V51_CTRL_EV_MDU_STOP_TRAFFIC, NULL);
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
|
||||
void v51_ctrl_init(void);
|
||||
struct v5x_ctrl_proto *v51_ctrl_create(struct v5x_interface *v5if, struct v5x_user_port *v5up, uint16_t nr);
|
||||
void v51_ctrl_destroy(struct v5x_ctrl_proto *ctrl);
|
||||
int v51_rcv_ctrl(struct v5x_interface *v5if, uint16_t l3_addr, bool is_isdn, uint8_t msg_type, const struct tlv_parsed *tp);
|
||||
int v51_snd_ctrl_common(struct v5x_interface *v5if, enum v51_ctrl_func_id cfi,
|
||||
uint8_t *rej_cause, uint8_t *variant, uint32_t *interface_id);
|
||||
int v51_snd_ctrl_port(struct v5x_user_port *v5up, enum v51_ctrl_func_el cfe);
|
||||
void v51_start_ctrl(struct v5x_ctrl_proto *ctrl);
|
||||
void v51_stop_ctrl(struct v5x_ctrl_proto *ctrl);
|
Loading…
Reference in New Issue