/* ITU-T G.964 Section 13 PSTN signalling protocol specification - LE */ /* (C) 2022 by Andreas Eversberg * * 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. * */ /***********************************************************************/ /* internal data structures */ /***********************************************************************/ #include #include #include #include #include "v5x_internal.h" #include "v5x_protocol.h" #include "v5x_le_pstn_fsm.h" #include "v5x_le_management.h" #include "logging.h" /* Table 28/G.964 Timers in the LE */ #define TIMEOUT_T1 2 /* default */ #define TIMEOUT_T3 2 #define TIMEOUT_T4 2 #define TIMEOUT_Tr 5 #define TIMEOUT_Tt 10 #define S(x) (1 << (x)) /***********************************************************************/ /* state names, event names, primitives, ... */ /***********************************************************************/ /* 13.2.1.2 PSTN protocol FSM - LE(PSTN) */ enum v5x_le_pstn_state { V5x_LE_PTSN_S_LE0_OUT_OF_SERVICE, /* LE0 */ V5x_LE_PTSN_S_LE1_NULL, /* LE1 */ V5x_LE_PTSN_S_LE2_PATH_INITIATED_LE, /* LE2 */ V5x_LE_PTSN_S_LE3_PATH_INITIATED_AN, /* LE3 */ V5x_LE_PTSN_S_LE4_PATH_ACTIVE, /* LE4 */ V5x_LE_PTSN_S_LE5_PATH_DICONNECT_REQ, /* LE5 */ V5x_LE_PTSN_S_LE6_PORT_BLOCKED, /* LE6 */ }; enum v5x_le_pstn_fsm_event { /* inbound function elements from AN (Table 3/G.964) */ V5x_PSTN_LE_FE_establish_req, V5x_PSTN_LE_FE_establish_ack_req, V5x_PSTN_LE_FE_line_signal_req, V5x_PSTN_LE_FE_protocol_param_req, V5x_PSTN_LE_FE_disconnect_req, V5x_PSTN_LE_FE_disconnect_compl_req, /* inbound messages from AN (Table 3/G.964) */ V5x_PSTN_LE_DISCONNECT, V5x_PSTN_LE_DISCONNECT_COMPL, V5x_PSTN_LE_ESTABLISH, V5x_PSTN_LE_ESTABLISH_ACK, V5x_PSTN_LE_SIGNAL, V5x_PSTN_LE_SIGNAL_ACK, V5x_PSTN_LE_STATUS, /* timer events (Table 3/G.964) */ V5x_PSTN_LE_TIMEOUT_T1, V5x_PSTN_LE_TIMEOUT_T3, V5x_PSTN_LE_TIMEOUT_T4, V5x_PSTN_LE_TIMEOUT_Tr, V5x_PSTN_LE_TIMEOUT_Tt, /* inbound primitives from Mgmt (Table 3/G.964) */ V5x_PSTN_LE_MDU_CTRL_port_blocked, V5x_PSTN_LE_MDU_CTRL_port_unblocked, V5x_PSTN_LE_MDU_CTRL_restart_req, V5x_PSTN_LE_MDU_CTRL_restart_compl_req, }; static const struct value_string v5x_ctrl_le_port_fsm_event_names[] = { { V5x_PSTN_LE_FE_establish_req, "FE-establish_request" }, { V5x_PSTN_LE_FE_establish_ack_req, "FE-establish_acknowledge_request" }, { V5x_PSTN_LE_FE_line_signal_req, "FE-line_signal_request" }, { V5x_PSTN_LE_FE_protocol_param_req, "FE-protocol_parameter_request" }, { V5x_PSTN_LE_FE_disconnect_req, "FE-disconnect_request" }, { V5x_PSTN_LE_FE_disconnect_compl_req, "FE-disconnect_complete_request" }, { V5x_PSTN_LE_DISCONNECT, "DISCONNECT" }, { V5x_PSTN_LE_DISCONNECT_COMPL, "DISCONNECT COMPLETE" }, { V5x_PSTN_LE_ESTABLISH, "ESTABLISH " }, { V5x_PSTN_LE_ESTABLISH_ACK, "ESTABLISH ACK" }, { V5x_PSTN_LE_SIGNAL, "SIGNAL" }, { V5x_PSTN_LE_SIGNAL_ACK, "SIGNAL_ACK" }, { V5x_PSTN_LE_STATUS, "STATUS" }, { V5x_PSTN_LE_TIMEOUT_T1, "TIMEOUT T1" }, { V5x_PSTN_LE_TIMEOUT_T3, "TIMEOUT T3" }, { V5x_PSTN_LE_TIMEOUT_T4, "TIMEOUT T4" }, { V5x_PSTN_LE_TIMEOUT_Tr, "TIMEOUT Tr" }, { V5x_PSTN_LE_TIMEOUT_Tt, "TIMEOUT Tt" }, { V5x_PSTN_LE_MDU_CTRL_port_blocked, "MDU-CTRL (port blocked)" }, { V5x_PSTN_LE_MDU_CTRL_port_unblocked, "MDU-CTRL (port unblocked)" }, { V5x_PSTN_LE_MDU_CTRL_restart_req, "MDU-CTRL (restart request)" }, { V5x_PSTN_LE_MDU_CTRL_restart_compl_req, "MDU-CTRL (restart complete)" }, { 0, NULL } }; /*********************************************************************** * V5 Message encoding / sending ***********************************************************************/ /* G.964 Section 13.3.1 / Table 5 */ static struct msgb *v5x_enc_establish(struct v5x_user_port *v5up, uint8_t *cond_ie, uint8_t cond_len) { 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(v5up->nr, false)); l3h->msg_type = V5X_CTRL_MSGT_ESTABLISH; /* optional conditional IE */ if (cond_ie && cond_len) memcpy(msgb_put(msg, cond_len), cond_ie, cond_len); return msg; } /* G.964 Section 13.3.1 / Table 6 */ static struct msgb *v5x_enc_establish_ack(struct v5x_user_port *v5up, uint8_t *cond_ie, uint8_t cond_len) { 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(v5up->nr, false)); l3h->msg_type = V5X_CTRL_MSGT_ESTABLISH_ACK; /* optional conditional IE */ if (cond_ie && cond_len) memcpy(msgb_put(msg, cond_len), cond_ie, cond_len); return msg; } /* G.964 Section 13.3.1 / Table 7 */ static struct msgb *v5x_enc_signal(struct v5x_user_port *v5up, uint8_t seq_nr, uint8_t *cond_ie, uint8_t cond_len) { uint8_t seq_ie = seq_nr | 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(v5up->nr, false)); l3h->msg_type = V5X_CTRL_MSGT_SIGNAL; /* Sequence-number */ msgb_tlv_put(msg, V5X_CTRL_IEI_SEQUENCE_NR, 1, &seq_ie); /* manatory conditional IE */ OSMO_ASSERT(cond_ie && cond_len); memcpy(msgb_put(msg, cond_len), cond_ie, cond_len); return msg; } /* G.964 Section 13.3.1 / Table 8 */ static struct msgb *v5x_enc_signal_ack(struct v5x_user_port *v5up, uint8_t seq_nr) { uint8_t seq_ie = seq_nr | 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(v5up->nr, false)); l3h->msg_type = V5X_CTRL_MSGT_SIGNAL_ACK; /* Sequence-number */ msgb_tlv_put(msg, V5X_CTRL_IEI_SEQUENCE_NR, 1, &seq_ie); return msg; } /* G.964 Section 13.3.1 / Table 10 */ static struct msgb *v5x_enc_status_enquiry(struct v5x_user_port *v5up) { 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(v5up->nr, false)); l3h->msg_type = V5X_CTRL_MSGT_STATUS_ENQUIRY; return msg; } /* G.964 Section 13.3.1 / Table 11 */ static struct msgb *v5x_enc_disconnect(struct v5x_user_port *v5up, uint8_t *steady_ie, uint8_t steady_len) { 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(v5up->nr, false)); l3h->msg_type = V5X_CTRL_MSGT_DISCONNECT; /* optional conditional IE */ if (steady_ie && steady_len) memcpy(msgb_put(msg, steady_len), steady_ie, steady_len); return msg; } /* G.964 Section 13.3.1 / Table 12 */ static struct msgb *v5x_enc_disconnect_compl(struct v5x_user_port *v5up, uint8_t *steady_ie, uint8_t steady_len) { 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(v5up->nr, false)); l3h->msg_type = V5X_CTRL_MSGT_DISCONNECT_COMPLETE; /* optional conditional IE */ if (steady_ie && steady_len) memcpy(msgb_put(msg, steady_len), steady_ie, steady_len); return msg; } /* G.964 Section 13.3.1 / Table 13 */ static struct msgb *v5x_enc_protocol_param(struct v5x_user_port *v5up, uint8_t seq_nr, uint8_t *cond_ie, uint8_t cond_len) { uint8_t seq_ie = seq_nr | 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(v5up->nr, false)); l3h->msg_type = V5X_CTRL_MSGT_PROTOCOL_PARAMETER; /* Sequence-number */ msgb_tlv_put(msg, V5X_CTRL_IEI_SEQUENCE_NR, 1, &seq_ie); /* manatory conditional IE */ OSMO_ASSERT(cond_ie && cond_len); memcpy(msgb_put(msg, cond_len), cond_ie, cond_len); return msg; } /***********************************************************************/ /* upper layer interface */ /***********************************************************************/ static void rcv_fe(struct osmo_fsm_inst *fi, enum v5x_fe_prim prim, const struct tlv_parsed *tp) { struct v5x_pstn_proto *pstn = fi->priv; struct msgb *msg; uint8_t *tlv, len; msg = msgb_alloc_v5x(); OSMO_ASSERT(msg); /* add IE, if tp was given */ if (tp) { /* single octet */ if (TLVP_PRESENT(tp, V5X_CTRL_IEI_PULSE_NOTIFICATION)) *msgb_put(msg, 1) = *TLVP_VAL(tp, V5X_CTRL_IEI_PULSE_NOTIFICATION); if (TLVP_PRESENT(tp, V5X_CTRL_IEI_LINE_INFORMATION)) *msgb_put(msg, 1) = *TLVP_VAL(tp, V5X_CTRL_IEI_LINE_INFORMATION); if (TLVP_PRESENT(tp, V5X_CTRL_IEI_SEQUENCE_RESPONSE)) *msgb_put(msg, 1) = *TLVP_VAL(tp, V5X_CTRL_IEI_SEQUENCE_RESPONSE); /* multiple octets */ if (TLVP_PRESENT(tp, V5X_CTRL_IEI_PULSED_SIGNAL)) { len = TLVP_LEN(tp, V5X_CTRL_IEI_PULSED_SIGNAL); tlv = msgb_put(msg, 2 + len); tlv[0] = V5X_CTRL_IEI_PULSED_SIGNAL; tlv[1] = len; memcpy(tlv + 2, TLVP_VAL(tp, V5X_CTRL_IEI_PULSED_SIGNAL), len); } if (TLVP_PRESENT(tp, V5X_CTRL_IEI_STEADY_SIGNAL)) { len = TLVP_LEN(tp, V5X_CTRL_IEI_STEADY_SIGNAL); tlv = msgb_put(msg, 2 + len); tlv[0] = V5X_CTRL_IEI_STEADY_SIGNAL; tlv[1] = len; memcpy(tlv + 2, TLVP_VAL(tp, V5X_CTRL_IEI_STEADY_SIGNAL), len); } if (TLVP_PRESENT(tp, V5X_CTRL_IEI_DIGIT_SIGNAL)) { len = TLVP_LEN(tp, V5X_CTRL_IEI_DIGIT_SIGNAL); tlv = msgb_put(msg, 2 + len); tlv[0] = V5X_CTRL_IEI_DIGIT_SIGNAL; tlv[1] = len; memcpy(tlv + 2, TLVP_VAL(tp, V5X_CTRL_IEI_DIGIT_SIGNAL), len); } if (TLVP_PRESENT(tp, V5X_CTRL_IEI_RESOURCE_UNAVAILABLE)) { len = TLVP_LEN(tp, V5X_CTRL_IEI_RESOURCE_UNAVAILABLE); tlv = msgb_put(msg, 2 + len); tlv[0] = V5X_CTRL_IEI_RESOURCE_UNAVAILABLE; tlv[1] = len; memcpy(tlv + 2, TLVP_VAL(tp, V5X_CTRL_IEI_RESOURCE_UNAVAILABLE), len); } if (TLVP_PRESENT(tp, V5X_CTRL_IEI_METERING_REPORT)) { len = TLVP_LEN(tp, V5X_CTRL_IEI_METERING_REPORT); tlv = msgb_put(msg, 2 + len); tlv[0] = V5X_CTRL_IEI_METERING_REPORT; tlv[1] = len; memcpy(tlv + 2, TLVP_VAL(tp, V5X_CTRL_IEI_METERING_REPORT), len); } } v5x_le_pstn_fe_rcv(pstn->v5up, prim, msg); } static void rcv_mdu(struct osmo_fsm_inst *fi, enum v5x_mgmt_prim prim) { struct v5x_pstn_proto *pstn = fi->priv; v5x_le_pstn_mdu_rcv(pstn->v5up, prim); } void v5x_le_pstn_fe_snd(struct v5x_user_port *v5up, enum v5x_fe_prim prim, struct msgb *msg) { struct v5x_pstn_proto *pstn = v5up->pstn.proto; struct osmo_fsm_inst *fi = pstn->fi; enum v5x_le_pstn_fsm_event event; int rc; OSMO_ASSERT(msg); switch (prim) { case FE_establish_req: event = V5x_PSTN_LE_FE_establish_req; break; case FE_establish_ack_req: event = V5x_PSTN_LE_FE_establish_ack_req; break; case FE_line_signal_req: event = V5x_PSTN_LE_FE_line_signal_req; break; case FE_protocol_param_req: event = V5x_PSTN_LE_FE_protocol_param_req; break; case FE_disconnect_req: event = V5x_PSTN_LE_FE_disconnect_req; break; case FE_disconnect_compl_req: event = V5x_PSTN_LE_FE_disconnect_compl_req; break; default: LOGPFSML(v5up->port_fi, LOGL_NOTICE, "Got invalid prim %d at this protocol\n", prim); return; } /* send event to FSM */ rc = osmo_fsm_inst_dispatch(fi, event, msg); if (rc < 0) msgb_free(msg); } void v5x_le_pstn_mdu_snd(struct v5x_user_port *v5up, enum v5x_mgmt_prim prim) { struct v5x_pstn_proto *pstn = v5up->pstn.proto; struct osmo_fsm_inst *fi = pstn->fi; enum v5x_le_pstn_fsm_event event; switch (prim) { case MDU_CTRL_port_blocked: event = V5x_PSTN_LE_MDU_CTRL_port_blocked; break; case MDU_CTRL_port_unblocked: event = V5x_PSTN_LE_MDU_CTRL_port_unblocked; break; case MDU_CTRL_port_restart_req: event = V5x_PSTN_LE_MDU_CTRL_restart_req; break; case MDU_CTRL_port_restart_compl: event = V5x_PSTN_LE_MDU_CTRL_restart_compl_req; break; default: LOGPFSML(v5up->port_fi, LOGL_NOTICE, "Got invalid prim %d at this protocol\n", prim); return; } /* send event to FSM */ osmo_fsm_inst_dispatch(fi, event, NULL); } /***********************************************************************/ /* lower layer interface */ /***********************************************************************/ static void dl_send(struct osmo_fsm_inst *fi, struct msgb *msg) { struct v5x_pstn_proto *pstn = fi->priv; v5x_dl_snd(pstn->v5up->interface, V5X_DLADDR_PSTN, msg); } /***********************************************************************/ /* PSTN state FSM */ /***********************************************************************/ static void start_timer_Tr(struct v5x_pstn_proto *pstn) { LOGPFSML(pstn->fi, LOGL_DEBUG, "Start timer Tr\n"); osmo_timer_schedule(&pstn->timer_Tr, TIMEOUT_Tr, 0); } static void start_timer_Tt(struct v5x_pstn_proto *pstn) { LOGPFSML(pstn->fi, LOGL_DEBUG, "Start timer Tt\n"); osmo_timer_schedule(&pstn->timer_Tt, TIMEOUT_Tt, 0); } static void stop_timer_Tr(struct v5x_pstn_proto *pstn) { if (osmo_timer_pending(&pstn->timer_Tr)) { LOGPFSML(pstn->fi, LOGL_DEBUG, "Stop timer Tr\n"); osmo_timer_del(&pstn->timer_Tr); } } static void stop_timer_Tt(struct v5x_pstn_proto *pstn) { if (osmo_timer_pending(&pstn->timer_Tt)) { LOGPFSML(pstn->fi, LOGL_DEBUG, "Stop timer Tt\n"); osmo_timer_del(&pstn->timer_Tt); } } static void stop_timer(struct osmo_fsm_inst *fi) { struct v5x_pstn_proto *pstn = fi->priv; LOGPFSML(fi, LOGL_DEBUG, "Stop all timers\n"); osmo_timer_del(&fi->timer); stop_timer_Tr(pstn); stop_timer_Tt(pstn); } static void start_timer(struct osmo_fsm_inst *fi, enum v5x_le_pstn_fsm_event event, int count) { struct v5x_pstn_proto *pstn = fi->priv; int timeout = 0; const char *timer_name = NULL; switch (event) { case V5x_PSTN_LE_TIMEOUT_T1: timer_name = "T1"; timeout = TIMEOUT_T1; fi->T = 1; break; case V5x_PSTN_LE_TIMEOUT_T3: timer_name = "T3"; timeout = TIMEOUT_T3; fi->T = 3; break; case V5x_PSTN_LE_TIMEOUT_T4: timer_name = "T4"; timeout = TIMEOUT_T4; fi->T = 4; break; default: OSMO_ASSERT(0); } LOGPFSML(fi, LOGL_DEBUG, "Start timer %s (count = %d)\n", timer_name, count); pstn->timeout_event = event; pstn->timeout_count = count; osmo_timer_schedule(&fi->timer, timeout, 0); } static int v5x_le_pstn_fsm_timer_cb(struct osmo_fsm_inst *fi) { struct v5x_pstn_proto *pstn = fi->priv; osmo_fsm_inst_dispatch(fi, pstn->timeout_event, NULL); return 0; } static void timeout_Tr_cb(void *data) { struct v5x_pstn_proto *pstn = data; osmo_fsm_inst_dispatch(pstn->fi, V5x_PSTN_LE_TIMEOUT_Tr, NULL); } static void timeout_Tt_cb(void *data) { struct v5x_pstn_proto *pstn = data; osmo_fsm_inst_dispatch(pstn->fi, V5x_PSTN_LE_TIMEOUT_Tt, NULL); } static void do_status_enquiry(struct osmo_fsm_inst *fi) { struct v5x_pstn_proto *pstn = fi->priv; LOGPFSML(fi, LOGL_NOTICE, "Received message not allowed in current state, sending status enquiry.\n"); /* start T4 */ start_timer(fi, V5x_PSTN_LE_TIMEOUT_T4, 1); /* send STATUS ENQUIRY */ dl_send(fi, v5x_enc_status_enquiry(pstn->v5up)); } static void do_establish_request(struct osmo_fsm_inst *fi, struct msgb *msg) { struct v5x_pstn_proto *pstn = fi->priv; /* change state to LE2 */ osmo_fsm_inst_state_chg(fi, V5x_LE_PTSN_S_LE2_PATH_INITIATED_LE, 0, 0); /* init sequence variables */ pstn->S_s = pstn->S_a = pstn->S_r = 0; /* start T1 */ start_timer(fi, V5x_PSTN_LE_TIMEOUT_T1, 1); /* store ESTABLISH message */ if (pstn->tx_msg) msgb_free(pstn->tx_msg); pstn->tx_msg = msgb_copy(msg, NULL); /* send ESTABLISH */ dl_send(fi, v5x_enc_establish(pstn->v5up, msg->data, msg->len)); msgb_free(msg); } static void do_establish_indication(struct osmo_fsm_inst *fi, const struct tlv_parsed *tp) { struct v5x_pstn_proto *pstn = fi->priv; /* init sequence variables */ pstn->S_s = pstn->S_a = pstn->S_r = 0; /* send FE-establish_indication */ rcv_fe(fi, FE_establish_ind, tp); } static void do_disconnect_complete(struct osmo_fsm_inst *fi, const struct tlv_parsed *tp) { /* change state to LE1 */ osmo_fsm_inst_state_chg(fi, V5x_LE_PTSN_S_LE1_NULL, 0, 0); /* stop all timers */ stop_timer(fi); /* send FE-disconnect_complete_indication */ rcv_fe(fi, FE_disconnect_compl_ind, tp); } static void do_disconnect_with_error(struct osmo_fsm_inst *fi) { struct v5x_pstn_proto *pstn = fi->priv; /* change state to LE5 */ osmo_fsm_inst_state_chg(fi, V5x_LE_PTSN_S_LE5_PATH_DICONNECT_REQ, 0, 0); /* stop all timers */ stop_timer(fi); /* start T3 */ start_timer(fi, V5x_PSTN_LE_TIMEOUT_T3, 1); /* store empty disconnect message */ if (pstn->tx_msg) { msgb_free(pstn->tx_msg); pstn->tx_msg = NULL; } /* send DISCONNECT */ dl_send(fi, v5x_enc_disconnect(pstn->v5up, NULL, 0)); /* send MDU-error_indication */ rcv_mdu(fi, MDU_error_ind); } static void do_block(struct osmo_fsm_inst *fi) { /* change state to LE6 */ osmo_fsm_inst_state_chg(fi, V5x_LE_PTSN_S_LE6_PORT_BLOCKED, 0, 0); /* stop all timers */ stop_timer(fi); /* send FE-disconnect_complete_indication */ rcv_fe(fi, FE_disconnect_compl_ind, NULL); } static void do_restart(struct osmo_fsm_inst *fi) { /* change state to LE0 */ osmo_fsm_inst_state_chg(fi, V5x_LE_PTSN_S_LE0_OUT_OF_SERVICE, 0, 0); /* send FE-disconnect_complete_indication */ rcv_fe(fi, FE_disconnect_compl_ind, NULL); /* send MDU-CTRL (restart ack) */ rcv_mdu(fi, MDU_CTRL_port_restart_ack); } static void pstn_le0_out_of_service(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct v5x_pstn_proto *pstn = fi->priv; struct msgb *msg = data; const struct tlv_parsed *tp = data; uint8_t cause; switch (event) { case V5x_PSTN_LE_FE_establish_req: msgb_free(msg); /* ignore */ break; case V5x_PSTN_LE_ESTABLISH: case V5x_PSTN_LE_DISCONNECT: case V5x_PSTN_LE_ESTABLISH_ACK: case V5x_PSTN_LE_SIGNAL: case V5x_PSTN_LE_SIGNAL_ACK: /* do several thing here */ do_status_enquiry(fi); break; case V5x_PSTN_LE_DISCONNECT_COMPL: /* ignore */ break; case V5x_PSTN_LE_FE_establish_ack_req: msgb_free(msg); /* ignore */ break; case V5x_PSTN_LE_STATUS: cause = *TLVP_VAL(tp, V5X_CTRL_IEI_CAUSE) & 0x7f; if (cause == 0) { /* stop T4 */ stop_timer(fi); } else { /* send MDU-error_indication */ rcv_mdu(fi, MDU_error_ind); } break; case V5x_PSTN_LE_FE_protocol_param_req: msgb_free(msg); /* ignore */ break; case V5x_PSTN_LE_FE_disconnect_req: msgb_free(msg); /* ignore */ break; case V5x_PSTN_LE_MDU_CTRL_port_blocked: /* change state to LE6 */ osmo_fsm_inst_state_chg(fi, V5x_LE_PTSN_S_LE6_PORT_BLOCKED, 0, 0); break; case V5x_PSTN_LE_MDU_CTRL_port_unblocked: /* ignore */ break; case V5x_PSTN_LE_MDU_CTRL_restart_req: /* send MDU-CTRL (restart ack) */ rcv_mdu(fi, MDU_CTRL_port_restart_ack); break; case V5x_PSTN_LE_MDU_CTRL_restart_compl_req: /* change state to LE1 */ osmo_fsm_inst_state_chg(fi, V5x_LE_PTSN_S_LE1_NULL, 0, 0); break; case V5x_PSTN_LE_TIMEOUT_T4: /* first or second timeout ? */ if (pstn->timeout_count <= 3) { /* start T4 */ start_timer(fi, V5x_PSTN_LE_TIMEOUT_T4, pstn->timeout_count + 1); /* send STATUS ENQUIRY again */ dl_send(fi, v5x_enc_status_enquiry(pstn->v5up)); } else { /* send MDU-error_indication */ rcv_mdu(fi, MDU_error_ind); } break; default: OSMO_ASSERT(0); } } static void pstn_le1_null(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct v5x_pstn_proto *pstn = fi->priv; struct msgb *msg = data; const struct tlv_parsed *tp = data; uint8_t cause; switch (event) { case V5x_PSTN_LE_FE_establish_req: /* do several thing here */ do_establish_request(fi, msg); break; case V5x_PSTN_LE_ESTABLISH: /* change state to LE3 */ osmo_fsm_inst_state_chg(fi, V5x_LE_PTSN_S_LE3_PATH_INITIATED_AN, 0, 0); /* do several thing here */ do_establish_indication(fi, tp); break; case V5x_PSTN_LE_DISCONNECT: /* send DISCONNECT COMPLETE */ dl_send(fi, v5x_enc_disconnect_compl(pstn->v5up, NULL, 0)); break; case V5x_PSTN_LE_ESTABLISH_ACK: case V5x_PSTN_LE_SIGNAL: case V5x_PSTN_LE_SIGNAL_ACK: /* do several thing here */ do_status_enquiry(fi); break; case V5x_PSTN_LE_STATUS: cause = *TLVP_VAL(tp, V5X_CTRL_IEI_CAUSE) & 0x7f; if (cause == 0) { /* stop T4 */ stop_timer(fi); } else { /* do several thing here */ do_disconnect_with_error(fi); } break; case V5x_PSTN_LE_FE_protocol_param_req: msgb_free(msg); /* ignore */ break; case V5x_PSTN_LE_MDU_CTRL_port_blocked: /* do several thing here */ do_block(fi); break; case V5x_PSTN_LE_MDU_CTRL_port_unblocked: /* ignore */ break; case V5x_PSTN_LE_MDU_CTRL_restart_req: /* do several thing here */ do_restart(fi); break; case V5x_PSTN_LE_TIMEOUT_T4: /* first or second timeout ? */ if (pstn->timeout_count < 3) { /* start T4 */ start_timer(fi, V5x_PSTN_LE_TIMEOUT_T4, pstn->timeout_count + 1); /* send STATUS ENQUIRY again */ dl_send(fi, v5x_enc_status_enquiry(pstn->v5up)); } else { /* do several thing here */ do_disconnect_with_error(fi); } break; default: OSMO_ASSERT(0); } } static void pstn_le2_path_initiated_le(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct v5x_pstn_proto *pstn = fi->priv; struct msgb *msg = data; const struct tlv_parsed *tp = data; uint8_t cause; switch (event) { case V5x_PSTN_LE_ESTABLISH: /* do several thing here */ do_establish_indication(fi, tp); break; case V5x_PSTN_LE_DISCONNECT: /* send DISCONNECT COMPLETE */ dl_send(fi, v5x_enc_disconnect_compl(pstn->v5up, NULL, 0)); /* do several thing here */ do_disconnect_complete(fi, tp); break; case V5x_PSTN_LE_DISCONNECT_COMPL: /* do several thing here */ do_disconnect_complete(fi, tp); break; case V5x_PSTN_LE_FE_establish_ack_req: /* change state to LE4 */ osmo_fsm_inst_state_chg(fi, V5x_LE_PTSN_S_LE4_PATH_ACTIVE, 0, 0); /* stop T1 */ stop_timer(fi); /* send ESTABLISH ACK */ dl_send(fi, v5x_enc_establish_ack(pstn->v5up, msg->data, msg->len)); msgb_free(msg); break; case V5x_PSTN_LE_ESTABLISH_ACK: /* change state to LE4 */ osmo_fsm_inst_state_chg(fi, V5x_LE_PTSN_S_LE4_PATH_ACTIVE, 0, 0); /* stop T1 */ stop_timer(fi); /* send FE-establish_acknowledge_indication */ rcv_fe(fi, FE_establish_ack_ind, tp); break; case V5x_PSTN_LE_SIGNAL: case V5x_PSTN_LE_SIGNAL_ACK: /* do several thing here */ do_status_enquiry(fi); break; case V5x_PSTN_LE_STATUS: cause = *TLVP_VAL(tp, V5X_CTRL_IEI_CAUSE) & 0x7f; if (cause == 0) { /* stop T4 */ stop_timer(fi); } else { /* do several thing here */ do_disconnect_with_error(fi); } break; case V5x_PSTN_LE_FE_protocol_param_req: msgb_free(msg); /* ignore */ break; case V5x_PSTN_LE_FE_disconnect_req: /* change state to LE5 */ osmo_fsm_inst_state_chg(fi, V5x_LE_PTSN_S_LE5_PATH_DICONNECT_REQ, 0, 0); /* stop all timers */ stop_timer(fi); /* start T3 */ start_timer(fi, V5x_PSTN_LE_TIMEOUT_T3, 2); /* store DISCONNECT message */ if (pstn->tx_msg) msgb_free(pstn->tx_msg); pstn->tx_msg = msgb_copy(msg, NULL); /* send DISCONNECT */ dl_send(fi, v5x_enc_disconnect(pstn->v5up, msg->data, msg->len)); msgb_free(msg); break; case V5x_PSTN_LE_FE_disconnect_compl_req: // NOTE: The Table 31-32/G.964 is wrong at this point, there is no use here msgb_free(msg); /* ignore */ break; case V5x_PSTN_LE_MDU_CTRL_port_blocked: /* do several thing here */ do_block(fi); break; case V5x_PSTN_LE_MDU_CTRL_port_unblocked: /* ignore */ break; case V5x_PSTN_LE_MDU_CTRL_restart_req: /* send DISCONNECT COMPLETE */ dl_send(fi, v5x_enc_disconnect_compl(pstn->v5up, NULL, 0)); /* do several thing here */ do_restart(fi); break; case V5x_PSTN_LE_MDU_CTRL_restart_compl_req: /* ignore */ break; case V5x_PSTN_LE_TIMEOUT_T1: /* first timeout ? */ if (pstn->timeout_count == 1) { /* start T1 */ start_timer(fi, V5x_PSTN_LE_TIMEOUT_T1, 2); /* send ESTABLISH again */ OSMO_ASSERT(pstn->tx_msg); dl_send(fi, v5x_enc_establish(pstn->v5up, pstn->tx_msg->data, pstn->tx_msg->len)); } else { /* do several thing here */ do_disconnect_with_error(fi); } break; case V5x_PSTN_LE_TIMEOUT_T4: /* first or second timeout ? */ if (pstn->timeout_count < 3) { /* start T4 */ start_timer(fi, V5x_PSTN_LE_TIMEOUT_T4, pstn->timeout_count + 1); /* send STATUS ENQUIRY again */ dl_send(fi, v5x_enc_status_enquiry(pstn->v5up)); } else { /* do several thing here */ do_disconnect_with_error(fi); } break; default: OSMO_ASSERT(0); } } static void pstn_le3_path_initiated_an(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct v5x_pstn_proto *pstn = fi->priv; struct msgb *msg = data; const struct tlv_parsed *tp = data; uint8_t cause; switch (event) { case V5x_PSTN_LE_FE_establish_req: /* do several thing here */ do_establish_request(fi, msg); break; case V5x_PSTN_LE_ESTABLISH: /* do several thing here */ do_establish_indication(fi, tp); break; case V5x_PSTN_LE_DISCONNECT: /* send DISCONNECT COMPLETE */ dl_send(fi, v5x_enc_disconnect_compl(pstn->v5up, NULL, 0)); /* do several thing here */ do_disconnect_complete(fi, tp); break; case V5x_PSTN_LE_DISCONNECT_COMPL: /* do several thing here */ do_disconnect_complete(fi, tp); break; case V5x_PSTN_LE_FE_establish_ack_req: /* change state to LE4 */ osmo_fsm_inst_state_chg(fi, V5x_LE_PTSN_S_LE4_PATH_ACTIVE, 0, 0); /* send ESTABLISH ACK */ dl_send(fi, v5x_enc_establish_ack(pstn->v5up, msg->data, msg->len)); msgb_free(msg); break; case V5x_PSTN_LE_ESTABLISH_ACK: case V5x_PSTN_LE_SIGNAL: case V5x_PSTN_LE_SIGNAL_ACK: /* do several thing here */ do_status_enquiry(fi); break; case V5x_PSTN_LE_STATUS: cause = *TLVP_VAL(tp, V5X_CTRL_IEI_CAUSE) & 0x7f; if (cause == 0) { /* stop T4 */ stop_timer(fi); } else { /* do several thing here */ do_disconnect_with_error(fi); } break; case V5x_PSTN_LE_FE_protocol_param_req: msgb_free(msg); /* ignore */ break; case V5x_PSTN_LE_FE_disconnect_req: /* change state to LE5 */ osmo_fsm_inst_state_chg(fi, V5x_LE_PTSN_S_LE5_PATH_DICONNECT_REQ, 0, 0); /* stop all timers */ stop_timer(fi); /* start T3 */ start_timer(fi, V5x_PSTN_LE_TIMEOUT_T3, 2); /* store DISCONNECT message */ if (pstn->tx_msg) msgb_free(pstn->tx_msg); pstn->tx_msg = msgb_copy(msg, NULL); /* Send DISCONNECT */ dl_send(fi, v5x_enc_disconnect(pstn->v5up, msg->data, msg->len)); msgb_free(msg); break; case V5x_PSTN_LE_FE_disconnect_compl_req: /* change state to LE1 */ // NOTE: The Table 31-32/G.964 is wrong at this point, see Figure B.3/G.964 osmo_fsm_inst_state_chg(fi, V5x_LE_PTSN_S_LE1_NULL, 0, 0); /* stop all timers */ stop_timer(fi); /* send DISCONNECT COMPLETE */ dl_send(fi, v5x_enc_disconnect_compl(pstn->v5up, msg->data, msg->len)); msgb_free(msg); break; case V5x_PSTN_LE_MDU_CTRL_port_blocked: /* do several thing here */ do_block(fi); break; case V5x_PSTN_LE_MDU_CTRL_port_unblocked: /* ignore */ break; case V5x_PSTN_LE_MDU_CTRL_restart_req: /* send DISCONNECT COMPLETE */ dl_send(fi, v5x_enc_disconnect_compl(pstn->v5up, NULL, 0)); /* do several thing here */ do_restart(fi); break; case V5x_PSTN_LE_MDU_CTRL_restart_compl_req: /* ignore */ break; case V5x_PSTN_LE_TIMEOUT_T4: /* first or second timeout ? */ if (pstn->timeout_count < 3) { /* start T4 */ start_timer(fi, V5x_PSTN_LE_TIMEOUT_T4, pstn->timeout_count + 1); /* send STATUS ENQUIRY again */ dl_send(fi, v5x_enc_status_enquiry(pstn->v5up)); } else { /* do several thing here */ do_disconnect_with_error(fi); } break; default: OSMO_ASSERT(0); } } static void pstn_le4_path_active(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct v5x_pstn_proto *pstn = fi->priv; struct msgb *msg = data; const struct tlv_parsed *tp = data; uint8_t cause, seq_nr, unacked, acked; switch (event) { case V5x_PSTN_LE_ESTABLISH: /* do several thing here */ do_status_enquiry(fi); break; case V5x_PSTN_LE_DISCONNECT: /* send DISCONNECT COMPLETE */ dl_send(fi, v5x_enc_disconnect_compl(pstn->v5up, NULL, 0)); /* do several thing here */ do_disconnect_complete(fi, tp); break; case V5x_PSTN_LE_DISCONNECT_COMPL: /* send FE-disconnect_complete_indication */ rcv_fe(fi, FE_disconnect_compl_ind, NULL); /* do several thing here */ do_disconnect_complete(fi, tp); break; case V5x_PSTN_LE_ESTABLISH_ACK: /* start T4 */ start_timer(fi, V5x_PSTN_LE_TIMEOUT_T4, 1); /* send STATUS ENQUIRY */ dl_send(fi, v5x_enc_status_enquiry(pstn->v5up)); break; case V5x_PSTN_LE_SIGNAL: /* get sequence number and check if msg is in sequence */ seq_nr = *TLVP_VAL(tp, V5X_CTRL_IEI_SEQUENCE_NR) & 0x7f; if (pstn->S_r == seq_nr) { LOGPFSML(fi, LOGL_DEBUG, "Received expected 'receive' sequence number: S(R)=%d == M(R)=%d\n", pstn->S_r, seq_nr); /* start Tr, if not pending */ if (!osmo_timer_pending(&pstn->timer_Tr)) start_timer_Tr(pstn); /* increment receive sequence number */ pstn->S_r = (pstn->S_r + 1) & 127; /* send FE-signal_indication */ rcv_fe(fi, FE_line_signal_ind, tp); } else { LOGPFSML(fi, LOGL_ERROR, "Invalid 'receive' sequence number: S(R)=%d != M(R)=%d\n", pstn->S_r, seq_nr); /* send FE-signal_indication */ rcv_fe(fi, FE_line_signal_ind, tp); /* do several thing here */ do_disconnect_with_error(fi); } break; case V5x_PSTN_LE_SIGNAL_ACK: seq_nr = *TLVP_VAL(tp, V5X_CTRL_IEI_SEQUENCE_NR) & 0x7f; unacked = (pstn->S_s - pstn->S_a) & 127; acked = (pstn->S_s - seq_nr) & 127; if (acked <= unacked) { LOGPFSML(fi, LOGL_DEBUG, "Received expected 'transmit' sequence number: M(R)=%d is inside " "S(A)=%d...S(S)=%d range\n", seq_nr, pstn->S_a, pstn->S_s); LOGPFSML(fi, LOGL_DEBUG, "We had %d unacked messages, now we have %d acked messages.\n", unacked, acked); /* update acknowledge sequence number */ pstn->S_a = seq_nr; if (pstn->S_a == pstn->S_s) { LOGPFSML(fi, LOGL_DEBUG, "No more outstanding message, so we don't need Tt anymore.\n"); /* stop Tt */ stop_timer_Tt(pstn); } else { LOGPFSML(fi, LOGL_DEBUG, "There are still outstanding message, we still need Tt.\n"); /* start Tt */ start_timer_Tt(pstn); } } else { LOGPFSML(fi, LOGL_ERROR, "Invalid 'transmit' sequence number: M(R)=%d not inside " "S(A)=%d...S(S)=%d range\n", seq_nr, pstn->S_a, pstn->S_s); /* do several thing here */ do_disconnect_with_error(fi); } break; case V5x_PSTN_LE_STATUS: cause = *TLVP_VAL(tp, V5X_CTRL_IEI_CAUSE) & 0x7f; if (cause == 0) { /* stop T4 */ stop_timer(fi); } else { /* do several thing here */ do_disconnect_with_error(fi); } break; case V5x_PSTN_LE_FE_line_signal_req: /* if the maximum number of outstanding sequence numbers is not exceeded (127) */ if (((pstn->S_s + 1) & 127) != pstn->S_a) { /* start Tt, if not pending: This is wrong in the state tables, but look at 13.5.5.2.2! */ if (!osmo_timer_pending(&pstn->timer_Tt)) start_timer_Tt(pstn); /* store SIGNAL message */ if (pstn->tx_msg) msgb_free(pstn->tx_msg); pstn->tx_msg = msgb_copy(msg, NULL); /* send SIGNAL */ dl_send(fi, v5x_enc_signal(pstn->v5up, pstn->S_s, msg->data, msg->len)); msgb_free(msg); /* increment send sequence number */ pstn->S_s = (pstn->S_s + 1) & 127; } else { /* send SIGNAL */ dl_send(fi, v5x_enc_signal(pstn->v5up, pstn->S_s, msg->data, msg->len)); msgb_free(msg); /* do several thing here */ do_disconnect_with_error(fi); } break; case V5x_PSTN_LE_FE_protocol_param_req: /* if the maximum number of outstanding sequence numbers is not exceeded (127) */ if (((pstn->S_s + 1) & 127) != pstn->S_a) { /* start Tt */ start_timer_Tt(pstn); /* send SIGNAL */ dl_send(fi, v5x_enc_protocol_param(pstn->v5up, pstn->S_s, msg->data, msg->len)); msgb_free(msg); /* increment send sequence number */ pstn->S_s = (pstn->S_s + 1) & 127; } else { /* send SIGNAL */ dl_send(fi, v5x_enc_protocol_param(pstn->v5up, pstn->S_s, msg->data, msg->len)); msgb_free(msg); /* do several thing here */ do_disconnect_with_error(fi); } break; case V5x_PSTN_LE_FE_disconnect_req: /* change state to LE5 */ osmo_fsm_inst_state_chg(fi, V5x_LE_PTSN_S_LE5_PATH_DICONNECT_REQ, 0, 0); /* stop all timers */ stop_timer(fi); /* start T3 */ start_timer(fi, V5x_PSTN_LE_TIMEOUT_T3, 2); /* store DISCONNECT message */ if (pstn->tx_msg) msgb_free(pstn->tx_msg); pstn->tx_msg = msgb_copy(msg, NULL); /* Send DISCONNECT */ dl_send(fi, v5x_enc_disconnect(pstn->v5up, msg->data, msg->len)); msgb_free(msg); break; case V5x_PSTN_LE_FE_disconnect_compl_req: msgb_free(msg); /* ignore */ break; case V5x_PSTN_LE_MDU_CTRL_port_blocked: /* do several thing here */ do_block(fi); break; case V5x_PSTN_LE_MDU_CTRL_port_unblocked: /* ignore */ break; case V5x_PSTN_LE_MDU_CTRL_restart_req: /* send DISCONNECT COMPLETE */ dl_send(fi, v5x_enc_disconnect_compl(pstn->v5up, NULL, 0)); /* do several thing here */ do_restart(fi); break; case V5x_PSTN_LE_MDU_CTRL_restart_compl_req: /* ignore */ break; case V5x_PSTN_LE_TIMEOUT_T4: /* first or second timeout ? */ if (pstn->timeout_count < 3) { /* start T4 */ start_timer(fi, V5x_PSTN_LE_TIMEOUT_T4, pstn->timeout_count + 1); /* send STATUS ENQUIRY again */ dl_send(fi, v5x_enc_status_enquiry(pstn->v5up)); } else { /* do several thing here */ do_disconnect_with_error(fi); } break; case V5x_PSTN_LE_TIMEOUT_Tr: /* send SIGNAL ACK */ LOGPFSML(fi, LOGL_DEBUG, "Sending recent 'receive' sequence number: S(R)=%d as acknowledge.\n", pstn->S_r); dl_send(fi, v5x_enc_signal_ack(pstn->v5up, pstn->S_r)); break; case V5x_PSTN_LE_TIMEOUT_Tt: /* do several thing here */ LOGPFSML(fi, LOGL_ERROR, "Aborting, because of missing acknowledge of outstanding 'transmit' sequence number: S(A)=%d...S(S)=%d\n", pstn->S_a, pstn->S_s); do_disconnect_with_error(fi); break; default: OSMO_ASSERT(0); } } static void pstn_le5_path_disconnect_req(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct v5x_pstn_proto *pstn = fi->priv; struct msgb *msg = data; const struct tlv_parsed *tp = data; switch (event) { case V5x_PSTN_LE_ESTABLISH: /* ignore */ break; case V5x_PSTN_LE_DISCONNECT: case V5x_PSTN_LE_DISCONNECT_COMPL: /* stop T3 */ stop_timer(fi); /* change state to LE1 */ osmo_fsm_inst_state_chg(fi, V5x_LE_PTSN_S_LE1_NULL, 0, 0); /* send FE-disconnect_complete_indication */ rcv_fe(fi, FE_disconnect_compl_ind, tp); break; case V5x_PSTN_LE_ESTABLISH_ACK: /* ignore */ break; case V5x_PSTN_LE_SIGNAL: /* ignore */ break; case V5x_PSTN_LE_SIGNAL_ACK: /* ignore */ break; case V5x_PSTN_LE_STATUS: /* ignore */ break; case V5x_PSTN_LE_FE_line_signal_req: msgb_free(msg); /* ignore */ break; case V5x_PSTN_LE_FE_protocol_param_req: msgb_free(msg); /* ignore */ break; case V5x_PSTN_LE_FE_disconnect_compl_req: msgb_free(msg); /* ignore */ break; case V5x_PSTN_LE_MDU_CTRL_port_blocked: /* do several thing here */ do_block(fi); break; case V5x_PSTN_LE_MDU_CTRL_port_unblocked: /* ignore */ break; case V5x_PSTN_LE_MDU_CTRL_restart_req: /* send DISCONNECT COMPLETE */ dl_send(fi, v5x_enc_disconnect_compl(pstn->v5up, NULL, 0)); /* do several thing here */ do_restart(fi); break; case V5x_PSTN_LE_MDU_CTRL_restart_compl_req: /* ignore */ break; case V5x_PSTN_LE_TIMEOUT_T3: /* send DISCONNECT again */ if (pstn->tx_msg) dl_send(fi, v5x_enc_disconnect(pstn->v5up, pstn->tx_msg->data, pstn->tx_msg->len)); else dl_send(fi, v5x_enc_disconnect(pstn->v5up, NULL, 0)); /* start T3 */ start_timer(fi, V5x_PSTN_LE_TIMEOUT_T3, pstn->timeout_count + 1); /* third timeout ? */ if (pstn->timeout_count > 3) { /* send MDU-error_indication */ rcv_mdu(fi, MDU_error_ind); } break; default: OSMO_ASSERT(0); } } static void pstn_le6_port_blocked(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct v5x_pstn_proto *pstn = fi->priv; struct msgb *msg = data; switch (event) { case V5x_PSTN_LE_FE_establish_req: msgb_free(msg); /* ignore */ break; case V5x_PSTN_LE_ESTABLISH: case V5x_PSTN_LE_DISCONNECT: case V5x_PSTN_LE_ESTABLISH_ACK: case V5x_PSTN_LE_SIGNAL: case V5x_PSTN_LE_SIGNAL_ACK: /* do several thing here */ do_status_enquiry(fi); break; case V5x_PSTN_LE_DISCONNECT_COMPL: /* ignore */ break; case V5x_PSTN_LE_FE_establish_ack_req: msgb_free(msg); /* ignore */ break; case V5x_PSTN_LE_STATUS: break; case V5x_PSTN_LE_MDU_CTRL_port_blocked: /* ignore */ break; case V5x_PSTN_LE_MDU_CTRL_port_unblocked: /* change state to LE1 */ osmo_fsm_inst_state_chg(fi, V5x_LE_PTSN_S_LE1_NULL, 0, 0); break; case V5x_PSTN_LE_MDU_CTRL_restart_req: /* send MDU-CTRL (restart ack) */ rcv_mdu(fi, MDU_CTRL_port_restart_ack); break; case V5x_PSTN_LE_MDU_CTRL_restart_compl_req: /* ignore */ break; case V5x_PSTN_LE_TIMEOUT_T4: /* first or second timeout ? */ if (pstn->timeout_count < 3) { /* start T4 */ start_timer(fi, V5x_PSTN_LE_TIMEOUT_T4, pstn->timeout_count + 1); /* send STATUS ENQUIRY again */ dl_send(fi, v5x_enc_status_enquiry(pstn->v5up)); } else { /* send MDU-error_indication */ rcv_mdu(fi, MDU_error_ind); } break; default: OSMO_ASSERT(0); } } /* Table 3/G.964 Primitives, messages and timers used in the LE(PSTN) FSM */ static const struct osmo_fsm_state v5x_le_pstn_fsm_states[] = { [V5x_LE_PTSN_S_LE0_OUT_OF_SERVICE] = { .name = "Out of service (LE0)", .in_event_mask = S(V5x_PSTN_LE_FE_establish_req) | S(V5x_PSTN_LE_ESTABLISH) | S(V5x_PSTN_LE_DISCONNECT) | S(V5x_PSTN_LE_DISCONNECT_COMPL) | S(V5x_PSTN_LE_FE_establish_ack_req) | S(V5x_PSTN_LE_ESTABLISH_ACK) | S(V5x_PSTN_LE_SIGNAL) | S(V5x_PSTN_LE_SIGNAL_ACK) | S(V5x_PSTN_LE_STATUS) | S(V5x_PSTN_LE_FE_protocol_param_req) | S(V5x_PSTN_LE_FE_disconnect_req) | S(V5x_PSTN_LE_MDU_CTRL_port_blocked) | S(V5x_PSTN_LE_MDU_CTRL_port_unblocked) | S(V5x_PSTN_LE_MDU_CTRL_restart_req) | S(V5x_PSTN_LE_MDU_CTRL_restart_compl_req) | S(V5x_PSTN_LE_TIMEOUT_T4), .out_state_mask = S(V5x_LE_PTSN_S_LE1_NULL) | S(V5x_LE_PTSN_S_LE6_PORT_BLOCKED), .action = pstn_le0_out_of_service, }, [V5x_LE_PTSN_S_LE1_NULL] = { .name = "Null (LE1)", .in_event_mask = S(V5x_PSTN_LE_FE_establish_req) | S(V5x_PSTN_LE_ESTABLISH) | S(V5x_PSTN_LE_DISCONNECT) | S(V5x_PSTN_LE_ESTABLISH_ACK) | S(V5x_PSTN_LE_SIGNAL) | S(V5x_PSTN_LE_SIGNAL_ACK) | S(V5x_PSTN_LE_STATUS) | S(V5x_PSTN_LE_FE_protocol_param_req) | S(V5x_PSTN_LE_MDU_CTRL_port_blocked) | S(V5x_PSTN_LE_MDU_CTRL_port_unblocked) | S(V5x_PSTN_LE_MDU_CTRL_restart_req) | S(V5x_PSTN_LE_TIMEOUT_T4), .out_state_mask = S(V5x_LE_PTSN_S_LE0_OUT_OF_SERVICE) | S(V5x_LE_PTSN_S_LE2_PATH_INITIATED_LE) | S(V5x_LE_PTSN_S_LE3_PATH_INITIATED_AN) | S(V5x_LE_PTSN_S_LE5_PATH_DICONNECT_REQ) | S(V5x_LE_PTSN_S_LE6_PORT_BLOCKED), .action = pstn_le1_null, }, [V5x_LE_PTSN_S_LE2_PATH_INITIATED_LE] = { .name = "Path initiated by LE (LE2)", .in_event_mask = S(V5x_PSTN_LE_ESTABLISH) | S(V5x_PSTN_LE_DISCONNECT) | S(V5x_PSTN_LE_DISCONNECT_COMPL) | S(V5x_PSTN_LE_FE_establish_ack_req) | S(V5x_PSTN_LE_ESTABLISH_ACK) | S(V5x_PSTN_LE_SIGNAL) | S(V5x_PSTN_LE_SIGNAL_ACK) | S(V5x_PSTN_LE_STATUS) | S(V5x_PSTN_LE_FE_protocol_param_req) | S(V5x_PSTN_LE_FE_disconnect_req) | S(V5x_PSTN_LE_FE_disconnect_compl_req) | S(V5x_PSTN_LE_MDU_CTRL_port_blocked) | S(V5x_PSTN_LE_MDU_CTRL_port_unblocked) | S(V5x_PSTN_LE_MDU_CTRL_restart_req) | S(V5x_PSTN_LE_MDU_CTRL_restart_compl_req) | S(V5x_PSTN_LE_TIMEOUT_T1) | S(V5x_PSTN_LE_TIMEOUT_T4), .out_state_mask = S(V5x_LE_PTSN_S_LE0_OUT_OF_SERVICE) | S(V5x_LE_PTSN_S_LE1_NULL) | S(V5x_LE_PTSN_S_LE4_PATH_ACTIVE) | S(V5x_LE_PTSN_S_LE5_PATH_DICONNECT_REQ) | S(V5x_LE_PTSN_S_LE6_PORT_BLOCKED), .action = pstn_le2_path_initiated_le, }, [V5x_LE_PTSN_S_LE3_PATH_INITIATED_AN] = { .name = "Path initiated by AN (LE3)", .in_event_mask = S(V5x_PSTN_LE_FE_establish_req) | S(V5x_PSTN_LE_ESTABLISH) | S(V5x_PSTN_LE_DISCONNECT) | S(V5x_PSTN_LE_DISCONNECT_COMPL) | S(V5x_PSTN_LE_FE_establish_ack_req) | S(V5x_PSTN_LE_ESTABLISH_ACK) | S(V5x_PSTN_LE_SIGNAL) | S(V5x_PSTN_LE_SIGNAL_ACK) | S(V5x_PSTN_LE_STATUS) | S(V5x_PSTN_LE_FE_protocol_param_req) | S(V5x_PSTN_LE_FE_disconnect_req) | S(V5x_PSTN_LE_FE_disconnect_compl_req) | S(V5x_PSTN_LE_MDU_CTRL_port_blocked) | S(V5x_PSTN_LE_MDU_CTRL_port_unblocked) | S(V5x_PSTN_LE_MDU_CTRL_restart_req) | S(V5x_PSTN_LE_MDU_CTRL_restart_compl_req) | S(V5x_PSTN_LE_TIMEOUT_T4), .out_state_mask = S(V5x_LE_PTSN_S_LE0_OUT_OF_SERVICE) | S(V5x_LE_PTSN_S_LE1_NULL) | S(V5x_LE_PTSN_S_LE2_PATH_INITIATED_LE) | S(V5x_LE_PTSN_S_LE4_PATH_ACTIVE) | S(V5x_LE_PTSN_S_LE5_PATH_DICONNECT_REQ) | S(V5x_LE_PTSN_S_LE6_PORT_BLOCKED), .action = pstn_le3_path_initiated_an, }, [V5x_LE_PTSN_S_LE4_PATH_ACTIVE] = { .name = "Path active (LE4)", .in_event_mask = S(V5x_PSTN_LE_ESTABLISH) | S(V5x_PSTN_LE_DISCONNECT) | S(V5x_PSTN_LE_DISCONNECT_COMPL) | S(V5x_PSTN_LE_ESTABLISH_ACK) | S(V5x_PSTN_LE_SIGNAL) | S(V5x_PSTN_LE_SIGNAL_ACK) | S(V5x_PSTN_LE_STATUS) | S(V5x_PSTN_LE_FE_line_signal_req) | S(V5x_PSTN_LE_FE_protocol_param_req) | S(V5x_PSTN_LE_FE_disconnect_req) | S(V5x_PSTN_LE_FE_disconnect_compl_req) | S(V5x_PSTN_LE_MDU_CTRL_port_blocked) | S(V5x_PSTN_LE_MDU_CTRL_port_unblocked) | S(V5x_PSTN_LE_MDU_CTRL_restart_req) | S(V5x_PSTN_LE_MDU_CTRL_restart_compl_req) | S(V5x_PSTN_LE_TIMEOUT_Tr) | S(V5x_PSTN_LE_TIMEOUT_Tt) | S(V5x_PSTN_LE_TIMEOUT_T4), .out_state_mask = S(V5x_LE_PTSN_S_LE0_OUT_OF_SERVICE) | S(V5x_LE_PTSN_S_LE1_NULL) | S(V5x_LE_PTSN_S_LE5_PATH_DICONNECT_REQ) | S(V5x_LE_PTSN_S_LE6_PORT_BLOCKED), .action = pstn_le4_path_active, }, [V5x_LE_PTSN_S_LE5_PATH_DICONNECT_REQ] = { .name = "Path Disconnect request (LE5)", .in_event_mask = S(V5x_PSTN_LE_ESTABLISH) | S(V5x_PSTN_LE_DISCONNECT) | S(V5x_PSTN_LE_DISCONNECT_COMPL) | S(V5x_PSTN_LE_ESTABLISH_ACK) | S(V5x_PSTN_LE_SIGNAL) | S(V5x_PSTN_LE_SIGNAL_ACK) | S(V5x_PSTN_LE_STATUS) | S(V5x_PSTN_LE_FE_line_signal_req) | S(V5x_PSTN_LE_FE_protocol_param_req) | S(V5x_PSTN_LE_FE_disconnect_compl_req) | S(V5x_PSTN_LE_MDU_CTRL_port_blocked) | S(V5x_PSTN_LE_MDU_CTRL_port_unblocked) | S(V5x_PSTN_LE_MDU_CTRL_restart_req) | S(V5x_PSTN_LE_MDU_CTRL_restart_compl_req) | S(V5x_PSTN_LE_TIMEOUT_T3), .out_state_mask = S(V5x_LE_PTSN_S_LE1_NULL), .action = pstn_le5_path_disconnect_req, }, [V5x_LE_PTSN_S_LE6_PORT_BLOCKED] = { .name = "Port blocked (LE6)", .in_event_mask = S(V5x_PSTN_LE_FE_establish_req) | S(V5x_PSTN_LE_ESTABLISH) | S(V5x_PSTN_LE_DISCONNECT) | S(V5x_PSTN_LE_DISCONNECT_COMPL) | S(V5x_PSTN_LE_FE_establish_ack_req) | S(V5x_PSTN_LE_ESTABLISH_ACK) | S(V5x_PSTN_LE_SIGNAL) | S(V5x_PSTN_LE_SIGNAL_ACK) | S(V5x_PSTN_LE_STATUS) | S(V5x_PSTN_LE_MDU_CTRL_port_blocked) | S(V5x_PSTN_LE_MDU_CTRL_port_unblocked) | S(V5x_PSTN_LE_MDU_CTRL_restart_req) | S(V5x_PSTN_LE_MDU_CTRL_restart_compl_req) | S(V5x_PSTN_LE_TIMEOUT_T4), .out_state_mask = S(V5x_LE_PTSN_S_LE1_NULL), .action = pstn_le6_port_blocked, }, }; struct osmo_fsm v5x_le_pstn_fsm = { .name = "V5X_LE_PSTN", .states = v5x_le_pstn_fsm_states, .num_states = ARRAY_SIZE(v5x_le_pstn_fsm_states), .allstate_event_mask = 0, .allstate_action = NULL, .cleanup = NULL, .timer_cb = v5x_le_pstn_fsm_timer_cb, .log_subsys = DV5PSTN, .event_names = v5x_ctrl_le_port_fsm_event_names, }; struct v5x_pstn_proto *v5x_le_pstn_create(struct v5x_user_port *v5up, uint16_t nr) { struct v5x_pstn_proto *pstn; OSMO_ASSERT(v5up); pstn = talloc_zero(v5up, struct v5x_pstn_proto); if (!pstn) return NULL; pstn->v5up = v5up; pstn->fi = osmo_fsm_inst_alloc(&v5x_le_pstn_fsm, pstn, pstn, LOGL_DEBUG, NULL); if (!pstn->fi) NULL; osmo_fsm_inst_update_id_f(pstn->fi, "%d", nr); pstn->timer_Tr.data = pstn; pstn->timer_Tr.cb = timeout_Tr_cb; pstn->timer_Tt.data = pstn; pstn->timer_Tt.cb = timeout_Tt_cb; return pstn; } void v5x_le_pstn_destroy(struct v5x_pstn_proto *pstn) { /* free pending message */ if (pstn->tx_msg) msgb_free(pstn->tx_msg); if (pstn->fi) { stop_timer_Tr(pstn); stop_timer_Tt(pstn); osmo_fsm_inst_free(pstn->fi); } talloc_free(pstn); } const char *v5x_le_pstn_state_name(struct v5x_pstn_proto *pstn) { return v5x_le_pstn_fsm_states[pstn->fi->state].name; } void v5x_le_pstn_init(void) { int rc; rc = osmo_fsm_register(&v5x_le_pstn_fsm); OSMO_ASSERT(!rc); LOGP(DV5PSTN, LOGL_NOTICE, "Using V5x PSTN protocol\n"); } /*********************************************************************** * V5 Message receiving / decoding ***********************************************************************/ /* receive message from lower (DL) layer */ int v5x_le_pstn_dl_rcv(struct v5x_user_port *v5up, uint8_t msg_type, const struct tlv_parsed *tp) { enum v5x_le_pstn_fsm_event event; switch (msg_type) { case V5X_CTRL_MSGT_ESTABLISH: event = V5x_PSTN_LE_ESTABLISH; break; case V5X_CTRL_MSGT_ESTABLISH_ACK: event = V5x_PSTN_LE_ESTABLISH_ACK; break; case V5X_CTRL_MSGT_SIGNAL: event = V5x_PSTN_LE_SIGNAL; break; case V5X_CTRL_MSGT_SIGNAL_ACK: event = V5x_PSTN_LE_SIGNAL_ACK; break; case V5X_CTRL_MSGT_DISCONNECT: event = V5x_PSTN_LE_DISCONNECT; break; case V5X_CTRL_MSGT_DISCONNECT_COMPLETE: event = V5x_PSTN_LE_DISCONNECT_COMPL; break; case V5X_CTRL_MSGT_STATUS: event = V5x_PSTN_LE_STATUS; break; default: LOGPFSML(v5up->port_fi, LOGL_NOTICE, "Invalid PSTN protocol message %d receied from AN.\n", msg_type); return -EINVAL; } osmo_fsm_inst_dispatch(v5up->pstn.proto->fi, event, (void *)tp); return 0; }