From e1070616f17ef21e6d5760c1988ec95d863c47c5 Mon Sep 17 00:00:00 2001 From: Harald Welte Date: Tue, 28 Dec 2021 11:32:53 +0100 Subject: [PATCH] WIP initial import of far incomplete ETSI V5 implementation --- lapv5.c | 735 +++++++++++++++++++++++++++++++++++++++++ v51_l1_fsm.c | 35 ++ v51_le_ctrl.c | 365 ++++++++++++++++++++ v52_lcp_fsm.c | 612 ++++++++++++++++++++++++++++++++++ v52_le_user_port_fsm.c | 448 +++++++++++++++++++++++++ v5x_internal.h | 120 +++++++ v5x_protocol.c | 399 ++++++++++++++++++++++ v5x_protocol.h | 300 +++++++++++++++++ 8 files changed, 3014 insertions(+) create mode 100644 lapv5.c create mode 100644 v51_l1_fsm.c create mode 100644 v51_le_ctrl.c create mode 100644 v52_lcp_fsm.c create mode 100644 v52_le_user_port_fsm.c create mode 100644 v5x_internal.h create mode 100644 v5x_protocol.c create mode 100644 v5x_protocol.h diff --git a/lapv5.c b/lapv5.c new file mode 100644 index 0000000..4b688bd --- /dev/null +++ b/lapv5.c @@ -0,0 +1,735 @@ +/* LAPV5-DL as per Section 10.1 of ITU-T G.964 */ + +/* (C) 2021 by Harald Welte + * + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include "internal.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define LAPD_ADDR2(sapi, cr) ((((sapi) & 0x3f) << 2) | (((cr) & 0x1) << 1)) +#define LAPD_ADDR3(tei) ((((tei) & 0x7f) << 1) | 0x1) + +#define LAPD_ADDR_SAPI(addr) ((addr) >> 2) +#define LAPD_ADDR_CR(addr) (((addr) >> 1) & 0x1) +#define LAPD_ADDR_EA(addr) ((addr) & 0x1) +#define LAPD_ADDR_TEI(addr) ((addr) >> 1) + +#define LAPD_CTRL_I4(ns) (((ns) & 0x7f) << 1) +#define LAPD_CTRL_I5(nr, p) ((((nr) & 0x7f) << 1) | ((p) & 0x1)) +#define LAPD_CTRL_S4(s) ((((s) & 0x3) << 2) | 0x1) +#define LAPD_CTRL_S5(nr, p) ((((nr) & 0x7f) << 1) | ((p) & 0x1)) +#define LAPD_CTRL_U4(u, p) ((((u) & 0x1c) << (5-2)) | (((p) & 0x1) << 4) | (((u) & 0x3) << 2) | 0x3) + +#define LAPD_CTRL_is_I(ctrl) (((ctrl) & 0x1) == 0) +#define LAPD_CTRL_is_S(ctrl) (((ctrl) & 0x3) == 1) +#define LAPD_CTRL_is_U(ctrl) (((ctrl) & 0x3) == 3) + +#define LAPD_CTRL_U_BITS(ctrl) ((((ctrl) & 0xC) >> 2) | ((ctrl) & 0xE0) >> 3) +#define LAPD_CTRL_U_PF(ctrl) (((ctrl) >> 4) & 0x1) + +#define LAPD_CTRL_S_BITS(ctrl) (((ctrl) & 0xC) >> 2) +#define LAPD_CTRL_S_PF(ctrl) (ctrl & 0x1) + +#define LAPD_CTRL_I_Ns(ctrl) (((ctrl) & 0xFE) >> 1) +#define LAPD_CTRL_I_P(ctrl) (ctrl & 0x1) +#define LAPD_CTRL_Nr(ctrl) (((ctrl) & 0xFE) >> 1) + +#define LAPD_LEN(len) ((len << 2) | 0x1) +#define LAPD_EL 0x1 + +#define LAPD_SET_K(n, o) {n,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o} + +#define LOGLI(li, level, fmt, args ...) \ + LOGP(DLLAPD, level, "(%s): " fmt, (li)->name, ## args) + +#define LOGTEI(teip, level, fmt, args ...) \ + LOGP(DLLAPD, level, "(%s-T%u): " fmt, (teip)->li->name, (teip)->tei, ## args) + +#define LOGSAP(sap, level, fmt, args ...) \ + LOGP(DLLAPD, level, "(%s): " fmt, (sap)->dl.name, ## args) + +#define DLSAP_MSGB_SIZE 128 +#define DLSAP_MSGB_HEADROOM 56 + +const struct lapd_profile lapd_profile_lapv5dl = { + .k = LAPD_SET_K(7,7), + .n200 = 3, + .n201 = 260, + .n202 = 3, + .t200_sec = 1, .t200_usec = 0, + .t203_sec = 10, .t203_usec = 0, + .short_address = 0 +}; + +typedef enum { + LAPD_TEI_NONE = 0, + LAPD_TEI_ASSIGNED, + LAPD_TEI_ACTIVE, +} lapd_tei_state; + +const char *lapd_tei_states[] = { + "NONE", + "ASSIGNED", + "ACTIVE", +}; + +/* Structure representing an allocated TEI within a LAPD instance. */ +struct lapd_tei { + struct llist_head list; + struct lapd_instance *li; + uint8_t tei; + lapd_tei_state state; + + struct llist_head sap_list; +}; + +/* Structure representing a SAP within a TEI. It includes exactly one datalink + * instance. */ +struct lapd_sap { + struct llist_head list; + struct lapd_tei *tei; + uint8_t sapi; + + struct lapd_datalink dl; +}; + +/* Resolve TEI structure from given numeric TEI */ +static struct lapd_tei *teip_from_tei(struct lapd_instance *li, uint8_t tei) +{ + struct lapd_tei *lt; + + llist_for_each_entry(lt, &li->tei_list, list) { + if (lt->tei == tei) + return lt; + } + return NULL; +}; + +/* Change state of TEI */ +static void lapd_tei_set_state(struct lapd_tei *teip, int newstate) +{ + LOGTEI(teip, LOGL_INFO, "LAPD state change on TEI %d: %s -> %s\n", + teip->tei, lapd_tei_states[teip->state], + lapd_tei_states[newstate]); + teip->state = newstate; +}; + +/* Allocate a new TEI */ +struct lapd_tei *lapd_tei_alloc(struct lapd_instance *li, uint8_t tei) +{ + struct lapd_tei *teip; + + teip = talloc_zero(li, struct lapd_tei); + if (!teip) + return NULL; + + teip->li = li; + teip->tei = tei; + llist_add(&teip->list, &li->tei_list); + INIT_LLIST_HEAD(&teip->sap_list); + + lapd_tei_set_state(teip, LAPD_TEI_ASSIGNED); + + return teip; +} + +/* Find a SAP within a given TEI */ +static struct lapd_sap *lapd_sap_find(struct lapd_tei *teip, uint8_t sapi) +{ + struct lapd_sap *sap; + + llist_for_each_entry(sap, &teip->sap_list, list) { + if (sap->sapi == sapi) + return sap; + } + + return NULL; +} + +static int send_ph_data_req(struct lapd_msg_ctx *lctx, struct msgb *msg); +static int send_dlsap(struct osmo_dlsap_prim *dp, struct lapd_msg_ctx *lctx); + +/* Allocate a new SAP within a given TEI */ +static struct lapd_sap *lapd_sap_alloc(struct lapd_tei *teip, uint8_t sapi) +{ + struct lapd_sap *sap; + struct lapd_datalink *dl; + struct lapd_instance *li = teip->li; + struct lapd_profile *profile; + char name[256]; + int k; + + snprintf(name, sizeof(name), "%s-T%u-S%u", li->name, teip->tei, sapi); + + sap = talloc_zero(teip, struct lapd_sap); + if (!sap) + return NULL; + + LOGP(DLLAPD, LOGL_NOTICE, + "(%s): LAPD Allocating SAP for SAPI=%u / TEI=%u (dl=%p, sap=%p)\n", + name, sapi, teip->tei, &sap->dl, sap); + + sap->sapi = sapi; + sap->tei = teip; + dl = &sap->dl; + profile = &li->profile; + + k = profile->k[sapi & 0x3f]; + LOGP(DLLAPD, LOGL_NOTICE, "(%s): k=%d N200=%d N201=%d T200=%d.%d T203=%d.%d\n", + name, k, profile->n200, profile->n201, profile->t200_sec, + profile->t200_usec, profile->t203_sec, profile->t203_usec); + lapd_dl_init2(dl, k, 128, profile->n201, name); + dl->use_sabme = 1; /* use SABME instead of SABM (GSM) */ + dl->send_ph_data_req = send_ph_data_req; + dl->send_dlsap = send_dlsap; + dl->n200 = profile->n200; + dl->n200_est_rel = profile->n200; + dl->t200_sec = profile->t200_sec; dl->t200_usec = profile->t200_usec; + dl->t203_sec = profile->t203_sec; dl->t203_usec = profile->t203_usec; + dl->lctx.dl = &sap->dl; + dl->lctx.sapi = sapi; + dl->lctx.tei = teip->tei; + dl->lctx.n201 = profile->n201; + + lapd_set_mode(&sap->dl, (teip->li->network_side) ? LAPD_MODE_NETWORK + : LAPD_MODE_USER); + + llist_add(&sap->list, &teip->sap_list); + + return sap; +} + +/* Free SAP instance, including the datalink */ +static void lapd_sap_free(struct lapd_sap *sap) +{ + LOGSAP(sap, LOGL_NOTICE, + "LAPD Freeing SAP for SAPI=%u / TEI=%u (dl=%p, sap=%p)\n", + sap->sapi, sap->tei->tei, &sap->dl, sap); + + /* free datalink structures and timers */ + lapd_dl_exit(&sap->dl); + + llist_del(&sap->list); + talloc_free(sap); +} + +/* Free TEI instance */ +static void lapd_tei_free(struct lapd_tei *teip) +{ + struct lapd_sap *sap, *sap2; + + llist_for_each_entry_safe(sap, sap2, &teip->sap_list, list) { + lapd_sap_free(sap); + } + + llist_del(&teip->list); + talloc_free(teip); +} + +/* Input function into TEI manager */ +static int lapd_tei_receive(struct lapd_instance *li, uint8_t *data, int len) +{ + uint8_t entity; + uint8_t ref; + uint8_t mt; + uint8_t action; + uint8_t e; + uint8_t resp[8]; + struct lapd_tei *teip; + struct msgb *msg; + + if (len < 5) { + LOGLI(li, LOGL_ERROR, "LAPD TEIMGR frame receive len %d < 5" + ", ignoring\n", len); + return -EINVAL; + }; + + entity = data[0]; + ref = data[1]; + mt = data[3]; + action = data[4] >> 1; + e = data[4] & 1; + + DEBUGP(DLLAPD, "LAPD TEIMGR: entity %x, ref %x, mt %x, action %x, " + "e %x\n", entity, ref, mt, action, e); + + switch (mt) { + case 0x01: /* IDENTITY REQUEST */ + DEBUGP(DLLAPD, "LAPD TEIMGR: identity request for TEI %u\n", + action); + + teip = teip_from_tei(li, action); + if (!teip) { + LOGLI(li, LOGL_INFO, "TEI MGR: New TEI %u\n", + action); + teip = lapd_tei_alloc(li, action); + if (!teip) + return -ENOMEM; + } + + /* Send ACCEPT */ + memmove(resp, "\xfe\xff\x03\x0f\x00\x00\x02\x00", 8); + resp[7] = (action << 1) | 1; + msg = msgb_alloc_headroom(DLSAP_MSGB_SIZE, DLSAP_MSGB_HEADROOM, "DL EST"); + msg->l2h = msgb_push(msg, 8); + memcpy(msg->l2h, resp, 8); + + /* write to PCAP file, if enabled. */ + osmo_pcap_lapd_write(li->pcap_fd, OSMO_LAPD_PCAP_OUTPUT, msg); + + LOGTEI(teip, LOGL_DEBUG, "TX: %s\n", osmo_hexdump(msg->data, msg->len)); + li->transmit_cb(msg, li->transmit_cbdata); + + if (teip->state == LAPD_TEI_NONE) + lapd_tei_set_state(teip, LAPD_TEI_ASSIGNED); + break; + default: + LOGLI(li, LOGL_NOTICE, "LAPD TEIMGR: unknown mt %x action %x\n", mt, action); + break; + }; + + return 0; +} + +/* General input function for any data received for this LAPD instance */ +int lapd_receive(struct lapd_instance *li, struct msgb *msg, int *error) +{ + int i; + struct lapd_msg_ctx lctx; + int rc; + struct lapd_sap *sap; + struct lapd_tei *teip; + + /* write to PCAP file, if enabled. */ + osmo_pcap_lapd_write(li->pcap_fd, OSMO_LAPD_PCAP_INPUT, msg); + + LOGLI(li, LOGL_DEBUG, "RX: %s\n", osmo_hexdump(msg->data, msg->len)); + if (msg->len < 2) { + LOGLI(li, LOGL_ERROR, "LAPD frame receive len %d < 2, ignoring\n", msg->len); + *error = LAPD_ERR_BAD_LEN; + return -EINVAL; + }; + msg->l2h = msg->data; + + memset(&lctx, 0, sizeof(lctx)); + + i = 0; + /* adress field */ + lctx.sapi = LAPD_ADDR_SAPI(msg->l2h[i]); + lctx.cr = LAPD_ADDR_CR(msg->l2h[i]); + lctx.lpd = 0; + if (!LAPD_ADDR_EA(msg->l2h[i])) { + if (msg->len < 3) { + LOGLI(li, LOGL_ERROR, "LAPD frame with TEI receive " + "len %d < 3, ignoring\n", msg->len); + *error = LAPD_ERR_BAD_LEN; + return -EINVAL; + }; + i++; + lctx.tei = LAPD_ADDR_TEI(msg->l2h[i]); + } + i++; + /* control field */ + if (LAPD_CTRL_is_I(msg->l2h[i])) { + lctx.format = LAPD_FORM_I; + lctx.n_send = LAPD_CTRL_I_Ns(msg->l2h[i]); + i++; + if (msg->len < 3 && i == 2) { + LOGLI(li, LOGL_ERROR, "LAPD I frame without TEI " + "receive len %d < 3, ignoring\n", msg->len); + *error = LAPD_ERR_BAD_LEN; + return -EINVAL; + }; + if (msg->len < 4 && i == 3) { + LOGLI(li, LOGL_ERROR, "LAPD I frame with TEI " + "receive len %d < 4, ignoring\n", msg->len); + *error = LAPD_ERR_BAD_LEN; + return -EINVAL; + }; + lctx.n_recv = LAPD_CTRL_Nr(msg->l2h[i]); + lctx.p_f = LAPD_CTRL_I_P(msg->l2h[i]); + } else if (LAPD_CTRL_is_S(msg->l2h[i])) { + lctx.format = LAPD_FORM_S; + lctx.s_u = LAPD_CTRL_S_BITS(msg->l2h[i]); + i++; + if (msg->len < 3 && i == 2) { + LOGLI(li, LOGL_ERROR, "LAPD S frame without TEI " + "receive len %d < 3, ignoring\n", msg->len); + *error = LAPD_ERR_BAD_LEN; + return -EINVAL; + }; + if (msg->len < 4 && i == 3) { + LOGLI(li, LOGL_ERROR, "LAPD S frame with TEI " + "receive len %d < 4, ignoring\n", msg->len); + *error = LAPD_ERR_BAD_LEN; + return -EINVAL; + }; + lctx.n_recv = LAPD_CTRL_Nr(msg->l2h[i]); + lctx.p_f = LAPD_CTRL_S_PF(msg->l2h[i]); + } else if (LAPD_CTRL_is_U(msg->l2h[i])) { + lctx.format = LAPD_FORM_U; + lctx.s_u = LAPD_CTRL_U_BITS(msg->l2h[i]); + lctx.p_f = LAPD_CTRL_U_PF(msg->l2h[i]); + } else + lctx.format = LAPD_FORM_UKN; + i++; + /* length */ + msg->l3h = msg->l2h + i; + msgb_pull(msg, i); + lctx.length = msg->len; + + /* perform TEI assignment, if received */ + if (lctx.tei == 127) { + rc = lapd_tei_receive(li, msg->data, msg->len); + msgb_free(msg); + return rc; + } + + /* resolve TEI and SAPI */ + teip = teip_from_tei(li, lctx.tei); + if (!teip) { + LOGLI(li, LOGL_NOTICE, "LAPD Unknown TEI %u\n", lctx.tei); + *error = LAPD_ERR_UNKNOWN_TEI; + msgb_free(msg); + return -EINVAL; + } + sap = lapd_sap_find(teip, lctx.sapi); + if (!sap) { + LOGTEI(teip, LOGL_INFO, "LAPD No SAP for TEI=%u / SAPI=%u, " + "allocating\n", lctx.tei, lctx.sapi); + sap = lapd_sap_alloc(teip, lctx.sapi); + if (!sap) { + *error = LAPD_ERR_NO_MEM; + msgb_free(msg); + return -ENOMEM; + } + } + lctx.dl = &sap->dl; + lctx.n201 = lctx.dl->maxf; + + if (msg->len > lctx.n201) { + LOGSAP(sap, LOGL_ERROR, "message len %d > N201(%d) " + "(discarding)\n", msg->len, lctx.n201); + msgb_free(msg); + *error = LAPD_ERR_BAD_LEN; + return -EINVAL; + } + + /* send to LAPD */ + return lapd_ph_data_ind(msg, &lctx); +} + +/* Start a (user-side) SAP for the specified TEI/SAPI on the LAPD instance */ +int lapd_sap_start(struct lapd_instance *li, uint8_t tei, uint8_t sapi) +{ + struct lapd_sap *sap; + struct lapd_tei *teip; + struct osmo_dlsap_prim dp; + struct msgb *msg; + + teip = teip_from_tei(li, tei); + if (!teip) + teip = lapd_tei_alloc(li, tei); + + sap = lapd_sap_find(teip, sapi); + if (sap) + return -EEXIST; + + sap = lapd_sap_alloc(teip, sapi); + if (!sap) + return -ENOMEM; + + LOGSAP(sap, LOGL_NOTICE, "LAPD DL-ESTABLISH request TEI=%d SAPI=%d\n", tei, sapi); + + /* prepare prim */ + msg = msgb_alloc_headroom(DLSAP_MSGB_SIZE, DLSAP_MSGB_HEADROOM, "DL EST"); + msg->l3h = msg->data; + osmo_prim_init(&dp.oph, 0, PRIM_DL_EST, PRIM_OP_REQUEST, msg); + + /* send to L2 */ + return lapd_recv_dlsap(&dp, &sap->dl.lctx); +} + +/* Stop a (user-side) SAP for the specified TEI/SAPI on the LAPD instance */ +int lapd_sap_stop(struct lapd_instance *li, uint8_t tei, uint8_t sapi) +{ + struct lapd_tei *teip; + struct lapd_sap *sap; + struct osmo_dlsap_prim dp; + struct msgb *msg; + + teip = teip_from_tei(li, tei); + if (!teip) + return -ENODEV; + + sap = lapd_sap_find(teip, sapi); + if (!sap) + return -ENODEV; + + LOGSAP(sap, LOGL_NOTICE, "LAPD DL-RELEASE request TEI=%d SAPI=%d\n", tei, sapi); + + /* prepare prim */ + msg = msgb_alloc_headroom(DLSAP_MSGB_SIZE, DLSAP_MSGB_HEADROOM, "DL REL"); + msg->l3h = msg->data; + osmo_prim_init(&dp.oph, 0, PRIM_DL_REL, PRIM_OP_REQUEST, msg); + + /* send to L2 */ + return lapd_recv_dlsap(&dp, &sap->dl.lctx); +} + +/* Transmit Data (DL-DATA request) on the given LAPD Instance / TEI / SAPI */ +void lapd_transmit(struct lapd_instance *li, uint8_t tei, uint8_t sapi, + struct msgb *msg) +{ + struct lapd_tei *teip = teip_from_tei(li, tei); + struct lapd_sap *sap; + struct osmo_dlsap_prim dp; + + if (!teip) { + LOGLI(li, LOGL_ERROR, "LAPD Cannot transmit on non-existing TEI %u\n", tei); + msgb_free(msg); + return; + } + + sap = lapd_sap_find(teip, sapi); + if (!sap) { + LOGTEI(teip, LOGL_INFO, "LAPD Tx on unknown SAPI=%u in TEI=%u\n", sapi, tei); + msgb_free(msg); + return; + } + + /* prepare prim */ + msg->l3h = msg->data; + osmo_prim_init(&dp.oph, 0, PRIM_DL_DATA, PRIM_OP_REQUEST, msg); + + /* send to L2 */ + lapd_recv_dlsap(&dp, &sap->dl.lctx); +}; + +static int send_ph_data_req(struct lapd_msg_ctx *lctx, struct msgb *msg) +{ + struct lapd_datalink *dl = lctx->dl; + struct lapd_sap *sap = + container_of(dl, struct lapd_sap, dl); + struct lapd_instance *li = sap->tei->li; + int format = lctx->format; + int addr_len; + + /* control field */ + switch (format) { + case LAPD_FORM_I: + msg->l2h = msgb_push(msg, 2); + msg->l2h[0] = LAPD_CTRL_I4(lctx->n_send); + msg->l2h[1] = LAPD_CTRL_I5(lctx->n_recv, lctx->p_f); + break; + case LAPD_FORM_S: + msg->l2h = msgb_push(msg, 2); + msg->l2h[0] = LAPD_CTRL_S4(lctx->s_u); + msg->l2h[1] = LAPD_CTRL_S5(lctx->n_recv, lctx->p_f); + break; + case LAPD_FORM_U: + msg->l2h = msgb_push(msg, 1); + msg->l2h[0] = LAPD_CTRL_U4(lctx->s_u, lctx->p_f); + break; + default: + msgb_free(msg); + return -EINVAL; + } + /* address field */ + if (li->profile.short_address && lctx->tei == 0) + addr_len = 1; + else + addr_len = 2; + msg->l2h = msgb_push(msg, addr_len); + msg->l2h[0] = LAPD_ADDR2(lctx->sapi, lctx->cr); + if (addr_len == 1) + msg->l2h[0] |= 0x1; + else + msg->l2h[1] = LAPD_ADDR3(lctx->tei); + + /* write to PCAP file, if enabled. */ + osmo_pcap_lapd_write(li->pcap_fd, OSMO_LAPD_PCAP_OUTPUT, msg); + + /* forward frame to L1 */ + LOGDL(dl, LOGL_DEBUG, "TX: %s\n", osmo_hexdump(msg->data, msg->len)); + li->transmit_cb(msg, li->transmit_cbdata); + + return 0; +} + +/* A DL-SAP message is received from datalink instance and forwarded to L3 */ +static int send_dlsap(struct osmo_dlsap_prim *dp, struct lapd_msg_ctx *lctx) +{ + struct lapd_datalink *dl = lctx->dl; + struct lapd_sap *sap = + container_of(dl, struct lapd_sap, dl); + struct lapd_instance *li; + uint8_t tei, sapi; + char *op = (dp->oph.operation == PRIM_OP_INDICATION) ? "indication" + : "confirm"; + + li = sap->tei->li; + tei = lctx->tei; + sapi = lctx->sapi; + + switch (dp->oph.primitive) { + case PRIM_DL_EST: + LOGDL(dl, LOGL_NOTICE, "LAPD DL-ESTABLISH %s TEI=%d " + "SAPI=%d\n", op, lctx->tei, lctx->sapi); + break; + case PRIM_DL_REL: + LOGDL(dl, LOGL_NOTICE, "LAPD DL-RELEASE %s TEI=%d " + "SAPI=%d\n", op, lctx->tei, lctx->sapi); + lapd_sap_free(sap); + /* note: sap and dl is now gone, don't use it anymore */ + break; + default: + ; + } + + li->receive_cb(dp, tei, sapi, li->receive_cbdata); + + return 0; +} + +/* Allocate a new LAPD instance */ +struct lapd_instance *lapd_instance_alloc2(int network_side, + void (*tx_cb)(struct msgb *msg, void *cbdata), void *tx_cbdata, + void (*rx_cb)(struct osmo_dlsap_prim *odp, uint8_t tei, uint8_t sapi, + void *rx_cbdata), void *rx_cbdata, + const struct lapd_profile *profile, const char *name) +{ + struct lapd_instance *li; + + li = talloc_zero(NULL, struct lapd_instance); + if (!li) + return NULL; + + li->network_side = network_side; + li->transmit_cb = tx_cb; + li->transmit_cbdata = tx_cbdata; + li->receive_cb = rx_cb; + li->receive_cbdata = rx_cbdata; + li->pcap_fd = -1; + li->name = talloc_strdup(li, name); + memcpy(&li->profile, profile, sizeof(li->profile)); + + INIT_LLIST_HEAD(&li->tei_list); + + return li; +} + +struct lapd_instance *lapd_instance_alloc(int network_side, + void (*tx_cb)(struct msgb *msg, void *cbdata), void *tx_cbdata, + void (*rx_cb)(struct osmo_dlsap_prim *odp, uint8_t tei, uint8_t sapi, + void *rx_cbdata), void *rx_cbdata, + const struct lapd_profile *profile) +{ + return lapd_instance_alloc2(network_side, tx_cbdata, tx_cb, rx_cb, rx_cbdata, profile, NULL); +} + + +/* Change lapd-profile on the fly (use with caution!) */ +void lapd_instance_set_profile(struct lapd_instance *li, + const struct lapd_profile *profile) +{ + memcpy(&li->profile, profile, sizeof(li->profile)); +} + +void lapd_instance_free(struct lapd_instance *li) +{ + struct lapd_tei *teip, *teip2; + + /* Free all TEI instances */ + llist_for_each_entry_safe(teip, teip2, &li->tei_list, list) { + lapd_tei_free(teip); + } + + talloc_free(li); +} + +/*********************************************************************** + * LAPV5-EF (Encapsulation Function) + ***********************************************************************/ + +/* The LAPV5-EF adds another layer around of the LAPD-style LAPV5-DL. It consists of + * a two-byte header in front of the normal data link header. Examples see Figure E.1/G.964 */ + + +/* main entry point for receiving signaling frames from the AN. The assumption is that + * the flag octets are removed and the msg->data points to the first octet of the EFaddr, + * while the last octet (before msg->tail) points to the last FCS octet. */ +int lapv5ef_rx(struct v5x_link *link, struct msgb *msg) +{ + uint16_t efaddr, efaddr_enc; + bool is_isdn; + + msg->l1h = msg->data; + + if (msgb_length(msg) < 2) { + msgb_free(msg); + return -EINVAL; + } + + msg->l2h = msg->l1h + 2; + efaddr_enc = msg->l1h[0] << 8 | msg->l1h[1]; + efaddr = v51_l3_addr_dec(efaddr_enc, &is_isdn); + + if (!is_isdn) { + /* EFaddr are structured like isdn-type L3 Address */ + msgb_free(msg); + return -EINVAL; + } + + switch (efaddr) { + case V51_DLADDR_PSTN: + /* hand-over to LAPD-DL instance for PSTN */ + break; + case V51_DLADDR_CTRL: + /* hand-over to LAPD-DL instance for CTRL */ + break; + case V52_DLADDR_BCC: + case V52_DLADDR_PROTECTION: + case V52_DLADDR_LCP: + /* TOOD: implement V5.2 */ + msgb_free(msg); + break; + default: + if (efaddr >= 8176) { + /* reserved as per Section 9.2.2.2 of G.964 */ + msgb_free(msg); + return -EINVAL; + } + /* relay function for LAPD of user ports */ + } +} diff --git a/v51_l1_fsm.c b/v51_l1_fsm.c new file mode 100644 index 0000000..bb4172c --- /dev/null +++ b/v51_l1_fsm.c @@ -0,0 +1,35 @@ +/* ITU-T G.964 Section 14.3.3 V5.1-intterface Layer 1 FSM - AN and LE */ + +/* (C) 2021 by Harald Welte + * + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + + +#include "v5x_internal.h" + +enum v51_l1_fsm_state { + V51_L1FSM_S_LE1_NORMAL, + V51_L1FSM_S_LE2_LOCALLY_DET_FAIL, + V51_L1FSM_S_LE3_REMOTELY_DET_FAIL, + V51_L1FSM_S_LE4_INTERNAL_FAIL, +}; + +/* TODO */ diff --git a/v51_le_ctrl.c b/v51_le_ctrl.c new file mode 100644 index 0000000..aa804f4 --- /dev/null +++ b/v51_le_ctrl.c @@ -0,0 +1,365 @@ +/* (C) 2021 by Harald Welte + * + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include +#include +#include + +#include +#include +#include + +#include "v5x_internal.h" + +/* PSTN Protocol FSM */ +enum v51_le_ctrl_port_fsm_state { + V51_PSTN_PROT_S_OUT_OF_SERVICE, /* LE0 */ + V51_PSTN_PROT_S_NULL /* LE1 */ + V51_PSTN_PROT_S_PATH_INIT_BY_LE /* LE2 */ + V51_PSTN_PROT_S_PATH_INIT_BY_AN /* LE3 */ + V51_PSTN_PROT_S_PATH_ACTIVE /* LE4 */ + V51_PSTN_PROT_S_PATH_DISC_REQ /* LE5 */ + V51_PSTN_PROT_S_PORT_BLOCKED /* LE6 */ +}; + +/*********************************************************************** + * user port CTRL protocol FSM / G.964 Section 14 + ***********************************************************************/ + +enum v51_ctrl_port_fsm_state { + V51_CTRL_PORT_ST_OUT_OF_SERVICE, + V51_CTRL_PORT_ST_IN_SERVICE, + V51_CTRL_PORT_AWAIT_PORT_ACK, +}; + +enum v51_ctrl_port_fsm_event { + V51_CTRL_PE_MDU_START_TRAFFIC, + V51_CTRL_PE_MDU_STOP_TRAFFIC, + V51_CTRL_PE_MDU_CTRL, + V51_CTRL_PE_RX_PORT_CONTROL, + V51_CTRL_PE_RX_PORT_CONTROL_ACK, +}; + +static const struct value_string v51_ctrl_port_fsm_event_names[] = { + { V51_CTRL_PE_MDU_START_TRAFFIC, "MDU-start_traffic" }, + { V51_CTRL_PE_MDU_STOP_TRAFFIC, "MDU-stop_traffic" }, + { V51_CTRL_PE_MDU_CTRL, "MDU-CTRL" }, + { V51_CTRL_PE_RX_PORT_CONTROL, "PORT_CONTROL" }, + { V51_CTRL_PE_RX_PORT_CONTROL_ACK, "PORT_CONTROL_ACK" }, + { 0, NULL } +}; + +static int v51_ctrl_port_fsm_timer_cb(struct osmo_fsm_inst *fi) +{ + if () { + /* first expiry: repeat PORT_CONTROL; re-start T01 */ + } else { + /* second expiry: send MDU-error_ind; go to IN_SERVICE */ + osmo_fsm_inst_state_chg(fi, V51_CTRL_PORT_ST_IN_SERVICE); + } +} + +static const struct osmo_fsm_state v51_ctrl_port_fsm_states[] = { + [V51_CTRL_PORT_ST_OUT_OF_SERVICE] = { + .name = "OUT_OF_SERVICE", + .in_event_mask = S(V51_CTRL_PE_MDU_START_TRAFFIC) | + S(V51_CTRL_PE_MDU_STOP_TRAFFIC) | + S(V51_CTRL_PE_MDU_STOP_TRAFFIC) | + S(V51_CTRL_PE_RX_PORT_CONTROL) | + S(V51_CTRL_PE_RX_PORT_CONTROL_ACK), + .out_state_mask = S(V51_CTRL_PORT_ST_IN_SERVICE), + }, + [V51_CTRL_PORT_ST_IN_SERVICE] = { + .name = "IN_SERVICE", + .in_event_mask = S(V51_CTRL_PE_MDU_START_TRAFFIC) | + S(V51_CTRL_PE_MDU_STOP_TRAFFIC) | + S(V51_CTRL_PE_MDU_STOP_TRAFFIC) | + S(V51_CTRL_PE_RX_PORT_CONTROL), + + .out_state_mask = S(V51_CTRL_PORT_ST_OUT_OF_SERVICE) | + S(V51_CTRL_PORT_AWAIT_PORT_ACK), + }, + [V51_CTRL_PORT_AWAIT_PORT_ACK] = { + .name = "AWAIT_PORT_ACK", + .in_event_mask = S(V51_CTRL_PE_MDU_START_TRAFFIC) | + S(V51_CTRL_PE_MDU_STOP_TRAFFIC) | + S(V51_CTRL_PE_MDU_STOP_TRAFFIC) | + S(V51_CTRL_PE_RX_PORT_CONTROL) | + S(V51_CTRL_PE_RX_PORT_CONTROL_ACK), + .out_state_mask = S(V51_CTRL_PORT_ST_OUT_OF_SERVICE) | + S(V51_CTRL_PORT_ST_IN_SERVICE), + }, +}; + +static struct osmo_fsm v51_ctrl_port_fsm = { + .name = "V51_CTRL_PORT", + .states = v51_ctrl_port_fsm_states, + .num_states = ARRAY_SIZE(v51_ctrl_port_fsm_states), + .allstate_event_mask = 0, + .allstate_action = NULL, + .cleanup = NULL, + .timer_cb = v51_ctrl_port_fsm_timer_cb, + .log_subsys = DV5CTRL, + .event_names = v51_ctrl_port_fsm_event_names, +}; + +/*********************************************************************** + * common CTRL protocol FSM / G.964 Section 14 + ***********************************************************************/ + +enum v51_ctrl_common_fsm_state { + V51_CTRL_COM_ST_OUT_OF_SERVICE, + V51_CTRL_COM_ST_IN_SERVICE, + V51_CTRL_COM_AWAIT_COMMON_ACK, +}; + +enum v51_ctrl_common_fsm_event { + V51_CTRL_CE_MDU_START_TRAFFIC, + V51_CTRL_CE_MDU_STOP_TRAFFIC, + V51_CTRL_CE_MDU_CTRL, + V51_CTRL_CE_RX_COMMON_CONTROL, + V51_CTRL_CE_RX_COMMON_CONTROL_ACK, +}; + +static const struct value_string v51_ctrl_common_fsm_event_names[] = { + { V51_CTRL_CE_MDU_START_TRAFFIC, "MDU-start_traffic" }, + { V51_CTRL_CE_MDU_STOP_TRAFFIC, "MDU-stop_traffic" }, + { V51_CTRL_CE_MDU_CTRL, "MDU-CTRL" }, + { V51_CTRL_CE_RX_COMMON_CONTROL, "COMMON_CONTROL" }, + { V51_CTRL_CE_RX_COMMON_CONTROL_ACK, "COMMON_CONTROL_ACK" }, + { 0, NULL } +}; + +static int v51_ctrl_common_fsm_timer_cb(struct osmo_fsm_inst *fi) +{ + if () { + /* first expiry: repeat COMMON_CONTROL; re-start T02 */ + } else { + /* second expiry: send MDU-error_ind; go to IN_SERVICE */ + osmo_fsm_inst_state_chg(fi, V51_CTRL_COM_ST_IN_SERVICE); + } +} + +static const struct osmo_fsm_state v51_ctrl_common_fsm_states[] = { + [V51_CTRL_COM_ST_OUT_OF_SERVICE] = { + .name = "OUT_OF_SERVICE", + .in_event_mask = S(V51_CTRL_CE_MDU_START_TRAFFIC) | + S(V51_CTRL_CE_MDU_STOP_TRAFFIC) | + S(V51_CTRL_CE_MDU_CTRL) | + S(V51_CTRL_CE_RX_COMMON_CONTROL) | + S(V51_CTRL_CE_RX_COMMON_CONTROL_ACK), + .out_state_mask = S(V51_CTRL_COM_ST_IN_SERVICE), + }, + [V51_CTRL_COM_ST_IN_SERVICE] = { + .name = "IN_SERVICE", + .in_event_mask = S(V51_CTRL_CE_MDU_START_TRAFFIC) | + S(V51_CTRL_CE_MDU_STOP_TRAFFIC) | + S(V51_CTRL_CE_MDU_CTRL) | + S(V51_CTRL_CE_RX_COMMON_CONTROL), + .out_state_mask = S(V51_CTRL_COM_ST_OUT_OF_SERVICE) | + S(V51_CTRL_COM_AWAIT_COMMON_ACK), + }, + [V51_CTRL_COM_AWAIT_COMMON_ACK] = { + .name = "AWAIT_COMMON_ACK", + .in_event_mask = S(V51_CTRL_CE_MDU_START_TRAFFIC) | + S(V51_CTRL_CE_MDU_STOP_TRAFFIC) | + S(V51_CTRL_CE_MDU_CTRL) | + S(V51_CTRL_CE_RX_COMMON_CONTROL) | + S(V51_CTRL_CE_RX_COMMON_CONTROL_ACK), + .out_state_mask = S(V51_CTRL_COM_ST_OUT_OF_SERVICE) | + S(V51_CTRL_COM_ST_IN_SERVICE), + }, +}; + +static struct osmo_fsm v51_ctrl_common_fsm = { + .name = "V51_CTRL_COMMON", + .states = v51_ctrl_common_fsm_states, + .num_states = ARRAY_SIZE(v51_ctrl_common_fsm_states), + .allstate_event_mask = 0, + .allstate_action = NULL, + .cleanup = NULL, + .timer_cb = v51_ctrl_common_fsm_timer_cb, + .log_subsys = DV5CTRL, + .event_names = v51_ctrl_common_fsm_event_names, +}; + + +/*********************************************************************** + * V5 Message encoding / sending + ***********************************************************************/ + +/* G.964 Section 14.4.1.1 / Table 48 */ +struct mgsb *v51_enc_ctrl_port(struct v5x_user_port *v5up, enum v51_ctrl_func_el cfe) +{ + uint8_t cfe_ie = cfe; + struct v51_l3_hdr *l3h; + struct msgb *msg = msgb_alloc_v5x(); + if (!msg) + return NULL; + + l3h = (struct v51_l3_hdr *) msgb_put(msg, sizeof(*l3h)); + l3h->pdisc = V51_CTRL_PDISC; + l3h->l3_addr = v51_l3_addr_enc(v5up->nr, true); + l3h->msg_type = V51_CTRL_MSGT_PORT_CTRL; + + msgb_tlv_put(msg, V51_CTRL_IEI_CTRL_F_ELEMENT, 1, &cfe_ie); + + return msg; +} + +/* G.964 Section 14.4.1.2 / Table 49 */ +static struct msgb *v51_enc_ctrl_port_ack(struct v5x_user_port *v5up, enum v51_ctrl_func_el cfe) +{ + uint8_t cfe_ie = cfe; + struct v51_l3_hdr *l3h; + struct msgb *msg = msgb_alloc_v5x(); + if (!msg) + return NULL; + + l3h = (struct v51_l3_hdr *) msgb_put(msg, sizeof(*l3h)); + l3h->pdisc = V51_CTRL_PDISC; + l3h->l3_addr = v51_l3_addr_enc(v5up->nr, true); + l3h->msg_type = V51_CTRL_MSGT_PORT_CTRL_ACK; + + msgb_tlv_put(msg, V51_CTRL_IEI_CTRL_F_ELEMENT, 1, &cfe_ie); + + return msg; +} + +/* G.964 Section 14.4.1.3 / Table 50 */ +static struct msgb *v51_enc_ctrl_common(struct v5x_instance *v5i, enum v51_ctrl_func_id cfi, + uint8_t *rej_cause, uint8_t *variant, uint32_t *interface_id) +{ + uint8_t cfi_ie = cfi; + struct v51_l3_hdr *l3h; + struct msgb *msg = msgb_alloc_v5x(); + if (!msg) + return NULL; + + l3h = (struct v51_l3_hdr *) msgb_put(msg, sizeof(*l3h)); + l3h->pdisc = V51_CTRL_PDISC; + l3h->l3_addr = v51_l3_addr_enc(V51_DLADDR_CTRL, true); + l3h->msg_type = V51_CTRL_MSGT_COMMON_CTRL; + + msgb_tlv_put(msg, V51_CTRL_IEI_CTRL_F_ID, 1, &cfi_ie); + if (variant) { + /* Conditional: Variant */ + msgb_tlv_put(msg, V51_CTRL_IEI_VARIANT, 1, variant); + } + if (rej_cause) { + /* Conditional: Rejection Cause */ + msgb_put_u8(0xF0 | (*rej_cause & 0x0F)); + } + if (interface_id) { + /* Conditional: Interface Id */ + uint8_t iid_ie[3]; + osmo_store32be_ext(*interface_id, iid_ie, 3); + msgb_tlv_put(msg, V51_CTRL_IEI_INTERFACE_ID, sizeof(iid_ie), iid_ie); + } + + return msg; +} + +/* G.964 Section 14.4.1.4 / Table 51 */ +static struct msgb *v51_enc_ctrl_common_ack(struct v5x_instance *v5i, enum v51_ctrl_func_id cfi) +{ + uint8_t cfi_ie = cfi; + struct v51_l3_hdr *l3h; + struct msgb *msg = msgb_alloc_v5x(); + if (!msg) + return NULL; + + l3h = (struct v51_l3_hdr *) msgb_put(msg, sizeof(*l3h)); + l3h->pdisc = V51_CTRL_PDISC; + l3h->l3_addr = v51_l3_addr_enc(V51_DLADDR_CTRL, true); + l3h->msg_type = V51_CTRL_MSGT_COMMON_CTRL_ACK; + + msgb_tlv_put(msg, V51_CTRL_IEI_CTRL_F_ID, 1, &cfi_ie); + + return msg; +} + +/*********************************************************************** + * V5 Message receiving / decoding + ***********************************************************************/ + +static int v51_rcv_ctrl_port(struct v5x_user_port *v5up, struct msgb *msg, const struct tlv_parsed *tp) +{ + uint16_t l3_addr; + enum v51_ctrl_func_el cfe = *TLVP_VAL(tp, V51_CTRL_IEI_CTRL_F_ELEMENT); + + switch (l3h->msg_type) { + case V51_CTRL_MSGT_PORT_CTRL: + /* FIXME: send event to FSM */ + /* send ACK to AN */ + return v51_tx(v5up->inst, v51_enc_ctrl_port_ack(v5up, cfe)); + case V51_CTRL_MSGT_PORT_CTRL_ACK: + default: + return -EINVAL; + } +} + +static int v51_rcv_ctrl_common(struct v5x_instance *v5i, struct msgb *msg, const struct tlv_parsed *tp) +{ + uint16_t l3_addr; + enum v51_ctrl_func_id cfi = *TLVP_VAL(tp, V51_CTRL_IEI_CTRL_F_ID); + + switch (l3h->msg_type) { + case V51_CTRL_MSGT_COMMON_CTRL: + /* FIXME: send event to FSM */ + /* send ACK to AN */ + return v51_tx(v5up->inst, v51_enc_ctrl_common_ack(v5i, cfi)); + case V51_CTRL_MSGT_COMMON_CTRL_ACK: + default: + return -EINVAL; + } +} + + +static int v51_rcv_ctrl(struct v5x_instance *v5i, struct msgb *msg, const struct tlv_parsed *tp) +{ + struct v51_l3_hdr *l3h = msgb_l3(msg); + uint16_t l3_addr; + bool is_isdn; + int rc; + + l3_addr = v51_l3_addr_dec(l3h->l3_addr, &is_isdn); + if (!is_isdn) + return -EINVAL; + + switch (l3h->msg_type) { + case V51_CTRL_MSGT_PORT_CTRL: + case V51_CTRL_MSGT_PORT_CTRL_ACK: + v5up = v5x_user_port_find(v5i, l4_addr); + if (!v5up) + return -ENODEV; + return v5x_rcv_ctrl_port(v5up, msg, tp); + case V51_CTRL_MSGT_COMMON_CTRL: + case V51_CTRL_MSGT_COMMON_CTRL_ACK: + if (l3_addr != V51_DLADDR_CTRL) + return -EINVAL; + /* TODO */ + return v5x_rcv_ctrl_common(v5i, msg, tp); + } + + return rc; +} diff --git a/v52_lcp_fsm.c b/v52_lcp_fsm.c new file mode 100644 index 0000000..271bf78 --- /dev/null +++ b/v52_lcp_fsm.c @@ -0,0 +1,612 @@ +/* ITu-T G.965 Section 16.2 V5.2-interface Link control FSM - LE side */ + +#include "v5x_internal.h" + +/* 16.2.4.2.2 */ +enum v52_lcp_fsm_state { + V52_LCPFSM_S_LE01_NOP_LINK_FAILURE, + V52_LCPFSM_S_LE02_NOP_LINK_FAILURE_AND_BLOCKED, + V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED, + V52_LCPFSM_S_LE11_NOP_LOCAL_LINK_UNBLOCK, + V52_LCPFSM_S_LE12_NOP_REMOTE_LINK_UNBLOCK, + V52_LCPFSM_S_LE20_OP_OPERATIONAL, + V52_LCPFSM_S_LE21_OP_REMOTE_LINK_ID, + V52_LCPFSM_S_LE22_OP_LOCAL_LINK_ID, +}; + +enum v52_lcp_event { + V52_LCPFSM_E_MPH_AI, /* Activate Indication (L1 link operational) */ + V52_LCPFSM_E_MPH_DI, /* Deactivate Indication (L1 link not operational) */ + V52_LCPFSM_E_MDU_IDReq, /* Identification Request */ + V52_LCPFSM_E_FE_IDAck, + V52_LCPFSM_E_MPH_IDI, /* Identification Indication */ + V52_LCPFSM_E_MPH_EIg, /* Identification Failure */ + V52_LCPFSM_E_FE_IDReq, + V52_LCPFSM_E_MDU_IDAck, /* Send Link ID ACK */ + V52_LCPFSM_E_FE_IDRel, + V52_LCPFSM_E_MDU_IDRej, /* Link ID Reject */ + V52_LCPFSM_E_FE_IDRej, + V52_LCPFSM_E_MDU_LUBR, /* Link Unblock Request */ + V52_LCPFSM_E_MDU_LBI, /* Link Block Indication */ + V52_LCPFSM_E_FE302, /* AN Initiated unblocking */ + V52_LCPFSM_E_FE304, /* AN initiated link block */ + V52_LCPFSM_E_FE305, /* deferred link block request */ + V52_LCPFSM_E_FE306, /* non-deferred link block request */ +}; + + + + +static struct msgb *v51_enc_link_control(uint8_t link_id, enum v52_link_ctrl_func lcf) +{ + struct v51_l3_hdr *l3h; + uint8_t lcf_enc = lcf; + struct msgb *msg = msgb_alloc_v5x(); + if (!msg) + return NULL; + + l3h = (struct v51_l3_hdr *) msgb_put(msg, sizeof(*l3h)); + l3h->pdisc = V51_LCP_PDISC; + l3h->l3_addr = link_id + l3h->msg_type = V52_CTRL_MSGT_LCP_LINK_CTRL; + + msgb_tlv_put(msg, V52_CTRL_IEI_LCP_LINK_CTRL_FUNCTION, 1, &lcf_enc); + + return msg; +} + +static struct msgb *v51_enc_link_control_ack(uint8_t link_id, enum v52_link_ctrl_func lcf) +{ + struct v51_l3_hdr *l3h; + uint8_t lcf_enc = lcf; + struct msgb *msg = msgb_alloc_v5x(); + if (!msg) + return NULL; + + l3h = (struct v51_l3_hdr *) msgb_put(msg, sizeof(*l3h)); + l3h->pdisc = V51_LCP_PDISC; + l3h->l3_addr = link_id; + l3h->msg_type = V52_CTRL_MSGT_LCP_LINK_CTRL_ACK; + + msgb_tlv_put(msg, V52_CTRL_IEI_LCP_LINK_CTRL_FUNCTION, 1, &lcf_enc); + + return msg; +} + +static void lcp_fsm_le01_link_failure(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case V52_LCPFSM_E_MPH_AI: + /* TODO: Send MDU-LAI */ + osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE20_OP_OPERATIONAL, 0, 0); + break; + case V52_LCPFSM_E_MPH_DI: + /* ignore */ + break; + case V52_LCPFSM_E_MDU_IDReq: + /* TODO: Send MDU-DI */ + break; + case V52_LCPFSM_E_FE_IDReq: + /* TODO: Send FE303 */ + osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE02_NOP_LINK_FAILURE_AND_BLOCKED, 0, 0); + break; + case V52_LCPFSM_E_FE_IDRel: + case V52_LCPFSM_E_FE_IDRej: + /* ignore */ + break; + case V52_LCPFSM_E_MDU_LUBR: + /* TODO: Send MDU-DI */ + /* TODO: Send FE303 */ + osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE02_NOP_LINK_FAILURE_AND_BLOCKED, 0, 0); + break; + case V52_LCPFSM_E_MDU_LBI: + case V52_LCPFSM_E_FE302: + case V52_LCPFSM_E_FE305: + case V52_LCPFSM_E_FE306: + /* TODO: Send FE303 */ + osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE02_NOP_LINK_FAILURE_AND_BLOCKED, 0, 0); + break; + case V52_LCPFSM_E_FE304: + osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE02_NOP_LINK_FAILURE_AND_BLOCKED, 0, 0); + break; + default: + OSMO_ASSERT(0); + } +} + +static void lcp_fsm_le02_link_failure_blocked(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case V52_LCPFSM_E_MPH_AI: + /* TODO: Send MDU-LAI */ + /* TODO: Send MDU-LBl */ + osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED, 0, 0); + break; + case V52_LCPFSM_E_MPH_DI: + /* ignore */ + break; + case V52_LCPFSM_E_MDU_IDReq: + /* TODO: Send MDU-DI */ + break; + case V52_LCPFSM_E_FE_IDReq: + /* TODO: Send FE303 */ + break; + case V52_LCPFSM_E_MDU_LUBR: + /* TODO: Send MDU-DI */ + /* TODO: Send FE303 */ + break; + case V52_LCPFSM_E_MDU_LBI: + case V52_LCPFSM_E_FE302: + case V52_LCPFSM_E_FE305: + case V52_LCPFSM_E_FE306: + /* TODO: Send FE303 */ + break; + case V52_LCPFSM_E_FE304: + /* ignore */ + break; + default: + OSMO_ASSERT(0); + } +} + +static void lcp_fsm_le10_link_blocked(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case V52_LCPFSM_E_MPH_AI: + /* TODO: Send MDU-LAI */ + break; + case V52_LCPFSM_E_MPH_DI: + /* TODO: Send MDU-DI */ + osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE02_NOP_LINK_FAILURE_AND_BLOCKED, 0, 0); + break; + case V52_LCPFSM_E_MDU_IDReq: + /* TODO: Send MDU-LBl */ + break; + case V52_LCPFSM_E_FE_IDReq: + /* TODO: Send FE303 */ + break; + case V52_LCPFSM_E_FE_IDRel: + case V52_LCPFSM_E_FE_IDRej: + /* ignore */ + break; + case V52_LCPFSM_E_MDU_LUBR: + /* TODO: Send FE303 */ + osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE11_NOP_LOCAL_LINK_UNBLOCK, 0, 0); + break; + case V52_LCPFSM_E_FE302: + /* TODO: Send MDL-LUBR */ + osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE12_NOP_REMOTE_LINK_UNBLOCK, 0, 0); + break; + case V52_LCPFSM_E_MDU_LBI: + case V52_LCPFSM_E_FE305: + case V52_LCPFSM_E_FE306: + /* TODO: Send FE303 */ + osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE02_NOP_LINK_FAILURE_AND_BLOCKED, 0, 0); + break; + case V52_LCPFSM_E_FE304: + break; + default: + OSMO_ASSERT(0); + } + +} + +static void lcp_fsm_le11_local_link_unblock(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case V52_LCPFSM_E_MPH_AI: + /* ignore */ + break; + case V52_LCPFSM_E_MPH_DI: + /* TODO: Send MDU-DI */ + osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE02_NOP_LINK_FAILURE_AND_BLOCKED, 0, 0); + break; + case V52_LCPFSM_E_MDU_IDReq: + /* TODO: Send MDU-LBl */ + osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED, 0, 0); + break; + case V52_LCPFSM_E_FE_IDReq: + /* TODO: Send FE-IDRej */ + break; + case V52_LCPFSM_E_FE_IDRel: + case V52_LCPFSM_E_FE_IDRej: + /* ignore */ + break; + case V52_LCPFSM_E_MDU_LUBR: + /* TODO: Send FE301 */ + break; + case V52_LCPFSM_E_MDU_LBI: + /* TODO: Send FE303 */ + osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED, 0, 0); + break; + case V52_LCPFSM_E_FE302: + /* TODO: Send MDU-LUBl */ + osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE20_OP_OPERATIONAL, 0, 0); + break; + case V52_LCPFSM_E_FE304: + /* TODO: Send MDU-LBl */ + osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED, 0, 0); + break; + case V52_LCPFSM_E_FE305: + case V52_LCPFSM_E_FE306: + /* TODO: Send FE303 */ + /* TODO: Send MDU-LBl */ + osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED, 0, 0); + break; + default: + OSMO_ASSERT(0); + } +} + +static void lcp_fsm_le12_remote_link_unblock(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case V52_LCPFSM_E_MPH_AI: + /* ignore */ + break; + case V52_LCPFSM_E_MPH_DI: + /* TODO: Send MDU-Dl */ + osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE02_NOP_LINK_FAILURE_AND_BLOCKED, 0, 0); + break; + case V52_LCPFSM_E_MDU_IDReq: + /* TODO: Send MDU-LUBR */ + /* TODO: Send MDU-IDRej */ + break; + case V52_LCPFSM_E_FE_IDReq: + /* TODO: Send FE-IDRej */ + break; + case V52_LCPFSM_E_MDU_LUBR: + /* TODO: Send FE301 */ + /* TODO: Send MDU-LUBl */ + break; + case V52_LCPFSM_E_MDU_LBI: + /* TODO: Send FE303 */ + osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE20_OP_OPERATIONAL, 0, 0); + break; + case V52_LCPFSM_E_FE302: + /* TODO: Send MDU-LUBR */ + break; + case V52_LCPFSM_E_FE304: + /* TODO: Send MDU-LBl */ + osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED, 0, 0); + break; + case V52_LCPFSM_E_FE305: + case V52_LCPFSM_E_FE306: + /* TODO: Send FE303 */ + /* TODO: Send MDU-LBl */ + osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED, 0, 0); + break; + default: + OSMO_ASSERT(0); + } +} + +static void lcp_fsm_le20_operational(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case V52_LCPFSM_E_MPH_AI: + /* ignore */ + break; + case V52_LCPFSM_E_MPH_DI: + /* TODO: Send MDU-DI */ + osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE01_NOP_LINK_FAILURE, 0, 0); + break; + case V52_LCPFSM_E_MDU_IDReq: + /* TODO: Send FE-IDReq */ + osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE22_OP_LOCAL_LINK_ID, 0, 0); + break; + case V52_LCPFSM_E_FE_IDReq: + /* TODO: Send MDU-IDReq */ + break; + case V52_LCPFSM_E_MDU_IDAck: + /* TODO: Send MPH-ID */ + /* TODO: Send FE-IDAck */ + osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE21_OP_REMOTE_LINK_ID, 0, 0); + break; + case V52_LCPFSM_E_MDU_IDRej: + /* TODO: Send FE-IDRej */ + break; + case V52_LCPFSM_E_MDU_LUBR: + /* TODO: Send FE301 */ + break; + case V52_LCPFSM_E_MDU_LBI: + /* TODO: Send FE303 */ + osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED, 0, 0); + break; + case V52_LCPFSM_E_FE302: + /* TODO: Send MDU-LUBI */ + break; + case V52_LCPFSM_E_FE304: + /* TODO: Send MDU-LBI */ + osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED, 0, 0); + break; + case V52_LCPFSM_E_FE305: + /* TODO: Send MDU-LBR */ + break; + case V52_LCPFSM_E_FE306: + /* TODO: Send MDU-LBRN */ + break; + default: + OSMO_ASSERT(0); + } + +} + +static void lcp_fsm_le21_op_remote_link_id(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case V52_LCPFSM_E_MPH_AI: + /* ignore */ + break; + case V52_LCPFSM_E_MPH_DI: + /* TODO: Send MDU-DI */ + /* TODO: Send MPH-NOR */ + osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE01_NOP_LINK_FAILURE, 0, 0); + break; + case V52_LCPFSM_E_MDU_IDReq: + /* TODO: Send MDU-IDRej */ + break; + case V52_LCPFSM_E_MDU_IDAck: + /* ignore */ + break; + case V52_LCPFSM_E_FE_IDRel: + /* TODO: Send MDU-IDRel */ + /* TODO: Sen MPH-NOR */ + osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE20_OP_OPERATIONAL, 0, 0); + break; + case V52_LCPFSM_E_MDU_IDRej: + /* TODO: Send FE-IDRej */ + /* TODO: Sen MPH-NOR */ + osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE20_OP_OPERATIONAL, 0, 0); + break; + case V52_LCPFSM_E_MDU_LUBR: + /* TODO: Send FE301 */ + /* TODO: Sen MPH-NOR */ + osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE20_OP_OPERATIONAL, 0, 0); + break; + case V52_LCPFSM_E_MDU_LBI: + /* TODO: Send FE303 */ + /* TODO: Sen MPH-NOR */ + osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED, 0, 0); + break; + case V52_LCPFSM_E_FE302: + /* TODO: Send MDU-IDRel */ + /* TODO: Send MDU-LUBI */ + /* TODO: Sen MPH-NOR */ + osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE20_OP_OPERATIONAL, 0, 0); + break; + case V52_LCPFSM_E_FE304: + /* TODO: Send MDU-LBI */ + /* TODO: Sen MPH-NOR */ + osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED, 0, 0); + break; + case V52_LCPFSM_E_FE305: + /* TODO: Send MDU-LBR */ + break; + case V52_LCPFSM_E_FE306: + /* TODO: Send MDU-LBRN */ + break; + default: + OSMO_ASSERT(0); + } +} + +static void lcp_fsm_le22_op_local_link_id(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case V52_LCPFSM_E_MPH_AI: + /* ignore */ + break; + case V52_LCPFSM_E_MPH_DI: + /* TODO: Send MDU-DI */ + /* TODO: Send FE-IdRel */ + osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE01_NOP_LINK_FAILURE, 0, 0); + break; + case V52_LCPFSM_E_MDU_IDReq: + /* ignore */ + break; + case V52_LCPFSM_E_MDU_IDAck: + /* TODO: Send MPH-IDR */ + break; + case V52_LCPFSM_E_MPH_IDI: + /* TODO: Send MDU-AI */ + /* TODO: Send FE-IDRel */ + osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE20_OP_OPERATIONAL, 0, 0); + break; + case V52_LCPFSM_E_MPH_EIg: + /* TODO: Send FE-IDRel */ + /* TODO: Send MDU-ELg */ + osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE20_OP_OPERATIONAL, 0, 0); + break; + case V52_LCPFSM_E_FE_IDRej: + /* TODO: Send MDU-IDRej */ + osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE20_OP_OPERATIONAL, 0, 0); + break; + case V52_LCPFSM_E_MDU_LUBR: + /* TODO: Send FE301 */ + osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE20_OP_OPERATIONAL, 0, 0); + break; + case V52_LCPFSM_E_MDU_LBI: + /* TODO: Send FE303 */ + osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED, 0, 0); + break; + case V52_LCPFSM_E_FE302: + /* TODO: Send MDU-IDRej */ + /* TODO: Send MDU-LUBI */ + osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE20_OP_OPERATIONAL, 0, 0); + break; + case V52_LCPFSM_E_FE304: + /* TODO: Send MDU-LBI */ + osmo_fsm_inst_state_chg(fi, V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED, 0, 0); + break; + case V52_LCPFSM_E_FE305: + /* TODO: Send MDU-LBR */ + break; + case V52_LCPFSM_E_FE306: + /* TODO: Send MDU-LBRN */ + break; + default: + OSMO_ASSERT(0); + } +} + +/* Table 17G.965 */ +static const struct osmo_fsm_state v52_lcp_le_fsm_states[] = { + [V52_LCPFSM_S_LE01_NOP_LINK_FAILURE] = { + .name = "LE0.1_LINK_FAILURE", + .in_event_mask = S(V52_LCPFSM_E_MPH_AI) | + S(V52_LCPFSM_E_MPH_DI) | + S(V52_LCPFSM_E_MDU_IDReq) | + S(V52_LCPFSM_E_FE_IDReq) | + S(V52_LCPFSM_E_FE_IDRel) | + S(V52_LCPFSM_E_FE_IDRej) | + S(V52_LCPFSM_E_MDU_LUBR) | + S(V52_LCPFSM_E_MDU_LBI) | + S(V52_LCPFSM_E_FE302) | + S(V52_LCPFSM_E_FE304) | + S(V52_LCPFSM_E_FE305) | + S(V52_LCPFSM_E_FE306), + .out_state_mask = S(V52_LCPFSM_S_LE20_OP_OPERATIONAL) | + S(V52_LCPFSM_S_LE02_NOP_LINK_FAILURE_AND_BLOCKED), + .action = lcp_fsm_le01_link_failure, + }, + [V52_LCPFSM_S_LE02_NOP_LINK_FAILURE_AND_BLOCKED] = { + .name = "LE0.2_LINK_FAILURE_AND_BLOCKED", + .in_event_mask = S(V52_LCPFSM_E_MPH_AI) | + S(V52_LCPFSM_E_MPH_DI) | + S(V52_LCPFSM_E_MDU_IDReq) | + S(V52_LCPFSM_E_FE_IDReq) | + S(V52_LCPFSM_E_MDU_LUBR) | + S(V52_LCPFSM_E_MDU_LBI) | + S(V52_LCPFSM_E_FE302) | + S(V52_LCPFSM_E_FE304) | + S(V52_LCPFSM_E_FE305) | + S(V52_LCPFSM_E_FE306), + .out_state_mask = S(V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED), + .action = lcp_fsm_le02_link_failure_blocked, + }, + [V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED] = { + .name = "LE1.0_NOP_LINK_BLOCKED", + .in_event_mask = S(V52_LCPFSM_E_MPH_AI) | + S(V52_LCPFSM_E_MPH_DI) | + S(V52_LCPFSM_E_MDU_IDReq) | + S(V52_LCPFSM_E_FE_IDReq) | + S(V52_LCPFSM_E_FE_IDRel) | + S(V52_LCPFSM_E_FE_IDRej) | + S(V52_LCPFSM_E_MDU_LUBR) | + S(V52_LCPFSM_E_MDU_LBI) | + S(V52_LCPFSM_E_FE302) | + S(V52_LCPFSM_E_FE304) | + S(V52_LCPFSM_E_FE305) | + S(V52_LCPFSM_E_FE306), + .out_state_mask = S(V52_LCPFSM_S_LE02_NOP_LINK_FAILURE_AND_BLOCKED) | + S(V52_LCPFSM_S_LE11_NOP_LOCAL_LINK_UNBLOCK) | + S(V52_LCPFSM_S_LE21_OP_REMOTE_LINK_ID), + .action = lcp_fsm_le10_link_blocked, + }, + [V52_LCPFSM_S_LE11_NOP_LOCAL_LINK_UNBLOCK] = { + .name = "LE1.1_NOP_LOCAL_LINK_UNBLOCK", + .in_event_mask = S(V52_LCPFSM_E_MPH_AI) | + S(V52_LCPFSM_E_MPH_DI) | + S(V52_LCPFSM_E_MDU_IDReq) | + S(V52_LCPFSM_E_FE_IDReq) | + S(V52_LCPFSM_E_FE_IDRel) | + S(V52_LCPFSM_E_FE_IDRej) | + S(V52_LCPFSM_E_MDU_LUBR) | + S(V52_LCPFSM_E_MDU_LBI) | + S(V52_LCPFSM_E_FE302) | + S(V52_LCPFSM_E_FE304) | + S(V52_LCPFSM_E_FE305) | + S(V52_LCPFSM_E_FE306), + .out_state_mask = S(V52_LCPFSM_S_LE02_NOP_LINK_FAILURE_AND_BLOCKED) | + S(V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED) | + S(V52_LCPFSM_S_LE20_OP_OPERATIONAL), + .action = lcp_fsm_le11_local_link_unblock, + }, + [V52_LCPFSM_S_LE12_NOP_REMOTE_LINK_UNBLOCK] = { + .name = "LE1.2_NOP_REMOTE_LINK_UNBLOCK", + .in_event_mask = S(V52_LCPFSM_E_MPH_AI) | + S(V52_LCPFSM_E_MPH_DI) | + S(V52_LCPFSM_E_MDU_IDReq) | + S(V52_LCPFSM_E_FE_IDReq) | + S(V52_LCPFSM_E_MDU_LUBR) | + S(V52_LCPFSM_E_MDU_LBI) | + S(V52_LCPFSM_E_FE302) | + S(V52_LCPFSM_E_FE304) | + S(V52_LCPFSM_E_FE305) | + S(V52_LCPFSM_E_FE306), + .out_state_mask = S(V52_LCPFSM_S_LE02_NOP_LINK_FAILURE_AND_BLOCKED) | + S(V52_LCPFSM_S_LE20_OP_OPERATIONAL) | + S(V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED), + .action = lcp_fsm_le12_remote_link_unblock, + }, + [V52_LCPFSM_S_LE20_OP_OPERATIONAL] = { + .name = "LE2.0_OP_OPERATIONAL", + .in_event_mask = S(V52_LCPFSM_E_MPH_AI) | + S(V52_LCPFSM_E_MPH_DI) | + S(V52_LCPFSM_E_MDU_IDReq) | + S(V52_LCPFSM_E_FE_IDReq) | + S(V52_LCPFSM_E_MDU_IDAck) | + S(V52_LCPFSM_E_MDU_IDRej) | + S(V52_LCPFSM_E_MDU_LUBR) | + S(V52_LCPFSM_E_MDU_LBI) | + S(V52_LCPFSM_E_FE302) | + S(V52_LCPFSM_E_FE304) | + S(V52_LCPFSM_E_FE305) | + S(V52_LCPFSM_E_FE306), + .out_state_mask = S(V52_LCPFSM_S_LE01_NOP_LINK_FAILURE) | + S(V52_LCPFSM_S_LE22_OP_LOCAL_LINK_ID) | + S(V52_LCPFSM_S_LE21_OP_REMOTE_LINK_ID) | + S(V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED), + .action = lcp_fsm_le20_operational, + }, + [V52_LCPFSM_S_LE21_OP_REMOTE_LINK_ID] = { + .name = "LE2.1_OP_REMOTE_LINK_ID", + .in_event_mask = S(V52_LCPFSM_E_MPH_AI) | + S(V52_LCPFSM_E_MPH_DI) | + S(V52_LCPFSM_E_MDU_IDReq) | + S(V52_LCPFSM_E_FE_IDReq) | + S(V52_LCPFSM_E_MDU_IDAck) | + S(V52_LCPFSM_E_MDU_IDRel) | + S(V52_LCPFSM_E_MDU_IDRej) | + S(V52_LCPFSM_E_MDU_LUBR) | + S(V52_LCPFSM_E_MDU_LBI) | + S(V52_LCPFSM_E_FE302) | + S(V52_LCPFSM_E_FE304) | + S(V52_LCPFSM_E_FE305) | + S(V52_LCPFSM_E_FE306), + .out_state_mask = S(V52_LCPFSM_S_LE01_NOP_LINK_FAILURE) | + S(V52_LCPFSM_S_LE20_OP_OPERATIONAL) | + S(V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED), + .action = lcp_fsm_le21_op_remote_link_id, + }, + [V52_LCPFSM_S_LE22_OP_LOCAL_LINK_ID] = { + .name = "LE2.2_OP_LOCAL_LINK_ID", + .in_event_mask = S(V52_LCPFSM_E_MPH_AI) | + S(V52_LCPFSM_E_MPH_DI) | + S(V52_LCPFSM_E_MDU_IDReq) | + S(V52_LCPFSM_E_MDU_IDAck) | + S(V52_LCPFSM_E_MPH_IDI) | + S(V52_LCPFSM_E_MPH_EIg) | + S(V52_LCPFSM_E_FE_IDReq) | + S(V52_LCPFSM_E_MDU_LUBR) | + S(V52_LCPFSM_E_MDU_LBI) | + S(V52_LCPFSM_E_FE302) | + S(V52_LCPFSM_E_FE304) | + S(V52_LCPFSM_E_FE305) | + S(V52_LCPFSM_E_FE306), + .out_state_mask = S(V52_LCPFSM_S_LE01_NOP_LINK_FAILURE) | + S(V52_LCPFSM_S_LE20_OP_OPERATIONAL) | + S(V52_LCPFSM_S_LE10_NOP_LINK_BLOCKED), + .action =lcp_fsm_le22_op_local_link_id, + }, +}; + +struct osmo_fsm v52_lcp_le_fsm = { + .name = "V52_LCP_LE", + .states = v52_lcp_le_fsm_states, + .num_states = ARRAY_SIZE(v52_lcp_le_fsm_states), + .timer_cb = NULL, + .log_subsys = 0, + .event_names = v52_lcp_le_fsm_event_names, +}; diff --git a/v52_le_user_port_fsm.c b/v52_le_user_port_fsm.c new file mode 100644 index 0000000..77499f2 --- /dev/null +++ b/v52_le_user_port_fsm.c @@ -0,0 +1,448 @@ +/* ITU-T G.964 Section 14.1.3.2.2 ISDN user port FSM - LE */ + +/***********************************************************************/ +/* internal data structures */ +/***********************************************************************/ + +#include + +#include "v5x_internal.h" + + +/***********************************************************************/ +/* state names, event names, primitives, ... */ +/***********************************************************************/ + +/* Table 35/G.964 */ +enum v51_mph_prim { + V51_LE_MPH_UB, /* Unblock (req, ind)*/ + V51_LE_MPH_B, /* Block (req, ind) */ + V51_LE_MPH_A, /* Activate (req, ind) */ + V51_LE_MPH_AW, /* Access activation by user (ind) */ + V51_LE_MPH_DSA, /* DS Activated (ind) */ + V51_LE_MPH_D, /* Deactivate (req, ind) */ + V51_LE_MPH_G, /* Grading Information */ + V51_LE_MPH_DB, /* Block D-Channel from user port */ + V51_LE_MPH_DU, /* Unblock D-Channel from user port */ +}; + +/* 14.1.3.2.2 ISDN user port FSM - LE(ISDN port) */ +enum v51_le_isdn_port_state { + V51_LE_UP_I_S_LE10_NOP_BLOCKED, /* LE1.0 */ + V51_LE_UP_I_S_LE11_NOP_LOCAL_UNBLOCK, /* LE1.1 */ + V51_LE_UP_I_S_LE12_NOP_REMOTE_UNBLOCK, /* LE1.2 */ + V51_LE_UP_I_S_LE20_OP_OPERATIONAL_DEACTIVTED, /* LE2.0 */ + V51_LE_UP_I_S_LE21_OP_ACTIVATION_INITIATED, /* LE2.1 */ + V51_LE_UP_I_S_LE22_OP_ACCESS_ACTIVATED, /* LE2.2 */ +}; + +/* 14.2.3.2.2 PSTN user port FSM - LE(PSTN port) */ +enum v51_le_pstn_port_state { + V51_LE_UP_P_S_LE10_NOP_BLOCKED, /* LE1.0 */ + V51_LE_UP_P_S_LE11_NOP_LOCAL_UNBLOCK, /* LE1.1 */ + V51_LE_UP_P_S_LE12_NOP_REMOTE_UNBLOCK, /* LE1.2 */ + V51_LE_UP_P_S_LE22_OPERATIONAL, /* LE2.0 */ +}; + +enum v51_ctrl_le_port_fsm_event { + /* inbound function elements from AN (Table 34/G.964) */ + V51_CUP_LE_FE102_ACTIV_INIT_USER_IND, + V51_CUP_LE_FE103_DS_ACTIVATED_IND, + V51_CUP_LE_FE104_ACCESS_ACTIVATED_IND, + V51_CUP_LE_FE106_ACCESS_DEACTIVATED_IND, + V51_CUP_LE_FE202_UNBLOCK_REQ, + V51_CUP_LE_FE202_UNBLOCK_ACK, + V51_CUP_LE_FE204_BLOCK_CMD, + V51_CUP_LE_FE205_BLOCK_REQ, + V51_CUP_LE_FE206_PREFORMANCE_GRADING_IND, + /* inbound primitives from Mgmt (Table 35/G.964) */ + V51_CUP_LE_MPH_UBR_UNBLOCK_REQ, + V51_CUP_LE_MPH_BI_BLOCK_CMD, + V51_CUP_LE_MPH_AR_ACTIVATE_ACCESS_REQ, + V51_CUP_LE_MPH_DR_DEACTIVATE_ACCESS_REQ, + V51_CUP_LE_MPH_DB_BLOCK_DCHAN_REQ, + V51_CUP_LE_MPH_DU_BLOCK_DCHAN_REQ, +}; + +static void isdn_up_le10_blocked(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case V51_CUP_LE_FE102_ACTIV_INIT_USER_IND: + case V51_CUP_LE_FE103_DS_ACTIVATED_IND: + case V51_CUP_LE_FE104_ACCESS_ACTIVATED_IND: + case V51_CUP_LE_MPH_DR_DEACTIVATE_ACCESS_REQ: + case V51_CUP_LE_FE106_ACCESS_DEACTIVATED_IND: + /* ignore */ + break; + case V51_CUP_LE_MPH_UBR_UNBLOCK_REQ: + /* Send FE201 */ + v51_ctrl_tx(v5up->inst, v51_enc_ctrl_port(v5up, V51_CTRL_FE201_UNBLOCK)); + osmo_fsm_inst_state_change(fi, V51_LE_UP_I_S_LE11_NOP_LOCAL_UNBLOCK, 0, 0); + break; + case V51_CUP_LE_MPH_BI_BLOCK_CMD: + /* Send FE203 */ + v51_ctrl_tx(v5up->inst, v51_enc_ctrl_port(v5up, V51_CTRL_FE203_BLOCK)); + break; + case V51_CUP_LE_FE202_UNBLOCK_ACK: + /* TODO: Send MPU-UBR */ + osmo_fsm_inst_state_change(fi, V51_LE_UP_I_S_LE11_NOP_LOCAL_UNBLOCK, 0, 0); + break; + case V51_CUP_LE_FE204_BLOCK_CMD: + case V51_CUP_LE_FE205_BLOCK_REQ: + /* ignore */ + break; + default: + OSMO_ASSERT(0); + } + +} + +static void isdn_up_le11_local_unblock(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case V51_CUP_LE_FE102_ACTIV_INIT_USER_IND: + /* TODO: send MPG-AWI */ + osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE21_OP_ACTIVATION_INITIATED, 0, 0); + break; + case V51_CUP_LE_FE103_DS_ACTIVATED_IND: + /* ignore */ + break; + case V51_CUP_LE_FE104_ACCESS_ACTIVATED_IND: + /* TODO: send MPH-AI*/ + osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE22_OP_ACCESS_ACTIVATED, 0, 0); + break; + case V51_CUP_LE_MPH_DR_DEACTIVATE_ACCESS_REQ: + case V51_CUP_LE_FE106_ACCESS_DEACTIVATED_IND: + /* ignore */ + break; + case V51_CUP_LE_MPH_UBR_UNBLOCK_REQ: + /* send FE201 */ + v51_ctrl_tx(v5up->inst, v51_enc_ctrl_port(v5up, V51_CTRL_FE201_UNBLOCK)); + break; + case V51_CUP_LE_MPH_BI_BLOCK_CMD: + /* send FE203 */ + v51_ctrl_tx(v5up->inst, v51_enc_ctrl_port(v5up, V51_CTRL_FE203_BLOCK)); + osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE10_NOP_BLOCKED, 0, 0); + break; + case V51_CUP_LE_FE202_UNBLOCK_ACK: + /* TODO: send MPH-UBI */ + osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE20_OP_OPERATIONAL_DEACTIVTED, 0, 0); + break; + case V51_CUP_LE_FE204_BLOCK_CMD: + /* TODO: send MPH-BI */ + osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE10_NOP_BLOCKED, 0, 0); + break; + case V51_CUP_LE_FE205_BLOCK_REQ: + /* ignore */ + break; + default: + OSMO_ASSERT(0); + } +} + +static void isdn_up_le12_remote_unblock(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case V51_CUP_LE_FE103_DS_ACTIVATED_IND: + /* ignore */ + break; + case V51_CUP_LE_MPH_DR_DEACTIVATE_ACCESS_REQ: + case V51_CUP_LE_FE106_ACCESS_DEACTIVATED_IND: + /* ignore */ + break; + case V51_CUP_LE_MPH_UBR_UNBLOCK_REQ: + /* TODO: send MPH-UBI */ + /* send FE201 */ + v51_ctrl_tx(v5up->inst, v51_enc_ctrl_port(v5up, V51_CTRL_FE201_UNBLOCK)); + osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE20_OP_OPERATIONAL_DEACTIVTED, 0, 0); + break; + case V51_CUP_LE_MPH_BI_BLOCK_CMD: + /* send FE203 */ + v51_ctrl_tx(v5up->inst, v51_enc_ctrl_port(v5up, V51_CTRL_FE203_BLOCK)); + osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE10_NOP_BLOCKED, 0, 0); + break; + case V51_CUP_LE_FE202_UNBLOCK_ACK: + /* TODO: send MPH-UBR */ + break; + case V51_CUP_LE_FE204_BLOCK_CMD: + /* TODO: send MPH-BI */ + osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE10_NOP_BLOCKED, 0, 0); + break; + case V51_CUP_LE_FE205_BLOCK_REQ: + /* ignore */ + break; + default: + OSMO_ASSERT(0); + } +} + +/* LE 2.0 Operational deactivated */ +static void isdn_up_le20_op_deact(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case V51_CUP_LE_MPH_AR_ACTIVATE_ACCESS_REQ: + /* Send FE101 */ + v51_ctrl_tx(v5up->inst, v51_enc_ctrl_port(v5up, V51_CTRL_FE101_ACTIVATE_ACCESS)); + osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE21_OP_ACTIVATION_INITIATED, 0, 0); + break; + case V51_CUP_LE_FE102_ACTIV_INIT_USER_IND: + /* TODO: Send MPH-AWI */ + osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE21_OP_ACTIVATION_INITIATED, 0, 0); + break; + case V51_CUP_LE_FE103_DS_ACTIVATED_IND: + /* ignore */ + break; + case V51_CUP_LE_FE104_ACCESS_ACTIVATED_IND: + /* TODO: Send MPH-AI */ + osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE22_OP_ACCESS_ACTIVATED, 0, 0); + break; + case V51_CUP_LE_MPH_DR_DEACTIVATE_ACCESS_REQ: + /* Send FE105 */ + v51_ctrl_tx(v5up->inst, v51_enc_ctrl_port(v5up, V51_CTRL_FE105_DEACTIVATE_ACCESS)); + break; + case V51_CUP_LE_FE106_ACCESS_DEACTIVATED_IND: + /* TODO: Send MPH-DI */ + break; + case V51_CUP_LE_MPH_UBR_UNBLOCK_REQ: + /* Send FE201 */ + v51_ctrl_tx(v5up->inst, v51_enc_ctrl_port(v5up, V51_CTRL_FE201_UNBLOCK)); + break; + case V51_CUP_LE_MPH_BI_BLOCK_CMD: + /* Send FE203 */ + v51_ctrl_tx(v5up->inst, v51_enc_ctrl_port(v5up, V51_CTRL_FE203_BLOCK)); + osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE10_NOP_BLOCKED, 0, 0); + break; + case V51_CUP_LE_FE202_UNBLOCK_ACK: + /* TODO: Send MPH-UBI */ + break; + case V51_CUP_LE_FE204_BLOCK_CMD: + /* TODO: Send MPH-BI */ + osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE10_NOP_BLOCKED, 0, 0); + break; + case V51_CUP_LE_FE205_BLOCK_REQ: + /* TODO: Send MPH-BR */ + break; + case V51_CUP_LE_FE206_PREFORMANCE_GRADING_IND: + /* TODO: Send MPH-GI */ + break; + default: + OSMO_ASSERT(0); + } +} + +/* LE 2.1 Access initiated */ +static void isdn_up_le21_op_act_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case V51_CUP_LE_MPH_AR_ACTIVATE_ACCESS_REQ: + case V51_CUP_LE_FE102_ACTIV_INIT_USER_IND: + /* ignore */ + break; + case V51_CUP_LE_FE103_DS_ACTIVATED_IND: + /* TODO: Send MPH-DSAI */ + break; + case V51_CUP_LE_FE104_ACCESS_ACTIVATED_IND: + /* TODO: Send MPH-AI */ + osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE22_OP_ACCESS_ACTIVATED, 0, 0); + break; + case V51_CUP_LE_MPH_DR_DEACTIVATE_ACCESS_REQ: + /* Send FE105 */ + v51_ctrl_tx(v5up->inst, v51_enc_ctrl_port(v5up, V51_CTRL_FE105_DEACTIVATE_ACCESS)); + /* TODO: Send MPH-DI */ + osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE20_OP_OPERATIONAL_DEACTIVTED, 0, 0); + break; + case V51_CUP_LE_FE106_ACCESS_DEACTIVATED_IND: + /* TODO: Send MPH-DI */ + osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE20_OP_OPERATIONAL_DEACTIVTED, 0, 0); + break; + case V51_CUP_LE_MPH_UBR_UNBLOCK_REQ: + /* Send FE201 */ + v51_ctrl_tx(v5up->inst, v51_enc_ctrl_port(v5up, V51_CTRL_FE201_UNBLOCK)); + break; + case V51_CUP_LE_MPH_BI_BLOCK_CMD: + /* Send FE203 */ + v51_ctrl_tx(v5up->inst, v51_enc_ctrl_port(v5up, V51_CTRL_FE203_BLOCK)); + osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE10_NOP_BLOCKED, 0, 0); + break; + case V51_CUP_LE_FE202_UNBLOCK_ACK: + /* TODO: Send MPH-UBI */ + break; + case V51_CUP_LE_FE204_BLOCK_CMD: + /* TODO: Send MPH-BI */ + /* TODO: Send MPH-DI */ + osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE10_NOP_BLOCKED, 0, 0); + break; + case V51_CUP_LE_FE205_BLOCK_REQ: + /* TODO: Send MPH-BR */ + break; + case V51_CUP_LE_FE206_PREFORMANCE_GRADING_IND: + /* TODO: Send MPH-GI */ + break; + default: + OSMO_ASSERT(0); + } + +} + +/* LE 2.2 Access activated */ +static void isdn_up_le22_op_acc_act(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case V51_CUP_LE_FE104_ACCESS_ACTIVATED_IND: + /* ignore */ + break; + case V51_CUP_LE_MPH_DR_DEACTIVATE_ACCESS_REQ: + /* Send FE105 */ + v51_ctrl_tx(v5up->inst, v51_enc_ctrl_port(v5up, V51_CTRL_FE105_DEACTIVATE_ACCESS)); + /* TODO: Send MPH-DI */ + osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE20_OP_OPERATIONAL_DEACTIVTED, 0, 0); + break; + case V51_CUP_LE_FE106_ACCESS_DEACTIVATED_IND: + /* TODO: Send MPH-DI */ + osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE20_OP_OPERATIONAL_DEACTIVTED, 0, 0); + break; + case V51_CUP_LE_MPH_UBR_UNBLOCK_REQ: + /* TODO: Send MPH-AI */ + /* Send FE201 */ + v51_ctrl_tx(v5up->inst, v51_enc_ctrl_port(v5up, V51_CTRL_FE201_UNBLOCK)); + break; + case V51_CUP_LE_MPH_BI_BLOCK_CMD: + /* Send FE203 */ + v51_ctrl_tx(v5up->inst, v51_enc_ctrl_port(v5up, V51_CTRL_FE203_BLOCK)); + osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE10_NOP_BLOCKED, 0, 0); + break; + case V51_CUP_LE_FE202_UNBLOCK_ACK: + /* TODO: Send MPH-UBI */ + break; + case V51_CUP_LE_FE204_BLOCK_CMD: + /* TODO: Send MPH-BI */ + /* TODO: Send MPH-DI */ + osmo_fsm_inst_state_chg(fi, V51_LE_UP_I_S_LE10_NOP_BLOCKED, 0, 0); + break; + case V51_CUP_LE_FE205_BLOCK_REQ: + /* TODO: Send MPH-BR */ + break; + case V51_CUP_LE_FE206_PREFORMANCE_GRADING_IND: + /* TODO: Send MPH-GI */ + break; + case V51_CUP_LE_MPH_DB_BLOCK_DCHAN_REQ: + /* Send FE207 */ + v51_ctrl_tx(v5up->inst, v51_enc_ctrl_port(v5up, V51_CTRL_FE207_D_CHANNEL_BLOCK)); + break; + case V51_CUP_LE_MPH_DU_BLOCK_DCHAN_REQ: + /* Send FE208 */ + v51_ctrl_tx(v5up->inst, v51_enc_ctrl_port(v5up, V51_CTRL_FE208_D_CHANNEL_UNBLOCK)); + break; + default: + OSMO_ASSERT(0); + } +} + + +/* Table 38/G.964 LE (ISDN port) FSM for ISDN basic access user ports */ +static const struct osmo_fsm_state v51_ctrl_le_port_fsm_states[] = { + [V51_LE_UP_I_S_LE10_NOP_BLOCKED] = { + .name = "LE1.0_BLOCKED", + .in_event_mask = S(V51_CUP_LE_FE102_ACTIV_INIT_USER_IND) | + S(V51_CUP_LE_FE103_DS_ACTIVATED_IND) | + S(V51_CUP_LE_FE104_ACCESS_ACTIVATED_IND) | + S(V51_CUP_LE_MPH_DR_DEACTIVATE_ACCESS_REQ) | + S(V51_CUP_LE_FE106_ACCESS_DEACTIVATED_IND) | + S(V51_CUP_LE_MPH_UBR_UNBLOCK_REQ) | + S(V51_CUP_LE_MPH_BI_BLOCK_CMD) | + S(V51_CUP_LE_FE202_UNBLOCK_ACK) | + S(V51_CUP_LE_FE204_BLOCK_CMD) | + S(V51_CUP_LE_FE205_BLOCK_REQ), + .out_state_mask = S(V51_LE_UP_I_S_LE10_NOP_BLOCKED) | + S(V51_LE_UP_I_S_LE11_NOP_LOCAL_UNBLOCK) | + S(V51_LE_UP_I_S_LE12_NOP_REMOTE_UNBLOCK), + .action = isdn_up_le10_blocked, + }, + [V51_LE_UP_I_S_LE11_NOP_LOCAL_UNBLOCK] = { + .name = "LE1.1_LOCAL_UNBLOCK", + .in_event_mask = S(V51_CUP_LE_FE102_ACTIV_INIT_USER_IND) | + S(V51_CUP_LE_FE103_DS_ACTIVATED_IND) | + S(V51_CUP_LE_FE104_ACCESS_ACTIVATED_IND) | + S(V51_CUP_LE_MPH_DR_DEACTIVATE_ACCESS_REQ) | + S(V51_CUP_LE_FE106_ACCESS_DEACTIVATED_IND) | + S(V51_CUP_LE_MPH_UBR_UNBLOCK_REQ) | + S(V51_CUP_LE_MPH_BI_BLOCK_CMD) | + S(V51_CUP_LE_FE202_UNBLOCK_ACK) | + S(V51_CUP_LE_FE204_BLOCK_CMD) | + S(V51_CUP_LE_FE205_BLOCK_REQ), + .out_state_mask = S(V51_LE_UP_I_S_LE21_OP_ACTIVATION_INITIATED) | + S(V51_LE_UP_I_S_LE22_OP_ACCESS_ACTIVATED) | + S(V51_LE_UP_I_S_LE10_NOP_BLOCKED) | + S(V51_LE_UP_I_S_LE20_OP_OPERATIONAL_DEACTIVTED), + .action = isdn_up_le11_local_unblock, + }, + [V51_LE_UP_I_S_LE12_NOP_REMOTE_UNBLOCK] = { + .name = "LE1.2_REMOTE_UNBLOCK", + .in_event_mask = S(V51_CUP_LE_FE103_DS_ACTIVATED_IND) | + S(V51_CUP_LE_MPH_DR_DEACTIVATE_ACCESS_REQ) | + S(V51_CUP_LE_FE106_ACCESS_DEACTIVATED_IND) | + S(V51_CUP_LE_MPH_UBR_UNBLOCK_REQ) | + S(V51_CUP_LE_MPH_BI_BLOCK_CMD) | + S(V51_CUP_LE_FE202_UNBLOCK_ACK) | + S(V51_CUP_LE_FE204_BLOCK_CMD) | + S(V51_CUP_LE_FE205_BLOCK_REQ), + .out_state_mask = S(V51_LE_UP_I_S_LE20_OP_OPERATIONAL_DEACTIVTED) | + S(V51_LE_UP_I_S_LE10_NOP_BLOCKED), + .action = isdn_up_le12_remote_unblock, + }, + [V51_LE_UP_I_S_LE20_OP_OPERATIONAL_DEACTIVTED] = { + .name = "LE2.0_OPERATIONAL_DEACTIVATED", + .in_event_mask = S(V51_CUP_LE_MPH_AR_ACTIVATE_ACCESS_REQ) | + S(V51_CUP_LE_FE102_ACTIV_INIT_USER_IND) | + S(V51_CUP_LE_FE103_DS_ACTIVATED_IND) | + S(V51_CUP_LE_FE104_ACCESS_ACTIVATED_IND) | + S(V51_CUP_LE_MPH_DR_DEACTIVATE_ACCESS_REQ) | + S(V51_CUP_LE_FE106_ACCESS_DEACTIVATED_IND) | + S(V51_CUP_LE_MPH_UBR_UNBLOCK_REQ) | + S(V51_CUP_LE_MPH_BI_BLOCK_CMD) | + S(V51_CUP_LE_FE202_UNBLOCK_ACK) | + S(V51_CUP_LE_FE204_BLOCK_CMD) | + S(V51_CUP_LE_FE205_BLOCK_REQ) | + S(V51_CUP_LE_FE206_PREFORMANCE_GRADING_IND), + .out_state_mask = S(V51_LE_UP_I_S_LE21_OP_ACTIVATION_INITIATED) | + S(V51_LE_UP_I_S_LE22_OP_ACCESS_ACTIVATED) | + S(V51_LE_UP_I_S_LE10_NOP_BLOCKED), + .action = isdn_up_le21_op_deact, + }, + [V51_LE_UP_I_S_LE21_OP_ACTIVATION_INITIATED] = { + .name = "LE2.1_ACTIVATION_INITIATED", + .in_event_mask = S(V51_CUP_LE_MPH_AR_ACTIVATE_ACCESS_REQ) | + S(V51_CUP_LE_FE102_ACTIV_INIT_USER_IND) | + S(V51_CUP_LE_FE103_DS_ACTIVATED_IND) | + S(V51_CUP_LE_FE104_ACCESS_ACTIVATED_IND) | + S(V51_CUP_LE_MPH_DR_DEACTIVATE_ACCESS_REQ) | + S(V51_CUP_LE_FE106_ACCESS_DEACTIVATED_IND) | + S(V51_CUP_LE_MPH_UBR_UNBLOCK_REQ) | + S(V51_CUP_LE_MPH_BI_BLOCK_CMD) | + S(V51_CUP_LE_FE202_UNBLOCK_ACK) | + S(V51_CUP_LE_FE204_BLOCK_CMD) | + S(V51_CUP_LE_FE205_BLOCK_REQ), + .out_state_mask = S(V51_LE_UP_I_S_LE22_OP_ACCESS_ACTIVATED) | + S(V51_LE_UP_I_S_LE20_OP_OPERATIONAL_DEACTIVTED) | + S(V51_LE_UP_I_S_LE10_NOP_BLOCKED), + .action = isdn_up_le21_op_act_init, + }, + [V51_LE_UP_I_S_LE22_OP_ACCESS_ACTIVATED] = { + .name = "LE2.2_ACCESS_ACTIVATED", + .in_event_mask = S(V51_CUP_LE_FE104_ACCESS_ACTIVATED_IND) | + S(V51_CUP_LE_MPH_DR_DEACTIVATE_ACCESS_REQ) | + S(V51_CUP_LE_FE106_ACCESS_DEACTIVATED_IND) | + S(V51_CUP_LE_MPH_UBR_UNBLOCK_REQ) | + S(V51_CUP_LE_MPH_BI_BLOCK_CMD) | + S(V51_CUP_LE_FE202_UNBLOCK_ACK) | + S(V51_CUP_LE_FE204_BLOCK_CMD) | + S(V51_CUP_LE_FE205_BLOCK_REQ) | + S(V51_CUP_LE_FE206_PREFORMANCE_GRADING_IND) | + S(V51_CUP_LE_MPH_DB_BLOCK_DCHAN_REQ) | + S(V51_CUP_LE_MPH_DU_BLOCK_DCHAN_REQ), + .out_state_mask = S(V51_LE_UP_I_S_LE20_OP_OPERATIONAL_DEACTIVTED) | + S(V51_LE_UP_I_S_LE10_NOP_BLOCKED), + .action = isdn_up_le22_op_acc_act, + }, +}; + diff --git a/v5x_internal.h b/v5x_internal.h new file mode 100644 index 0000000..3b07b36 --- /dev/null +++ b/v5x_internal.h @@ -0,0 +1,120 @@ +#pragma once + +/* (C) 2021 by Harald Welte + * + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + + +#include +#include +#include + +struct osmo_fsm_inst; + +enum v5x_dialect { + V5X_DIALECT_V51 = 1, + V5X_DIALECT_V52 = 2, +}; + +/* forward-declarations */ +struct v5x_interface; +struct v5x_instance; +struct v5x_link; + +/* A C-channel is a 64k timeslot used for signalling */ +struct v5x_c_channel { + struct v5x_link *link; /* back-pointer */ + struct v5x_timeslot *ts; /* E1 link timeslot. NULL = C-channel doesn't exist */ + bool active; /* false == standby */ +}; + +/* one physical E1 timeslot used on an E1 interface part of a V5.2 interface */ +struct v5x_timeslot { + uint8_t nr; + struct v5x_link *link; /* back-pointer */ + uint16_t l3_address; +}; + +/* one physical E1 interface used within a V5.2 interface */ +struct v5x_link { + uint8_t id; + struct v5x_interface *interface; /* back-pointer */ + struct v5x_timeslot ts[32]; /* 32 E1 slots; 0 not available */ + struct v5x_c_channel c_channel[3]; /* 64k signaling possible on TS16, TS15 and TS31 */ + struct osmo_fsm_inst *l1_link_fi; /* Layer 1 Link FSM instance */ +}; + +/* one V5.x interface between AN (Access Network) and LE (Local Exchange) */ +struct v5x_interface { + struct llist_head list; /* instance.interfaces */ + struct v5x_instance *instance; /* back-pointer */ + enum v5x_dialect dialect; + struct v5x_link *primary_link; /* one of the links below */ + struct v5x_link *secondary_link; /* one of the links below */ + /* 1..16 links in one interface */ + struct v5x_link links[16]; + + struct osmo_fsm_inst *fi; /* Interface FSM instance */ + struct { + struct lapd_datalink dl; /* Control data link */ + struct v5x_c_channel *c_chan; /* pointer to active C-channel */ + } control; + struct { + struct lapd_datalink dl; /* Link control data link */ + struct v5x_c_channel *c_chan; /* pointer to active C-channel */ + struct osmo_fsm_inst *fi; /* Linkg Control FSM instance */ + } lcp; + struct { + struct lapd_datalink dl; /* PSTN data link */ + struct v5x_c_channel *c_chan; /* pointer to active C-channel */ + } pstn; + struct { + struct lapd_datalink dl; /* BCC data link */ + struct v5x_c_channel *c_chan; /* pointer to active C-channel */ + } bcc; + struct { + struct lapd_datalink dl; /* Protection data link 1 + 2 */ + struct v5x_c_channel *c_chan; /* pointer to active C-channel */ + } protection[2]; +}; + +/* one user-facing port (subscriber line) */ +struct v5x_user_port { + struct llist_head list; /* part of v5x_instance.ports */ + struct v5x_instance *inst; /* back-pointer to instance we're part of */ + + uint16_t nr; /* port-number in decoded form (0..32767) */ + iool is_isdn; /* is this port an ISDN port? */ + + struct { + struct osmo_fsm_inst *ctrl_fi; /* control protocol FSM instance */ + struct osmo_fsm_inst *fi; /* port state FSM instance */ + } isdn; + struct { + } pstn; +}; + +struct v5x_instance { + struct llist_head list; /* part of global list of instances */ + struct llist_head interfaces; /* v5x_interface.list */ + struct llist_head user_ports; /* list of v5x_user_port */ + struct osmo_fsm_inst *ctrl_fi; /* common control FSM */ +}; diff --git a/v5x_protocol.c b/v5x_protocol.c new file mode 100644 index 0000000..c34de33 --- /dev/null +++ b/v5x_protocol.c @@ -0,0 +1,399 @@ +/* (C) 2021 by Harald Welte + * + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include +#include +#include + +#include +#include +#include + +#include "v5x_protocol.h" + +const struct value_string v51_ctrl_msg_typ_str[] = { + { V51_CTRL_MSGT_ESTABLISH, "ESTABLISH" }, + { V51_CTRL_MSGT_ESTABLISH_ACK, "ESTABLISH_ACK" }, + { V51_CTRL_MSGT_SIGNAL, "SIGNAL" }, + { V51_CTRL_MSGT_SIGNAL_ACK, "SIGNAL_ACK" }, + { V51_CTRL_MSGT_DISCONNECT, "DISCONNECT" }, + { V51_CTRL_MSGT_DISCONNECT_ACK, "DISCONNECT_ACK" }, + { V51_CTRL_MSGT_STATUS_ENQUIRY, "STATUS_ENQUIRY" }, + { V51_CTRL_MSGT_STATUS, "STATUS" }, + { V51_CTRL_MSGT_PROTOCOL_PARAMETER, "PROTOCOL_PARAMETER" }, + { V51_CTRL_MSGT_PORT_CTRL, "PORT_CTRL" }, + { V51_CTRL_MSGT_PORT_CTRL_ACK, "PORT_CTRL_ACK" }, + { V51_CTRL_MSGT_COMMON_CTRL, "COMMON_CTRL" }, + { V51_CTRL_MSGT_COMMON_CTRL_ACK, "COMMON_CTRL_ACK" }, + { V52_CTRL_MSGT_SWITCH_OVER_REQ, "SWITCH_OVER_REQ" }, + { V52_CTRL_MSGT_SWITCH_OVER_COM, "SWITCH_OVER_COM" }, + { V52_CTRL_MSGT_OS_SWITCH_OVER_COM, "OS_SWITCH_OVER_COM" }, + { V52_CTRL_MSGT_SWITCH_OVER_ACK, "SWITCH_OVER_ACK" }, + { V52_CTRL_MSGT_SWITCH_OVER_REJECT, "SWITCH_OVER_REJECT" }, + { V52_CTRL_MSGT_RESET_SN_COM, "RESET_SN_COM" }, + { V52_CTRL_MSGT_RESET_SN_ACK, "RESET_SN_ACK" }, + { V52_CTRL_MSGT_ALLOCATION, "ALLOCATION" }, + { V52_CTRL_MSGT_ALLOCATION_COMPLETE, "ALLOCATION_COMPLETE" }, + { V52_CTRL_MSGT_ALLOCATION_REJECT, "ALLOCATION_REJECT" }, + { V52_CTRL_MSGT_DE_ALLOCATION, "DE_ALLOCATION" }, + { V52_CTRL_MSGT_DE_ALLOCATION_COMPLETE, "DE_ALLOCATION_COMPLETE" }, + { V52_CTRL_MSGT_DE_ALLOCATION_REJECT, "DE_ALLOCATION_REJECT" }, + { V52_CTRL_MSGT_AUDIT, "AUDIT" }, + { V52_CTRL_MSGT_AUDIT_COMPLETE, "AUDIT_COMPLETE" }, + { V52_CTRL_MSGT_AN_FAULT, "AN_FAULT" }, + { V52_CTRL_MSGT_AN_FAULT_ACKNOWLEDGE "AN_FAULT_ACKNOWLEDGE" }, + { V52_CTRL_MSGT_PROTOCOL_ERROR "PROTOCOL_ERROR" }, + { V52_CTRL_MSGT_LINK_CTRL, "LINK_CTRL" }, + { V52_CTRL_MSGT_LINK_CTRL_ACK, "LINK_CTRL_ACK" }, + { 0, NULL } +}; + +const struct value_string v51_ctrl_iei_str[] = { + { V51_CTRL_IEI_PULSE_NOTIFICATION, "PULSE_NOTIFICATION" }, + { V51_CTLR_IEI_LINE_NOTIFICATION, "LINE_NOTIFICATION" }, + { V51_CTLR_IEI_STATE, "STATE" }, + { V51_CTLR_IEI_AUTONOMOUS_SIG_SEQ, "AUTONOMOUS_SIG_SEQ" }, + { V51_CTLR_IEI_SEQUENCE_RESPONSE, "SEQUENCE_RESPONSE" }, + { V51_CTRL_IEI_PERFORMANCE_GRADING, "PERFORMANCE_GRADING" }, + { V51_CTRL_IEI_REJECTION_CAUSE, "REJECTION_CAUSE" }, + { V51_CTRL_IEI_SEQUENCE_NR, "SEQUENCE_NR" }, + { V51_CTRL_IEI_CADENCED_RINGING, "CADENCED_RINGING" }, + { V51_CTRL_IEI_PULSED_SIGNAL, "PULSED_SIGNAL" }, + { V51_CTRL_IEI_STEADY_SIGNAL, "STEADY_SIGNAL" }, + { V51_CTRL_IEI_DIGIT_SIGNAL, "DIGIT_SIGNAL" }, + { V51_CTRL_IEI_RECOGNITION_TIME, "RECOGNITION_TIME" }, + { V51_CTRL_IEI_ENABLE_AUTONOMOUS_ACK, "ENABLE_AUTONOMOUS_ACK" }, + { V51_CTRL_IEI_DISABLE_AUTONOMOUS_ACK, "DISABLE_AUTONOMOUS_ACK" }, + { V51_CTRL_IEI_CAUSE, "CAUSE" }, + { V51_CTRL_IEI_RESOURCE_UNAVAILABLE, "RESOURCE_UNAVAILABLE" }, + { V51_CTRL_IEI_ENABLE_METERING, "ENABLE_METERING" }, + { V51_CTRL_IEI_METERING_REPORT, "METERING_REPORT" }, + { V51_CTRL_IEI_ATTENUATION, "ATTENUATION" }, + { V51_CTRL_IEI_CTRL_F_ELEMENT, "CTRL_F_ELEMENT" }, + { V51_CTRL_IEI_CTRL_F_ID, "CTRL_F_ID" }, + { V51_CTRL_IEI_VARIANT, "VARIANT" }, + { V51_CTRL_IEI_INTERFACE_ID, "INTERFACE_ID" }, + { V52_CTRL_IEI_LCP_LINK_CTRL_FUNCTION, "LCP_LINK_CTRL_FUNCTION" }, + { V52_CTRL_IEI_BCC_USER_PORT_ID "BCC_USER_PORT_ID" }, + { V52_CTRL_IEI_BCC_ISDN_PORT_TS_ID, "BCC_ISDN_PORT_TS_ID" }, + { V52_CTRL_IEI_BCC_V5_TS_ID, "BCC_V5_TS_ID" }, + { V52_CTRL_IEI_BCC_MULTI_TS_MAP, "BCC_MULTI_TS_MAP" }, + { V52_CTRL_IEI_BCC_REJECT_CAUSE, "BCC_REJECT_CAUSE" }, + { V52_CTRL_IEI_BCC_PROTOCOL_ERROR_CAUSE, "BCC_PROTOCOL_ERROR_CAUSE" }, + { V52_CTRL_IEI_BCC_CONNECTION_INCOMPLETE, "BCC_CONNECTION_INCOMPLETE" }, + { V52_CTRL_IEI_BCC_INFO_TRANSFER_CAPABILITY, "BCC_INFO_TRANSFER_CAPABILITY" }, + { V52_CTRL_IEI_PP_SEQUENCE_NR, "PP_SEQUENCE_NR" }, + { V52_CTRL_IEI_PP_PHYSICAL_C_CHAN_ID, "PP_PHYSICAL_C_CHAN_ID" }, + { V52_CTRL_IEI_PP_REJECTION_CAUSE, "PP_REJECTION_CAUSE" }, + { V52_CTRL_IEI_PP_PROTOCOL_ERROR_CAUSE, "PP_PROTOCOL_ERROR_CAUSE" }, + { 0, NULL } +}; + +const struct value_string v51_ctrl_func_el_str[] = { + { V51_CTRL_FE101_ACTIVATE_ACCESS, "FE101_ACTIVATE_ACCESS" }, + { V51_CTRL_FE102_ACT_INIT_BY_USER, "FE102_ACT_INIT_BY_USER" }, + { V51_CTRL_FE103_DS_ACTIVATED, "FE103_DS_ACTIVATED" }, + { V51_CTRL_FE104_ACCESS_ACTIVATED, "FE104_ACCESS_ACTIVATED" }, + { V51_CTRL_FE105_DEACTIVATE_ACCESS, "FE105_DEACTIVATE_ACCESS" }, + { V51_CTRL_FE106_ACCESS_DEACTIVATED, "FE106_ACCESS_DEACTIVATED" }, + { V51_CTRL_FE201_UNBLOCK, "FE201_UNBLOCK" }, + { V51_CTRL_FE203_BLOCK, "FE203_BLOCK" }, + { V51_CTRL_FE205_BLOCK_REQ, "FE205_BLOCK_REQ" }, + { V51_CTRL_FE206_PERFORMANCE_GRADING, "FE206_PERFORMANCE_GRADING" }, + { V51_CTRL_FE206_D_CHANNEL_BLOCK, "FE206_D_CHANNEL_BLOCK" }, + { V51_CTRL_FE206_D_CHANNEL_UNBLOCK, "FE206_D_CHANNEL_UNBLOCK" } + { 0, NULL } +}; + +const struct value_string v51_ctrl_func_id_str[] = { + { V51_CTRL_ID_VERIFY_RE_PROVISIONING, "VERIFY_RE_PROVISIONING" }, + { V51_CTRL_ID_READY_FOR_RE_PROVISIONING, "READY_FOR_RE_PROVISIONING" }, + { V51_CTRL_ID_NOT_READY_FOR_RE_PROVISIONING, "NOT_READY_FOR_RE_PROVISIONING" }, + { V51_CTRL_ID_SWITCH_OVER_TO_NEW_VARIANT, "SWITCH_OVER_TO_NEW_VARIANT" }, + { V51_CTRL_ID_RE_PROVISIONING_STARTED, "RE_PROVISIONING_STARTED" }, + { V51_CTRL_ID_CANNOT_RE_PROVISION, "CANNOT_RE_PROVISION" }, + { V51_CTRL_ID_REQUEST_VARIANT_AND_INTERFACE_ID, "REQUEST_VARIANT_AND_INTERFACE_ID" }, + { V51_CTRL_ID_VARIANT_AND_INTERFACE_ID, "VARIANT_AND_INTERFACE_ID" }, + { V51_CTRL_ID_BLOCKING_STARTED, "BLOCKING_STARTED" }, + { V51_CTRL_ID_RESTART_REQUEST, "RESTART_REQUEST" }, + { V51_CTRL_ID_RESTART_COMPLETE, "RESTART_COMPLETE" }, + { 0, NULL } +}; + +const struct value_string v51_cause_type_str[] = { + { V51_CTRL_CAUSE_T_RESP_TO_STATUS_ENQ, "RESP_TO_STATUS_ENQ" }, + { V51_CTRL_CAUSE_T_L3_ADDRESS_ERROR, "L3_ADDRESS_ERROR" }, + { V51_CTRL_CAUSE_T_MSG_TYPE_UNRECOGNIZED, "MSG_TYPE_UNRECOGNIZED" }, + { V51_CTRL_CAUSE_T_OUT_OF_SEQUENCE_IE, "OUT_OF_SEQUENCE_IE" }, + { V51_CTRL_CAUSE_T_REPEATED_OPT_IE, "REPEATED_OPT_IE" }, + { V51_CTRL_CAUSE_T_MAND_IE_MISSING, "MAND_IE_MISSING" }, + { V51_CTRL_CAUSE_T_UNRECOGNIZED_IE, "UNRECOGNIZED_IE" }, + { V51_CTRL_CAUSE_T_MAND_IE_CONTENT_ERROR, "MAND_IE_CONTENT_ERROR" }, + { V51_CTRL_CAUSE_T_OPT_IE_CONTENT_ERROR, "OPT_IE_CONTENT_ERROR" }, + { V51_CTRL_CAUSE_T_MSG_INCOMP_PATH_STATE, "MSG_INCOMP_PATH_STATE" }, + { V51_CTRL_CAUSE_T_REPEATED_MAND_IE, "REPEATED_MAND_IE" }, + { V51_CTRL_CAUSE_T_TOO_MANY_IE, "TOO_MANY_IE" }, + { 0, NULL } +}; + +static const uint8_t signal_mand_ies[] = { V51_CTRL_IEI_SEQUENCE_NR }; +static const uint8_t signal_ack_mand_ies[] = { V51_CTRL_IEI_SEQUENCE_NR }; +static const uint8_t status_mand_ies[] = { V51_CTRL_IEI_STATE, V51_CTRL_IEI_CAUSE }; +static const uint8_t prot_par_mand_ies[] = { V51_CTRL_IEI_SEQUENCE_NR }; +static const uint8_t port_ctrl_mand_ies[] = { V51_CTRL_IEI_CTRL_F_ELEMENT }; +static const uint8_t lcp_mand_ies[] = { V52_CTRL_IEI_LCP_LINK_CTRL_FUNCTION }; +static const uint8_t alloc_mand_ies[] = { V52_CTRL_IEI_BCC_USER_PORT_ID }; +static const uint8_t alloc_rej_mand_ies[] = { V52_CTRL_IEI_BCC_REJECT_CAUSE }; +static const uint8_t prot_err_mand_ies[] = { V52_CTRL_IEI_BCC_PROTOCOL_ERROR_CAUSE }; +static const uint8_t pp_swo_mand_ies[] = { + V52_CTRL_IEI_PP_SEQUENCE_NR, + V52_CTRL_IEI_PP_PHYSICAL_C_CHAN_ID +}; +static const uint8_t pp_swo_rej_mand_ies[] = { + V52_CTRL_IEI_PP_SEQUENCE_NR, + V52_CTRL_IEI_PP_PHYSICAL_C_CHAN_ID, + V52_CTRL_IEI_PP_REJECTION_CAUSE, +}; +static const uint8_t pp_perr_mand_ies[] = { + V52_CTRL_IEI_PP_SEQUENCE_NR, + V52_CTRL_IEI_PP_PROTOCOL_ERROR_CAUSE, +}; + + +const struct osmo_tlv_prot_def v51_ctrl_msg_tlv = { + .name = "V51_CTRL", + .tlv_def = v51_ctrl_tlv_def, + .msg_def = { + /* G.964 Section 13.3 */ + [V51_CTRL_MSGT_ESTABLISH] = MSG_DEF("ESTABLISH", NULL, 0), + [V51_CTRL_MSGT_ESTABLISH_ACK] = MSG_DEF("ESTABLISH", NULL, 0), + [V51_CTRL_MSGT_SIGNAL] = MSG_DEF("SIGNAL", signal_mand_ies, 0), + [V51_CTRL_MSGT_SIGNAL_ACK] = MSG_DEF("SIGNAL_ACK", signal_ack_mand_ies, 0), + [V51_CTRL_MSGT_STATUS] = MSG_DEF("STATUS", status_mand_ies, 0), + [V51_CTRL_MSGT_STATUS_ENQUIRY] = MSG_DEF("STATUS_ENQUIRY", NULL, 0), + [V51_CTRL_MSGT_DISCONNECT] = MSG_DEF("DISCONNECT", NULL, 0), + [V51_CTRL_MSGT_DISCONNECT_ACK] = MSG_DEF("DISCONNECT_ACK", NULL, 0), + [V51_CTRL_MSGT_PROTOCOL_PARAMETER] = MSG_DEF("PROTOCOL_PARAMETER", prot_par_mand_ies, 0), + /* G.964 Section 14.4 */ + [V51_CTRL_MSGT_PORT_CTRL] = MSG_DEF("PORT_CTRL", port_ctrl_mand_ies, 0), + [V51_CTRL_MSGT_PORT_CTRL_ACK] = MSG_DEF("PORT_CTRL_ACK", port_ctrl_mand_ies, 0), + [V51_CTRL_MSGT_COMMON_CTRL] = MSG_DEF("COMMON_CTRL", port_ctrl_mand_ies, 0), + [V51_CTRL_MSGT_COMMON_CTRL_ACK] = MSG_DEF("COMMON_CTRL_ACK", port_ctrl_mand_ies, 0), + /* G.965 Section 16.3 LCP */ + [V52_CTRL_MSGT_LCP_LINK_CTRL] = MSG_DEF("LINK_CONTROL", lcp_mand_ies, 0), + [V52_CTRL_MSGT_LCP_LINK_CTRL_ACK] = MSG_DEF("LINK_CONTROL_ACK", lcp_mand_ies, 0), + /* G.965 Section 17.3 BCC */ + [V52_CTRL_MSGT_ALLOCATION] = MSG_DEF("ALLOCATION", alloc_mand_ies, 0), + [V52_CTRL_MSGT_ALLOCATION_COMPLETE] = MSG_DEF("ALLOCATION_COMPLETE", NULL, 0), + [V52_CTRL_MSGT_ALLOCATION_REJECT] = MSG_DEF("ALLOCATION_REJECT", alloc_rej_mand_ies, 0), + [V52_CTRL_MSGT_DE_ALLOCATION] = MSG_DEF("DE_ALLOCATION", alloc_mand_ies, 0), + [V52_CTRL_MSGT_DE_ALLOCATION_COMPLETE] = MSG_DEF("DE_ALLOCATION_COMPLETE", NULL, 0), + [V52_CTRL_MSGT_DE_ALLOCATION_REJECT] = MSG_DEF("DE_ALLOCATION_REJECT", alloc_rej_mand_ies, 0), + [V52_CTRL_MSGT_AUDIT] = MSG_DEF("AUDIT", NULL, 0), + [V52_CTRL_MSGT_AUDIT_COMPLETE] = MSG_DEF("AUDIT_COMPLETE", NULL, 0), + [V52_CTRL_MSGT_AN_FAULT] = MSG_DEF("AN_FAULT", NULL, 0), + [V52_CTRL_MSGT_AN_FAULT_ACK] = MSG_DEF("AN_FAULT_ACK", NULL, 0), + [V52_CTRL_MSGT_PROTOCOL_ERROR] = MSG_DEF("PROTOCOL_ERROR", prot_err_mand_ies, 0), + /* G.965 Section 18.3 PP */ + [V52_CTRL_MSGT_PP_SWITCH_OVER_REQ] = MSG_DEF("SWITCH_OVER_REQ", pp_swo_mand_ies, 0), + [V52_CTRL_MSGT_PP_SWITCH_OVER_COM] = MSG_DEF("SWITCH_OVER_COM", pp_swo_mand_ies, 0), + [V52_CTRL_MSGT_PP_OS_SWITCH_OVER_COM] = MSG_DEF("OS_SWITCH_OVER_COM", pp_swo_mand_ies, 0), + [V52_CTRL_MSGT_PP_SWITCH_OVER_ACK] = MSG_DEF("SWITCH_OVER_ACK", pp_swo_mand_ies, 0), + [V52_CTRL_MSGT_PP_SWITCH_OVER_REJECT] = MSG_DEF("SWITCH_OVER_REJECT", pp_swo_rej_mand_ies, 0), + [V52_CTRL_MSGT_PP_PROTOCOL_ERROR] = MSG_DEF("PROTOCOL_ERROR", pp_perr_mand_ies, 0), + + [V52_CTRL_MSGT_PP_RESET_SN_COM] = MSG_DEF("RESET_SN_COM", NULL, 0), + [V52_CTRL_MSGT_PP_RESET_SN_ACK] = MSG_DEF("RESET_SN_ACK", NULL, 0), + + }, + .ie_def = { + /* single byte, only upper nibble matches IEI */ + [V51_CTRL_IEI_PULSE_NOTIFICATION] = { 0, "PULSE_NOTIFICATION" }, + [V51_CTLR_IEI_LINE_NOTIFICATION] = { 0, "LINE_NOTIFICATION" }, + [V51_CTLR_IEI_STATE] = { 0, "STATE" }, + [V51_CTLR_IEI_AUTONOMOUS_SIG_SEQ] = { 0, "AUTONOMOUS_SIG_SEQ" }, + [V51_CTLR_IEI_SEQUENCE_RESPONSE] = { 0, "SEQUENCE_RESPONSE" }, + /* single byte: ISDN */ + [V51_CTRL_IEI_PERFORMANCE_GRADING] = { 0, "PERFORMANCE_GRADING" }, + [V51_CTRL_IEI_REJECTION_CAUSE] = { 0, "REJECTION_CAUSE" }, + /* variable length: PSTN */ + [V51_CTRL_IEI_SEQUENCE_NR] = { 1, "SEQUENCE_NR" }, + [V51_CTRL_IEI_CADENCED_RINGING] = { 1, "CADENCED_RINGING" }, + [V51_CTRL_IEI_PULSED_SIGNAL] = { 2, "PULSED_SIGNAL" }, + [V51_CTRL_IEI_STEADY_SIGNAL] = { 1, "STEADY_SIGNAL" }, + [V51_CTRL_IEI_DIGIT_SIGNAL] = { 1, "DIGIT_SIGNAL" }, + [V51_CTRL_IEI_RECOGNITION_TIME] = { 2, "RECOGNITION_TIME" }, + [V51_CTRL_IEI_ENABLE_AUTONOMOUS_ACK] = { 2, "ENABLE_AUTONOMOUS_ACK" }, + [V51_CTRL_IEI_DISABLE_AUTONOMOUS_ACK] = { 1, "DISABLE_AUTONOMOUS_ACK" }, + [V51_CTRL_IEI_CAUSE] = { 2, "CAUSE" }, + [V51_CTRL_IEI_RESOURCE_UNAVAILABLE] = { 1, "RESOURCE_UNAVAILABLE" }, + [V51_CTRL_IEI_ENABLE_METERING] = { 1, "ENABLE_METERING" }, + [V51_CTRL_IEI_METERING_REPORT] = { 2, "METERING_REPORT" }, + [V51_CTRL_IEI_ATTENUATION] = { 1, "ATTENUATION" }, + /* variable length: ISDN */ + [V51_CTRL_IEI_CTRL_F_ELEMENT] = { 1, "CTRL_F_ELEMENT" }, + [V51_CTRL_IEI_CTRL_F_ID] = { 1, "CTRL_F_ID" }, + [V51_CTRL_IEI_VARIANT] = { 1, "VARIANT" }, + [V51_CTRL_IEI_INTERFACE_ID] = { 3, "INTERFACE_ID" }, + /* variable length: LCP */ + [V52_CTRL_IEI_LCP_LINK_CTRL_FUNCTION] = { 1, "LINK_CTRL_FUNCTION" }, + /* variable length: BCC */ + [V52_CTRL_IEI_BCC_USER_PORT_ID] = { 2, "USER_PORT_ID" }, + [V52_CTRL_IEI_BCC_ISDN_PORT_TS_ID] = { 1, "ISDN_PORT_TS_ID" }, + [V52_CTRL_IEI_BCC_V5_TS_ID] = { 2, "V5_TS_ID" }, + [V52_CTRL_IEI_BCC_MULTI_TS_MAP] = { 9, "MULTI_TS_MAP" }, + [V52_CTRL_IEI_BCC_REJECT_CAUSE] = { 1, "REJECT_CAUSE" }, + [V52_CTRL_IEI_BCC_PROTOCOL_ERROR_CAUSE] = { 1, "PROTOCOL_ERR_CAUSE" }, + [V52_CTRL_IEI_BCC_CONNECTION_INCOMPLETE]= { 1, "CONNECTION_INCOMPLETE" }, + [V52_CTRL_IEI_BCC_INFO_TRANSFER_CAPABILITY] = { 1, "INFO_TRANSFER_CAPABILITY" }, + /* variable length: PP */ + [V52_CTRL_IEI_PP_SEQUENCE_NR] = { 1, "SEQUENCE_NR" }, + [V52_CTRL_IEI_PP_PHYSICAL_C_CHAN_ID] = { 2, "PHYSICAL_C_CHAN_ID" }, + [V52_CTRL_IEI_PP_REJECTION_CAUSE] = { 1, "REJECTION_CAUSE" }, + [V52_CTRL_IEI_PP_PROTOCOL_ERROR_CAUSE] = { 1, "PROTOCOL_ERR_CAUSE" }, + }, + .msgt_names = v51_ctrl_msg_typ_str, +}; + + +struct v5x_user_port *v5x_user_port_find(struct v5x_instance *v5i, uint16_t nr) +{ + struct v5x_user_port *v5up; + + llist_for_each_entry(v5up, &v5i->user_ports, list) { + if (v5up->nr == l3_addr) + return v5up; + } +} + + +static int v51_rcv_pstn(struct v5x_user_port *v5i, struct msgb *msg, const struct tlv_parsed *tp) +{ + struct v51_l3_hdr *l3h = msgb_l3(msg); + + switch (l3h->msg_type) { + case V51_CTRL_MSGT_ESTABLISH: + case V51_CTRL_MSGT_ESTABLISH_ACK: + case V51_CTRL_MSGT_SIGNAL: + case V51_CTRL_MSGT_SIGNAL_ACK: + case V51_CTRL_MSGT_DISCONNECT: + case V51_CTRL_MSGT_DISCONNECT_ACK: + case V51_CTRL_MSGT_STATUS_ENQUIRY: + case V51_CTRL_MSGT_STATUS: + case V51_CTRL_MSGT_PROTOCOL_PARAMETER: + return -EINVAL; + } +} + +static int v52_rcv_pp(struct v5x_instance *v5i, struct msgb *msg, const struct tlv_parsed *tp) +{ + struct v51_l3_hdr *l3h = msgb_l3(msg); +} + +static int v52_rcv_bcc(struct v5x_instance *v5i, struct msgb *msg, const struct tlv_parsed *tp) +{ + struct v51_l3_hdr *l3h = msgb_l3(msg); +} + +static int v52_rcv_lcp(struct v5x_instance *v5i, struct msgb *msg, const struct tlv_parsed *tp) +{ + struct v51_l3_hdr *l3h = msgb_l3(msg); +} + + +/* main entry point for received V5 messages */ +int v5x_rcv(struct v5x_instance *v5i, struct msgb *msg) +{ + struct v51_l3_hdr *l3h = msgb_l3(msg); + struct tlv_parsed tp; + uint16_t l3_addr; + + if (msgb_l3len(msg) < sizeof(*l3h)) { + LOGP(DV5, LOGL_ERROR, "Received short message (%u bytes < %u)\n", msgb_l3len(msg), sizeof(*l3h)); + return -EINVAL; + } + + if (l3h->pdisc != V51_CTRL_PDISC) { + LOGP(DV5, LOGL_ERROR, "Received unsupported protocol discriminator 0x%02x\n", l3h->pdisc); + return -EINVAL; + } + + rc = osmo_tlv_prot_parse(v51_ctrl_msg_tlv, &tp, 1, l3h->msg_type, msgb_l3(msg) + sizeof(*l3h), + msgb_l3len(msg) - sizeof(*l3h), 0, 0, DV5, __func__); + if (rc < 0) + return -EINVAL; + + l3_addr = v51_l3_addr_dec(l3h->l3_addr); + + /* look-up user port based on L3 addr? */ + v5up = v5x_user_port_find(v5i, l3_addr); + + switch (l3h->msg_type) { + case V51_CTRL_MSGT_ESTABLISH: + case V51_CTRL_MSGT_ESTABLISH_ACK: + case V51_CTRL_MSGT_SIGNAL: + case V51_CTRL_MSGT_SIGNAL_ACK: + case V51_CTRL_MSGT_DISCONNECT: + case V51_CTRL_MSGT_DISCONNECT_ACK: + case V51_CTRL_MSGT_STATUS_ENQUIRY: + case V51_CTRL_MSGT_STATUS: + case V51_CTRL_MSGT_PROTOCOL_PARAMETER: + rc = v51_rcv_pstn(v5up, msg, &tp); + break; + case V51_CTRL_MSGT_PORT_CTRL: + case V51_CTRL_MSGT_PORT_CTRL_ACK: + case V51_CTRL_MSGT_COMMON_CTRL: + case V51_CTRL_MSGT_COMMON_CTRL_ACK: + rc = v51_rcv_ctrl(v5i, msg, &tp); + break; + case V52_CTRL_MSGT_PP_SWITCH_OVER_REQ: + case V52_CTRL_MSGT_PP_SWITCH_OVER_COM: + case V52_CTRL_MSGT_PP_OS_SWITCH_OVER_COM: + case V52_CTRL_MSGT_PP_SWITCH_OVER_ACK: + case V52_CTRL_MSGT_PP_SWITCH_OVER_REJECT: + case V52_CTRL_MSGT_PP_PROTOCOL_ERROR: + case V52_CTRL_MSGT_PP_RESET_SN_COM: + case V52_CTRL_MSGT_PP_RESET_SN_ACK: + rc = v52_rcv_pp(v5i, msg, &tp); + break; + case V52_CTRL_MSGT_ALLOCATION: + case V52_CTRL_MSGT_ALLOCATION_COMPLETE: + case V52_CTRL_MSGT_ALLOCATION_REJECT: + case V52_CTRL_MSGT_DE_ALLOCATION: + case V52_CTRL_MSGT_DE_ALLOCATION_COMPLETE: + case V52_CTRL_MSGT_DE_ALLOCATION_REJECT: + case V52_CTRL_MSGT_AUDIT: + case V52_CTRL_MSGT_AUDIT_COMPLETE: + case V52_CTRL_MSGT_AN_FAULT: + case V52_CTRL_MSGT_AN_FAULT_ACK: + case V52_CTRL_MSGT_PROTOCOL_ERROR: + rc = v52_rcv_bcc(v5i, msg, &tp); + break; + case V52_CTRL_MSGT_LCP_LINK_CTRL: + case V52_CTRL_MSGT_LCP_LINK_CTRL_ACK: + rc = v52_rcv_lcp(v5i, msg, &tp); + break; + default: + LOGP(DV5, LOGL_ERROR, "Received unsupported message type %s\n", + osmo_tlv_prot_msg_name(v51_ctrl_msg_tlv, l3h->msg_type); + } +} diff --git a/v5x_protocol.h b/v5x_protocol.h new file mode 100644 index 0000000..0bc3f1b --- /dev/null +++ b/v5x_protocol.h @@ -0,0 +1,300 @@ +#pragma once +#include +#include +#include + +/* Definitions related to the V5.1 and V5.2 interface between AN (access + * network) and LE (local exchange) as per ITU-T G.964 + G.965. + * + * (C) 2021 by Harald Welte + */ + +/***********************************************************************/ +/* protocol wire format */ +/***********************************************************************/ + +/* Table 14/G.964 */ +#define V51_CTRL_PDISC 0x48 + +/* Table 1/G.965 + Table 1/G.964 */ +#define V51_DLADDR_PSTN 8176 +#define V51_DLADDR_CTRL 8177 +#define V52_DLADDR_BCC 8178 +#define V52_DLADDR_PROTECTION 8179 +#define V52_DLADDR_LCP 8180 + +/* G.965 Section 13.1 */ +struct v51_l3_hdr { + uint8_t pdisc; + uint16_t l3_addr; /* with C/R and EA bits! */ + uint8_t msg_type; + uint8_t payload[0]; +} __attribute__ ((packed)); + +/* G.964 Section 14.4.2.3 Figure 33 + 34 */ +static inline bool v51_l3_addr_is_isdn(uint16_t in) +{ + /* extract two bytes */ + uint8_t b1 = in >> 8; + uint8_t b2 = in & 0xff; + + if ((b1 & 0x03) == 0x00) && (b2 & 0x01) + return true; + + return false; +} + +static inline uint16_t v51_l3_addr_dec(uint16_t in, bool *is_isdn) +{ + /* extract two bytes */ + uint8_t b1 = in >> 8; + uint8_t b2 = in & 0xff; + + if (v51_l3_addr_is_isdn(in)) { + /* remove EA/C/R bits */ + uint8_t upper = b1 >> 2; + uint8_t lower = b2 >> 1; + if (is_isdn) + *is_isdn = true; + /* shift them together */ + return lower | (upper << 7); + } else{ + uint8_t upper = b1 >> 1; + uint8_t lower = b2; + if (is_isdn) + *is_isdn = false; + return lower | (upper << 8); + } +} + +static inline uint16_t v51_l3_addr_enc(uint16_t in, bool is_isdn) +{ + uint8_t b1, b2; + if (is_isdn) { + uint8_t upper = in >> 7; + uint8_t lower = in & 0x7f; + b1 = upper << 2; + b2 = (lower << 1) | 0x01; + } else { + uint8_t upper = in >> 8; + uint8_t lower = in & 0xff; + b1 = (upper << 1) | 0x01; + b2 = lower; + } + return (b1 << 8) | b2; +} + +/* Table 15 + 52/G.964 */ +enum v51_ctrl_msg_type { + /* 000xxxx: PSTN protocol message types */ + V51_CTRL_MSGT_ESTABLISH = 0x00, + V51_CTRL_MSGT_ESTABLISH_ACK = 0x01, + V51_CTRL_MSGT_SIGNAL = 0x02, + V51_CTRL_MSGT_SIGNAL_ACK = 0x03, + V51_CTRL_MSGT_DISCONNECT = 0x08, + V51_CTRL_MSGT_DISCONNECT_ACK = 0x09, + V51_CTRL_MSGT_STATUS_ENQUIRY = 0x0c, + V51_CTRL_MSGT_STATUS = 0x0d, + V51_CTRL_MSGT_PROTOCOL_PARAMETER = 0x0e, + /* 0010xxx: Control protocol message types */ + V51_CTRL_MSGT_PORT_CTRL = 0x10, + V51_CTRL_MSGT_PORT_CTRL_ACK = 0x11, + V51_CTRL_MSGT_COMMON_CTRL = 0x12, + V51_CTRL_MSGT_COMMON_CTRL_ACK = 0x13, + /* 0011xxx: Protection protocol message types */ + V52_CTRL_MSGT_PP_SWITCH_OVER_REQ = 0x18, + V52_CTRL_MSGT_PP_SWITCH_OVER_COM = 0x19, + V52_CTRL_MSGT_PP_OS_SWITCH_OVER_COM = 0x1a, + V52_CTRL_MSGT_PP_SWITCH_OVER_ACK = 0x1b, + V52_CTRL_MSGT_PP_SWITCH_OVER_REJECT = 0x1c, + V52_CTRL_MSGT_PP_PROTOCOL_ERROR = 0x1d, + V52_CTRL_MSGT_PP_RESET_SN_COM = 0x1e, + V52_CTRL_MSGT_PP_RESET_SN_ACK = 0x1f, + /* 010xxxx: BCC protocol message types */ + V52_CTRL_MSGT_ALLOCATION = 0x20, + V52_CTRL_MSGT_ALLOCATION_COMPLETE = 0x21, + V52_CTRL_MSGT_ALLOCATION_REJECT = 0x22, + V52_CTRL_MSGT_DE_ALLOCATION = 0x23, + V52_CTRL_MSGT_DE_ALLOCATION_COMPLETE = 0x24, + V52_CTRL_MSGT_DE_ALLOCATION_REJECT = 0x25, + V52_CTRL_MSGT_AUDIT = 0x26, + V52_CTRL_MSGT_AUDIT_COMPLETE = 0x27, + V52_CTRL_MSGT_AN_FAULT = 0x28, + V52_CTRL_MSGT_AN_FAULT_ACK = 0x29, + V52_CTRL_MSGT_PROTOCOL_ERROR = 0x2a, + /* 0110xxx: Link control protocol message types */ + V52_CTRL_MSGT_LCP_LINK_CTRL = 0x30, + V52_CTRL_MSGT_LCP_LINK_CTRL_ACK = 0x31, +}; + +extern const struct value_string v51_ctrl_msg_typ_str[]; + + +/* Table 17 + 53/G.964 */ +enum v51_ctrl_iei { + /* single byte, only upper nibble matches IEI */ + V51_CTRL_IEI_PULSE_NOTIFICATION = 0xc0, + V51_CTLR_IEI_LINE_NOTIFICATION = 0x80, + V51_CTLR_IEI_STATE = 0x90, + V51_CTLR_IEI_AUTONOMOUS_SIG_SEQ = 0xa0, + V51_CTLR_IEI_SEQUENCE_RESPONSE = 0xb0, + /* single byte: ISDN */ + V51_CTRL_IEI_PERFORMANCE_GRADING = 0xe0, + V51_CTRL_IEI_REJECTION_CAUSE = 0xf0, + /* variable length: PSTN */ + V51_CTRL_IEI_SEQUENCE_NR = 0x00, + V51_CTRL_IEI_CADENCED_RINGING = 0x01, + V51_CTRL_IEI_PULSED_SIGNAL = 0x02, + V51_CTRL_IEI_STEADY_SIGNAL = 0x03, + V51_CTRL_IEI_DIGIT_SIGNAL = 0x04, + V51_CTRL_IEI_RECOGNITION_TIME = 0x10, + V51_CTRL_IEI_ENABLE_AUTONOMOUS_ACK = 0x11, + V51_CTRL_IEI_DISABLE_AUTONOMOUS_ACK = 0x12, + V51_CTRL_IEI_CAUSE = 0x13, + V51_CTRL_IEI_RESOURCE_UNAVAILABLE = 0x14, + V51_CTRL_IEI_ENABLE_METERING = 0x32, + V51_CTRL_IEI_METERING_REPORT = 0x33, + V51_CTRL_IEI_ATTENUATION = 0x34, + /* variable length: ISDN */ + V51_CTRL_IEI_CTRL_F_ELEMENT = 0x20, + V51_CTRL_IEI_CTRL_F_ID = 0x21, + V51_CTRL_IEI_VARIANT = 0x22, + V51_CTRL_IEI_INTERFACE_ID = 0x23, + /* variable length: LCP */ + V52_CTRL_IEI_LCP_LINK_CTRL_FUNCTION = 0x30, + /* variable length: BCC */ + V52_CTRL_IEI_BCC_USER_PORT_ID = 0x40, + V52_CTRL_IEI_BCC_ISDN_PORT_TS_ID = 0x41, + V52_CTRL_IEI_BCC_V5_TS_ID = 0x42, + V52_CTRL_IEI_BCC_MULTI_TS_MAP = 0x43, + V52_CTRL_IEI_BCC_REJECT_CAUSE = 0x44, + V52_CTRL_IEI_BCC_PROTOCOL_ERROR_CAUSE = 0x45, + V52_CTRL_IEI_BCC_CONNECTION_INCOMPLETE = 0x46, + V52_CTRL_IEI_BCC_INFO_TRANSFER_CAPABILITY = 0x47, + /* variable-length: Protection */ + V52_CTRL_IEI_PP_SEQUENCE_NR = 0x50, + V52_CTRL_IEI_PP_PHYSICAL_C_CHAN_ID = 0x51, + V52_CTRL_IEI_PP_REJECTION_CAUSE = 0x52, + V52_CTRL_IEI_PP_PROTOCOL_ERROR_CAUSE = 0x53, +}; + +extern const struct value_string v51_ctrl_iei_str[]; + +extern const struct tlv_definition v51_ctrl_tlv_def[] = { + /* single byte: PSTN / G.964 Table 17 */ + [V51_CTRL_IEI_PULSE_NOTIFICATION] = TLV_TYPE_SINGLE_TV, + [V51_CTLR_IEI_LINE_NOTIFICATION] = TLV_TYPE_SINGLE_TV, + [V51_CTLR_IEI_STATE] = TLV_TYPE_SINGLE_TV, + [V51_CTLR_IEI_AUTONOMOUS_SIG_SEQ] = TLV_TYPE_SINGLE_TV, + [V51_CTLR_IEI_SEQUENCE_RESPONSE] = TLV_TYPE_SIGNLE_TV, + /* single byte: ISDN / G.964 Table 53 */ + [V51_CTRL_IEI_PERFORMANCE_GRADING] = TLV_TYPE_SINGLE_TV, + [V51_CTRL_IEI_REJECTION_CAUSE] = TLV_TYPE_SINGLE_TV, + + /* variable length: PSTN / G.964 Table 17 */ + [V51_CTRL_IEI_SEQUENCE_NR] = TLV_TYPE_TLV, + [V51_CTRL_IEI_CADENCED_RINGING] = TLV_TYPE_TLV, + [V51_CTRL_IEI_PULSED_SIGNAL] = TLV_TYPE_TLV, + [V51_CTRL_IEI_STEADY_SIGNAL] = TLV_TYPE_TLV, + [V51_CTRL_IEI_DIGIT_SIGNAL] = TLV_TYPE_TLV, + [V51_CTRL_IEI_RECOGNITION_TIME] = TLV_TYPE_TLV, + [V51_CTRL_IEI_ENABLE_AUTONOMOUS_ACK] = TLV_TYPE_TLV, + [V51_CTRL_IEI_DISABLE_AUTONOMOUS_ACK] = TLV_TYPE_TLV, + [V51_CTRL_IEI_CAUSE] = TLV_TYPE_TLV, + [V51_CTRL_IEI_RESOURCE_UNAVAILABLE] = TLV_TYPE_TLV, + [V51_CTRL_IEI_ENABLE_METERING] = TLV_TYPE_TLV, + [V51_CTRL_IEI_METERING_REPORT] = TLV_TYPE_TLV, + [V51_CTRL_IEI_ATTENUATION] = TLV_TYPE_TLV, + + /* variable length: ISDN / G.964 Table 53 */ + [V51_CTRL_IEI_CTRL_F_ELEMENT] = TLV_TYPE_TLV, + [V51_CTRL_IEI_CTRL_F_ID] = TLV_TYPE_TLV, + [V51_CTRL_IEI_VARIANT] = TLV_TYPE_TLV, + [V51_CTRL_IEI_INTERFACE_ID] = TLV_TYPE_TLV, + + /* variable length: LCP / G.965 Table FIXME */ + [V52_CTRL_IEI_LCP_LINK_CTRL_FUNCTION] = TLV_TYPE_TLV, + /* variable length: BCC */ + [V52_CTRL_IEI_BCC_USER_PORT_ID] = TLV_TYPE_TLV, + [V52_CTRL_IEI_BCC_ISDN_PORT_TS_ID] = TLV_TYPE_TLV, + [V52_CTRL_IEI_BCC_V5_TS_ID] = TLV_TYPE_TLV, + [V52_CTRL_IEI_BCC_MULTI_TS_MAP] = TLV_TYPE_TLV, + [V52_CTRL_IEI_BCC_REJECT_CAUSE] = TLV_TYPE_TLV, + [V52_CTRL_IEI_BCC_PROTOCOL_ERROR_CAUSE] = TLV_TYPE_TLV, + [V52_CTRL_IEI_BCC_CONNECTION_INCOMPLETE] = TLV_TYPE_TLV, + [V52_CTRL_IEI_BCC_INFO_TRANSFER_CAPABILITY] = TLV_TYPE_TLV, + /* variable-length: Protection */ + [V52_CTRL_IEI_PP_SEQUENCE_NR] = TLV_TYPE_TLV, + [V52_CTRL_IEI_PP_PHYSICAL_C_CHAN_ID] = TLV_TYPE_TLV, + [V52_CTRL_IEI_PP_REJECTION_CAUSE] = TLV_TYPE_TLV, + [V52_CTRL_IEI_PP_PROTOCOL_ERROR_CAUSE] = TLV_TYPE_TLV, +}; + +extern const struct osmo_tlv_prot_def v51_ctrl_msg_tlv; + +/* 14.4.2.5.4 Table 56/G.964 - Coding of Control Function Element */ +enum v51_ctrl_func_el { + V51_CTRL_FE101_ACTIVATE_ACCESS = 0x01, + V51_CTRL_FE102_ACT_INIT_BY_USER = 0x02, + V51_CTRL_FE103_DS_ACTIVATED = 0x03, + V51_CTRL_FE104_ACCESS_ACTIVATED = 0x04, + V51_CTRL_FE105_DEACTIVATE_ACCESS = 0x05, + V51_CTRL_FE106_ACCESS_DEACTIVATED = 0x06, + V51_CTRL_FE201_UNBLOCK = 0x11, + V51_CTRL_FE203_BLOCK = 0x13, + V51_CTRL_FE205_BLOCK_REQ = 0x15, + V51_CTRL_FE206_PERFORMANCE_GRADING = 0x16, + V51_CTRL_FE207_D_CHANNEL_BLOCK = 0x17, + V51_CTRL_FE208_D_CHANNEL_UNBLOCK = 0x18, +}; + +extern const struct value_string v51_ctrl_func_el_str[]; + +/* 14.4.2.5.5 Table 57/G.964 - Coding of Control Function ID */ +enum v51_ctrl_func_id { + V51_CTRL_ID_VERIFY_RE_PROVISIONING = 0x00, + V51_CTRL_ID_READY_FOR_RE_PROVISIONING = 0x01, + V51_CTRL_ID_NOT_READY_FOR_RE_PROVISIONING = 0x02, + V51_CTRL_ID_SWITCH_OVER_TO_NEW_VARIANT = 0x03, + V51_CTRL_ID_RE_PROVISIONING_STARTED = 0x04, + V51_CTRL_ID_CANNOT_RE_PROVISION = 0x05, + V51_CTRL_ID_REQUEST_VARIANT_AND_INTERFACE_ID = 0x06, + V51_CTRL_ID_VARIANT_AND_INTERFACE_ID = 0x07, + V51_CTRL_ID_BLOCKING_STARTED = 0x08, + V51_CTRL_ID_RESTART_REQUEST = 0x10, + V51_CTRL_ID_RESTART_COMPLETE = 0x11, +}; + +extern const struct value_string v51_ctrl_func_id_str[]; + +/* 13.4.7.9 Cause */ +enum v51_cause_type { + V51_CTRL_CAUSE_T_RESP_TO_STATUS_ENQ = 0x00, + V51_CTRL_CAUSE_T_L3_ADDRESS_ERROR = 0x03, + V51_CTRL_CAUSE_T_MSG_TYPE_UNRECOGNIZED = 0x04, + V51_CTRL_CAUSE_T_OUT_OF_SEQUENCE_IE = 0x05, + V51_CTRL_CAUSE_T_REPEATED_OPT_IE = 0x06, + V51_CTRL_CAUSE_T_MAND_IE_MISSING = 0x07, + V51_CTRL_CAUSE_T_UNRECOGNIZED_IE = 0x08, + V51_CTRL_CAUSE_T_MAND_IE_CONTENT_ERROR = 0x09, + V51_CTRL_CAUSE_T_OPT_IE_CONTENT_ERROR = 0x0a, + V51_CTRL_CAUSE_T_MSG_INCOMP_PATH_STATE = 0x0b, + V51_CTRL_CAUSE_T_REPEATED_MAND_IE = 0x0c, + V51_CTRL_CAUSE_T_TOO_MANY_IE = 0x0d, +}; + +extern const struct value_string v51_cause_type_str[]; + +const struct osmo_tlv_prot_def v51_ctrl_msg_tlv; + +/* 16.3.2.2 Table 22/G.965 */ +enum v52_link_ctrl_func { + V52_LCP_FE_IDReq = 0, + V52_LCP_FE_IDAck = 1, + V52_LCP_FE_IDRel = 2, + V52_LCP_FE_IDRej = 3, + V52_LCP_FE_301_302_LINK_UNBLOCK = 4, + V52_LCP_FE_303_304_LINK_BLOCK = 5, + V52_LCP_FE_305_DEF_LINK_BLOCK_REQ = 6, + V52_LCP_FE_306_NON_DEF_LINK_BLOCK_REQ = 7, +};