diff --git a/src/v52_le_pp_fsm.c b/src/v52_le_pp_fsm.c new file mode 100644 index 0000000..69648be --- /dev/null +++ b/src/v52_le_pp_fsm.c @@ -0,0 +1,845 @@ +/* ITu-T G.965 Section 18 V5.2-interface Protection protocol FSM - LE side */ + +/* (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 + +#include "v5x_internal.h" +#include "v5x_protocol.h" +#include "v5x_le_management.h" +#include "v52_le_pp_fsm.h" +#include "logging.h" + +/* Table 64/G.965 Timers in the LE */ +#define TIMEOUT_TSO1 1500 +#define TIMEOUT_TSO2 1500 +#define TIMEOUT_TSO4 20000 +#define TIMEOUT_TSO5 10000 + +#define S(x) (1 << (x)) + +/***********************************************************************/ +/* state names, event names, primitives, ... */ +/***********************************************************************/ + +/* 18.3.1.2 */ +enum v52_le_pp_fsm_state { + V52_PPFSM_S_SOLE0_NULL, + V52_PPFSM_S_SOLE1_SWITCH_OVER_INIT_BY_LE, + V52_PPFSM_S_SOLE2_SWITCH_OVER_REQ_BY_AN, +}; + +enum v52_le_pp_fsm_event { + V52_PPFSM_E_MDU_Protection_switch_over_com, + V52_PPFSM_E_MDU_Protection_OS_switch_over_com, + V52_PPFSM_E_MDU_Protection_switch_over_reject, + V52_PPFSM_E_MDU_Protection_reset_SN_req, + V52_PPFSM_E_VP_S_VP_R_misalignment_detected, + V52_PPFSM_E_SWITCH_OVER_REQ, + V52_PPFSM_E_SWITCH_OVER_ACK, + V52_PPFSM_E_SWITCH_OVER_REJECT, + V52_PPFSM_E_PROTOCOL_ERROR, + V52_PPFSM_E_RESET_SN_COM, + V52_PPFSM_E_RESET_SN_ACK, + V52_PPFSM_E_TSO1, + V52_PPFSM_E_TSO2, + V52_PPFSM_E_TSO4, + V52_PPFSM_E_TSO5, +}; + +static const struct value_string v52_le_pp_fsm_event_names[] = { + { V52_PPFSM_E_MDU_Protection_switch_over_com, "MDU-Protection (switch_over com)" }, + { V52_PPFSM_E_MDU_Protection_OS_switch_over_com, "MDU-Protection (OS_switch-over com)" }, + { V52_PPFSM_E_MDU_Protection_switch_over_reject, "MDU-Protection (switch-over reject)" }, + { V52_PPFSM_E_MDU_Protection_reset_SN_req, "MDU-Protection (reset SN req)" }, + { V52_PPFSM_E_VP_S_VP_R_misalignment_detected, "VP(S) VP(R) misalignment detected" }, + { V52_PPFSM_E_SWITCH_OVER_REQ, "SWITCH-OVER REQ" }, + { V52_PPFSM_E_SWITCH_OVER_ACK, "SWITCH-OVER ACK" }, + { V52_PPFSM_E_SWITCH_OVER_REJECT, "SWITCH-OVER REJECT" }, + { V52_PPFSM_E_PROTOCOL_ERROR, "PROTOCOL ERROR" }, + { V52_PPFSM_E_RESET_SN_COM, "RESET SN COM" }, + { V52_PPFSM_E_RESET_SN_ACK, "RESET SN ACK" }, + { V52_PPFSM_E_TSO1, "expiry TSO1" }, + { V52_PPFSM_E_TSO2, "expiry TSO2" }, + { V52_PPFSM_E_TSO4, "expiry TSO4" }, + { V52_PPFSM_E_TSO5, "expiry TSO5" }, + { 0, NULL } +}; + +/***********************************************************************/ +/* Messages to other layers */ +/***********************************************************************/ + +static inline void v52_pp_pcc_dec(uint16_t ie, uint8_t *link_id, uint8_t *ts); + +/* send message to upper (management) layer */ +static void mdu_rcv(struct osmo_fsm_inst *fi, enum v5x_mgmt_prim prim, const struct tlv_parsed *tp) +{ + struct v52_pp_proto *pp = fi->priv; + struct v5x_interface *v5if = pp->interface; + const uint8_t *ie, *cause = NULL; + uint8_t ie_len, link_id = 0, ts = 0, cause_len = 0; + + if (tp) { + if (TLVP_PRESENT(tp, V52_CTRL_IEI_PP_PHYSICAL_C_CHAN_ID)) { + ie = TLVP_VAL(tp, V52_CTRL_IEI_PP_PHYSICAL_C_CHAN_ID); + ie_len = TLVP_LEN(tp, V52_CTRL_IEI_PP_PHYSICAL_C_CHAN_ID); + if (ie_len == 2) + v52_pp_pcc_dec(ntohs(*((uint16_t *)ie)), &link_id, &ts); + + } + if (TLVP_PRESENT(tp, V52_CTRL_IEI_PP_REJECTION_CAUSE)) { + cause = TLVP_VAL(tp, V52_CTRL_IEI_PP_REJECTION_CAUSE); + cause_len = TLVP_LEN(tp, V52_CTRL_IEI_PP_REJECTION_CAUSE); + } + if (TLVP_PRESENT(tp, V52_CTRL_IEI_PP_PROTOCOL_ERROR_CAUSE)) { + cause = TLVP_VAL(tp, V52_CTRL_IEI_PP_PROTOCOL_ERROR_CAUSE); + cause_len = TLVP_LEN(tp, V52_CTRL_IEI_PP_PROTOCOL_ERROR_CAUSE); + } + } + + v52_le_pp_mdu_rcv(v5if, prim, link_id, ts, cause, cause_len); +} + +/* send message to lower (DL) layer(s) */ +static void dl_send(struct osmo_fsm_inst *fi, struct msgb *msg) +{ + struct v52_pp_proto *pp = fi->priv; + + v5x_dl_snd(pp->interface, V52_DLADDR_PROTECTION, msg); +} + +/*********************************************************************** + * V5 Message encoding / sending + ***********************************************************************/ + +static inline uint16_t v52_pp_pcc_enc(uint8_t link_id, uint8_t ts) +{ + return (link_id << 8) | ts; +} + +static inline void v52_pp_pcc_dec(uint16_t ie, uint8_t *link_id, uint8_t *ts) +{ + *link_id = ie >> 8; + *ts = ie & 0x1f; +} + +/* G.965 Section 18.4.2/18.4.3 / Table 53/54 */ +static struct msgb *v52_enc_switch_over_com(struct v52_pp_proto *pp, uint16_t cc_id, uint8_t link_id, uint8_t ts, + uint8_t msg_type) +{ + uint8_t seq_ie = pp->vp_s | 0x80; + uint16_t v5_cc_ie; + 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(cc_id); + l3h->msg_type = msg_type; + + /* Sequence Number */ + msgb_tlv_put(msg, V52_CTRL_IEI_PP_SEQUENCE_NR, 1, &seq_ie); + pp->vp_s = (pp->vp_s + 1) & 0x7f; + /* Physical C-channel identification */ + v5_cc_ie = htons(v52_pp_pcc_enc(link_id, ts)); + msgb_tlv_put(msg, V52_CTRL_IEI_PP_PHYSICAL_C_CHAN_ID, 2, (uint8_t *)&v5_cc_ie); + + return msg; +} + +/* G.965 Section 18.4.5 / Table 56 */ +static struct msgb *v52_enc_switch_over_rej(struct v52_pp_proto *pp, uint16_t cc_id, uint8_t link_id, uint8_t ts, + uint8_t cause) +{ + uint8_t seq_ie = pp->vp_s | 0x80; + uint16_t v5_cc_ie; + uint8_t cause_ie = cause | 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(cc_id); + l3h->msg_type = V52_CTRL_MSGT_PP_SWITCH_OVER_REJECT; + + /* Sequence Number */ + msgb_tlv_put(msg, V52_CTRL_IEI_PP_SEQUENCE_NR, 1, &seq_ie); + pp->vp_s = (pp->vp_s + 1) & 0x7f; + /* Physical C-channel identification */ + v5_cc_ie = htons(v52_pp_pcc_enc(link_id, ts)); + msgb_tlv_put(msg, V52_CTRL_IEI_PP_PHYSICAL_C_CHAN_ID, 2, (uint8_t *)&v5_cc_ie); + /* Rejection Cause */ + msgb_tlv_put(msg, V52_CTRL_IEI_PP_REJECTION_CAUSE, 1, &cause_ie); + + return msg; +} + +/* G.965 Section 18.4.6 / Table 57 */ +static struct msgb *v52_enc_protocol_error(struct v52_pp_proto *pp, uint16_t cc_id, uint8_t cause, uint8_t *diagnostic, + int diagnostic_len) +{ + uint8_t seq_ie = pp->vp_s | 0x80; + uint8_t cause_ie[3] = { cause | 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(cc_id); + l3h->msg_type = V52_CTRL_MSGT_PP_PROTOCOL_ERROR; + + /* Sequence Number */ + msgb_tlv_put(msg, V52_CTRL_IEI_PP_SEQUENCE_NR, 1, &seq_ie); + pp->vp_s = (pp->vp_s + 1) & 0x7f; + /* Protocol Error Cause */ + if (diagnostic && diagnostic_len) + memcpy(cause_ie + 1, diagnostic, diagnostic_len); + msgb_tlv_put(msg, V52_CTRL_IEI_PP_PROTOCOL_ERROR_CAUSE, 1 + diagnostic_len, (uint8_t *)&cause_ie); + + return msg; +} + +/* G.965 Section 18.4.7/18.4.8 / Table 58/59 */ +static struct msgb *v52_enc_reset_sn_xxx(uint16_t cc_id, uint8_t msg_type) +{ + 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(cc_id); + l3h->msg_type = msg_type; + + return msg; +} + +/***********************************************************************/ +/* Protection Protocol state FSM */ +/***********************************************************************/ + +static void stop_timer(struct osmo_fsm_inst *fi) +{ + LOGP(DV5PP, LOGL_DEBUG, "Stop all timers\n"); + osmo_timer_del(&fi->timer); +} + +static void start_timer(struct osmo_fsm_inst *fi, enum v52_le_pp_fsm_event event, int count, struct v52_pp_mgmt_info *info) +{ + struct v52_pp_proto *pp = fi->priv; + int timeout = 0; + const char *timer_name = NULL; + + switch (event) { + case V52_PPFSM_E_TSO1: + timer_name = "TSO1"; + timeout = TIMEOUT_TSO1; + fi->T = 1; + break; + case V52_PPFSM_E_TSO2: + timer_name = "TSO2"; + timeout = TIMEOUT_TSO2; + fi->T = 2; + break; + case V52_PPFSM_E_TSO4: + timer_name = "TSO4"; + timeout = TIMEOUT_TSO4; + fi->T = 3; + break; + case V52_PPFSM_E_TSO5: + timer_name = "TSO5"; + timeout = TIMEOUT_TSO5; + fi->T = 4; + break; + default: + OSMO_ASSERT(0); + } + + if (info) + memcpy(&pp->info, info, sizeof(*info)); + + LOGP(DV5PP, LOGL_DEBUG, "Start timer %s (count = %d)\n", timer_name, count); + pp->timeout_event = event; + pp->timeout_count = count; + osmo_timer_schedule(&fi->timer, timeout / 1000, timeout % 1000); +} + +static bool timer_pending(struct osmo_fsm_inst *fi, enum v52_le_pp_fsm_event event) +{ + struct v52_pp_proto *pp = fi->priv; + + if (pp->timeout_event != (int)event) + return false; + + return osmo_timer_pending(&fi->timer); +} + +static int v52_le_pp_fsm_timer_cb(struct osmo_fsm_inst *fi) +{ + struct v52_pp_proto *pp = fi->priv; + + osmo_fsm_inst_dispatch(fi, pp->timeout_event, &pp->info); + + return 0; +} + + + +static void pp_sole0_null(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct v52_pp_proto *pp = fi->priv; + struct v52_pp_mgmt_info *info = data; + + switch (event) { + case V52_PPFSM_E_MDU_Protection_switch_over_com: + if (!timer_pending(fi, V52_PPFSM_E_TSO4)) { + /* change state to SOLE1 */ + osmo_fsm_inst_state_chg_ms(fi, V52_PPFSM_S_SOLE1_SWITCH_OVER_INIT_BY_LE, 0, 0); + /* start TSO1 */ + start_timer(fi, V52_PPFSM_E_TSO1, 1, info); + /* send SWITCH-OVER COM */ + dl_send(fi, v52_enc_switch_over_com(pp, pp->interface->protection.cc_id, info->link_id, info->ts, V52_CTRL_MSGT_PP_SWITCH_OVER_COM)); + } else { + /* send MDU-Prot. (reset SN error ind) */ + mdu_rcv(fi, MDU_Protection_reset_SN_error_ind, NULL); + } + break; + case V52_PPFSM_E_MDU_Protection_OS_switch_over_com: + if (!timer_pending(fi, V52_PPFSM_E_TSO4)) { + /* change state to SOLE1 */ + osmo_fsm_inst_state_chg_ms(fi, V52_PPFSM_S_SOLE1_SWITCH_OVER_INIT_BY_LE, 0, 0); + /* start TSO2 */ + start_timer(fi, V52_PPFSM_E_TSO2, 1, info); + /* send OS SWITCH-OVER COM */ + dl_send(fi, v52_enc_switch_over_com(pp, pp->interface->protection.cc_id, info->link_id, info->ts, V52_CTRL_MSGT_PP_OS_SWITCH_OVER_COM)); + } else { + /* send MDU-Prot. (reset SN error ind) */ + mdu_rcv(fi, MDU_Protection_reset_SN_error_ind, NULL); + } + break; + case V52_PPFSM_E_SWITCH_OVER_ACK: + if (!timer_pending(fi, V52_PPFSM_E_TSO4)) { + /* send MDU-Prot. (switch-over ack) */ + mdu_rcv(fi, MDU_Protection_switch_over_ack, data); + } else { + /* ignore */ + } + break; + case V52_PPFSM_E_SWITCH_OVER_REQ: + if (!timer_pending(fi, V52_PPFSM_E_TSO4)) { + /* change state to SOLE2 */ + osmo_fsm_inst_state_chg(fi, V52_PPFSM_S_SOLE2_SWITCH_OVER_REQ_BY_AN, 0, 0); + /* send MDU-Prot. (switch-over req) */ + mdu_rcv(fi, MDU_Protection_switch_over_req, data); + } else { + /* ignore */ + } + break; + case V52_PPFSM_E_SWITCH_OVER_REJECT: + if (!timer_pending(fi, V52_PPFSM_E_TSO4)) { + /* send MDU-Prot. (switch-over reject ind) */ + mdu_rcv(fi, MDU_Protection_switch_over_reject_ind, data); + } else { + /* ignore */ + } + break; + case V52_PPFSM_E_VP_S_VP_R_misalignment_detected: + case V52_PPFSM_E_MDU_Protection_reset_SN_req: + /* start TSO4 */ + start_timer(fi, V52_PPFSM_E_TSO4, 1, NULL); + /* send RESET SN COM */ + dl_send(fi, v52_enc_reset_sn_xxx(pp->interface->protection.cc_id, V52_CTRL_MSGT_PP_RESET_SN_COM)); + /* set VP(S) = VP(R) = 0 */ + pp->vp_s = pp->vp_r = 0; + /* send MDU-Prot. (reset SN com) */ + mdu_rcv(fi, MDU_Protection_reset_SN_com, NULL); + break; + case V52_PPFSM_E_RESET_SN_COM: + if (!timer_pending(fi, V52_PPFSM_E_TSO5)) { + /* start TSO5 */ + start_timer(fi, V52_PPFSM_E_TSO5, 1, NULL); + /* set VP(S) = VP(R) = 0 */ + pp->vp_s = pp->vp_r = 0; + /* send RESET SN ACK */ + dl_send(fi, v52_enc_reset_sn_xxx(pp->interface->protection.cc_id, V52_CTRL_MSGT_PP_RESET_SN_ACK)); + /* send MDU-Prot. (reset SN ind) */ + mdu_rcv(fi, MDU_Protection_reset_SN_ind, NULL); + } else { + /* ignore */ + } + break; + case V52_PPFSM_E_RESET_SN_ACK: + if (!timer_pending(fi, V52_PPFSM_E_TSO4)) { + /* ignore */ + } else { + /* stop TSO4 */ + stop_timer(fi); + /* send MDU-Prot. (reset SN ack) */ + mdu_rcv(fi, MDU_Protection_reset_SN_ack, NULL); + } + break; + case V52_PPFSM_E_TSO4: + if (pp->timeout_count < 2) { + /* restart TSO4 */ + start_timer(fi, V52_PPFSM_E_TSO4, 2, NULL); + /* send RESET SN COM */ + dl_send(fi, v52_enc_reset_sn_xxx(pp->interface->protection.cc_id, V52_CTRL_MSGT_PP_RESET_SN_COM)); + /* set VP(S) = VP(R) = 0 */ + pp->vp_s = pp->vp_r = 0; + /* send MDU-Prot. (reset SN com) */ + mdu_rcv(fi, MDU_Protection_reset_SN_com, NULL); + } else { + /* send MDU-Prot. (reset SN error ind) */ + mdu_rcv(fi, MDU_Protection_reset_SN_error_ind, NULL); + } + break; + case V52_PPFSM_E_TSO5: + /* ignore */ + break; + case V52_PPFSM_E_PROTOCOL_ERROR: + if (!timer_pending(fi, V52_PPFSM_E_TSO4)) { + /* send MDU-Prot. (Protocol error ind) */ + mdu_rcv(fi, MDU_Protection_protocol_error_ind, data); + } else { + /* ignore */ + } + break; + default: + OSMO_ASSERT(0); + } +} + +static void pp_sole1_switch_over_init_by_le(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct v52_pp_proto *pp = fi->priv; + struct v52_pp_mgmt_info *info = data; + + switch (event) { + case V52_PPFSM_E_SWITCH_OVER_ACK: + /* stop timer */ + stop_timer(fi); + /* change state to SOLE0, stop timer */ + osmo_fsm_inst_state_chg(fi, V52_PPFSM_S_SOLE0_NULL, 0, 0); + /* send MDU-Prot. (switch-over ack) */ + mdu_rcv(fi, MDU_Protection_switch_over_ack, data); + break; + case V52_PPFSM_E_SWITCH_OVER_REQ: + /* ignore */ + break; + case V52_PPFSM_E_SWITCH_OVER_REJECT: + /* stop timer */ + stop_timer(fi); + /* change state to SOLE0 */ + osmo_fsm_inst_state_chg(fi, V52_PPFSM_S_SOLE0_NULL, 0, 0); + /* send MDU-Prot. (switch-over reject ind) */ + mdu_rcv(fi, MDU_Protection_switch_over_reject_ind, data); + break; + case V52_PPFSM_E_TSO1: + /* first timeout ? */ + if (pp->timeout_count < 2) { + /* restart TSO1 */ + start_timer(fi, V52_PPFSM_E_TSO1, 2, info); + /* send SWITCH-OVER COM */ + dl_send(fi, v52_enc_switch_over_com(pp, pp->interface->protection.cc_id, info->link_id, info->ts, V52_CTRL_MSGT_PP_SWITCH_OVER_COM)); + } else { + /* change state to SOLE0 */ + osmo_fsm_inst_state_chg(fi, V52_PPFSM_S_SOLE0_NULL, 0, 0); + /* send MDU-Prot. (switch-over error ind) */ + mdu_rcv(fi, MDU_Protection_switch_over_error_ind, NULL); + } + break; + case V52_PPFSM_E_TSO2: + if (pp->timeout_count < 2) { + /* restart TSO2 */ + start_timer(fi, V52_PPFSM_E_TSO2, 2, info); + /* send OS SWITCH-OVER COM */ + dl_send(fi, v52_enc_switch_over_com(pp, pp->interface->protection.cc_id, info->link_id, info->ts, V52_CTRL_MSGT_PP_OS_SWITCH_OVER_COM)); + } else { + /* change state to SOLE0 */ + osmo_fsm_inst_state_chg(fi, V52_PPFSM_S_SOLE0_NULL, 0, 0); + /* send MDU-Prot. (switch-over error ind) */ + mdu_rcv(fi, MDU_Protection_switch_over_error_ind, NULL); + } + break; + case V52_PPFSM_E_VP_S_VP_R_misalignment_detected: + case V52_PPFSM_E_MDU_Protection_reset_SN_req: + /* stop timer */ + stop_timer(fi); + /* change state to SOLE0, stop timer */ + osmo_fsm_inst_state_chg(fi, V52_PPFSM_S_SOLE0_NULL, 0, 4); + /* start TSO4 */ + start_timer(fi, V52_PPFSM_E_TSO4, 1, NULL); + /* send RESET SN COM */ + dl_send(fi, v52_enc_reset_sn_xxx(pp->interface->protection.cc_id, V52_CTRL_MSGT_PP_RESET_SN_COM)); + /* set VP(S) = VP(R) = 0 */ + pp->vp_s = pp->vp_r = 0; + /* send MDU-Prot. (reset SN com) */ + mdu_rcv(fi, MDU_Protection_reset_SN_com, NULL); + break; + case V52_PPFSM_E_RESET_SN_COM: + /* first timeout ? */ + if (pp->timeout_count < 2) { + /* stop timer */ + stop_timer(fi); + /* change state to SOLE0 */ + osmo_fsm_inst_state_chg(fi, V52_PPFSM_S_SOLE0_NULL, 0, 0); + /* start TSO5 */ + start_timer(fi, V52_PPFSM_E_TSO5, 1, NULL); + /* set VP(S) = VP(R) = 0 */ + pp->vp_s = pp->vp_r = 0; + /* send RESET SN ACK */ + dl_send(fi, v52_enc_reset_sn_xxx(pp->interface->protection.cc_id, V52_CTRL_MSGT_PP_RESET_SN_ACK)); + /* send MDU-Prot. (reset SN ind) */ + mdu_rcv(fi, MDU_Protection_reset_SN_ind, NULL); + } else { + /* ignore */ + } + break; + case V52_PPFSM_E_RESET_SN_ACK: + /* ignore */ + break; + case V52_PPFSM_E_TSO5: + /* ignore */ + break; + case V52_PPFSM_E_PROTOCOL_ERROR: + /* send MDU-Prot. (Protocol error ind) */ + mdu_rcv(fi, MDU_Protection_protocol_error_ind, data); + break; + default: + OSMO_ASSERT(0); + } +} + +static void pp_sole2_switch_over_req_by_an(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct v52_pp_proto *pp = fi->priv; + struct v52_pp_mgmt_info *info = data; + + switch (event) { + case V52_PPFSM_E_MDU_Protection_switch_over_com: + /* change state to SOLE1 */ + osmo_fsm_inst_state_chg_ms(fi, V52_PPFSM_S_SOLE1_SWITCH_OVER_INIT_BY_LE, 0, 0); + /* start TSO1 */ + start_timer(fi, V52_PPFSM_E_TSO1, 1, info); + /* send SWITCH-OVER COM */ + dl_send(fi, v52_enc_switch_over_com(pp, pp->interface->protection.cc_id, info->link_id, info->ts, V52_CTRL_MSGT_PP_SWITCH_OVER_COM)); + break; + case V52_PPFSM_E_MDU_Protection_OS_switch_over_com: + /* change state to SOLE1 */ + osmo_fsm_inst_state_chg_ms(fi, V52_PPFSM_S_SOLE1_SWITCH_OVER_INIT_BY_LE, 0, 0); + /* start TSO2 */ + start_timer(fi, V52_PPFSM_E_TSO2, 1, info); + /* send OS SWITCH-OVER COM */ + dl_send(fi, v52_enc_switch_over_com(pp, pp->interface->protection.cc_id, info->link_id, info->ts, V52_CTRL_MSGT_PP_OS_SWITCH_OVER_COM)); + break; + case V52_PPFSM_E_MDU_Protection_switch_over_reject: + /* change state to SOLE0 */ + osmo_fsm_inst_state_chg(fi, V52_PPFSM_S_SOLE0_NULL, 0, 0); + /* send OS SWITCH-OVER REJ */ + dl_send(fi, v52_enc_switch_over_rej(pp, pp->interface->protection.cc_id, info->link_id, info->ts, info->cause)); + break; + case V52_PPFSM_E_VP_S_VP_R_misalignment_detected: + case V52_PPFSM_E_MDU_Protection_reset_SN_req: + /* change state to SOLE0 */ + osmo_fsm_inst_state_chg(fi, V52_PPFSM_S_SOLE0_NULL, TIMEOUT_TSO4, 0); + /* start TSO4 */ + start_timer(fi, V52_PPFSM_E_TSO4, 1, NULL); + /* send RESET SN COM */ + dl_send(fi, v52_enc_reset_sn_xxx(pp->interface->protection.cc_id, V52_CTRL_MSGT_PP_RESET_SN_COM)); + /* set VP(S) = VP(R) = 0 */ + pp->vp_s = pp->vp_r = 0; + /* send MDU-Prot. (reset SN com) */ + mdu_rcv(fi, MDU_Protection_reset_SN_com, NULL); + break; + case V52_PPFSM_E_RESET_SN_COM: + /* first timeout ? */ + if (pp->timeout_count < 2) { + /* change state to SOLE0, start TSO5 */ + osmo_fsm_inst_state_chg(fi, V52_PPFSM_S_SOLE0_NULL, 0, 0); + /* start TSO5 */ + start_timer(fi, V52_PPFSM_E_TSO5, 1, NULL); + /* set VP(S) = VP(R) = 0 */ + pp->vp_s = pp->vp_r = 0; + /* send RESET SN ACK */ + dl_send(fi, v52_enc_reset_sn_xxx(pp->interface->protection.cc_id, V52_CTRL_MSGT_PP_RESET_SN_ACK)); + /* send MDU-Prot. (reset SN ind) */ + mdu_rcv(fi, MDU_Protection_reset_SN_ind, NULL); + } else { + /* ignore */ + } + break; + case V52_PPFSM_E_RESET_SN_ACK: + /* ignore */ + break; + case V52_PPFSM_E_TSO5: + /* ignore */ + break; + case V52_PPFSM_E_PROTOCOL_ERROR: + /* send MDU-Prot. (Protocol error ind) */ + mdu_rcv(fi, MDU_Protection_protocol_error_ind, data); + break; + default: + OSMO_ASSERT(0); + } +} + +/* Table 66/G.965 LE protection protocol FSM */ +static const struct osmo_fsm_state v52_le_pp_fsm_states[] = { + [V52_PPFSM_S_SOLE0_NULL] = { + .name = "NULL (SOLE0)", + .in_event_mask = S(V52_PPFSM_E_MDU_Protection_switch_over_com) | + S(V52_PPFSM_E_MDU_Protection_OS_switch_over_com) | + S(V52_PPFSM_E_SWITCH_OVER_ACK) | + S(V52_PPFSM_E_SWITCH_OVER_REQ) | + S(V52_PPFSM_E_SWITCH_OVER_REJECT) | + S(V52_PPFSM_E_VP_S_VP_R_misalignment_detected) | + S(V52_PPFSM_E_MDU_Protection_reset_SN_req) | + S(V52_PPFSM_E_RESET_SN_COM) | + S(V52_PPFSM_E_RESET_SN_ACK) | + S(V52_PPFSM_E_TSO4) | + S(V52_PPFSM_E_TSO5) | + S(V52_PPFSM_E_PROTOCOL_ERROR), + .out_state_mask = S(V52_PPFSM_S_SOLE1_SWITCH_OVER_INIT_BY_LE) | + S(V52_PPFSM_S_SOLE2_SWITCH_OVER_REQ_BY_AN), + .action = pp_sole0_null, + }, + [V52_PPFSM_S_SOLE1_SWITCH_OVER_INIT_BY_LE] = { + .name = "Switch over init by LE (SOLE1)", + .in_event_mask = S(V52_PPFSM_E_SWITCH_OVER_ACK) | + S(V52_PPFSM_E_SWITCH_OVER_REQ) | + S(V52_PPFSM_E_SWITCH_OVER_REJECT) | + S(V52_PPFSM_E_TSO1) | + S(V52_PPFSM_E_TSO2) | + S(V52_PPFSM_E_VP_S_VP_R_misalignment_detected) | + S(V52_PPFSM_E_MDU_Protection_reset_SN_req) | + S(V52_PPFSM_E_RESET_SN_COM) | + S(V52_PPFSM_E_RESET_SN_ACK) | + S(V52_PPFSM_E_TSO5) | + S(V52_PPFSM_E_PROTOCOL_ERROR), + .out_state_mask = S(V52_PPFSM_S_SOLE0_NULL), + .action = pp_sole1_switch_over_init_by_le, + }, + [V52_PPFSM_S_SOLE2_SWITCH_OVER_REQ_BY_AN] = { + .name = "Switch over req by AN (SOLE2)", + .in_event_mask = S(V52_PPFSM_E_MDU_Protection_switch_over_com) | + S(V52_PPFSM_E_MDU_Protection_OS_switch_over_com) | + S(V52_PPFSM_E_MDU_Protection_switch_over_reject) | + S(V52_PPFSM_E_VP_S_VP_R_misalignment_detected) | + S(V52_PPFSM_E_MDU_Protection_reset_SN_req) | + S(V52_PPFSM_E_RESET_SN_COM) | + S(V52_PPFSM_E_RESET_SN_ACK) | + S(V52_PPFSM_E_TSO5) | + S(V52_PPFSM_E_PROTOCOL_ERROR), + .out_state_mask = S(V52_PPFSM_S_SOLE0_NULL) | + S(V52_PPFSM_S_SOLE1_SWITCH_OVER_INIT_BY_LE), + .action = pp_sole2_switch_over_req_by_an, + }, +}; + +struct osmo_fsm v52_le_pp_fsm = { + .name = "V52_LE_PP", + .states = v52_le_pp_fsm_states, + .num_states = ARRAY_SIZE(v52_le_pp_fsm_states), + .allstate_event_mask = 0, + .allstate_action = NULL, + .cleanup = NULL, + .timer_cb = v52_le_pp_fsm_timer_cb, + .log_subsys = DV5PP, + .event_names = v52_le_pp_fsm_event_names, +}; + +struct v52_pp_proto *v52_le_pp_create(struct v5x_interface *v5if) +{ + struct v52_pp_proto *pp; + + OSMO_ASSERT(v5if); + + pp = talloc_zero(v5if, struct v52_pp_proto); + if (!pp) + return NULL; + pp->interface = v5if; + + pp->fi = osmo_fsm_inst_alloc(&v52_le_pp_fsm, pp, pp, LOGL_DEBUG, NULL); + if (!pp->fi) + goto error; + + return pp; + +error: + v52_le_pp_destroy(pp); + return NULL; +} + +void v52_le_pp_destroy(struct v52_pp_proto *pp) +{ + OSMO_ASSERT(pp); + + if (pp->fi) { + osmo_fsm_inst_free(pp->fi); + } + + talloc_free(pp); +} + +void v52_le_pp_init(void) +{ + int rc; + + rc = osmo_fsm_register(&v52_le_pp_fsm); + OSMO_ASSERT(!rc); + LOGP(DV5PP, LOGL_NOTICE, "Using V52 Protection protocol\n"); +} + +/*********************************************************************** + * V5 Message receiving / decoding + ***********************************************************************/ + +/* receive message from lower (DL) layer */ +int v52_le_pp_dl_rcv(struct v5x_link *v5l, uint16_t cc_id, uint8_t msg_type, const struct tlv_parsed *tp) +{ + struct v52_pp_proto *pp = v5l->interface->protection.pp; + enum v52_le_pp_fsm_event event; + int check_sn = 0; + + if (!pp) { + LOGP(DV5PP, LOGL_NOTICE, "Received message from DL, but there is no protection on this interface.\n"); + return -EIO; + } + + switch (msg_type) { + case V52_CTRL_MSGT_PP_SWITCH_OVER_REQ: + event = V52_PPFSM_E_SWITCH_OVER_REQ; + check_sn = 1; + break; + case V52_CTRL_MSGT_PP_SWITCH_OVER_ACK: + event = V52_PPFSM_E_SWITCH_OVER_ACK; + check_sn = 1; + break; + case V52_CTRL_MSGT_PP_SWITCH_OVER_REJECT: + event = V52_PPFSM_E_SWITCH_OVER_REJECT; + check_sn = 1; + break; + case V52_CTRL_MSGT_PP_PROTOCOL_ERROR: + event = V52_PPFSM_E_PROTOCOL_ERROR; + check_sn = 1; + break; + case V52_CTRL_MSGT_PP_RESET_SN_COM: + event = V52_PPFSM_E_RESET_SN_COM; + break; + case V52_CTRL_MSGT_PP_RESET_SN_ACK: + event = V52_PPFSM_E_RESET_SN_ACK; + break; + default: + LOGP(DV5PP, LOGL_NOTICE, "Invalid Protection protocol message %d receied from AN.\n", msg_type); + return -EINVAL; + } + + if (cc_id != pp->interface->protection.cc_id) { + LOGP(DV5PP, LOGL_NOTICE, "Protection protocol of AN uses CC-ID %d, but we use CC-ID %d. Please check provisioning.\n", cc_id, pp->interface->protection.cc_id); + dl_send(pp->fi, v52_enc_protocol_error(pp, pp->interface->protection.cc_id, V5X_CAUSE_T_REF_NR_CODING_ERROR, NULL, 0)); + return -EINVAL; + } + + /* check sequence number (see 18.6.2.2 G.965) */ + /* NOTE: If TSO4 is running, there is an ongoing sequence reset, so no check is performed and all received + * message with sequence number are ignored by state machine. */ + if (check_sn && !timer_pending(pp->fi, V52_PPFSM_E_TSO4)) { + uint8_t seq_nr = *TLVP_VAL(tp, V52_CTRL_IEI_PP_SEQUENCE_NR) & 0x7f, diff; + + /* if we have our first message, we take the serial number */ + if (!pp->vp_r_set) { + pp->vp_r = seq_nr; + pp->vp_r_set = true; + } + + /* how much is the SN ahead of what we expect */ + diff = (seq_nr - pp->vp_r) & 0x7f; + /* if VP(R)-5 <= SN <= VP(R)-1 (SN is 1..5 values lower than we want) */ + if (diff >= 128 - 5) { + LOGP(DV5PP, LOGL_DEBUG, "Receiveds message with sequence %d, while we expect %d. Ignore, because it was already received earlier.\n", seq_nr, pp->vp_r); + return 0; + } + /* if VP(R) <= SN <= VP(R) + 4 (SN is 0..4 higher than we want) */ + if (diff <= 4) { + /* set received sequence number to the next one expected and deliver message */ + pp->vp_r = (seq_nr + 1) & 0x7f; + } else { + LOGP(DV5PP, LOGL_NOTICE, "Receiveds message with sequence %d, while we expect %d. This a a misalignment, perform an SN reset!\n", seq_nr, pp->vp_r); + event = V52_PPFSM_E_VP_S_VP_R_misalignment_detected; + } + } + + osmo_fsm_inst_dispatch(pp->fi, event, (void *)tp); + + return 0; +} +int v52_le_pp_mdu_snd(struct v5x_interface *v5if, enum v5x_mgmt_prim prim, uint8_t link_id, uint8_t ts, uint8_t cause) +{ + enum v52_le_pp_fsm_event event; + struct v52_pp_mgmt_info info = { link_id, ts, cause }; + + if (!v5if->protection.pp) { + LOGP(DV5PP, LOGL_NOTICE, "Cannot perform action, because there is no protection on this interface.\n"); + return -EINVAL; + } + OSMO_ASSERT(v5if->protection.pp); + + switch (prim) { + case MDU_Protection_switch_over_com: + event = V52_PPFSM_E_MDU_Protection_switch_over_com; + break; + case MDU_Protection_OS_switch_over_com: + event = V52_PPFSM_E_MDU_Protection_OS_switch_over_com; + break; + case MDU_Protection_switch_over_rej: + event = V52_PPFSM_E_MDU_Protection_switch_over_reject; + break; + case MDU_Protection_reset_SN_req: + event = V52_PPFSM_E_MDU_Protection_reset_SN_req; + break; + default: + LOGP(DV5PP, LOGL_NOTICE, "Invalid MDU primitive %d receied from management.\n", prim); + return -EINVAL; + } + + osmo_fsm_inst_dispatch(v5if->protection.pp->fi, event, &info); + + return 0; +} diff --git a/src/v52_le_pp_fsm.h b/src/v52_le_pp_fsm.h new file mode 100644 index 0000000..bc3bcdb --- /dev/null +++ b/src/v52_le_pp_fsm.h @@ -0,0 +1,6 @@ + +struct v52_pp_proto *v52_le_pp_create(struct v5x_interface *v5if); +void v52_le_pp_destroy(struct v52_pp_proto *pp); +void v52_le_pp_init(void); +int v52_le_pp_dl_rcv(struct v5x_link *v5l, uint16_t cc_id, uint8_t msg_type, const struct tlv_parsed *tp); +int v52_le_pp_mdu_snd(struct v5x_interface *v5if, enum v5x_mgmt_prim prim, uint8_t link_id, uint8_t ts, uint8_t cause);