osmo-v5/src/v52_le_bcc_fsm.c

818 lines
27 KiB
C

/* ITu-T G.965 Section 17 V5.2-interface BCC protocol - LE side */
/* (C) 2022 by Andreas Eversberg <jolly@eversberg.eu>
*
* 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 <unistd.h>
#include <stdint.h>
#include <errno.h>
#include <arpa/inet.h>
#include <osmocom/core/utils.h>
#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;
}