fixeria has submitted this change. ( https://gerrit.osmocom.org/c/osmo-trx/+/43104?usp=email )
Change subject: libosmo-trx: add TRXC/TRXD message codec ......................................................................
libosmo-trx: add TRXC/TRXD message codec
Introduce libosmo-trx: a shared implementation of the TRXC/TRXD (OpenBTS-style TRX) protocol, to be used by osmo-trx, osmo-bts, trxcon, and the upcoming C rewrite of fake_trx.
This initial version provides two I/O-free codec modules:
* trxd: TRXD PDU codec for BURST.ind and BURST.req * trxc: verb-agnostic TRXC message codec (CMD/RSP/IND)
The codec logic is based on the existing implementations in osmo-bts (f0ee51997470e1c3020ddb85082385ed43ba0c68).
Change-Id: I933fc417a67d0043f74b04626b7643c79e381492 --- M .gitignore M Makefile.am M configure.ac A libosmo-trx/Makefile.am A libosmo-trx/include/Makefile.am A libosmo-trx/include/osmocom/trx/trxc.h A libosmo-trx/include/osmocom/trx/trxd.h A libosmo-trx/libosmo-trx.pc.in A libosmo-trx/src/Makefile.am A libosmo-trx/src/trxc.c A libosmo-trx/src/trxd.c M tests/Makefile.am A tests/libosmo-trx/Makefile.am A tests/libosmo-trx/trxc_test.c A tests/libosmo-trx/trxc_test.ok A tests/libosmo-trx/trxd_test.c A tests/libosmo-trx/trxd_test.ok M tests/testsuite.at 18 files changed, 1,948 insertions(+), 1 deletion(-)
Approvals: Jenkins Builder: Verified laforge: Looks good to me, but someone else must approve pespin: Looks good to me, but someone else must approve fixeria: Looks good to me, approved
diff --git a/.gitignore b/.gitignore index 21ff141..e33145b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ *.o *.lo *.la +*.pc Transceiver52M/osmo-trx-uhd Transceiver52M/osmo-trx-usrp1 Transceiver52M/osmo-trx-lms @@ -19,6 +20,8 @@ .clang-format
# tests +tests/libosmo-trx/trxc_test +tests/libosmo-trx/trxd_test tests/CommonLibs/BitVectorTest tests/CommonLibs/F16Test tests/CommonLibs/InterthreadTest diff --git a/Makefile.am b/Makefile.am index d4c3c93..844d6e7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -34,6 +34,7 @@
# Order must be preserved SUBDIRS += \ + libosmo-trx \ CommonLibs \ GSM \ Transceiver52M \ diff --git a/configure.ac b/configure.ac index 50bdd64..356ce9d 100644 --- a/configure.ac +++ b/configure.ac @@ -66,7 +66,11 @@ PKG_PROG_PKG_CONFIG([0.20])
AC_LIBTOOL_WIN32_DLL -AC_DISABLE_SHARED dnl don't build shared libraries +dnl Most libraries in this tree (CommonLibs, GSM, Transceiver52M/*) are +dnl noinst convenience libraries and never installed, so static vs. shared +dnl makes no difference for them. libosmo-trx/src/libosmo-trx.la, however, +dnl is installed and meant to be linked by other packages (osmo-bts, +dnl osmocom-bb/trxcon), so shared library support must stay enabled. AC_ENABLE_STATIC dnl do build static libraries LT_INIT
@@ -357,6 +361,10 @@ dnl Output files AC_CONFIG_FILES([\ Makefile \ + libosmo-trx/Makefile \ + libosmo-trx/include/Makefile \ + libosmo-trx/src/Makefile \ + libosmo-trx/libosmo-trx.pc \ CommonLibs/Makefile \ GSM/Makefile \ Transceiver52M/Makefile \ @@ -372,6 +380,7 @@ Transceiver52M/device/ipc/Makefile \ Transceiver52M/device/bladerf/Makefile \ tests/Makefile \ + tests/libosmo-trx/Makefile \ tests/CommonLibs/Makefile \ tests/Transceiver52M/Makefile \ utils/Makefile \ diff --git a/libosmo-trx/Makefile.am b/libosmo-trx/Makefile.am new file mode 100644 index 0000000..e8ae7eb --- /dev/null +++ b/libosmo-trx/Makefile.am @@ -0,0 +1,7 @@ +SUBDIRS = \ + include \ + src \ + $(NULL) + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = libosmo-trx.pc diff --git a/libosmo-trx/include/Makefile.am b/libosmo-trx/include/Makefile.am new file mode 100644 index 0000000..3c5f908 --- /dev/null +++ b/libosmo-trx/include/Makefile.am @@ -0,0 +1,4 @@ +nobase_include_HEADERS = \ + osmocom/trx/trxc.h \ + osmocom/trx/trxd.h \ + $(NULL) diff --git a/libosmo-trx/include/osmocom/trx/trxc.h b/libosmo-trx/include/osmocom/trx/trxc.h new file mode 100644 index 0000000..ea84b81 --- /dev/null +++ b/libosmo-trx/include/osmocom/trx/trxc.h @@ -0,0 +1,62 @@ +/*! \file osmocom/trx/trxc.h + * TRXC (control) protocol and clock indications: I/O-free message codec. */ +#pragma once + +#include <stdint.h> +#include <stddef.h> + +/*! Maximum length of a command verb (incl. '\0') */ +#define OSMO_TRXC_CMD_LEN_MAX 32 +/*! Maximum length of the parameters string (incl. '\0') */ +#define OSMO_TRXC_PARAMS_LEN_MAX 128 +/*! Recommended TRXC socket read/send buffer size */ +#define OSMO_TRXC_MSG_BUF_SIZE 1500 + +enum osmo_trxc_msg_type { + OSMO_TRXC_MT_CMD, /*!< "CMD <verb> [<params>]" (L1 -> TRX) */ + OSMO_TRXC_MT_RSP, /*!< "RSP <verb> <status> [<params>]" (TRX -> L1) */ + OSMO_TRXC_MT_IND, /*!< "IND <verb> <params>" (TRX -> L1) */ +}; + +/*! A single de-/serialized TRXC message. + * The command verb set is deliberately open: the codec is verb-agnostic, + * so dialect specific (e.g. trxcon's ECHO/MEASURE/SETTA) and custom + * (e.g. fake_trx's FAKE_*) commands need no library changes. */ +struct osmo_trxc_msg { + enum osmo_trxc_msg_type type; + char cmd[OSMO_TRXC_CMD_LEN_MAX]; /*!< verb, e.g. "POWERON" */ + int status; /*!< RSP only */ + char params[OSMO_TRXC_PARAMS_LEN_MAX]; /*!< raw parameters, may be "" */ +}; + +int osmo_trxc_msg_parse(struct osmo_trxc_msg *msg, const char *buf, size_t len); +int osmo_trxc_msg_build(char *buf, size_t buf_size, const struct osmo_trxc_msg *msg); +int osmo_trxc_msg_params_scan(const struct osmo_trxc_msg *msg, const char *fmt, ...); + +const char *osmo_trxc_msg_name(const struct osmo_trxc_msg *msg); +char *osmo_trxc_msg_name_buf(char *buf, size_t buf_size, + const struct osmo_trxc_msg *msg); + +/* Well-known command verbs (the codec itself is verb-agnostic) */ +#define OSMO_TRXC_CMD_POWERON "POWERON" +#define OSMO_TRXC_CMD_POWEROFF "POWEROFF" +#define OSMO_TRXC_CMD_RXTUNE "RXTUNE" +#define OSMO_TRXC_CMD_TXTUNE "TXTUNE" +#define OSMO_TRXC_CMD_SETSLOT "SETSLOT" +#define OSMO_TRXC_CMD_SETTSC "SETTSC" +#define OSMO_TRXC_CMD_SETBSIC "SETBSIC" +#define OSMO_TRXC_CMD_SETPOWER "SETPOWER" +#define OSMO_TRXC_CMD_ADJPOWER "ADJPOWER" +#define OSMO_TRXC_CMD_NOMTXPOWER "NOMTXPOWER" +#define OSMO_TRXC_CMD_SETRXGAIN "SETRXGAIN" +#define OSMO_TRXC_CMD_SETMAXDLY "SETMAXDLY" +#define OSMO_TRXC_CMD_SETMAXDLYNB "SETMAXDLYNB" +#define OSMO_TRXC_CMD_SETFORMAT "SETFORMAT" +#define OSMO_TRXC_CMD_HANDOVER "HANDOVER" +#define OSMO_TRXC_CMD_NOHANDOVER "NOHANDOVER" +#define OSMO_TRXC_CMD_RFMUTE "RFMUTE" +#define OSMO_TRXC_CMD_ERR "ERR" /*!< verb of a reject response */ + +/* Clock socket: "IND CLOCK <fn>" */ +int osmo_trxc_clock_ind_parse(uint32_t *fn, const char *buf, size_t len); +int osmo_trxc_clock_ind_build(char *buf, size_t buf_size, uint32_t fn); diff --git a/libosmo-trx/include/osmocom/trx/trxd.h b/libosmo-trx/include/osmocom/trx/trxd.h new file mode 100644 index 0000000..b415c47 --- /dev/null +++ b/libosmo-trx/include/osmocom/trx/trxd.h @@ -0,0 +1,128 @@ +/*! \file osmocom/trx/trxd.h + * TRXD (burst data) protocol: I/O-free PDU codec. */ +#pragma once + +#include <stdint.h> +#include <stddef.h> + +#include <osmocom/core/bits.h> +#include <osmocom/core/utils.h> + +struct msgb; + +/*! The highest TRXD PDU version supported by this library */ +#define OSMO_TRXD_PDU_VER_MAX 2 + +/*! GMSK modulated burst: 1 bit per symbol */ +#define OSMO_TRXD_BURST_LEN_GMSK 148 +/*! 8-PSK modulated burst: 3 bits per symbol */ +#define OSMO_TRXD_BURST_LEN_8PSK 444 +/*! Maximum burst length in (soft-)bits */ +#define OSMO_TRXD_BURST_LEN_MAX OSMO_TRXD_BURST_LEN_8PSK + +/*! Maximum single-PDU header size, in bytes: the TRXDv2 header (largest of + * all versions, either direction) plus the TDMA FN present on the first + * PDU of a datagram. */ +#define OSMO_TRXD_HDR_LEN_MAX 12 +/*! Recommended Rx/Tx socket buffer size, in bytes, for a single + * (non-batched) TRXD datagram: largest possible header plus the largest + * possible burst payload. */ +#define OSMO_TRXD_MSG_BUF_SIZE (OSMO_TRXD_HDR_LEN_MAX + OSMO_TRXD_BURST_LEN_MAX) + +/*! Modulation types (defined by the TRXDv2 MTS encoding, 3GPP TS 45.002) */ +enum osmo_trxd_mod_type { + OSMO_TRXD_MOD_T_GMSK, + OSMO_TRXD_MOD_T_8PSK, + OSMO_TRXD_MOD_T_AQPSK, + /* room for 16QAM/32QAM */ +}; + +/* Presence/meta flags for osmo_trxd_burst_{ind,req} */ +#define OSMO_TRXD_F_NOPE_IND (1 << 0) /*!< no burst detected / idle indication */ +#define OSMO_TRXD_F_MOD_TYPE (1 << 1) /*!< 'mod' is valid */ +#define OSMO_TRXD_F_TS_INFO (1 << 2) /*!< 'tsc_set'/'tsc' are valid */ +#define OSMO_TRXD_F_CI_CB (1 << 3) /*!< 'ci_cb' is valid */ +#define OSMO_TRXD_F_TRX_NUM (1 << 4) /*!< 'trx_num' is valid */ +#define OSMO_TRXD_F_BATCH_IND (1 << 5) /*!< more PDUs follow in this datagram */ +#define OSMO_TRXD_F_SHADOW_IND (1 << 6) /*!< burst received on a shadow (VAMOS) channel */ +#define OSMO_TRXD_F_ACCESS_BURST (1 << 7) /*!< an Access Burst (RACH) */ + +/*! TRX -> L1: received burst indication (soft bits + measurements). + * + * ABI stability rule: mandatory fields come first, optional (flag-gated) + * fields after them; fields introduced by future TRXD revisions shall be + * appended at the end of the struct. */ +struct osmo_trxd_burst_ind { + uint32_t flags; /*!< see OSMO_TRXD_F_* */ + + /* Mandatory fields (all TRXD versions) */ + uint32_t fn; /*!< TDMA frame number */ + uint8_t tn; /*!< TDMA timeslot number */ + int16_t toa256; /*!< Timing of Arrival in units of 1/256 of symbol */ + int8_t rssi; /*!< Received Signal Strength Indication (dBm) */ + sbit_t burst[OSMO_TRXD_BURST_LEN_MAX]; /*!< soft-bits buffer */ + size_t burst_len; /*!< number of soft-bits (0 for NOPE.ind) */ + + /* Optional fields (presence indicated by flags) */ + enum osmo_trxd_mod_type mod; /*!< Modulation type */ + uint8_t tsc_set; /*!< Training Sequence Set */ + uint8_t tsc; /*!< Training Sequence Code */ + int16_t ci_cb; /*!< Carrier-to-Interference ratio (centiBels) */ + uint8_t trx_num; /*!< TRX (RF channel) number */ +}; + +/*! L1 -> TRX: burst transmit request (hard bits). + * Same ABI stability rule as for osmo_trxd_burst_ind. */ +struct osmo_trxd_burst_req { + uint32_t flags; /*!< see OSMO_TRXD_F_* */ + + /* Mandatory fields (all TRXD versions) */ + uint32_t fn; /*!< TDMA frame number */ + uint8_t tn; /*!< TDMA timeslot number */ + uint8_t att; /*!< Tx power attenuation (BTS side) */ + ubit_t burst[OSMO_TRXD_BURST_LEN_MAX]; /*!< hard-bits buffer */ + size_t burst_len; /*!< number of hard-bits */ + + /* Optional fields (presence indicated by flags) */ + enum osmo_trxd_mod_type mod; /*!< Modulation type */ + uint8_t tsc_set; /*!< Training Sequence Set */ + uint8_t tsc; /*!< Training Sequence Code */ + int8_t scpir; /*!< SCPIR (AQPSK only, TRXDv2) */ + uint8_t trx_num; /*!< TRX (RF channel) number */ +}; + +/*! Parser state, keeping cross-PDU context for TRXDv2 batches. + * Initialize with osmo_trxd_parse_state_init() before parsing each datagram. */ +struct osmo_trxd_parse_state { + uint8_t pdu_ver; /*!< PDU version (parsed from the first PDU) */ + unsigned int num_pdus; /*!< number of PDUs parsed so far */ + uint32_t fn; /*!< TDMA fn of the first PDU (v2 batches omit it later) */ +}; + +void osmo_trxd_parse_state_init(struct osmo_trxd_parse_state *st); + +int osmo_trxd_burst_ind_parse(struct osmo_trxd_parse_state *st, + struct osmo_trxd_burst_ind *bi, + const uint8_t *buf, size_t buf_len); +int osmo_trxd_burst_req_parse(struct osmo_trxd_parse_state *st, + struct osmo_trxd_burst_req *br, + const uint8_t *buf, size_t buf_len); + +int osmo_trxd_burst_ind_build(struct msgb *msg, uint8_t pdu_ver, + const struct osmo_trxd_burst_ind *bi); +int osmo_trxd_burst_req_build(struct msgb *msg, uint8_t pdu_ver, + const struct osmo_trxd_burst_req *br); +void osmo_trxd_build_fin(struct msgb *msg, uint8_t pdu_ver); + +const char *osmo_trxd_burst_ind_name(const struct osmo_trxd_burst_ind *bi); +char *osmo_trxd_burst_ind_name_buf(char *buf, size_t buf_len, + const struct osmo_trxd_burst_ind *bi); +const char *osmo_trxd_burst_req_name(const struct osmo_trxd_burst_req *br); +char *osmo_trxd_burst_req_name_buf(char *buf, size_t buf_len, + const struct osmo_trxd_burst_req *br); + +extern const struct value_string osmo_trxd_mod_type_names[]; +static inline const char *osmo_trxd_mod_type_name(enum osmo_trxd_mod_type mod) +{ + return get_value_string(osmo_trxd_mod_type_names, mod); +} diff --git a/libosmo-trx/libosmo-trx.pc.in b/libosmo-trx/libosmo-trx.pc.in new file mode 100644 index 0000000..7e0de46 --- /dev/null +++ b/libosmo-trx/libosmo-trx.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: Osmocom TRX protocol library +Description: C Utility Library for the TRXC/TRXD (OpenBTS-style TRX) protocol +Version: @VERSION@ +Requires: libosmocore libosmogsm +Libs: -L${libdir} -losmo-trx +Cflags: -I${includedir}/ diff --git a/libosmo-trx/src/Makefile.am b/libosmo-trx/src/Makefile.am new file mode 100644 index 0000000..cec05ec --- /dev/null +++ b/libosmo-trx/src/Makefile.am @@ -0,0 +1,33 @@ +# This is _NOT_ the library release version, it's an API version. +# Please read chapter "Library interface versions" of the libtool +# documentation before making any modification +LIBVERSION = 0:0:0 + +AM_CPPFLAGS = \ + -I$(top_srcdir)/libosmo-trx/include \ + -I$(top_builddir)/libosmo-trx/include \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOGSM_CFLAGS) \ + $(NULL) + +lib_LTLIBRARIES = libosmo-trx.la + +libosmo_trx_la_SOURCES = \ + trxc.c \ + trxd.c \ + $(NULL) + +libosmo_trx_la_LDFLAGS = \ + -version-info $(LIBVERSION) \ + -no-undefined \ + -export-symbols-regex '^osmo_' \ + $(NULL) + +libosmo_trx_la_LIBADD = \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOGSM_LIBS) \ + $(NULL) diff --git a/libosmo-trx/src/trxc.c b/libosmo-trx/src/trxc.c new file mode 100644 index 0000000..70d8c16 --- /dev/null +++ b/libosmo-trx/src/trxc.c @@ -0,0 +1,212 @@ +/*! \file src/trxc.c + * TRXC (control) protocol and clock indications: I/O-free message codec. + * Based on the TRXC implementation in osmo-bts-trx and osmo-trx. */ + +/* + * (C) 2013 Andreas Eversberg jolly@eversberg.eu + * (C) 2016-2017 Harald Welte laforge@gnumonks.org + * (C) 2019 Vadim Yanitskiy axilirator@gmail.com + * (C) 2021-2026 by sysmocom - s.f.m.c. GmbH info@sysmocom.de + * + * All Rights Reserved + * + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <stdarg.h> +#include <stdlib.h> + +#include <osmocom/core/utils.h> +#include <osmocom/gsm/gsm0502.h> + +#include <osmocom/trx/trxc.h> + +static const struct value_string trxc_msg_type_names[] = { + { OSMO_TRXC_MT_CMD, "CMD" }, + { OSMO_TRXC_MT_RSP, "RSP" }, + { OSMO_TRXC_MT_IND, "IND" }, + { 0, NULL } +}; + +/*! Parse a TRXC message ("CMD <verb> [<params>]", "RSP <verb> <status> + * [<params>]" or "IND <verb> <params>") from a zero-terminated buffer. + * \param[out] msg parsed message + * \param[in] buf message buffer (not necessarily zero-terminated) + * \param[in] len length of the message in the buffer + * \returns 0 on success; negative on error */ +int osmo_trxc_msg_parse(struct osmo_trxc_msg *msg, const char *buf, size_t len) +{ + char tmp[OSMO_TRXC_MSG_BUF_SIZE]; + const char *p, *verb; + size_t vlen; + + memset(msg, 0, sizeof(*msg)); + + if (len >= sizeof(tmp)) + return -EMSGSIZE; + /* work on a zero-terminated copy */ + memcpy(tmp, buf, len); + tmp[len] = '\0'; + + if (strncmp(tmp, "CMD ", 4) == 0) + msg->type = OSMO_TRXC_MT_CMD; + else if (strncmp(tmp, "RSP ", 4) == 0) + msg->type = OSMO_TRXC_MT_RSP; + else if (strncmp(tmp, "IND ", 4) == 0) + msg->type = OSMO_TRXC_MT_IND; + else + return -EINVAL; + + /* the verb ends at the next space (or end of string) */ + verb = tmp + 4; + p = strchr(verb, ' '); + vlen = (p != NULL) ? (size_t)(p - verb) : strlen(verb); + if (vlen == 0 || vlen >= sizeof(msg->cmd)) + return -EINVAL; + memcpy(msg->cmd, verb, vlen); + msg->cmd[vlen] = '\0'; + + if (p == NULL) { /* no parameters at all */ + if (msg->type == OSMO_TRXC_MT_RSP) + return -EINVAL; /* RSP requires a status code */ + return 0; + } + p++; + + /* RSP carries a status code before the parameters */ + if (msg->type == OSMO_TRXC_MT_RSP) { + if (sscanf(p, "%d", &msg->status) != 1) + return -EINVAL; + p = strchr(p, ' '); + if (p == NULL) /* no parameters after the status */ + return 0; + p++; + } + + if (strlen(p) >= sizeof(msg->params)) + return -EMSGSIZE; + OSMO_STRLCPY_ARRAY(msg->params, p); + + return 0; +} + +/*! Serialize a TRXC message into the given buffer (zero-terminated). + * \returns length of the message (excl. '\0') on success; negative on error */ +int osmo_trxc_msg_build(char *buf, size_t buf_size, const struct osmo_trxc_msg *msg) +{ + int rc; + + switch (msg->type) { + case OSMO_TRXC_MT_CMD: + case OSMO_TRXC_MT_IND: + rc = snprintf(buf, buf_size, "%s %s%s%s", + get_value_string(trxc_msg_type_names, msg->type), + msg->cmd, + msg->params[0] != '\0' ? " " : "", msg->params); + break; + case OSMO_TRXC_MT_RSP: + rc = snprintf(buf, buf_size, "RSP %s %d%s%s", + msg->cmd, msg->status, + msg->params[0] != '\0' ? " " : "", msg->params); + break; + default: + return -EINVAL; + } + + if (rc < 0 || (size_t)rc >= buf_size) + return -EMSGSIZE; + return rc; +} + +/*! Parse the parameters string of a TRXC message, sscanf() style. + * \returns number of successfully matched items (see sscanf) */ +int osmo_trxc_msg_params_scan(const struct osmo_trxc_msg *msg, const char *fmt, ...) +{ + va_list ap; + int rc; + + va_start(ap, fmt); + rc = vsscanf(msg->params, fmt, ap); + va_end(ap); + + return rc; +} + +/*! Compose a human-readable representation of the given message + * (for logging), store into a thread-local static buffer. + * \param[in] msg parsed TRXC message to get a human-readable representation of + * \returns pointer to a thread-local static buffer; NULL on error */ +const char *osmo_trxc_msg_name(const struct osmo_trxc_msg *msg) +{ + static __thread char buf[OSMO_TRXC_MSG_BUF_SIZE]; + return osmo_trxc_msg_name_buf(&buf[0], sizeof(buf), msg); +} + +/*! Compose a human-readable representation of the given message + * (for logging), store into the given buffer. + * \param[out] buf output buffer to store the result + * \param[in] buf_size size of the output buffer + * \param[in] msg parsed TRXC message to get a human-readable representation of + * \returns pointer to the given buffer; NULL on error */ +char *osmo_trxc_msg_name_buf(char *buf, size_t buf_size, + const struct osmo_trxc_msg *msg) +{ + return osmo_trxc_msg_build(buf, buf_size, msg) < 0 ? NULL : buf; +} + +/*! Parse a clock indication ("IND CLOCK <fn>"). + * \param[out] fn TDMA frame number (validated to be < GSM_TDMA_HYPERFRAME) + * \param[in] buf message buffer (not necessarily zero-terminated) + * \param[in] len length of the message in the buffer + * \returns 0 on success; negative on error */ +int osmo_trxc_clock_ind_parse(uint32_t *fn, const char *buf, size_t len) +{ + struct osmo_trxc_msg msg; + int rc; + + rc = osmo_trxc_msg_parse(&msg, buf, len); + if (rc < 0) + return rc; + if (msg.type != OSMO_TRXC_MT_IND || strcmp(msg.cmd, "CLOCK") != 0) + return -EINVAL; + if (osmo_trxc_msg_params_scan(&msg, "%u", fn) != 1) + return -EINVAL; + if (*fn >= GSM_TDMA_HYPERFRAME) + return -ERANGE; + + return 0; +} + +/*! Serialize a clock indication ("IND CLOCK <fn>") into the given buffer. + * \param[out] buf message buffer to store the result + * \param[in] buf_size size of the message buffer + * \param[in] fn TDMA frame number (validated to be < GSM_TDMA_HYPERFRAME) + * \returns length of the message (excl. '\0') on success; negative on error */ +int osmo_trxc_clock_ind_build(char *buf, size_t buf_size, uint32_t fn) +{ + int rc; + + if (fn >= GSM_TDMA_HYPERFRAME) + return -ERANGE; + + rc = snprintf(buf, buf_size, "IND CLOCK %u", fn); + if (rc < 0 || (size_t)rc >= buf_size) + return -EMSGSIZE; + return rc; +} diff --git a/libosmo-trx/src/trxd.c b/libosmo-trx/src/trxd.c new file mode 100644 index 0000000..9b8cf39 --- /dev/null +++ b/libosmo-trx/src/trxd.c @@ -0,0 +1,724 @@ +/*! \file src/trxd.c + * TRXD (burst data) protocol: I/O-free PDU codec. + * Based on the TRXD implementation in osmo-bts-trx and osmo-trx. */ + +/* + * (C) 2013 Andreas Eversberg jolly@eversberg.eu + * (C) 2016-2017 Harald Welte laforge@gnumonks.org + * (C) 2019 Vadim Yanitskiy axilirator@gmail.com + * (C) 2021-2026 by sysmocom - s.f.m.c. GmbH info@sysmocom.de + * + * All Rights Reserved + * + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +#include <errno.h> +#include <string.h> +#include <stdint.h> + +#include <osmocom/core/bits.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/utils.h> +#include <osmocom/gsm/gsm0502.h> + +#include <osmocom/trx/trxd.h> + +/* Uplink TRXDv0 header length: TDMA TN + FN + RSSI + ToA256 */ +#define TRXD_IND_V0HDR_LEN (1 + 4 + 1 + 2) +/* Uplink TRXDv1 header length: additional MTS + C/I */ +#define TRXD_IND_V1HDR_LEN (TRXD_IND_V0HDR_LEN + 1 + 2) +/* Uplink TRXDv2 header length: TDMA TN + TRXN + MTS + RSSI + ToA256 + C/I */ +#define TRXD_IND_V2HDR_LEN (1 + 1 + 1 + 1 + 2 + 2) + +/* Downlink TRXDv0/v1 header length: TDMA TN + FN + Att */ +#define TRXD_REQ_V01HDR_LEN (1 + 4 + 1) +/* Downlink TRXDv2 header length: TDMA TN + TRXN + MTS + Att + SCPIR + spare3 */ +#define TRXD_REQ_V2HDR_LEN (1 + 1 + 1 + 1 + 1 + 3) + +const struct value_string osmo_trxd_mod_type_names[] = { + { OSMO_TRXD_MOD_T_GMSK, "GMSK" }, + { OSMO_TRXD_MOD_T_8PSK, "8-PSK" }, + { OSMO_TRXD_MOD_T_AQPSK, "AQPSK" }, + { 0, NULL } +}; + +/*! Initialize a parser state; call before parsing each datagram. + * \param[out] st parser state to be initialized */ +void osmo_trxd_parse_state_init(struct osmo_trxd_parse_state *st) +{ + memset(st, 0, sizeof(*st)); +} + +/* Expected burst length (in bits on the wire) for a given modulation */ +static int burst_len_by_mod(enum osmo_trxd_mod_type mod) +{ + switch (mod) { + case OSMO_TRXD_MOD_T_GMSK: + case OSMO_TRXD_MOD_T_AQPSK: + return OSMO_TRXD_BURST_LEN_GMSK; + case OSMO_TRXD_MOD_T_8PSK: + return OSMO_TRXD_BURST_LEN_8PSK; + default: + return -ENOTSUP; + } +} + +static int mts_parse(uint32_t *flags, enum osmo_trxd_mod_type *mod, + uint8_t *tsc_set, uint8_t *tsc, uint8_t mts) +{ + if (mts & (1 << 7)) { + *flags |= OSMO_TRXD_F_NOPE_IND; + return 0; + } + + /* | 7 6 5 4 3 2 1 0 | Bitmask / description + * | . 0 0 X X . . . | GMSK, 4 TSC sets (0..3) + * | . 0 1 0 X . . . | 8-PSK, 2 TSC sets (0..1) + * | . 0 1 1 0 . . . | GMSK, Access Burst + * | . 1 1 X X . . . | AQPSK, 2 TSC sets + SCPIR */ + if ((mts >> 5) == 0x00) { + *mod = OSMO_TRXD_MOD_T_GMSK; + *tsc_set = (mts >> 3) & 0x03; + } else if ((mts >> 4) == 0x02) { + *mod = OSMO_TRXD_MOD_T_8PSK; + *tsc_set = (mts >> 3) & 0x01; + } else if ((mts >> 3) == 0x06) { + *flags |= OSMO_TRXD_F_ACCESS_BURST; + *mod = OSMO_TRXD_MOD_T_GMSK; + *tsc_set = 0; + } else if ((mts >> 5) == 0x03) { + *mod = OSMO_TRXD_MOD_T_AQPSK; + *tsc_set = (mts >> 3) & 0x01; + } else { + return -ENOTSUP; + } + + *tsc = mts & 0x07; + *flags |= (OSMO_TRXD_F_MOD_TYPE | OSMO_TRXD_F_TS_INFO); + + return 0; +} + +int trxd_mts_parse_ind(struct osmo_trxd_burst_ind *bi, uint8_t mts) +{ + return mts_parse(&bi->flags, &bi->mod, &bi->tsc_set, &bi->tsc, mts); +} + +int trxd_mts_parse_req(struct osmo_trxd_burst_req *br, uint8_t mts) +{ + return mts_parse(&br->flags, &br->mod, &br->tsc_set, &br->tsc, mts); +} + +static int mts_build(uint8_t *mts, uint32_t flags, + enum osmo_trxd_mod_type mod, + uint8_t tsc_set, uint8_t tsc) +{ + if (flags & OSMO_TRXD_F_NOPE_IND) { + *mts = (1 << 7); + return 0; + } + + if (flags & OSMO_TRXD_F_ACCESS_BURST) { + *mts = (0x06 << 3) | (tsc & 0x07); + return 0; + } + + switch (mod) { + case OSMO_TRXD_MOD_T_GMSK: + *mts = ((tsc_set & 0x03) << 3); + break; + case OSMO_TRXD_MOD_T_8PSK: + *mts = (0x02 << 4) | ((tsc_set & 0x01) << 3); + break; + case OSMO_TRXD_MOD_T_AQPSK: + *mts = (0x03 << 5) | ((tsc_set & 0x01) << 3); + break; + default: + return -ENOTSUP; + } + + *mts |= (tsc & 0x07); + return 0; +} + +int trxd_mts_build_ind(uint8_t *mts, const struct osmo_trxd_burst_ind *bi) +{ + return mts_build(mts, bi->flags, bi->mod, bi->tsc_set, bi->tsc); +} + +int trxd_mts_build_req(uint8_t *mts, const struct osmo_trxd_burst_req *br) +{ + return mts_build(mts, br->flags, br->mod, br->tsc_set, br->tsc); +} + +/*********************************************************************** + * burst indication (TRX -> L1): soft bits + measurements + ***********************************************************************/ + +/* Convert unsigned soft-bits [254..0] to soft-bits [-127..127] */ +static void soft_bits_parse(sbit_t *out, const uint8_t *in, size_t len) +{ + size_t i; + + for (i = 0; i < len; i++) { + if (in[i] == 255) + out[i] = -127; + else + out[i] = 127 - in[i]; + } +} + +/* Convert soft-bits [-127..127] to unsigned soft-bits [254..0] */ +static void soft_bits_build(uint8_t *out, const sbit_t *in, size_t len) +{ + size_t i; + + for (i = 0; i < len; i++) + out[i] = 127 - in[i]; +} + +/* Common header part shared by TRXDv0 and TRXDv1 */ +static void trxd_burst_ind_parse_hdr_v0(struct osmo_trxd_burst_ind *bi, + const uint8_t *buf) +{ + bi->tn = buf[0] & 0x07; + bi->fn = osmo_load32be(buf + 1); + bi->rssi = -(int8_t)buf[5]; + bi->toa256 = (int16_t)osmo_load16be(buf + 6); +} + +/* TRXDv0: modulation is guessed by the burst length; a legacy transceiver + * may append two garbage bytes, which we tolerate (and consume). */ +static int trxd_burst_ind_parse_v0(struct osmo_trxd_burst_ind *bi, + const uint8_t *buf, size_t buf_len) +{ + size_t burst_len = buf_len - TRXD_IND_V0HDR_LEN; + + trxd_burst_ind_parse_hdr_v0(bi, buf); + + switch (burst_len) { + case OSMO_TRXD_BURST_LEN_GMSK: + case OSMO_TRXD_BURST_LEN_GMSK + 2: + bi->mod = OSMO_TRXD_MOD_T_GMSK; + bi->burst_len = OSMO_TRXD_BURST_LEN_GMSK; + break; + case OSMO_TRXD_BURST_LEN_8PSK: + case OSMO_TRXD_BURST_LEN_8PSK + 2: + bi->mod = OSMO_TRXD_MOD_T_8PSK; + bi->burst_len = OSMO_TRXD_BURST_LEN_8PSK; + break; + default: + return -EINVAL; + } + + bi->flags |= OSMO_TRXD_F_MOD_TYPE; + soft_bits_parse(&bi->burst[0], buf + TRXD_IND_V0HDR_LEN, bi->burst_len); + + /* consume everything, including the optional legacy padding */ + return buf_len; +} + +static int trxd_burst_ind_parse_v1(struct osmo_trxd_burst_ind *bi, + const uint8_t *buf, size_t buf_len) +{ + int rc; + + trxd_burst_ind_parse_hdr_v0(bi, buf); + + /* MTS (Modulation and Training Sequence) */ + rc = trxd_mts_parse_ind(bi, buf[TRXD_IND_V0HDR_LEN + 0]); + if (rc < 0) + return rc; + + /* C/I: Carrier-to-Interference ratio (in centiBels) */ + bi->ci_cb = (int16_t)osmo_load16be(buf + TRXD_IND_V0HDR_LEN + 1); + bi->flags |= OSMO_TRXD_F_CI_CB; + + return TRXD_IND_V1HDR_LEN; +} + +static int trxd_burst_ind_parse_v2(struct osmo_trxd_parse_state *st, + struct osmo_trxd_burst_ind *bi, + const uint8_t *buf, size_t buf_len) +{ + int rc; + + /* TDMA timeslot number (other bits are RFU) */ + bi->tn = buf[0] & 0x07; + + if (buf[1] & (1 << 7)) /* BATCH.ind */ + bi->flags |= OSMO_TRXD_F_BATCH_IND; + if (buf[1] & (1 << 6)) /* VAMOS.ind */ + bi->flags |= OSMO_TRXD_F_SHADOW_IND; + + /* TRX (RF channel) number */ + bi->trx_num = buf[1] & 0x3f; + bi->flags |= OSMO_TRXD_F_TRX_NUM; + + /* MTS (Modulation and Training Sequence) */ + rc = trxd_mts_parse_ind(bi, buf[2]); + if (rc < 0) + return rc; + + bi->rssi = -(int8_t)buf[3]; + bi->toa256 = (int16_t)osmo_load16be(buf + 4); + bi->ci_cb = (int16_t)osmo_load16be(buf + 6); + bi->flags |= OSMO_TRXD_F_CI_CB; + + /* TDMA frame number is absent in batched PDUs */ + if (st->num_pdus == 0) { + if (buf_len < TRXD_IND_V2HDR_LEN + sizeof(uint32_t)) + return -EINVAL; + bi->fn = osmo_load32be(buf + TRXD_IND_V2HDR_LEN); + st->fn = bi->fn; + return TRXD_IND_V2HDR_LEN + sizeof(uint32_t); + } + + bi->fn = st->fn; + return TRXD_IND_V2HDR_LEN; +} + +/*! Parse one burst indication (TRX -> L1) PDU from the given buffer. + * + * The PDU version is parsed from the first PDU of a datagram and kept + * in \a st. Starting from TRXDv2, a datagram may batch multiple PDUs: + * call this function repeatedly on the remaining buffer as long as the + * parsed PDU has OSMO_TRXD_F_BATCH_IND set in bi->flags. + * + * \param[inout] st parser state (see osmo_trxd_parse_state_init()) + * \param[out] bi parsed burst indication + * \param[in] buf buffer positioned at the beginning of a PDU + * \param[in] buf_len remaining length of the buffer + * \returns number of consumed bytes on success; negative on error */ +int osmo_trxd_burst_ind_parse(struct osmo_trxd_parse_state *st, + struct osmo_trxd_burst_ind *bi, + const uint8_t *buf, size_t buf_len) +{ + int hdr_len; + int burst_len; + + if (buf_len == 0) + return -EINVAL; + + /* PDU version is parsed from the first PDU of a datagram */ + if (st->num_pdus == 0) { + st->pdu_ver = buf[0] >> 4; + if (st->pdu_ver > OSMO_TRXD_PDU_VER_MAX) + return -ENOTSUP; + } + + memset(bi, 0, sizeof(*bi)); + + switch (st->pdu_ver) { + case 0: + if (buf_len < TRXD_IND_V0HDR_LEN) + return -EINVAL; + hdr_len = trxd_burst_ind_parse_v0(bi, buf, buf_len); + break; + case 1: + if (buf_len < TRXD_IND_V1HDR_LEN) + return -EINVAL; + hdr_len = trxd_burst_ind_parse_v1(bi, buf, buf_len); + break; + case 2: + if (buf_len < TRXD_IND_V2HDR_LEN) + return -EINVAL; + hdr_len = trxd_burst_ind_parse_v2(st, bi, buf, buf_len); + break; + default: + return -ENOTSUP; + } + + if (hdr_len < 0) + return hdr_len; + + if (bi->fn >= GSM_TDMA_HYPERFRAME) + return -EINVAL; + + st->num_pdus++; + + /* TRXDv0 consumes the whole datagram, incl. the burst bits */ + if (st->pdu_ver == 0) + return hdr_len; + + /* NOPE.ind contains no burst */ + if (bi->flags & OSMO_TRXD_F_NOPE_IND) { + bi->burst_len = 0; + return hdr_len; + } + + burst_len = burst_len_by_mod(bi->mod); + if (burst_len < 0) + return burst_len; + if (buf_len < (size_t)(hdr_len + burst_len)) + return -EINVAL; + + bi->burst_len = burst_len; + soft_bits_parse(&bi->burst[0], buf + hdr_len, bi->burst_len); + + return hdr_len + burst_len; +} + +/*! Append one encoded burst indication (TRX -> L1) PDU to the given msgb. + * + * For TRXDv2, PDU batching works by calling this function repeatedly on + * the same msgb; call osmo_trxd_build_fin() once the datagram is complete + * (it clears the BATCH.ind bit of the last PDU). + * + * \param[inout] msg destination message buffer + * \param[in] pdu_ver TRXD PDU version to encode + * \param[in] bi burst indication to be encoded + * \returns 0 on success; negative on error. Note that TRXDv0 cannot + * carry NOPE.ind PDUs: -ENOTSUP is returned and the caller + * shall skip (not send) them. */ +int osmo_trxd_burst_ind_build(struct msgb *msg, uint8_t pdu_ver, + const struct osmo_trxd_burst_ind *bi) +{ + bool first = (msgb_length(msg) == 0); + uint8_t *buf; + int rc; + + switch (pdu_ver) { + case 0: + /* v0 doesn't support NOPE.ind, the caller shall skip it */ + if (bi->flags & OSMO_TRXD_F_NOPE_IND) + return -ENOTSUP; + buf = msgb_put(msg, TRXD_IND_V0HDR_LEN); + buf[0] = ((pdu_ver & 0x0f) << 4) | (bi->tn & 0x07); + osmo_store32be(bi->fn, buf + 1); + buf[5] = (uint8_t)(-bi->rssi); + osmo_store16be(bi->toa256, buf + 6); + break; + case 1: + buf = msgb_put(msg, TRXD_IND_V1HDR_LEN); + buf[0] = ((pdu_ver & 0x0f) << 4) | (bi->tn & 0x07); + osmo_store32be(bi->fn, buf + 1); + buf[5] = (uint8_t)(-bi->rssi); + osmo_store16be(bi->toa256, buf + 6); + rc = trxd_mts_build_ind(&buf[8], bi); + if (rc < 0) + return rc; + osmo_store16be(bi->ci_cb, buf + 9); + break; + case 2: + /* l2h points to the last encoded PDU (for osmo_trxd_build_fin) */ + msg->l2h = msg->tail; + buf = msgb_put(msg, TRXD_IND_V2HDR_LEN); + buf[0] = bi->tn & 0x07; + /* BATCH.ind; unset in the last PDU by osmo_trxd_build_fin() */ + buf[1] = (bi->trx_num & 0x3f) | (1 << 7); + if (bi->flags & OSMO_TRXD_F_SHADOW_IND) + buf[1] |= (1 << 6); + rc = trxd_mts_build_ind(&buf[2], bi); + if (rc < 0) + return rc; + buf[3] = (uint8_t)(-bi->rssi); + osmo_store16be(bi->toa256, buf + 4); + osmo_store16be(bi->ci_cb, buf + 6); + /* Some fields are not present in batched PDUs */ + if (first) { + buf[0] |= (pdu_ver & 0x0f) << 4; + msgb_put_u32(msg, bi->fn); + } + break; + default: + return -ENOTSUP; + } + + if (~bi->flags & OSMO_TRXD_F_NOPE_IND) { + soft_bits_build(msgb_put(msg, bi->burst_len), + &bi->burst[0], bi->burst_len); + } + + return 0; +} + +/*********************************************************************** + * burst transmit request (L1 -> TRX): hard bits + ***********************************************************************/ + +static int trxd_burst_req_parse_v01(struct osmo_trxd_burst_req *br, + const uint8_t *buf, size_t buf_len) +{ + size_t burst_len = buf_len - TRXD_REQ_V01HDR_LEN; + + br->tn = buf[0] & 0x07; + br->fn = osmo_load32be(&buf[1]); + br->att = buf[5]; + + switch (burst_len) { + case OSMO_TRXD_BURST_LEN_8PSK: + br->mod = OSMO_TRXD_MOD_T_8PSK; + break; + case OSMO_TRXD_BURST_LEN_GMSK: + br->mod = OSMO_TRXD_MOD_T_GMSK; + break; + default: + return -EINVAL; + } + + br->flags |= OSMO_TRXD_F_MOD_TYPE; + br->burst_len = burst_len; + memcpy(&br->burst[0], buf + TRXD_REQ_V01HDR_LEN, burst_len); + + return buf_len; +} + +static int trxd_burst_req_parse_v2(struct osmo_trxd_parse_state *st, + struct osmo_trxd_burst_req *br, + const uint8_t *buf, size_t buf_len) +{ + size_t hdr_len = TRXD_REQ_V2HDR_LEN; + int burst_len; + int rc; + + br->tn = buf[0] & 0x07; + + if (buf[1] & (1 << 7)) /* BATCH.ind */ + br->flags |= OSMO_TRXD_F_BATCH_IND; + + br->trx_num = buf[1] & 0x3f; + br->flags |= OSMO_TRXD_F_TRX_NUM; + + rc = trxd_mts_parse_req(br, buf[2]); + if (rc < 0) + return rc; + + br->att = buf[3]; + br->scpir = (int8_t)buf[4]; + /* buf[5..7] is spare */ + + /* TDMA frame number is absent in batched PDUs */ + if (st->num_pdus == 0) { + if (buf_len < hdr_len + sizeof(uint32_t)) + return -EINVAL; + br->fn = osmo_load32be(buf + hdr_len); + st->fn = br->fn; + hdr_len += sizeof(uint32_t); + } else { + br->fn = st->fn; + } + + burst_len = burst_len_by_mod(br->mod); + if (burst_len < 0) + return burst_len; + if (buf_len < hdr_len + burst_len) + return -EINVAL; + + br->burst_len = burst_len; + memcpy(&br->burst[0], buf + hdr_len, burst_len); + + return hdr_len + burst_len; +} + +/*! Parse one burst transmit request (L1 -> TRX) PDU from the given buffer. + * + * The PDU version is parsed from the first PDU of a datagram and kept + * in \a st. Starting from TRXDv2, a datagram may batch multiple PDUs: + * call this function repeatedly on the remaining buffer as long as the + * parsed PDU has OSMO_TRXD_F_BATCH_IND set in br->flags. + * + * \param[inout] st parser state (see osmo_trxd_parse_state_init()) + * \param[out] br parsed burst transmit request + * \param[in] buf buffer positioned at the beginning of a PDU + * \param[in] buf_len remaining length of the buffer + * \returns number of consumed bytes on success; negative on error */ +int osmo_trxd_burst_req_parse(struct osmo_trxd_parse_state *st, + struct osmo_trxd_burst_req *br, + const uint8_t *buf, size_t buf_len) +{ + int pdu_len; + + if (buf_len == 0) + return -EINVAL; + + if (st->num_pdus == 0) { + st->pdu_ver = buf[0] >> 4; + if (st->pdu_ver > OSMO_TRXD_PDU_VER_MAX) + return -ENOTSUP; + } + + memset(br, 0, sizeof(*br)); + + switch (st->pdu_ver) { + case 0: + case 1: + if (buf_len < TRXD_REQ_V01HDR_LEN) + return -EINVAL; + pdu_len = trxd_burst_req_parse_v01(br, buf, buf_len); + break; + case 2: + if (buf_len < TRXD_REQ_V2HDR_LEN) + return -EINVAL; + pdu_len = trxd_burst_req_parse_v2(st, br, buf, buf_len); + break; + default: + return -ENOTSUP; + } + + if (pdu_len < 0) + return pdu_len; + + if (br->fn >= GSM_TDMA_HYPERFRAME) + return -EINVAL; + + st->num_pdus++; + return pdu_len; +} + +/*! Append one encoded burst transmit request (L1 -> TRX) PDU to the given msgb. + * + * For TRXDv2, PDU batching works by calling this function repeatedly on + * the same msgb; call osmo_trxd_build_fin() once the datagram is complete + * (it clears the BATCH.ind bit of the last PDU). + * + * \param[inout] msg destination message buffer + * \param[in] pdu_ver TRXD PDU version to encode + * \param[in] br burst transmit request to be encoded + * \returns 0 on success; negative on error */ +int osmo_trxd_burst_req_build(struct msgb *msg, uint8_t pdu_ver, + const struct osmo_trxd_burst_req *br) +{ + bool first = (msgb_length(msg) == 0); + uint8_t *buf; + int rc; + + switch (pdu_ver) { + /* Both versions have the same PDU format */ + case 0: + case 1: + buf = msgb_put(msg, TRXD_REQ_V01HDR_LEN); + buf[0] = ((pdu_ver & 0x0f) << 4) | (br->tn & 0x07); + osmo_store32be(br->fn, buf + 1); + buf[5] = br->att; + break; + case 2: + /* l2h points to the last encoded PDU (for osmo_trxd_build_fin) */ + msg->l2h = msg->tail; + buf = msgb_put(msg, TRXD_REQ_V2HDR_LEN); + buf[0] = br->tn & 0x07; + /* BATCH.ind; unset in the last PDU by osmo_trxd_build_fin() */ + buf[1] = (br->trx_num & 0x3f) | (1 << 7); + rc = trxd_mts_build_req(&buf[2], br); + if (rc < 0) + return rc; + buf[3] = br->att; + buf[4] = (uint8_t)br->scpir; + buf[5] = buf[6] = buf[7] = 0x00; /* Spare */ + /* Some fields are not present in batched PDUs */ + if (first) { + buf[0] |= (pdu_ver & 0x0f) << 4; + msgb_put_u32(msg, br->fn); + } + break; + default: + return -ENOTSUP; + } + + /* copy hard-bits {0,1} */ + memcpy(msgb_put(msg, br->burst_len), + &br->burst[0], br->burst_len); + + return 0; +} + +/*! Finalize a datagram built by osmo_trxd_burst_{ind,req}_build(). + * \param[inout] msg message buffer holding the encoded PDU(s) + * \param[in] pdu_ver TRXD PDU version in use */ +void osmo_trxd_build_fin(struct msgb *msg, uint8_t pdu_ver) +{ + /* TRXDv2: unset BATCH.ind in the last PDU */ + if (pdu_ver >= 2 && msg->l2h != NULL) + msg->l2h[1] &= ~(1 << 7); +} + +/*********************************************************************** + * logging helpers + ***********************************************************************/ + +/*! Compose a human-readable representation of the given burst indication + * (for logging), store into a thread-local static buffer. + * \param[in] bi BURST.ind to get a human-readable representation of + * \returns pointer to a thread-local static buffer */ +const char *osmo_trxd_burst_ind_name(const struct osmo_trxd_burst_ind *bi) +{ + static __thread char buf[256]; + return osmo_trxd_burst_ind_name_buf(&buf[0], sizeof(buf), bi); +} + +/*! Compose a human-readable representation of the given burst indication + * (for logging), store into the given buffer. + * \param[out] buf output buffer to store the result + * \param[in] buf_len size of the output buffer + * \param[in] bi BURST.ind to get a human-readable representation of + * \returns pointer to the given buffer */ +char *osmo_trxd_burst_ind_name_buf(char *buf, size_t buf_len, + const struct osmo_trxd_burst_ind *bi) +{ + struct osmo_strbuf sb = { .buf = buf, .len = buf_len }; + + OSMO_STRBUF_PRINTF(sb, "%s tn=%u fn=%u", + (bi->flags & OSMO_TRXD_F_NOPE_IND) ? "NOPE.ind" : "BURST.ind", + bi->tn, bi->fn); + if (bi->flags & OSMO_TRXD_F_TRX_NUM) + OSMO_STRBUF_PRINTF(sb, " trx_num=%u", bi->trx_num); + OSMO_STRBUF_PRINTF(sb, " rssi=%d toa256=%d", bi->rssi, bi->toa256); + if (bi->flags & OSMO_TRXD_F_CI_CB) + OSMO_STRBUF_PRINTF(sb, " C/I=%d cB", bi->ci_cb); + if (bi->flags & OSMO_TRXD_F_NOPE_IND) + return buf; + if (bi->flags & OSMO_TRXD_F_MOD_TYPE) + OSMO_STRBUF_PRINTF(sb, " mod=%s", osmo_trxd_mod_type_name(bi->mod)); + if (bi->flags & OSMO_TRXD_F_TS_INFO) + OSMO_STRBUF_PRINTF(sb, " set=%u tsc=%u", bi->tsc_set, bi->tsc); + OSMO_STRBUF_PRINTF(sb, " burst_len=%zu", bi->burst_len); + + return buf; +} + +/*! Compose a human-readable representation of the given burst transmit + * request (for logging), store into a thread-local static buffer. + * \param[in] br BURST.req to get a human-readable representation of + * \returns pointer to a thread-local static buffer */ +const char *osmo_trxd_burst_req_name(const struct osmo_trxd_burst_req *br) +{ + static __thread char buf[256]; + return osmo_trxd_burst_req_name_buf(&buf[0], sizeof(buf), br); +} + +/*! Compose a human-readable representation of the given burst transmit + * request (for logging), store into the given buffer. + * \param[out] buf output buffer to store the result + * \param[in] buf_len size of the output buffer + * \param[in] br BURST.req to get a human-readable representation of + * \returns pointer to the given buffer */ +char *osmo_trxd_burst_req_name_buf(char *buf, size_t buf_len, + const struct osmo_trxd_burst_req *br) +{ + struct osmo_strbuf sb = { .buf = buf, .len = buf_len }; + + OSMO_STRBUF_PRINTF(sb, "BURST.req tn=%u fn=%u att=%u", br->tn, br->fn, br->att); + if (br->flags & OSMO_TRXD_F_TRX_NUM) + OSMO_STRBUF_PRINTF(sb, " trx_num=%u", br->trx_num); + if (br->flags & OSMO_TRXD_F_MOD_TYPE) + OSMO_STRBUF_PRINTF(sb, " mod=%s", osmo_trxd_mod_type_name(br->mod)); + if (br->flags & OSMO_TRXD_F_TS_INFO) + OSMO_STRBUF_PRINTF(sb, " set=%u tsc=%u", br->tsc_set, br->tsc); + OSMO_STRBUF_PRINTF(sb, " burst_len=%zu", br->burst_len); + + return buf; +} diff --git a/tests/Makefile.am b/tests/Makefile.am index d4589a4..60fd0b0 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,4 +1,5 @@ SUBDIRS = \ + libosmo-trx \ CommonLibs \ Transceiver52M \ $(NULL) diff --git a/tests/libosmo-trx/Makefile.am b/tests/libosmo-trx/Makefile.am new file mode 100644 index 0000000..162cbac --- /dev/null +++ b/tests/libosmo-trx/Makefile.am @@ -0,0 +1,34 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/libosmo-trx/include \ + -I$(top_builddir)/libosmo-trx/include \ + $(NULL) + +AM_CFLAGS = \ + -Wall -g \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOGSM_CFLAGS) \ + $(NULL) + +# Link statically, so that the tests can also call symbols hidden +# from the public library ABI (-export-symbols-regex). +AM_LDFLAGS = -static + +LDADD = \ + $(top_builddir)/libosmo-trx/src/libosmo-trx.la \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOGSM_LIBS) \ + $(NULL) + +EXTRA_DIST = \ + trxc_test.ok \ + trxd_test.ok \ + $(NULL) + +check_PROGRAMS = \ + trxc_test \ + trxd_test \ + $(NULL) + +trxc_test_SOURCES = trxc_test.c + +trxd_test_SOURCES = trxd_test.c diff --git a/tests/libosmo-trx/trxc_test.c b/tests/libosmo-trx/trxc_test.c new file mode 100644 index 0000000..82676a7 --- /dev/null +++ b/tests/libosmo-trx/trxc_test.c @@ -0,0 +1,162 @@ +/*! \file tests/trxc_test.c + * Regression test for the TRXC message codec. */ + +/* + * (C) 2026 by sysmocom - s.f.m.c. GmbH info@sysmocom.de + * Author: Vadim Yanitskiy vyanitskiy@sysmocom.de + * + * All Rights Reserved + * + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +#include <stdio.h> +#include <string.h> + +#include <osmocom/core/utils.h> + +#include <osmocom/trx/trxc.h> + +static void test_msg_parse(void) +{ + static const char * const messages[] = { + "CMD POWERON", + "CMD POWEROFF", + "CMD RXTUNE 890000", + "CMD SETSLOT 0 7 C5/S1", + "CMD ECHO", /* MS dialect */ + "CMD FAKE_TOA 128 0", /* fake_trx custom command */ + "RSP POWERON 0", + "RSP SETSLOT 0 0 7", + "RSP SETFORMAT 1 2", + "RSP NOMTXPOWER 0 23", + "RSP ERR 1", + "RSP MEASURE 0 890000 -85", + "IND CLOCK 402312", + /* malformed messages */ + "NDI KCOLC 123456", + "RSP NOSTATUS", + "CMD ", + "", + }; + + printf("=== %s ===\n", __func__); + + for (unsigned int i = 0; i < ARRAY_SIZE(messages); i++) { + struct osmo_trxc_msg msg; + int rc; + + rc = osmo_trxc_msg_parse(&msg, messages[i], strlen(messages[i])); + if (rc < 0) { + printf("'%s' -> rc=%d\n", messages[i], rc); + continue; + } + printf("'%s' -> type=%d cmd='%s' status=%d params='%s'\n", + messages[i], msg.type, msg.cmd, msg.status, msg.params); + /* re-encode and compare against the original */ + printf("\tre-encoded: '%s'\n", osmo_trxc_msg_name(&msg)); + OSMO_ASSERT(strcmp(osmo_trxc_msg_name(&msg), messages[i]) == 0); + } +} + +static void test_msg_build(void) +{ + static const struct osmo_trxc_msg messages[] = { + { .type = OSMO_TRXC_MT_CMD, .cmd = "POWERON" }, + { .type = OSMO_TRXC_MT_CMD, .cmd = "SETSLOT", .params = "0 7" }, + { .type = OSMO_TRXC_MT_RSP, .cmd = "POWERON", .status = 0 }, + { .type = OSMO_TRXC_MT_RSP, .cmd = "SETSLOT", .status = 1, .params = "0 7" }, + { .type = OSMO_TRXC_MT_IND, .cmd = "CLOCK", .params = "402312" }, + }; + char buf[OSMO_TRXC_MSG_BUF_SIZE]; + int rc; + + printf("=== %s ===\n", __func__); + + for (unsigned int i = 0; i < ARRAY_SIZE(messages); i++) { + rc = osmo_trxc_msg_build(buf, sizeof(buf), &messages[i]); + OSMO_ASSERT(rc > 0 && rc == (int)strlen(buf)); + printf("'%s' (rc=%d)\n", buf, rc); + } + + /* buffer too small */ + rc = osmo_trxc_msg_build(buf, 8, &messages[1]); + printf("build into a too small buffer: rc=%d\n", rc); + OSMO_ASSERT(rc < 0); +} + +static void test_params_scan(void) +{ + struct osmo_trxc_msg msg; + unsigned int tn, ts_type; + int rc; + + printf("=== %s ===\n", __func__); + + rc = osmo_trxc_msg_parse(&msg, "RSP SETSLOT 0 3 7", 17); + OSMO_ASSERT(rc == 0); + + rc = osmo_trxc_msg_params_scan(&msg, "%u %u", &tn, &ts_type); + printf("'%s' -> rc=%d tn=%u ts_type=%u\n", msg.params, rc, tn, ts_type); + OSMO_ASSERT(rc == 2 && tn == 3 && ts_type == 7); +} + +static void test_clk_ind(void) +{ + static const char * const messages[] = { + "IND CLOCK 402312", + "IND CLOCK 0", + "IND CLOCK 2715647", /* GSM_TDMA_HYPERFRAME - 1 */ + "IND CLOCK 2715648", /* GSM_TDMA_HYPERFRAME */ + "IND CLOCK", + "IND KCOLC 123", + "CMD CLOCK 123", + }; + char buf[OSMO_TRXC_MSG_BUF_SIZE]; + uint32_t fn; + int rc; + + printf("=== %s ===\n", __func__); + + for (unsigned int i = 0; i < ARRAY_SIZE(messages); i++) { + rc = osmo_trxc_clock_ind_parse(&fn, messages[i], strlen(messages[i])); + if (rc < 0) { + printf("'%s' -> rc=%d\n", messages[i], rc); + continue; + } + printf("'%s' -> fn=%u\n", messages[i], fn); + /* re-encode and compare against the original */ + rc = osmo_trxc_clock_ind_build(buf, sizeof(buf), fn); + OSMO_ASSERT(rc > 0); + OSMO_ASSERT(strcmp(buf, messages[i]) == 0); + } + + /* out of range TDMA fn */ + rc = osmo_trxc_clock_ind_build(buf, sizeof(buf), 2715648); + printf("build with out of range fn: rc=%d\n", rc); + OSMO_ASSERT(rc < 0); +} + +int main(int argc, char **argv) +{ + test_msg_parse(); + test_msg_build(); + test_params_scan(); + test_clk_ind(); + + printf("Done\n"); + return 0; +} diff --git a/tests/libosmo-trx/trxc_test.ok b/tests/libosmo-trx/trxc_test.ok new file mode 100644 index 0000000..9422d19 --- /dev/null +++ b/tests/libosmo-trx/trxc_test.ok @@ -0,0 +1,50 @@ +=== test_msg_parse === +'CMD POWERON' -> type=0 cmd='POWERON' status=0 params='' + re-encoded: 'CMD POWERON' +'CMD POWEROFF' -> type=0 cmd='POWEROFF' status=0 params='' + re-encoded: 'CMD POWEROFF' +'CMD RXTUNE 890000' -> type=0 cmd='RXTUNE' status=0 params='890000' + re-encoded: 'CMD RXTUNE 890000' +'CMD SETSLOT 0 7 C5/S1' -> type=0 cmd='SETSLOT' status=0 params='0 7 C5/S1' + re-encoded: 'CMD SETSLOT 0 7 C5/S1' +'CMD ECHO' -> type=0 cmd='ECHO' status=0 params='' + re-encoded: 'CMD ECHO' +'CMD FAKE_TOA 128 0' -> type=0 cmd='FAKE_TOA' status=0 params='128 0' + re-encoded: 'CMD FAKE_TOA 128 0' +'RSP POWERON 0' -> type=1 cmd='POWERON' status=0 params='' + re-encoded: 'RSP POWERON 0' +'RSP SETSLOT 0 0 7' -> type=1 cmd='SETSLOT' status=0 params='0 7' + re-encoded: 'RSP SETSLOT 0 0 7' +'RSP SETFORMAT 1 2' -> type=1 cmd='SETFORMAT' status=1 params='2' + re-encoded: 'RSP SETFORMAT 1 2' +'RSP NOMTXPOWER 0 23' -> type=1 cmd='NOMTXPOWER' status=0 params='23' + re-encoded: 'RSP NOMTXPOWER 0 23' +'RSP ERR 1' -> type=1 cmd='ERR' status=1 params='' + re-encoded: 'RSP ERR 1' +'RSP MEASURE 0 890000 -85' -> type=1 cmd='MEASURE' status=0 params='890000 -85' + re-encoded: 'RSP MEASURE 0 890000 -85' +'IND CLOCK 402312' -> type=2 cmd='CLOCK' status=0 params='402312' + re-encoded: 'IND CLOCK 402312' +'NDI KCOLC 123456' -> rc=-22 +'RSP NOSTATUS' -> rc=-22 +'CMD ' -> rc=-22 +'' -> rc=-22 +=== test_msg_build === +'CMD POWERON' (rc=11) +'CMD SETSLOT 0 7' (rc=15) +'RSP POWERON 0' (rc=13) +'RSP SETSLOT 1 0 7' (rc=17) +'IND CLOCK 402312' (rc=16) +build into a too small buffer: rc=-90 +=== test_params_scan === +'3 7' -> rc=2 tn=3 ts_type=7 +=== test_clk_ind === +'IND CLOCK 402312' -> fn=402312 +'IND CLOCK 0' -> fn=0 +'IND CLOCK 2715647' -> fn=2715647 +'IND CLOCK 2715648' -> rc=-34 +'IND CLOCK' -> rc=-22 +'IND KCOLC 123' -> rc=-22 +'CMD CLOCK 123' -> rc=-22 +build with out of range fn: rc=-34 +Done diff --git a/tests/libosmo-trx/trxd_test.c b/tests/libosmo-trx/trxd_test.c new file mode 100644 index 0000000..454de86 --- /dev/null +++ b/tests/libosmo-trx/trxd_test.c @@ -0,0 +1,412 @@ +/*! \file tests/trxd_test.c + * Regression test for the TRXD PDU codec. */ + +/* + * (C) 2026 by sysmocom - s.f.m.c. GmbH info@sysmocom.de + * Author: Vadim Yanitskiy vyanitskiy@sysmocom.de + * + * All Rights Reserved + * + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +#include <stdio.h> +#include <string.h> +#include <errno.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/utils.h> + +#include <osmocom/trx/trxd.h> + +/* MTS (Modulation and Training Sequence) field, for TRXDv1 and higher */ +int trxd_mts_parse_ind(struct osmo_trxd_burst_ind *bi, uint8_t mts); +int trxd_mts_parse_req(struct osmo_trxd_burst_req *br, uint8_t mts); +int trxd_mts_build_ind(uint8_t *mts, const struct osmo_trxd_burst_ind *bi); +int trxd_mts_build_req(uint8_t *mts, const struct osmo_trxd_burst_req *br); + +static void fill_burst_ind(struct osmo_trxd_burst_ind *bi, size_t burst_len) +{ + *bi = (struct osmo_trxd_burst_ind){ + .flags = OSMO_TRXD_F_MOD_TYPE + | OSMO_TRXD_F_TS_INFO + | OSMO_TRXD_F_CI_CB, + .fn = 1234567, + .tn = 5, + .toa256 = -512, + .rssi = -63, + .mod = (burst_len == OSMO_TRXD_BURST_LEN_8PSK) ? + OSMO_TRXD_MOD_T_8PSK : OSMO_TRXD_MOD_T_GMSK, + .tsc_set = 1, + .tsc = 7, + .ci_cb = -150, + .burst_len = burst_len, + }; + + for (size_t i = 0; i < burst_len; i++) + bi->burst[i] = (i & 1) ? -100 : 100; +} + +static void fill_burst_req(struct osmo_trxd_burst_req *br, size_t burst_len) +{ + *br = (struct osmo_trxd_burst_req){ + .flags = OSMO_TRXD_F_MOD_TYPE + | OSMO_TRXD_F_TS_INFO, + .fn = 2654321, + .tn = 2, + .att = 10, + .mod = (burst_len == OSMO_TRXD_BURST_LEN_8PSK) ? + OSMO_TRXD_MOD_T_8PSK : OSMO_TRXD_MOD_T_GMSK, + .tsc_set = 0, + .tsc = 3, + .burst_len = burst_len, + }; + + for (size_t i = 0; i < burst_len; i++) + br->burst[i] = (i & 1); +} + +static void test_burst_ind(uint8_t pdu_ver, size_t burst_len) +{ + struct osmo_trxd_parse_state st; + struct osmo_trxd_burst_ind bi, bi2; + struct msgb *msg = msgb_alloc(4096, "ind"); + int rc; + + printf("=== %s(v%u, burst_len=%zu) ===\n", + __func__, pdu_ver, burst_len); + + fill_burst_ind(&bi, burst_len); + rc = osmo_trxd_burst_ind_build(msg, pdu_ver, &bi); + OSMO_ASSERT(rc == 0); + osmo_trxd_build_fin(msg, pdu_ver); + + printf("build: %s\n", osmo_trxd_burst_ind_name(&bi)); + printf("datagram (%u bytes): %s...\n", msgb_length(msg), + osmo_hexdump_nospc(msgb_data(msg), OSMO_MIN(16, msgb_length(msg)))); + + osmo_trxd_parse_state_init(&st); + rc = osmo_trxd_burst_ind_parse(&st, &bi2, msgb_data(msg), msgb_length(msg)); + OSMO_ASSERT(rc == (int)msgb_length(msg)); + printf("parse: %s\n", osmo_trxd_burst_ind_name(&bi2)); + + OSMO_ASSERT(bi2.fn == bi.fn && bi2.tn == bi.tn); + OSMO_ASSERT(bi2.rssi == bi.rssi && bi2.toa256 == bi.toa256); + OSMO_ASSERT(bi2.burst_len == bi.burst_len); + OSMO_ASSERT(memcmp(bi2.burst, bi.burst, bi.burst_len) == 0); + if (pdu_ver >= 1) { + OSMO_ASSERT(bi2.mod == bi.mod); + OSMO_ASSERT(bi2.tsc_set == bi.tsc_set && bi2.tsc == bi.tsc); + OSMO_ASSERT(bi2.ci_cb == bi.ci_cb); + } + + msgb_free(msg); +} + +static void test_burst_ind_nope(uint8_t pdu_ver) +{ + struct osmo_trxd_parse_state st; + struct osmo_trxd_burst_ind bi, bi2; + struct msgb *msg = msgb_alloc(4096, "nope"); + int rc; + + printf("=== %s(v%u) ===\n", __func__, pdu_ver); + + fill_burst_ind(&bi, 0); + bi.flags = OSMO_TRXD_F_NOPE_IND | OSMO_TRXD_F_CI_CB; + + rc = osmo_trxd_burst_ind_build(msg, pdu_ver, &bi); + if (pdu_ver == 0) { + /* TRXDv0 cannot carry NOPE.ind */ + printf("build: rc=%d (expected -ENOTSUP)\n", rc); + OSMO_ASSERT(rc == -ENOTSUP); + msgb_free(msg); + return; + } + OSMO_ASSERT(rc == 0); + osmo_trxd_build_fin(msg, pdu_ver); + printf("build: %s\n", osmo_trxd_burst_ind_name(&bi)); + + osmo_trxd_parse_state_init(&st); + rc = osmo_trxd_burst_ind_parse(&st, &bi2, msgb_data(msg), msgb_length(msg)); + OSMO_ASSERT(rc == (int)msgb_length(msg)); + printf("parse: %s\n", osmo_trxd_burst_ind_name(&bi2)); + + OSMO_ASSERT(bi2.flags & OSMO_TRXD_F_NOPE_IND); + OSMO_ASSERT(bi2.burst_len == 0); + + msgb_free(msg); +} + +static void test_burst_ind_v0_legacy_padding(void) +{ + struct osmo_trxd_parse_state st; + struct osmo_trxd_burst_ind bi, bi2; + struct msgb *msg = msgb_alloc(4096, "legacy"); + int rc; + + printf("=== %s ===\n", __func__); + + fill_burst_ind(&bi, OSMO_TRXD_BURST_LEN_GMSK); + rc = osmo_trxd_burst_ind_build(msg, 0, &bi); + OSMO_ASSERT(rc == 0); + + /* a legacy transceiver may append two garbage bytes */ + msgb_put_u16(msg, 0xdead); + + osmo_trxd_parse_state_init(&st); + rc = osmo_trxd_burst_ind_parse(&st, &bi2, msgb_data(msg), msgb_length(msg)); + printf("parse: rc=%d (%u bytes incl. padding)\n", rc, msgb_length(msg)); + OSMO_ASSERT(rc == (int)msgb_length(msg)); + OSMO_ASSERT(bi2.burst_len == OSMO_TRXD_BURST_LEN_GMSK); + + msgb_free(msg); +} + +static void test_burst_ind_batch(uint8_t pdu_ver) +{ + struct osmo_trxd_parse_state st; + struct osmo_trxd_burst_ind bi, bi2; + struct msgb *msg; + const uint8_t *buf; + size_t buf_len; + unsigned int i; + int rc; + + /* PDU batching is only supported by TRXDv2 and higher */ + if (pdu_ver < 2) + return; + + printf("=== %s(v%u) ===\n", __func__, pdu_ver); + + msg = msgb_alloc(4096, "batch"); + + /* batch a normal burst + a NOPE.ind for another timeslot */ + fill_burst_ind(&bi, OSMO_TRXD_BURST_LEN_GMSK); + bi.trx_num = 1; + bi.flags |= OSMO_TRXD_F_TRX_NUM; + rc = osmo_trxd_burst_ind_build(msg, pdu_ver, &bi); + OSMO_ASSERT(rc == 0); + + bi.tn = 6; + bi.flags = OSMO_TRXD_F_NOPE_IND | OSMO_TRXD_F_TRX_NUM; + bi.burst_len = 0; + rc = osmo_trxd_burst_ind_build(msg, pdu_ver, &bi); + OSMO_ASSERT(rc == 0); + + osmo_trxd_build_fin(msg, pdu_ver); + printf("datagram (%u bytes)\n", msgb_length(msg)); + + osmo_trxd_parse_state_init(&st); + buf = msgb_data(msg); + buf_len = msgb_length(msg); + for (i = 0; buf_len > 0; i++) { + rc = osmo_trxd_burst_ind_parse(&st, &bi2, buf, buf_len); + OSMO_ASSERT(rc > 0); + printf("parse[%u]: %s\n", i, osmo_trxd_burst_ind_name(&bi2)); + /* the batched PDU inherits the TDMA fn of the first one */ + OSMO_ASSERT(bi2.fn == 1234567); + buf += rc; + buf_len -= rc; + /* BATCH.ind shall be set on all PDUs but the last one */ + OSMO_ASSERT(!!(bi2.flags & OSMO_TRXD_F_BATCH_IND) == (buf_len > 0)); + } + OSMO_ASSERT(i == 2); + + msgb_free(msg); +} + +static void test_burst_req(uint8_t pdu_ver, size_t burst_len) +{ + struct osmo_trxd_parse_state st; + struct osmo_trxd_burst_req br, br2; + struct msgb *msg = msgb_alloc(4096, "req"); + int rc; + + printf("=== %s(v%u, burst_len=%zu) ===\n", __func__, pdu_ver, burst_len); + + fill_burst_req(&br, burst_len); + rc = osmo_trxd_burst_req_build(msg, pdu_ver, &br); + OSMO_ASSERT(rc == 0); + osmo_trxd_build_fin(msg, pdu_ver); + + printf("build: %s\n", osmo_trxd_burst_req_name(&br)); + printf("datagram (%u bytes): %s...\n", msgb_length(msg), + osmo_hexdump_nospc(msgb_data(msg), OSMO_MIN(16, msgb_length(msg)))); + + osmo_trxd_parse_state_init(&st); + rc = osmo_trxd_burst_req_parse(&st, &br2, msgb_data(msg), msgb_length(msg)); + OSMO_ASSERT(rc == (int)msgb_length(msg)); + printf("parse: %s\n", osmo_trxd_burst_req_name(&br2)); + + OSMO_ASSERT(br2.fn == br.fn && br2.tn == br.tn); + OSMO_ASSERT(br2.att == br.att); + OSMO_ASSERT(br2.burst_len == br.burst_len); + OSMO_ASSERT(memcmp(br2.burst, br.burst, br.burst_len) == 0); + if (pdu_ver >= 2) { + OSMO_ASSERT(br2.mod == br.mod); + OSMO_ASSERT(br2.tsc_set == br.tsc_set && br2.tsc == br.tsc); + } + + msgb_free(msg); +} + +static void test_burst_req_batch(uint8_t pdu_ver) +{ + struct osmo_trxd_parse_state st; + struct osmo_trxd_burst_req br, br2; + struct msgb *msg; + const uint8_t *buf; + size_t buf_len; + unsigned int i; + int rc; + + /* PDU batching is only supported by TRXDv2 and higher */ + if (pdu_ver < 2) + return; + + printf("=== %s(v%u) ===\n", __func__, pdu_ver); + + msg = msgb_alloc(4096, "batch"); + + for (i = 0; i < 3; i++) { + fill_burst_req(&br, OSMO_TRXD_BURST_LEN_GMSK); + br.tn = i; + rc = osmo_trxd_burst_req_build(msg, pdu_ver, &br); + OSMO_ASSERT(rc == 0); + } + osmo_trxd_build_fin(msg, pdu_ver); + printf("datagram (%u bytes)\n", msgb_length(msg)); + + osmo_trxd_parse_state_init(&st); + buf = msgb_data(msg); + buf_len = msgb_length(msg); + for (i = 0; buf_len > 0; i++) { + rc = osmo_trxd_burst_req_parse(&st, &br2, buf, buf_len); + OSMO_ASSERT(rc > 0); + printf("parse[%u]: %s\n", i, osmo_trxd_burst_req_name(&br2)); + OSMO_ASSERT(br2.tn == i && br2.fn == 2654321); + buf += rc; + buf_len -= rc; + OSMO_ASSERT(!!(br2.flags & OSMO_TRXD_F_BATCH_IND) == (buf_len > 0)); + } + OSMO_ASSERT(i == 3); + + msgb_free(msg); +} + +static void test_mts(void) +{ + static const uint8_t mts_bytes[] = { + 0x80, /* NOPE.ind */ + 0x07, /* GMSK, set 0, tsc 7 */ + 0x1d, /* GMSK, set 3, tsc 5 */ + 0x2b, /* 8-PSK, set 1, tsc 3 */ + 0x31, /* GMSK, Access Burst, tsc 1 */ + 0x66, /* AQPSK, set 0, tsc 6 */ + 0x51, /* invalid */ + }; + + printf("=== %s ===\n", __func__); + + for (unsigned int i = 0; i < ARRAY_SIZE(mts_bytes); i++) { + struct osmo_trxd_burst_ind bi = { }; + uint8_t mts = mts_bytes[i]; + + if (trxd_mts_parse_ind(&bi, mts) < 0) { + printf("trxd_mts_parse_ind(0x%02x) failed\n", mts); + continue; + } + printf("trxd_mts_parse_ind(0x%02x): flags=0x%02x mod=%s set=%u tsc=%u", + mts, bi.flags, osmo_trxd_mod_type_name(bi.mod), + bi.tsc_set, bi.tsc); + + /* re-encode and compare */ + OSMO_ASSERT(trxd_mts_build_ind(&mts, &bi) == 0); + printf(" (re-encoded: 0x%02x)\n", mts); + OSMO_ASSERT(mts == mts_bytes[i]); + } +} + +static void test_parse_errors(void) +{ + struct osmo_trxd_parse_state st; + struct osmo_trxd_burst_ind bi; + struct osmo_trxd_burst_req br; + uint8_t buf[512]; + int rc; + + printf("=== %s ===\n", __func__); + + /* empty buffer */ + osmo_trxd_parse_state_init(&st); + rc = osmo_trxd_burst_ind_parse(&st, &bi, buf, 0); + printf("ind, empty buffer: rc=%d\n", rc); + OSMO_ASSERT(rc < 0); + + /* unknown PDU version */ + memset(buf, 0, sizeof(buf)); + buf[0] = (0x0f << 4); + osmo_trxd_parse_state_init(&st); + rc = osmo_trxd_burst_ind_parse(&st, &bi, buf, sizeof(buf)); + printf("ind, PDU version 15: rc=%d\n", rc); + OSMO_ASSERT(rc == -ENOTSUP); + + /* TRXDv0 with odd burst length */ + memset(buf, 0, sizeof(buf)); + osmo_trxd_parse_state_init(&st); + rc = osmo_trxd_burst_ind_parse(&st, &bi, buf, 8 + 100); + printf("ind, v0 with odd burst length: rc=%d\n", rc); + OSMO_ASSERT(rc == -EINVAL); + + /* TRXDv1 header, but no burst bits */ + memset(buf, 0, sizeof(buf)); + buf[0] = (1 << 4); + osmo_trxd_parse_state_init(&st); + rc = osmo_trxd_burst_ind_parse(&st, &bi, buf, 11); + printf("ind, v1 without burst bits: rc=%d\n", rc); + OSMO_ASSERT(rc == -EINVAL); + + /* illegal TDMA fn */ + memset(buf, 0, sizeof(buf)); + osmo_store32be(0xffffffff, buf + 1); + osmo_trxd_parse_state_init(&st); + rc = osmo_trxd_burst_req_parse(&st, &br, buf, 6 + 148); + printf("req, illegal TDMA fn: rc=%d\n", rc); + OSMO_ASSERT(rc == -EINVAL); +} + +int main(int argc, char **argv) +{ + for (uint8_t pdu_ver = 0; pdu_ver <= OSMO_TRXD_PDU_VER_MAX; pdu_ver++) { + test_burst_req(pdu_ver, OSMO_TRXD_BURST_LEN_GMSK); + test_burst_req(pdu_ver, OSMO_TRXD_BURST_LEN_8PSK); + test_burst_req_batch(pdu_ver); + + test_burst_ind(pdu_ver, OSMO_TRXD_BURST_LEN_GMSK); + test_burst_ind(pdu_ver, OSMO_TRXD_BURST_LEN_8PSK); + test_burst_ind_batch(pdu_ver); + test_burst_ind_nope(pdu_ver); + } + + test_burst_ind_v0_legacy_padding(); + + test_mts(); + test_parse_errors(); + + printf("Done\n"); + return 0; +} diff --git a/tests/libosmo-trx/trxd_test.ok b/tests/libosmo-trx/trxd_test.ok new file mode 100644 index 0000000..e4f22da --- /dev/null +++ b/tests/libosmo-trx/trxd_test.ok @@ -0,0 +1,82 @@ +=== test_burst_req(v0, burst_len=148) === +build: BURST.req tn=2 fn=2654321 att=10 mod=GMSK set=0 tsc=3 burst_len=148 +datagram (154 bytes): 02002880710a00010001000100010001... +parse: BURST.req tn=2 fn=2654321 att=10 mod=GMSK burst_len=148 +=== test_burst_req(v0, burst_len=444) === +build: BURST.req tn=2 fn=2654321 att=10 mod=8-PSK set=0 tsc=3 burst_len=444 +datagram (450 bytes): 02002880710a00010001000100010001... +parse: BURST.req tn=2 fn=2654321 att=10 mod=8-PSK burst_len=444 +=== test_burst_ind(v0, burst_len=148) === +build: BURST.ind tn=5 fn=1234567 rssi=-63 toa256=-512 C/I=-150 cB mod=GMSK set=1 tsc=7 burst_len=148 +datagram (156 bytes): 050012d6873ffe001be31be31be31be3... +parse: BURST.ind tn=5 fn=1234567 rssi=-63 toa256=-512 mod=GMSK burst_len=148 +=== test_burst_ind(v0, burst_len=444) === +build: BURST.ind tn=5 fn=1234567 rssi=-63 toa256=-512 C/I=-150 cB mod=8-PSK set=1 tsc=7 burst_len=444 +datagram (452 bytes): 050012d6873ffe001be31be31be31be3... +parse: BURST.ind tn=5 fn=1234567 rssi=-63 toa256=-512 mod=8-PSK burst_len=444 +=== test_burst_ind_nope(v0) === +build: rc=-95 (expected -ENOTSUP) +=== test_burst_req(v1, burst_len=148) === +build: BURST.req tn=2 fn=2654321 att=10 mod=GMSK set=0 tsc=3 burst_len=148 +datagram (154 bytes): 12002880710a00010001000100010001... +parse: BURST.req tn=2 fn=2654321 att=10 mod=GMSK burst_len=148 +=== test_burst_req(v1, burst_len=444) === +build: BURST.req tn=2 fn=2654321 att=10 mod=8-PSK set=0 tsc=3 burst_len=444 +datagram (450 bytes): 12002880710a00010001000100010001... +parse: BURST.req tn=2 fn=2654321 att=10 mod=8-PSK burst_len=444 +=== test_burst_ind(v1, burst_len=148) === +build: BURST.ind tn=5 fn=1234567 rssi=-63 toa256=-512 C/I=-150 cB mod=GMSK set=1 tsc=7 burst_len=148 +datagram (159 bytes): 150012d6873ffe000fff6a1be31be31b... +parse: BURST.ind tn=5 fn=1234567 rssi=-63 toa256=-512 C/I=-150 cB mod=GMSK set=1 tsc=7 burst_len=148 +=== test_burst_ind(v1, burst_len=444) === +build: BURST.ind tn=5 fn=1234567 rssi=-63 toa256=-512 C/I=-150 cB mod=8-PSK set=1 tsc=7 burst_len=444 +datagram (455 bytes): 150012d6873ffe002fff6a1be31be31b... +parse: BURST.ind tn=5 fn=1234567 rssi=-63 toa256=-512 C/I=-150 cB mod=8-PSK set=1 tsc=7 burst_len=444 +=== test_burst_ind_nope(v1) === +build: NOPE.ind tn=5 fn=1234567 rssi=-63 toa256=-512 C/I=-150 cB +parse: NOPE.ind tn=5 fn=1234567 rssi=-63 toa256=-512 C/I=-150 cB +=== test_burst_req(v2, burst_len=148) === +build: BURST.req tn=2 fn=2654321 att=10 mod=GMSK set=0 tsc=3 burst_len=148 +datagram (160 bytes): 2200030a000000000028807100010001... +parse: BURST.req tn=2 fn=2654321 att=10 trx_num=0 mod=GMSK set=0 tsc=3 burst_len=148 +=== test_burst_req(v2, burst_len=444) === +build: BURST.req tn=2 fn=2654321 att=10 mod=8-PSK set=0 tsc=3 burst_len=444 +datagram (456 bytes): 2200230a000000000028807100010001... +parse: BURST.req tn=2 fn=2654321 att=10 trx_num=0 mod=8-PSK set=0 tsc=3 burst_len=444 +=== test_burst_req_batch(v2) === +datagram (472 bytes) +parse[0]: BURST.req tn=0 fn=2654321 att=10 trx_num=0 mod=GMSK set=0 tsc=3 burst_len=148 +parse[1]: BURST.req tn=1 fn=2654321 att=10 trx_num=0 mod=GMSK set=0 tsc=3 burst_len=148 +parse[2]: BURST.req tn=2 fn=2654321 att=10 trx_num=0 mod=GMSK set=0 tsc=3 burst_len=148 +=== test_burst_ind(v2, burst_len=148) === +build: BURST.ind tn=5 fn=1234567 rssi=-63 toa256=-512 C/I=-150 cB mod=GMSK set=1 tsc=7 burst_len=148 +datagram (160 bytes): 25000f3ffe00ff6a0012d6871be31be3... +parse: BURST.ind tn=5 fn=1234567 trx_num=0 rssi=-63 toa256=-512 C/I=-150 cB mod=GMSK set=1 tsc=7 burst_len=148 +=== test_burst_ind(v2, burst_len=444) === +build: BURST.ind tn=5 fn=1234567 rssi=-63 toa256=-512 C/I=-150 cB mod=8-PSK set=1 tsc=7 burst_len=444 +datagram (456 bytes): 25002f3ffe00ff6a0012d6871be31be3... +parse: BURST.ind tn=5 fn=1234567 trx_num=0 rssi=-63 toa256=-512 C/I=-150 cB mod=8-PSK set=1 tsc=7 burst_len=444 +=== test_burst_ind_batch(v2) === +datagram (168 bytes) +parse[0]: BURST.ind tn=5 fn=1234567 trx_num=1 rssi=-63 toa256=-512 C/I=-150 cB mod=GMSK set=1 tsc=7 burst_len=148 +parse[1]: NOPE.ind tn=6 fn=1234567 trx_num=1 rssi=-63 toa256=-512 C/I=-150 cB +=== test_burst_ind_nope(v2) === +build: NOPE.ind tn=5 fn=1234567 rssi=-63 toa256=-512 C/I=-150 cB +parse: NOPE.ind tn=5 fn=1234567 trx_num=0 rssi=-63 toa256=-512 C/I=-150 cB +=== test_burst_ind_v0_legacy_padding === +parse: rc=158 (158 bytes incl. padding) +=== test_mts === +trxd_mts_parse_ind(0x80): flags=0x01 mod=GMSK set=0 tsc=0 (re-encoded: 0x80) +trxd_mts_parse_ind(0x07): flags=0x06 mod=GMSK set=0 tsc=7 (re-encoded: 0x07) +trxd_mts_parse_ind(0x1d): flags=0x06 mod=GMSK set=3 tsc=5 (re-encoded: 0x1d) +trxd_mts_parse_ind(0x2b): flags=0x06 mod=8-PSK set=1 tsc=3 (re-encoded: 0x2b) +trxd_mts_parse_ind(0x31): flags=0x86 mod=GMSK set=0 tsc=1 (re-encoded: 0x31) +trxd_mts_parse_ind(0x66): flags=0x06 mod=AQPSK set=0 tsc=6 (re-encoded: 0x66) +trxd_mts_parse_ind(0x51) failed +=== test_parse_errors === +ind, empty buffer: rc=-22 +ind, PDU version 15: rc=-95 +ind, v0 with odd burst length: rc=-22 +ind, v1 without burst bits: rc=-22 +req, illegal TDMA fn: rc=-22 +Done diff --git a/tests/testsuite.at b/tests/testsuite.at index 0ac870d..6e72c81 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -49,3 +49,15 @@ cat $abs_srcdir/Transceiver52M/convolve_test.ok > expout AT_CHECK([$abs_top_builddir/tests/Transceiver52M/convolve_test], [], [expout], []) AT_CLEANUP + +AT_SETUP([trxc_test]) +AT_KEYWORDS([trxc_test]) +cat $abs_srcdir/libosmo-trx/trxc_test.ok > expout +AT_CHECK([$abs_top_builddir/tests/libosmo-trx/trxc_test], [], [expout], []) +AT_CLEANUP + +AT_SETUP([trxd_test]) +AT_KEYWORDS([trxd_test]) +cat $abs_srcdir/libosmo-trx/trxd_test.ok > expout +AT_CHECK([$abs_top_builddir/tests/libosmo-trx/trxd_test], [], [expout], []) +AT_CLEANUP