From f6977584ff64c4152a8cdc941d38761c597c2604 Mon Sep 17 00:00:00 2001 From: Harald Welte Date: Thu, 29 Nov 2018 13:47:39 +0100 Subject: WIP: CRC and FSM for IuUP (user plane) as used in 3G RTP data Change-Id: Ibe356fa7b1abaca0091e368db8478e79c09c6cb0 --- include/Makefile.am | 1 + include/osmocom/gsm/prim.h | 3 + include/osmocom/gsm/protocol/gsm_25_415.h | 116 ++++++ src/gsm/Makefile.am | 2 +- src/gsm/iu_up.c | 598 ++++++++++++++++++++++++++++++ src/gsm/iuup_crc.c | 95 +++++ 6 files changed, 814 insertions(+), 1 deletion(-) create mode 100644 include/osmocom/gsm/protocol/gsm_25_415.h create mode 100644 src/gsm/iu_up.c create mode 100644 src/gsm/iuup_crc.c diff --git a/include/Makefile.am b/include/Makefile.am index 59a5feda..c7f88f91 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -114,6 +114,7 @@ nobase_include_HEADERS = \ osmocom/gsm/protocol/gsm_09_02.h \ osmocom/gsm/protocol/gsm_12_21.h \ osmocom/gsm/protocol/gsm_23_003.h \ + osmocom/gsm/protocol/gsm_25_415.h \ osmocom/gsm/protocol/gsm_29_118.h \ osmocom/gsm/protocol/gsm_44_318.h \ osmocom/gsm/protocol/ipaccess.h \ diff --git a/include/osmocom/gsm/prim.h b/include/osmocom/gsm/prim.h index 386b7d89..85725e22 100644 --- a/include/osmocom/gsm/prim.h +++ b/include/osmocom/gsm/prim.h @@ -14,4 +14,7 @@ enum osmo_gsm_sap { SAP_BSSGP_LL, SAP_BSSGP_NM, SAP_BSSGP_PFM, + + SAP_IUUP_TNL, + SAP_IUUP_RNL, }; diff --git a/include/osmocom/gsm/protocol/gsm_25_415.h b/include/osmocom/gsm/protocol/gsm_25_415.h new file mode 100644 index 00000000..90f76836 --- /dev/null +++ b/include/osmocom/gsm/protocol/gsm_25_415.h @@ -0,0 +1,116 @@ +#pragma once +/* Iu User Plane (IuUP) Definitions as per 3GPP TS 25.415 */ +/* (C) 2017 by Harald Welte + * All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include + +/* 3GPP TS 25.415 Section 6.6.2.1 */ +struct iuup_pdutype0_hdr { + /* control part */ + uint8_t frame_nr:4, + pdu_type:4; + uint8_t rfci:6, + fqc:2; + /* checksum part */ + uint16_t payload_crc:10, + header_crc:6; + /* payload part */ + uint8_t payload[0]; +} __attribute__((packed)); + +/* 3GPP TS 25.415 Section 6.6.2.2 */ +struct iuup_pdutype1_hdr { + /* control part */ + uint8_t frame_nr:4, + pdu_type:4; + uint8_t rfci:6, + fqc:2; + /* checksum part */ + uint8_t spare:2, + header_crc:6; + /* payload part */ + uint8_t payload[0]; +} __attribute__((packed)); + +/* 3GPP TS 25.415 Section 6.6.2.3 */ +struct iuup_pdutype14_hdr { + /* control part */ + uint8_t frame_nr:2, + ack_nack:2, + pdu_type:4; + uint8_t proc_ind:4, + mode_version:4; + /* checksum part */ + uint16_t payload_crc:10, + header_crc:6; + /* payload part */ + uint8_t payload[0]; +} __attribute__((packed)); + +/* 3GPP TS 25.415 Section 6.6.2 + 6.6.3.1 */ +enum iuup_pdu_type { + IUUP_PDU_T_DATA_CRC = 0, + IUUP_PDU_T_DATA_NOCRC = 1, + IUUP_PDU_T_CONTROL = 14, +}; + +/* 3GPP TS 25.415 Section 6.6.3.2 */ +enum iuup_ack_nack { + IUUP_AN_PROCEDURE = 0, + IUUP_AN_ACK = 1, + IUUP_AN_NACK = 2, +}; + +/* 3GPP TS 25.415 Section 6.6.3.5 */ +enum iuup_fqc { + IUUP_FQC_FRAME_GOOD = 0, + IUUP_FQC_FRAME_BAD = 1, + IUUP_FQC_FRAME_BAD_RADIO= 2, +}; + +/* 3GPP TS 25.415 Section 6.6.3.7 */ +enum iuup_procedure { + IUUP_PROC_INIT = 0, + IUUP_PROC_RATE_CTRL = 1, + IUUP_PROC_TIME_ALIGN = 2, + IUUP_PROC_ERR_EVENT = 3, +}; + + +/* 3GPP TS 25.415 Section 6.6.3.15 */ +enum iuup_error_distance { + IUUP_ERR_DIST_LOCAL = 0, + IUUP_ERR_DIST_FIRST_FWD = 1, + IUUP_ERR_DIST_SECOND_FWD = 2, + IUUP_ERR_DIST_RESERVED = 3, +}; + + +/* 3GPP TS 25.415 Section 6.6.3.16 */ +enum iuup_error_cause { + IUUP_ERR_CAUSE_CRC_ERR_HDR = 0, + IUUP_ERR_CAUSE_CRC_ERR_DATA = 1, + IUUP_ERR_CAUSE_UNEXPECTED_FN = 2, + IUUP_ERR_CAUSE_FRAME_LOSS = 3, + IUUP_ERR_CAUSE_UNKNOWN_PDUTYPE = 4, + IUUP_ERR_CAUSE_UNKNOWN_PROC = 5, + IUUP_ERR_CAUSE_UNKNNOWN_RES_VAL = 6, + IUUP_ERR_CAUSE_UNKNNOWN_FIELD = 7, + IUUP_ERR_CAUSE_FRAME_TOO_SHORT = 8, + IUUP_ERR_CAUSE_MISSING_FIELDS = 9, + IUUP_ERR_CAUSE_UNEXPECTED_PDU_T = 16, + IUUP_ERR_CAUSE_UNEXPECTED_PROC = 18, + IUUP_ERR_CAUSE_UNEXPECTED_RFCI = 19, + IUUP_ERR_CAUSE_UNEXPECTED_VALUE = 20, + IUUP_ERR_CAUSE_INIT_FAILURE = 42, + IUUP_ERR_CAUSE_INIT_FAILURE_NET_TMR = 43, + IUUP_ERR_CAUSE_INIT_FAILURE_REP_NACK = 44, + IUUP_ERR_CAUSE_RATE_CTRL_FAILURE= 45, + IUUP_ERR_CAUSE_ERR_EVENT_FAIL = 46, + IUUP_ERR_CAUSE_TIME_ALIGN_NOTSUPP = 47, + IUUP_ERR_CAUSE_REQ_TIME_ALIGN_NOTPOSS = 48, +}; diff --git a/src/gsm/Makefile.am b/src/gsm/Makefile.am index e28ea335..96d08be0 100644 --- a/src/gsm/Makefile.am +++ b/src/gsm/Makefile.am @@ -31,7 +31,7 @@ libgsmint_la_SOURCES = a5.c rxlev_stat.c tlv_parser.c comp128.c comp128v23.c \ milenage/milenage.c gan.c ipa.c gsm0341.c apn.c \ gsup.c gprs_gea.c gsm0503_conv.c oap.c gsm0808_utils.c \ gsm23003.c mncc.c bts_features.c oap_client.c \ - gsm29118.c + gsm29118.c iu_up.c libgsmint_la_LDFLAGS = -no-undefined libgsmint_la_LIBADD = $(top_builddir)/src/libosmocore.la diff --git a/src/gsm/iu_up.c b/src/gsm/iu_up.c new file mode 100644 index 00000000..e6fd52dd --- /dev/null +++ b/src/gsm/iu_up.c @@ -0,0 +1,598 @@ +/*! \file iu_up.c + * IuUP (Iu User Plane) according to 3GPP TS 25.415 */ +/* + * (C) 2017 by Harald Welte + * + * 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 + +#include +#include + +/*********************************************************************** + * CRC Calculation + ***********************************************************************/ + +/* Section 6.6.3.8 Header CRC */ +const struct osmo_crc8gen_code iuup_hdr_crc_code = { + .bits = 6, + .poly = 47, + .init = 0, + .remainder = 0, +}; + +/* Section 6.6.3.9 Payload CRC */ +const struct osmo_crc16gen_code iuup_data_crc_code = { + .bits = 10, + .poly = 563, + .init = 0, + .remainder = 0, +}; + +static int iuup_get_payload_offset(const uint8_t *iuup_pdu) +{ + uint8_t pdu_type = iuup_pdu[0] >> 4; + switch (pdu_type) { + case 0: + case 14: + return 4; + case 1: + return 3; + default: + return -1; + } +} + +int osmo_iuup_compute_payload_crc(const uint8_t *iuup_pdu, unsigned int pdu_len) +{ + ubit_t buf[1024*8]; + uint8_t pdu_type; + int offset, payload_len_bytes; + + if (pdu_len < 1) + return -1; + + pdu_type = iuup_pdu[0] >> 4; + + /* Type 1 has no CRC */ + if (pdu_type == 1) + return 0; + + offset = iuup_get_payload_offset(iuup_pdu); + if (offset < 0) + return offset; + + if (pdu_len < offset) + return -1; + + payload_len_bytes = pdu_len - offset; + osmo_pbit2ubit(buf, iuup_pdu+offset, payload_len_bytes*8); + return osmo_crc16gen_compute_bits(&iuup_data_crc_code, buf, payload_len_bytes*8); +} + +int osmo_iuup_compute_header_crc(const uint8_t *iuup_pdu, unsigned int pdu_len) +{ + ubit_t buf[2*8]; + + if (pdu_len < 2) + return -1; + + osmo_pbit2ubit(buf, iuup_pdu, 2*8); + return osmo_crc8gen_compute_bits(&iuup_hdr_crc_code, buf, 2*8); +} + +/*********************************************************************** + * Primitives towards the lower layers (typically RTP transport) + ***********************************************************************/ +enum osmo_tnl_iuup_prim_type { + OSMO_TNL_IUUP_UNITDATA, +}; + +struct osmo_tnl_iuup_prim { + struct osmo_prim_hdr oph; +}; + +/*********************************************************************** + * Primitives towards the upper layers at the RNL SAP + ***********************************************************************/ + +/* 3GPP TS 25.415 Section 7.2.1 */ +enum osmo_rnl_iuup_prim_type { + OSMO_RNL_IUUP_CONFIG, + OSMO_RNL_IUUP_DATA, + OSMO_RNL_IUUP_STATUS, + OSMO_RNL_IUUP_UNIT_DATA, +}; + +struct osmo_rnl_iuup_config { + /* transparent (true) or SMpSDU (false) */ + bool transparent; + /* should we actively transmit INIT in SmpSDU mode? */ + bool active; +}; + +struct osmo_rnl_iuup_data { + uint8_t rfci; + uint8_t frame_nr; + uint8_t fqc; +}; + +struct osmo_rnl_iuup_status { + enum iuup_procedure procedure; + union { + struct { + enum iuup_error_cause cause; + enum iuup_error_distance distance; + } error_event; + struct { + } initialization; + struct { + } rate_control; + struct { + } time_alignment; + } u; +}; + +/* SAP on the upper side of IuUP, towards the user */ +struct osmo_rnl_iuup_prim { + struct osmo_prim_hdr oph; + union { + struct osmo_rnl_iuup_config config; + struct osmo_rnl_iuup_data data; + struct osmo_rnl_iuup_status status; + //struct osmo_rnl_iuup_unitdata unitdata; + } u; +}; + +/*********************************************************************** + * Internal State / FSM (Annex B) + ***********************************************************************/ + +#define S(x) (1 << (x)) + +struct osmo_timer_nt { + uint32_t t_ms; /* time in ms */ + uint32_t n; /* number of repetitons */ + struct osmo_timer_list timer; +}; + +struct osmo_iuup_instance { + struct { + bool transparent; + bool active; + bool use_type0_crc; + } config; + struct osmo_fsm_inst *fi; + struct { + struct osmo_timer_nt init; + struct osmo_timer_nt ta; + struct osmo_timer_nt rc; + } timer; + /* call-back function to pass primitives up to the user */ + osmo_prim_cb user_prim_cb; + osmo_prim_cb transport_prim_cb; +}; + +enum iuup_fsm_state { + IUUP_FSM_ST_NULL, + IUUP_FSM_ST_INIT, + IUUP_FSM_ST_TrM_DATA_XFER_READY, + IUUP_FSM_ST_SMpSDU_DATA_XFER_READY, +}; + +enum iuup_fsm_event { + IUUP_FSM_EVT_IUUP_CONFIG_REQ, + IUUP_FSM_EVT_IUUP_DATA_REQ, + IUUP_FSM_EVT_IUUP_DATA_IND, + IUUP_FSM_EVT_SSASAR_UNITDATA_REQ, + IUUP_FSM_EVT_SSASAR_UNITDATA_IND, + IUUP_FSM_EVT_IUUP_UNITDATA_REQ, + IUUP_FSM_EVT_IUUP_UNITDATA_IND, + IUUP_FSM_EVT_INIT, + IUUP_FSM_EVT_LAST_INIT_ACK, + IUUP_FSM_EVT_INIT_NACK, +}; + +static const struct value_string iuup_fsm_event_names[] = { + { IUUP_FSM_EVT_IUUP_CONFIG_REQ, "IuUP-CONFIG.req" }, + { IUUP_FSM_EVT_IUUP_DATA_REQ, "IuUP-DATA.req" }, + { IUUP_FSM_EVT_IUUP_DATA_IND, "IuUP-DATA.ind" }, + { IUUP_FSM_EVT_SSASAR_UNITDATA_REQ, "SSSAR-UNITDATA.req" }, + { IUUP_FSM_EVT_SSASAR_UNITDATA_IND, "SSSAR-UNITDATA.ind" }, + { IUUP_FSM_EVT_IUUP_UNITDATA_REQ, "IuUP-UNITDATA.req" }, + { IUUP_FSM_EVT_IUUP_UNITDATA_IND, "IuUP-UNITDATA.ind" }, + { IUUP_FSM_EVT_INIT, "INIT" }, + { IUUP_FSM_EVT_LAST_INIT_ACK, "LAST_INIT_ACK" }, + { IUUP_FSM_EVT_INIT_NACK, "INIT_NACK" }, + { 0, NULL } +}; + +static inline uint8_t iuup_get_pdu_type(const uint8_t *data) +{ + return data[0] >> 4; +} + +static inline uint8_t iuup_get_hdr_crc(const uint8_t *data) +{ + return data[2] >> 2; +} + +static void iuup_fsm_null(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct osmo_iuup_instance *iui = fi->priv; + struct osmo_rnl_iuup_prim *user_prim = NULL; + + switch (event) { + case IUUP_FSM_EVT_IUUP_CONFIG_REQ: + user_prim = data; + if (user_prim->u.config.transparent) + osmo_fsm_inst_state_chg(fi, IUUP_FSM_ST_TrM_DATA_XFER_READY, 0, 0); + else { + osmo_fsm_inst_state_chg(fi, IUUP_FSM_ST_INIT, 0, 0); + if (iui->config.active) { + /* FIXME: Active side: Send Frame + Start timer */ + } + } + break; + } +} + +/* transform a RNL primitive into a TNL primitive (down the stack) */ +static struct osmo_tnl_iuup_prim *rnl_to_tnl_data(struct osmo_iuup_instance *iui, + struct osmo_rnl_iuup_prim *rip) +{ + struct osmo_tnl_iuup_prim *tip; + struct osmo_rnl_iuup_data dt; + struct msgb *msg; + + OSMO_ASSERT(OSMO_PRIM_HDR(&rip->oph) == OSMO_PRIM(OSMO_RNL_IUUP_DATA, PRIM_OP_REQUEST)); + + msg = rip->oph.msg; + dt = rip->u.data; + + /* pull up to the IuUP payload and push a new primitive header in front */ + msgb_pull_to_l3(msg); + + /* push the PDU TYPE 0 / 1 header in front of the payload */ + if (iui->config.use_type0_crc) { + struct iuup_pdutype0_hdr *h0 = msg->l2h = msgb_push(msg, sizeof(*h0)); + h0->frame_nr = dt.frame_nr; + h0->pdu_type = IUUP_PDU_T_DATA_CRC; + h0->rfci = dt.rfci; + h0->fqc = dt.fqc; + h0->header_crc = osmo_iuup_compute_header_crc(msgb_l2(msg), msgb_l2len(msg)); + h0->payload_crc = osmo_iuup_compute_payload_crc(msgb_l2(msg), msgb_l2len(msg)); + } else { + struct iuup_pdutype1_hdr *h1 = msg->l2h = msgb_push(msg, sizeof(*h1)); + h1->frame_nr = dt.frame_nr; + h1->pdu_type = IUUP_PDU_T_DATA_NOCRC; + h1->rfci = dt.rfci; + h1->fqc = dt.fqc; + h1->header_crc = osmo_iuup_compute_header_crc(msgb_l2(msg), msgb_l2len(msg)); + h1->spare = 0; + } + + tip = (struct osmo_tnl_iuup_prim *) msgb_push(msg, sizeof(*tip)); + osmo_prim_init(&tip->oph, SAP_IUUP_TNL, OSMO_TNL_IUUP_UNITDATA, PRIM_OP_REQUEST, msg); + + return tip; +} + +/* transform a TNL primitive into a RNL primitive (up the stack) */ +static struct osmo_rnl_iuup_prim *tnl_to_rnl_data(struct osmo_tnl_iuup_prim *tip) +{ + struct msgb *msg; + struct iuup_pdutype0_hdr *h0; + struct iuup_pdutype1_hdr *h1; + struct osmo_rnl_iuup_data dt; + struct osmo_rnl_iuup_prim *rip; + + msg = tip->oph.msg; + + OSMO_ASSERT(OSMO_PRIM_HDR(&tip->oph) == OSMO_PRIM(OSMO_TNL_IUUP_UNITDATA, PRIM_OP_INDICATION)); + + switch (iuup_get_pdu_type(msgb_l2(msg))) { + case IUUP_PDU_T_DATA_CRC: + h0 = (struct iuup_pdutype0_hdr *) msgb_l2(msg); + dt.rfci = h0->rfci; + dt.frame_nr = h0->frame_nr; + dt.fqc = h0->frame_nr; + break; + case IUUP_PDU_T_DATA_NOCRC: + h1 = (struct iuup_pdutype1_hdr *) msgb_l2(msg); + dt.rfci = h1->rfci; + dt.frame_nr = h1->frame_nr; + dt.fqc = h1->frame_nr; + break; + } + + /* pull up to the IuUP payload and push a new primitive header in front */ + msgb_pull_to_l3(msg); + rip = (struct osmo_rnl_iuup_prim *) msgb_push(msg, sizeof(*rip)); + osmo_prim_init(&rip->oph, SAP_IUUP_RNL, OSMO_RNL_IUUP_DATA, PRIM_OP_INDICATION, msg); + rip->u.data = dt; + + return rip; +} + +/* transparent mode data transfer */ +static void iuup_fsm_trm_data(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + //struct osmo_iuup_instance *iui = fi->priv; + + switch (event) { + case IUUP_FSM_EVT_IUUP_CONFIG_REQ: + osmo_fsm_inst_state_chg(fi, IUUP_FSM_ST_NULL, 0, 0); + break; + case IUUP_FSM_EVT_IUUP_DATA_REQ: + /* Data coming down from RNL (user) towards TNL (transport) */ + break; + case IUUP_FSM_EVT_IUUP_DATA_IND: + /* Data coming up from TNL (transport) towards RNL (user) */ + break; + case IUUP_FSM_EVT_IUUP_UNITDATA_REQ: + case IUUP_FSM_EVT_IUUP_UNITDATA_IND: + case IUUP_FSM_EVT_SSASAR_UNITDATA_REQ: + case IUUP_FSM_EVT_SSASAR_UNITDATA_IND: + /* no state change */ + break; + } +} + +static void iuup_fsm_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + //struct osmo_iuup_instance *iui = fi->priv; + + switch (event) { + case IUUP_FSM_EVT_IUUP_CONFIG_REQ: + /* the only permitted 'config req' type is the request to release the instance */ + osmo_fsm_inst_state_chg(fi, IUUP_FSM_ST_NULL, 0, 0); + break; + case IUUP_FSM_EVT_LAST_INIT_ACK: + /* last INIT ACK was received, transition to DATA_XFER_READY state */ + osmo_fsm_inst_state_chg(fi, IUUP_FSM_ST_SMpSDU_DATA_XFER_READY, 0, 0); + break; + } +} + +static void iuup_fsm_smpsdu_data(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct osmo_iuup_instance *iui = fi->priv; + struct osmo_rnl_iuup_prim *rip = NULL; + struct osmo_tnl_iuup_prim *tip = NULL; + + switch (event) { + case IUUP_FSM_EVT_IUUP_CONFIG_REQ: + rip = data; + osmo_fsm_inst_state_chg(fi, IUUP_FSM_ST_NULL, 0, 0); + break; + case IUUP_FSM_EVT_IUUP_DATA_REQ: + /* Data coming down from RNL (user) towards TNL (transport) */ + rip = data; + tip = rnl_to_tnl_data(iui, rip); + iui->transport_prim_cb(&tip->oph, iui); + break; + case IUUP_FSM_EVT_IUUP_DATA_IND: + /* Data coming up from TNL (transport) towards RNL (user) */ + tip = data; + rip = tnl_to_rnl_data(tip); + iui->user_prim_cb(&rip->oph, iui); + break; + case IUUP_FSM_EVT_IUUP_UNITDATA_REQ: + case IUUP_FSM_EVT_IUUP_UNITDATA_IND: + case IUUP_FSM_EVT_SSASAR_UNITDATA_REQ: + case IUUP_FSM_EVT_SSASAR_UNITDATA_IND: + /* no state change */ + break; + } +} + +static const struct osmo_fsm_state iuup_fsm_states[] = { + [IUUP_FSM_ST_NULL] = { + .in_event_mask = S(IUUP_FSM_EVT_IUUP_CONFIG_REQ), + .out_state_mask = S(IUUP_FSM_ST_INIT) | + S(IUUP_FSM_ST_TrM_DATA_XFER_READY), + .name = "NULL", + .action = iuup_fsm_null, + }, + [IUUP_FSM_ST_TrM_DATA_XFER_READY] = { + .in_event_mask = S(IUUP_FSM_EVT_IUUP_CONFIG_REQ) | + S(IUUP_FSM_EVT_IUUP_DATA_REQ) | + S(IUUP_FSM_EVT_IUUP_DATA_IND) | + S(IUUP_FSM_EVT_IUUP_UNITDATA_REQ) | + S(IUUP_FSM_EVT_IUUP_UNITDATA_IND) | + S(IUUP_FSM_EVT_SSASAR_UNITDATA_REQ) | + S(IUUP_FSM_EVT_SSASAR_UNITDATA_IND), + .out_state_mask = S(IUUP_FSM_ST_NULL), + .name = "TrM Data Transfer Ready", + .action = iuup_fsm_trm_data, + }, + [IUUP_FSM_ST_INIT] = { + //.in_event_mask = , + .out_state_mask = S(IUUP_FSM_ST_NULL) | + S(IUUP_FSM_ST_SMpSDU_DATA_XFER_READY), + .name = "Initialisation", + .action = iuup_fsm_init, + }, + [IUUP_FSM_ST_SMpSDU_DATA_XFER_READY] = { + .in_event_mask = S(IUUP_FSM_EVT_IUUP_DATA_REQ) | + S(IUUP_FSM_EVT_IUUP_DATA_IND), + .out_state_mask = S(IUUP_FSM_ST_NULL) | + S(IUUP_FSM_ST_INIT), + .name = "SMpSDU Data Transfer Ready", + .action = iuup_fsm_smpsdu_data, + }, +}; + +struct osmo_fsm iuup_fsm = { + .name = "IuUP", + .states = iuup_fsm_states, + .num_states = ARRAY_SIZE(iuup_fsm_states), + .log_subsys = DLMGCP, + .event_names = iuup_fsm_event_names, +}; + +static int iuup_verify_pdu(const uint8_t *data, unsigned int len) +{ + int header_crc_computed, payload_crc_computed; + uint8_t pdu_type = iuup_get_pdu_type(data); + struct iuup_pdutype0_hdr *t0h; + + if (len < 3) + return -EINVAL; + + header_crc_computed = osmo_iuup_compute_header_crc(data, len); + if (iuup_get_hdr_crc(data) != header_crc_computed) { + return -EIO; + } + switch (pdu_type) { + case IUUP_PDU_T_DATA_NOCRC: + if (len < 4) + return -EINVAL; + break; + case IUUP_PDU_T_DATA_CRC: + case IUUP_PDU_T_CONTROL: + t0h = (struct iuup_pdutype0_hdr *) data; + payload_crc_computed = osmo_iuup_compute_payload_crc(data, len); + if (t0h->payload_crc != payload_crc_computed) + return -EIO; + break; + default: + return -EINVAL; + } + return 0; +} + +/* A IuUP TNL SAP primitive from transport (lower layer) */ +int osmo_iuup_tnl_prim_up(struct osmo_iuup_instance *inst, struct osmo_prim_hdr *oph) +{ + struct osmo_tnl_iuup_prim *tnp = (struct osmo_tnl_iuup_prim *) oph; + struct iuup_pdutype14_hdr *t14h; + int rc = 0; + + OSMO_ASSERT(oph->sap == SAP_IUUP_TNL); + + switch (OSMO_PRIM_HDR(oph)) { + case OSMO_PRIM(OSMO_TNL_IUUP_UNITDATA, PRIM_OP_INDICATION): + if (iuup_verify_pdu(msgb_l2(oph->msg), msgb_l2len(oph->msg)) < 0) { + LOGPFSML(inst->fi, LOGL_NOTICE, "Discarding invalid IuUP PDU"); + /* don't return error as the caller is not responsible for the PDU which + * was transmitted from some remote peer */ + return 0; + } + switch (iuup_get_pdu_type(msgb_l2(oph->msg))) { + case IUUP_PDU_T_DATA_CRC: + oph->msg->l3h = msgb_l2(oph->msg) + sizeof(struct iuup_pdutype0_hdr); + rc = osmo_fsm_inst_dispatch(inst->fi, IUUP_FSM_EVT_IUUP_DATA_IND, tnp); + break; + case IUUP_PDU_T_DATA_NOCRC: + oph->msg->l3h = msgb_l2(oph->msg) + sizeof(struct iuup_pdutype1_hdr); + rc = osmo_fsm_inst_dispatch(inst->fi, IUUP_FSM_EVT_IUUP_DATA_IND, tnp); + break; + case IUUP_PDU_T_CONTROL: + t14h = (struct iuup_pdutype14_hdr *) msgb_l2(oph->msg); + switch (t14h->ack_nack) { + case IUUP_AN_PROCEDURE: + switch (t14h->proc_ind) { + case IUUP_PROC_INIT: + rc = osmo_fsm_inst_dispatch(inst->fi, IUUP_FSM_EVT_INIT, tnp); + break; + case IUUP_PROC_RATE_CTRL: + case IUUP_PROC_TIME_ALIGN: + case IUUP_PROC_ERR_EVENT: + LOGPFSML(inst->fi, LOGL_NOTICE, "Received Request for " + "unsupported IuUP procedure %u\n", t14h->proc_ind); + break; + default: + LOGPFSML(inst->fi, LOGL_NOTICE, "Received Request for " + "unknown IuUP procedure %u\n", t14h->proc_ind); + break; + } + break; + case IUUP_AN_ACK: + switch (t14h->proc_ind) { + case IUUP_PROC_INIT: + rc = osmo_fsm_inst_dispatch(inst->fi, + IUUP_FSM_EVT_LAST_INIT_ACK, tnp); + break; + default: + LOGPFSML(inst->fi, LOGL_ERROR, "Received ACK for " + "unknown IuUP procedure %u\n", t14h->proc_ind); + break; + } + break; + case IUUP_AN_NACK: + switch (t14h->proc_ind) { + case IUUP_PROC_INIT: + rc = osmo_fsm_inst_dispatch(inst->fi, + IUUP_FSM_EVT_INIT_NACK, tnp); + break; + default: + LOGPFSML(inst->fi, LOGL_ERROR, "Received NACK for " + "unknown IuUP procedure %u\n", t14h->proc_ind); + break; + } + break; + default: + LOGPFSML(inst->fi, LOGL_ERROR, "Received unknown IuUP ACK/NACK\n"); + break; + } + break; + default: + LOGPFSML(inst->fi, LOGL_NOTICE, "Received unknown IuUP PDU type %u\n", + iuup_get_pdu_type(msgb_l2(oph->msg))); + break; + } + break; + default: + /* exception: return an error code due to a wrong primitive */ + return -EINVAL; + } + + return rc; +} + +/* A IuUP RNL SAP primitive from user (higher layer) */ +int osmo_iuup_rnl_prim_down(struct osmo_iuup_instance *inst, struct osmo_prim_hdr *oph) +{ + struct osmo_rnl_iuup_prim *rnp = (struct osmo_rnl_iuup_prim *) oph; + int rc; + + OSMO_ASSERT(oph->sap == SAP_IUUP_RNL); + + switch (OSMO_PRIM_HDR(oph)) { + case OSMO_PRIM(OSMO_RNL_IUUP_CONFIG, PRIM_OP_REQUEST): + rc = osmo_fsm_inst_dispatch(inst->fi, IUUP_FSM_EVT_IUUP_CONFIG_REQ, rnp); + break; + case OSMO_PRIM(OSMO_RNL_IUUP_DATA, PRIM_OP_REQUEST): + rc = osmo_fsm_inst_dispatch(inst->fi, IUUP_FSM_EVT_IUUP_DATA_REQ, rnp); + break; + default: + rc = -EINVAL; + } + return rc; +} diff --git a/src/gsm/iuup_crc.c b/src/gsm/iuup_crc.c new file mode 100644 index 00000000..1cefe5a3 --- /dev/null +++ b/src/gsm/iuup_crc.c @@ -0,0 +1,95 @@ +#include +#include + +#include +#include + +static const struct osmo_crc8gen_code iuup_hdr_crc_code = { + .bits = 6, + .poly = 47, + .init = 0, + .remainder = 0, +}; + +static const struct osmo_crc16gen_code iuup_data_crc_code = { + .bits = 10, + .poly = 563, + .init = 0, + .remainder = 0, +}; + +static int iuup_get_payload_offset(const uint8_t *iuup_pdu) +{ + uint8_t pdu_type = iuup_pdu[0] >> 4; + switch (pdu_type) { + case 0: + case 14: + return 4; + case 1: + return 3; + default: + return -1; + } +} + +int osmo_iuup_compute_payload_crc(const uint8_t *iuup_pdu, unsigned int pdu_len) +{ + ubit_t buf[1024*8]; + uint8_t pdu_type; + int offset, payload_len_bytes; + + if (pdu_len < 1) + return -1; + + pdu_type = iuup_pdu[0] >> 4; + + /* Type 1 has no CRC */ + if (pdu_type == 1) + return 0; + + offset = iuup_get_payload_offset(iuup_pdu, pdu_len); + if (offset < 0) + return offset; + + if (pdu_len < offset) + return -1; + + payload_len_bytes = pdu_eln - offset; + osmo_pbit2ubit(buf, iuup_pdu+offset, payload_len_bytes*8); + return osmo_crc16gen_compute_bits(&iuup_data_crc_code, buf, payload_len_bytes*8); +} + +int osmo_iuup_compute_header_crc(const uint8_t *iuup_pdu, unsigned int pdu_len) +{ + ubit_t buf[2*8]; + + if (pdu_len < 2) + return -1; + + osmo_pbit2ubit(buf, iuup_pdu, 2*8); + return osmo_crc8gen_compute_bits(&iuup_hdr_crc_code, buf, 2*8); +} + +#if 0 +/* Frame 29 of MobileOriginatingCall_AMR.cap */ +const uint8_t iuup_frame29[] = { + 0x02, 0x00, 0x7d, 0x27, 0x55, 0x00, 0x88, 0xb6, 0x66, 0x79, 0xe1, 0xe0, + 0x01, 0xe7, 0xcf, 0xf0, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + + +int main(int argc, char **argv) +{ + ubit_t buf[1024*8]; + int rc; + + osmo_pbit2ubit(buf, iuup_frame29, 2*8); + rc = osmo_crc8gen_compute_bits(&iuup_hdr_crc_code, buf, 2*8); + printf("Header CRC = 0x%02x\n", rc); + + osmo_pbit2ubit(buf, iuup_frame29+4, 31*8); + rc = osmo_crc16gen_compute_bits(&iuup_data_crc_code, buf, 31*8); + printf("Payload CRC = 0x%03x\n", rc); +} +#endif -- cgit v1.2.3