/* ITu-T G.965 Section 17 V5.2-interface BCC protocol - 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_bcc_fsm.h" #include "logging.h" #define S(x) (1 << (x)) #define TIMEOUT_Tbcc1 2 // 1500 ms #define TIMEOUT_Tbcc2 2 #define TIMEOUT_Tbcc3 2 #define TIMEOUT_Tbcc4 1 /***********************************************************************/ /* state names, event names, primitives, ... */ /***********************************************************************/ /* 17.2.1.2 */ enum v52_bcc_fsm_state { V52_BCCFSM_S_LEBcc0_NULL, V52_BCCFSM_S_LEBcc1_WAITING_ALLOCATION, V52_BCCFSM_S_LEBcc2_ALLOCATION_ABORT, V52_BCCFSM_S_LEBcc3_WAITING_DE_ALLOCATION, V52_BCCFSM_S_LEBcc4_WAITING_AUDIT, }; enum v52_bcc_fsm_event { V52_BCCFSM_E_MDU_BCC_allocation_req, V52_BCCFSM_E_MDU_BCC_deallocation_req, V52_BCCFSM_E_MDU_BCC_audit_req, V52_BCCFSM_E_ALLOCATION_COMPLETE, V52_BCCFSM_E_ALLOCATION_REJECT, V52_BCCFSM_E_DE_ALLOCATION_COMPLETE, V52_BCCFSM_E_DE_ALLOCATION_REJECT, V52_BCCFSM_E_AUDIT_COMPLETE, V52_BCCFSM_E_AN_FAULT, V52_BCCFSM_E_PROTOCOL_ERROR, V52_BCCFSM_E_TIMEOUT_Tbcc1, V52_BCCFSM_E_TIMEOUT_Tbcc2, V52_BCCFSM_E_TIMEOUT_Tbcc3, V52_BCCFSM_E_TIMEOUT_Tbcc4, }; static const struct value_string v52_bcc_fsm_event_names[] = { { V52_BCCFSM_E_MDU_BCC_allocation_req, "MDU-BCC (Allocation request)" }, { V52_BCCFSM_E_MDU_BCC_deallocation_req, "MDU-BCC (De-allocation request)" }, { V52_BCCFSM_E_MDU_BCC_audit_req, "MDU-BCC (Audit request)" }, { V52_BCCFSM_E_ALLOCATION_COMPLETE, "ALLOCATION COMPLETE" }, { V52_BCCFSM_E_ALLOCATION_REJECT, "ALLOCATION REJECT" }, { V52_BCCFSM_E_DE_ALLOCATION_COMPLETE, "DE-ALLOCATION COMPLETE" }, { V52_BCCFSM_E_DE_ALLOCATION_REJECT, "DE-ALLOCATION REJECT" }, { V52_BCCFSM_E_AUDIT_COMPLETE, "AUDIT COMPLETE" }, { V52_BCCFSM_E_AN_FAULT, "AN FAULT" }, { V52_BCCFSM_E_PROTOCOL_ERROR, "PROTOCOL ERROR" }, { V52_BCCFSM_E_TIMEOUT_Tbcc1, "Timeout Tbbc1" }, { V52_BCCFSM_E_TIMEOUT_Tbcc2, "Timeout Tbbc2" }, { V52_BCCFSM_E_TIMEOUT_Tbcc3, "Timeout Tbbc3" }, { V52_BCCFSM_E_TIMEOUT_Tbcc4, "Timeout Tbbc4" }, { 0, NULL } }; /*********************************************************************** * V5 Message encoding / sending ***********************************************************************/ static inline uint16_t v52_bcc_ref_enc(uint16_t ref, uint8_t source_id) { uint8_t low, high; high = (ref >> 6) | (source_id << 7); low = ref & 0x3f; return (high << 8) | low; } static inline uint16_t v52_bcc_ref_dec(uint16_t ie, uint8_t *source_id) { uint8_t low, high; high = (ie >> 8) & 0x7f; low = ie & 0x3f; *source_id = ie >> 15; return (high << 6) | low; } static inline uint16_t v52_bcc_ts_enc(uint8_t link_id, uint8_t ts, uint8_t override) { OSMO_ASSERT(!(override >> 1)); return (link_id << 8) | (override << 5) | ts; } static inline void v52_bcc_ts_dec(uint16_t ie, uint8_t *link_id, uint8_t *ts, uint8_t *override) { *link_id = ie >> 8; *override = (ie >> 5) & 0x01; *ts = ie & 0x1f; } static struct v52_bcc_proc *find_bcc_proto_ref(struct v5x_interface *v5if, uint16_t ref, uint8_t source_id) { struct v52_bcc_proc *bcc; llist_for_each_entry(bcc, &v5if->bcc_procs, list) { if (bcc->ref == ref && bcc->source_id == source_id) return bcc; } return NULL; } static struct v52_bcc_proc *find_bcc_proto_ts(struct v5x_interface *v5if, uint8_t link_id, uint8_t ts) { struct v52_bcc_proc *bcc; llist_for_each_entry(bcc, &v5if->bcc_procs, list) { if (bcc->isdn_multislot) continue; if (bcc->link_id == link_id && bcc->ts == ts) return bcc; } return NULL; } static struct v52_bcc_proc *find_bcc_proto_multislot(struct v5x_interface *v5if, uint8_t link_id, uint8_t *isdn_multislot) { struct v52_bcc_proc *bcc; llist_for_each_entry(bcc, &v5if->bcc_procs, list) { if (!bcc->isdn_multislot) continue; if (bcc->link_id == link_id && !memcmp(bcc->isdn_multislot, isdn_multislot, 8)) return bcc; } return NULL; } /* G.965 Section 17.3.1 / Table 29 */ static struct msgb *v52_enc_allocation(uint16_t ref, uint8_t source_id, uint16_t user_port_id, bool is_isdn, uint8_t link_id, uint8_t ts, uint8_t override, uint8_t isdn_slot, uint8_t *isdn_multislot, uint8_t *capability) { uint16_t user_port_ie; uint8_t isdn_ts_ie, capa_ie; uint16_t v5_ts_ie; uint8_t multislot_ie[9]; 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(v52_bcc_ref_enc(ref, source_id)); l3h->msg_type = V52_CTRL_MSGT_ALLOCATION; /* user port identification */ user_port_ie = htons(v5x_l3_addr_enc(user_port_id, is_isdn)); msgb_tlv_put(msg, V52_CTRL_IEI_BCC_USER_PORT_ID, 2, (uint8_t *)&user_port_ie); if (is_isdn) { /* ISDN port time slot */ isdn_ts_ie = 0x80 | isdn_slot; msgb_tlv_put(msg, V52_CTRL_IEI_BCC_ISDN_PORT_TS_ID, 1, &isdn_ts_ie); } if (!isdn_multislot) { /* V5 time slot */ v5_ts_ie = htons(v52_bcc_ts_enc(link_id, ts, override)); msgb_tlv_put(msg, V52_CTRL_IEI_BCC_V5_TS_ID, 2, (uint8_t *)&v5_ts_ie); } else { /* V5 multi slot */ multislot_ie[0] = link_id; memcpy(multislot_ie + 1, isdn_multislot, 8); msgb_tlv_put(msg, V52_CTRL_IEI_BCC_MULTI_TS_MAP, 9, multislot_ie); } if (is_isdn && capability) { /* information transfer capability */ capa_ie = 0x80 | *capability; msgb_tlv_put(msg, V52_CTRL_IEI_BCC_INFO_TRANSFER_CAPABILITY, 1, &capa_ie); } return msg; } /* G.965 Section 17.3.4 / Table 32 */ static struct msgb *v52_enc_de_allocation(uint16_t ref, uint8_t source_id, uint16_t user_port_id, bool is_isdn, uint8_t link_id, uint8_t ts, uint8_t override, uint8_t isdn_slot, uint8_t *isdn_multislot) { uint16_t user_port_ie; uint8_t isdn_ts_ie; uint16_t v5_ts_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(v52_bcc_ref_enc(ref, source_id)); l3h->msg_type = V52_CTRL_MSGT_DE_ALLOCATION; /* user port identification */ user_port_ie = htons(v5x_l3_addr_enc(user_port_id, is_isdn)); msgb_tlv_put(msg, V52_CTRL_IEI_BCC_USER_PORT_ID, 2, (uint8_t *)&user_port_ie); if (is_isdn) { /* ISDN port time slot */ isdn_ts_ie = 0x80 | isdn_slot; msgb_tlv_put(msg, V52_CTRL_IEI_BCC_ISDN_PORT_TS_ID, 1, &isdn_ts_ie); } if (!isdn_multislot) { /* V5 time slot */ v5_ts_ie = htons(v52_bcc_ts_enc(link_id, ts, override)); msgb_tlv_put(msg, V52_CTRL_IEI_BCC_V5_TS_ID, 2, (uint8_t *)&v5_ts_ie); } else { /* V5 multi slot */ msgb_tlv_put(msg, V52_CTRL_IEI_BCC_MULTI_TS_MAP, 9, isdn_multislot); } return msg; } /* G.965 Section 17.3.7 / Table 35 */ static struct msgb *v52_enc_audit(uint16_t ref, uint8_t source_id, uint8_t link_id, uint8_t ts, uint8_t override) { uint16_t v5_ts_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(v52_bcc_ref_enc(ref, source_id)); l3h->msg_type = V52_CTRL_MSGT_AUDIT; /* V5 time slot */ v5_ts_ie = htons(v52_bcc_ts_enc(link_id, ts, override)); msgb_tlv_put(msg, V52_CTRL_IEI_BCC_V5_TS_ID, 2, (uint8_t *)&v5_ts_ie); return msg; } /* G.965 Section 17.3.10 / Table 38 */ static struct msgb *v52_enc_an_fault_ack(uint16_t ref, uint8_t source_id) { 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(v52_bcc_ref_enc(ref, source_id)); l3h->msg_type = V52_CTRL_MSGT_AN_FAULT_ACK; return msg; } /***********************************************************************/ /* Messages to other layers */ /***********************************************************************/ /* send message to upper (management) layer */ static void rcv_mdu(struct osmo_fsm_inst *fi, enum v5x_mgmt_prim prim, const uint8_t *cause, uint8_t cause_len) { struct v52_bcc_proc *bcc = fi->priv; v52_le_bcc_mdu_rcv(bcc->interface, bcc->link_id, bcc->ts, bcc->isdn_multislot, prim, cause, cause_len); } /* message to lower (DL) layer */ static void snd_msg(struct osmo_fsm_inst *fi, struct msgb *msg) { struct v52_bcc_proc *bcc = fi->priv; if (!msg) return; v5x_dl_snd(bcc->interface, V52_DLADDR_BCC, msg); } /***********************************************************************/ /* LCP state FSM */ /***********************************************************************/ static int v52_le_bcc_fsm_timer_cb(struct osmo_fsm_inst *fi) { struct v52_bcc_proc *bcc = fi->priv; bcc->expired++; osmo_fsm_inst_dispatch(fi, bcc->timer, NULL); return 0; } static void bcc_fsm_lebcc0_null(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct v52_bcc_proc *bcc = fi->priv; const struct tlv_parsed *tp = data; uint8_t const *cause; int cause_len; switch (event) { case V52_BCCFSM_E_MDU_BCC_allocation_req: /* Start Tbcc1 */ bcc->timer = V52_BCCFSM_E_TIMEOUT_Tbcc1; bcc->expired = 0; osmo_fsm_inst_state_chg(fi, V52_BCCFSM_S_LEBcc1_WAITING_ALLOCATION, TIMEOUT_Tbcc1, 1); /* Send ALLOCATION */ snd_msg(fi, v52_enc_allocation(bcc->ref, bcc->source_id, bcc->user_port_id, bcc->is_isdn, bcc->link_id, bcc->ts, bcc->override, bcc->isdn_slot, bcc->isdn_multislot, (bcc->use_capability) ? &bcc->capability : NULL)); break; case V52_BCCFSM_E_ALLOCATION_COMPLETE: /* Send MDU-BCC (Allocation confirmation) */ rcv_mdu(fi, MDU_BCC_allocation_conf, 0, 0); break; case V52_BCCFSM_E_ALLOCATION_REJECT: /* Send MDU-BCC (Allocation rejedct indication) */ cause = TLVP_VAL(tp, V52_CTRL_IEI_BCC_REJECT_CAUSE); cause_len = TLVP_LEN(tp, V52_CTRL_IEI_BCC_REJECT_CAUSE); rcv_mdu(fi, MDU_BCC_allocation_reject_ind, cause, cause_len); break; case V52_BCCFSM_E_MDU_BCC_deallocation_req: /* Start Tbcc3 */ bcc->timer = V52_BCCFSM_E_TIMEOUT_Tbcc3; bcc->expired = 0; osmo_fsm_inst_state_chg(fi, V52_BCCFSM_S_LEBcc3_WAITING_DE_ALLOCATION, TIMEOUT_Tbcc3, 3); /* Send DE-ALLOCATION */ snd_msg(fi, v52_enc_de_allocation(bcc->ref, bcc->source_id, bcc->user_port_id, bcc->is_isdn, bcc->link_id, bcc->ts, bcc->override, bcc->isdn_slot, bcc->isdn_multislot)); break; case V52_BCCFSM_E_DE_ALLOCATION_COMPLETE: /* Send MDU-BCC (De-allocation confirmation) */ rcv_mdu(fi, MDU_BCC_deallocation_conf, 0, 0); break; case V52_BCCFSM_E_DE_ALLOCATION_REJECT: /* Send MDU-BCC (De-allocation reject indication) */ cause = TLVP_VAL(tp, V52_CTRL_IEI_BCC_REJECT_CAUSE); cause_len = TLVP_LEN(tp, V52_CTRL_IEI_BCC_REJECT_CAUSE); rcv_mdu(fi, MDU_BCC_deallocation_reject_ind, cause, cause_len); break; case V52_BCCFSM_E_MDU_BCC_audit_req: /* Start Tbcc4 */ bcc->timer = V52_BCCFSM_E_TIMEOUT_Tbcc4; bcc->expired = 0; osmo_fsm_inst_state_chg(fi, V52_BCCFSM_S_LEBcc4_WAITING_AUDIT, TIMEOUT_Tbcc4, 4); /* Send ADUIT */ snd_msg(fi, v52_enc_audit(bcc->ref, bcc->source_id, bcc->link_id, bcc->ts, bcc->override)); break; case V52_BCCFSM_E_AN_FAULT: /* Send AN FAULT ACK */ snd_msg(fi, v52_enc_an_fault_ack(bcc->ref, bcc->source_id)); /* Send MDU-BCC (AN Fault indication) */ rcv_mdu(fi, MDU_BCC_AN_fault_ind, 0, 0); break; default: OSMO_ASSERT(0); } } static void bcc_fsm_lebcc1_waiting_alloc(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct v52_bcc_proc *bcc = fi->priv; const struct tlv_parsed *tp = data; uint8_t const *cause; int cause_len; switch (event) { case V52_BCCFSM_E_ALLOCATION_COMPLETE: osmo_fsm_inst_state_chg(fi, V52_BCCFSM_S_LEBcc0_NULL, 0, 0); /* Send MDU-BCC (Allocation confirmation) */ rcv_mdu(fi, MDU_BCC_allocation_conf, 0, 0); break; case V52_BCCFSM_E_ALLOCATION_REJECT: osmo_fsm_inst_state_chg(fi, V52_BCCFSM_S_LEBcc0_NULL, 0, 0); /* Send MDU-BCC (Allocation reject indication) */ cause = TLVP_VAL(tp, V52_CTRL_IEI_BCC_REJECT_CAUSE); cause_len = TLVP_LEN(tp, V52_CTRL_IEI_BCC_REJECT_CAUSE); rcv_mdu(fi, MDU_BCC_allocation_reject_ind, cause, cause_len); break; case V52_BCCFSM_E_MDU_BCC_deallocation_req: /* Start Tbcc2 */ bcc->timer = V52_BCCFSM_E_TIMEOUT_Tbcc2; bcc->expired = 0; osmo_fsm_inst_state_chg(fi, V52_BCCFSM_S_LEBcc2_ALLOCATION_ABORT, TIMEOUT_Tbcc2, 2); /* Send DE-ALLOCATION */ snd_msg(fi, v52_enc_de_allocation(bcc->ref, bcc->source_id, bcc->user_port_id, bcc->is_isdn, bcc->link_id, bcc->ts, bcc->override, bcc->isdn_slot, bcc->isdn_multislot)); break; case V52_BCCFSM_E_TIMEOUT_Tbcc1: if (bcc->expired == 1) { /* Restart Tbcc1 */ osmo_timer_schedule(&fi->timer, TIMEOUT_Tbcc1, 0); /* Send ALLOCATION */ snd_msg(fi, v52_enc_allocation(bcc->ref, bcc->source_id, bcc->user_port_id, bcc->is_isdn, bcc->link_id, bcc->ts, bcc->override, bcc->isdn_slot, bcc->isdn_multislot, (bcc->use_capability) ? &bcc->capability : NULL)); break; } osmo_fsm_inst_state_chg(fi, V52_BCCFSM_S_LEBcc0_NULL, 0, 0); /* Send MDU-BCC (Allocation error indication) */ rcv_mdu(fi, MDU_BCC_allocation_error_ind, 0, 0); break; case V52_BCCFSM_E_PROTOCOL_ERROR: osmo_fsm_inst_state_chg(fi, V52_BCCFSM_S_LEBcc0_NULL, 0, 0); /* Send MDU-BCC (Protocol error indication) */ cause = TLVP_VAL(tp, V52_CTRL_IEI_BCC_PROTOCOL_ERROR_CAUSE); cause_len = TLVP_LEN(tp, V52_CTRL_IEI_BCC_PROTOCOL_ERROR_CAUSE); rcv_mdu(fi, MDU_BCC_protocol_error_ind, cause, cause_len); break; default: OSMO_ASSERT(0); } } static void bcc_fsm_lebcc2_alloc_abort(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct v52_bcc_proc *bcc = fi->priv; const struct tlv_parsed *tp = data; uint8_t const *cause; int cause_len; switch (event) { case V52_BCCFSM_E_ALLOCATION_COMPLETE: /* ignore */ break; case V52_BCCFSM_E_ALLOCATION_REJECT: /* ignore */ break; case V52_BCCFSM_E_DE_ALLOCATION_COMPLETE: osmo_fsm_inst_state_chg(fi, V52_BCCFSM_S_LEBcc0_NULL, 0, 0); /* Send MDU-BCC (De-allocation confirmation) */ rcv_mdu(fi, MDU_BCC_deallocation_conf, 0, 0); break; case V52_BCCFSM_E_DE_ALLOCATION_REJECT: osmo_fsm_inst_state_chg(fi, V52_BCCFSM_S_LEBcc0_NULL, 0, 0); /* Send MDU-BCC (De-allocation reject indication) */ cause = TLVP_VAL(tp, V52_CTRL_IEI_BCC_REJECT_CAUSE); cause_len = TLVP_LEN(tp, V52_CTRL_IEI_BCC_REJECT_CAUSE); rcv_mdu(fi, MDU_BCC_deallocation_reject_ind, cause, cause_len); break; case V52_BCCFSM_E_TIMEOUT_Tbcc2: if (bcc->expired == 1) { /* Restart Tbcc2 */ osmo_timer_schedule(&fi->timer, TIMEOUT_Tbcc2, 0); /* Send DE-ALLOCATION */ snd_msg(fi, v52_enc_de_allocation(bcc->ref, bcc->source_id, bcc->user_port_id, bcc->is_isdn, bcc->link_id, bcc->ts, bcc->override, bcc->isdn_slot, bcc->isdn_multislot)); break; } osmo_fsm_inst_state_chg(fi, V52_BCCFSM_S_LEBcc0_NULL, 0, 0); /* Send MDU-BCC (De-allocation error indication) */ rcv_mdu(fi, MDU_BCC_deallocation_error_ind, 0, 0); break; case V52_BCCFSM_E_PROTOCOL_ERROR: osmo_fsm_inst_state_chg(fi, V52_BCCFSM_S_LEBcc0_NULL, 0, 0); /* Send MDU-BCC (Protocol error indication) */ cause = TLVP_VAL(tp, V52_CTRL_IEI_BCC_PROTOCOL_ERROR_CAUSE); cause_len = TLVP_LEN(tp, V52_CTRL_IEI_BCC_PROTOCOL_ERROR_CAUSE); rcv_mdu(fi, MDU_BCC_protocol_error_ind, cause, cause_len); break; default: OSMO_ASSERT(0); } } static void bcc_fsm_lebcc3_waiting_dealloc(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct v52_bcc_proc *bcc = fi->priv; const struct tlv_parsed *tp = data; uint8_t const *cause; int cause_len; switch (event) { case V52_BCCFSM_E_DE_ALLOCATION_COMPLETE: osmo_fsm_inst_state_chg(fi, V52_BCCFSM_S_LEBcc0_NULL, 0, 0); /* Send MDU-BCC (De-allocation confirmation) */ rcv_mdu(fi, MDU_BCC_deallocation_conf, 0, 0); break; case V52_BCCFSM_E_DE_ALLOCATION_REJECT: osmo_fsm_inst_state_chg(fi, V52_BCCFSM_S_LEBcc0_NULL, 0, 0); /* Send MDU-BCC (De-allocation reject indication) */ cause = TLVP_VAL(tp, V52_CTRL_IEI_BCC_REJECT_CAUSE); cause_len = TLVP_LEN(tp, V52_CTRL_IEI_BCC_REJECT_CAUSE); rcv_mdu(fi, MDU_BCC_deallocation_reject_ind, cause, cause_len); break; case V52_BCCFSM_E_TIMEOUT_Tbcc3: if (bcc->expired == 1) { /* Restart Tbcc3 */ osmo_timer_schedule(&fi->timer, TIMEOUT_Tbcc3, 0); /* Send DE-ALLOCATION */ snd_msg(fi, v52_enc_de_allocation(bcc->ref, bcc->source_id, bcc->user_port_id, bcc->is_isdn, bcc->link_id, bcc->ts, bcc->override, bcc->isdn_slot, bcc->isdn_multislot)); break; } osmo_fsm_inst_state_chg(fi, V52_BCCFSM_S_LEBcc0_NULL, 0, 0); /* Send MDU-BCC (De-allocation error indication) */ rcv_mdu(fi, MDU_BCC_deallocation_error_ind, 0, 0); break; case V52_BCCFSM_E_PROTOCOL_ERROR: /* Send MDU-BCC (Protocol error indication) */ cause = TLVP_VAL(tp, V52_CTRL_IEI_BCC_PROTOCOL_ERROR_CAUSE); cause_len = TLVP_LEN(tp, V52_CTRL_IEI_BCC_PROTOCOL_ERROR_CAUSE); rcv_mdu(fi, MDU_BCC_protocol_error_ind, cause, cause_len); break; default: OSMO_ASSERT(0); } } static void bcc_fsm_lebcc4_waiting_audit(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct v52_bcc_proc *bcc = fi->priv; const struct tlv_parsed *tp = data; uint8_t const *cause; int cause_len; switch (event) { case V52_BCCFSM_E_AUDIT_COMPLETE: osmo_fsm_inst_state_chg(fi, V52_BCCFSM_S_LEBcc0_NULL, 0, 0); /* Send MDU-BCC (Audit confirmation) */ rcv_mdu(fi, MDU_BCC_audit_conf, 0, 0); break; case V52_BCCFSM_E_TIMEOUT_Tbcc4: if (bcc->expired == 1) { /* Restart Tbcc4 */ osmo_timer_schedule(&fi->timer, TIMEOUT_Tbcc4, 0); /* Send AUDIT */ snd_msg(fi, v52_enc_audit(bcc->ref, bcc->source_id, bcc->user_port_id, bcc->ts, bcc->override)); break; } osmo_fsm_inst_state_chg(fi, V52_BCCFSM_S_LEBcc0_NULL, 0, 0); /* Send MDU-BCC (Audit error indication) */ rcv_mdu(fi, MDU_BCC_audit_error_ind, 0, 0); break; case V52_BCCFSM_E_PROTOCOL_ERROR: osmo_fsm_inst_state_chg(fi, V52_BCCFSM_S_LEBcc0_NULL, 0, 0); /* Send MDU-BCC (Protocol error indication) */ cause = TLVP_VAL(tp, V52_CTRL_IEI_BCC_PROTOCOL_ERROR_CAUSE); cause_len = TLVP_LEN(tp, V52_CTRL_IEI_BCC_PROTOCOL_ERROR_CAUSE); rcv_mdu(fi, MDU_BCC_protocol_error_ind, cause, cause_len); break; default: OSMO_ASSERT(0); } } /* Table 47/G.965 */ static const struct osmo_fsm_state v52_le_bcc_fsm_states[] = { [V52_BCCFSM_S_LEBcc0_NULL] = { .name = "LEBcc0 BCC BULL", .in_event_mask = S(V52_BCCFSM_E_MDU_BCC_allocation_req) | S(V52_BCCFSM_E_ALLOCATION_COMPLETE) | S(V52_BCCFSM_E_ALLOCATION_REJECT) | S(V52_BCCFSM_E_MDU_BCC_deallocation_req) | S(V52_BCCFSM_E_DE_ALLOCATION_COMPLETE) | S(V52_BCCFSM_E_DE_ALLOCATION_REJECT) | S(V52_BCCFSM_E_MDU_BCC_audit_req) | S(V52_BCCFSM_E_AN_FAULT) , .out_state_mask = S(V52_BCCFSM_S_LEBcc1_WAITING_ALLOCATION) | S(V52_BCCFSM_S_LEBcc3_WAITING_DE_ALLOCATION) | S(V52_BCCFSM_S_LEBcc4_WAITING_AUDIT), .action = bcc_fsm_lebcc0_null, }, [V52_BCCFSM_S_LEBcc1_WAITING_ALLOCATION] = { .name = "LEBcc1 BCC WAITING ALLOCATION", .in_event_mask = S(V52_BCCFSM_E_ALLOCATION_COMPLETE) | S(V52_BCCFSM_E_ALLOCATION_REJECT) | S(V52_BCCFSM_E_MDU_BCC_deallocation_req) | S(V52_BCCFSM_E_TIMEOUT_Tbcc1) | S(V52_BCCFSM_E_PROTOCOL_ERROR), .out_state_mask = S(V52_BCCFSM_S_LEBcc0_NULL) | S(V52_BCCFSM_S_LEBcc2_ALLOCATION_ABORT), .action = bcc_fsm_lebcc1_waiting_alloc, }, [V52_BCCFSM_S_LEBcc2_ALLOCATION_ABORT] = { .name = "LEBcc2 BCC ALLOCATION ABORT", .in_event_mask = S(V52_BCCFSM_E_ALLOCATION_COMPLETE) | S(V52_BCCFSM_E_ALLOCATION_REJECT) | S(V52_BCCFSM_E_DE_ALLOCATION_COMPLETE) | S(V52_BCCFSM_E_DE_ALLOCATION_REJECT) | S(V52_BCCFSM_E_TIMEOUT_Tbcc2) | S(V52_BCCFSM_E_PROTOCOL_ERROR), .out_state_mask = S(V52_BCCFSM_S_LEBcc0_NULL), .action = bcc_fsm_lebcc2_alloc_abort, }, [V52_BCCFSM_S_LEBcc3_WAITING_DE_ALLOCATION] = { .name = "LEBcc3 BCC WAITING DE-ALLOCATION", .in_event_mask = S(V52_BCCFSM_E_DE_ALLOCATION_COMPLETE) | S(V52_BCCFSM_E_DE_ALLOCATION_REJECT) | S(V52_BCCFSM_E_TIMEOUT_Tbcc3) | S(V52_BCCFSM_E_PROTOCOL_ERROR), .out_state_mask = S(V52_BCCFSM_S_LEBcc0_NULL), .action = bcc_fsm_lebcc3_waiting_dealloc, }, [V52_BCCFSM_S_LEBcc4_WAITING_AUDIT] = { .name = "LEBcc4 BCC wAITING AUDIT", .in_event_mask = S(V52_BCCFSM_E_MDU_BCC_audit_req) | S(V52_BCCFSM_E_TIMEOUT_Tbcc1) | S(V52_BCCFSM_E_PROTOCOL_ERROR), .out_state_mask = S(V52_BCCFSM_S_LEBcc0_NULL), .action = bcc_fsm_lebcc4_waiting_audit, }, }; struct osmo_fsm v52_le_bcc_fsm = { .name = "V52_LE_BCC", .states = v52_le_bcc_fsm_states, .num_states = ARRAY_SIZE(v52_le_bcc_fsm_states), .timer_cb = v52_le_bcc_fsm_timer_cb, .log_subsys = DV5BCC, .event_names = v52_bcc_fsm_event_names, }; static uint16_t bcc_new_ref = 0; static struct v52_bcc_proc *v52_le_bcc_create(struct v5x_interface *v5if, uint16_t user_port_id, bool is_isdn, uint8_t link_id, uint8_t ts, uint8_t override, uint8_t isdn_slot, uint8_t *isdn_multislot, uint8_t *capability, int source_id) { struct v52_bcc_proc *bcc; OSMO_ASSERT(v5if); LOGP(DV5BCC, LOGL_DEBUG, "Creating BCC instance, initiated by %s.\n", (source_id) ? "AN" : "LE"); bcc = talloc_zero(v5if, struct v52_bcc_proc); if (!bcc) return NULL; bcc->interface = v5if; bcc->fi = osmo_fsm_inst_alloc(&v52_le_bcc_fsm, bcc, bcc, LOGL_DEBUG, NULL); if (!bcc->fi) { talloc_free(bcc); return NULL; } bcc->ref = (bcc_new_ref++) & 0x1fff; bcc->source_id = source_id; bcc->user_port_id = user_port_id; bcc->is_isdn = is_isdn; bcc->link_id = link_id; bcc->ts = ts; bcc->override = override; bcc->isdn_slot = isdn_slot; if (isdn_multislot) { /* copy multislot IE content */ bcc->isdn_multislot = talloc_zero_size(bcc, 9); if (bcc->isdn_multislot) { bcc->isdn_multislot[0] = link_id; memcpy(bcc->isdn_multislot + 1, isdn_multislot, 8); } } if (capability) { bcc->use_capability = true; bcc->capability = *capability; } llist_add_tail(&bcc->list, &v5if->bcc_procs); return bcc; } void v52_le_bcc_destroy(struct v52_bcc_proc *bcc) { LOGP(DV5BCC, LOGL_DEBUG, "Destroying BCC instance.\n"); if (bcc->fi) osmo_fsm_inst_free(bcc->fi); llist_del(&bcc->list); talloc_free(bcc); } void v52_le_bcc_init(void) { int rc; rc = osmo_fsm_register(&v52_le_bcc_fsm); OSMO_ASSERT(!rc); LOGP(DV5BCC, LOGL_NOTICE, "Using V52 BCC protocol\n"); } /*********************************************************************** * Message receiving / decoding ***********************************************************************/ /* receive message from lower (DL) layer */ int v52_le_bcc_dl_rcv(struct v5x_interface *v5if, uint16_t l3_addr, uint8_t msg_type, const struct tlv_parsed *tp) { struct v52_bcc_proc *bcc; uint16_t ref; uint8_t source_id; /* search for ref */ ref = v52_bcc_ref_dec(l3_addr, &source_id); bcc = find_bcc_proto_ref(v5if, ref, source_id); /* handle AN fault here, so there is no need to create a process */ if (msg_type == V52_CTRL_MSGT_AN_FAULT) { LOGP(DV5BCC, LOGL_NOTICE, "Replying to AN FAULT message. No action is taken here.\n"); /* Send AN FAULT ACK */ v5x_dl_snd(v5if, V52_DLADDR_BCC, v52_enc_an_fault_ack(ref, source_id)); return 0; } /* ignoring remote process (may happen due to message repetition in state 0) */ if (!bcc) return 0; switch (msg_type) { case V52_CTRL_MSGT_ALLOCATION_COMPLETE: osmo_fsm_inst_dispatch(bcc->fi, V52_BCCFSM_E_ALLOCATION_COMPLETE, (void *)tp); break; case V52_CTRL_MSGT_ALLOCATION_REJECT: osmo_fsm_inst_dispatch(bcc->fi, V52_BCCFSM_E_ALLOCATION_REJECT, (void *)tp); break; case V52_CTRL_MSGT_DE_ALLOCATION_COMPLETE: osmo_fsm_inst_dispatch(bcc->fi, V52_BCCFSM_E_DE_ALLOCATION_COMPLETE, (void *)tp); break; case V52_CTRL_MSGT_DE_ALLOCATION_REJECT: osmo_fsm_inst_dispatch(bcc->fi, V52_BCCFSM_E_DE_ALLOCATION_REJECT, (void *)tp); break; case V52_CTRL_MSGT_AUDIT_COMPLETE: osmo_fsm_inst_dispatch(bcc->fi, V52_BCCFSM_E_AUDIT_COMPLETE, (void *)tp); break; case V52_CTRL_MSGT_AN_FAULT: osmo_fsm_inst_dispatch(bcc->fi, V52_BCCFSM_E_AN_FAULT, (void *)tp); break; case V52_CTRL_MSGT_PROTOCOL_ERROR: osmo_fsm_inst_dispatch(bcc->fi, V52_BCCFSM_E_PROTOCOL_ERROR, (void *)tp); break; default: LOGP(DV5BCC, LOGL_NOTICE, "Invalid BCC protocol message %d receied from AN.\n", msg_type); return -EINVAL; } if (bcc->fi->state == V52_BCCFSM_S_LEBcc0_NULL) { /* destroy, if NULL state is reached */ v52_le_bcc_destroy(bcc); } return 0; } /* receive message from upper (management) layer */ int v52_le_bcc_mdu_snd(struct v5x_interface *v5if, uint16_t user_port_id, bool is_isdn, uint8_t link_id, uint8_t ts, uint8_t override, uint8_t isdn_slot, uint8_t *isdn_multislot, uint8_t *capability, enum v5x_mgmt_prim prim) { struct v52_bcc_proc *bcc; /* search TS */ if (isdn_multislot) bcc = find_bcc_proto_multislot(v5if, link_id, isdn_multislot); else bcc = find_bcc_proto_ts(v5if, link_id, ts); if (!bcc) { bcc = v52_le_bcc_create(v5if, user_port_id, is_isdn, link_id, ts, override, isdn_slot, isdn_multislot, capability, 0 /* LE */); if (!bcc) return -ENOMEM; } switch (prim) { case MDU_BCC_allocation_req: osmo_fsm_inst_dispatch(bcc->fi, V52_BCCFSM_E_MDU_BCC_allocation_req, NULL); break; case MDU_BCC_deallocation_req: osmo_fsm_inst_dispatch(bcc->fi, V52_BCCFSM_E_MDU_BCC_deallocation_req, NULL); break; case MDU_BCC_audit_req: osmo_fsm_inst_dispatch(bcc->fi, V52_BCCFSM_E_MDU_BCC_audit_req, NULL); break; default: LOGP(DV5BCC, LOGL_NOTICE, "Invalid BCC protocol message %d receied from AN.\n", prim); return -EINVAL; } if (bcc->fi->state == V52_BCCFSM_S_LEBcc0_NULL) { /* destroy, if NULL state is reached */ v52_le_bcc_destroy(bcc); } return 0; }