diff options
-rw-r--r-- | include/Makefile.am | 2 | ||||
-rw-r--r-- | include/osmocom/core/logging.h | 3 | ||||
-rw-r--r-- | include/osmocom/gsm/iuup.h | 122 | ||||
-rw-r--r-- | include/osmocom/gsm/prim.h | 3 | ||||
-rw-r--r-- | include/osmocom/gsm/protocol/gsm_25_415.h | 222 | ||||
-rw-r--r-- | src/gsm/Makefile.am | 2 | ||||
-rw-r--r-- | src/gsm/iuup.c | 1007 | ||||
-rw-r--r-- | src/gsm/libosmogsm.map | 11 | ||||
-rw-r--r-- | src/logging.c | 6 | ||||
-rw-r--r-- | tests/Makefile.am | 7 | ||||
-rw-r--r-- | tests/iuup/iuup_test.c | 543 | ||||
-rw-r--r-- | tests/iuup/iuup_test.ok | 45 | ||||
-rw-r--r-- | tests/logging/logging_vty_test.vty | 5 | ||||
-rw-r--r-- | tests/testsuite.at | 6 |
14 files changed, 1980 insertions, 4 deletions
diff --git a/include/Makefile.am b/include/Makefile.am index e3246cf2..eab04894 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -125,6 +125,7 @@ nobase_include_HEADERS = \ osmocom/gsm/gsup_sms.h \ osmocom/gsm/i460_mux.h \ osmocom/gsm/ipa.h \ + osmocom/gsm/iuup.h \ osmocom/gsm/lapd_core.h \ osmocom/gsm/lapdm.h \ osmocom/gsm/meas_rep.h \ @@ -148,6 +149,7 @@ nobase_include_HEADERS = \ osmocom/gsm/protocol/gsm_12_21.h \ osmocom/gsm/protocol/gsm_23_003.h \ osmocom/gsm/protocol/gsm_23_041.h \ + osmocom/gsm/protocol/gsm_25_415.h \ osmocom/gsm/protocol/gsm_29_118.h \ osmocom/gsm/protocol/gsm_44_004.h \ osmocom/gsm/protocol/gsm_44_318.h \ diff --git a/include/osmocom/core/logging.h b/include/osmocom/core/logging.h index 18ad3ff1..79077340 100644 --- a/include/osmocom/core/logging.h +++ b/include/osmocom/core/logging.h @@ -136,7 +136,8 @@ void logp(int subsys, const char *file, int line, int cont, const char *format, #define DLBSSGP -21 /*!< Osmocom BSSGP layer */ #define DLNSDATA -22 /*!< Osmocom NS layer data pdus */ #define DLNSSIGNAL -23 /*!< Osmocom NS layer signal pdus */ -#define OSMO_NUM_DLIB 23 /*!< Number of logging sub-systems in libraries */ +#define DLIUUP -24 /*!< Osmocom IuUP layer */ +#define OSMO_NUM_DLIB 24 /*!< Number of logging sub-systems in libraries */ /* Colors that can be used in log_info_cat.color */ #define OSMO_LOGCOLOR_NORMAL NULL diff --git a/include/osmocom/gsm/iuup.h b/include/osmocom/gsm/iuup.h new file mode 100644 index 00000000..b1651c59 --- /dev/null +++ b/include/osmocom/gsm/iuup.h @@ -0,0 +1,122 @@ +#pragma once + +#include <stdint.h> + +#include <osmocom/core/prim.h> +#include <osmocom/gsm/protocol/gsm_25_415.h> + +/*********************************************************************** + * Primitives towards the lower layers (typically RTP transport) + ***********************************************************************/ +enum osmo_iuup_tnl_prim_type { + OSMO_IUUP_TNL_UNITDATA, +}; + +struct osmo_iuup_tnl_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_iuup_rnl_prim_type { + OSMO_IUUP_RNL_CONFIG, + OSMO_IUUP_RNL_DATA, + OSMO_IUUP_RNL_STATUS, + OSMO_IUUP_RNL_UNIT_DATA, +}; + +/* TS 25.413 9.2.1.3*/ +#define IUUP_MAX_SUBFLOWS 7 +#define IUUP_MAX_RFCIS 64 + +#define IUUP_TIMER_INIT_T_DEFAULT 1000 +#define IUUP_TIMER_TA_T_DEFAULT 500 +#define IUUP_TIMER_RC_T_DEFAULT 500 +#define IUUP_TIMER_INIT_N_DEFAULT 3 +#define IUUP_TIMER_TA_N_DEFAULT 1 +#define IUUP_TIMER_RC_N_DEFAULT 1 +struct osmo_iuup_rnl_config_timer { + uint32_t t_ms; /* time in ms */ + uint32_t n_max; /* max number of repetitions */ +}; +struct osmo_iuup_rnl_config { + /* transparent (true) or SMpSDU (false): */ + bool transparent; + + /* should we actively transmit INIT in SmpSDU mode? */ + bool active; + + /* Currently Version 0 or 1: */ + uint8_t data_pdu_type; + + /* Supported mode versions */ + uint16_t supported_versions_mask; + uint8_t num_rfci; + uint8_t num_subflows; + uint16_t subflow_sizes[IUUP_MAX_RFCIS][IUUP_MAX_SUBFLOWS]; + bool IPTIs_present; + uint8_t IPTIs[IUUP_MAX_RFCIS]; /* values range 0-15, 4 bits */ + + /* TODO: Indication of delivery of erroneous SDUs*/ + struct osmo_iuup_rnl_config_timer t_init; + struct osmo_iuup_rnl_config_timer t_ta; + struct osmo_iuup_rnl_config_timer t_rc; +}; + +struct osmo_iuup_rnl_data { + uint8_t rfci; + uint8_t frame_nr; + uint8_t fqc; +}; + +struct osmo_iuup_rnl_status { + enum iuup_procedure procedure; + union { + struct { + enum iuup_error_cause cause; + enum iuup_error_distance distance; + } error_event; + struct { + uint16_t supported_versions_mask; + uint8_t num_subflows; + uint8_t num_rfci; + uint16_t subflow_sizes[IUUP_MAX_RFCIS][IUUP_MAX_SUBFLOWS]; + bool IPTIs_present; + uint8_t IPTIs[IUUP_MAX_RFCIS]; /* values range 0-15, 4 bits */ + } initialization; + struct { + } rate_control; + struct { + } time_alignment; + } u; +}; + +/* SAP on the upper side of IuUP, towards the user */ +struct osmo_iuup_rnl_prim { + struct osmo_prim_hdr oph; + union { + struct osmo_iuup_rnl_config config; + struct osmo_iuup_rnl_data data; + struct osmo_iuup_rnl_status status; + //struct osmo_iuup_rnl_unitdata unitdata; + } u; +}; + +struct osmo_iuup_instance; +struct osmo_iuup_instance *osmo_iuup_instance_alloc(void *ctx, const char *id); +void osmo_iuup_instance_free(struct osmo_iuup_instance *iui); + +void osmo_iuup_instance_set_user_prim_cb(struct osmo_iuup_instance *iui, osmo_prim_cb func, void *priv); +void osmo_iuup_instance_set_transport_prim_cb(struct osmo_iuup_instance *iui, osmo_prim_cb func, void *priv); +int osmo_iuup_tnl_prim_up(struct osmo_iuup_instance *iui, struct osmo_iuup_tnl_prim *itp); +int osmo_iuup_rnl_prim_down(struct osmo_iuup_instance *inst, struct osmo_iuup_rnl_prim *irp); + + +int osmo_iuup_compute_header_crc(const uint8_t *iuup_pdu, unsigned int pdu_len); +int osmo_iuup_compute_payload_crc(const uint8_t *iuup_pdu, unsigned int pdu_len); + +struct osmo_iuup_rnl_prim *osmo_iuup_rnl_prim_alloc(void *ctx, unsigned int primitive, unsigned int operation, unsigned int size); +struct osmo_iuup_tnl_prim *osmo_iuup_tnl_prim_alloc(void *ctx, unsigned int primitive, unsigned int operation, unsigned int size); diff --git a/include/osmocom/gsm/prim.h b/include/osmocom/gsm/prim.h index 045e353b..73cd7f71 100644 --- a/include/osmocom/gsm/prim.h +++ b/include/osmocom/gsm/prim.h @@ -18,4 +18,7 @@ enum osmo_gsm_sap { SAP_NS, SAP_BSSGP_RIM, + + 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..5d90fd35 --- /dev/null +++ b/include/osmocom/gsm/protocol/gsm_25_415.h @@ -0,0 +1,222 @@ +#pragma once +/* Iu User Plane (IuUP) Definitions as per 3GPP TS 25.415 */ +/* (C) 2017 by Harald Welte <laforge@gnumonks.org> + * All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <stdint.h> +#include <osmocom/core/endian.h> + +/* 3GPP TS 25.415 Section 6.6.2.1 */ +struct iuup_pdutype0_hdr { +#if OSMO_IS_LITTLE_ENDIAN + /* control part */ + uint8_t frame_nr:4, + pdu_type:4; + uint8_t rfci:6, + fqc:2; + /* checksum part */ + uint8_t payload_crc_hi:2, header_crc:6; + uint8_t payload_crc_lo; + + /* payload part */ + uint8_t payload[0]; +#elif OSMO_IS_BIG_ENDIAN +/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */ + uint8_t pdu_type:4, frame_nr:4; + uint8_t fqc:2, rfci:6; + uint8_t header_crc:6, payload_crc_hi:2; + uint8_t payload_crc_lo; + uint8_t payload[0]; +#endif +} __attribute__((packed)); + +/* 3GPP TS 25.415 Section 6.6.2.2 */ +struct iuup_pdutype1_hdr { +#if OSMO_IS_LITTLE_ENDIAN + /* 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]; +#elif OSMO_IS_BIG_ENDIAN +/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */ + uint8_t pdu_type:4, frame_nr:4; + uint8_t fqc:2, rfci:6; + uint8_t header_crc:6, spare:2; + uint8_t payload[0]; +#endif +} __attribute__((packed)); + +/* 3GPP TS 25.415 Section 6.6.2.3 */ +struct iuup_pdutype14_hdr { +#if OSMO_IS_LITTLE_ENDIAN + /* control part */ + uint8_t frame_nr:2, + ack_nack:2, + pdu_type:4; + uint8_t proc_ind:4, + mode_version:4; + /* checksum part */ + uint8_t payload_crc_hi:2, header_crc:6; + uint8_t payload_crc_lo; + /* payload part */ + uint8_t payload[0]; +#elif OSMO_IS_BIG_ENDIAN +/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */ + uint8_t pdu_type:4, ack_nack:2, frame_nr:2; + uint8_t mode_version:4, proc_ind:4; + uint8_t header_crc:6, payload_crc_hi:2; + uint8_t payload_crc_lo; + uint8_t payload[0]; +#endif +} __attribute__((packed)); + +/* 3GPP TS 25.415 Section 6.6.2.3.4.1 */ +struct iuup_ctrl_init_rfci_hdr { +#if OSMO_IS_LITTLE_ENDIAN + uint8_t rfci:6, + li:1, + lri:1; + uint8_t subflow_length[0]; /* 1 or 2 bytes depending on li */ +#elif OSMO_IS_BIG_ENDIAN +/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */ + uint8_t lri:1, li:1, rfci:6; + uint8_t subflow_length[0]; +#endif +} __attribute__((packed)); +struct iuup_ctrl_init_hdr { +#if OSMO_IS_LITTLE_ENDIAN + uint8_t chain_ind:1, + num_subflows_per_rfci:3, + ti:1, + spare:3; + uint8_t rfci_data[0]; /* struct iuup_ctrl_init_rfci_hdr* */ +#elif OSMO_IS_BIG_ENDIAN +/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */ + uint8_t spare:3, ti:1, num_subflows_per_rfci:3, chain_ind:1; + uint8_t rfci_data[0]; /* struct iuup_ctrl_init_rfci_hdr* */ +; +#endif +} __attribute__((packed)); +struct iuup_ctrl_init_tail { +#if OSMO_IS_LITTLE_ENDIAN + uint16_t versions_supported; + uint8_t spare:4, + data_pdu_type:4; + uint8_t spare_extension[0]; +#elif OSMO_IS_BIG_ENDIAN +/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */ + uint16_t versions_supported; + uint8_t data_pdu_type:4, spare:4; + uint8_t spare_extension[0]; +#endif +} __attribute__((packed)); + +/* 3GPP TS 25.415 Section 6.6.2.3.4.4 */ +struct iuup_ctrl_error_event { +#if OSMO_IS_LITTLE_ENDIAN + struct iuup_pdutype14_hdr hdr; + uint8_t error_cause:6, + error_distance:2; + uint8_t spare_extension[0]; +#elif OSMO_IS_BIG_ENDIAN +/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */ + struct iuup_pdutype14_hdr hdr; + uint8_t error_distance:2, error_cause:6; + uint8_t spare_extension[0]; +#endif +} __attribute__((packed)); + +/* 3GPP TS 25.415 Section 6.6.2.3.2 */ +struct iuup_ctrl_ack { + struct iuup_pdutype14_hdr hdr; + uint8_t spare_extension[0]; +} __attribute__((packed)); + +/* 3GPP TS 25.415 Section 6.6.2.3.3 */ +struct iuup_ctrl_nack { +#if OSMO_IS_LITTLE_ENDIAN + struct iuup_pdutype14_hdr hdr; + uint8_t spare:2, + error_cause:6; + uint8_t spare_extension[0]; +#elif OSMO_IS_BIG_ENDIAN +/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */ + struct iuup_pdutype14_hdr hdr; + uint8_t error_cause:6, spare:2; + uint8_t spare_extension[0]; +#endif +} __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, + IUUP_ERR_CAUSE_MODE_VERSION_NOT_SUPPORTED = 49, +}; diff --git a/src/gsm/Makefile.am b/src/gsm/Makefile.am index 580e3972..326efd21 100644 --- a/src/gsm/Makefile.am +++ b/src/gsm/Makefile.am @@ -33,7 +33,7 @@ libgsmint_la_SOURCES = a5.c rxlev_stat.c tlv_parser.c comp128.c comp128v23.c \ gsup.c gsup_sms.c gprs_gea.c gsm0503_conv.c oap.c gsm0808_utils.c \ gsm23003.c gsm23236.c mncc.c bts_features.c oap_client.c \ gsm29118.c gsm48_rest_octets.c cbsp.c gsm48049.c i460_mux.c \ - gad.c bsslap.c bssmap_le.c kdf.c + gad.c bsslap.c bssmap_le.c kdf.c iuup.c libgsmint_la_LDFLAGS = -no-undefined libgsmint_la_LIBADD = $(top_builddir)/src/libosmocore.la diff --git a/src/gsm/iuup.c b/src/gsm/iuup.c new file mode 100644 index 00000000..159e533e --- /dev/null +++ b/src/gsm/iuup.c @@ -0,0 +1,1007 @@ +/*! \file iu_up.c + * IuUP (Iu User Plane) according to 3GPP TS 25.415 */ +/* + * (C) 2017 by Harald Welte <laforge@gnumonks.org> + * + * 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. + */ + +#include <errno.h> +#include <inttypes.h> + +#include <osmocom/core/crc8gen.h> +#include <osmocom/core/crc16gen.h> +#include <osmocom/core/fsm.h> +#include <osmocom/core/prim.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/logging.h> + +#include <osmocom/gsm/prim.h> +#include <osmocom/gsm/protocol/gsm_25_415.h> +#include <osmocom/gsm/iuup.h> + +/*********************************************************************** + * 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); +} + +/*********************************************************************** + * Internal State / FSM (Annex B) + ***********************************************************************/ + +#define S(x) (1 << (x)) + +#define IUUP_TIMER_INIT 1 +#define IUUP_TIMER_TA 2 +#define IUUP_TIMER_RC 3 + +struct osmo_timer_nt { + uint32_t n; /* number of repetitions */ + struct osmo_iuup_tnl_prim *retrans_itp; + struct osmo_timer_list timer; +}; + +struct osmo_iuup_instance { + struct osmo_iuup_rnl_config config; + struct osmo_fsm_inst *fi; + + uint8_t mode_version; + + 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; + void *user_prim_priv; + osmo_prim_cb transport_prim_cb; + void *transport_prim_priv; + uint8_t type14_fn; /* 2 bits */ +}; + +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_IUUP_STATUS_REQ, + IUUP_FSM_EVT_IUUP_STATUS_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_IUUP_STATUS_REQ, "IuUP-STATUS.req" }, + { IUUP_FSM_EVT_IUUP_STATUS_IND, "IuUP-STATUS.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; +} + +/* Helper functions to store non-packed structs in msgb so that pointers are properly aligned: */ +#define IUUP_MSGB_SIZE 4096 +#define PTR_ALIGNMENT_BYTES 8 +#define IUUP_MSGB_HEADROOM_MIN_REQUIRED (OSMO_MAX(sizeof(struct osmo_iuup_tnl_prim), sizeof(struct osmo_iuup_rnl_prim)) + (PTR_ALIGNMENT_BYTES - 1)) +static inline struct msgb *osmo_iuup_msgb_alloc_c(void *ctx, size_t size) +{ + osmo_static_assert(size > IUUP_MSGB_HEADROOM_MIN_REQUIRED, iuup_msgb_alloc_headroom_bigger); + return msgb_alloc_headroom_c(ctx, size, IUUP_MSGB_HEADROOM_MIN_REQUIRED, "iuup-msgb"); +} + +/* push data so that the resulting pointer to write to is aligned to 8 byte */ +static inline __attribute__((assume_aligned(PTR_ALIGNMENT_BYTES))) +unsigned char *aligned_msgb_push(struct msgb *msg, unsigned int len) +{ + uint8_t *ptr = (msgb_data(msg) - len); + size_t extra_size = ((uintptr_t)ptr & (PTR_ALIGNMENT_BYTES - 1)); + + return msgb_push(msg, len + extra_size); +} + +struct osmo_iuup_rnl_prim *osmo_iuup_rnl_prim_alloc(void *ctx, unsigned int primitive, unsigned int operation, unsigned int size) +{ + struct msgb *msg; + struct osmo_iuup_rnl_prim *irp; + + msg = osmo_iuup_msgb_alloc_c(ctx, size); + irp = (struct osmo_iuup_rnl_prim *)aligned_msgb_push(msg, sizeof(*irp)); + osmo_prim_init(&irp->oph, SAP_IUUP_RNL, primitive, operation, msg); + return irp; +} + +struct osmo_iuup_tnl_prim *osmo_iuup_tnl_prim_alloc(void *ctx, unsigned int primitive, unsigned int operation, unsigned int size) +{ + struct msgb *msg; + struct osmo_iuup_tnl_prim *itp; + + msg = osmo_iuup_msgb_alloc_c(ctx, size); + itp = (struct osmo_iuup_tnl_prim *) aligned_msgb_push(msg, sizeof(*itp)); + osmo_prim_init(&itp->oph, SAP_IUUP_TNL, primitive, operation, msg); + return itp; +} + +/* 6.6.2.3.2 */ +static struct osmo_iuup_tnl_prim *itp_ctrl_ack_alloc(struct osmo_iuup_instance *iui, enum iuup_procedure proc_ind, uint8_t fn) +{ + struct osmo_iuup_tnl_prim *itp; + struct iuup_ctrl_ack *ack; + itp = osmo_iuup_tnl_prim_alloc(iui, OSMO_IUUP_TNL_UNITDATA, PRIM_OP_REQUEST, IUUP_MSGB_SIZE); + itp->oph.msg->l2h = msgb_put(itp->oph.msg, sizeof(struct iuup_ctrl_ack)); + ack = (struct iuup_ctrl_ack *) msgb_l2(itp->oph.msg); + *ack = (struct iuup_ctrl_ack){ + .hdr = { + .frame_nr = fn, + .ack_nack = IUUP_AN_ACK, + .pdu_type = IUUP_PDU_T_CONTROL, + .proc_ind = proc_ind, + .mode_version = iui->mode_version, + .payload_crc_hi = 0, + .header_crc = 0, + .payload_crc_lo = 0, + }, + }; + ack->hdr.header_crc = osmo_iuup_compute_header_crc(msgb_l2(itp->oph.msg), msgb_l2len(itp->oph.msg)); + return itp; +} + +/* 6.6.2.3.3 */ +static struct osmo_iuup_tnl_prim *tnp_ctrl_nack_alloc(struct osmo_iuup_instance *iui, enum iuup_procedure proc_ind, enum iuup_error_cause error_cause, uint8_t fn) +{ + struct osmo_iuup_tnl_prim *itp; + struct iuup_ctrl_nack *nack; + itp = osmo_iuup_tnl_prim_alloc(iui, OSMO_IUUP_TNL_UNITDATA, PRIM_OP_REQUEST, IUUP_MSGB_SIZE); + itp->oph.msg->l2h = msgb_put(itp->oph.msg, sizeof(struct iuup_ctrl_nack)); + nack = (struct iuup_ctrl_nack *) msgb_l2(itp->oph.msg); + *nack = (struct iuup_ctrl_nack){ + .hdr = { + .frame_nr = fn, + .ack_nack = IUUP_AN_NACK, + .pdu_type = IUUP_PDU_T_CONTROL, + .proc_ind = proc_ind, + .mode_version = iui->mode_version, + .payload_crc_hi = 0, + .header_crc = 0, + .payload_crc_lo = 0, + }, + .spare = 0, + .error_cause = error_cause, + }; + nack->hdr.header_crc = osmo_iuup_compute_header_crc(msgb_l2(itp->oph.msg), msgb_l2len(itp->oph.msg)); + return itp; +} + +/* 6.6.2.3.4.1 */ +static struct osmo_iuup_tnl_prim *tnp_ctrl_init_alloc(struct osmo_iuup_instance *iui) +{ + struct osmo_iuup_tnl_prim *itp; + struct iuup_pdutype14_hdr *hdr; + struct iuup_ctrl_init_hdr *ihdr; + struct iuup_ctrl_init_rfci_hdr *ihdr_rfci; + struct iuup_ctrl_init_tail *itail; + unsigned int i, j; + uint8_t num_subflows, num_rfci; + uint16_t payload_crc; + struct msgb *msg; + + num_subflows = iui->config.num_subflows; + num_rfci = iui->config.num_rfci; + + itp = osmo_iuup_tnl_prim_alloc(iui, OSMO_IUUP_TNL_UNITDATA, PRIM_OP_REQUEST, IUUP_MSGB_SIZE); + msg = itp->oph.msg; + + msg->l2h = msgb_put(msg, sizeof(*hdr)); + hdr = (struct iuup_pdutype14_hdr *)msgb_l2(msg); + hdr->frame_nr = iui->type14_fn++; + hdr->ack_nack = IUUP_AN_PROCEDURE; + hdr->pdu_type = IUUP_PDU_T_CONTROL; + hdr->proc_ind = IUUP_PROC_INIT; + hdr->mode_version = 0; /* Use here the minimum version required to negotiate */ + hdr->header_crc = osmo_iuup_compute_header_crc(msgb_l2(msg), msgb_l2len(msg)); + + ihdr = (struct iuup_ctrl_init_hdr *)msgb_put(msg, sizeof(*ihdr)); + ihdr->chain_ind = 0; /* this frame is the last frame for the procedure. TODO: support several */ + ihdr->num_subflows_per_rfci = num_subflows; + ihdr->ti = iui->config.IPTIs_present ? 1 : 0; + ihdr->spare = 0; + + /* RFCI + subflow size part: */ + for (i = 0; i < num_rfci; i++) { + bool last = (i+1 == num_rfci); + uint8_t len_size = 1; + for (j = 0; j < num_subflows; j++) { + if (iui->config.subflow_sizes[i][j] > UINT8_MAX) + len_size = 2; + } + ihdr_rfci = (struct iuup_ctrl_init_rfci_hdr *)msgb_put(msg, sizeof(*ihdr_rfci) + len_size * num_subflows); + ihdr_rfci->rfci = i; + ihdr_rfci->li = len_size - 1; + ihdr_rfci->lri = last; + if (len_size == 2) { + uint16_t *buf = (uint16_t *)&ihdr_rfci->subflow_length[0]; + for (j = 0; j < num_subflows; j++) + osmo_store16be(iui->config.subflow_sizes[i][j], buf++); + } else { + for (j = 0; j < num_subflows; j++) + ihdr_rfci->subflow_length[j] = iui->config.subflow_sizes[i][j]; + } + } + + if (iui->config.IPTIs_present) { + uint8_t num_bytes = (num_rfci + 1) / 2; + uint8_t *buf = msgb_put(msg, num_bytes); + for (i = 0; i < num_bytes - 1; i++) + buf[i] = iui->config.IPTIs[i*2] << 4 | + (iui->config.IPTIs[i*2 + 1] & 0x0f); + buf[i] = iui->config.IPTIs[i*2] << 4; + if (!(num_rfci & 0x01)) /* is even: */ + buf[i] |= (iui->config.IPTIs[i*2 + 1] & 0x0f); + + } + + itail = (struct iuup_ctrl_init_tail *)msgb_put(msg, sizeof(*itail)); + osmo_store16be(iui->config.supported_versions_mask, &itail->versions_supported); + itail->spare = 0; + itail->data_pdu_type = iui->config.data_pdu_type; + + payload_crc = osmo_iuup_compute_payload_crc(msgb_l2(msg), msgb_l2len(msg)); + hdr->payload_crc_hi = (payload_crc >> 8) & 0x03; + hdr->payload_crc_lo = payload_crc & 0xff; + + + return itp; +} + +/* transform a RNL data primitive into a TNL data primitive (down the stack) */ +static struct osmo_iuup_tnl_prim *rnl_to_tnl_data(struct osmo_iuup_instance *iui, + struct osmo_iuup_rnl_prim *irp) +{ + struct osmo_iuup_tnl_prim *itp; + struct osmo_iuup_rnl_data dt; + struct msgb *msg; + uint16_t payload_crc; + struct iuup_pdutype0_hdr *h0; + struct iuup_pdutype1_hdr *h1; + + OSMO_ASSERT(OSMO_PRIM_HDR(&irp->oph) == OSMO_PRIM(OSMO_IUUP_RNL_DATA, PRIM_OP_REQUEST)); + + msg = irp->oph.msg; + dt = irp->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 */ + switch (iui->config.data_pdu_type) { + case 0: + msg->l2h = msgb_push(msg, sizeof(*h0)); + h0 = (struct iuup_pdutype0_hdr *)msg->l2h; + 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)); + payload_crc = osmo_iuup_compute_payload_crc(msgb_l2(msg), msgb_l2len(msg)); + h0->payload_crc_hi = (payload_crc >> 8) & 0x03; + h0->payload_crc_lo = payload_crc & 0xff; + break; + case 1: + msg->l2h = msgb_push(msg, sizeof(*h1)); + h1 = (struct iuup_pdutype1_hdr *)msg->l2h; + 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; + break; + default: + OSMO_ASSERT(0); + } + + /* Avoid allocating irp out of 8byte-aligned address, Asan is not happy with it */ + itp = (struct osmo_iuup_tnl_prim *) aligned_msgb_push(msg, sizeof(*itp)); + osmo_prim_init(&itp->oph, SAP_IUUP_TNL, OSMO_IUUP_TNL_UNITDATA, PRIM_OP_REQUEST, msg); + + return itp; +} + +/* transform a TNL primitive into a RNL primitive (up the stack) */ +static struct osmo_iuup_rnl_prim *tnl_to_rnl_data(struct osmo_iuup_tnl_prim *itp) +{ + struct msgb *msg; + struct iuup_pdutype0_hdr *h0; + struct iuup_pdutype1_hdr *h1; + struct osmo_iuup_rnl_data dt; + struct osmo_iuup_rnl_prim *irp; + + msg = itp->oph.msg; + + OSMO_ASSERT(OSMO_PRIM_HDR(&itp->oph) == OSMO_PRIM(OSMO_IUUP_TNL_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->fqc; + 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->fqc; + break; + } + + /* pull up to the IuUP payload and push a new primitive header in front */ + msgb_pull_to_l3(msg); + + /* Avoid allocating irp out of 8byte-aligned address, Asan is not happy with it */ + irp = (struct osmo_iuup_rnl_prim *) aligned_msgb_push(msg, sizeof(*irp)); + osmo_prim_init(&irp->oph, SAP_IUUP_RNL, OSMO_IUUP_RNL_DATA, PRIM_OP_INDICATION, msg); + irp->u.data = dt; + + return irp; +} + +static struct osmo_iuup_rnl_prim *irp_error_event_alloc_c(void *ctx, enum iuup_error_cause cause, enum iuup_error_distance distance) +{ + struct osmo_iuup_rnl_prim *irp; + struct msgb *msg; + msg = msgb_alloc_c(ctx, sizeof(*irp), "iuup-tx"); + irp = (struct osmo_iuup_rnl_prim *) msgb_put(msg, sizeof(*irp)); + osmo_prim_init(&irp->oph, SAP_IUUP_RNL, OSMO_IUUP_RNL_STATUS, PRIM_OP_INDICATION, msg); + irp->u.status.procedure = IUUP_PROC_ERR_EVENT; + irp->u.status.u.error_event.cause = cause; + irp->u.status.u.error_event.distance = distance; + return irp; +} + +static struct osmo_iuup_tnl_prim *itp_copy_c(void *ctx, const struct osmo_iuup_tnl_prim *src_itp) +{ + struct msgb *msg; + struct osmo_iuup_tnl_prim *dst_itp; + + msg = msgb_copy_c(ctx, src_itp->oph.msg, "iuup-tx-retrans"); + dst_itp = (struct osmo_iuup_tnl_prim *)msgb_data(msg); + dst_itp->oph.msg = msg; + return dst_itp; +} + +static void retransmit_initialization(struct osmo_iuup_instance *iui) +{ + struct osmo_iuup_tnl_prim *itp; + iui->fi->T = IUUP_TIMER_INIT; + osmo_timer_schedule(&iui->fi->timer, iui->config.t_init.t_ms / 1000, (iui->config.t_init.t_ms % 1000) * 1000); + itp = itp_copy_c(iui, iui->timer.init.retrans_itp); + iui->transport_prim_cb(&itp->oph, iui->transport_prim_priv); +} + +/* return: whether the last Init was Acked correctly and hence can transition to next state */ +static bool iuup_rx_initialization(struct osmo_iuup_instance *iui, struct osmo_iuup_tnl_prim *itp) +{ + struct iuup_pdutype14_hdr *hdr; + struct iuup_ctrl_init_hdr *ihdr; + struct iuup_ctrl_init_rfci_hdr *ihdr_rfci; + struct iuup_ctrl_init_tail *itail; + enum iuup_error_cause err_cause; + uint8_t num_rfci = 0; + unsigned int i; + bool is_last; + uint16_t remote_mask, match_mask; + struct osmo_iuup_tnl_prim *resp; + + /* TODO: whenever we check message boundaries, length, etc. and we fail, send NACK */ + + hdr = (struct iuup_pdutype14_hdr *)msgb_l2(itp->oph.msg); + ihdr = (struct iuup_ctrl_init_hdr *)hdr->payload; + if (ihdr->num_subflows_per_rfci == 0) { + LOGPFSML(iui->fi, LOGL_NOTICE, "Initialization: Unexpected num_subflows=0 received\n"); + err_cause = IUUP_ERR_CAUSE_UNEXPECTED_VALUE; + goto send_nack; + } + ihdr_rfci = (struct iuup_ctrl_init_rfci_hdr *)ihdr->rfci_data; + + do { + uint8_t l_size_bytes = ihdr_rfci->li + 1; + is_last = ihdr_rfci->lri; + if (ihdr_rfci->rfci != num_rfci) { + LOGPFSML(iui->fi, LOGL_NOTICE, "Initialization: Unexpected RFCI %u at position %u received\n", + ihdr_rfci->rfci, num_rfci); + err_cause = IUUP_ERR_CAUSE_UNEXPECTED_RFCI; + goto send_nack; + } + if (l_size_bytes == 2) { + uint16_t *subflow_size = (uint16_t *)ihdr_rfci->subflow_length; + for (i = 0; i < ihdr->num_subflows_per_rfci; i++) { + iui->config.subflow_sizes[ihdr_rfci->rfci][i] = osmo_load16be(subflow_size); + subflow_size++; + } + } else { + uint8_t *subflow_size = ihdr_rfci->subflow_length; + for (i = 0; i < ihdr->num_subflows_per_rfci; i++) { + iui->config.subflow_sizes[ihdr_rfci->rfci][i] = osmo_load16be(subflow_size); + subflow_size++; + } + } + num_rfci++; + ihdr_rfci++; + ihdr_rfci = (struct iuup_ctrl_init_rfci_hdr *)(((uint8_t *)ihdr_rfci) + ihdr->num_subflows_per_rfci * l_size_bytes); + } while (!is_last); + + if (ihdr->ti) { /* Timing information present */ + uint8_t *buf = (uint8_t *)ihdr_rfci; + uint8_t num_bytes = (num_rfci + 1) / 2; + iui->config.IPTIs_present = true; + for (i = 0; i < num_bytes - 1; i++) { + iui->config.IPTIs[i*2] = *buf >> 4; + iui->config.IPTIs[i*2 + 1] = *buf & 0x0f; + buf++; + } + iui->config.IPTIs[i*2] = *buf >> 4; + if (!(num_rfci & 0x01)) /* is even: */ + iui->config.IPTIs[i*2 + 1] = *buf & 0x0f; + buf++; + itail = (struct iuup_ctrl_init_tail *)buf; + } else { + itail = (struct iuup_ctrl_init_tail *)ihdr_rfci; + } + + if (itail->data_pdu_type > 1) { + LOGPFSML(iui->fi, LOGL_NOTICE, "Initialization: Unexpected Data PDU Type %u received\n", itail->data_pdu_type); + err_cause = IUUP_ERR_CAUSE_UNEXPECTED_VALUE; + goto send_nack; + } + + remote_mask = osmo_load16be(&itail->versions_supported); + match_mask = (remote_mask & iui->config.supported_versions_mask); + if (match_mask == 0x0000) { + LOGPFSML(iui->fi, LOGL_NOTICE, + "Initialization: No match in supported versions local=0x%04x vs remote=0x%04x\n", + iui->config.supported_versions_mask, remote_mask); + err_cause = IUUP_ERR_CAUSE_UNEXPECTED_VALUE; + goto send_nack; + } + for (i = 15; i >= 0; i--) { + if (match_mask & (1<<i)) { + iui->mode_version = i; + break; + } + } + + iui->config.num_rfci = num_rfci; + iui->config.num_subflows = ihdr->num_subflows_per_rfci; + iui->config.data_pdu_type = itail->data_pdu_type; + + LOGPFSML(iui->fi, LOGL_DEBUG, "Tx Initialization ACK\n"); + resp = itp_ctrl_ack_alloc(iui, IUUP_PROC_INIT, hdr->frame_nr); + iui->transport_prim_cb(&resp->oph, iui->transport_prim_priv); + return ihdr->chain_ind == 0; +send_nack: + LOGPFSML(iui->fi, LOGL_NOTICE, "Tx Initialization NACK cause=%u orig_message=%s\n", + err_cause, osmo_hexdump((const unsigned char *) msgb_l2(itp->oph.msg), msgb_l2len(itp->oph.msg))); + resp = tnp_ctrl_nack_alloc(iui, IUUP_PROC_INIT, err_cause, hdr->frame_nr); + iui->transport_prim_cb(&resp->oph, iui->transport_prim_priv); + return false; +} + +/********************** + * FSM STATE FUNCTIONS + **********************/ +static void iuup_fsm_null(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct osmo_iuup_instance *iui = fi->priv; + struct osmo_iuup_rnl_prim *user_prim = NULL; + + switch (event) { + case IUUP_FSM_EVT_IUUP_CONFIG_REQ: + user_prim = data; + iui->config = user_prim->u.config; + iui->config.supported_versions_mask &= 0x0003; /* We only support versions 1 and 2 ourselves */ + //TODO: if supported_versions_mask == 0x0000,no supported versions, send error to upper layers + + if (iui->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); + } + break; + } +} + +/* 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_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct osmo_iuup_instance *iui = fi->priv; + + iui->type14_fn = 0; + if (iui->config.active) { + iui->timer.init.n = 0; + iui->timer.init.retrans_itp = tnp_ctrl_init_alloc(iui); + retransmit_initialization(iui); + } +} + +static void iuup_fsm_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct osmo_iuup_instance *iui = fi->priv; + struct osmo_iuup_rnl_prim *irp; + struct osmo_iuup_tnl_prim *itp; + + 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_INIT: + itp = data; + if (iuup_rx_initialization(iui, itp)) + osmo_fsm_inst_state_chg(fi, IUUP_FSM_ST_SMpSDU_DATA_XFER_READY, 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; + case IUUP_FSM_EVT_INIT_NACK: + LOGPFSML(fi, LOGL_NOTICE, "Rx Initialization NACK N=%" PRIu32 "/%" PRIu32 "\n", + iui->timer.init.n, iui->config.t_init.n_max); + osmo_timer_del(&fi->timer); + if (iui->timer.init.n == iui->config.t_init.n_max) { + irp = irp_error_event_alloc_c(iui, IUUP_ERR_CAUSE_INIT_FAILURE_REP_NACK, IUUP_ERR_DIST_SECOND_FWD); + iui->user_prim_cb(&irp->oph, iui->user_prim_priv); + return; + } + iui->timer.init.n++; + retransmit_initialization(iui); + break; + default: + OSMO_ASSERT(false); + } +} + +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_iuup_rnl_prim *irp = NULL; + struct osmo_iuup_tnl_prim *itp = NULL; + + switch (event) { + case IUUP_FSM_EVT_IUUP_CONFIG_REQ: + irp = 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) */ + irp = data; + itp = rnl_to_tnl_data(iui, irp); + iui->transport_prim_cb(&itp->oph, iui->transport_prim_priv); + break; + case IUUP_FSM_EVT_IUUP_DATA_IND: + /* Data coming up from TNL (transport) towards RNL (user) */ + itp = data; + irp = tnl_to_rnl_data(itp); + iui->user_prim_cb(&irp->oph, iui->user_prim_priv); + 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 int iuup_fsm_timer_cb(struct osmo_fsm_inst *fi) +{ + struct osmo_iuup_instance *iui = fi->priv; + struct osmo_iuup_rnl_prim *irp; + + switch (fi->T) { + case IUUP_TIMER_INIT: + OSMO_ASSERT(fi->state == IUUP_FSM_ST_INIT); + if (iui->timer.init.n == iui->config.t_init.n_max) { + irp = irp_error_event_alloc_c(iui, IUUP_ERR_CAUSE_INIT_FAILURE_NET_TMR, IUUP_ERR_DIST_LOCAL); + iui->user_prim_cb(&irp->oph, iui->user_prim_priv); + return 0; + } + iui->timer.init.n++; + retransmit_initialization(iui); + break; + case IUUP_TIMER_TA: + break; + case IUUP_TIMER_RC: + break; + default: + OSMO_ASSERT(0); + } + return 0; +} + + +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_STATUS_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 = S(IUUP_FSM_EVT_IUUP_CONFIG_REQ) | + S(IUUP_FSM_EVT_INIT) | + S(IUUP_FSM_EVT_LAST_INIT_ACK) | + S(IUUP_FSM_EVT_INIT_NACK), + .out_state_mask = S(IUUP_FSM_ST_NULL) | + S(IUUP_FSM_ST_SMpSDU_DATA_XFER_READY), + .name = "Initialisation", + .onenter = iuup_fsm_init_on_enter, + .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, + }, +}; + +static struct osmo_fsm iuup_fsm = { + .name = "IuUP", + .states = iuup_fsm_states, + .num_states = ARRAY_SIZE(iuup_fsm_states), + .timer_cb = iuup_fsm_timer_cb, + .log_subsys = DLIUUP, + .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; + uint16_t payload_crc; + uint8_t pdu_type = iuup_get_pdu_type(data); + struct iuup_pdutype0_hdr *t0h; + struct iuup_pdutype14_hdr *t14h; + + if (len < 3) + return -EINVAL; + + header_crc_computed = osmo_iuup_compute_header_crc(data, len); + if (iuup_get_hdr_crc(data) != header_crc_computed) { + LOGP(DLIUUP, LOGL_NOTICE, "Checksum error: rx 0x%02x vs exp 0x%02x\n", + 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: + t0h = (struct iuup_pdutype0_hdr *) data; + payload_crc = ((uint16_t)t0h->payload_crc_hi << 8) | t0h->payload_crc_lo; + payload_crc_computed = osmo_iuup_compute_payload_crc(data, len); + if (payload_crc != payload_crc_computed) + return -EIO; + break; + case IUUP_PDU_T_CONTROL: + t14h = (struct iuup_pdutype14_hdr *) data; + if (t14h->ack_nack == IUUP_AN_PROCEDURE) { + payload_crc = ((uint16_t)t14h->payload_crc_hi << 8) | t14h->payload_crc_lo; + payload_crc_computed = osmo_iuup_compute_payload_crc(data, len); + if (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_iuup_tnl_prim *itp) +{ + struct osmo_prim_hdr *oph = &itp->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_IUUP_TNL_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: %s\n", + osmo_hexdump((const unsigned char *) msgb_l2(oph->msg), msgb_l2len(oph->msg))); + /* 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, itp); + 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, itp); + 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, itp); + 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, itp); + 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, itp); + 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_iuup_rnl_prim *irp) +{ + struct osmo_prim_hdr *oph = &irp->oph; + int rc; + + OSMO_ASSERT(oph->sap == SAP_IUUP_RNL); + + switch (OSMO_PRIM_HDR(oph)) { + case OSMO_PRIM(OSMO_IUUP_RNL_CONFIG, PRIM_OP_REQUEST): + rc = osmo_fsm_inst_dispatch(inst->fi, IUUP_FSM_EVT_IUUP_CONFIG_REQ, irp); + msgb_free(irp->oph.msg); + break; + case OSMO_PRIM(OSMO_IUUP_RNL_DATA, PRIM_OP_REQUEST): + rc = osmo_fsm_inst_dispatch(inst->fi, IUUP_FSM_EVT_IUUP_DATA_REQ, irp); + if (rc != 0) + msgb_free(irp->oph.msg); + break; + case OSMO_PRIM(OSMO_IUUP_RNL_STATUS, PRIM_OP_REQUEST): + rc = osmo_fsm_inst_dispatch(inst->fi, IUUP_FSM_EVT_IUUP_STATUS_REQ, irp); + msgb_free(irp->oph.msg); + break; + default: + rc = -EINVAL; + msgb_free(irp->oph.msg); + } + return rc; +} + +struct osmo_iuup_instance *osmo_iuup_instance_alloc(void *ctx, const char *id) +{ + struct osmo_iuup_instance *iui; + iui = talloc_zero(ctx, struct osmo_iuup_instance); + if (!iui) + return NULL; + + iui->fi = osmo_fsm_inst_alloc(&iuup_fsm, NULL, iui, LOGL_DEBUG, id); + if (!iui->fi) + goto free_ret; + + return iui; +free_ret: + talloc_free(iui); + return NULL; +} + +void osmo_iuup_instance_free(struct osmo_iuup_instance *iui) +{ + if (!iui) + return; + + if (iui->fi) + osmo_fsm_inst_free(iui->fi); + iui->fi = NULL; + talloc_free(iui); +} + +void osmo_iuup_instance_set_user_prim_cb(struct osmo_iuup_instance *iui, osmo_prim_cb func, void *priv) +{ + iui->user_prim_cb = func; + iui->user_prim_priv = priv; +} +void osmo_iuup_instance_set_transport_prim_cb(struct osmo_iuup_instance *iui, osmo_prim_cb func, void *priv) +{ + iui->transport_prim_cb = func; + iui->transport_prim_priv = priv; +} + +static __attribute__((constructor)) void on_dso_load_iuup_fsm(void) +{ + OSMO_ASSERT(osmo_fsm_register(&iuup_fsm) == 0); +} diff --git a/src/gsm/libosmogsm.map b/src/gsm/libosmogsm.map index 475ec02a..b971ca01 100644 --- a/src/gsm/libosmogsm.map +++ b/src/gsm/libosmogsm.map @@ -771,5 +771,16 @@ osmo_gad_raw_read; osmo_gad_raw_write; osmo_gad_type_names; +osmo_iuup_compute_header_crc; +osmo_iuup_compute_payload_crc; +osmo_iuup_instance_alloc; +osmo_iuup_instance_free; +osmo_iuup_instance_set_user_prim_cb; +osmo_iuup_instance_set_transport_prim_cb; +osmo_iuup_tnl_prim_up; +osmo_iuup_rnl_prim_down; +osmo_iuup_rnl_prim_alloc; +osmo_iuup_tnl_prim_alloc; + local: *; }; diff --git a/src/logging.c b/src/logging.c index 9497f288..e5c66f21 100644 --- a/src/logging.c +++ b/src/logging.c @@ -294,6 +294,12 @@ static const struct log_info_cat internal_cat[OSMO_NUM_DLIB] = { .enabled = 1, .loglevel = LOGL_NOTICE, .color = "\033[38;5;63m", }, + [INT2IDX(DLIUUP)] = { + .name = "DLIUUP", + .description = "Iu UP layer", + .enabled = 1, .loglevel = LOGL_NOTICE, + .color = "\033[38;5;65m", + }, }; void assert_loginfo(const char *src) diff --git a/tests/Makefile.am b/tests/Makefile.am index a6f60179..f54ce188 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -45,6 +45,7 @@ check_PROGRAMS = timer/timer_test sms/sms_test ussd/ussd_test \ time_cc/time_cc_test \ gsm48/rest_octets_test \ base64/base64_test \ + iuup/iuup_test \ $(NULL) if ENABLE_MSGFILE @@ -328,6 +329,9 @@ it_q_it_q_test_LDADD = $(LDADD) time_cc_time_cc_test_SOURCES = time_cc/time_cc_test.c time_cc_time_cc_test_LDADD = $(LDADD) +iuup_iuup_test_SOURCES = iuup/iuup_test.c +iuup_iuup_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la + # The `:;' works around a Bash 3.2 bug when the output is not writeable. $(srcdir)/package.m4: $(top_srcdir)/configure.ac :;{ \ @@ -420,6 +424,7 @@ EXTRA_DIST = testsuite.at $(srcdir)/package.m4 $(TESTSUITE) \ time_cc/time_cc_test.ok \ gsm48/rest_octets_test.ok \ base64/base64_test.ok \ + iuup/iuup_test.ok \ $(NULL) if ENABLE_LIBSCTP @@ -616,6 +621,8 @@ endif >$(srcdir)/it_q/it_q_test.ok time_cc/time_cc_test \ >$(srcdir)/time_cc/time_cc_test.ok + iuup/iuup_test \ + >$(srcdir)/iuup/iuup_test.ok check-local: atconfig $(TESTSUITE) [ -e /proc/cpuinfo ] && cat /proc/cpuinfo diff --git a/tests/iuup/iuup_test.c b/tests/iuup/iuup_test.c new file mode 100644 index 00000000..0c7dbff1 --- /dev/null +++ b/tests/iuup/iuup_test.c @@ -0,0 +1,543 @@ +#include <stdint.h> +#include <stdio.h> + +#include <osmocom/core/application.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/fsm.h> +#include <osmocom/core/select.h> + +#include <osmocom/gsm/prim.h> +#include <osmocom/gsm/iuup.h> + +static void *iuup_test_ctx; + +static struct osmo_iuup_rnl_config def_configure_req = { + .transparent = false, + .active = true, + .supported_versions_mask = 0x0001, + .num_rfci = 3, + .num_subflows = 3, + .subflow_sizes = { + {81, 103, 60}, + {39, 0, 0}, + {0, 0, 0}, + }, + /* .delivery_err_sdu = All set to 0 (YES) by default, */ + .IPTIs_present = true, + .IPTIs = {1, 7, 1}, + .t_init = { .t_ms = IUUP_TIMER_INIT_T_DEFAULT, .n_max = IUUP_TIMER_INIT_N_DEFAULT }, + .t_ta = { .t_ms = IUUP_TIMER_TA_T_DEFAULT, .n_max = IUUP_TIMER_TA_N_DEFAULT }, + .t_rc = { .t_ms = IUUP_TIMER_RC_T_DEFAULT, .n_max = IUUP_TIMER_RC_N_DEFAULT }, +}; + +/* Frame 33, "Initialization", OS#4744 3g_call_23112021.pcapng +IuUP + 1110 .... = PDU Type: Control Procedure (14) + .... 00.. = Ack/Nack: Procedure (0) + .... ..00 = Frame Number: 0 + 0000 .... = Mode Version: 0x0 + .... 0000 = Procedure: Initialization (0) + 1101 11.. = Header CRC: 0x37 [correct] + .... ..11 1001 1001 = Payload CRC: 0x399 + 000. .... = Spare: 0x0 + ...1 .... = TI: IPTIs present in frame (1) + .... 011. = Subflows: 3 + .... ...0 = Chain Indicator: this frame is the last frame for the procedure (0) + RFCI 0 Initialization + 0... .... = RFCI 0 LRI: Not last RFCI (0x0) + .0.. .... = RFCI 0 LI: one octet used (0x0) + ..00 0000 = RFCI 0: 0 + RFCI 0 Flow 0 Len: 81 + RFCI 0 Flow 1 Len: 103 + RFCI 0 Flow 2 Len: 60 + RFCI 1 Initialization + 0... .... = RFCI 1 LRI: Not last RFCI (0x0) + .0.. .... = RFCI 1 LI: one octet used (0x0) + ..00 0001 = RFCI 1: 1 + RFCI 1 Flow 0 Len: 39 + RFCI 1 Flow 1 Len: 0 + RFCI 1 Flow 2 Len: 0 + RFCI 2 Initialization + 1... .... = RFCI 2 LRI: Last RFCI in current frame (0x1) + .0.. .... = RFCI 2 LI: one octet used (0x0) + ..00 0010 = RFCI 2: 2 + RFCI 2 Flow 0 Len: 0 + RFCI 2 Flow 1 Len: 0 + RFCI 2 Flow 2 Len: 0 + IPTIs + 0001 .... = RFCI 0 IPTI: 0x1 + .... 0111 = RFCI 1 IPTI: 0x7 + 0001 .... = RFCI 2 IPTI: 0x1 + Iu UP Mode Versions Supported: 0x0001 + 0... .... .... .... = Version 16: not supported (0x0) + .0.. .... .... .... = Version 15: not supported (0x0) + ..0. .... .... .... = Version 14: not supported (0x0) + ...0 .... .... .... = Version 13: not supported (0x0) + .... 0... .... .... = Version 12: not supported (0x0) + .... .0.. .... .... = Version 11: not supported (0x0) + .... ..0. .... .... = Version 10: not supported (0x0) + .... ...0 .... .... = Version 9: not supported (0x0) + .... .... 0... .... = Version 8: not supported (0x0) + .... .... .0.. .... = Version 7: not supported (0x0) + .... .... ..0. .... = Version 6: not supported (0x0) + .... .... ...0 .... = Version 5: not supported (0x0) + .... .... .... 0... = Version 4: not supported (0x0) + .... .... .... .0.. = Version 3: not supported (0x0) + .... .... .... ..0. = Version 2: not supported (0x0) + .... .... .... ...1 = Version 1: supported (0x1) + 0000 .... = RFCI Data Pdu Type: PDU type 0 (0x0) +*/ +static const uint8_t iuup_initialization[] = { + 0xe0, 0x00, 0xdf, 0x99, 0x16, 0x00, 0x51, 0x67, 0x3c, 0x01, 0x27, 0x00, + 0x00, 0x82, 0x00, 0x00, 0x00, 0x17, 0x10, 0x00, 0x01, 0x00 +}; + +/* Frame 87, "Data RFCI=0 FN = 1", OS#4744 3g_call_23112021.pcapng +IuUP + 0000 .... = PDU Type: Data with CRC (0) + .... 0001 = Frame Number: 1 + 00.. .... = FQC: Frame Good (0) + ..00 0000 = RFCI: 0x00 + 1110 00.. = Header CRC: 0x38 [correct] + .... ..11 1111 1111 = Payload CRC: 0x3ff + Payload Data: 08556d944c71a1a081e7ead204244480000ecd82b81118000097c4794e7740 +*/ +static const uint8_t iuup_data[] = { + 0x01, 0x00, 0xe3, 0xff, /*payload starts here: */ 0x08, 0x55, 0x6d, 0x94, 0x4c, 0x71, 0xa1, 0xa0, + 0x81, 0xe7, 0xea, 0xd2, 0x04, 0x24, 0x44, 0x80, 0x00, 0x0e, 0xcd, 0x82, + 0xb8, 0x11, 0x18, 0x00, 0x00, 0x97, 0xc4, 0x79, 0x4e, 0x77, 0x40 +}; + +#define IUUP_MSGB_SIZE 4096 + +static struct osmo_iuup_tnl_prim *itp_ctrl_nack_alloc(enum iuup_procedure proc_ind, enum iuup_error_cause error_cause, uint8_t fn) +{ + struct osmo_iuup_tnl_prim *tnp; + struct iuup_ctrl_nack *nack; + tnp = osmo_iuup_tnl_prim_alloc(iuup_test_ctx, OSMO_IUUP_TNL_UNITDATA, PRIM_OP_INDICATION, IUUP_MSGB_SIZE); + tnp->oph.msg->l2h = msgb_put(tnp->oph.msg, sizeof(struct iuup_ctrl_nack)); + nack = (struct iuup_ctrl_nack *) msgb_l2(tnp->oph.msg); + *nack = (struct iuup_ctrl_nack){ + .hdr = { + .frame_nr = fn, + .ack_nack = IUUP_AN_NACK, + .pdu_type = IUUP_PDU_T_CONTROL, + .proc_ind = proc_ind, + .mode_version = 0, + .payload_crc_hi = 0, + .header_crc = 0, + .payload_crc_lo = 0, + }, + .spare = 0, + .error_cause = error_cause, + }; + nack->hdr.header_crc = osmo_iuup_compute_header_crc(msgb_l2(tnp->oph.msg), msgb_l2len(tnp->oph.msg)); + return tnp; +} + +static struct osmo_iuup_tnl_prim *itp_ctrl_ack_alloc(enum iuup_procedure proc_ind, uint8_t fn) +{ + struct osmo_iuup_tnl_prim *tnp; + struct iuup_ctrl_ack *ack; + tnp = osmo_iuup_tnl_prim_alloc(iuup_test_ctx, OSMO_IUUP_TNL_UNITDATA, PRIM_OP_INDICATION, IUUP_MSGB_SIZE); + tnp->oph.msg->l2h = msgb_put(tnp->oph.msg, sizeof(struct iuup_ctrl_ack)); + ack = (struct iuup_ctrl_ack *) msgb_l2(tnp->oph.msg); + *ack = (struct iuup_ctrl_ack){ + .hdr = { + .frame_nr = fn, + .ack_nack = IUUP_AN_ACK, + .pdu_type = IUUP_PDU_T_CONTROL, + .proc_ind = proc_ind, + .mode_version = 0, + .payload_crc_hi = 0, + .header_crc = 0, + .payload_crc_lo = 0, + }, + }; + ack->hdr.header_crc = osmo_iuup_compute_header_crc(msgb_l2(tnp->oph.msg), msgb_l2len(tnp->oph.msg)); + return tnp; +} + +static void clock_override_set(long sec, long usec) +{ + osmo_gettimeofday_override_time.tv_sec = sec + usec / (1000*1000); + osmo_gettimeofday_override_time.tv_usec = usec % (1000*1000); + printf("sys={%lu.%06lu}, %s\n", osmo_gettimeofday_override_time.tv_sec, + osmo_gettimeofday_override_time.tv_usec, __func__); +} + +void test_crc(void) +{ + int rc; + + /* Frame 34, "Initialization ACK", OS#4744 3g_call_23112021.pcapng */ + static const uint8_t iuup_initialization_ack[] = { + 0xe4, 0x00, 0xdf, 0x99, 0x16, 0x00, 0x51, 0x67, 0x3c, 0x01, 0x27, 0x00, + 0x00, 0x82, 0x00, 0x00, 0x00, 0x17, 0x10, 0x00, 0x01, 0x00 + }; + + printf("=== start: %s ===\n", __func__); + + rc = osmo_iuup_compute_header_crc(iuup_initialization, sizeof(iuup_initialization)); + printf("iuup_initialization: Header CRC = 0x%02x\n", rc); + rc = osmo_iuup_compute_payload_crc(iuup_initialization, sizeof(iuup_initialization)); + printf("iuup_initialization: Payload CRC = 0x%03x\n", rc); + + rc = osmo_iuup_compute_header_crc(iuup_initialization_ack, sizeof(iuup_initialization_ack)); + printf("iuup_initialization_ack: Header CRC = 0x%02x\n", rc); + rc = osmo_iuup_compute_payload_crc(iuup_initialization_ack, sizeof(iuup_initialization_ack)); + printf("iuup_initialization_ack: Payload CRC = 0x%03x\n", rc); + + printf("=== end: %s ===\n", __func__); +} + + +/**************************** + * test_tinit_timeout_retrans + ****************************/ +static unsigned int _tinit_timeout_retrans_user_rx_prim = 0; +static int _tinit_timeout_retrans_user_prim_cb(struct osmo_prim_hdr *oph, void *ctx) +{ + struct osmo_iuup_rnl_prim *irp = (struct osmo_iuup_rnl_prim *)oph; + printf("%s()\n", __func__); + + OSMO_ASSERT(OSMO_PRIM_HDR(&irp->oph) == OSMO_PRIM(OSMO_IUUP_RNL_STATUS, PRIM_OP_INDICATION)); + + OSMO_ASSERT(irp->u.status.procedure == IUUP_PROC_ERR_EVENT); + OSMO_ASSERT(irp->u.status.u.error_event.cause == IUUP_ERR_CAUSE_INIT_FAILURE_NET_TMR); + OSMO_ASSERT(irp->u.status.u.error_event.distance == IUUP_ERR_DIST_LOCAL); + _tinit_timeout_retrans_user_rx_prim++; + msgb_free(oph->msg); + return 0; +} +static unsigned int _tinit_timeout_retrans_transport_rx_prim = 0; +static int _tinit_timeout_retrans_transport_prim_cb(struct osmo_prim_hdr *oph, void *ctx) +{ + struct osmo_iuup_tnl_prim *itp = (struct osmo_iuup_tnl_prim *)oph; + struct msgb *msg = oph->msg; + + printf("%s()\n", __func__); + OSMO_ASSERT(OSMO_PRIM_HDR(&itp->oph) == OSMO_PRIM(OSMO_IUUP_TNL_UNITDATA, PRIM_OP_REQUEST)); + printf("Transport: DL len=%u: %s\n", msgb_l2len(msg), + osmo_hexdump((const unsigned char *) msgb_l2(msg), msgb_l2len(msg))); + _tinit_timeout_retrans_transport_rx_prim++; + + msgb_free(msg); + return 0; +} +void test_tinit_timeout_retrans(void) +{ + struct osmo_iuup_instance *iui; + struct osmo_iuup_rnl_prim *rnp; + int rc, i; + + iui = osmo_iuup_instance_alloc(iuup_test_ctx, __func__); + OSMO_ASSERT(iui); + osmo_iuup_instance_set_user_prim_cb(iui, _tinit_timeout_retrans_user_prim_cb, NULL); + osmo_iuup_instance_set_transport_prim_cb(iui, _tinit_timeout_retrans_transport_prim_cb, NULL); + + clock_override_set(0, 0); + + /* Tx CONFIG.req */ + rnp = osmo_iuup_rnl_prim_alloc(iuup_test_ctx, OSMO_IUUP_RNL_CONFIG, PRIM_OP_REQUEST, IUUP_MSGB_SIZE); + rnp->u.config = def_configure_req; + OSMO_ASSERT((rc = osmo_iuup_rnl_prim_down(iui, rnp)) == 0); + /* STATUS-INIT.req is transmitted automatically: */ + OSMO_ASSERT(_tinit_timeout_retrans_transport_rx_prim == 1); + + /* After one sec, INITIALIZATION msg is retransmitted */ + for (i = 1; i < IUUP_TIMER_INIT_N_DEFAULT + 1; i++) { + clock_override_set(0, IUUP_TIMER_INIT_T_DEFAULT*1000 * i); + osmo_select_main(0); + OSMO_ASSERT(_tinit_timeout_retrans_transport_rx_prim == i + 1); + } + /* Last one should send an error event: */ + OSMO_ASSERT(_tinit_timeout_retrans_user_rx_prim == 0); + clock_override_set(0, IUUP_TIMER_INIT_T_DEFAULT*1000 * i); + osmo_select_main(0); + OSMO_ASSERT(_tinit_timeout_retrans_transport_rx_prim == i); + OSMO_ASSERT(_tinit_timeout_retrans_user_rx_prim == 1); + + /* Nothing else is received afterwards. osmo_select_main() will block forever. */ + /*clock_override_set(i + 1, 0); + osmo_select_main(0); + OSMO_ASSERT(_tinit_timeout_retrans_transport_rx_prim == i); + OSMO_ASSERT(_tinit_timeout_retrans_user_rx_prim == 1);*/ + + osmo_iuup_instance_free(iui); +} + +/**************************** + * test_tinit_nack + ****************************/ +static unsigned int _init_nack_retrans_user_rx_prim = 0; +static int _init_nack_retrans_user_prim_cb(struct osmo_prim_hdr *oph, void *ctx) +{ + struct osmo_iuup_rnl_prim *irp = (struct osmo_iuup_rnl_prim *)oph; + + printf("%s()\n", __func__); + + OSMO_ASSERT(OSMO_PRIM_HDR(&irp->oph) == OSMO_PRIM(OSMO_IUUP_RNL_STATUS, PRIM_OP_INDICATION)); + + OSMO_ASSERT(irp->u.status.procedure == IUUP_PROC_ERR_EVENT); + OSMO_ASSERT(irp->u.status.u.error_event.cause == IUUP_ERR_CAUSE_INIT_FAILURE_REP_NACK); + OSMO_ASSERT(irp->u.status.u.error_event.distance == IUUP_ERR_DIST_SECOND_FWD); + _init_nack_retrans_user_rx_prim++; + msgb_free(oph->msg); + return 0; +} +static int _init_nack_retrans_transport_rx_prim = 0; +static int _init_nack_retrans_transport_prim_cb(struct osmo_prim_hdr *oph, void *ctx) +{ + struct osmo_iuup_tnl_prim *itp = (struct osmo_iuup_tnl_prim *)oph; + struct msgb *msg = oph->msg; + + printf("%s()\n", __func__); + OSMO_ASSERT(OSMO_PRIM_HDR(&itp->oph) == OSMO_PRIM(OSMO_IUUP_TNL_UNITDATA, PRIM_OP_REQUEST)); + printf("Transport: DL len=%u: %s\n", msgb_l2len(msg), + osmo_hexdump((const unsigned char *) msgb_l2(msg), msgb_l2len(msg))); + _init_nack_retrans_transport_rx_prim++; + + msgb_free(msg); + return 0; +} +void test_init_nack_retrans(void) +{ + struct osmo_iuup_instance *iui; + struct osmo_iuup_rnl_prim *rnp; + struct osmo_iuup_tnl_prim *tnp; + int rc, i; + + iui = osmo_iuup_instance_alloc(iuup_test_ctx, __func__); + OSMO_ASSERT(iui); + osmo_iuup_instance_set_user_prim_cb(iui, _init_nack_retrans_user_prim_cb, NULL); + osmo_iuup_instance_set_transport_prim_cb(iui, _init_nack_retrans_transport_prim_cb, NULL); + + clock_override_set(0, 0); + + /* Tx CONFIG.req */ + rnp = osmo_iuup_rnl_prim_alloc(iuup_test_ctx, OSMO_IUUP_RNL_CONFIG, PRIM_OP_REQUEST, IUUP_MSGB_SIZE); + rnp->u.config = def_configure_req; + OSMO_ASSERT((rc = osmo_iuup_rnl_prim_down(iui, rnp)) == 0); + /* STATUS-INIT.req is transmitted automatically: */ + OSMO_ASSERT(_init_nack_retrans_transport_rx_prim == 1); + + /* After one sec, INITIALIZATION msg is retransmitted */ + for (i = 1; i < IUUP_TIMER_INIT_N_DEFAULT + 1; i++) { + /* Send NACK: */ + tnp = itp_ctrl_nack_alloc(IUUP_PROC_INIT, IUUP_ERR_CAUSE_MODE_VERSION_NOT_SUPPORTED, 0); + OSMO_ASSERT((rc = osmo_iuup_tnl_prim_up(iui, tnp)) == 0); + /* A new INIT is retransmitted: */ + OSMO_ASSERT(_init_nack_retrans_transport_rx_prim == i + 1); + } + /* Last one should send an error event: */ + OSMO_ASSERT(_init_nack_retrans_user_rx_prim == 0); + tnp = itp_ctrl_nack_alloc(IUUP_PROC_INIT, IUUP_ERR_CAUSE_MODE_VERSION_NOT_SUPPORTED, 0); + OSMO_ASSERT((rc = osmo_iuup_tnl_prim_up(iui, tnp)) == 0); + OSMO_ASSERT(_init_nack_retrans_transport_rx_prim == i); + OSMO_ASSERT(_init_nack_retrans_user_rx_prim == 1); + + /* Nothing else is received afterwards. osmo_select_main() will block forever. */ + + osmo_iuup_instance_free(iui); +} + + +/**************************** + * test_init_ack + ****************************/ +static unsigned int _init_ack_user_rx_prim = 0; +static int _init_ack_user_prim_cb(struct osmo_prim_hdr *oph, void *ctx) +{ + struct osmo_iuup_rnl_prim *irp = (struct osmo_iuup_rnl_prim *)oph; + struct msgb *msg = oph->msg; + + printf("%s()\n", __func__); + + OSMO_ASSERT(OSMO_PRIM_HDR(&irp->oph) == OSMO_PRIM(OSMO_IUUP_RNL_DATA, PRIM_OP_INDICATION)); + printf("User: UL len=%u: %s\n", msgb_l3len(msg), + osmo_hexdump((const unsigned char *) msgb_l3(msg), msgb_l3len(msg))); + + _init_ack_user_rx_prim++; + msgb_free(oph->msg); + return 0; +} +static int _init_ack_transport_rx_prim = 0; +static int _init_ack_transport_prim_cb(struct osmo_prim_hdr *oph, void *ctx) +{ + struct osmo_iuup_tnl_prim *itp = (struct osmo_iuup_tnl_prim *)oph; + struct msgb *msg = oph->msg; + + printf("%s()\n", __func__); + OSMO_ASSERT(OSMO_PRIM_HDR(&itp->oph) == OSMO_PRIM(OSMO_IUUP_TNL_UNITDATA, PRIM_OP_REQUEST)); + printf("Transport: DL len=%u: %s\n", msgb_l2len(msg), + osmo_hexdump((const unsigned char *) msgb_l2(msg), msgb_l2len(msg))); + _init_ack_transport_rx_prim++; + + msgb_free(msg); + return 0; +} +void test_init_ack(void) +{ + struct osmo_iuup_instance *iui; + struct osmo_iuup_rnl_prim *rnp; + struct osmo_iuup_tnl_prim *tnp; + struct iuup_pdutype0_hdr *hdr0; + int rc; + + iui = osmo_iuup_instance_alloc(iuup_test_ctx, __func__); + OSMO_ASSERT(iui); + osmo_iuup_instance_set_user_prim_cb(iui, _init_ack_user_prim_cb, NULL); + osmo_iuup_instance_set_transport_prim_cb(iui, _init_ack_transport_prim_cb, NULL); + + clock_override_set(0, 0); + + /* Tx CONFIG.req */ + rnp = osmo_iuup_rnl_prim_alloc(iuup_test_ctx, OSMO_IUUP_RNL_CONFIG, PRIM_OP_REQUEST, IUUP_MSGB_SIZE); + rnp->u.config = def_configure_req; + OSMO_ASSERT((rc = osmo_iuup_rnl_prim_down(iui, rnp)) == 0); + /* STATUS-INIT.req is transmitted automatically: */ + OSMO_ASSERT(_init_ack_transport_rx_prim == 1); + + /* Send ACK: */ + tnp = itp_ctrl_ack_alloc(IUUP_PROC_INIT, 0); + OSMO_ASSERT((rc = osmo_iuup_tnl_prim_up(iui, tnp)) == 0); + OSMO_ASSERT(_init_ack_transport_rx_prim == 1); /* Make sure there's no retrans */ + OSMO_ASSERT(_init_ack_user_rx_prim == 0); /* Make sure there's no error event */ + + /* Send IuUP incoming data to the implementation: */ + tnp = osmo_iuup_tnl_prim_alloc(iuup_test_ctx, OSMO_IUUP_TNL_UNITDATA, PRIM_OP_INDICATION, IUUP_MSGB_SIZE); + tnp->oph.msg->l2h = msgb_put(tnp->oph.msg, sizeof(iuup_data)); + hdr0 = (struct iuup_pdutype0_hdr *)msgb_l2(tnp->oph.msg); + memcpy(hdr0, iuup_data, sizeof(iuup_data)); + OSMO_ASSERT((rc = osmo_iuup_tnl_prim_up(iui, tnp)) == 0); + /* We receive it in RNL: */ + OSMO_ASSERT(_init_ack_user_rx_prim == 1); + + /* Now in opposite direction, RNL->[IuuP]->TNL: */ + rnp = osmo_iuup_rnl_prim_alloc(iuup_test_ctx, OSMO_IUUP_RNL_DATA, PRIM_OP_REQUEST, IUUP_MSGB_SIZE); + rnp->u.data.rfci = 0; + rnp->u.data.frame_nr = 1; + rnp->u.data.fqc = IUUP_FQC_FRAME_GOOD; + rnp->oph.msg->l3h = msgb_put(rnp->oph.msg, sizeof(iuup_data) - 4); + memcpy(rnp->oph.msg->l3h, iuup_data + 4, sizeof(iuup_data) - 4); + OSMO_ASSERT((rc = osmo_iuup_rnl_prim_down(iui, rnp)) == 0); + OSMO_ASSERT(_init_ack_transport_rx_prim == 2); /* We receive data in TNL */ + + osmo_iuup_instance_free(iui); +} + +/**************************** + * test_passive_init + ****************************/ +static unsigned int _passive_init_user_rx_prim = 0; +static int _passive_init_user_prim_cb(struct osmo_prim_hdr *oph, void *ctx) +{ + struct osmo_iuup_rnl_prim *irp = (struct osmo_iuup_rnl_prim *)oph; + struct msgb *msg = oph->msg; + + printf("%s()\n", __func__); + + OSMO_ASSERT(OSMO_PRIM_HDR(&irp->oph) == OSMO_PRIM(OSMO_IUUP_RNL_DATA, PRIM_OP_INDICATION)); + printf("User: UL len=%u: %s\n", msgb_l3len(msg), + osmo_hexdump((const unsigned char *) msgb_l3(msg), msgb_l3len(msg))); + + _passive_init_user_rx_prim++; + msgb_free(oph->msg); + return 0; +} +static int _passive_init_transport_rx_prim = 0; +static int _passive_init_transport_prim_cb(struct osmo_prim_hdr *oph, void *ctx) +{ + struct osmo_iuup_tnl_prim *itp = (struct osmo_iuup_tnl_prim *)oph; + struct msgb *msg; + + printf("%s()\n", __func__); + msg = oph->msg; + OSMO_ASSERT(OSMO_PRIM_HDR(&itp->oph) == OSMO_PRIM(OSMO_IUUP_TNL_UNITDATA, PRIM_OP_REQUEST)); + printf("Transport: DL len=%u: %s\n", msgb_l2len(msg), + osmo_hexdump((const unsigned char *) msgb_l2(msg), msgb_l2len(msg))); + _passive_init_transport_rx_prim++; + + msgb_free(msg); + return 0; +} +void test_passive_init(void) +{ + /* Here we check the passive INIT code path, aka receiving INIT and returning INIT_ACK/NACK */ + struct osmo_iuup_instance *iui; + struct osmo_iuup_rnl_prim *rnp; + struct osmo_iuup_tnl_prim *tnp; + struct iuup_pdutype14_hdr *hdr14; + struct iuup_pdutype0_hdr *hdr0; + int rc; + + iui = osmo_iuup_instance_alloc(iuup_test_ctx, __func__); + OSMO_ASSERT(iui); + osmo_iuup_instance_set_user_prim_cb(iui, _passive_init_user_prim_cb, NULL); + osmo_iuup_instance_set_transport_prim_cb(iui, _passive_init_transport_prim_cb, NULL); + + clock_override_set(0, 0); + + /* Tx CONFIG.req */ + rnp = osmo_iuup_rnl_prim_alloc(iuup_test_ctx, OSMO_IUUP_RNL_CONFIG, PRIM_OP_REQUEST, IUUP_MSGB_SIZE); + rnp->u.config = def_configure_req; + rnp->u.config.active = false; + OSMO_ASSERT((rc = osmo_iuup_rnl_prim_down(iui, rnp)) == 0); + /* STATUS-INIT.req is NOT transmitted automatically: */ + OSMO_ASSERT(_passive_init_transport_rx_prim == 0); + + /* Send Init: */ + tnp = osmo_iuup_tnl_prim_alloc(iuup_test_ctx, OSMO_IUUP_TNL_UNITDATA, PRIM_OP_INDICATION, IUUP_MSGB_SIZE); + tnp->oph.msg->l2h = msgb_put(tnp->oph.msg, sizeof(iuup_initialization)); + hdr14 = (struct iuup_pdutype14_hdr *)msgb_l2(tnp->oph.msg); + memcpy(hdr14, iuup_initialization, sizeof(iuup_initialization)); + OSMO_ASSERT((rc = osmo_iuup_tnl_prim_up(iui, tnp)) == 0); + OSMO_ASSERT(_passive_init_transport_rx_prim == 1); /* We receive an Init ACK */ + OSMO_ASSERT(_passive_init_user_rx_prim == 0); + + /* Send IuUP incoming data to the implementation: */ + tnp = osmo_iuup_tnl_prim_alloc(iuup_test_ctx, OSMO_IUUP_TNL_UNITDATA, PRIM_OP_INDICATION, IUUP_MSGB_SIZE); + tnp->oph.msg->l2h = msgb_put(tnp->oph.msg, sizeof(iuup_data)); + hdr0 = (struct iuup_pdutype0_hdr *)msgb_l2(tnp->oph.msg); + memcpy(hdr0, iuup_data, sizeof(iuup_data)); + OSMO_ASSERT((rc = osmo_iuup_tnl_prim_up(iui, tnp)) == 0); + /* We receive it in RNL: */ + OSMO_ASSERT(_passive_init_user_rx_prim == 1); + + /* Now in opposite direction, RNL->[IuuP]->TNL: */ + rnp = osmo_iuup_rnl_prim_alloc(iuup_test_ctx, OSMO_IUUP_RNL_DATA, PRIM_OP_REQUEST, IUUP_MSGB_SIZE); + rnp->u.data.rfci = 0; + rnp->u.data.frame_nr = 1; + rnp->u.data.fqc = IUUP_FQC_FRAME_GOOD; + rnp->oph.msg->l3h = msgb_put(rnp->oph.msg, sizeof(iuup_data) - 4); + memcpy(rnp->oph.msg->l3h, iuup_data + 4, sizeof(iuup_data) - 4); + OSMO_ASSERT((rc = osmo_iuup_rnl_prim_down(iui, rnp)) == 0); + OSMO_ASSERT(_passive_init_transport_rx_prim == 2); /* We receive data in TNL */ + + osmo_iuup_instance_free(iui); +} + +int main(int argc, char **argv) +{ + iuup_test_ctx = talloc_named_const(NULL, 0, "iuup_test"); + osmo_init_logging2(iuup_test_ctx, NULL); + log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_NONE); + log_set_print_category(osmo_stderr_target, 1); + log_set_print_category_hex(osmo_stderr_target, 0); + log_set_use_color(osmo_stderr_target, 0); + log_set_category_filter(osmo_stderr_target, DLIUUP, 1, LOGL_DEBUG); + osmo_fsm_log_addr(false); + + osmo_gettimeofday_override = true; + + test_crc(); + test_tinit_timeout_retrans(); + test_init_nack_retrans(); + test_init_ack(); + test_passive_init(); + + printf("OK.\n"); +} diff --git a/tests/iuup/iuup_test.ok b/tests/iuup/iuup_test.ok new file mode 100644 index 00000000..54230968 --- /dev/null +++ b/tests/iuup/iuup_test.ok @@ -0,0 +1,45 @@ +=== start: test_crc === +iuup_initialization: Header CRC = 0x37 +iuup_initialization: Payload CRC = 0x399 +iuup_initialization_ack: Header CRC = 0x09 +iuup_initialization_ack: Payload CRC = 0x399 +=== end: test_crc === +sys={0.000000}, clock_override_set +_tinit_timeout_retrans_transport_prim_cb() +Transport: DL len=22: e0 00 df 99 16 00 51 67 3c 01 27 00 00 82 00 00 00 17 10 00 01 00 +sys={1.000000}, clock_override_set +_tinit_timeout_retrans_transport_prim_cb() +Transport: DL len=22: e0 00 df 99 16 00 51 67 3c 01 27 00 00 82 00 00 00 17 10 00 01 00 +sys={2.000000}, clock_override_set +_tinit_timeout_retrans_transport_prim_cb() +Transport: DL len=22: e0 00 df 99 16 00 51 67 3c 01 27 00 00 82 00 00 00 17 10 00 01 00 +sys={3.000000}, clock_override_set +_tinit_timeout_retrans_transport_prim_cb() +Transport: DL len=22: e0 00 df 99 16 00 51 67 3c 01 27 00 00 82 00 00 00 17 10 00 01 00 +sys={4.000000}, clock_override_set +_tinit_timeout_retrans_user_prim_cb() +sys={0.000000}, clock_override_set +_init_nack_retrans_transport_prim_cb() +Transport: DL len=22: e0 00 df 99 16 00 51 67 3c 01 27 00 00 82 00 00 00 17 10 00 01 00 +_init_nack_retrans_transport_prim_cb() +Transport: DL len=22: e0 00 df 99 16 00 51 67 3c 01 27 00 00 82 00 00 00 17 10 00 01 00 +_init_nack_retrans_transport_prim_cb() +Transport: DL len=22: e0 00 df 99 16 00 51 67 3c 01 27 00 00 82 00 00 00 17 10 00 01 00 +_init_nack_retrans_transport_prim_cb() +Transport: DL len=22: e0 00 df 99 16 00 51 67 3c 01 27 00 00 82 00 00 00 17 10 00 01 00 +_init_nack_retrans_user_prim_cb() +sys={0.000000}, clock_override_set +_init_ack_transport_prim_cb() +Transport: DL len=22: e0 00 df 99 16 00 51 67 3c 01 27 00 00 82 00 00 00 17 10 00 01 00 +_init_ack_user_prim_cb() +User: UL len=31: 08 55 6d 94 4c 71 a1 a0 81 e7 ea d2 04 24 44 80 00 0e cd 82 b8 11 18 00 00 97 c4 79 4e 77 40 +_init_ack_transport_prim_cb() +Transport: DL len=35: 01 00 e3 ff 08 55 6d 94 4c 71 a1 a0 81 e7 ea d2 04 24 44 80 00 0e cd 82 b8 11 18 00 00 97 c4 79 4e 77 40 +sys={0.000000}, clock_override_set +_passive_init_transport_prim_cb() +Transport: DL len=4: e4 00 24 00 +_passive_init_user_prim_cb() +User: UL len=31: 08 55 6d 94 4c 71 a1 a0 81 e7 ea d2 04 24 44 80 00 0e cd 82 b8 11 18 00 00 97 c4 79 4e 77 40 +_passive_init_transport_prim_cb() +Transport: DL len=35: 01 00 e3 ff 08 55 6d 94 4c 71 a1 a0 81 e7 ea d2 04 24 44 80 00 0e cd 82 b8 11 18 00 00 97 c4 79 4e 77 40 +OK. diff --git a/tests/logging/logging_vty_test.vty b/tests/logging/logging_vty_test.vty index 99e8781f..c74accda 100644 --- a/tests/logging/logging_vty_test.vty +++ b/tests/logging/logging_vty_test.vty @@ -54,7 +54,7 @@ logging_vty_test# list logging print level (0|1) logging print file (0|1|basename) [last] logging set-log-mask MASK - logging level (aa|bb|ccc|dddd|eee|lglobal|llapd|linp|lmux|lmi|lmib|lsms|lctrl|lgtp|lstats|lgsup|loap|lss7|lsccp|lsua|lm3ua|lmgcp|ljibuf|lrspro|lns|lbssgp|lnsdata|lnssignal) (debug|info|notice|error|fatal) + logging level (aa|bb|ccc|dddd|eee|lglobal|llapd|linp|lmux|lmi|lmib|lsms|lctrl|lgtp|lstats|lgsup|loap|lss7|lsccp|lsua|lm3ua|lmgcp|ljibuf|lrspro|lns|lbssgp|lnsdata|lnssignal|liuup) (debug|info|notice|error|fatal) logging level set-all (debug|info|notice|error|fatal) logging level force-all (debug|info|notice|error|fatal) no logging level force-all @@ -472,7 +472,7 @@ DEEE FATAL Log message for DEEE on level LOGL_FATAL logging_vty_test# list ... - logp (aa|bb|ccc|dddd|eee|lglobal|llapd|linp|lmux|lmi|lmib|lsms|lctrl|lgtp|lstats|lgsup|loap|lss7|lsccp|lsua|lm3ua|lmgcp|ljibuf|lrspro|lns|lbssgp|lnsdata|lnssignal) (debug|info|notice|error|fatal) .LOGMESSAGE + logp (aa|bb|ccc|dddd|eee|lglobal|llapd|linp|lmux|lmi|lmib|lsms|lctrl|lgtp|lstats|lgsup|loap|lss7|lsccp|lsua|lm3ua|lmgcp|ljibuf|lrspro|lns|lbssgp|lnsdata|lnssignal|liuup) (debug|info|notice|error|fatal) .LOGMESSAGE ... logging_vty_test# logp? @@ -507,6 +507,7 @@ logging_vty_test# logp ? lbssgp GPRS BSSGP layer lnsdata GPRS NS layer data PDU lnssignal GPRS NS layer signal PDU + liuup Iu UP layer logging_vty_test# logp lglobal ? debug Log debug messages and higher levels diff --git a/tests/testsuite.at b/tests/testsuite.at index 92c4e39c..882203e0 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -458,3 +458,9 @@ AT_KEYWORDS([time_cc]) cat $abs_srcdir/time_cc/time_cc_test.ok > expout AT_CHECK([$abs_top_builddir/tests/time_cc/time_cc_test], [0], [expout], [ignore]) AT_CLEANUP + +AT_SETUP([iuup]) +AT_KEYWORDS([iuup]) +cat $abs_srcdir/iuup/iuup_test.ok > expout +AT_CHECK([$abs_top_builddir/tests/iuup/iuup_test], [0], [expout], [ignore]) +AT_CLEANUP |