First steps towards an open source implementation of the LE (Local Exchange) side of the ETSI/ITU V5 interface, in order to talk to Access Multiplexers (AN), such as Nokia EKSOS, DeTeWe ALIAN, etc.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
osmo-v5/v51_le_ctrl.c

371 lines
11 KiB

/* (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 <unitstd.h>
#include <stdint.h>
#include <errno.h>
#include <osmocom/core/util.h>
#include <osmocom/core/msgb.h>
#include <osmocom/gsm/tlv.h>
#include "v5x_internal.h"
/* PSTN Protocol FSM */
enum v51_le_ctrl_port_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 */
};
/***********************************************************************
* user port CTRL protocol FSM / G.964 Section 14
***********************************************************************/
enum v51_ctrl_port_fsm_state {
V51_CTRL_PORT_ST_OUT_OF_SERVICE,
V51_CTRL_PORT_ST_IN_SERVICE,
V51_CTRL_PORT_AWAIT_PORT_ACK,
};
enum v51_ctrl_port_fsm_event {
V51_CTRL_PE_MDU_START_TRAFFIC,
V51_CTRL_PE_MDU_STOP_TRAFFIC,
V51_CTRL_PE_MDU_CTRL,
V51_CTRL_PE_RX_PORT_CONTROL,
V51_CTRL_PE_RX_PORT_CONTROL_ACK,
};
static const struct value_string v51_ctrl_port_fsm_event_names[] = {
{ V51_CTRL_PE_MDU_START_TRAFFIC, "MDU-start_traffic" },
{ V51_CTRL_PE_MDU_STOP_TRAFFIC, "MDU-stop_traffic" },
{ V51_CTRL_PE_MDU_CTRL, "MDU-CTRL" },
{ V51_CTRL_PE_RX_PORT_CONTROL, "PORT_CONTROL" },
{ V51_CTRL_PE_RX_PORT_CONTROL_ACK, "PORT_CONTROL_ACK" },
{ 0, NULL }
};
static int v51_ctrl_port_fsm_timer_cb(struct osmo_fsm_inst *fi)
{
if () {
/* first expiry: repeat PORT_CONTROL; re-start T01 */
} else {
/* second expiry: send MDU-error_ind; go to IN_SERVICE */
osmo_fsm_inst_state_chg(fi, V51_CTRL_PORT_ST_IN_SERVICE);
}
}
static const struct osmo_fsm_state v51_ctrl_port_fsm_states[] = {
[V51_CTRL_PORT_ST_OUT_OF_SERVICE] = {
.name = "OUT_OF_SERVICE",
.in_event_mask = S(V51_CTRL_PE_MDU_START_TRAFFIC) |
S(V51_CTRL_PE_MDU_STOP_TRAFFIC) |
S(V51_CTRL_PE_MDU_STOP_TRAFFIC) |
S(V51_CTRL_PE_RX_PORT_CONTROL) |
S(V51_CTRL_PE_RX_PORT_CONTROL_ACK),
.out_state_mask = S(V51_CTRL_PORT_ST_IN_SERVICE),
},
[V51_CTRL_PORT_ST_IN_SERVICE] = {
.name = "IN_SERVICE",
.in_event_mask = S(V51_CTRL_PE_MDU_START_TRAFFIC) |
S(V51_CTRL_PE_MDU_STOP_TRAFFIC) |
S(V51_CTRL_PE_MDU_STOP_TRAFFIC) |
S(V51_CTRL_PE_RX_PORT_CONTROL),
.out_state_mask = S(V51_CTRL_PORT_ST_OUT_OF_SERVICE) |
S(V51_CTRL_PORT_AWAIT_PORT_ACK),
},
[V51_CTRL_PORT_AWAIT_PORT_ACK] = {
.name = "AWAIT_PORT_ACK",
.in_event_mask = S(V51_CTRL_PE_MDU_START_TRAFFIC) |
S(V51_CTRL_PE_MDU_STOP_TRAFFIC) |
S(V51_CTRL_PE_MDU_STOP_TRAFFIC) |
S(V51_CTRL_PE_RX_PORT_CONTROL) |
S(V51_CTRL_PE_RX_PORT_CONTROL_ACK),
.out_state_mask = S(V51_CTRL_PORT_ST_OUT_OF_SERVICE) |
S(V51_CTRL_PORT_ST_IN_SERVICE),
},
};
static struct osmo_fsm v51_ctrl_port_fsm = {
.name = "V51_CTRL_PORT",
.states = v51_ctrl_port_fsm_states,
.num_states = ARRAY_SIZE(v51_ctrl_port_fsm_states),
.allstate_event_mask = 0,
.allstate_action = NULL,
.cleanup = NULL,
.timer_cb = v51_ctrl_port_fsm_timer_cb,
.log_subsys = DV5CTRL,
.event_names = v51_ctrl_port_fsm_event_names,
};
/***********************************************************************
* common CTRL protocol FSM / G.964 Section 14
***********************************************************************/
enum v51_ctrl_common_fsm_state {
V51_CTRL_COM_ST_OUT_OF_SERVICE,
V51_CTRL_COM_ST_IN_SERVICE,
V51_CTRL_COM_AWAIT_COMMON_ACK,
};
enum v51_ctrl_common_fsm_event {
V51_CTRL_CE_MDU_START_TRAFFIC,
V51_CTRL_CE_MDU_STOP_TRAFFIC,
V51_CTRL_CE_MDU_CTRL,
V51_CTRL_CE_RX_COMMON_CONTROL,
V51_CTRL_CE_RX_COMMON_CONTROL_ACK,
};
static const struct value_string v51_ctrl_common_fsm_event_names[] = {
{ V51_CTRL_CE_MDU_START_TRAFFIC, "MDU-start_traffic" },
{ V51_CTRL_CE_MDU_STOP_TRAFFIC, "MDU-stop_traffic" },
{ V51_CTRL_CE_MDU_CTRL, "MDU-CTRL" },
{ V51_CTRL_CE_RX_COMMON_CONTROL, "COMMON_CONTROL" },
{ V51_CTRL_CE_RX_COMMON_CONTROL_ACK, "COMMON_CONTROL_ACK" },
{ 0, NULL }
};
static int v51_ctrl_common_fsm_timer_cb(struct osmo_fsm_inst *fi)
{
if () {
/* first expiry: repeat COMMON_CONTROL; re-start T02 */
} else {
/* second expiry: send MDU-error_ind; go to IN_SERVICE */
osmo_fsm_inst_state_chg(fi, V51_CTRL_COM_ST_IN_SERVICE);
}
}
static const struct osmo_fsm_state v51_ctrl_common_fsm_states[] = {
[V51_CTRL_COM_ST_OUT_OF_SERVICE] = {
.name = "OUT_OF_SERVICE",
.in_event_mask = S(V51_CTRL_CE_MDU_START_TRAFFIC) |
S(V51_CTRL_CE_MDU_STOP_TRAFFIC) |
S(V51_CTRL_CE_MDU_CTRL) |
S(V51_CTRL_CE_RX_COMMON_CONTROL) |
S(V51_CTRL_CE_RX_COMMON_CONTROL_ACK),
.out_state_mask = S(V51_CTRL_COM_ST_IN_SERVICE),
},
[V51_CTRL_COM_ST_IN_SERVICE] = {
.name = "IN_SERVICE",
.in_event_mask = S(V51_CTRL_CE_MDU_START_TRAFFIC) |
S(V51_CTRL_CE_MDU_STOP_TRAFFIC) |
S(V51_CTRL_CE_MDU_CTRL) |
S(V51_CTRL_CE_RX_COMMON_CONTROL),
.out_state_mask = S(V51_CTRL_COM_ST_OUT_OF_SERVICE) |
S(V51_CTRL_COM_AWAIT_COMMON_ACK),
},
[V51_CTRL_COM_AWAIT_COMMON_ACK] = {
.name = "AWAIT_COMMON_ACK",
.in_event_mask = S(V51_CTRL_CE_MDU_START_TRAFFIC) |
S(V51_CTRL_CE_MDU_STOP_TRAFFIC) |
S(V51_CTRL_CE_MDU_CTRL) |
S(V51_CTRL_CE_RX_COMMON_CONTROL) |
S(V51_CTRL_CE_RX_COMMON_CONTROL_ACK),
.out_state_mask = S(V51_CTRL_COM_ST_OUT_OF_SERVICE) |
S(V51_CTRL_COM_ST_IN_SERVICE),
},
};
static struct osmo_fsm v51_ctrl_common_fsm = {
.name = "V51_CTRL_COMMON",
.states = v51_ctrl_common_fsm_states,
.num_states = ARRAY_SIZE(v51_ctrl_common_fsm_states),
.allstate_event_mask = 0,
.allstate_action = NULL,
.cleanup = NULL,
.timer_cb = v51_ctrl_common_fsm_timer_cb,
.log_subsys = DV5CTRL,
.event_names = v51_ctrl_common_fsm_event_names,
};
/***********************************************************************
* V5 Message encoding / sending
***********************************************************************/
/* G.964 Section 14.4.1.1 / Table 48 */
struct mgsb *v51_enc_ctrl_port(struct v5x_user_port *v5up, enum v51_ctrl_func_el cfe)
{
uint8_t cfe_ie = cfe;
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 = v51_l3_addr_enc(v5up->nr, true);
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)
{
uint8_t cfe_ie = cfe;
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 = v51_l3_addr_enc(v5up->nr, true);
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_instance *v5i, enum v51_ctrl_func_id cfi,
uint8_t *rej_cause, uint8_t *variant, uint32_t *interface_id)
{
uint8_t cfi_ie = cfi;
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 = 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 */
msgb_tlv_put(msg, V51_CTRL_IEI_VARIANT, 1, variant);
}
if (rej_cause) {
/* Conditional: Rejection Cause */
msgb_put_u8(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_instance *v5i, enum v51_ctrl_func_id cfi)
{
uint8_t cfi_ie = cfi;
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 = 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, struct msgb *msg, const struct tlv_parsed *tp)
{
uint16_t l3_addr;
enum v51_ctrl_func_el cfe = *TLVP_VAL(tp, V51_CTRL_IEI_CTRL_F_ELEMENT);
struct v5x_user_port *port = FIXME;
switch (l3h->msg_type) {
case V51_CTRL_MSGT_PORT_CTRL:
/* FIXME: send event to FSM */
osmo_fsm_inst_dispatch(port->ctrl_fi, V51_CTRL_PE_RX_PORT_CONTROL, tp);
/* send ACK to AN */
return v51_tx(v5up->inst, v51_enc_ctrl_port_ack(v5up, cfe));
case V51_CTRL_MSGT_PORT_CTRL_ACK:
osmo_fsm_inst_dispatch(port->ctrl_fi, V51_CTRL_PE_RX_PORT_CONTROL_ACK, tp);
default:
return -EINVAL;
}
}
static int v51_rcv_ctrl_common(struct v5x_instance *v5i, struct msgb *msg, const struct tlv_parsed *tp)
{
uint16_t l3_addr;
enum v51_ctrl_func_id cfi = *TLVP_VAL(tp, V51_CTRL_IEI_CTRL_F_ID);
switch (l3h->msg_type) {
case V51_CTRL_MSGT_COMMON_CTRL:
/* send event to FSM */
osmo_fsm_inst_dispatch(v5i->ctrl_fi, V51_CTRL_CE_RX_COMMON_CONTROL, tp);
/* send ACK to AN */
return v51_tx(v5up->inst, v51_enc_ctrl_common_ack(v5i, cfi));
case V51_CTRL_MSGT_COMMON_CTRL_ACK:
/* send event to FSM */
osmo_fsm_inst_dispatch(v5i->ctrl_fi, V51_CTRL_CE_RX_COMMON_CONTROL_ACK, tp);
break;
default:
return -EINVAL;
}
}
static int v51_rcv_ctrl(struct v5x_instance *v5i, struct msgb *msg, const struct tlv_parsed *tp)
{
struct v51_l3_hdr *l3h = msgb_l3(msg);
uint16_t l3_addr;
bool is_isdn;
int rc;
l3_addr = v51_l3_addr_dec(l3h->l3_addr, &is_isdn);
if (!is_isdn)
return -EINVAL;
switch (l3h->msg_type) {
case V51_CTRL_MSGT_PORT_CTRL:
case V51_CTRL_MSGT_PORT_CTRL_ACK:
v5up = v5x_user_port_find(v5i, l4_addr);
if (!v5up)
return -ENODEV;
return v5x_rcv_ctrl_port(v5up, msg, tp);
case V51_CTRL_MSGT_COMMON_CTRL:
case V51_CTRL_MSGT_COMMON_CTRL_ACK:
if (l3_addr != V51_DLADDR_CTRL)
return -EINVAL;
return v5x_rcv_ctrl_common(v5i, msg, tp);
}
return rc;
}