/* (C) 2021 by Harald Welte * * 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 #include #include #include #include #include #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; }